Fixtures fixed repeated actions, like login. There is a second kind of repetition that hurts just as much.
These lines appear in eight different test files:
await page.getByTestId('contact-name-input').fill('Dana');
await page.getByTestId('contact-email-input').fill('dana@foxlabs.io');
await page.getByTestId('save-contact').click();
A developer renames contact-name-input. Now you search the whole project and fix eight files. Miss one, and a test fails tomorrow for a reason unrelated to the feature.
Put the locators in one place:
export class ContactForm {
constructor(private page: Page) {}
get name() { return this.page.getByTestId('contact-name-input'); }
get email() { return this.page.getByTestId('contact-email-input'); }
get save() { return this.page.getByTestId('save-contact'); }
async fill(contact: { name: string; email: string }) {
await this.name.fill(contact.name);
await this.email.fill(contact.email);
await this.save.click();
}
}
Each getter returns a locator. fill is a shortcut that uses them together. When that test id changes, you edit one line in one file.
Your test now reads almost like English:
const form = new ContactForm(page);
await form.fill({ name: 'Dana Fox', email: 'dana@foxlabs.io' });
await expect(page.getByTestId('contact-row').filter({ hasText: 'Dana Fox' })).toBeVisible();
This pattern is called the Page Object Model. You will be asked about it in interviews. The short answer: it keeps locators and page actions in one place, so a UI change is one edit instead of many.
Now the mistake almost everyone makes at first. Do not put your assertions inside the page object:
// Do not do this
async verifySaved() {
await expect(this.page.getByTestId('toast')).toContainText('Contact created');
}
It looks tidy, but now nobody can read a test and see what it proves without opening a second file. Keep it simple: the page object holds locators and actions. The test holds the assertions.
Remember this: if you cannot tell what a test proves without opening another file, you have gone too far.
Reference: Page object models.