Page Object Model (POM)

Automationintermediate

// Definition

A design pattern that wraps page interactions in dedicated classes. Tests call methods like `loginPage.signIn(email, password)` instead of manipulating selectors directly. Improves maintainability when locators change.

// Why it matters

POM puts each page's locators and actions behind one class, so a UI change is fixed in one place instead of across 50 tests. QA cares because it's the difference between a suite that scales and one that becomes a maintenance tax — but over-abstracted POMs become their own problem.

// How to test

// The page object owns locators; the test reads as intent
class LoginPage {
  visit() { cy.visit('/login') }
  fill(user: string, pass: string) {
    cy.get('[data-cy=email]').type(user)
    cy.get('[data-cy=password]').type(pass)
  }
  submit() { cy.get('[data-cy=submit]').click() }
}
const login = new LoginPage()
login.visit(); login.fill('a@b.com', 'pw'); login.submit()
cy.url().should('include', '/dashboard')

// Code Example

CypressA login page object
TypeScript
class LoginPage {
  visit() { cy.visit('/login'); }
  fill(email: string, password: string) {
    cy.get('[data-testid=email]').type(email);
    cy.get('[data-testid=password]').type(password);
  }
  submit() { cy.get('[data-testid=submit]').click(); }
}

it('signs in with valid credentials', () => {
  const page = new LoginPage();
  page.visit();
  page.fill('user@example.com', 'secret');
  page.submit();
  cy.url().should('include', '/dashboard');
});

// Common mistakes

  • Putting assertions inside the page object (it should expose state, tests assert)
  • One giant god-object for the whole app instead of per-page classes
  • Encoding waits/sleeps in the POM instead of waiting on real signals

// Related terms

Learn more · Playwright with Python

Chapter 5 · Lesson 1: Page Object Model in Python