Карта города и справочник предприятий
87,19
рейтинг
23 июля 2015 в 11:16

Разработка → 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ГИС.
Автор: @skyline-gleb
2ГИС
рейтинг 87,19
Карта города и справочник предприятий

Комментарии (4)

  • 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
        Спасибо, посмотрю в его сторону.

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

Самое читаемое Разработка