Модель объектов страницы (POM) в Playwright

В этой главе представлены философия проектирования и реализация Page Object Model, которые помогут вам организовать большие наборы тестов.


Что такое POM

Page Object Model — это паттерн проектирования тестов, который инкапсулирует элементы страницы и операциив независимые классыв.

Каждый объект страницы представляет страницу или компонент веб-приложения и содержит локаторы и методы операций для этой страницы.


Преимущества POM

ПреимуществоОписание
Упрощает написание тестовТестовый код использует высокоуровневые API (такие какloginPage.login('user', 'pass')) вместо низкоуровневых операций Locator
Снижает затраты на сопровождениеИзменения в структуре страницы требуют изменения только класса Page Object, а не каждого теста
Повышает читаемостьТестовый код читается скорее как бизнес-язык, что делает его понятным даже для людей без технической подготовки
Позволяет избежать дублированияОдни и те же локаторы и операции нужно определить только один раз

Создание класса Page Object

Далее на примере сайта EXAMPLE создаётся Page Object для страницы входа.

Пример

// Путь к файлу: pages/LoginPage.ts
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 в тестах

Пример

// Путь к файлу: tests/login.spec.ts
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, и они могут быть связаны друг с другом.

Пример

// Путь к файлу: pages/DashboardPage.ts
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:

Пример

test('Полный сценарий входа и выхода', async ({ page }) => {
  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.

Другие расширения