Page Object Model (POM)
Паттерн Page Object Model для организации тестов Playwright: инкапсуляция селекторов, переиспользование кода и упрощение поддержки.
Что такое Page Object Model?
Page Object Model (POM) — это паттерн проектирования тестовой автоматизации, при котором каждая страница или значимый компонент веб-приложения представляется отдельным классом. Класс страницы инкапсулирует селекторы (локаторы) и методы взаимодействия с этой страницей. Тесты используют эти классы, оставаясь независимыми от деталей разметки.
Основная идея: при изменении вёрстки страницы достаточно обновить только класс страницы, а не все тесты, которые с ней работают.
Структура класса страницы
Каждый класс POM содержит:
- Конструктор
__init__(self, page: Page)— принимает объект страницы Playwright - Локаторы — объявляются как атрибуты через
page.get_by_role(),page.locator()и другие методы - Методы действий —
navigate(),search(),get_result()и т.д. - Методы проверок — возвращают состояние элементов для assert/expect
from playwright.sync_api import Page class SearchPage: """Page Object для страницы поиска.""" def __init__(self, page: Page): self.page = page self.search_input = page.get_by_role("textbox", name="Поиск") self.search_button = page.get_by_role("button", name="Найти") self.results = page.get_by_role("listitem") self.no_results_msg = page.get_by_text("Ничего не найдено") def navigate(self) -> None: self.page.goto("https://example.com/search") def search(self, query: str) -> None: self.search_input.fill(query) self.search_button.click() self.page.wait_for_load_state("networkidle") def get_result(self, index: int = 0): return self.results.nth(index) def has_results(self) -> bool: return self.results.count() > 0 def no_results_message_visible(self) -> bool: return self.no_results_msg.is_visible()
Использование POM в тестах pytest
В pytest класс POM удобно использовать через фикстуру page, которую предоставляет плагин pytest-playwright.
from search_page import SearchPage def test_search_finds_results(page): search_page = SearchPage(page) search_page.navigate() search_page.search("Playwright Python") result = search_page.get_result(0) expect(result).to_be_visible() expect(result).to_contain_text("Playwright") def test_search_empty_query(page): search_page = SearchPage(page) search_page.navigate() search_page.search("") expect(search_page.no_results_msg).to_be_visible()
Продвинутый POM: композиция и фрагменты
Для сложных страниц можно выделять повторяющиеся компоненты в отдельные классы-фрагменты.
class HeaderFragment: def __init__(self, page: Page): self.logo = page.get_by_role("link", name="Главная") self.login_btn = page.get_by_role("button", name="Войти") self.cart_link = page.get_by_role("link", name="Корзина") def go_to_cart(self): self.cart_link.click() def is_logged_in(self) -> bool: return not self.login_btn.is_visible() class MainPage: def __init__(self, page: Page): self.page = page self.header = HeaderFragment(page) def navigate(self): self.page.goto("https://example.com")
Асинхронный POM (async/await)
Playwright поддерживает как синхронный, так и асинхронный API. Для async-проектов классы POM пишутся с async/await.
from playwright.async_api import Page as AsyncPage class AsyncSearchPage: def __init__(self, page: AsyncPage): self.page = page self.search_input = page.get_by_role("textbox", name="Поиск") self.search_button = page.get_by_role("button", name="Найти") async def navigate(self): await self.page.goto("https://example.com/search") async def search(self, query: str): await self.search_input.fill(query) await self.search_button.click() await self.page.wait_for_load_state("networkidle")
Совет: Выбор между синхронным и асинхронным POM зависит от проекта. Для большинства тестов достаточно синхронного подхода. Асинхронный вариант полезен при параллельных операциях или интеграции с асинхронными фреймворками.
Преимущества POM
- Переиспользование кода — методы страницы используются во всех тестах
- Лёгкость поддержки — изменение селектора в одном месте, а не во всех тестах
- Читаемость — тесты читаются как сценарии на естественном языке
- Модульность — каждый класс отвечает за свою страницу или компонент
- Снижение дублирования — общие проверки выносятся в методы класса
Важно: Не злоупотребляйте наследованием в POM. Предпочитайте композицию (вложение фрагментов) или делегирование. Глубокие иерархии наследования усложняют поддержку.
Рекомендации по организации
- Храните каждый класс POM в отдельном файле
- Используйте осмысленные имена методов:
login(),add_to_cart(),get_total_price() - Возвращайте из методов другие Page Object для организации fluent-интерфейса
- Не смешивайте бизнес-логику с техническими деталями
- Для утверждений используйте
expect()из Playwright
def test_checkout_flow(page): main_page = MainPage(page) main_page.navigate() main_page.header.go_to_cart() cart_page = CartPage(page) cart_page.proceed_to_checkout() checkout_page = CheckoutPage(page) checkout_page.fill_shipping_info("Иван", "ул. Ленина, 1") checkout_page.place_order() confirmation = OrderConfirmationPage(page) expect(confirmation.order_number).to_be_visible()