Вступление

Java testing framework for streamlined and efficient test creation.

В этой статье хочу поделиться инструментом, который я понемногу разрабатывал и улучшал при наличии свободного времени. Применяя для упрощения своих задач, над которыми я работал в разных компаниях за это время.

Работа в автоматизации тестирования

Работа в автоматизации тестирования стала моей оффициальной профессией несколько лет назад. За это время мне довелось позаниматься очень широким спектром задач, в зависимости от компаний, где я работал. Начиная с тестирования пользовательских интерфейсов различных сайтов, web и мобильных приложений, обязанности заводили меня глубоко в тестирование сложных API сервисов и даже автоматизацию десктопных программ.

Первые годы я плотно занимался тестированием Web приложений на Java, хотя начинал не с неё. Разумеется, перепробовав много подходов, я нашёл для себя любимый stack, который применял при наличии свободы выбора. Для меня это сложилось в TestNg + Selenium + Allure. Естественно, мне приходилось работать со знаменитым Cucumber, а потом и вообще с чудовищьным (на мой взгляд) Serenity и ещё многими другими решениями, включая кастомные разработки разных людей.

Отказ от BDD и сложности автологирования

Моё расположение никогда не лежало в сторону BDD, так как чаще всего я сам читал отчёты и проверял тесты, и в 100% случаев сам составлял сценарии с Gherkin (иногда по предоставленным сценариям, чаще нет). Так называемое удобство, дающее BDD пользователю, сводилось к минимуму, а вот взамен требовало многое — небоскрёбы из обёрток классов, сложности с наследованием, необходимость создавать кучу дубликатов мизерных действий, абсолютное неудобство передачи данных между шагами и процессами и прочие трудности.

Появление Selenide и автологирования шагов

Потом пришёл 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

То, что Allure отчёты Selenide генерировались автоматически, меня не устраивало. Ведь спустя время, чтобы вспомнить какой-то сценарий и понять, что произошло, приходилось напрягаться даже мне, не говоря уже о том, если отчёт смотрит человек, не знакомый с кодом. Кроме того, в некоторых случаях отчёты приходилось делать не на английском языке.

Цель создания Allurium

Идеального инструмента не бывает, но я задумывался и пытался найти решение, которое позволит исключить большинство сложностей любого подхода. Мне хотелось сделать так, чтобы всё, что я делал, – это определял сущности страниц, виджетов и элементов, и писал сценарий, а всё остальное делал бы фреймворк сам. Таким образом мне почти не пришлось бы писать логику шагов, а она сама организовывалась бы на основании структуры кода и типов данных, не ограничивая свободный стиль Selenide. При этом фреймворк должен автоматически собирать и логировать все вызываемые действия, максимально приближая их к красивым BDD-отчётам и предоставляя возможность редактировать этот процесс по желанию.

Мои рассуждения
  1. Нужно взять стандартную и понятную структуру, но добавить больше конкретики.
  2. Каждая сущность должна иметь ожидаемые свойства и методы поведения, большинство из которых реализованы сразу из коробки.
  3. Каждая сущность, будь то страница, виджет или элемент, должна понимать, где она находится и к чему принадлежит.
  4. Все сущности должны быть легко расширяемыми; должна быть возможность добавлять любые кастомные элементы, компоненты и доработки.
  5. Фреймворк должен на 99% самостоятельно собирать метаданные объектов.
  6. Фреймворк должен опираться на Allure и Selenide, не ограничивая их возможности.
  7. Я должен наконец избавиться от поиска элементов через фильтрацию, а вообще от поиска в списках, и забыть как о страшном сне.
  8. Код должен повторяться минимум (DRY). Даже объекты страниц должны быть легко расширяемыми и переиспользуемыми.
  9. Фреймворк должен включать множество заранее реализованных действий пользователя с браузером, которые логируются в отчёты.
  10. Я должен мочь редактировать логируемые шаги и определять степень их подробности динамически.
  11. И, наконец, красивые отчёты должны собираться на разных языках, а не только на английском.
Структура PageObject

Начиная с пункта 1, я хотел видеть примерно такую структуру. Я указываю аннотацией классу, что он является страницей. Элементы и виджеты страницы должны быть представлены соответствующими типами этих элементов.

PageObject с простыми элементами

@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 с виджетами

@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;
}
      

Где:

Тестовый сценарий, как положено, не должен содержать логики, а представлять собой последовательное обращение к методам виджетов и элементов.

Я набросал следующую схему и стал работать над её реализацией.

Я решил не выпендриваться и просто сложил название из технологий в фундаменте – получилось "Allurium". Далее будут рассмотрены примеры, иллюстрирующие суть работы и устройство фреймворка.