Всем доброго времени суток. Давайте попробуем позаимствовать у UX/UI-дизайнеров методологию проектирования дизайн систем и применить ее в автоматизации тестирования. Начнем разбор с теоретической части. Если вы знаете что такое Atomic Design и Page Object, то можно смело пропускать первые 2 блока.
Что такое Atomic Design.
Методология Брэда Фроста для проектирования дизайн-систем. Она делит интерфейс на пять уровней абстракции — от базовых элементов (атомов) до целых страниц, делая компоненты гибкими и масштабируемыми.
Полный список абстракций:
Атомы(Atoms)
Молекулы(Molecules)
Организмы(Organisms)
Шаблоны(Templates)
Страницы(Pages)
Какие основные преимущества:
Чёткая организация. Каждый компонент имеет своё место в иерархии: атом, молекула, организм, шаблон или страница.
Переиспользование. Атом или молекулу достаточно описать один раз. Затем их можно повторно использовать в различных местах приложения.
Согласованность. Все UI-элементы объединены единым визуальным стилем и принципами. Изменение в атоме (например, цвета) автоматически применяется ко всем молекулам и организмам, где он используется.
Ускорение разработки. Из готовых наборов «кирпичиков» (атомов, молекул, организмов), можно быстро собирать новые страницы и шаблоны.
Ну и куда же без недостатков:
Подходит не для всех типов проектов. Хорошо работает для дизайн-систем и UI-библиотек, но может быть избыточен для больших бизнес-приложений, где имеется сложная логика и структура.
Размытая граница между уровнями. В реальных проектах порой сложно определить, что относить к молекулам, а что к организмам.
Отсутствие бизнес-ориентированности. Подход, в первую очередь, решает вопросы UI. Организацию бизнес-логики в проекте он не затрагивает.
Более детально про Atomic Design можно прочитать книгу автора тут.
Что есть Page Object.
Page Object Model («объектная модель страницы»), также известная как POM, — это шаблон проектирования в Selenium, который создаёт хранилище объектов для управления веб-элементами. Он помогает сократить дублирование кода и упрощает поддержку тест-кейсов.
Взято с просторов хабра.(не реклама). Теоретической части для ознакомления достаточно.
Так же есть еще варианты, когда локаторы/селекторы выносятся в отдельный класс/enum, но сути это не меняет.
Что есть общего у Page Object и Atomic Design.
На первый взгляд кажется что общего маловато, но это не совсем так.
Давайте сравним, что есть в Page Object и чего нет в Atomic Design:
Page Object |
Atomic Design |
|
Атомарная единица |
локатор, селектор и т.д. |
Элемент интерфейса |
Взаимодействие с атомарной единицей |
Вспомогательные классы для стандартизации работы с типовыми элементам(полями ввода, кнопками и т.д.) |
Компоненты, собранные из атомов. |
Работа в контексте страницы |
Page классы, в которых мы описываем методы работы. |
Страница, которая "собирается" из всех пяти абстрактных групп |
Судя по тому, что мы имеем в таблице, Atomic Design содержит в себе все то, что есть в Page Object.
Почему именно Atomic Design?
Так как мы говорим про паттерн который применяется для написания UI тестов, почему бы не заимствовать то, чем уже пользуются UX/UI-дизайнеры и фронтенд-разработчики?
Давайте посмотрим из чего состоит самый простой интерфейс страницы:
Шапка (header)
Верхнее меню
Тело (body)
Боковое меню (sidebar)
Подвал (footer)
А теперь давайте ответим на вопрос: "А что из перечисленного выше списка, чаще всего меняется при переходе на другую страницу?". В 90% случаев будет первым назовут: "Тело(body)". И если нам необходимо использовать верхнее меню, боковое меню или любой другой статичный блок, то вписаться в концепцию Page Object'a уже не особо получается.
Недостатки Page Object.
На основе личного опыта, выделил несколько недостатков.
Невозможность повторного использования одних и тех же элементов, на разных страницах.
Страницы, с большим количеством степов, превращаются в огромную портянку и поддерживать ее весьма муторно.
Взаимодействие со статическими блоками страницы, превращаются в попытку усидеть на двух стульях(соблюдение паттерна и при этом сделать нормальный читаемый page/test)
Сложность в понимании локаторов/селекторов элемента, когда их становится очень много.(они могут быть вынесены в отдельные переменные или нет).
Дублирование локаторов/селекторов и методов, при наличии динамических форм.(как следствие из первого пункта)
Если пункт 2 не нуждается в пояснении(как мне кажется), то с остальными пунктами примеры будут не лишними.
Начнем с пункта 1.
Допустим у нас есть кнопка "Save" и кнопка "Cancel", которая применяется во всех формах, где присутствует редактирование.
String saveButton = "//*[@name='save']"; String cancelButton = "//*[@name='cancel']"; @Step public void clickSave() { elementIsVisible(saveButton); clickOnElementByXpath(saveButton); } @Step public void clickCancel() { elementIsVisible(cancelButton); clickOnElementByXpath(cancelButton); }
То есть, у нас получается так, что каждая страница где у нас есть создание/редактирование будет иметь вот такие одинаковые куски кода. А если мы еще добавим сообщение об успешном сохранении, то это еще + 50% от того кода, что выше. А если добавить такой момент, что name=‘save’ решили поменять на name=‘apply’ - прелесть. Осталось понять, сколько страниц надо поправить. И это только самые очевидные вещи.
Пункт 3.
Допустим, мы находимся на главной странице и нам нужно нажать на пункт сайдбара "Пользователи". Чаще всего, сайдбар описывается, как обычный page и выглядит в тесте примерно так
@Test pubcli void navigateToUsersPage() { mainPage .checkMainPageIsOpened; sidebar .openUsersPage; usersPage .checkUsersPageIsOpened; }
С практической точки зрения - придраться не к чему. Так много кто делает и я сам так делал, потому что никому не хочется переписывать меню на каждой page. Но если взглянуть с точки зрения того, что мы использовали сайдбар в контексте главной страницы, то по идее мы должны были вызвать взаимодействие с меню через mainPage, как говорит нам Page Object.
Теперь п4.
В этом пункте, на самом деле, есть 2 вариации.
Локаторы/селекторы вынесены в отдельные переменные. Здесь ключевую проблему играет нейминг переменных, когда их у тебя 50 и более. Попробуй придумать им нейминг так, чтобы через пол года сходу вспомнить что они значат. Если вы так можете - поздравляю. У вас такой проблемы нет. Но у большинства они есть.
Локаторы/селекторы внутри степов. Давайте сразу с примера:
@Step public void clickSaveAddressButton() { clickOnElementByXpath("//button[@name='Ok']") } @Step public void clickSaveAppointmentsButton() { clickOnElementByXpath("//button[@name='Approve']") }
В этом примере у нас есть всего 2 кнопки. Только вот ответить на вопрос: "Относятся ли кнопки к одной форме редактирования или нет?" - нет, пока не посмотришь в тесты, ну или в код страницы(Elements в dev-tools). А если у нас степов 50+ ?
И напоследок, пункт 5.
Эту боль знает каждый, кому приходилось автоматизировать формы, анкеты, заявления в которых есть конфигурация.
Представим, у нас есть форма заявления на кредитную карту. В зависимости от того, является наш клиент зарплатным или нет, форма может отличаться. А если, допустить что есть еще короткая и полная форма заявления. Если смотреть с точки зрения Page Object, то нам приходится делать разные формы в зависимости от 2 параметров:
isSalary=true/false
isShortForm=true/false
То есть, как минимум, уже 4 страницы надо описать. Некоторые локаторы/селекторы и методы будут гарантировано дублироваться. А если добавляем еще возможность добавления/удаления форм через админку - уже звучит больно. Да, есть мысли что можно вынести общие локаторы или сделать одно хранилище в виде класса/enum, но будет каша. Так же можно выносить общие методы во вспомогательные классы, но это тоже будет каша, но из методов. Приходилось наблюдать такие решения. Да, оно работает. Правда, поддерживать такое - неприятно.
Ключевые сущности в тестовом фреймворке при использовании Atomic Design.
Давайте сразу определимся что есть что:
Атом |
Это наши локаторы, селекторы и все чем мы можем идентифицировать элемент на странице |
Молекула |
Это компонент, использующий наши атомы, чтобы взаимодействовать с UI. |
Организм |
Это форма "в сборе" из атомов и молекул. |
Шаблон |
Это "каркас" страницы с его статическими блоками. |
Страница |
Это все перечисленное выше, собранное в единое целое. |
Атом. Для их хранения проще всего использовать отдельный класс. Можно использовать и енам, если нужно иметь краткое описание поля. Такой подход весьма неплохо себя показал, если специфика продукта весьма сложная, а обмазывать комментариями каждый локатор/селектор не хочется.
Молекула. Это ни что иное как унифицированный метод, для работы с определенным элементом. Для примера возьмем обычное поле ввода:
public static void fillInput(String xpath, String text) { page.locator(xpath).fill(text); }
Может также может быть ситуация, когда мы используем уже готовые методы, чтобы из них собрать чуть более сложную молекулу. Примером может быть выпадающий список с поиском(список содержит уникальный набор элементов):
public static void searchElementAndSelectFromDropdown(String xpath, String text) { clickOnElement(xpath); fillInput(text) clickOnElementFromListByText(text); }
Можно возразить, что молекула состоящая из молекул и как-то не вписывается в эту концепцию. И да, вы будете правы. Но если специально дублировать код, чтобы вписаться в концепцию "atomic design" - выглядит это еще хуже.(а такого нам не надо).
Организм. Это готовая форма для использования. Пример:
public void fillPersonalData(String firstname, String lastname, String passport, String inn) { fillInput(PERSONAL_DATA_FORM_FIRSTNAME, firstname); fillInput(PERSONAL_DATA_FORM_LASTNAME, lastName); fillInput(PERSONAL_DATA_FORM_PASSPORT, passport; fillInput(PERSONAL_DATA_FORM_INN, inn); }
Выглядит это примерно так, как и обычный степ. Собирать элементы в группу или в каждом методе описывать взаимодействие с каждым отдельно - на ваш вкус. Можно хоть взять пример выше и передавать вместо кучи полей 1 объект user. Принципиальной разницы тут нет.
Шаблон. Если вы когда-то выносили меню, футер, шапку или что-то иное, в отдельный класс - поздравляю, вы уже использовали часть шаблона.
Страница. Здесь мы уже берем конкретный шаблон, упаковываем нужные нам "организмы" в "тело" страницы и собираем из них тесты(как у обычного Page Object)
Итог.
На мой взгляд, такой подход имеет право на существование, так как помогает решить следующие проблемы:
создание "конструктора" форм и страниц, для максимально простого масштабирования и компоновки форм, где ограничением для нас является только бизнес логика.
Исключение дублирования локаторов/селекторов и их методов.
Предотвращение "разбухания" страниц путем разделения на слои(организм, шаблон, страница)
P.S. Не считаю что Page Object плохой и его не нужно использовать. По совокупности своих свойств, у него есть много плюсов. Цель статьи, развить "классику" в нечто большее, чтобы устранить некоторые недостатки. Это решение не претендует на звание "silver bullet" и не должно таковым быть.
P.P.S. В следующей статье более детально разберу кейсы применения.