Java testing framework for streamlined and efficient test creation.
В этой статье хочу поделиться инструментом, который я понемногу разрабатывал и улучшал при наличии свободного времени. Применяя для упрощения своих задач, над которыми я работал в разных компаниях за это время.
Работа в автоматизации тестирования стала моей оффициальной профессией несколько лет назад. За это время мне довелось позаниматься очень широким спектром задач, в зависимости от компаний, где я работал. Начиная с тестирования пользовательских интерфейсов различных сайтов, web и мобильных приложений, обязанности заводили меня глубоко в тестирование сложных API сервисов и даже автоматизацию десктопных программ.
Первые годы я плотно занимался тестированием Web приложений на Java, хотя начинал не с неё. Разумеется, перепробовав много подходов, я нашёл для себя любимый stack, который применял при наличии свободы выбора. Для меня это сложилось в TestNg + Selenium + Allure. Естественно, мне приходилось работать со знаменитым Cucumber, а потом и вообще с чудовищьным (на мой взгляд) Serenity и ещё многими другими решениями, включая кастомные разработки разных людей.
Моё расположение никогда не лежало в сторону BDD, так как чаще всего я сам читал отчёты и проверял тесты, и в 100% случаев сам составлял сценарии с Gherkin (иногда по предоставленным сценариям, чаще нет). Так называемое удобство, дающее BDD пользователю, сводилось к минимуму, а вот взамен требовало многое — небоскрёбы из обёрток классов, сложности с наследованием, необходимость создавать кучу дубликатов мизерных действий, абсолютное неудобство передачи данных между шагами и процессами и прочие трудности.
Потом пришёл Selenide, сделавший отказ от BDD-фреймворков ещё более обоснованным (хотя не сразу и не без оговорок). Тем не менее, BDD-отчёты мне в принципе нравились – они почти всегда были более содержательными и легко читаемыми, чем отчёты, собираемые через аннотации @Step
в Allure, и куда более читаемыми, чем встроенное автологирование Allure.
Если же хотелось сделать отчёт более содержательным и интуитивно понятным без BDD-подходов и фреймворков, приходилось бы, как и в BDD, оборачивать каждое минимальное действие в обёртку шага. Например:
public class FormPage {
SelenideElement btnSave = $(".btn-primary").as("Save");
}
Данный способ предоставит простейший автогенерированный лог на событие btnSave.click();
– что-то типа "Step1: Click Save".
Конечно, можно сказать, что любой сценарий состоит из последовательности простых действий. Может, в идеальном мире, так и было бы, но если попробовать написать тест для CRM, содержащей десятки таблиц, множество виджетов в каждой записи, сложные фильтрации, поисковые запросы, загрузки, импорты данных, динамические события и прочее – итоговый тест может состоять из 50–60 строк, где каждая строка является достаточно сложным шагом. После этого, взглянув на автогенерированный отчёт, спорить о его "легкочитаемости" будет крайне сложно.
Однако, если я хочу увидеть в отчёте нормальное описание шага, например:
"Click on the <Save> button of the <User settings dialog>"
то придётся писать обёртку:
@Step("Click on the <Save> button of the <User settings dialog>")
public void clickBtnSave() {
btnSave.click();
}
Либо можно, например, так:
public class FormPage {
SelenideElement btnSave = $(".btn-primary").as("the <Save> button of the <User settings widget>");
}
В отчёте это будет выглядеть лучше, но, во-первых, не настолько, а во-вторых, если эта кнопка располагается в каждой записи длинного списка пользователей, где каждая строка является сложным виджетом с множеством различных элементов, включая кнопку, мы получим ElementsCollection, отфильтруем её по какому-то устойчивому параметру, найдём нужные и нажмём на кнопку в каждой записи. Все действия в отчёте будут выглядеть одинаково.
В итоге мы вернёмся к большому недостатку BDD-подхода — будем плодить тонны обёрточных методов для каждого мизерного клика, а таких в web-приложениях может быть сотни. И это только для клика! Прибавьте другие действия, контекстные меню, операции с различными полями ввода, события, проверки от простого текста до html-атрибутов и т.д. и умножьте на количество элементов в приложении. Каждое малейшее действие требует конкретного описания, а если пойти по BDD-подходу, то тестовый проект может оказаться намного больше тестируемого приложения, что неоднократно подтверждалось на практике.
Еще большую боль, как с BDD-фреймворками, так и без них, доставляют операции с коллекциями элементов. Всевозможные поиски, фильтрации и проверки требуют конкретных описаний шагов, которые плодятся со страшной скоростью.
Например, в таблице данных пользователей может быть 30 и более параметров:
public class FormPage {
ElementsCollection btnSave = $$(".user-record").as("Users settings list");
}
Что если нам нужно сделать проверку каждого в отдельности из 30 параметров и для каждого создать отдельный метод с описанием? То есть:
public class FormPage {
ElementsCollection users = $$(".user-record").as("Users settings list");
public void assertEmail(String userId, String expectedEmail) {
String targetEmail = users.filter(Condition.text(userId)).first().$x("div[@id='#email-container']").text();
Assertions.assertThat(targetEmail).as("User email").isEqualTo(expectedEmail);
}
}
И делать так для остальных 29 параметров? Конечно, нет. Тут надо сказать, что следует создать DataObject с параметрами пользователя и написать универсальный метод, который будет брать этот объект и проходить по каждому полю, сравнивая все параметры посредством Soft Assert. Да, это здоровое решение, но всё равно придётся писать логику, а о красивом автологируемом отчёте можно забыть.
То, что Allure отчёты Selenide генерировались автоматически, меня не устраивало. Ведь спустя время, чтобы вспомнить какой-то сценарий и понять, что произошло, приходилось напрягаться даже мне, не говоря уже о том, если отчёт смотрит человек, не знакомый с кодом. Кроме того, в некоторых случаях отчёты приходилось делать не на английском языке.
Идеального инструмента не бывает, но я задумывался и пытался найти решение, которое позволит исключить большинство сложностей любого подхода. Мне хотелось сделать так, чтобы всё, что я делал, – это определял сущности страниц, виджетов и элементов, и писал сценарий, а всё остальное делал бы фреймворк сам. Таким образом мне почти не пришлось бы писать логику шагов, а она сама организовывалась бы на основании структуры кода и типов данных, не ограничивая свободный стиль Selenide. При этом фреймворк должен автоматически собирать и логировать все вызываемые действия, максимально приближая их к красивым BDD-отчётам и предоставляя возможность редактировать этот процесс по желанию.
Начиная с пункта 1, я хотел видеть примерно такую структуру. Я указываю аннотацией классу, что он является страницей. Элементы и виджеты страницы должны быть представлены соответствующими типами этих элементов.
@PageObject
public class FormPage {
@Name("Login")
@Locator(css = "#login")
protected TextField fieldLogin;
@Name("Email")
@Locator(css = "#email")
protected TextField fieldEmail;
@Name("Password")
@Locator(css = "#password")
protected TextField fieldPassword;
// ... другие поля
}
@PageObject
public class HomePage {
@Name("Navigation bar")
NavBar navBar;
@Name("Search block")
SearchBlock searchBlock;
@Name("Gallery block")
GalleryBlock galleryBlock;
@Name("Footer")
Footer footer;
}
@Widget
public static class NavBar extends AbstractWidget {
@Name("Images")
@Locator(css = ".navbar-nav .nav-item:nth-child(1)")
protected Link linkPhotos;
@Name("Stickers")
@Locator(css = ".navbar-nav .nav-item:nth-child(2)")
protected Link linkStickers;
@Name("Icons")
@Locator(css = ".navbar-nav .nav-item:nth-child(3)")
protected Link linkIcons;
}
Где:
@PageObject
— указывает, что класс является страницей;@Widget
— указывает, что класс является виджетом (частью страницы);@Name
— присваивает элементу или виджету конкретное название;@Locator
— один из способов указать путь до объекта на HTML-странице.Тестовый сценарий, как положено, не должен содержать логики, а представлять собой последовательное обращение к методам виджетов и элементов.
Я набросал следующую схему и стал работать над её реализацией.
Я решил не выпендриваться и просто сложил название из технологий в фундаменте – получилось "Allurium". Далее будут рассмотрены примеры, иллюстрирующие суть работы и устройство фреймворка.