UI автотесты на Python с запуском на CI/CD и Allure отчетом. PageObject, PageComponent, PageFactory
Разбираем, как писать масштабируемые и читаемые UI автотесты на Python с использованием паттернов PageObject, PageFactory и PageComponent. Разберем на атомы как устроены эти подходы, когда их применять и чем они отличаются. Всё это — на примере тестового проекта UI Course с CI/CD и наглядными отчетами в Allure.
Вступление
В этой статье мы разберем, как писать UI автотесты на Python, используя проверенные подходы и лучшие практики автоматизации. Мы поэтапно рассмотрим ключевые паттерны, такие как PageObject, PageComponent и PageFactory, подробно объясняя, когда и зачем они применяются, а также чем они отличаются друг от друга.
Цель статьи — не просто показать работу с этими паттернами, а разобрать их «на атомы» и объяснить, как они помогают сделать тесты масштабируемыми, читаемыми и легко поддерживаемыми.
Для практики мы будем использовать учебное веб-приложение UI Course. Его исходный код доступен в моём GitHub-репозитории: UI Course GitHub.
Для запуска тестов в CI/CD мы будем использовать GitHub Actions, но все примеры легко адаптируются под другие системы, такие как Jenkins, GitLab CI или CircleCI — отличия будут касаться лишь синтаксиса и конфигурации пайплайнов.
Сценарий
Прежде чем погрузиться в реализацию паттернов и архитектуры автотестов, давайте определим, какой именно сценарий мы собираемся автоматизировать.
Намеренно выберем максимально простой и понятный сценарий, чтобы сосредоточиться на структуре автотестов, а не на логике приложения. Это поможет лучше понять, как применяются паттерны PageObject, PageComponent и PageFactory на практике.
Сценарий будет включать следующие шаги:
- Открытие страницы регистрации: /#/auth/registration
- Ввод данных нового пользователя: Email, Username, Password.
- Нажатие кнопки "Registration" и проверка перехода на страницу "Dashboard": /#/dashboard
- Проверка наличия всех ключевых элементов на странице "Dashboard".
Технологии
Перед тем как приступить к реализации, определим инструменты и библиотеки, с которыми будем работать:
- Python 3.12 — для написания автотестов
- Playwright — инструмент для взаимодействия с браузером, управления страницами и проверки UI.
- Pytest — тестовый фреймворк
- Pytest Playwright — интеграция Playwright с pytest, позволяет запускать тесты с фикстурами и настройками Playwright.
- Pydantic Settings — удобная работа с конфигурацией проекта через переменные окружения и .env файлы.
- Allure — для генерации детализированного отчёта
Почему Playwright, а не Selenium?
На момент написания статьи выбор между Playwright и Selenium — это выбор между современностью и устаревшими решениями. Ниже приведены аргументы в пользу Playwright.
Преимущества Playwright:
- Скорость и надёжность. Playwright работает напрямую через DevTools Protocol, в отличие от Selenium, который использует прослойку WebDriver. Это делает Playwright быстрее и менее подверженным флаки-тестам.
- Простая установка. Установка Playwright — это буквально две команды: pip install playwright pytest-playwright, playwright install. В случае с Selenium всё куда сложнее: нужно вручную скачивать драйверы под конкретные браузеры и следить за их актуальностью.
- Видео и скриншоты "из коробки". Playwright умеет записывать видео без дополнительной настройки. Это сильно упрощает отладку.
- Встроенные ожидания. Каждый метод Playwright уже содержит встроенные ожидания (waits). Не нужно писать кастомные ожидания и “костыли”, как часто бывает в Selenium.
- Playwright Trace Viewer. Уникальный инструмент, позволяющий в деталях анализировать каждый шаг выполнения теста. Особенно полезен при отладке и CI-ошибках.
- Асинхронность. Playwright изначально асинхронный (на базе asyncio), что позволяет эффективно масштабировать тесты. Есть и синхронная обёртка, но под капотом всё работает асинхронно.
- Поддержка WebKit. В отличие от Selenium, Playwright поддерживает тестирование в браузере WebKit (на котором основан Safari), что критично для кросс-браузерного тестирования.
- Перехват и моки запросов. Playwright позволяет мокать и подменять HTTP-запросы, что полезно для изоляции UI-слоя при тестировании.
- Поддержка нескольких вкладок и фреймов. Работа с несколькими вкладками и iframes в Playwright реализована гораздо удобнее, чем в Selenium.
- Интуитивный API. API у Playwright современный, читаемый и минималистичный — в отличие от Selenium, где код часто перегружен.
Почему Selenium всё ещё используют?
Несмотря на устаревание, Selenium по-прежнему встречается в проектах. Основные причины:
- Неосведомленность. Многие специалисты просто не знакомы с Playwright или не успели глубоко его изучить.
- Наследие. В компаниях уже написаны сотни и тысячи тестов на Selenium. Переписывать — дорого, долго и страшно.
- Переход в процессе. Некоторые команды уже планируют или начали миграцию, но процесс небыстрый.
- Рынок вакансий. Selenium всё ещё встречается в требованиях, особенно в legacy-проектах. Новички делают выбор в его пользу из-за "частотности", не зная, что это временно.
- Сопротивление новому. Есть компании и специалисты, которые придерживаются "если работает — не трогай", и не спешат менять инструменты.
Вывод
Playwright — это современный, мощный и удобный инструмент, который уже сегодня вытесняет Selenium из новых проектов. Если вы только начинаете или планируете развивать тестовую инфраструктуру — смело делайте выбор в пользу Playwright. Его функциональность, простота и активное развитие говорят сами за себя.
Паттерны автоматизации UI-тестов
Чтобы автотесты были поддерживаемыми, читаемыми и масштабируемыми, важно не просто писать тест-кейсы "как получится", а использовать проверенные архитектурные подходы — паттерны автоматизации.
В этой статье мы будем использовать сразу три ключевых паттерна: PageObject, PageComponent и PageFactory. Вместе они создают иерархичную, гибкую и масштабируемую структуру для тестов.
PageObject
PageObject — это самый распространённый и базовый паттерн в UI-автоматизации. Суть его в том, чтобы представить каждую страницу (или экран, если речь о мобильном приложении) в виде класса.
- Атрибуты класса — это элементы страницы (кнопки, поля ввода и т.д.).
- Методы класса — действия с этими элементами (клик, ввод текста, проверка состояния и т.д.).
Таким образом, тест больше не "размазывается" по локаторам и сырым действиям, а обращается к странице как к объекту:
Page Object решает задачу структурирования страниц, но он не даёт универсального подхода к переиспользуемым компонентам, которые встречаются на разных страницах.
PageComponent
PageComponent — следующий уровень абстракции. Он нужен тогда, когда в интерфейсе есть повторяющиеся компоненты, которые встречаются на разных страницах. Например:
Если такой компонент вшивать напрямую в PageObject, то возникает дублирование и костыли: один и тот же Navbar реализуется на каждой странице по-своему, хотя логика у него одна и та же.
PageComponent решает это через композицию:
- Каждый компонент оформляется в виде отдельного класса.
- Внутри класса описываются атомарные элементы и действия, связанные с этим компонентом.
- Компоненты встраиваются внутрь PageObject'ов как атрибуты, используя композицию.
Пример:
Таким образом, логика компонентов не дублируется, и можно централизованно поддерживать и переиспользовать их поведение.
PageFactory
PageFactory — менее известный паттерн, особенно в Python-сообществе. Чаще он встречается в Java-проектах, но с лёгкостью адаптируется и под Python.
Если PageObject — про страницы, PageComponent — про компоненты, то PageFactory — про атомарные элементы.
Суть в том, чтобы каждый элемент на странице — будь то кнопка, инпут, иконка, текст, чекбокс — представить в виде объекта одного типа, с универсальным поведением.
Этот инпут не знает ничего о странице или компоненте, в котором он находится — он просто умеет взаимодействовать с конкретным элементом DOM.
Такой подход позволяет:
- убрать дублирование поведения для однотипных элементов,
- централизовать логику (например, ввод текста, очистку, валидацию),
- и, главное, переиспользовать атомарные элементы в любых PageObject или PageComponent.
Почему это мощно?
Вместе три паттерна образуют многоуровневую архитектуру, применимую к любым пользовательским интерфейсам:
Эта структура легко масштабируется на:
- веб-приложения (как в нашем случае),
- мобильные приложения (через Appium, например),
- десктопные интерфейсы.
Логирование
Перед тем как приступить к написанию PageObject, PageComponent и PageFactory, важно сразу внедрить единое логирование. Это поможет:
- быстрее находить причину падений,
- анализировать тесты на CI/CD,
- понимать, какие действия происходили на каждом этапе.
В каждом компоненте, странице или тесте можешь создать логгер вот так:
А дальше логировать действия:
PageFactory
Несмотря на то, что паттерн PageFactory находится на самом нижнем уровне архитектуры (после PageObject и PageComponent), реализацию стоит начинать именно с него. Причина проста — страницы и компоненты будут состоять из атомарных элементов (кнопок, текстов, полей ввода и т.д.), и наличие абстракции этих элементов необходимо уже на начальном этапе.
Реализуемые элементы:
- Button — элемент кнопки
- Input — поле ввода
- Text — текст на странице
- Image — изображение
- Link — ссылка
Базовый класс BaseElement
Все элементы на странице (будь то кнопка, текст или поле ввода) имеют общую базовую функциональность: получение локатора, проверка видимости, клик, проверка текста и другое. Вынесем эту логику в отдельный класс BaseElement, от которого будут наследоваться остальные элементы.
- Конструктор класса — принимает страницу, локатор (data-testid) и название элемента
- type_of — позволяет переопределить "тип" элемента для логов и аллюра
- get_locator — получает Locator через data-testid, поддерживает индексацию и шаблоны
- click — кликает по элементу
- check_visible — проверяет, что элемент отображается на странице
- check_have_text — проверяет, что элемент содержит указанный текст
На основе BaseElement будут построены специализированные элементы: Button, Input, Text, Image. Они добавят свою специфику (например, метод fill() для инпута) и помогут сделать тесты максимально читаемыми.
Важность использования кастомных data-testid атрибутов для локаторов
При автоматизации тестирования важно правильно выбирать подходы к локаторам элементов на странице. Обратите внимание на то, как инициализируется локатор в примере кода: self.page.get_by_test_id(locator).nth(nth). Этот подход использует атрибуты data-testid, которые помогают однозначно идентифицировать элементы на странице.
Почему лучше использовать кастомные атрибуты, такие как data-testid?
Настоятельно рекомендую использовать именно кастомные атрибуты типа data-testid, qa-id, data-test-id, qa-data-id и другие подобные. Важно понимать, что название атрибута не имеет значения, главное — это его предназначение: обеспечение уникальной и стабильной идентификации элементов на странице.
Использование кастомных атрибутов вместо универсальных подходов, таких как CSS-селекторы или XPath, имеет несколько преимуществ:
- Стабильность: В отличие от XPath или CSS-селекторов, которые могут зависеть от структуры HTML или классов, атрибуты data-testid независимы от дизайна страницы и редизайнов. Если изменения касаются внешнего вида или структуры, тесты с data-testid не потребуют переписывания локаторов.
- Читаемость и поддерживаемость: Тесты, использующие кастомные атрибуты, легко читаемы и поддерживаемы. Вам не нужно анализировать сложные цепочки XPath или идентификаторы классов, чтобы понять, что именно вы тестируете. Это позволяет улучшить удобство работы как для тестировщиков, так и для разработчиков.
- Избежание споров о локаторах: Одним из самых популярных вопросов является, что лучше использовать для поиска элементов — CSS или XPath? Ответ: ни одно, ни другое не является идеальным. Кастомные атрибуты qa-id или data-testid — это наиболее надежное решение. Они позволяют избежать долгих споров и создают стабильную основу для локаторов.
Зачем это нужно?
Задумайтесь: что лучше — потратить неделю или две на добавление кастомных атрибутов по всему приложению, или же в дальнейшем сталкиваться с постоянными проблемами с тестами и локаторами?
Без использования кастомных атрибутов локаторы в виде длинных и сложных CSS-селекторов или XPath могут быть подвержены постоянным изменениям, особенно при редизайне страниц. Это приведет к тому, что тесты будут ломаться и требовать частых изменений, что затруднит масштабирование и поддержку тестов.
С другой стороны, потратив неделю или две на добавление атрибутов data-testid или аналогичных по всему приложению, вы создадите надежную базу для написания стабильных тестов, которые не потребуют изменений после каждого редизайна.
Как добавить data-testid?
Для успешного использования этого подхода есть два пути:
- Попросить фронтенд-разработчиков: Фронтенд-разработчики, будь то React, Vue, Angular или даже мобильные разработки, прекрасно понимают важность и простоту добавления кастомных атрибутов. Попросите их добавить уникальные атрибуты в ваш проект.
- Сделать это самостоятельно: На самом деле, добавление кастомных атрибутов несложно, и вы можете научиться делать это сами. Это поможет вам получить больший контроль над тестами и улучшить их стабильность.
Почему важно?
Использование кастомных qa-id атрибутов — это основа, на которой строятся хорошие практики автоматизации тестирования UI. К сожалению, многие команды игнорируют этот подход и пытаются обходиться без него, что приводит к созданию ненадежных и трудных в поддержке тестов. В результате такие тесты становятся нестабильными, а время на их поддержку растет.
Например, в мобильной автоматизации тестирования автотесты без использования кастомных атрибутов для идентификации элементов вообще не пишутся. Это становится обязательным стандартом, позволяющим повысить стабильность и предсказуемость тестов.
Итог
Итак, кастомные атрибуты — это не просто опция, а необходимая база для стабильных и поддерживаемых тестов. Использование data-testid или аналогичных атрибутов позволяет вам создать четкую и надежную систему локаторов, которая избавит от необходимости переделывать тесты при каждом изменении дизайна. Потратить неделю на их внедрение — это гораздо меньший ресурс, чем потом тратить месяцы на исправление тестов, которые постоянно ломаются.
Класс Button
Класс Button представляет собой элемент кнопки на странице и является расширением базового элемента BaseElement. В этом классе добавляются методы, специфичные для кнопки, такие как проверка состояния кнопки (включена или выключена). В остальном, структура класса и методы аналогичны классу BaseElement.
Класс Button — это расширение базового класса BaseElement, который предназначен для работы с кнопками на веб-странице. В классе реализованы специфичные для кнопки методы, такие как проверка состояния активности кнопки. Этот подход позволяет централизованно хранить логику работы с элементами интерфейса и повторно использовать ее в разных частях тестов.
Имплементация классов для других элементов страницы, таких как поля ввода, текстовые блоки и изображения, делается аналогичным образом. Основное отличие — это добавление методов, специфичных для каждого типа элемента, которые позволяют тестировать конкретное поведение на странице (например, активность кнопки или текстовое содержимое элемента).
Input
Класс Input расширяет базовый класс BaseElement и используется для взаимодействия с полями ввода на странице. Этот класс включает методы, которые позволяют заполнять поля ввода и проверять их значения.
Text
Image
Link
Все три класса (Text, Image, и Link) реализуют минимальное расширение от BaseElement. Они все переопределяют метод type_of, чтобы указать тип соответствующего элемента на странице. В остальном, они могут использовать общие методы из базового класса для взаимодействия с элементами на странице, такие как проверка видимости и другие базовые операции.
Это позволяет нам создавать универсальные и расширяемые элементы, которые можно использовать в тестах, не создавая для каждого нового типа элемента дополнительные повторяющиеся методы.
Преимущества использования PageFactory в автоматизации тестирования
1. Семантическое разделение
Использование PageFactory помогает перейти от работы с абстрактными локаторами к конкретным элементам на странице. Вместо того чтобы манипулировать неясными локаторами, мы работаем с элементами, которые явно представляют собой кнопки, поля ввода и другие видимые компоненты. Это делает код более читаемым и понятным.
Пример: Если вам нужно взаимодействовать с кнопкой "Submit", вы прямо указываете, что работаете именно с кнопкой, а не с абстрактным локатором. Это упрощает понимание теста, даже если его читает новый разработчик или тестировщик.
2. Инкапсуляция
PageFactory инкапсулирует логику взаимодействия с элементами. Каждый элемент имеет свои методы, такие как click или check_visible, которые выполняют всю необходимую логику внутри себя. Это упрощает работу, так как не нужно беспокоиться о мелких деталях, таких как ожидание видимости элемента или правильный клик. Вместо этого, каждый метод становится единым источником истины.
Пример: Чтобы проверить видимость элемента, можно использовать несколько способов:
- assert locator.is_visible()
- expect(locator).to_be_visible()
- locator.wait_for(state='visible')
Все эти методы делают одно и то же, но верный вариант — expect(locator).to_be_visible(), потому что он корректно ожидает появления элемента. Если элемент не появляется, будет выброшена понятная ошибка AssertionError. Используя PageFactory, мы гарантируем, что все тесты используют одинаковый способ проверки видимости, что предотвращает возможные ошибки и несоответствия в реализации.
Без PageFactory такая ситуация может привести к хаосу в проекте: каждый тестировщик может использовать свой способ проверки видимости, что приведет к дискуссиям и увеличению времени на поддержание кода. В PageFactory логика централизована, и все тесты используют один метод, что гарантирует правильность работы и упрощает сопровождение кода.
3. Разделение интерфейсов
В PageFactory каждый элемент предоставляет строго определённый набор методов, соответствующих его типу. Например, для чекбокса будет доступен метод для изменения его состояния, а для поля ввода — метод для ввода текста. Это предотвращает ошибки, такие как попытки применить методы, которые не подходят для конкретного элемента.
Пример: Если мы работаем с полем ввода, то элемент будет содержать только методы, соответствующие полю ввода, такие как fill(). Если попытаться вызвать метод изменения состояния чекбокса, это вызовет ошибку, что предотвращает использование некорректных действий для различных типов элементов.
4. Логирование и Allure шаги
PageFactory предоставляет встроенные возможности для добавления логирования и шагов в отчет Allure. Это позволяет нам автоматически генерировать читаемые шаги для всех действий с элементами, делая отчет более информативным.
Пример: При клике на кнопку или проверке видимости элемента, Allure шаг может выглядеть так:
Мы можем явно указать, с каким элементом работаем, что делает отчет более понятным для автоматизаторов, тестировщиков и даже бизнес-аналистов. При добавлении нового элемента достаточно создать новый класс, переопределить свойство type_of и новый элемент автоматически будет работать с Allure и логированием.
5. Переопределение функциональности
PageFactory дает возможность переопределить функциональность для конкретных элементов. Это открывает гибкость в случае, если стандартный способ взаимодействия с элементом не подходит. Например, если на странице есть поле ввода, требующее уникальной логики для поиска локатора, мы можем просто переопределить метод get_locator.
Пример: Если на странице есть сложное поле ввода, которое требует печать с клавиатуры для работы с ним, мы можем создать новый элемент KeyboardInput, переопределить метод fill() для работы с клавиатурным вводом и использовать его в тестах.
6. Динамическое форматирование локаторов
PageFactory поддерживает динамическое форматирование локаторов, что решает проблему работы с динамическими элементами на странице. Вместо того чтобы создавать множество разных локаторов или методы для их форматирования, мы можем использовать шаблоны и передавать параметры в метод.
Пример:
Здесь index будет подставлен в строку локатора, создавая динамически нужный локатор, например, dynamic-input-100.
7. Вербозность и ясность отчетов
PageFactory позволяет не только добавить шаги в Allure, но и логировать их с полным контекстом. Это означает, что отчеты и логи будут максимально понятными. Например:
Такой подход делает отчеты и логи более информативными и полезными для всех участников процесса — как для автоматизаторов, так и для тестировщиков или бизнес-аналистов.
8. Фокус на бизнес-логику
Все преимущества, описанные выше, позволяют нам сосредоточиться на тестировании бизнес-логики, а не на решении технических вопросов, таких как динамическое форматирование локаторов или логирование. Все это вынесено на уровень PageFactory, что позволяет значительно упростить процесс написания тестов.
9. Простота
Последним, но не менее важным преимуществом является простота реализации PageFactory. Этот подход не требует сложных техник или магии, все сделано с использованием стандартных принципов ООП. Это делает подход универсальным и легко адаптируемым для любых языков программирования, будь то Python, Java или JavaScript, а также для мобильных тестов.
PageComponent
Теперь реализуем компоненты, которые понадобятся для написания сценариев. Мы будем применять паттерн PageComponent — он позволяет логически структурировать страницу, разбивая её на отдельные, переиспользуемые части (компоненты). Внутри компонентов мы будем использовать элементы, реализованные с помощью PageFactory.
Такая архитектура делает тесты более читаемыми и масштабируемыми. Нам понадобятся несколько компонентов:
- Форма регистрации
- Тулбар на странице панели управления
- Компонент графика/чарта
- Навигационная панель (Navbar)
BaseComponent
Все компоненты будут наследоваться от BaseComponent. Это базовый класс, который задаёт интерфейс для всех дочерних компонентов и предоставляет базовые методы, общие для всех. Как и любой паттерн, PageComponent всегда начинается с реализации базового компонента.
BaseComponent сам по себе не описывает конкретный участок интерфейса, он служит основой для остальных компонентов.
- BaseComponent принимает в конструкторе объект Page от Playwright и сохраняет его как self.page. Это позволит любому дочернему компоненту обращаться к странице напрямую.
- Метод check_current_url — удобный базовый метод, который позволяет проверять текущий URL с использованием регулярного выражения. Это может пригодиться для проверки, что мы действительно на нужной странице.
- Метод использует:Allure для отчёта: добавляется шаг с описанием проверки.Логгер для записи информации в файл или консоль.Playwright expect() для ожидания соответствия URL.
RegistrationForm
Реализуем компонент формы регистрации — RegistrationFormComponent. Он будет отвечать за взаимодействие с полями ввода email, username и password. Этот компонент унаследован от BaseComponent и использует элементы типа Input, реализованные в PageFactory.
Компонент предоставляет два основных метода:
- fill — для заполнения формы
- check_visible — для проверки, что форма отобразилась и все значения в ней соответствуют ожидаемым
Важно! В методах fill и check_visible параметры передаются напрямую. Это нормально для маленьких форм. Однако если полей становится больше, или компоненты вложены, лучше использовать @dataclass для группировки параметров:
Тогда метод fill будет выглядеть так:
Такой подход:
- упрощает передачу параметров
- снижает вероятность ошибок при передаче значений
- повышает читаемость тестов
В нашем примере это не критично, но полезно запомнить при работе с более сложными формами и вложенными структурами.
DashboardToolbarViewComponent
Этот компонент отвечает за проверку видимости тулбара (панели инструментов) на дашборде. Основной его задачей является проверка заголовка панели — текста Dashboard.
Он может использоваться, например, сразу после логина или при переходе на основную страницу, чтобы убедиться, что пользователь действительно оказался на дашборде.
- Мы используем Text, унаследованный от BaseElement, поэтому у нас уже встроены шаги Allure и логирование.
- Метод check_visible включает две проверки:элемент отображается на страницетекст элемента соответствует ожидаемому значению "Dashboard"
Это — хорошая базовая проверка, которая может использоваться во многих smoke-тестах или sanity-check'ах после авторизации.
ChartViewComponent
Компонент, представляющий график (чарт) на странице. Используется для проверки отображения заголовка и самого графика по заданному identifier и chart_type.
- Использование identifier и chart_type позволяет переиспользовать компонент для различных графиков на странице.
- В методе check_visible мы валидируем два элемента: заголовок и сам график.
- Шаг allure динамически подставляет title, что делает отчёт понятным.
NavbarComponent
Компонент навигационной панели. Отвечает за отображение заголовка приложения и приветствия пользователя.
- Компонент можно использовать сразу после входа в систему для валидации корректного отображения интерфейса.
- Приветственное сообщение проверяется динамически, что особенно полезно в тестах с разными пользователями.
- Использование Text позволяет переиспользовать уже готовую обёртку с логами и шагами.
PageObject
В этой секции реализуем паттерн Page Object Model (POM) — один из самых распространённых подходов к структурированной автоматизации UI.
PageObject — это объект, представляющий страницу приложения или её часть. Каждый такой объект инкапсулирует поведение страницы: навигацию, действия и проверки. Это позволяет:
- Повысить читаемость и переиспользуемость кода
- Сократить дублирование
- Сделать поддержку тестов проще
В нашем примере будет две страницы:
- RegistrationPage — страница регистрации
- DashboardPage — страница панели управления
Но перед этим начнём с базовой страницы, от которой будут наследоваться все остальные.
BasePage
Базовая страница, от которой наследуются все остальные. Содержит наиболее общие методы, такие как открытие URL, перезагрузка и проверка текущего URL.
- visit(url) — переходит на указанный адрес и ждёт, пока завершатся все сетевые запросы.
- reload() — перезагружает текущую страницу и ожидает загрузки DOM.
- check_current_url() — использует регулярное выражение (Pattern[str]), что позволяет гибко проверять URL (например, с параметрами, токенами и т.п.).
- Все действия обёрнуты в шаги allure, что делает отчёты читаемыми и информативными.
- Логирование с использованием кастомного логгера BASE_PAGE поможет быстро локализовать проблему в логах.
RegistrationPage
Это PageObject для страницы регистрации. Он инкапсулирует все взаимодействия с элементами на странице регистрации, такие как форму регистрации и кнопки.
- Конструктор __init__(self, page: Page):Инициализирует компоненты и элементы страницы, используя элементы на странице, такие как форма регистрации, кнопки и ссылки.Наследуется от базового класса BasePage, предоставляя функционал для перехода по страницам и проверки URL.
- Метод click_registration_button(self):Кликает по кнопке регистрации.После клика выполняется проверка, что URL страницы соответствует ожидаемому, что означает успешный переход на страницу панели управления.
DashboardPage
Это PageObject для страницы панели управления. Он инкапсулирует взаимодействие с различными компонентами на странице, такими как панели навигации, различные графики и панель инструментов.
- Конструктор __init__(self, page: Page):Этот конструктор инициализирует компоненты, которые будут присутствовать на странице панели управления:NavbarComponent — панель навигации.ChartViewComponent — различные компоненты графиков для разных категорий (оценки, курсы, студенты, активности).DashboardToolbarViewComponent — панель инструментов для управления на странице.
- Методы check_visible_*_chart(self):Каждый из этих методов проверяет, видим ли определённый график на странице.Внутри каждого метода вызывается метод check_visible для соответствующего графика (например, для студентов — self.students_chart_view.check_visible('Students')).Если график не виден или текст не совпадает, будет выброшено исключение AssertionError.
Плюсы и минусы PageObject
В практике часто можно встретить мнение, что PageObject — это лучший паттерн для UI автотестов, и что он буквально спасает от всех проблем. Возможно, в отдельных случаях это действительно так, но, как и любой другой паттерн, PageObject имеет свои недостатки. Давайте кратко рассмотрим их:
Минусы PageObject
1. Сложность в реализации
PageObject добавляет дополнительный оверхед, и иногда это делает написание тестов более сложным, особенно для новичков. Прежде чем писать тест, необходимо реализовать сам PageObject. Для простых страниц это не представляет особых трудностей, но если страница становится большой и сложной, то может возникнуть множество проблем с реализацией. В таких случаях могут появиться неаккуратные решения, что только усложняет написание тестов.
Итог: PageObject не плох, но для эффективной работы с ним требуется опыт, и это не всегда легко.
2. PageObject сам по себе не полноценен
Если мы рассматриваем простые страницы, такие как LoginPage или RegistrationPage, то PageObject может быть вполне достаточен. Но когда приложение становится более сложным, одного PageObject уже недостаточно. Он не решает многие вопросы:
- Как работать с повторяющимися компонентами, например, Navbar или Sidebar?
- Как правильно интегрировать Allure шаги в отчеты?
- Как реализовать логирование?
- Как динамически форматировать локаторы?
Для таких задач приходится использовать другие паттерны, такие как PageComponent или PageFactory.
3. Проблемы при рефакторинге
PageObject не всегда удобен при рефакторинге и редизайне страниц. Если завтра разработчики переделают значительную часть приложения или изменят дизайн, то PageObject может оказаться неэффективным. В этом случае придется переработать все страницы. Этот момент также следует учитывать при проектировании UI тестов.
Плюсы PageObject
1. Упрощение тестов
PageObject помогает сделать тесты короче, читабельнее и удобнее для написания. С помощью PageObject можно инкапсулировать логику взаимодействия с компонентами страницы, что уменьшает дублирование кода и улучшает структуру тестов.
2. Переиспользуемость
PageObject позволяет переиспользовать готовые страницы в различных тестах. Это особенно полезно при работе с крупными приложениями, где одни и те же страницы могут быть задействованы в нескольких тестах, что значительно упрощает поддержку тестов.
Заключение
PageObject — это отличный паттерн для организации UI автотестов, но его нужно использовать в "умелых руках" и в комбинации с другими паттернами. Он имеет как плюсы, так и минусы, и важно понимать обе стороны, чтобы эффективно использовать его в проекте.
Настройки UI автотестов
Один из лучших подходов к управлению конфигурацией автотестов — централизованное хранение всех настроек. Это позволяет:
- легко переопределять параметры через .env
- использовать удобные типы данных (HttpUrl, DirectoryPath)
- не менять код при изменении значений.
В этом примере мы реализуем простую, но гибкую систему управления конфигурацией с помощью pydantic-settings — это надстройка над Pydantic, специально разработанная для работы с переменными окружения
Вот ключевые параметры, которые мы вынесем в конфигурацию:
- app_url — URL тестируемого приложения
- headless — запуск браузера в headless-режиме (если true, браузер запускается "в фоне", без UI — удобно для CI)
- videos_dir — путь, куда сохраняются видео при выполнении тестов (если включена запись)
- tracing_dir — путь для сохранения результатов Playwright Trace Viewer (подробный trace каждого теста)
- expect_timeout — таймаут в миллисекундах для всех expect() ожиданий
Важно: зачем нужен метод initialize и что он делает
Метод initialize() — это классовый метод в классе Settings, который выполняет сразу несколько важных задач:
1. Инициализация экземпляра Settings
Он создает и возвращает полностью готовый объект настроек, в котором все значения будут загружены из:
- .env файла (если он есть);
- переменных окружения (если переменных в .env не хватает).
2. Создание директорий videos и tracing (если они не существуют)
Перед созданием экземпляра Settings, метод вручную создаёт две директории:
- videos_dir — используется для сохранения видео-записей выполнения тестов.
- tracing_dir — используется для сохранения трейсов (отладочных логов) от Playwright, которые можно открыть через Trace Viewer.
Это особенно важно при первом запуске проекта, когда этих директорий ещё нет в файловой системе.
.env файл
Создадим .env файл в корне проекта со следующим содержимым:
Теперь, чтобы использовать настройки, достаточно вызвать метод initialize():
Важно! Глобальную переменную settings добавлять не будем — вместо этого будем инициализировать settings на уровне фикстур.
Фикстуры
В UI-тестировании крайне важно, чтобы каждый тест выполнялся в изолированном окружении, с чистым состоянием браузера, и при этом имел доступ к необходимым объектам страниц и настройкам.
Для этого мы реализуем следующие фикстуры:
- chromium_page – создаёт новую страницу браузера Chromium перед каждым тестом, с видео и трейсингом.
- registration_page – возвращает готовую страницу регистрации.
- dashboard_page - возвращает готовую страницу дашборда
- settings – инициализирует настройки один раз на всю тестовую сессию.
Почему Pytest плагины, а не conftest.py?
Для объявления и управления фикстурами будем использовать Pytest плагины. Плагины удобнее и гибче, чем объявления фикстур в conftest.py, потому что:
- Нет необходимости заботиться о расположении conftest.py.
- Фикстуры из плагинов доступны глобально во всём проекте, вне зависимости от структуры тестов.
- Использование conftest.py оправдано только для специфических групп тестов. В противном случае файлы conftest.py разрастаются до 1000+ строк, что затрудняет поддержку.
Чтобы фикстуры автоматически подключались во всех тестах проекта, добавим следующую строчку в корневой файл conftest.py:
Теперь фикстуры settings, chromium_page, registration_page, dashboard_page будут доступны глобально.
App routes
Для удобного и безопасного управления URL-роутами внутри приложения — вынесем все маршруты в отдельный Enum. Это позволит:
- избежать дублирования строк с путями;
- минимизировать опечатки;
- централизованно изменять маршруты при необходимости;
- использовать автодополнение в IDE и повысить читаемость кода.
UI тесты
Теперь создадим UI автотест, который проверяет успешную регистрацию пользователя. Тест будет использовать:
- PageObject, PageComponent, PageFactory — для взаимодействия со страницами и компонентами;
- Фикстуры — для инициализации браузера, страниц и глобальных настроек (через pytest).
Сценарий теста
- Открыть страницу регистрации: #/auth/registration
- Заполнить данные нового пользователя: Email, Username, Password
- Нажать кнопку Registration
- Проверить, что открылся Dashboard: #/dashboard
- Убедиться, что на Dashboard отображаются все нужные элементы: Navbar, Toolbar, Графики: Scores, Courses, Students, Activities
- @pytest.mark.regression и @pytest.mark.registration — метки для группировки и фильтрации тестов.
- @allure.title — заголовок, который будет отображён в отчёте Allure.
- Используемые фикстуры dashboard_page и registration_page автоматически создаются через pytest, благодаря pytest-плагинам.
Важно!
Совокупность описанных выше подходов позволяет создавать максимально чистые и понятные UI тесты — без визуального "шума" в виде ручных Allure шагов, логирования, лишней инициализации и прочих технических деталей.
Весь вспомогательный код и логика вынесены на уровень PageObject, PageComponent и PageFactory, что позволяет:
- Скрыть всю внутреннюю кухню взаимодействия с UI;
- Сфокусироваться только на бизнес-логике;
- Сделать тесты читаемыми и поддерживаемыми.
Фокус на главном — бизнес-логика
Когда мы читаем тест, мы должны сразу понимать:
“Что именно проверяется и как это влияет на бизнес”.
Если внутри теста появляются:
- Явные шаги Allure (allure.step(...));
- Логирование (print, logger.info, loguru);
- Инициализация браузера, страниц, окружения;
- Повторы или технические детали...
…тест быстро теряет читаемость и становится тяжело поддерживаемым. В таких условиях начинаются "костыли", появляются дубли, тесты ломаются от малейших изменений, а рефакторинг превращается в боль.
Хорошо читаемый тест — это когда:
- Видно, что тестируется;
- Понятно, с какими компонентами мы работаем;
- Легко добавить, изменить или удалить шаг.
Масштаб важен
Если у вас в проекте 5 тестов — можно писать их хоть в одном файле, без фикстур, паттернов и структур. Но такие проекты — редкость. На практике:
- UI-тестов обычно сотни;
- API-тестов — тысячи.
В таких условиях архитектура, структура и паттерны критически важны.
Удобная отладка через логирование
Благодаря встроенному логированию, при запуске можно сразу понять, что делает тест в каждый момент времени. Пример логов:
Такой вывод:
- Показывает где именно произошёл сбой;
- Помогает быстро анализировать логику работы компонентов;
- Используется даже при CI-прогоне, когда нет доступа к браузеру.
Регистрация pytest-маркировок
Добавим pytest-маркировки в pytest.ini, чтобы избежать предупреждений при запуске.
Запуск на CI/CD
Настроим workflow-файл для автоматического запуска UI-тестов в GitHub Actions, генерации Allure-отчета с сохранением истории и публикации его на GitHub Pages.
Ссылки на документацию для всех использованных actions можно найти ниже:
Разрешения для Workflow
Если сейчас запустить тесты на GitHub Actions то, будет ошибка, говорящая о том, что у github token из workflow по умолчанию нет прав на записть в репзоиторий
Для исправления этой ошибки необходимо вручную изменить настройки прав workflow:
- Откройте вкладку Settings в репозитории GitHub.
2. Перейдите в раздел Actions → General.
3. Прокрутите страницу вниз до блока Workflow permissions.
4. Выберите опцию Read and write permissions.
5.Нажмите кнопку Save для сохранения изменений.
После выполнения этих шагов можно отправить код с UI-тестами в удалённый репозиторий.
Запуск тестов и генерация Allure-отчёта
Если тесты пройдут успешно, Allure-отчёт будет сгенерирован и загружен в ветку gh-pages, после чего автоматически запустится workflow pages build and deployment. Этот процесс публикует Allure-отчёт на GitHub Pages, делая его доступным для просмотра в браузере.
Важно! Перед запуском workflow необходимо убедиться, что в репозитории существует ветка gh-pages. Если ветка отсутствует, её необходимо создать в удалённом репозитории, иначе публикация Allure-отчёта на GitHub Pages не будет работать.
Проверка настроек GitHub Pages
Если workflow pages build and deployment не запустился, необходимо проверить настройки GitHub Pages:
- Откройте вкладку Settings в репозитории.
- Перейдите в раздел Pages → Build and deployment.
- Убедитесь, что параметры соответствуют настройкам на скриншоте ниже.
На этой же странице будет отображаться виджет со ссылкой на опубликованный Allure-отчёт.
Доступ к Allure-отчётам
- Каждый отчёт публикуется на GitHub Pages с уникальным идентификатором workflow, в котором он был сгенерирован.
- Все сгенерированные Allure-отчёты также можно найти в ветке gh-pages.
- Перейдя по ссылке на GitHub Pages, можно открыть сгенерированный Allure-отчёт с историей результатов тестирования.
По итогу, после корректной настройки, при каждом новом запуске тестов Allure-отчёт будет автоматически обновляться и сохранять историю предыдущих прогонов.
Благодаря подходу с PageFactory и чётко реализованной архитектуре PageObject/PageComponent, мы получаем максимально детализированные шаги в Allure-отчёте без необходимости вручную прописывать @allure.step.
Каждое взаимодействие с компонентом автоматически логируется и отображается в виде шагов. Это делает отчёты понятными на всех уровнях детализации:
- На верхнем уровне — читаемое бизнес-описание шагов, соответствующее сценарию теста.
- При раскрытии — подробности: какой компонент использовался, какой локатор применялся и с каким индексом он искался.
В дополнение к шагам, в Allure-отчёт автоматически добавляются вложения:
- Видео выполнения теста, которое можно просмотреть прямо в отчёте. Это особенно полезно для анализа визуальных и анимационных багов.
- Архив trace (.zip), который можно скачать и загрузить в Playwright Trace Viewer для максимально детального анализа теста.
Trace Viewer позволяет интерактивно исследовать всё, что происходило в браузере: DOM-снимки, сетевые запросы, переходы по страницам, скриншоты, события и другое.
Заключение
Все ссылки на код, отчеты и запуски тестов в CI/CD можно найти на моем GitHub: