automationpage-objectarchitectureplaywrightseleniumqa

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 избыточен — вводи по мере роста.