QA Interview Hub · База знань · Тести
Патерни
Page Component і композиція
Page Component: що це і навіщо
Реальний UI складається з повторюваних блоків: хедер, навігація, таблиця з пагінацією, date picker, модальне вікно. Вони з'являються на багатьох сторінках. Якщо описувати їх у кожному Page Object, виходить дублювання, а якщо в BasePage, то god object.
Page Component це клас, який описує один блок UI і отримує не page, а кореневий локатор цього блоку. Усі внутрішні локатори будуються від кореня, тому компонент можна використати кілька разів на одній сторінці.
export class DataTable {
constructor(private readonly root: Locator) {}
rows() {
return this.root.getByRole('row');
}
rowByText(text: string) {
return this.rows().filter({ hasText: text });
}
async sortBy(column: string) {
await this.root.getByRole('columnheader', { name: column }).click();
}
}
Тепер на сторінці з двома таблицями (активні і архівні платежі) створюються два екземпляри з різними коренями. Локатори в Playwright ланцюжкові, тому scoping від кореня працює природно і без складних CSS-селекторів.
На співбесіді: Повторювані блоки UI я виношу в Page Component, який приймає кореневий Locator. Так один компонент таблиці або модалки працює на будь-якій сторінці і навіть кілька разів на одній.
Композиція замість наслідування
Принцип "has-a замість is-a". Сторінка не наслідує хедер, а має хедер.
export class PaymentsPage {
readonly header: Header;
readonly active: DataTable;
readonly archived: DataTable;
constructor(private readonly page: Page) {
this.header = new Header(page.getByRole('banner'));
this.active = new DataTable(page.getByTestId('active-payments'));
this.archived = new DataTable(page.getByTestId('archived-payments'));
}
async goto() {
await this.page.goto('/payments');
}
}
await paymentsPage.header.openProfileMenu();
await expect(paymentsPage.active.rowByText('INV-42')).toBeVisible();
Переваги перед наслідуванням:
- Сторінка бере тільки ті компоненти, які на ній реально є.
- Зміна компонента не тягне за собою всю ієрархію.
- Компоненти легко комбінувати (у модалці може бути та сама таблиця).
Коли НЕ варто: дрібний блок з одним елементом не потребує окремого класу, це зайва абстракція. Також не треба робити компонент "на виріст", якщо блок є тільки на одній сторінці і навряд чи повториться.
Типова помилка на співбесіді: сказати "є BasePage з хедером, футером і меню, всі сторінки від нього наслідуються". Інтерв'юер спитає, що робити зі сторінкою логіну без хедера, і ієрархія почне тріщати.
На співбесіді: Я віддаю перевагу композиції: сторінка має поля-компоненти, а не наслідує їх. Базовий клас, якщо є, дуже тонкий, наприклад тільки page і goto.