Page Object Model
Page Object Model (POM) — паттерн проектирования E2E тестов, при котором каждая страница приложения инкапсулируется в отдельный класс с методами для взаимодействия и селекторами.
Зачем нужно
Без POM каждый тест содержит дублирующиеся page.click('[data-testid="submit"]'). При изменении атрибута нужно обновлять десятки тестов. POM централизует селекторы и действия — один класс, один файл, одно место для обновления.
Где используется
- Playwright и Cypress проекты с несколькими E2E тестами
- Любой E2E suite где больше 3-5 тестов для одной страницы
- Команды с разделением ролей QA и разработчик
Основной контент
Базовый Page Object на Playwright
// pages/LoginPage.ts
import { Page, Locator } from '@playwright/test';
export class LoginPage {
readonly page: Page;
readonly emailInput: Locator;
readonly passwordInput: Locator;
readonly submitButton: Locator;
readonly errorMessage: Locator;
constructor(page: Page) {
this.page = page;
this.emailInput = page.getByTestId('email');
this.passwordInput = page.getByTestId('password');
this.submitButton = page.getByTestId('submit');
this.errorMessage = page.getByTestId('error-message');
}
async goto {
await this.page.goto('/login');
}
async login(email: string, password: string) {
await this.emailInput.fill(email);
await this.passwordInput.fill(password);
await this.submitButton.click();
}
async getError {
return this.errorMessage.textContent;
}
}
Использование в тестах
// tests/login.spec.ts
import { test, expect } from '@playwright/test';
import { LoginPage } from '../pages/LoginPage';
import { DashboardPage } from '../pages/DashboardPage';
test('успешный вход', async ({ page }) => {
const loginPage = new LoginPage(page);
const dashboardPage = new DashboardPage(page);
await loginPage.goto;
await loginPage.login('user@example.com', 'password123');
await expect(page).toHaveURL('/dashboard');
await expect(dashboardPage.welcomeText).toBeVisible;
});
test('неверный пароль', async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goto;
await loginPage.login('user@example.com', 'wrong');
expect(await loginPage.getError).toBe('Неверный пароль');
});
Связь между страницами — навигация
// pages/LoginPage.ts
async login(email: string, password: string): Promise<DashboardPage> {
await this.emailInput.fill(email);
await this.passwordInput.fill(password);
await this.submitButton.click();
return new DashboardPage(this.page); // возвращаем следующую страницу
}
// В тесте
const dashboard = await loginPage.login('user@example.com', 'pass');
await expect(dashboard.welcomeText).toBeVisible;
POM в Cypress
// cypress/pages/LoginPage.js
class LoginPage {
visit { cy.visit('/login'); }
fillEmail(email) { cy.get('[data-cy="email"]').type(email); }
fillPassword(pwd) { cy.get('[data-cy="password"]').type(pwd); }
submit { cy.get('[data-cy="submit"]').click(); }
getError { return cy.get('[data-cy="error"]'); }
}
export const loginPage = new LoginPage();
Структура папок
tests/
pages/
LoginPage.ts
DashboardPage.ts
CheckoutPage.ts
e2e/
login.spec.ts
checkout.spec.ts
Частые ошибки
- Assertions внутри Page Object — POM должен только инкапсулировать взаимодействие; assertions оставляй в тестах
- Один Page Object на всё приложение — большой класс со всеми методами; дели по страницам/компонентам
- Дублирование логики между POM и тестами — если в тесте есть
page.getByTestId(...), значит POM не используется полностью
Связанные темы
- _MOC Тестирование
- E2E тестирование
- E2E -- паттерны и антипаттерны
- Playwright -- обзор
- Cypress -- установка и основы