QA Interview Hub · Knowledge base · Quizzes
Patterns
Page Component and composition
Page Component: what it is and why
A real UI consists of repeating blocks: a header, navigation, a table with pagination, a date picker, a modal window. They appear on many pages. If you describe them in every Page Object you get duplication, and if in BasePage, a god object.
A Page Component is a class that describes one UI block and receives not page but the block's root locator. All inner locators are built from the root, so a component can be used several times on one 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();
}
}
Now on a page with two tables (active and archived payments) two instances are created with different roots. Locators in Playwright are chainable, so scoping from the root works naturally and without complex CSS selectors.
In an interview: I move repeating UI blocks into a Page Component that takes a root Locator. That way one table or modal component works on any page and even several times on one.
Composition instead of inheritance
The principle "has-a instead of is-a". A page doesn't inherit a header, it has a header.
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();
Advantages over inheritance:
- A page takes only the components that are really on it.
- Changing a component doesn't drag the whole hierarchy with it.
- Components are easy to combine (a modal may contain the same table).
When NOT to: a small block with one element doesn't need a separate class, that is unnecessary abstraction. Also don't make a component "for growth" if the block is on only one page and is unlikely to repeat.
A typical interview mistake: saying "There is a BasePage with a header, footer and menu, and all pages inherit from it". The interviewer will ask what to do with a login page that has no header, and the hierarchy starts to crack.
In an interview: I prefer composition: a page has component fields rather than inheriting them. A base class, if there is one, is very thin, for example only page and goto.