FRAMEWORKSINTERMEDIATE

Page Object Model with Playwright: A Practical Guide

Structure Playwright tests using Page Objects — reduce duplication, isolate selectors, and make refactors painless.

iff Solution Academy July 3, 2026 14 min read Updated July 6, 2026
Playwright Page Object Model POM Architecture

Why POM?

Selectors and workflows change. POM keeps them in one place so tests describe user intent, not DOM structure.

A Basic Page Object

pages/LoginPage.ts
import { Page, Locator } from '@playwright/test';

export class LoginPage {
  readonly username: Locator;
  readonly password: Locator;
  readonly submit: Locator;

  constructor(private page: Page) {
    this.username = page.getByLabel('Username');
    this.password = page.getByLabel('Password');
    this.submit = page.getByRole('button', { name: 'Sign in' });
  }

  async goto() {
    await this.page.goto('/login');
  }

  async loginAs(user: string, pass: string) {
    await this.username.fill(user);
    await this.password.fill(pass);
    await this.submit.click();
  }
}

Using It in a Test

ts
test('admin can log in', async ({ page }) => {
  const login = new LoginPage(page);
  await login.goto();
  await login.loginAs('admin', 'secret');
  await expect(page).toHaveURL(/dashboard/);
});

Level Up with Fixtures

Auto-inject page objects with a custom test fixture — no more manual new LoginPage(page).

fixtures.ts
import { test as base } from '@playwright/test';
import { LoginPage } from './pages/LoginPage';

export const test = base.extend<{ login: LoginPage }>({
  login: async ({ page }, use) => use(new LoginPage(page)),
});

POM Rules of Thumb

  • No assertions inside page objects — keep them in tests.
  • One page object per URL / screen.
  • Expose behaviour (loginAs), not internals (usernameInput).
  • Return other page objects from actions that navigate.

Get Playwright tutorials in your inbox

Weekly tips, real-world examples, and framework patterns – no spam, unsubscribe anytime.

Recommended Next Articles