QA Interview Hub · База знань · Тести
Патерни
Page Object Model
Яку проблему вирішує Page Object
Без Page Object локатори і дії з UI розкидані по всіх тестах. Змінився data-testid кнопки логіну, і ви правите 40 файлів. Тести читаються як набір кліків, а не як бізнес-сценарій.
Page Object (POM) інкапсулює сторінку або екран: локатори і дії живуть в одному класі, тест викликає методи з бізнес-назвами. Головні вигоди:
- Одна точка зміни. Локатор змінився, правите один рядок.
- Читабельність.
await loginPage.login(user) замість трьох fill і click. - Повторне використання. Однакові дії не копіюються між тестами.
У Playwright є важлива особливість: Locator лінивий. Він не шукає елемент у момент створення, а тільки при дії чи перевірці. Тому локатори можна спокійно створювати в конструкторі як readonly поля, навіть якщо сторінка ще не відкрита.
Важливо розуміти, що POM це не "клас на кожен URL". Межі Page Object визначає зміст: логічний екран, модальне вікно, складна форма. Якщо сторінка величезна, її розбивають на компоненти (наступний розділ).
На співбесіді: Page Object інкапсулює локатори і дії сторінки в одному класі, тому при зміні UI я правлю одне місце, а тести читаються як бізнес-сценарії. У Playwright локатори ліниві, тому я оголошую їх у конструкторі як readonly поля.
Мінімальний Page Object у Playwright
import { type Page, type Locator, expect } from '@playwright/test';
export class LoginPage {
readonly email: Locator;
readonly password: Locator;
readonly submit: Locator;
readonly error: Locator;
constructor(private readonly page: Page) {
this.email = page.getByLabel('Email');
this.password = page.getByLabel('Password');
this.submit = page.getByRole('button', { name: 'Sign in' });
this.error = page.getByRole('alert');
}
async goto() {
await this.page.goto('/login');
}
async login(email: string, password: string) {
await this.email.fill(email);
await this.password.fill(password);
await this.submit.click();
}
}
Тест:
test('shows error for wrong password', async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login('user@test.com', 'wrong');
await expect(loginPage.error).toHaveText('Invalid credentials');
});
Ключові моменти: page приходить ззовні через конструктор (не глобальна змінна), методи async і повертають Promise, локатори публічні, щоб тест міг робити web-first перевірки expect(locator). Методи описують намір користувача (login), а не механіку (clickSubmitButton). Якщо дія веде на іншу сторінку, метод може повертати новий Page Object, але в Playwright це не обов'язково, бо page той самий.
На співбесіді: Мій Page Object отримує page у конструкторі, тримає локатори як readonly поля і має async методи з бізнес-назвами. Локатори я лишаю доступними, щоб робити перевірки через expect(locator) у тесті.
Асерти в Page Object, god objects і наслідування
Асерти всередині Page Object
Класична дискусія. Мартін Фаулер радить тримати асерти в тестах: Page Object описує сторінку, тест вирішує, що перевіряти. Компромісний підхід, популярний у Playwright: дозволені методи-очікування стану, наприклад expectLoaded() з await expect(this.heading).toBeVisible(), бо це синхронізація, а не бізнес-перевірка. А от verifyOrderTotalIs100() у Page Object ховає мету тесту і погано перевикористовується.
God Page Object
Клас на 1500 рядків з усіма елементами застосунку. Ознаки: десятки не пов'язаних методів, постійні merge-конфлікти, ніхто не знає, що там є. Лікування: розбити на сторінки і компоненти (хедер, таблиця, модалка).
Ланцюжки наслідування
BasePage -> AuthenticatedPage -> AdminPage -> AdminUsersPage. Через рік зміна в BasePage ламає все, а щоб зрозуміти метод, треба пройти 4 файли. Кращий варіант: мінімальний або відсутній базовий клас, спільні частини через композицію (readonly header = new Header(page)).
Інші помилки
- Методи повертають
boolean замість того, щоб дати тесту локатор (isVisible() не чекає, на відміну від expect().toBeVisible()). page.waitForTimeout() у методах замість auto-waiting.- Page Object створює тестові дані або ходить в API: це вже інша відповідальність.
На співбесіді: Я тримаю бізнес-асерти в тестах, а в Page Object допускаю лише очікування стану сторінки типу expectLoaded. Великі сторінки ділю на компоненти, а замість глибоких ієрархій наслідування використовую композицію.