Component Object Pattern (COP) — это подход в тестировании, который помогает организовать код тестов таким образом, чтобы каждый элемент интерфейса и его действия были инкапсулированы в отдельном объекте. В Playwright данный паттерн позволяет сделать тесты более поддерживаемыми, гибкими и читаемыми. Основной идеей является разделение логики тестирования интерфейса на отдельные компоненты, что позволяет легко работать с частями UI, а также упрощает повторное использование кода.
В контексте Playwright каждый компонент будет представлять собой класс или модуль, инкапсулирующий взаимодействие с конкретным элементом UI, например, кнопкой, полем ввода, меню или даже целым блоком страницы. Важно, что компоненты должны быть максимально независимыми и самодостаточными, предоставляя только те методы, которые необходимы для взаимодействия с данным элементом.
Пример структуры компонента:
class LoginButton {
constructor(page) {
this.page = page;
this.selector = 'button#login';
}
async click() {
await this.page.click(this.selector);
}
async isVisible() {
return await this.page.isVisible(this.selector);
}
}
В этом примере класс LoginButton инкапсулирует логику
взаимодействия с кнопкой логина. Он имеет методы click для
нажатия на кнопку и isVisible для проверки видимости
кнопки. Такие методы можно использовать в тестах, не дублируя логику их
выполнения каждый раз.
Playwright позволяет использовать Page Object Pattern, но для более сложных интерфейсов часто требуется более детализированный подход. Разделение на компоненты полезно, когда интерфейс состоит из повторяющихся или сложных блоков.
Предположим, у нас есть форма логина с несколькими элементами, такими
как поля ввода для имени пользователя и пароля, а также кнопка логина.
Вместо того чтобы все эти элементы инкапсулировать в одном классе
LoginPage, лучше разделить их на отдельные компоненты:
class UsernameField {
constructor(page) {
this.page = page;
this.selector = 'input#username';
}
async type(text) {
await this.page.fill(this.selector, text);
}
async getValue() {
return await this.page.inputValue(this.selector);
}
}
class PasswordField {
constructor(page) {
this.page = page;
this.selector = 'input#password';
}
async type(text) {
await this.page.fill(this.selector, text);
}
}
class LoginButton {
constructor(page) {
this.page = page;
this.selector = 'button#login';
}
async click() {
await this.page.click(this.selector);
}
}
Каждый компонент теперь отвечает только за свой элемент формы. Это упрощает добавление новых методов и улучшает поддержку тестов. Когда необходимо внести изменения в поведение компонента (например, изменить селектор или добавить новый метод), это можно сделать локально, не затрагивая другие части системы.
Читаемость и поддерживаемость Код становится более структурированным и понятным. Каждый компонент выполняет свою задачу, что упрощает диагностику проблем и поддержку тестов.
Повторное использование Компоненты могут использоваться многократно в разных тестах. Это позволяет сократить дублирование кода и делает тесты более гибкими.
Изоляция изменений Если потребуется изменить логику взаимодействия с элементом UI (например, изменить селектор или добавить дополнительную логику), это можно сделать только в одном месте, не затрагивая остальные тесты.
Тестирование отдельных компонентов В случае необходимости, компоненты можно протестировать изолированно, что ускоряет процесс отладки и тестирования.
Тест с использованием Component Object Pattern будет выглядеть гораздо чище и проще:
const { test, expect } = require('@playwright/test');
const { UsernameField, PasswordField, LoginButton } = require('./components');
test('Login functionality', async ({ page }) => {
const usernameField = new UsernameField(page);
const passwordField = new PasswordField(page);
const loginButton = new LoginButton(page);
await usernameField.type('user123');
await passwordField.type('password123');
await loginButton.click();
// Проверка, что мы успешно зашли
await expect(page).toHaveURL('https://example.com/dashboard');
});
В этом тесте мы создаем экземпляры компонентов для каждого элемента страницы, взаимодействуем с ними и проверяем, что логин прошел успешно. Каждый шаг взаимодействия с UI разделен и инкапсулирован в своих компонентах, что делает код теста кратким и понятным.
Для более гибкой работы можно использовать фабричный метод для создания экземпляров компонентов. Например, если на странице есть несколько кнопок с одинаковым поведением, их можно инкапсулировать в один общий компонент, используя параметры:
class Button {
constructor(page, selector) {
this.page = page;
this.selector = selector;
}
async click() {
await this.page.click(this.selector);
}
async isVisible() {
return await this.page.isVisible(this.selector);
}
}
class LoginPage {
constructor(page) {
this.page = page;
this.usernameField = new UsernameField(page);
this.passwordField = new PasswordField(page);
this.loginButton = new Button(page, 'button#login');
}
async login(username, password) {
await this.usernameField.type(username);
await this.passwordField.type(password);
await this.loginButton.click();
}
}
В этом примере класс LoginPage использует компонент
Button, передавая ему селектор кнопки, что позволяет
переиспользовать компонент для других кнопок на странице.
Не перегружайте компоненты Каждый компонент должен отвечать только за один элемент или блок на странице. Избегайте создания “тяжелых” компонентов, которые инкапсулируют слишком много логики.
Используйте наследование с осторожностью Если компоненты имеют схожие поведения, можно использовать наследование для общего функционала. Однако следует избегать сложной иерархии, которая может затруднить понимание и поддержку кода.
Поддерживайте модульность Чем меньше компонентов будет зависеть друг от друга, тем проще будет поддерживать тесты в будущем. Модули можно тестировать и обновлять независимо.
Component Object Pattern помогает организовать код в Playwright, улучшая читаемость и поддержку тестов. Этот подход значительно упрощает управление сложными UI, позволяет избежать дублирования кода и облегчает работу с различными элементами интерфейса.