Winium.Desktop: Selenium для desktop-приложений под Windows


    Привет, меня зовут Глеб, и я занимаюсь автоматизацией тестирования в 2ГИС. Больше года назад я писал о нашем инструменте Cruciatus — с его помощью мы тестируем UI desktop-приложений под Windows.

    Cruciatus отлично решает задачу доступа к контролам, но тесты пишутся строго на C#. Это мешает шарить знания и опыт между тестировщиками под разные платформы: mobile, web и desktop.

    Решение мы увидели в Selenium — пожалуй, самом известном инструменте для автоматизации тестирования. В этой статье я расскажу, как мы скрестили Cruciatus и Selenium и как тестировать интерфейс Windows desktop-приложений с помощью привычных Selenium-биндингов.

    Почему не хватило Cruciatus


    Почти все команды, которые разрабатывают внутренние продукты 2ГИС, использовали Cruciatus. И каждая из этих команд предлагала улучшения для инструмента. Чтобы всем угодить, мы переработали логику Cruciatus вплоть до поломки обратной совместимости. Это было больно, но полезно.

    Также мы отказались от классов Mouse & Keyboard из CodedUI, чтобы убрать зависимость от библиотек, поставляемых вместе с VisualStudio. А значит научились собирать проект на публичных CI серверах вроде AppVeyor.

    В итоге мы сделали удобный и самодостаточный инструмент, который решает все наши задачи по доступу к элементам desktop-приложений под Windows. Но вместе с тем у Cruciatus’а осталось одно серьёзное ограничение — диктатура C#.

    Как пришли к Selenium


    Selenium — это набор инструментов и библиотек для автоматизации тестирования приложений в браузерах. Сердцем проекта Selenium можно считать Json Wire Protocol (JSWP) — единый REST-протокол взаимодействия между тестами и тестируемым приложением.

    Плюсы единого протокола:
    • тесты работают на всех платформах и во всех браузерах;
    • разработчики пишут их на любом языке. Selenium-биндинги уже есть для Python, C#, Java, JavaScript, Ruby, PHP, Perl. Для других языков биндинги можно разработать самому;
    • одни и те же команды работают для разных типов приложений. На уровне тестов клик по кнопке в веб-интерфейсе не отличается от клика в мобильном интерфейсе.

    Эти преимущества мы решили использовать при автоматизации тестирования desktop-приложений — так же, как мы используем их для веба.

    Что такое Winium.Desktop


    Чтобы уйти от диктатуры C#, мы написали для Cruciatus совместимую с Selenium обёртку. Параллельно в компании создавали такой же Selenium-совместимый инструмент для автотестов, но под мобильные Windows-приложения. Мы объединили эти наработки под общим именем Winium, а свой инструмент назвали Winium.Desktop.

    По сути Winium.Desktop — это http-клиент. Он реализует протокол JSWP и использует Cruciatus для работы с элементами пользовательского интерфейса. Фактически это реализация WebDriver для desktop-приложений под Windows.

    С Winium.Desktop мы используем привычные Selenium-биндинги для тестирования desktop-приложений под Windows.

    Как работать с Winium.Desktop


    Чтобы работать с Winium.Desktop, скачайте последний релиз драйвера с github и запустите его с правами администратора. Это не обязательное условие, но в противном случае вы рано или поздно столкнетесь с Access denied либо от операционной системы, либо от приложения.

    Всё готово. Теперь берите свой любимый язык, любимую IDE и пишите тесты так же, как вы бы делали это для веб-приложения. А если вы мало знакомы с Selenium, почитайте любую документацию. Советуем начать с Selenium Python Bindings.

    Единственное отличие от тестирования веб-приложений: чтобы узнать локаторы элементов, воспользуйтесь инструментами типа UISpy или UI Automation Verify. Подробней о них поговорим дальше.

    Когда запустите тесты, не трогайте мышку и клавиатуру: сдвинется курсор, сменится фокус, и автоматизационная магия не случится.

    Что умеет драйвер


    При реализации Json Wire Protocol мы основывались на двух черновиках протокола, используемого WebDriver: JsonWireProtocol и более свежего webdriver-spec.

    Сейчас мы реализовали большинство наиболее востребованных команд.

    Полный список
    Команда Запрос
    NewSession POST /session
    FindElement POST /session/:sessionId/element
    FindChildElement POST /session/:sessionId/element/:id/element
    ClickElement POST /session/:sessionId/element/:id/click
    SendKeysToElement POST /session/:sessionId/element/:id/value
    GetElementText GET /session/:sessionId/element/:id/text
    GetElementAttribute GET /session/:sessionId/element/:id/attribute/:name
    Quit DELETE /session/:sessionId
    ClearElement POST /session/:sessionId/element/:id/clear
    Close DELETE /session/:sessionId/window
    ElementEquals GET /session/:sessionId/element/:id/equals/:other
    ExecuteScript POST /session/:sessionId/execute
    FindChildElements POST /session/:sessionId/element/:id/elements
    FindElements POST /session/:sessionId/elements
    GetActiveElement POST /session/:sessionId/element/active
    GetElementSize GET /session/:sessionId/element/:id/size
    ImplicitlyWait POST /session/:sessionId/timeouts/implicit_wait
    IsElementDisplayed GET /session/:sessionId/element/:id/displayed
    IsElementEnabled GET /session/:sessionId/element/:id/enabled
    IsElementSelected GET /session/:sessionId/element/:id/selected
    MouseClick POST /session/:sessionId/click
    MouseDoubleClick POST /session/:sessionId/doubleclick
    MouseMoveTo POST /session/:sessionId/moveto
    Screenshot GET /session/:sessionId/screenshot
    SendKeysToActiveElement POST /session/:sessionId/keys
    Status GET /status
    SubmitElement POST /session/:sessionId/element/:id/submit


    Пример использования самых простых команд (Python):
    1. Запускаем приложение командой NewSession при создании драйвера:
      driver = webdriver.Remote(
         command_executor='http://localhost:9999',
         desired_capabilities={
             "app": r"C:/windows/system32/calc.exe"
         })
      
    2. Находим окно тестируемого приложения командой FindElement:
      window = driver.find_element_by_class_name('CalcFrame')
      
    3. Находим элемент в окне командой FindChildElement:
      result_field = window.find_element_by_id('150')
      
    4. Получаем свойство элемента командой GetElementAttribute:
      result_field.get_attribute('Name')
      
    5. Закрываем приложение командой Quit:
      driver.quit()
      

    Тоже самое, только C#:
    1. var dc = new DesiredCapabilities();
      dc.SetCapability("app", @"C:/windows/system32/calc.exe");
      var driver = new RemoteWebDriver(new Uri("http://localhost:9999"), dc);
      
      var window = driver.FindElementByClassName("CalcFrame");
      resultField = window.FindElement(By.Id("150"));
      resultField.GetAttribute("Name");
      
      driver.Quit();
      

    Больше о поддерживаемых командах читайте на вики в репозитории проекта.

    Работа с элементами


    Чтобы управлять элементами в тестах, эти элементы сначала нужно найти. Элементы ищут по локаторам — свойствам, уникально идентифицирующим элементы.

    Чтобы узнать локаторы элементов, воспользуйтесь UISpy, его более новой версией Inspect или UIAVerify. Последние два устанавливаются вместе с VisualStudio и находятся в директории “%PROGRAMFILES(X86)%\Windows Kits\8.1\bin\” (возможно отличие в версии Windows Kits).

    Запускать любой из этих инструментов желательно от администратора.
    Советуем использовать UIAVerify. На наш взгляд он самый производительный и удобный.

    Хотя Cruciatus умеет искать элементы по любому свойству из класса AutomationElementIdentifiers, Winium.Desktop поддерживает только три стратегии поиска (типа локаторов):
    • AutomationProperties.AutomationId;
    • Name;
    • ClassName.

    Корневым элементом при поиске является Рабочий стол. Советуем сначала находить окно тестируемого приложения (FindElement) и только потом элементы внутри него (FindChildElement).

    При необходимости расширения возможных стратегий поиска пишите нам или сразу создавайте новое issue.

    Пример. Код, который пишет код


    from selenium import webdriver
    from selenium.webdriver import ActionChains
    import time
    
    driver = webdriver.Remote(
        command_executor='http://localhost:9999',
        desired_capabilities={
            'app': r'C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\IDE\devenv.exe'
        })
    
    window = driver.find_element_by_id('VisualStudioMainWindow')
    menu_bar = window.find_element_by_id('MenuBar')
    
    menu_bar.click()
    menu_bar.find_element_by_name('File').click()
    menu_bar.find_element_by_name('New').click()
    menu_bar.find_element_by_name('Project...').click()
    
    project_name = 'SpecialForHabrahabr-' + str(time.time())
    new_project_win = window.find_element_by_name('New Project')
    new_project_win.find_element_by_id('Windows Desktop').click()
    new_project_win.find_element_by_name('Console Application').click()
    new_project_win.find_element_by_id('txt_Name').send_keys(project_name)
    new_project_win.find_element_by_id('btn_OK').click()
    
    text_view = window.find_element_by_id('WpfTextView')
    text_view.send_keys('using System;{ENTER}{ENTER}')
    
    actions = ActionChains(driver)
    actions.send_keys('namespace Habrahabr{ENTER}')
    actions.send_keys('{{}{ENTER}')
    actions.send_keys('class Program{ENTER}')
    actions.send_keys('{{}{ENTER}')
    actions.send_keys('static void Main{(}string{[}{]} args{)}{ENTER}')
    actions.send_keys('{{}{ENTER}')
    actions.send_keys('Console.WriteLine{(}\"Hello Habrahabr\"{)};')
    
    actions.send_keys('^{F5}')
    actions.perform()
    


    Continuous Integration для Winium.Desktop-тестов


    В CI-проект тесты, управляемые Winium.Desktop драйвером, включаются стандартным способом. Однако для их выполнения нужна реальная или виртуальная машина. Когда настраиваете такую машину, соблюдайте несколько формальностей.

    Во-первых, в системе требуется так называемый активный рабочий стол. Он существует на вашем компьютере или при RDP подключении. Причём окно этого подключения нельзя сворачивать. Для автоматического создания активного рабочего стола используйте Autologon.

    Во-вторых, активный рабочий стол необходимо поддерживать активным. Для этого настройте электропитание на машине (из-под пользователя, на которого настроен Autologon). Отмените отключение дисплея и сон. Если используете RDP-подключение, по его завершению перезагрузите машину. Это восстановит активный рабочий стол. Чтобы подсматривать за выполнением тестов, используйте System Center App Controller или VNC.

    В-третьих, агент вашего билд-сервера обязательно должен работать как процесс, а не как служба. Это ограничение связано с тем, что в Windows служба не имеет прав запускать пользовательский интерфейс приложения.

    Итого: настройте Autologon, держите рабочий стол активным и запускайте агент билд-сервера как процесс.

    Заключение


    Проект Winium.Desktop позволил нам стереть грань между автоматизацией тестирования пользовательского интерфейса web- и desktop-приложений.

    Теперь тестировщики свободно обмениваются опытом и практиками использования Selenium. А автотесты, написанные под эти совсем разные платформы, выполняются в одной облачной инфраструктуре, построенной на базе Selenium-Grid.

    Еще раз ссылка на репозиторий и другие opensource продукты 2ГИС.
    • +15
    • 25,1k
    • 6
    2ГИС 201,99
    Карта города и справочник предприятий
    Поделиться публикацией
    Похожие публикации
    Комментарии 6
    • 0
      Пытался использовать, но пришлось бросить: задача была

      DragFileFromExplorerToBrowser(string fileName)
      {
          var explorerWindow = OpenExplorerInFolder(defaultFolder); // запускаем explorer.exe и открываем нужную нам папку
          var chromeWindow = OpenChrome(testPageAddress);          // запускаем хром на странице с тестом
          AllignWindows();                                        // чтобы окна не перекрывали друг друга и не были закрыты другими окнами
          var fileToDrag = explorerWindow.FindFileInFilelist(fileName); // получаем элемент файла который мы будем перетаскивать
          var dropZone = chromeWindow.getContentCenter();       // получаем центр тестовой страницы
          DragAndDrop(fileToDrag, dropZone)                    // перетаскиваем файл в центр страницы
      }


      и вот буквально на каждом из трёх последних шагов было по проблеме.
      Приходилось ли вам решать схожие задачи?
      Как решали?
      • 0
        Буквально пару раз сталкивались с задачей автоматизации подобного кросс-продуктового сценария, но затрат получалось куда больше, чем полезности и мы не брались за это :)
        • +1
          explorer.exe можно автоматизировать с помощью pywinauto. Вот здесь пример, как в Control Panel залезть через explorer.exe. Есть пример на drag-n-drop, правда, для tree view, но можно и под list view переделать.

          Размеры окон выставить — ноу проблем. А вот с хромом пока что сложности: в нём «контролы» не нативные, а поддержка UIA в pywinauto пока ещё в проекте. Собственно, в окно хрома кинуть файл — легко. А вот вбить нужный URL в табу хрома придётся другими средствами. Равно как и проверять изменения странички.
          • 0
            Спасибо, посмотрю в его сторону.
            • 0
              На самом деле, два года назад такой сценарий было невозможно написать с помощью pywinauto (так что я вас зря тогда обнадежил), а осенью 2016-го стало возможно. Вот пример, написанный моим студентом. С Хромом вскрылся ряд моментов, в частности ключ, с которым его нужно запускать, чтобы он рендерил страницы с поддержкой UI Automation.

              Ну, и для полноты есть подобный хоть и неидеальный пример на Cruciatus на C# (думаю, можно существенно улучшить, просто руки не дошли).
              • 0
                Прошло 2 года и когда меня спросили «что в вашей работе было самым сложным?» — я назвал этот кейс. Конечно тогда у меня ничего не получилось, да и задача понемногу потеряла актуальность.
                Но спасибо, что через столько времени помните обо мне :)

        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

        Самое читаемое