Принципы Page Object паттерна

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


Основные принципы

1. Инкапсуляция элементов и действий Каждая страница или компонент веб-приложения оформляется как отдельный объект, содержащий:

  • локаторы элементов (selectors);
  • методы для взаимодействия с ними (actions), например, ввод текста, клики, проверка состояния;
  • методы для получения данных с страницы (getters).

Такой подход минимизирует дублирование кода, так как локаторы и действия хранятся в одном месте и могут использоваться во многих тестах.

Пример:

class LoginPage {
    get usernameInput() { return $('#username'); }
    get passwordInput() { return $('#password'); }
    get loginButton() { return $('#login'); }

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

module.exports = new LoginPage();

2. Четкое разделение тестов и логики страниц Тесты должны содержать только сценарии и проверки, а не детали работы с элементами. Page Object берёт на себя все низкоуровневые взаимодействия, обеспечивая:

  • уменьшение связности тестов с UI;
  • простую замену локаторов без изменения самих тестов;
  • возможность переиспользования методов в разных тестах.

Пример использования Page Object в тесте:

const loginPage = require('../pageobjects/login.page');

describe('Авторизация', () => {
    it('позволяет пользователю войти в систему', async () => {
        await browser.url('/login');
        await loginPage.login('user1', 'password123');
        await expect(browser).toHaveUrlContaining('/dashboard');
    });
});

3. Методы высокого уровня вместо прямого взаимодействия с элементами Page Object не должен быть просто набором геттеров для локаторов. Рекомендуется создавать методы, которые отражают бизнес-действия пользователя, а не технические шаги. Это упрощает чтение тестов и снижает количество изменений при изменении интерфейса.

Плохой пример:

await $('#username').setValue('user');
await $('#password').setValue('pass');
await $('#login').click();

Хороший пример с Page Object:

await loginPage.login('user', 'pass');

4. Использование явных ожиданий и стабильных стратегий взаимодействия Page Object может включать методы для ожидания элементов перед действием. Это повышает стабильность тестов и снижает вероятность ошибок из-за асинхронной загрузки страницы.

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

5. Поддержка переиспользуемости через базовые классы Часто создают базовый Page Object, который содержит общие методы для всех страниц, например:

  • open(path) — открыть URL страницы;
  • getTitle() — получить заголовок страницы;
  • waitForElement(selector) — универсальное ожидание элемента.
class BasePage {
    async open(path) {
        await browser.url(path);
    }

    async getTitle() {
        return browser.getTitle();
    }

    async waitForElement(element) {
        await element.waitForDisplayed();
    }
}

module.exports = BasePage;

Дочерние страницы наследуют этот класс и расширяют функциональность специфическими методами.


6. Разделение страниц и компонентов Для крупных приложений полезно делить UI на:

  • Page Objects — для целых страниц;
  • Component Objects — для повторяющихся блоков, например, шапки, футера, виджетов.

Это позволяет переиспользовать код компонентов на разных страницах, сохраняя структуру чистой и модульной.

Пример компонента:

class HeaderComponent {
    get profileButton() { return $('#profile'); }
    get logoutButton() { return $('#logout'); }

    async logout() {
        await this.profileButton.click();
        await this.logoutButton.click();
    }
}

module.exports = new HeaderComponent();

7. Чистая организация файлов Обычно структура проекта с Page Object выглядит так:

/test
  /pageobjects
    login.page.js
    dashboard.page.js
    /components
      header.component.js
      footer.component.js
  /specs
    login.spec.js
    dashboard.spec.js

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


8. Использование TypeScript или JSDoc для автодополнения Для крупных проектов рекомендуется добавлять аннотации типов, чтобы IDE подсказывала методы и свойства страниц. Это снижает количество ошибок и ускоряет разработку тестов.

/**
 * @typedef {import('webdriverio').Element} Element
 */
class LoginPage {
    /** @type {Element} */
    get usernameInput() { return $('#username'); }
}

Page Object паттерн является ключевым инструментом для создания стабильных, читаемых и масштабируемых тестов в WebdriverIO. Следование его принципам обеспечивает четкое разделение ответственности, переиспользуемость кода и минимизацию влияния изменений UI на тестовую инфраструктуру.