С чем едят


С помощью Winium мы можем автоматизировать обычные Windows-приложения. Как правило, Winium может работать с теми элементами, которые можно отыскать в окнах стандартными Windows-средствами (как правило, эти элементы имеют tab-ордер). Средства эти поставляются в стандартных китах (скачать, например, можно тут, после установки искать их, например, здесь: C:\Program Files (x86)\Windows Kits\8.1\bin\x64). Наиболее удобными для себя я считаю inspect и uiverify, но на вкус и цвет, как говорят некоторые мои товарищи, все фломастеры разные.

Однако, для того, чтобы автоматизировать самописные приложения, неплохо было бы товарищам разработчикам следить за тем, чтобы у элементов были адекватные ClassName, Name и AutomationId. Конечно, в определённых ситуациях нас могут спасти передачи нажатий определённых клавиш и щёлканье мышкой по координатам, но это не панацея. Лучше всегда иметь набор заранее заготовленных «рычагов» в виде описаний объектов, чем полагаться на то, что GUI всегда будет неизменным и будет себя хорошо вести в различных ситуациях.

С Winium в нормальном режиме можно общаться посредством Selenium с использованием Python и Java; так или иначе, но звёзды встали так, что выбор пал на Java.

Установка


Во-первых, нам потребуется Winium Desktop Driver. Скачать его можно, например, тут. От нас потребуется это дело разархивировать и запускать при тестах. Через него Selenium сервер будет общаться с Windows-приложениями.

Во-вторых, собственно, Java-IDE (использовалась IntelliJ IDEA Community, за что им отдельное большое спасибо) и JDK. Не забываем проверить/прописать переменную среды JAVA_HOME со значением пути до JDK (в моём случае — C:\Program Files\Java\jdk1.8.0_131).

В-третьих, нужен нам и Selenium сервер — идем сюда, качаем Selenium Standalone Server (JAR-файл).

Далее по настройке проекта — создаём новый Java-проект и добавляем в External libraries (кликаем по кнопке Project Structure (или Ctrl+Alt+Shift+S), там в Project Settings выбираем Libraries и нажимаем плюсик) наш селениумный JAR-файл. На этом всё, можно писать.

Использование


Для того, чтобы всё работало, нужно на время использования тестов запускать Winium.Desktop.Driver с админскими правами. Но, так как драйвер время от времени может подвисать по тем или иным причинам, удобно его запускать перед каждым тестом и в конце убивать. Например, для этой цели можно использовать ProcessBuilder:

ProcessBuilder pro = new ProcessBuilder(windriverPath + windriverName, windriverParam);
shell = pro.start();
//<наш код>
shell.destroy();

где для удобства развёртывания можно задавать windriverPath, windriverName – путь до драйвера и имя исполняемого файла; windriverParam – дополнительные параметры запуска, ежели таковые потребуются.

В начале начал в коде нужно прикрепить переменную драйвера к собственно Winium драйверу:

DesiredCapabilities cap = new DesiredCapabilities();
cap.setCapability("app","<path to executable file>"); //если хотим сразу запускать какую-либо программу
cap.setCapability("launchDelay","5"); //задержка после запуска программы
WebDriver driver = new RemoteWebDriver(new URL("http://localhost:9999"),cap); //на этом порту по умолчанию висит Winium драйвер

Если мы не хотим запускать какое-то приложение, а просто прикрепляться к какому-либо уже запущенному, то 2 и 3 строчки можно смело опустить.

Для того, чтобы нам работать с каким-либо элементом, нам сначала нужно к нему прикрепиться. Известны три механизма прикрепления – By.name – по полю Name, By.className — по полю ClassName и By.xpath для более изощрённых условий поиска. Также если нам нужен первый/единственный элемент, мы можем использовать метод findElement, а если мы хотим получить список всех таких элементов, то нам нужно использовать метод findElements. Во втором случае элементы будут добавлены в порядке tab-ордера. Примеры использования:


WebElement wrk = driver.findElement(By.name("Значение поля Name")); //один элемент, поиск по полю Name
List<WebElement> wrkL = driver.findElements(By.className("Значение поля ClassName")); //список элементов с заданным полем ClassName

Так же мы можем прикрепляться к элементам другого элемента:


WebElement wrk1 = wrk.findElement(By.name("Значение поля Name"));

Если первые два механизма прикрепления очень узкоспециализированы, т.е. работают строго в иерархической структуре и строго с полями Name и ClassName, то для работы с иными случаями нам потребуется третий механизм, а именно By.xpath.

В этом механизме всё строго по канонам использования xpath (во всяком случае, с использованных случаях всё работало как нужно). С помощью xpath можно получить и обрабатывать поля IsOffscreen, AutomationId, HasKeyboardFocus:


WebElement field = wrk.findElement(By.xpath("*[@HasKeyboardFocus='True']")); //найдёт элемент у wrk, на котором стоит фокус

При использовании xpath, особенно если мы работаем в более сложных условиях, удобно для отслеживания вероятных ошибок логировать всю строку, передаваемую в xpath:


String xpathStr = "*[(@AutomationId='" + autId + "') and (@IsOffscreen='False')]"; //будем искать элемент у текущего окна с каким-то заданным AutomationId = autId и у которого свойство IsOffscreen = False 
log("Performing xpath search: " + xpathStr);
WebElement tWrk = wrk1.findElement(By.xpath(xpathStr));

Конечно же, мы можем искать xpath'ом элементы не только текущего окна, но такой поиск может длиться достаточно продолжительное время.

Есть еще другие способы поиска, но, так или иначе, все сводятся к описанным выше. Например, такие.

Также нам может потребоваться имитация ввода клавиш с клавиатуры. В обычном порядке это делается методом sendKeys(«Последовательность символов») у элемента. Однако, следует помнить, что некоторые символы используются как служебные и их надо экранировать (например, "+" это Shift, и для того, чтобы ввести "+", нужно передать последовательность "+="). Для удобства пользования кодом можно написать обёртку, которая бы автоматически заменяла все "+" на "+=", но тут как кому удобнее. Подробнее ознакомиться со стандартами передач комбинаций клавиш можно, например, тут. Тем не менее, возникали проблемы с корректной передачей нажатий стрелок на клавиатуре, так что к сожалению, при текущей версии драйвера придётся искать обходные пути.

Одним из этих обходных путей является кликань�� мышкой по координатам относительно заданного элемента. Это не самая тривиальная задача, но тут можно использовать Actions:


Actions builder = new Actions(driver);
Action enter = builder
        .moveToElement(wrk)
        .moveByOffset(x,y)
        .click()
        .build();
enter.perform();

где wrk – имя WebElement, от центра которого будем двигать мышкой; x, y – расстояние, на которое будем двигать (положительное значение x двигает курсор вправо, положительное y – вниз).

Совсем чуть-чуть об общей структуре проекта


Как уже было слегка отмечено выше, основным элементом взаимодействия с внешним миром являются «рычаги». Их можно удобно описать классом с полями name, className и automationId и хранить в отдельных классах по блокам.

Так как автоматические тесты нередко повторяют какие-либо шаги друг друга, то эти процедуры тоже удобно оформить в виде отдельных статичных методов и тоже хранить в отдельных классах по блокам.

Всё остальное можно смело делать как обычно — классы с тестами, класс для запуска тестов и прочее.

Заключение


В целом погружение в мир Winium довольно интересно и не сильно сложно. Спасибо всем читателям, которые дочитали этот пост до конца. Очень надеюсь, что этот пост поможет кому-нибудь в освоении Winium. Отдельное спасибо моей напарнице Евгении, вместе с которой мы погружались в его дебри. Всем добра!