Page Object Model: архитектура UI-автотестов, чтобы не утонуть в поддержке
Первые десять автотестов пишутся легко: находишь элемент по селектору прямо в тесте, кликаешь, проверяешь. А потом дизайнер меняет кнопку логина — и оказывается, что этот селектор скопирован в 40 тестах, и чинить надо каждый. Так рождается ад поддержки. Page Object Model (POM) — паттерн, который его лечит.
Проблема без POM
Когда локаторы и шаги раскиданы по тестам:
- Дублирование. Один и тот же
#login-btnв десятках файлов. - Хрупкость. UI меняется — правки в куче мест, что-то забыл → красный прогон.
- Нечитаемость. Тест — это простыня
find().click().type(), а не сценарий.
Цель POM — чтобы тест читался как сценарий, а детали «как найти и кликнуть» жили в одном месте.
Page Object
Идея простая: класс на страницу (или компонент), который инкапсулирует локаторы и действия над ней. Тест не знает про селекторы — он вызывает методы.
class LoginPage:
def __init__(self, page):
self.page = page
self.user = page.locator("#username")
self.pwd = page.locator("#password")
self.submit = page.get_by_role("button", name="Войти")
def login(self, u, p):
self.user.fill(u)
self.pwd.fill(p)
self.submit.click()
return DashboardPage(self.page) # вернули следующую страницу
def test_login(page):
dashboard = LoginPage(page).login("user", "pass")
assert dashboard.greeting.is_visible()
Меняется вёрстка логина — правишь один класс, все тесты живы.
Что внутри Page Object, а чего быть не должно
- Должно: локаторы элементов и действия (
login(),add_to_cart(),open_menu()), ожидание готовности страницы. - Спорно, но по классике — НЕ должно: ассершены. Проверки держи в тестах (Page Object — про взаимодействие, тест — про проверку). PO может отдавать данные/состояние, а
assertпишет тест. (Некоторые команды делают «проверочные» методы вродеis_loaded()— это ок, но именно бизнес-проверки оставляй тесту.) - Не должно: тестовых данных (логины/пароли — из фикстур/данных теста), логики конкретного теста, хардкода окружения.
Component Objects
Не всё — «страница». Хедер, модалка, таблица, карточка товара повторяются на многих экранах. Выноси их в компонент-объекты и переиспользуй, а страницы складывай из компонентов. Меньше дублирования, чётче структура.
Fluent и loadable
- Fluent: метод-переход возвращает следующий Page Object (
login()→DashboardPage). Тест читается цепочкой и не таскает создание страниц вручную. - Loadable/ожидание готовности: Page Object сам дожидается, что страница загрузилась (ключевой элемент видим), — тест не сыплет
sleep. (См. отдельный разбор про ожидания.)
Анти-паттерны
- God Object — одна «страница» на 1000 строк со всем подряд. Дроби на компоненты.
- Ассершены внутри Page Object — размывает ответственность, PO начинает «знать» про ожидания теста.
- Хрупкие XPath внутри PO (
/div[3]/span[2]) — инкапсуляция не спасёт от плохих селекторов; бериgetByRole/data-testid. - Дубли локаторов — один элемент описан в двух местах.
- Логика теста в PO — условные проверки «если то — иначе» под конкретный кейс.
- PO-обёртка ради обёртки — метод, который просто прокидывает один клик, без ценности.
Playwright и современный подход
Playwright поощряет POM (в доках есть пример класса-страницы) поверх встроенных устойчивых locator/getByRole. Часто POM оформляют как fixtures — тест получает готовый loginPage. Идея та же: локаторы и действия — в классе, проверки — в тесте.
Когда POM избыточен
Два-три теста на маленький проект — POM только добавит церемоний. Паттерн окупается на росте набора и переиспользовании. Начни просто, вводи Page Object, когда почувствовал дублирование и боль поддержки.
Чек-лист хорошего Page Object
- Класс на страницу/компонент; тест не видит селекторов.
- Локаторы устойчивые (
getByRole/data-testid), не XPath-цепочки. - Методы — действия предметной области (
login,checkout), а неclick_button_3. - Ассершены — в тестах; PO отдаёт состояние.
- Переходы возвращают следующий PO; ожидание готовности внутри.
- Переиспользуемые блоки — в компонент-объекты.
- Нет God-object, нет тестовых данных и логики кейса внутри.
Коротко
- Локаторы в тестах = дублирование + ад поддержки; POM это лечит.
- Page Object = класс на страницу: инкапсулирует локаторы и действия, тест вызывает методы.
- Ассершены держи в тестах, не в PO; тестовые данные — снаружи.
- Переиспользуемые блоки — Component Objects; переходы — fluent; ожидание готовности — внутри PO.
- Избегай God-object и хрупких XPath; локаторы —
getByRole/data-testid. - Для двух тестов POM избыточен — вводи по мере роста.