Page Object Model

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

Принципы POM

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

2. Упрощение тестов: Тесты в POM работают с объектами страниц, а не с прямым взаимодействием с DOM. Это значительно упрощает код тестов, так как изменяются только классы объектов, а не сами тесты, если меняются элементы интерфейса.

3. Разделение тестов и страниц: Тесты не содержат подробностей о том, как элементы страницы устроены или как взаимодействовать с ними. Они только проверяют функциональность, делегируя взаимодействие с UI объектам страницы.

Структура Page Object

Обычно структура классов выглядит следующим образом:

  1. Page Object Class — класс, представляющий страницу или её компонент.
  2. Методы — функции, которые взаимодействуют с элементами страницы.
  3. Элементы страницы — элементы DOM, с которыми происходит взаимодействие.

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

Пример Page Object для Puppeteer

// loginPage.js
class LoginPage {
  constructor(page) {
    this.page = page;
    this.usernameInput = page.$(&
    this.passwordInput = page.$('#password');
    this.loginButton = page.$('#loginButton');
  }

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

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

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

  async login(username, password) {
    await this.enterUsername(username);
    await this.enterPassword(password);
    await this.submit();
  }
}

module.exports = LoginPage;

В этом примере класс LoginPage инкапсулирует взаимодействие с элементами страницы: поля ввода для имени пользователя и пароля, а также кнопку входа. Методы enterUsername, enterPassword, submit реализуют действия на странице, а метод login выполняет все необходимые шаги для успешного входа.

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

Теперь можно использовать объект страницы в тестах для автоматизации логина.

// loginTest.js
const puppeteer = require('puppeteer');
const LoginPage = require('./loginPage');

describe('Login Test', () => {
  let browser;
  let page;

  beforeAll(async () => {
    browser = await puppeteer.launch({ headless: false });
    page = await browser.newPage();
  });

  afterAll(async () => {
    await browser.close();
  });

  it('should login successfully', async () => {
    const loginPage = new LoginPage(page);

    await page.goto('https://example.com/login');
    await loginPage.login('user1', 'password123');

    // Проверка успешного логина
    const welcomeMessage = await page.$eval('#welcomeMessage', el => el.textContent);
    expect(welcomeMessage).toBe('Welcome, user1!');
  });
});

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

Преимущества применения POM

  1. Упрощение тестов: Тесты становятся более чистыми и краткими, так как они работают с абстракцией страниц, а не с подробным взаимодействием с элементами.
  2. Повторное использование кода: Логика взаимодействия с элементами страницы может быть повторно использована в разных тестах, что снижает дублирование кода.
  3. Лёгкость в поддержке: При изменении UI достаточно обновить лишь Page Object, а тесты не требуют изменений. Это особенно полезно в случаях, когда элементы на странице часто меняются.
  4. Снижение вероятности ошибок: Снижается вероятность ошибок, так как код взаимодействия с элементами страницы централизован в одном месте, что уменьшает дублирование и сокращает число точек возможных изменений.

Стратегии организации Page Objects

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

  2. Разделение на несколько объектов: Если страница сложная и включает множество компонентов (например, форма, таблица, всплывающее окно), рекомендуется разделить её на несколько классов, каждый из которых будет представлять отдельный компонент страницы.

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

Пример разделения Page Object на компоненты

// header.js
class Header {
  constructor(page) {
    this.page = page;
    this.loginButton = page.$('.login');
    this.logoutButton = page.$('.logout');
  }

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

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

module.exports = Header;

// loginPage.js
const Header = require('./header');

class LoginPage {
  constructor(page) {
    this.page = page;
    this.header = new Header(page);
    this.usernameInput = page.$('#username');
    this.passwordInput = page.$('#password');
    this.submitButton = page.$('#submit');
  }

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

module.exports = LoginPage;

В данном примере класс Header инкапсулирует действия, связанные с шапкой страницы, а LoginPage использует этот компонент для упрощения тестирования.

Переход от простого теста к Page Object Model

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

Пример простого теста без POM:

it('should login successfully', async () => {
  await page.goto('https://example.com/login');
  await page.type('#username', 'user1');
  await page.type('#password', 'password123');
  await page.click('#loginButton');

  const welcomeMessage = await page.$eval('#welcomeMessage', el => el.textContent);
  expect(welcomeMessage).toBe('Welcome, user1!');
});

Применение POM позволяет улучшить структуру тестов, как это показано ранее, с разделением на классы объектов страниц.

Заключение

Использование паттерна Page Object Model при автоматизации тестирования с Puppeteer помогает организовать код, улучшить читаемость и поддержку тестов, а также сделать тесты более стабильными при изменениях в пользовательском интерфейсе.