As your Playwright suite grows, ad hoc locators and inline flows quickly become hard to maintain. Advanced page objects and screen models give you a structured way to model the UI so tests stay readable and resilient to changes.
Designing Page Objects and Screen Models
A page object represents a logical screen or feature in your app and exposes high-level actions and assertions. Screen models are a variant that focus on states and transitions rather than strict URL pages.
// models/profile-page.ts
import { Page, expect } from '@playwright/test';
export class ProfilePage {
constructor(private readonly page: Page) {}
async goto() {
await this.page.goto('https://demo.myshop.com/profile');
}
async updateDisplayName(name: string) {
await this.page.getByRole('textbox', { name: 'Display name' }).fill(name);
await this.page.getByRole('button', { name: 'Save changes' }).click();
}
async expectUpdateSuccess() {
await expect(this.page.getByText('Profile updated')).toBeVisible();
}
}
// profile-page.spec.ts
import { test } from '@playwright/test';
import { ProfilePage } from './models/profile-page';
test('user can update profile name', async ({ page }) => {
const profile = new ProfilePage(page);
await profile.goto();
await profile.updateDisplayName('New Name');
await profile.expectUpdateSuccess();
});
Screen models can go further by modelling states like βcart-emptyβ, βcart-has-itemsβ or βcheckout-completeβ and providing transitions between them, which aligns well with complex business flows.
Common Mistakes
Mistake 1 β Treating page objects as simple locator bags
This leaks implementation details.
β Wrong: Exposing every locator and having tests call low-level methods directly.
β
Correct: Expose meaningful actions like updateDisplayName or addItemToCart.
Mistake 2 β Over-abstracting before patterns stabilise
This adds unnecessary complexity.
β Wrong: Designing a deep hierarchy of base pages before you have real tests.
β Correct: Start simple, then refactor shared behaviour into base classes or mixins as duplication appears.