Композиция Page Objects

Page Object Model (POM) является фундаментальной концепцией при автоматизации тестирования веб-приложений. Она направлена на структурирование кода тестов, улучшение читаемости и уменьшение дублирования. Одним из ключевых аспектов POM является композиция Page Objects, которая позволяет строить более гибкую, поддерживаемую и модульную архитектуру тестов.


Принцип композиции

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

Преимущества композиции:

  • Повторное использование кода. Один объект компонента можно использовать на разных страницах.
  • Упрощение поддержки. Изменения в компоненте не требуют правки всех страниц, где он используется.
  • Чистая структура тестов. Тестовые сценарии становятся более читаемыми и логически организованными.

Создание базового компонента

В Protractor базовый компонент часто создается как отдельный класс или модуль, инкапсулирующий локаторы и методы работы с элементами.

class InputField {
  constructor(locator) {
    this.field = element(locator);
  }

  async enterText(text) {
    await this.field.clear();
    await this.field.sendKeys(text);
  }

  async getValue() {
    return this.field.getAttribute('value');
  }
}

В этом примере InputField представляет собой компонент ввода текста, который можно использовать на любой странице, где есть текстовое поле.


Интеграция компонентов в Page Object

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

class LoginPage {
  constructor() {
    this.usernameInput = new InputField(by.id('username'));
    this.passwordInput = new InputField(by.id('password'));
    this.loginButton = element(by.buttonText('Login'));
  }

  async login(username, password) {
    await this.usernameInput.enterText(username);
    await this.passwordInput.enterText(password);
    await this.loginButton.click();
  }
}

Здесь LoginPage композирует два объекта InputField, представляя логическую структуру страницы. Такой подход улучшает читаемость и позволяет переиспользовать InputField на других страницах.


Вложенные компоненты и сложные страницы

Для более крупных страниц часто применяются вложенные компоненты. Например, таблица с пагинацией и фильтрацией может быть выделена в отдельный объект, который затем используется в Page Object страницы:

class TableComponent {
  constructor(tableLocator) {
    this.table = element(tableLocator);
  }

  async getRowText(rowIndex) {
    return this.table.all(by.css('tr')).get(rowIndex).getText();
  }

  async clickRow(rowIndex) {
    await this.table.all(by.css('tr')).get(rowIndex).click();
  }
}

class UsersPage {
  constructor() {
    this.userTable = new TableComponent(by.id('usersTable'));
    this.addUserButton = element(by.id('addUser'));
  }

  async addUser() {
    await this.addUserButton.click();
  }

  async getUserName(rowIndex) {
    return this.userTable.getRowText(rowIndex);
  }
}

Преимущества такого подхода:

  • Код для работы с таблицей инкапсулирован в отдельном компоненте.
  • Методы страницы остаются простыми, фокусируясь на бизнес-логике.
  • Компонент таблицы можно использовать на других страницах с идентичной структурой.

Подход к локаторам при композиции

При использовании композиции важно правильно управлять локаторами, чтобы обеспечить изоляцию компонентов. Обычно каждый компонент получает локатор корневого элемента, относительно которого определяются дочерние элементы.

class ModalDialog {
  constructor(rootLocator) {
    this.root = element(rootLocator);
    this.title = this.root.element(by.css('.modal-title'));
    this.closeButton = this.root.element(by.css('.close'));
  }

  async getTitle() {
    return this.title.getText();
  }

  async close() {
    await this.closeButton.click();
  }
}

Такой подход позволяет:

  • Использовать несколько модальных окон на одной странице без конфликтов локаторов.
  • Сохранять независимость компонентов при изменении структуры DOM.

Асинхронность и композиция

В Protractor все операции с элементами являются асинхронными. При композиции важно правильно использовать async/await в методах компонентов и Page Object:

async loginAndVerify(username, password, expectedName) {
  await this.loginPage.login(username, password);
  const actualName = await this.dashboardPage.getUserName();
  expect(actualName).toEqual(expectedName);
}

Неправильное управление асинхронностью может привести к нестабильным тестам и ошибкам, связанным с обращением к элементам до их появления на странице.


Принципы построения композиции

  1. Модульность — каждый компонент решает строго одну задачу.
  2. Переиспользуемость — компоненты должны быть независимыми и легко использоваться в разных Page Objects.
  3. Инкапсуляция локаторов — внешние тесты не должны знать о внутренней структуре компонента.
  4. Четкая иерархия — Page Object состоит из компонентов, которые могут содержать вложенные компоненты, но не наоборот.

Практические рекомендации

  • Компоненты с высокой повторяемостью (кнопки, поля ввода, таблицы) оформлять отдельно.
  • Методы компонентов должны быть грубого уровня абстракции, скрывая низкоуровневые операции (click, sendKeys).
  • Page Objects должны представлять бизнес-операции, комбинируя методы компонентов.
  • Не использовать композицию для одноразовых элементов, чтобы избежать ненужной сложности.

Композиция Page Objects позволяет создавать чистую и поддерживаемую структуру тестов, делая их легко расширяемыми и стабильными при изменении интерфейса. Правильное применение этого подхода существенно повышает качество автоматизации и ускоряет работу над тестами.