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.