FlaNium: как сделать тестирование Desktop-приложений под Windows проще
Когда есть доступ к исходному коду и тестирование с разработкой производятся параллельно, то никаких трудностей не возникает, но что делать, когда этого доступа нет и приходится тестировать уже готовый продукт?
В отличие от автоматизации WEB, API или мобильных приложений, тестирование Desktop в некоторой степени — «экзотика», и на это есть несколько причин:
- Отсутствуют качественные Open source решения, наподобие Selenium для Web, а те, что есть, либо сильно устарели, либо неудобны в использовании. Иными словами, воспитывают в автоматизаторах смирение и обреченность.
- Коммерческие продукты хоть и обладают широким функционалом и удобством в использовании, стоят зачастую дороже, чем вся автоматизация и используют vendor lock-inбизнес-модель, которая также накладывает дополнительные затраты и ограничения (переобучение персонала, отсутствие возможности использовать существующие наработки и решения, полная зависимость от поставщика ПО и т.п.).
- Ввиду стремительного развития web-технологий, Desktop понемногу отмирает, что сильно сказывается на развитии и поддержке инструментов тестирования и наличии квалифицированных специалистов в этой области.
Несмотря на всю пессимистичность сложившейся ситуации с автоматизацией Desktop-приложений, можно выделить некоторые Open source продукты, достойные внимания. Они же в дальнейшем стали основой в разработке собственного инструмента тестирования.
Обзор предлагаемых решений показал, что Open source продуктов не так уж много. Сначала мы испытали Winium — Automation framework for Windows platforms.
Winium — это фреймворк для тестирования Desktop Windows приложений на базе Selenium. Он обладает всеми основными требованиями, которые были необходимы для работы с предстоящим проектом:
- поддержка WinForms и WPF приложений;
- REST-протокол взаимодействия между тестами и тестируемым приложением;
- возможность взаимодействия с Selenium (а конкретно Java + Selenium).
Несмотря на все свои преимущества у Winium есть один существенный недостаток — ограниченный функционал. Ядро Cruciatus, на базе которого построен Winium, поддерживает только AutomationElement Identifiers class, и соответственно, доступа к некоторым основным параметрам элементов у него нет. Например, нет поддержки ValuePattern (нет возможности получить значение положения ProgressBar, Slider, ScrollBar, состояния трехрежимного CheckBox и др.), SelectionItemPattern (нет возможности получить данные о выбранном элементе ComboBox и т.п.) и других паттернов. Данный недостаток был критичным, и мы продолжили поиск.
Следующим, что показалось нам интересным в плане возможностей, оказалась библиотека FlaUI. Библиотека была написана на C# и не имела какого-либо API для взаимодействия извне. По сути это некая оболочка над библиотеками автоматизации Microsoft Windows Automation API, которая упрощает написание тестов. В отличие от Winium данная библиотека обладает полным функционалом взаимодействия с WinForms и WPF-приложениями, но требует написания тестов на языке C#. Поскольку у нас уже был готовый фреймворк тестирования, реализованный на языке Java, данная библиотека не удовлетворяла наши потребности.
Дальнейший анализ инструментов тестирования показал, что наиболее оптимальный вариант — это реализация своего инструмента. Имеющиеся продукты обладали теми или иными серьезными недостатками, а комбинировать несколько решений не представлялось возможным. Так как абсолютно все Open source решения использовали взаимодействие со стандартной библиотекой Windows Automation API, то было принято решение взять за основу ядро FlaUI Core, построенное на основе взаимодействия с данной библиотекой, и обладающее полным функционалом взаимодействия с элементами тестируемого приложения. Затем добавить поддержку Selenium REST API, аналогично Winium. Так родился проект FlaNium.
На данный момент в состав проекта входят пока только два компонента.
FlaNium.Desktop.Driver — основной компонент, представляющий из себя драйвер взаимодействия с тестируемым приложением посредством Windows Automation API и использующий протокол взаимодействия Selenium REST API.
FlaNium.WinAPI — Java-библиотека, расширяющая протокол Selenium REST API и добавляющая дополнительные возможности по настройке и взаимодействию с FlaNium драйвером. Также данная библиотека позволяет типизировать стандартный Selenium WebElement и привести его к компонентам тестируемого приложения, добавляя дополнительные методы взаимодействия, характерные определенному типу элемента.
Кто хоть немного сталкивался с тестированием, а особенно с тестированием Web-приложений знает, что такое Selenium WebDriver. Есть много информации про Selenium, поэтому в статье мы не будем рассматривать плюсы и минусы, а также особенности работы с Selenium, а рассмотрим лишь особенности работы с FlaNium драйвером.
Перед началом работы с FlaNium драйвером необходимо загрузить последнюю версию драйвера, а также прописать следующие зависимости в pom.xml:
...
com.github.lanit-exp
FlaNium.WinAPI
LATEST
org.seleniumhq.selenium
selenium-java
3.141.59
...
Далее производим настройку и инициализацию драйвера:
String DRIVER_PATH = "src/main/resources/driver/FlaNium.Desktop.Driver/FlaNium.Driver.exe";
String APP_PATH = "С:/Test_app.exe";
int driverPort = 9999;
// Инициализация драйвера:
FlaNiumDriverService service = new FlaNiumDriverService.Builder()
// Указание пути до драйвера
.usingDriverExecutable(new File(DRIVER_PATH).getAbsoluteFile())
// Установка порта (по умолчанию 9999)
.usingPort(driverPort)
// Включение режима отладки (вывод логов в консоль)
.withVerbose(true)
// Отключение логирования
.withSilent(false)
.buildDesktopService();
// Инициализация приложения:
DesktopOptions options = new DesktopOptions();
// Указание пути до тестируемого приложения
options.setApplicationPath(new File(APP_PATH));
// Задержка после запуска приложения (сек)
options.setLaunchDelay(5);
// Подключение к ранее запущенному экземпляру приложения
options.setDebugConnectToRunningApp(false);
// Получение экземпляра драйвера приложения
FlaNiumDriver driver = new FlaNiumDriver(service, options);
После получения экземпляра FlaniumDriver можно осуществлять поиск контролов приложения и взаимодействовать с ними через стандартные методы библиотеки Selenium.
WebElement edit = driver.findElement(By.xpath("//*[(@ControlType = 'Edit') and contains(@Name,'Text')]"));
edit.sendKeys("Test text");
Есть возможность поиска элементов по XPath, Name, Id (AutomationId) и ClassName, а также поддерживаются пять параметров поиска с помощью XPath — AutomationId, Name, ClassName, HelpText, ControlType.
driver.findElement(By.xpath("//*[(@AutomationId = '')]"));
driver.findElement(By.xpath("//*[(@Name = '')]"));
driver.findElement(By.xpath("//*[(@ClassName = '')]"));
driver.findElement(By.xpath("//*[(@HelpText = '')]"));
driver.findElement(By.xpath("//*[(@ControlType = '')]"));
driver.findElement(By.name("Checkbox1"));
driver.findElement(By.id("Form1"));
driver.findElement(By.className("MenuItem"));
Для более комфортной работы лучше воспользоваться такими инструментами как FlaUInspect, UISpy и подобными, так как они значительно упрощают написание тестов приложения, позволяя визуально отобразить структуру и параметры элементов приложения. Данные инструменты также позволяют понять, как можно обратиться к различным элементам или какие паттерны поддерживает конкретный из них.
Благодаря расширению протокола Selenium есть возможность типизировать любой WebElement и получить дополнительные возможности для работы. Для этого необходимо создать экземпляр требуемого класса и передать WebElement в качестве параметра:
TextBox textBox = new TextBox(edit);
// где edit — WebElement полученный ранее
textBox.setText("Test text2");
Рассмотрим на примере, для чего нужна типизация и что нам даёт расширение протокола Selenium. Возьмем для примера выбор значения из выпадающего списка:
На рисунке ниже изображено тестируемое приложение (слева) и инспектор (справа):
У нас есть элемент ComboBox, согласно инспектору, доступа к элементам списка у нас нет, чтобы его получить необходимо раскрыть список нажав на кнопку «Открыть».
После раскрытия списка мы получаем доступ ко всем вложенным элементам и можем осуществлять поиск и выбор необходимого элемента.
Вот так будет выглядеть код при использовании стандартных Selenium-методов:
// Находим элемент комбобокса по id элемента
WebElement comboBox = driver.findElement(By.xpath("//*[@AutomationId = 'NonEditableCombo']"));
// Находим кнопку раскрытия списка и кликаем по ней
comboBox.findElement(By.xpath(".//*[@ControlType = 'Button']")).click();
// После раскрытия списка, нам становятся доступны варианты выбора
List items = comboBox.findElements(By.xpath(".//*[@ControlType = 'ListItem']"));
// Далее пробегаемся по всему списку элементов и сравниваем имена с нужным нам значением
items.stream()
.filter(webElement -> webElement.getAttribute("Name").equals("Item 3"))
.findFirst().get().click();
// И в конце кликаем на найденном элементе
А вот так будет выглядеть то же самое, но при использовании методов типизированных элементов библиотеки FlaNium.WinAPI:
// Находим элемент комбобокса по id элемента
ComboBox comboBox = new ComboBox(driver.findElement(By.xpath("//*[@AutomationId = 'NonEditableCombo']")));
// Выбираем необходимое значение
comboBox.select("Item 3");
Как мы видим, использование данных методов значительно упрощает взаимодействие и сокращает код. С полным списком поддерживаемых элементов и реализованных методов можно ознакомиться по ссылке.
Мы не изобретали ничего кардинально нового, но взяв в основу преимущества Winium и FlaUI, скомпоновали продукт с удобным универсальным интерфейсом и широкими возможностями взаимодействия с тестируемым приложением. Удалось объединить протокол Selenium REST и библиотеки Windows Automation API.
Давайте рассмотрим, чем же обязан FlaNium этим двум проектам:
На момент написания статьи драйвер уже внедрен в один из наших проектов и успешно работает. Вложив силы и время в разработку собственного драйвера, удалось значительно сократить время и потратить меньше усилий на создание фреймворка и его внедрение, а также сильно упростить написание тестов.
Поскольку драйвер использует универсальный протокол взаимодействия, стало возможным использовать уже имеющийся фреймворк тестирования с некоторыми изменениями. Была добавлена работа с элементами посредством библиотеки FlaNium.WinAPI и реализована логика работы с конкретным тестируемым приложением. Также удалось избежать сложностей при работе с элементами приложения, поскольку используется полноценная библиотека взаимодействия и не возникало ситуаций, когда нет доступа к какому-либо параметру элемента.
Кроме всего перечисленного, внедрение данного драйвера позволило отказаться от вендорского ПО для тестирования, сократив стоимость автоматизации и её поддержки на значительную сумму, а также унифицировать стек технологий и требования к квалификации автоматизаторов.
Мы не останавливаемся на достигнутом и продолжаем развивать данный проект. Помимо взаимодействия с приложениями посредством Windows API ведется разработка технологии взаимодействия с приложением «изнутри» посредством инжекта исполняемой библиотеки в код тестируемого приложения. Данная технология необходима, например, для полноценного тестирования Delphi приложений, чего нельзя добиться с помощью стандартного Windows API.