В тестировании веб-приложений на JavaScript с помощью Puppeteer абстракция и инкапсуляция играют ключевую роль в поддерживаемости и масштабируемости тестов. Puppeteer предоставляет низкоуровневый API для управления браузером Chromium, но прямое использование всех методов без структуры быстро приводит к хаосу в проекте. Абстракции позволяют скрыть детали реализации, а инкапсуляция — ограничить доступ к внутренним механизмам, оставляя внешний интерфейс понятным и стабильным.
Паттерн Page Object Model (POM) является стандартом для организации тестов. Каждая страница или компонент приложения инкапсулируется в отдельный класс, который управляет взаимодействием с DOM и предоставляет методы высокого уровня.
class LoginPage {
constructor(page) {
this.page = page;
this.selectors = {
usernameInput: &
passwordInput: '#password',
loginButton: 'button[type="submit"]'
};
}
async navigate() {
await this.page.goto('https://example.com/login');
}
async login(username, password) {
await this.page.type(this.selectors.usernameInput, username);
await this.page.type(this.selectors.passwordInput, password);
await this.page.click(this.selectors.loginButton);
}
async isErrorVisible() {
return await this.page.$eval('.error', el => el.style.display !== 'none');
}
}
Ключевые моменты:
selectors инкапсулируют локаторы, чтобы их изменение не
ломало тесты.
navigate, login,
isErrorVisible предоставляют высокий уровень
взаимодействия, скрывая детали DOM.
Puppeteer требует явных ожиданий элементов перед взаимодействием. Инкапсуляция этих действий повышает надёжность тестов и снижает дублирование кода:
class BasePage {
constructor(page) {
this.page = page;
}
async click(selector) {
await this.page.waitForSelector(selector, { visible: true });
await this.page.click(selector);
}
async type(selector, text) {
await this.page.waitForSelector(selector, { visible: true });
await this.page.type(selector, text);
}
async getText(selector) {
await this.page.waitForSelector(selector, { visible: true });
return await this.page.$eval(selector, el => el.textContent.trim());
}
}
Использование базового класса позволяет наследовать общий функционал во всех страницах:
class DashboardPage extends BasePage {
constructor(page) {
super(page);
this.selectors = {
welcomeMessage: '.welcome'
};
}
async getWelcomeText() {
return await this.getText(this.selectors.welcomeMessage);
}
}
Преимущества:
click, type,
getText для любых страниц.
BasePage.
Некоторые сценарии требуют комбинации действий: заполнение форм, переходы между страницами, проверка состояний. В этом случае удобно создавать сервисные объекты или helper-классы:
class AuthService {
constructor(loginPage, dashboardPage) {
this.loginPage = loginPage;
this.dashboardPage = dashboardPage;
}
async loginAndCheck(username, password) {
await this.loginPage.navigate();
await this.loginPage.login(username, password);
return await this.dashboardPage.getWelcomeText();
}
}
Такой подход отделяет сценарий от деталей реализации страниц. Тесты становятся короткими, читаемыми и понятными:
const loginPage = new LoginPage(page);
const dashboardPage = new DashboardPage(page);
const authService = new AuthService(loginPage, dashboardPage);
const welcome = await authService.loginAndCheck('user1', 'pass1');
console.assert(welcome.includes('Welcome'), 'Login failed');
В Puppeteer важно контролировать состояние страницы и браузера. Инкапсуляция позволяет безопасно хранить состояние между тестами, например, сессии или cookies:
class SessionManager {
constructor(page) {
this.page = page;
}
async saveCookies() {
this.cookies = await this.page.cookies();
}
async loadCookies() {
if (this.cookies) {
await this.page.setCookie(...this.cookies);
}
}
}
Инкапсуляция работы с cookies обеспечивает повторное использование и предсказуемость тестов, без необходимости дублировать логику в каждом тесте.
Puppeteer поддерживает перехват и модификацию сетевых запросов через
page.setRequestInterception. Создание абстракций для работы
с API повышает гибкость:
class NetworkInterceptor {
constructor(page) {
this.page = page;
this.requests = [];
}
async start() {
await this.page.setRequestInterception(true);
this.page.on('request', request => {
this.requests.push(request.url());
request.continue();
});
}
getRequests() {
return this.requests;
}
}
Использование такого класса позволяет:
Использование этих подходов в Puppeteer позволяет строить стабильные, читаемые и поддерживаемые тесты, где:
Эти принципы создают основу для любых сложных тестовых сценариев, включая интеграционные и e2e-тесты с Puppeteer, сохраняя баланс между деталями реализации и удобством использования.