Классы в Playwright (Page Object)
Урок: Классы в Playwright (Page Object)
Введение
На предыдущих уроках мы разобрали:
- что такие классы — как способ объединять данные;
- что такое методы — как способ описывать поведение.
Теперь пришло время применить это к реальной задаче — автоматизации тестов.
Представь, что ты пишешь тесты для сайта:
- нужно открыть страницу логина;
- заполнить поля;
- нажать кнопку;
- проверить результат.
Можно писать всё прямо в тесте. В Playwright это асинхронные вызовы; в песочнице теории используем мок page:
const page = {
fill: async (selector, value) => console.log('fill', selector, value),
click: async (selector) => console.log('click', selector),
};
(async () => {
await page.fill('#login', 'user');
await page.fill('#password', '123');
await page.click('#submit');
})();
Сначала это выглядит нормально. Но:
- тесты начинают повторяться;
- селекторы разбросаны по коду;
- при изменении страницы нужно править десятки мест.
И вот здесь появляется подход, который используется в реальных проектах — Page Object (Page Object Model).
Что такое Page Object
Page Object — это способ представить страницу сайта как класс.
То есть:
- страница = класс;
- элементы страницы = свойства;
- действия пользователя = методы.
Мы буквально говорим:
«Страница логина — это объект, который умеет вводить логин, пароль и нажимать кнопку».
Первый Page Object класс
Создадим класс для страницы логина:
class LoginPage {
constructor(page) {
this.page = page;
}
}
const page = { label: 'mock' };
const loginPage = new LoginPage(page);
console.log('page внутри объекта:', loginPage.page.label);
Объяснение:
page— в реальном тесте это объект Playwright;- мы сохраняем его в
this.page, чтобы использовать внутри методов; - теперь класс знает, с какой страницей он работает.
Добавление элементов страницы
Обычно в Page Object мы описываем элементы:
class LoginPage {
constructor(page) {
this.page = page;
this.loginInput = '#login';
this.passwordInput = '#password';
this.submitButton = '#submit';
}
}
const page = {};
const lp = new LoginPage(page);
console.log(lp.loginInput, lp.passwordInput, lp.submitButton);
Что происходит:
- мы сохраняем селекторы в свойства;
- теперь они не разбросаны по тесту;
- если селектор изменится — меняем в одном месте.
Добавление методов (действий)
Теперь добавим поведение — методы (с моком page, как в прошлом уроке):
const page = {
fill: async (selector, value) => console.log('fill', selector, value),
click: async (selector) => console.log('click', selector),
};
class LoginPage {
constructor(page) {
this.page = page;
this.loginInput = '#login';
this.passwordInput = '#password';
this.submitButton = '#submit';
}
fillLogin(login) {
return this.page.fill(this.loginInput, login);
}
fillPassword(password) {
return this.page.fill(this.passwordInput, password);
}
clickSubmit() {
return this.page.click(this.submitButton);
}
}
(async () => {
const loginPage = new LoginPage(page);
await loginPage.fillLogin('user');
await loginPage.fillPassword('123');
await loginPage.clickSubmit();
})();
Разбор:
fillLogin()— вводит логин;fillPassword()— вводит пароль;clickSubmit()— нажимает кнопку;- внутри используется
this.pageи сохранённые селекторы.
Мы спрятали всю работу с «страницей» внутрь класса.
Как это выглядит в тесте
Тест читается как сценарий. Ниже — тот же класс и вызовы в одном фрагменте (в реальном проекте класс лежит в отдельном файле):
const page = {
fill: async (selector, value) => console.log('fill', selector, value),
click: async (selector) => console.log('click', selector),
};
class LoginPage {
constructor(page) {
this.page = page;
this.loginInput = '#login';
this.passwordInput = '#password';
this.submitButton = '#submit';
}
fillLogin(login) {
return this.page.fill(this.loginInput, login);
}
fillPassword(password) {
return this.page.fill(this.passwordInput, password);
}
clickSubmit() {
return this.page.click(this.submitButton);
}
}
(async () => {
const loginPage = new LoginPage(page);
await loginPage.fillLogin('user');
await loginPage.fillPassword('123');
await loginPage.clickSubmit();
})();
Было бы низкоуровнево: await page.fill('#login', 'user').
Стало по смыслу: await loginPage.fillLogin('user') — ровно так в большом блоке выше после const loginPage = new LoginPage(page).
Почему это важно
Page Object решает сразу несколько проблем: переиспользование, централизация селекторов, читаемость.
Улучшение: объединение действий
Можно сделать метод выше уровнем — один сценарий «логин»:
const page = {
fill: async (selector, value) => console.log('fill', selector, value),
click: async (selector) => console.log('click', selector),
};
class LoginPage {
constructor(page) {
this.page = page;
this.loginInput = '#login';
this.passwordInput = '#password';
this.submitButton = '#submit';
}
async login(login, password) {
await this.page.fill(this.loginInput, login);
await this.page.fill(this.passwordInput, password);
await this.page.click(this.submitButton);
}
}
(async () => {
const loginPage = new LoginPage(page);
await loginPage.login('user', '123');
})();
Важно:
Здесь появился async / await внутри метода — подробнее в следующем уроке.
Связь с предыдущими уроками
-
Урок 1: мы создали класс → теперь это страница
-
Урок 2: мы добавили методы → теперь это действия пользователя
Сейчас мы объединили всё:
класс + методы = Page Object
Связь с следующими уроками
Дальше мы усилим эту модель:
-
Урок 4: разберём
async/await, потому что все действия в Playwright асинхронные -
Урок 5: научимся правильно использовать Page Object в тестах и строить структуру проекта
Итоговое понимание
Page Object — это способ думать о тестах через объекты.
Главная идея урока:
Мы превращаем страницу в класс, а действия пользователя — в методы этого класса.
Это даёт:
- чистый и понятный код;
- переиспользуемость;
- простую поддержку;
- структуру, которая масштабируется.
Если раньше ты писал низкоуровневые вызовы к page, теперь ты думаешь так:
«Страница умеет кликать, вводить данные и выполнять действия».
И это мышление — основа профессиональной автоматизации тестирования.