Создание типизированных Page Objects

Page Object — это шаблон проектирования, который помогает упрощать тестирование пользовательских интерфейсов, инкапсулируя взаимодействие с элементами страницы в отдельные объекты. В контексте тестирования с Playwright создание Page Object позволяет повысить читаемость, поддерживаемость и переиспользуемость кода тестов. Однако для того, чтобы достичь максимальной эффективности, необходимо использовать возможности типизации, предоставляемые TypeScript, чтобы избежать ошибок на ранних стадиях разработки.

1. Зачем нужны типизированные Page Objects?

Типизированные Page Objects обеспечивают несколько важных преимуществ:

  • Автокомплешн в редакторе: За счет TypeScript можно заранее получить подсказки и автодополнение для методов и свойств, что существенно ускоряет процесс разработки тестов.
  • Более безопасный код: Типизация помогает избегать ошибок, связанных с неверными типами данных, и позволяет обнаружить ошибки до того, как они проявятся во время выполнения теста.
  • Упрощение поддержки кода: Когда страница или элементы на ней изменяются, типизация позволяет легко адаптировать существующие Page Object’ы, минимизируя риски ошибок в коде тестов.

2. Структура типизированного Page Object

Типизированный Page Object — это класс, в котором определены все элементы и методы для взаимодействия с ними. Вместо использования строковых селекторов или абстрактных методов, в таких объектах используются типы данных, которые явно указывают на ожидаемый результат.

Пример:

import { Page, Locator } from '@playwright/test';

class LoginPage {
  readonly page: Page;
  readonly usernameInput: Locator;
  readonly passwordInput: Locator;
  readonly submitButton: Locator;

  constructor(page: Page) {
    this.page = page;
    this.usernameInput = page.locator('input[name="username"]');
    this.passwordInput = page.locator('input[name="password"]');
    this.submitButton = page.locator('button[type="submit"]');
  }

  async login(username: string, password: string) {
    await this.usernameInput.fill(username);
    await this.passwordInput.fill(password);
    await this.submitButton.click();
  }
}

В этом примере LoginPage — это типизированный Page Object, где элементы страницы (поля ввода и кнопка) инкапсулированы в свойствах класса, а метод login выполняет действия на странице.

3. Инкапсуляция логики взаимодействия

Основная цель Page Object — скрыть детали реализации работы с элементами страницы. Это упрощает тесты и делает их более читабельными. В случае типизированного подхода можно дополнительно инкапсулировать логику обработки ошибок, ожидания и другие аспекты взаимодействия с элементами.

Пример:

class LoginPage {
  readonly page: Page;
  readonly usernameInput: Locator;
  readonly passwordInput: Locator;
  readonly submitButton: Locator;

  constructor(page: Page) {
    this.page = page;
    this.usernameInput = page.locator('input[name="username"]');
    this.passwordInput = page.locator('input[name="password"]');
    this.submitButton = page.locator('button[type="submit"]');
  }

  async login(username: string, password: string) {
    await this.fillUsername(username);
    await this.fillPassword(password);
    await this.submitForm();
  }

  private async fillUsername(username: string) {
    await this.usernameInput.fill(username);
    await this.usernameInput.press('Tab');
  }

  private async fillPassword(password: string) {
    await this.passwordInput.fill(password);
    await this.passwordInput.press('Enter');
  }

  private async submitForm() {
    await this.submitButton.click();
  }
}

В этой реализации логика для ввода имени пользователя и пароля была вынесена в отдельные методы. Это упрощает повторное использование методов и делает их более изолированными и тестируемыми.

4. Использование асинхронных операций и ожиданий

Playwright позволяет выполнять асинхронные операции, такие как ожидание появления элементов или успешного выполнения действия. В Page Object можно инкапсулировать эти операции, а типизация помогает четко определять, когда и как ожидаются результаты.

Пример использования ожиданий:

class LoginPage {
  readonly page: Page;
  readonly usernameInput: Locator;
  readonly passwordInput: Locator;
  readonly submitButton: Locator;
  readonly errorMessage: Locator;

  constructor(page: Page) {
    this.page = page;
    this.usernameInput = page.locator('input[name="username"]');
    this.passwordInput = page.locator('input[name="password"]');
    this.submitButton = page.locator('button[type="submit"]');
    this.errorMessage = page.locator('div.error-message');
  }

  async login(username: string, password: string) {
    await this.usernameInput.fill(username);
    await this.passwordInput.fill(password);
    await this.submitButton.click();
    await this.page.waitForSelector(this.errorMessage, { state: 'visible' });
  }

  async getErrorMessage(): Promise<string> {
    return await this.errorMessage.textContent();
  }
}

Здесь добавлено ожидание появления сообщения об ошибке после отправки формы. Типизация позволяет четко указать, что метод getErrorMessage возвращает строку, а login вызывает асинхронные операции, которые необходимо ожидать.

5. Параметризация и повторное использование Page Objects

Типизированные Page Objects хорошо подходят для параметризации тестов. Вместо того чтобы дублировать логику во многих местах, можно создавать универсальные методы и переиспользовать их в разных тестах.

Пример параметризированного метода:

class FormPage {
  readonly page: Page;
  readonly textField: Locator;
  readonly submitButton: Locator;

  constructor(page: Page) {
    this.page = page;
    this.textField = page.locator('input[type="text"]');
    this.submitButton = page.locator('button[type="submit"]');
  }

  async submitForm(value: string) {
    await this.textField.fill(value);
    await this.submitButton.click();
  }
}

В этом примере метод submitForm можно использовать для различных форм, передавая разные значения в качестве параметров.

6. Работа с несколькими страницами

Когда тесты требуют взаимодействия с несколькими страницами, типизированные Page Objects помогают структурировать код и избежать путаницы при управлении несколькими объектами. Для этого можно создать отдельные классы для каждой страницы.

Пример работы с несколькими страницами:

class HomePage {
  readonly page: Page;
  readonly navigateButton: Locator;

  constructor(page: Page) {
    this.page = page;
    this.navigateButton = page.locator('button.navigate');
  }

  async goToLoginPage() {
    await this.navigateButton.click();
  }
}

class LoginPage {
  readonly page: Page;
  readonly usernameInput: Locator;
  readonly passwordInput: Locator;
  readonly submitButton: Locator;

  constructor(page: Page) {
    this.page = page;
    this.usernameInput = page.locator('input[name="username"]');
    this.passwordInput = page.locator('input[name="password"]');
    this.submitButton = page.locator('button[type="submit"]');
  }

  async login(username: string, password: string) {
    await this.usernameInput.fill(username);
    await this.passwordInput.fill(password);
    await this.submitButton.click();
  }
}

В этом примере взаимодействие между страницами инкапсулировано в методах классов HomePage и LoginPage. Это помогает четко разделить логику для каждой страницы и упростить переход между ними.

7. Вывод

Типизированные Page Objects в Playwright позволяют создавать чистый, поддерживаемый и масштабируемый код для автоматизации тестирования. Использование TypeScript повышает безопасность и удобство работы с тестами, делая взаимодействие с элементами страницы явным и легко поддерживаемым.