Page Object Model (POM) — это паттерн проектирования, применяемый в автоматизированном тестировании для улучшения читаемости и поддерживаемости тестов. Он представляет собой структуру, в которой для каждой веб-страницы или её компонента создаётся отдельный класс, инкапсулирующий логику взаимодействия с элементами страницы. Это позволяет избежать дублирования кода и облегчить управление тестами при изменениях в пользовательском интерфейсе.
1. Инкапсуляция действий и элементов страницы: Каждая веб-страница или компонент страницы представляется как объект с методами, которые реализуют действия, доступные пользователю на этой странице. Элементы страницы, с которыми тест взаимодействует, инкапсулируются внутри класса.
2. Упрощение тестов: Тесты в POM работают с объектами страниц, а не с прямым взаимодействием с DOM. Это значительно упрощает код тестов, так как изменяются только классы объектов, а не сами тесты, если меняются элементы интерфейса.
3. Разделение тестов и страниц: Тесты не содержат подробностей о том, как элементы страницы устроены или как взаимодействовать с ними. Они только проверяют функциональность, делегируя взаимодействие с UI объектам страницы.
Обычно структура классов выглядит следующим образом:
Каждая страница или компонент имеет свой класс. Например, для страницы
входа в систему может быть создан класс LoginPage, который
будет содержать методы для ввода имени пользователя, пароля и нажатия на
кнопку входа.
// 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 выполняет все необходимые шаги для
успешного входа.
Теперь можно использовать объект страницы в тестах для автоматизации логина.
// 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, который
используется для выполнения всех необходимых шагов для входа. Тест
становится более читаемым и поддерживаемым, так как вся логика
взаимодействия с интерфейсом находится в отдельном классе.
Один класс для всей страницы: Для простых страниц, где нет сложных взаимодействий, можно использовать один класс для представления всей страницы. Однако с увеличением сложности страницы это может привести к перегрузке класса.
Разделение на несколько объектов: Если страница сложная и включает множество компонентов (например, форма, таблица, всплывающее окно), рекомендуется разделить её на несколько классов, каждый из которых будет представлять отдельный компонент страницы.
Абстракция действий: Для часто используемых действий, таких как навигация между страницами, можно создать базовые классы, которые будут инкапсулировать общие шаги. Например, базовый класс для перехода на главную страницу, проверки состояния страницы и т.д.
// 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 использует этот
компонент для упрощения тестирования.
При написании тестов без применения 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 помогает организовать код, улучшить читаемость и поддержку тестов, а также сделать тесты более стабильными при изменениях в пользовательском интерфейсе.