Всем привет! Меня зовут Артём Соковец. Хочу поделиться переводом своей статьи об Atlas: реинкарнации фреймворка HTML Elements, где представлен совершенно иной подход работы с Page Object.
Перед тем, как перейти к деталям, хочу спросить: сколько обёрток для Page Object вы знаете? Page Element, ScreenPlay, Loadable Component, Chain of invocations…
А что будет, если взять Page Object с реализацией на интерфейсе, прикрутить Proxy Pattern и добавить немного функциональности Java 8?
Если интересно, предлагаю перейти под кат.
При использовании стандартного шаблона проектирования PageObject возникает ряд проблем:
Дублирование элементов
Здесь блок Header используется в различных классах PageObject.
Отсутствие параметризации у элементов
В этом примере описываются элементы страницы редактирования настроек пользователя. Два элемента TextBlock содержат практически идентичный локатор с разницей только в текстовом значении («Text_1» и «Text_2»).
Однотипный код
В повседневной работе можно встретить Page Object, состоящие из множества строк кода с однотипными элементами. В дальнейшем такие классы «неудобно» поддерживать.
Большой класс с шагами (steps)
Со временем разрастается класс шагов для работы с элементами. Требуется более пристальное внимание, чтобы не было дубликатов методов.
Реинкарнация фреймворка HTML Elements направлена на решение вышеописанных проблем, уменьшение количество строк кода тестового проекта, более продуманную работу со списками и ожиданиями, а также тонкую настройку инструмента под себя благодаря системе расширений.
Atlas — Java-фреймворк нового поколения для разработки UI-автотестов с реализацией паттерна Page Object через интерфейсы. Данный подход предоставляет возможность множественного наследования при построении дерева элементов, что в итоге обеспечивает лаконичный код ваших автотестов.
Основным нововведением фреймворка является использование интерфейсов вместо стандартных классов.
Вот так выглядит описание главной страницы github.com:
В приведённом коде описывается главная страница сайта GitHub с одним элементом и множественным наследованием от слоёв WebPage и WithHeader (пример дан исключительно для учебных целей, поэтому большинство веб-элементов опущено).
На текущий момент Atlas состоит из трёх модулей:
В atlas-core описана основная функциональность обработки Page Object'ов с помощью интерфейсов. Сама идея использования интерфейсов была взята из известного инструмента Retrofit.
Два других модуля atlas-webdriver и atlas-appium используются для разработки автоматизированных скриптов UI web и UI mobile. Основной точкой входа для описания web-страниц является интерфейс WebPage, а для мобильных экранов — Screen. Концептуально atlas-webdriver и atlas-appium построены на расширениях (пакет *.extension).
В поставке инструмента идут два специализированных класса для работы с UI-элементами (аналог класса WebElement).
AtlasWebElement и AtlasMobileElement дополнены методами should и waitUntil. (рассмотрение данных методов будет далее в статье).
Инструмент предоставляет возможность создания своих компонентов с помощью расширения вышеуказанных классов, что позволяет создать кроссплатформенный элемент.
Рассмотрим подробнее функциональность инструмента:
Интерфейсы вместо классов
При описании стандартных PageObject используются интерфейсы вместо классов.
В данном примере описывается ссылка на стартовой странице GitHub.
Параметризация элементов
Представим, что у нас есть форма с полями:
Чтобы её описать, требуется создать 11 переменных с аннотацией @FindBy и, при необходимости, объявить getter.
Используя Atlas, потребуется лишь один параметризованный элемент AtlasWebElement.
Код автоматизированного теста выглядит следующим образом:
Обращаемся к нужной странице, вызываем метод с параметром и выполняем требуемые действия с элементом. Метод с параметром описывает конкретное поле.
Множественное наследование
Ранее упоминалось, что блок (например, Header), который используется в разных Page Object — это дублирование кода.
Есть header GitHub.
Опишем данный блок (большинство веб-элементов опущено):
Далее создадим слой, который можно подключить к любой странице:
Расширяем главную страницу блоком header.
В целом можно создать больше слоёв и подключить их к нужной странице. В примере ниже подключаем с главной странице слои header, footer, sidebar.
Пойдём дальше. Header содержит 4 кнопки, 3 выпадающих меню и одно поле поиска:
Создадим собственный элемент Button, и одним элементом опишем четыре кнопки.
Подключим кнопку button к слою header. Таким образом расширим функциональность шапки GitHub.
Отдельный элемент Button можно подключать к различным слоям веб-сайта и быстро получить на нужной странице требуемый элемент.
Пример:
Во второй строке теста происходит обращение к шапке сайта, далее вызываем параметризированную кнопку со значением «Pricing» и выполняем клик.
На тестируемом сайте может быть довольно много элементов, которые повторяются от страницы к странице. Чтобы не описывать их все с помощью стандартного подхода Page Object, можно описать их один раз и подключать там, где требуется. Экономия времени и количества строк кода налицо.
Методы по умолчанию
В Java 8 появились методы по умолчанию, которые используются, чтобы заранее определить нужную функциональность.
Допустим, у нас есть «вредный» элемент: например, чекбокс, который то включен, то выключен. Через него проходит много сценариев. Требуется включать чекбокс, если он выключен:
Чтобы не хранить весь этот код в классе шагов, возможно поместить его рядом с элементом в виде метода по умолчанию.
Теперь шаг в тесте будет выглядеть так:
Другой пример, в котором требуется совместить очистку и ввод символов в поле.
Определим метод, который очищает поле и возвращает сам элемент:
В тестовом методе шаг выглядит следующим образом:
Таким образом можно запрограммировать требуемое поведение в элементе.
Повторные попытки (Retry)
В Atlas есть встроенные повторные попытки. Вам не нужно заботиться о таких исключениях, как NotFoundException, StaleElementReferenceException и WebDriverException, а также можно забыть о применении явных и неявных ожиданий Selenium API.
Если на каком-то этапе цепочки вы поймали исключение, фаза повторяется с начала.
Есть возможность самостоятельно настраивать интервал времени, в течение которого можно выполнять повторение, или частоту повторения.
Ожидаем в течение трёх секунд c частотой опроса раз в секунду.
Также можем настроить ожидание для конкретного элемента с помощью аннотации Retry. Для всех элементов поиск будет происходить в течение 3 секунд, а в случае с одним составит 20.
Работа со списками
Из коробки инструмент предоставляет работу со списками. Что это значит? Есть поле с тегом input, куда вводим текст, далее появляется выпадающий список, элементы появляются не сразу.
Для таких случаев есть сущность ElementsCollection. С её помощью происходит работа со списками.
Использование:
Также есть возможность фильтровать элементы и конвертировать их в список другого вида.
Smart Assertions
Как ранее упоминалось, в сущностях AtlasWebElement и AtlasMobileElement используются методы should, waitUntil для работы с проверками (утверждениями).
Для чего это сделано? Чтобы сэкономить время при разборе отчётов прогона автоматизированных сценариев. Большинство функциональных проверок выполняются в конце сценария: они интересны специалисту функционального тестирования, а промежуточные проверки – специалисту автоматизированного тестирования. Следовательно, если функциональность продукта не работает, логично бросать исключение AssertationError, в ином случае — RuntimeException.
В Allure сразу будет видно, с чем мы имеем дело: либо у нас дефект продукта (в работу берёт специалист ФТ), либо сломался автотест (разбирается специалист АТ).
Модель расширений
У пользователя есть возможность переопределить базовый функционал инструмента либо внедрить свой функционал. Модель расширения Atlas похожа на модель расширения JUnit 5. Модули atlas-webdriver и atlas-appium построены на расширениях. Если вам интересно, посмотрите исходный код.
Разберём абстрактный пример: требуется разработать UI-автотесты для браузера Internet Explorer 11 (кое-где он ещё используется). Бывают моменты, когда стандартный клик по элементам не отрабатывает, тогда можно воспользоваться JS-кликом. Вы решаете на время переопределить клик на всём тестовом проекте.
Как это сделать?
Создаём расширение, которое реализует интерфейс MethodExtension.
Переопределяем два метода. В методе test() задаём, что переопределяем метод click. Метод invoke реализует требуемую логику. Теперь клик по элементу будет происходить через JavaScript.
Подключаем расширение следующим образом:
С помощью расширений возможно создать поиск локаторов для элементов в БД и реализовать другие интересные возможности — всё зависит от вашей фантазии и потребностей.
Единая точка входа к PageObject'ам (WebSite)
Инструмент позволяет хранить все ваши Pages в одном месте и в дальнейшем работать только через сущность Site.
Дополнительно Page'ам возможно задавать быстрый url, query-параметры и path-сегменты.
В строчке выше передаются два path-сегмента (qameta и atlas), что преобразовывается в адрес github.com/qameta/atlas/tree/master. Основное преимущество такого подхода в том, что возможно сразу открыть требуемую страницу без прокликивания до неё.
Работа с мобильным элементом
Работа с мобильным элементом (AtlasMobileElement) происходит аналогично работе с веб-элементом AtlasWebElement. Дополнительно в AtlasMobileElement добавлены три метода: скролл экрана вверх/вниз (scrollUp/scrollDown) и клик на элемент с удержанием (longPress).
Приведу пример главного экрана приложения Wikipedia. Один элемент описывается как для платформы iOS, так и для Android. Также описывают параметризованную кнопку.
Тесты выглядят аналогичным образом:
В примере выше мы открываем главную страницу Wikipedia, щёлкаем по поисковой строке, вводим текст Atlas, далее прокручиваем до элемента списка со значением Atlas LV-3B и переходим в его представление. Последняя строчка проверяет, что заголовок отображается и содержит требуемое значение.
Listener
Логирование событий возможно реализовать с помощью специального листенера (интерфейс Listener). Каждый метод при вызове имеет четыре события: Before, Pass, Fail. After.
Используя данный интерфейс, можно организовать отчётность. Ниже представлен пример Allure Listener, который можно найти по ссылке.
Далее подключаем слушатель при инициализации класса Atlas.
Вышеуказанным способом возможно создать listener для различных систем репортинга (например, для ReportPortal).
Для автоматизации UI web достаточно прописать зависимость atlas-webdriver и указать последнюю актуальную версию (на момент написания этого текста актуальна версия 1.6.0).
Maven:
Gradle:
Аналогичным образом поступаем, если требуется автоматизировать UI Mobile.
Maven:
Gradle:
После подключения зависимости в свой проект необходимо инициализировать инстанс класса Atlas.
В конструктор Atlas передаем инстанс конфигураци, а также драйвер.
На текущий момент есть две конфигурации: WebDriverConfiguration и AppiumDriverConfiguration. Каждая конфигурация содержит определенные расширения по умолчанию.
Далее определим метод, который будет создавать все PageObject.
Пример простенького тестового сценария:
Открываем сайт, обращаемся к слою header, в нём ищем текстовое поле (search input), вводим текст и нажимаем ввод.
В заключение хочу отметить, что Atlas — это гибкий инструмент с большими возможностями. Его можно настроить под конкретный тестовый проект так, как удобно вашей команде и вам. Заняться разработкой кроссплатформенных тестов и т.д.
Доступны видеозаписи докладов о нём с конференций Heisenbug, Selenium Camp и Nexign QA Meetup. Есть Telegram-чат @atlashelp.
С помощью данного инструмента вы сможете сократить значительное количество строк кода (проверено на проектах таких компаний, как Яндекс, СберТех и Тинькофф).
Перед тем, как перейти к деталям, хочу спросить: сколько обёрток для Page Object вы знаете? Page Element, ScreenPlay, Loadable Component, Chain of invocations…
А что будет, если взять Page Object с реализацией на интерфейсе, прикрутить Proxy Pattern и добавить немного функциональности Java 8?
Если интересно, предлагаю перейти под кат.
Введение
При использовании стандартного шаблона проектирования PageObject возникает ряд проблем:
Дублирование элементов
public class MainPage {
@FindBy(xpath = ".//div[@class = 'header']")
private Header header;
}
public class AnyOtherPage {
@FindBy(xpath = ".//div[@class = 'header']")
private Header header;
}
Здесь блок Header используется в различных классах PageObject.
Отсутствие параметризации у элементов
public class EditUserPage {
@FindBy(xpath = "//div[text()='Text_1']")
private TextBlock lastActivity;
@FindBy(xpath = "//div[text()='Text_2']")
private TextBlock blockReason;
}
В этом примере описываются элементы страницы редактирования настроек пользователя. Два элемента TextBlock содержат практически идентичный локатор с разницей только в текстовом значении («Text_1» и «Text_2»).
Однотипный код
public class UserPage {
@FindBy(xpath = "//div[text()='Телефон']/input")
private UfsTextInput innerPhone;
@FindBy(xpath = "//div[text()='Email']/input")
private UfsTextInput email;
@FindBy(xpath = "//button[text()='Сохранить']")
private UfsButton save;
@FindBy(xpath = "//button[text()='Список']")
private UfsButton toUsersList;
}
В повседневной работе можно встретить Page Object, состоящие из множества строк кода с однотипными элементами. В дальнейшем такие классы «неудобно» поддерживать.
Большой класс с шагами (steps)
public class MainSteps {
public void hasText(HtmlElement e, Matcher m)
public void hasValue(HtmlElement e, Matcher m)
public void linkContains(HtmlElement e, String s)
public void hasSize(List<HtmlElement> e, Matcher m)
public void hasItem(List<HtmlElement> e, Matcher m)
//...
}
Со временем разрастается класс шагов для работы с элементами. Требуется более пристальное внимание, чтобы не было дубликатов методов.
Ваш путеводитель в мире Page Object
Реинкарнация фреймворка HTML Elements направлена на решение вышеописанных проблем, уменьшение количество строк кода тестового проекта, более продуманную работу со списками и ожиданиями, а также тонкую настройку инструмента под себя благодаря системе расширений.
Atlas — Java-фреймворк нового поколения для разработки UI-автотестов с реализацией паттерна Page Object через интерфейсы. Данный подход предоставляет возможность множественного наследования при построении дерева элементов, что в итоге обеспечивает лаконичный код ваших автотестов.
Основным нововведением фреймворка является использование интерфейсов вместо стандартных классов.
Вот так выглядит описание главной страницы github.com:
public interface MainPage extends WebPage, WithHeader {
@FindBy("//a[contains(text(), 'Or start a free)]")
AtlasWebElement trial();
}
В приведённом коде описывается главная страница сайта GitHub с одним элементом и множественным наследованием от слоёв WebPage и WithHeader (пример дан исключительно для учебных целей, поэтому большинство веб-элементов опущено).
Архитектура фреймворка
На текущий момент Atlas состоит из трёх модулей:
- atlas-core
- atlas-webdriver
- atlas-appium
В atlas-core описана основная функциональность обработки Page Object'ов с помощью интерфейсов. Сама идея использования интерфейсов была взята из известного инструмента Retrofit.
Два других модуля atlas-webdriver и atlas-appium используются для разработки автоматизированных скриптов UI web и UI mobile. Основной точкой входа для описания web-страниц является интерфейс WebPage, а для мобильных экранов — Screen. Концептуально atlas-webdriver и atlas-appium построены на расширениях (пакет *.extension).
Элементы
В поставке инструмента идут два специализированных класса для работы с UI-элементами (аналог класса WebElement).
AtlasWebElement и AtlasMobileElement дополнены методами should и waitUntil. (рассмотрение данных методов будет далее в статье).
Инструмент предоставляет возможность создания своих компонентов с помощью расширения вышеуказанных классов, что позволяет создать кроссплатформенный элемент.
Основные возможности
Рассмотрим подробнее функциональность инструмента:
Интерфейсы вместо классов
При описании стандартных PageObject используются интерфейсы вместо классов.
public interface MainPage extends WebPage, WithHeader {
@FindBy("//a[contains(text(), 'Or start a free trial of Enterprise Server')]")
AtlasWebElement trial();
}
В данном примере описывается ссылка на стартовой странице GitHub.
Параметризация элементов
Представим, что у нас есть форма с полями:
Чтобы её описать, требуется создать 11 переменных с аннотацией @FindBy и, при необходимости, объявить getter.
Используя Atlas, потребуется лишь один параметризованный элемент AtlasWebElement.
public interface MainPage extends WebPage {
@FindBy("//div[text()='{{ text }}']/input")
AtlasWebElement input(@Param("text") String text);
}
Код автоматизированного теста выглядит следующим образом:
@Test
public void simpleTest() {
onMainPage().input("First Name").sendKeys("*");
onMainPage().input("Postcode").sendKeys("*");
onMainPage().input("Email").sendKeys("*");
}
Обращаемся к нужной странице, вызываем метод с параметром и выполняем требуемые действия с элементом. Метод с параметром описывает конкретное поле.
Множественное наследование
Ранее упоминалось, что блок (например, Header), который используется в разных Page Object — это дублирование кода.
Есть header GitHub.
Опишем данный блок (большинство веб-элементов опущено):
public interface Header extends AtlasWebElement {
@FindBy(".//input[contains(@class,'header-search-input')]")
AtlasWebElement searchInput();
}
Далее создадим слой, который можно подключить к любой странице:
public interface WithHeader {
@FindBy("//header[contains(@class,'Header')]")
Header header();
}
Расширяем главную страницу блоком header.
public interface MainPage extends WebPage, WithHeader {
@FindBy("//a[contains(text(), 'Or start a)]")
AtlasWebElement trial();
}
В целом можно создать больше слоёв и подключить их к нужной странице. В примере ниже подключаем с главной странице слои header, footer, sidebar.
public interface MainPage extends WithHeader, WithFooter, WithSidebar {}
Пойдём дальше. Header содержит 4 кнопки, 3 выпадающих меню и одно поле поиска:
Создадим собственный элемент Button, и одним элементом опишем четыре кнопки.
public interface Button extends AtlasWebElement {
@FindBy(".//a[contains(., '{{ value }}')]")
AtlasWebElement selectButton(@Param("value") String value);
}
Подключим кнопку button к слою header. Таким образом расширим функциональность шапки GitHub.
public interface Header extends WithButton {
…
}
Отдельный элемент Button можно подключать к различным слоям веб-сайта и быстро получить на нужной странице требуемый элемент.
Пример:
@Test
public void simpleTest() {
onMainPage().open("https://github.com");
onMainPage().header().button("Priсing").click();
}
Во второй строке теста происходит обращение к шапке сайта, далее вызываем параметризированную кнопку со значением «Pricing» и выполняем клик.
На тестируемом сайте может быть довольно много элементов, которые повторяются от страницы к странице. Чтобы не описывать их все с помощью стандартного подхода Page Object, можно описать их один раз и подключать там, где требуется. Экономия времени и количества строк кода налицо.
Методы по умолчанию
В Java 8 появились методы по умолчанию, которые используются, чтобы заранее определить нужную функциональность.
Допустим, у нас есть «вредный» элемент: например, чекбокс, который то включен, то выключен. Через него проходит много сценариев. Требуется включать чекбокс, если он выключен:
if(onMainPage().rentFilter().checkbox("Кирпич").getAttribute("class").contains("disabled")) {
onMainPage().rentFilter().checkbox("Кирпич").click();
}
Чтобы не хранить весь этот код в классе шагов, возможно поместить его рядом с элементом в виде метода по умолчанию.
public interface Checkbox extends AtlasWebElement {
@FindBy("//...")
AtlasWebElement checkBox((@Param("value") String value);
default void selectCheckbox(String value) {
if (checkBox(value).getAttribute("class").contains("disabled")) {
checkBox(value).click();
}
}
}
Теперь шаг в тесте будет выглядеть так:
onMainPage().rentFilter().selectCheckbox("Кирпич");
Другой пример, в котором требуется совместить очистку и ввод символов в поле.
onMainPage().header().input("GitHub").clear();
onMainPage().header().input("GitHub").sendKeys("*");
Определим метод, который очищает поле и возвращает сам элемент:
public interface Input extends AtlasWebElement {
@FindBy("//xpath")
AtlasWebElement input(@Param("value") String value);
default AtlasWebElement withClearInput(String value) {
input(value).clear();
return input(value);
}
}
В тестовом методе шаг выглядит следующим образом:
onMainPage().header().withClearInput("GitHub").sendKeys("Atlas");
Таким образом можно запрограммировать требуемое поведение в элементе.
Повторные попытки (Retry)
В Atlas есть встроенные повторные попытки. Вам не нужно заботиться о таких исключениях, как NotFoundException, StaleElementReferenceException и WebDriverException, а также можно забыть о применении явных и неявных ожиданий Selenium API.
onSite().onSearchPage("Junit 5").repositories().waitUntil(hasSize(10));
Если на каком-то этапе цепочки вы поймали исключение, фаза повторяется с начала.
Есть возможность самостоятельно настраивать интервал времени, в течение которого можно выполнять повторение, или частоту повторения.
Atlas atlas = new Atlas(new WebDriverConfiguration(driver))
.context(new RetryerContext(new DefaultRetryer(3000L, 1000L, Collections.singletonList(Throwable.class))));
Ожидаем в течение трёх секунд c частотой опроса раз в секунду.
Также можем настроить ожидание для конкретного элемента с помощью аннотации Retry. Для всех элементов поиск будет происходить в течение 3 секунд, а в случае с одним составит 20.
@Retry(timeout = 20_000L, polling = 2000L)
@IOSFindBy(xpath = "//XCUIElementTypeSearchField[@name='Search Wikipedia']")
@AndroidFindBy(xpath = "//*[contains(@text, 'Search Wikipedia')]")
AtlasMobileElement searchWikipedia();
Работа со списками
Из коробки инструмент предоставляет работу со списками. Что это значит? Есть поле с тегом input, куда вводим текст, далее появляется выпадающий список, элементы появляются не сразу.
Для таких случаев есть сущность ElementsCollection. С её помощью происходит работа со списками.
public interface ContributorsPage extends WebPage, WithHeader {
@FindBy(".//ol[contains(@class, 'contrib-data')]//li[contains(@class, 'contrib-person')]")
ElementsCollection<RepositoryCard> hovercards();
}
Использование:
onSite().onContributorsPage().hovercards().waitUntil(hasSize(4));
Также есть возможность фильтровать элементы и конвертировать их в список другого вида.
Smart Assertions
Как ранее упоминалось, в сущностях AtlasWebElement и AtlasMobileElement используются методы should, waitUntil для работы с проверками (утверждениями).
Метод | Описание |
---|---|
should(…) | Выполняет различные утверждения (проверки) с элементом. Бросает AssertionError |
waitUntil(…) | Выполняет различные утверждения (проверки) с элементом. Бросает RuntimeException |
Для чего это сделано? Чтобы сэкономить время при разборе отчётов прогона автоматизированных сценариев. Большинство функциональных проверок выполняются в конце сценария: они интересны специалисту функционального тестирования, а промежуточные проверки – специалисту автоматизированного тестирования. Следовательно, если функциональность продукта не работает, логично бросать исключение AssertationError, в ином случае — RuntimeException.
В Allure сразу будет видно, с чем мы имеем дело: либо у нас дефект продукта (в работу берёт специалист ФТ), либо сломался автотест (разбирается специалист АТ).
Модель расширений
У пользователя есть возможность переопределить базовый функционал инструмента либо внедрить свой функционал. Модель расширения Atlas похожа на модель расширения JUnit 5. Модули atlas-webdriver и atlas-appium построены на расширениях. Если вам интересно, посмотрите исходный код.
Разберём абстрактный пример: требуется разработать UI-автотесты для браузера Internet Explorer 11 (кое-где он ещё используется). Бывают моменты, когда стандартный клик по элементам не отрабатывает, тогда можно воспользоваться JS-кликом. Вы решаете на время переопределить клик на всём тестовом проекте.
onMainPage().header().button("en").click();
Как это сделать?
Создаём расширение, которое реализует интерфейс MethodExtension.
public class JSClickExt implements MethodExtension {
@Override
public Object invoke(Object proxy, MethodInfo methodInfo, Configuration config) {
final WebDriver driver = config.getContext(WebDriverContext.class)
.orElseThrow(() -> new AtlasException("Context doesn't exist")).getValue();
final JavascriptExecutor js = (JavascriptExecutor) driver;
js.executeScript("arguments[0].click();", proxy);
return proxy;
}
@Override
public boolean test(Method method) {
return method.getName().equals("click");
}
}
Переопределяем два метода. В методе test() задаём, что переопределяем метод click. Метод invoke реализует требуемую логику. Теперь клик по элементу будет происходить через JavaScript.
Подключаем расширение следующим образом:
atlas = new Atlas(new WebDriverConfiguration(driver, "https://github.com"))
.extension(new JSClickExt());
С помощью расширений возможно создать поиск локаторов для элементов в БД и реализовать другие интересные возможности — всё зависит от вашей фантазии и потребностей.
Единая точка входа к PageObject'ам (WebSite)
Инструмент позволяет хранить все ваши Pages в одном месте и в дальнейшем работать только через сущность Site.
public interface GitHubSite extends WebSite {
@Page
MainPage onMainPage();
@Page(url = "search")
SearchPage onSearchPage(@Query("q") String value);
@Page(url = "{profile}/{project}/tree/master/")
ProjectPage onProjectPage(@Path("profile") String profile, @Path("project") String project);
@Page
ContributorsPage onContributorsPage();
}
Дополнительно Page'ам возможно задавать быстрый url, query-параметры и path-сегменты.
onSite().onProjectPage("qameta", "atlas").contributors().click();
В строчке выше передаются два path-сегмента (qameta и atlas), что преобразовывается в адрес github.com/qameta/atlas/tree/master. Основное преимущество такого подхода в том, что возможно сразу открыть требуемую страницу без прокликивания до неё.
@Test
public void usePathWebSiteTest() {
onSite().onProjectPage("qameta", "atlas").contributors().click();
onSite().onContributorsPage().hovercards().waitUntil(hasSize(4));
}
Работа с мобильным элементом
Работа с мобильным элементом (AtlasMobileElement) происходит аналогично работе с веб-элементом AtlasWebElement. Дополнительно в AtlasMobileElement добавлены три метода: скролл экрана вверх/вниз (scrollUp/scrollDown) и клик на элемент с удержанием (longPress).
Приведу пример главного экрана приложения Wikipedia. Один элемент описывается как для платформы iOS, так и для Android. Также описывают параметризованную кнопку.
public interface MainScreen extends Screen {
@Retry(timeout = 20_000L, polling = 2000L)
@IOSFindBy(xpath = "//XCUIElementTypeSearchField[@name='Search Wikipedia']")
@AndroidFindBy(xpath = "//*[contains(@text, 'Search Wikipedia')]")
AtlasMobileElement searchWikipedia();
@IOSFindBy(id = "{{ value }}")
AtlasMobileElement button(@Param("value") String value);
}
Тесты выглядят аналогичным образом:
public void simpleExample() {
onMainScreen().searchWikipedia().click();
onSearchScreen().search().sendKeys("Atlas");
onSearchScreen().item("Atlas LV-3B").swipeDownOn().click();
onArticleScreen().articleTitle().should(allOf(displayed(), text("Atlas LV-3B")));
}
В примере выше мы открываем главную страницу Wikipedia, щёлкаем по поисковой строке, вводим текст Atlas, далее прокручиваем до элемента списка со значением Atlas LV-3B и переходим в его представление. Последняя строчка проверяет, что заголовок отображается и содержит требуемое значение.
Listener
Логирование событий возможно реализовать с помощью специального листенера (интерфейс Listener). Каждый метод при вызове имеет четыре события: Before, Pass, Fail. After.
Используя данный интерфейс, можно организовать отчётность. Ниже представлен пример Allure Listener, который можно найти по ссылке.
Далее подключаем слушатель при инициализации класса Atlas.
atlas = new Atlas(new WebDriverConfiguration(driver)).listener(new AllureListener());
Вышеуказанным способом возможно создать listener для различных систем репортинга (например, для ReportPortal).
Подключение
Для автоматизации UI web достаточно прописать зависимость atlas-webdriver и указать последнюю актуальную версию (на момент написания этого текста актуальна версия 1.6.0).
Maven:
<dependency>
<groupId>io.qameta.atlas</groupId>
<artifactId>atlas-webdriver</artifactId>
<version>${atlas.version}</version>
</dependency>
Gradle:
dependencies { сompile 'io.qameta.atlas:atlas-webdriver:1.+' }
Аналогичным образом поступаем, если требуется автоматизировать UI Mobile.
Maven:
<dependency>
<groupId>io.qameta.atlas</groupId>
<artifactId>atlas-appium</artifactId>
<version>${atlas.version}</version>
</dependency>
Gradle:
dependencies { сompile 'io.qameta.atlas:atlas-appium:1.+' }
Использование
После подключения зависимости в свой проект необходимо инициализировать инстанс класса Atlas.
@Before
public void startDriver() {
driver = new ChromeDriver();
atlas = new Atlas(new WebDriverConfiguration(driver));
}
В конструктор Atlas передаем инстанс конфигураци, а также драйвер.
На текущий момент есть две конфигурации: WebDriverConfiguration и AppiumDriverConfiguration. Каждая конфигурация содержит определенные расширения по умолчанию.
Далее определим метод, который будет создавать все PageObject.
private <T extends WebPage> T onPage(Class<T> page) {
return atlas.create(driver, page);
}
Пример простенького тестового сценария:
@Test
public void simpleTest() {
onPage(MainPage.class).open("https://github.com");
onPage(MainPage.class).header().searchInput().sendKeys("Atlas");
onPage(MainPage.class).header().searchInput().submit();
}
Открываем сайт, обращаемся к слою header, в нём ищем текстовое поле (search input), вводим текст и нажимаем ввод.
Итоги
В заключение хочу отметить, что Atlas — это гибкий инструмент с большими возможностями. Его можно настроить под конкретный тестовый проект так, как удобно вашей команде и вам. Заняться разработкой кроссплатформенных тестов и т.д.
Доступны видеозаписи докладов о нём с конференций Heisenbug, Selenium Camp и Nexign QA Meetup. Есть Telegram-чат @atlashelp.
С помощью данного инструмента вы сможете сократить значительное количество строк кода (проверено на проектах таких компаний, как Яндекс, СберТех и Тинькофф).