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

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


Структура Page Object класса

Page Object класс обычно содержит три ключевых компонента:

  1. Локаторы элементов страницы Локаторы описывают элементы, с которыми предстоит работать. В Protractor используется объект by для определения стратегий поиска: by.id, by.css, by.xpath и др.

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

  3. Методы ожидания и проверки состояния Для стабильности тестов важно использовать ожидания (ExpectedConditions), чтобы убедиться, что элемент доступен для взаимодействия или имеет нужное состояние.

Пример структуры:

const { element, by, browser, ExpectedConditions: EC } = require('protractor');

class LoginPage {
    constructor() {
        this.usernameInput = element(by.id('username'));
        this.passwordInput = element(by.id('password'));
        this.loginButton = element(by.css('.login-btn'));
        this.errorMessage = element(by.css('.error-msg'));
    }

    async enterUsername(username) {
        await browser.wait(EC.visibilityOf(this.usernameInput), 5000);
        await this.usernameInput.clear();
        await this.usernameInput.sendKeys(username);
    }

    async enterPassword(password) {
        await browser.wait(EC.visibilityOf(this.passwordInput), 5000);
        await this.passwordInput.clear();
        await this.passwordInput.sendKeys(password);
    }

    async clickLogin() {
        await browser.wait(EC.elementToBeClickable(this.loginButton), 5000);
        await this.loginButton.click();
    }

    async getErrorMessageText() {
        await browser.wait(EC.visibilityOf(this.errorMessage), 5000);
        return await this.errorMessage.getText();
    }
}

module.exports = new LoginPage();

Правила именования и организации

  • Имена классов — должны отражать сущность страницы: LoginPage, DashboardPage, SearchResultsPage.
  • Имена методов — описательные, глагольные: enterUsername, clickLogin, getErrorMessageText.
  • Локаторы — хранить в свойствах класса, чтобы их можно было легко изменить при изменении верстки.
  • Повторное использование — общие элементы можно вынести в базовый класс BasePage.

Базовый класс Page Object

Базовый класс упрощает создание новых Page Object и позволяет использовать повторяемые методы, например, ожидание элементов, скролл, навигацию:

class BasePage {
    async click(element) {
        await browser.wait(EC.elementToBeClickable(element), 5000);
        await element.click();
    }

    async typeText(element, text) {
        await browser.wait(EC.visibilityOf(element), 5000);
        await element.clear();
        await element.sendKeys(text);
    }

    async getText(element) {
        await browser.wait(EC.visibilityOf(element), 5000);
        return await element.getText();
    }

    async waitForElementVisible(element, timeout = 5000) {
        await browser.wait(EC.visibilityOf(element), timeout);
    }
}

module.exports = BasePage;

Page Object классы наследуют BasePage и используют его методы:

const BasePage = require('./BasePage');

class LoginPage extends BasePage {
    constructor() {
        super();
        this.usernameInput = element(by.id('username'));
        this.passwordInput = element(by.id('password'));
        this.loginButton = element(by.css('.login-btn'));
    }

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

module.exports = new LoginPage();

Использование Page Object в тестах

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

const LoginPage = require('../pages/LoginPage');

describe('Авторизация', () => {
    it('должна отображать ошибку при неверных данных', async () => {
        await browser.get('https://example.com/login');
        await LoginPage.login('wrongUser', 'wrongPass');
        const error = await LoginPage.getErrorMessageText();
        expect(error).toContain('Неверное имя пользователя или пароль');
    });
});

Такой подход повышает читаемость и упрощает поддержку тестов. Любые изменения в верстке затрагивают только Page Object, а тесты остаются неизменными.


Локаторы и стратегии поиска

Выбор стратегии локатора влияет на стабильность тестов:

  • by.id и by.css — предпочтительные, быстрые и устойчивые.
  • by.xpath — использовать при необходимости, если нет других способов.
  • by.binding, by.model — специфично для Angular, позволяет работать с привязками.

Рекомендуется избегать жёстких абсолютных XPath, использовать относительные и CSS-селекторы с классами и атрибутами.


Советы по поддержке Page Object

  • Разделять Page Object по страницам и крупным компонентам.
  • Использовать отдельные классы для повторяющихся элементов (меню, таблицы, модальные окна).
  • Всегда использовать методы ожидания перед взаимодействием.
  • Добавлять асинхронные методы для операций, которые могут занимать время (анимации, загрузка данных).
  • Инкапсулировать сложные сценарии как методы Page Object (loginAsAdmin, fillForm) для повторного использования.

Создание Page Object классов обеспечивает устойчивость тестов к изменениям интерфейса и позволяет строить читаемую, поддерживаемую структуру автоматизации в Protractor.