Модель объектов страницы (POM) в Playwright
В этой главе представлены философия проектирования и реализация Page Object Model, которые помогут вам организовать большие наборы тестов.
Что такое POM
Page Object Model — это паттерн проектирования тестов, который инкапсулирует элементы страницы и операциив независимые классыв.
Каждый объект страницы представляет страницу или компонент веб-приложения и содержит локаторы и методы операций для этой страницы.
Преимущества POM
| Преимущество | Описание |
|---|---|
| Упрощает написание тестов | Тестовый код использует высокоуровневые API (такие какloginPage.login('user', 'pass')) вместо низкоуровневых операций Locator |
| Снижает затраты на сопровождение | Изменения в структуре страницы требуют изменения только класса Page Object, а не каждого теста |
| Повышает читаемость | Тестовый код читается скорее как бизнес-язык, что делает его понятным даже для людей без технической подготовки |
| Позволяет избежать дублирования | Одни и те же локаторы и операции нужно определить только один раз |
Создание класса Page Object
Далее на примере сайта EXAMPLE создаётся Page Object для страницы входа.
Пример
import { type Page, type Locator, expect } from '@playwright/test';
export class LoginPage {
readonly page: Page;
// Объявить все локаторы как свойства только для чтения
readonly usernameInput: Locator;
readonly passwordInput: Locator;
readonly loginButton: Locator;
readonly errorMessage: Locator;
constructor(page: Page) {
this.page = page;
// Инициализировать все локаторы в конструкторе
this.usernameInput = page.getByLabel('Имя пользователя');
this.passwordInput = page.getByLabel('Пароль');
this.loginButton = page.getByRole('button', { name: 'Войти' });
this.errorMessage = page.getByTestId('login-error');
}
// Перейти на страницу входа
async goto() {
await this.page.goto('/login');
}
// Выполнить операцию входа
async login(username: string, password: string) {
await this.usernameInput.fill(username);
await this.passwordInput.fill(password);
await this.loginButton.click();
}
// Проверить сообщение об ошибке при неудачном входе
async expectLoginError(message: string) {
await expect(this.errorMessage).toBeVisible();
await expect(this.errorMessage).toContainText(message);
}
}
Использование Page Object в тестах
Пример
import { test, expect } from '@playwright/test';
import { LoginPage } from '../pages/LoginPage';
test.describe('Функциональность входа', () => {
test('Вход выполняется успешно с правильным именем пользователя и паролем', async ({ page }) => {
const loginPage = new LoginPage(page);
// Используйте методы Page Object
await loginPage.goto();
await loginPage.login('example_user', 'correct_password');
// Проверьте перенаправление после успешного входа
await expect(page).toHaveURL('/dashboard');
});
test('Вход не выполняется при неверном пароле', async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login('example_user', 'wrong_password');
// Используйте методы проверки Page Object
await loginPage.expectLoginError('Неверное имя пользователя или пароль');
});
});
Многоуровневые Page Objects
В реальных проектах можно создавать несколько Page Objects, и они могут быть связаны друг с другом.
Пример
import { type Page, type Locator, expect } from '@playwright/test';
export class DashboardPage {
readonly page: Page;
readonly welcomeText: Locator;
readonly logoutButton: Locator;
constructor(page: Page) {
this.page = page;
this.welcomeText = page.getByText('С возвращением');
this.logoutButton = page.getByRole('button', { name: 'Выйти' });
}
async expectWelcomeMessage(username: string) {
await expect(this.welcomeText).toContainText(username);
}
async logout() {
await this.logoutButton.click();
}
}
Объединение Page Objects:
Пример
const loginPage = new LoginPage(page);
const dashboardPage = new DashboardPage(page);
// Войти
await loginPage.goto();
await loginPage.login('example_user', 'password');
// Проверить дашборд
await dashboardPage.expectWelcomeMessage('example_user');
// Выход
await dashboardPage.logout();
await expect(page).toHaveURL('/login');
});
Лучшие практики POM
| Практика | Описание |
|---|---|
| Централизуйте локаторы в конструкторе | Определяйте все локаторы в конструкторе; не создавайте их динамически в методах |
| Методы возвращают Page Objects | Методы операций могут возвращать новый Page Object (например, возвращать DashboardPage после входа в систему) |
| Инкапсулируйте проверки, но не злоупотребляйте ими | Инкапсулируйте часто используемые комбинации проверок в методы; простые проверки пишите непосредственно в тестах |
| Используйте композицию вместо наследования | Избегайте глубокого наследования; используйте паттерн композиции для связи нескольких Page Objects |
| Один набор объектов покрывает одну страницу/компонент | Придерживайтесь умеренной гранулярности; не создавайте отдельные классы для каждого небольшого элемента |
Другие расширенияPOM не обязателен.
Для небольших проектов всего с несколькими тестами писать локаторы прямо в тестах понятнее. Когда тестовых файлов больше 5, стоит рассмотреть внедрение POM.