Module 4 of 5

Page Object Model

Build a maintainable, scalable test suite that doesn't break every time the UI changes.

🏗️ Why POM Matters

Without POM, if a button's selector changes, you update it in 20 test files. With POM, you update it in one place.

❌ Without POM

// test1.spec.ts
await page.locator('#login-btn').click();

// test2.spec.ts
await page.locator('#login-btn').click();

// test3.spec.ts
await page.locator('#login-btn').click();

// When selector changes → update 3+ files

âś… With POM

// pages/login.page.ts
get loginBtn() {
  return this.page.locator('#login-btn');
}

// test1/2/3.spec.ts
await loginPage.loginBtn.click();

// Selector changes → update 1 file

🏛️ Building Your First Page Object

1. Create the Page Object Class

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

export class LoginPage {
  readonly page: Page;
  readonly emailInput: Locator;
  readonly passwordInput: Locator;
  readonly submitButton: Locator;
  readonly errorMessage: Locator;

  constructor(page: Page) {
    this.page = page;
    this.emailInput = page.getByLabel('Email');
    this.passwordInput = page.getByLabel('Password');
    this.submitButton = page.getByRole('button', { name: 'Sign In' });
    this.errorMessage = page.getByRole('alert');
  }

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

  async login(email: string, password: string) {
    await this.emailInput.fill(email);
    await this.passwordInput.fill(password);
    await this.submitButton.click();
  }
}

2. Use It in Your Tests

tests/login.spec.ts
import { test, expect } from '@playwright/test';
import { LoginPage } from '../pages/LoginPage';

test('successful login', async ({ page }) => {
  const loginPage = new LoginPage(page);
  await loginPage.navigate();
  await loginPage.login('user@test.com', 'password123');
  await expect(page).toHaveURL('/dashboard');
});

test('shows error for invalid credentials', async ({ page }) => {
  const loginPage = new LoginPage(page);
  await loginPage.navigate();
  await loginPage.login('bad@email.com', 'wrongpass');
  await expect(loginPage.errorMessage).toBeVisible();
});
📝 Module Quiz

Test Your Knowledge

Score 70%+ to advance to the next module

Question 1 of 4
0 answered

Q1. What is the primary benefit of the Page Object Model pattern?

Q2. In the Page Object Model, what does the constructor typically do?

Q3. Where should navigation logic (like calling page.goto()) be placed in POM?

Q4. What is PageFactory in the context of Playwright's POM?