Перейти к основному контенту
Tech Path Finder
КурсыИнтервьюКод-ревьюБлог
Tech Path Finder

Персонализированный путеводитель в IT. Квизы, мок-интервью, код ревью и аналитика прогресса.

@potapov_me

Платформа

  • Курсы
  • Прогресс
  • Мок-интервью
  • Код ревью
  • Живое ревью с ИИ
  • Тренажёр переговоров
  • Закладки

Контент

  • Блог
  • Главная
  • Обратная связь

Компания

  • О проекте
  • Тарифы
  • Условия использования
  • Конфиденциальность
  • Согласие на обработку данных
  • Cookie
  • Реквизиты

Аккаунт

  • Войти
  • Зарегистрироваться
  • Профиль

© 2026 Tech Path Finder. Все права защищены.

·ИП Потапов К.С.·Политика конфиденциальности·
Сделано с ❤️ в России
  1. Page Object Model
page_object

Page Object Model

Паттерн Page Object, композиция страниц, переиспользуемые компоненты, лучшие практики

Page Object Model

Page Object Model (POM) — паттерн, при котором каждая страница приложения инкапсулируется в отдельный класс. Тесты оперируют высокоуровневыми операциями вместо селекторов.

#1. Проблема плоских тестов

Без POM тесты дублируют селекторы:

# Тест 1 def test_login(page): page.get_by_label('Email').fill('admin@test.com') page.get_by_label('Пароль').fill('secret') page.get_by_role('button', name='Войти').click() # Тест 2 def test_settings(page): page.get_by_label('Email').fill('admin@test.com') # Дублирование! page.get_by_label('Пароль').fill('secret') # Дублирование! page.get_by_role('button', name='Войти').click() page.get_by_role('link', name='Настройки').click()

Если селектор изменится — нужно править все тесты.

#2. Базовый Page Object

class LoginPage: def __init__(self, page): self.page = page self.email_input = page.get_by_label('Email') self.password_input = page.get_by_label('Пароль') self.login_button = page.get_by_role('button', name='Войти') self.error_message = page.locator('.alert-error') async def login(self, email: str, password: str) -> 'DashboardPage': await self.email_input.fill(email) await self.password_input.fill(password) await self.login_button.click() return DashboardPage(self.page) async def get_error_text(self) -> str: return await self.error_message.text_content()

Тест становится читаемым:

def test_valid_login(page): login_page = LoginPage(page) dashboard = login_page.login('admin@test.com', 'secret') # Assertions — в тесте, не в Page Object assert page.url.endswith('/dashboard')

#3. Принципы POM

Локаторы в __init__: Все локаторы страницы собираются в одном месте. При изменении UI правите только Page Object.

Методы — действия пользователя: login(), open_settings(), search(query). Не click_submit_button() — а submit_form(), описывающее намерение.

Возврат следующей страницы: Метод навигации возвращает Page Object следующей страницы. Это позволяет строить цепочки.

Никаких assertions в Page Object: Page Object предоставляет данные, тест проверяет.

#4. Компоненты (Component Object)

Хедер, футер, модальное окно — компоненты, используемые на множестве страниц:

class HeaderComponent: def __init__(self, page): self.page = page self.logo = page.get_by_alt_text('Логотип') self.profile_menu = page.get_by_role('button', name='Профиль') self.logout_item = page.get_by_role('menuitem', name='Выйти') async def open_profile(self) -> 'ProfilePage': await self.profile_menu.click() return ProfilePage(self.page) async def logout(self) -> 'LoginPage': await self.profile_menu.click() await self.logout_item.click() return LoginPage(self.page) class DashboardPage: def __init__(self, page): self.page = page self.header = HeaderComponent(page) # Композиция self.welcome_heading = page.get_by_role('heading', name='Добро пожаловать') self.stats_table = page.locator('.stats-table')

Композиция предпочтительнее наследования. Не class DashboardPage(BasePage) — а self.header = HeaderComponent(page).

#5. Обработка ошибок

class LoginPage: async def login(self, email: str, password: str) -> 'DashboardPage | LoginPage': await self.email_input.fill(email) await self.password_input.fill(password) await self.login_button.click() # Проверяем результат if await self.error_message.is_visible(): return self # Остались на странице логина return DashboardPage(self.page) # Перешли на dashboard

Или через исключение:

class LoginError(Exception): pass class LoginPage: async def login(self, email: str, password: str) -> 'DashboardPage': await self.email_input.fill(email) await self.password_input.fill(password) await self.login_button.click() if await self.error_message.is_visible(): raise LoginError(await self.error_message.text_content()) return DashboardPage(self.page)

#6. Page Object для SPA

В SPA нет перезагрузки страницы — навигация через JavaScript:

class UserProfilePage: def __init__(self, page): self.page = page self.name_field = page.get_by_label('Имя') self.save_button = page.get_by_role('button', name='Сохранить') async def wait_for_loaded(self): """Ждём появления ключевых элементов страницы.""" from playwright.sync_api import expect expect(self.name_field).to_be_visible(timeout=10000) return self

Тест ждёт загрузки:

def test_edit_profile(logged_in_page): profile = UserProfilePage(logged_in_page).wait_for_loaded() profile.name_field.fill('Новое Имя') profile.save_button.click()

#7. Организация файлов

tests/
├── pages/
│   ├── base.py           # Базовый класс (опционально)
│   ├── login_page.py
│   ├── dashboard_page.py
│   ├── components/
│   │   ├── header.py
│   │   └── modal.py
│   └── __init__.py
├── test_login.py
├── test_dashboard.py
└── conftest.py

#8. Антипаттерны

❌ God Object — один класс для всего приложения. Каждая страница — отдельный класс.

❌ Assertions в Page Object — assert self.name_field.is_visible(). Page Object предоставляет данные, тест проверяет.

❌ Наследование вместо композиции — class DashboardPage(BasePage). Компоненты через self.header = HeaderComponent(page).

❌ Хранение тестовых данных — ADMIN_EMAIL = 'admin@test.com' в Page Object. Данные — в тестах или фикстурах.

✅ Правильный POM: один класс на страницу, локаторы в __init__, методы действий, возврат следующей страницы, композиция компонентов, assertions в тестах.


#Упражнения

  1. Реализуйте LoginPage и DashboardPage для тестового приложения. Метод login() должен возвращать DashboardPage.
  2. Создайте HeaderComponent с методами open_profile() и logout(). Подключите его к DashboardPage.
  3. Добавьте обработку ошибки логина: метод login() должен поднимать LoginError при неверных данных.
  4. Напишите тест, который использует цепочку: LoginPage(page).login(...).open_settings().

#Что дальше?

Изучите интеграцию с CI/CD, чтобы запускать тесты в GitHub Actions и GitLab CI.

Далее: Интеграция с CI/CD