Тестирование с помощью Page Objects

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

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


Структура Page Object

1. Селекторы элементов Все элементы страницы следует хранить в свойствах класса или объекта. Пример:

class LoginPage {
  constructor() {
    this.usernameInput = element(by.id('username'));
    this.passwordInput = element(by.id('password'));
    this.loginButton = element(by.css('button[type="submit"]'));
    this.errorMessage = element(by.css('.error-message'));
  }
}

2. Методы взаимодействия Методы инкапсулируют действия пользователя: ввод текста, клики, проверки состояния элементов. Это позволяет тестам быть более выразительными:

class LoginPage {
  constructor() {
    this.usernameInput = element(by.id('username'));
    this.passwordInput = element(by.id('password'));
    this.loginButton = element(by.css('button[type="submit"]'));
    this.errorMessage = element(by.css('.error-message'));
  }

  async enterUsername(username) {
    await this.usernameInput.clear();
    await this.usernameInput.sendKeys(username);
  }

  async enterPassword(password) {
    await this.passwordInput.clear();
    await this.passwordInput.sendKeys(password);
  }

  async clickLogin() {
    await this.loginButton.click();
  }

  async getErrorMessageText() {
    return await this.errorMessage.getText();
  }
}

Преимущества использования Page Objects

  • Снижение дублирования кода: Селекторы и методы переиспользуются в разных тестах.
  • Повышение читаемости тестов: Тесты становятся понятными, они описывают поведение пользователя, а не детали DOM.
  • Легкость поддержки: При изменении интерфейса страницы нужно обновить только Page Object, а не все тесты.
  • Инкапсуляция логики страницы: Тесты не зависят от структуры HTML, а взаимодействуют только с методами Page Object.

Интеграция с Protractor

Protractor использует WebDriverJS, поэтому все действия с элементами являются асинхронными. Для Page Objects это означает использование async/await при взаимодействии с элементами:

const loginPage = new LoginPage();

describe('Авторизация', () => {
  it('должна показывать ошибку при неверных данных', async () => {
    await browser.get('https://example.com/login');
    await loginPage.enterUsername('wronguser');
    await loginPage.enterPassword('wrongpass');
    await loginPage.clickLogin();
    const message = await loginPage.getErrorMessageText();
    expect(message).toEqual('Неверный логин или пароль');
  });
});

Организация больших проектов

При масштабировании проекта рекомендуется:

  • Разделять Page Objects по функциональности: отдельная папка pages для каждой группы страниц.
  • Использовать базовый класс для общих методов: например, для навигации, ожиданий элементов или кликов.

Пример базового класса:

class BasePage {
  async navigateTo(url) {
    await browser.get(url);
  }

  async click(element) {
    await element.click();
  }

  async type(element, text) {
    await element.clear();
    await element.sendKeys(text);
  }

  async getText(element) {
    return await element.getText();
  }
}

Page Object может наследовать этот класс:

class LoginPage extends BasePage {
  constructor() {
    super();
    this.usernameInput = element(by.id('username'));
    this.passwordInput = element(by.id('password'));
    this.loginButton = element(by.css('button[type="submit"]'));
  }

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

Синхронизация и ожидания

Protractor автоматически синхронизируется с Angular-приложениями, но при работе с неангуларными элементами или динамическими компонентами рекомендуется использовать ExpectedConditions:

const EC = protractor.ExpectedConditions;

await browser.wait(EC.visibilityOf(loginPage.errorMessage), 5000);
const message = await loginPage.getErrorMessageText();

Это предотвращает ошибки типа ElementNotVisible или StaleElementReferenceError.


Паттерн Page Object и тесты end-to-end

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

await homePage.searchProduct('laptop');
await searchResultsPage.selectProduct(0);
await productPage.addToCart();
await cartPage.checkout();

Каждое действие скрывает детали DOM и делает тесты компактными, понятными и поддерживаемыми.


Рекомендации по написанию Page Objects

  • Использовать понятные имена методов, отражающие действия пользователя, а не технические детали.
  • Не включать в Page Object ассерты — проверки оставлять в тестах.
  • Ограничивать прямой доступ к элементам страницы через свойства класса, чтобы избежать хаоса в тестах.
  • Следить за стабильностью селекторов, предпочтительно использовать уникальные id, data-* атрибуты, а не сложные CSS-селекторы.

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