JS Sketchbooksee JavaScript think ✏️
← back to phase 11

11.9 — Page Object Model

Your suite is growing — and twenty spec files now contain the same getByLabel("Coupon"). One UI rename away from twenty edits. The Page Object Model: one class per screen — locators as properties, user verbs as methods — so page knowledge lives in exactly ONE place. 5.6’s classes finally meet their killer app.

watch it happen
// pages/checkout-page.ts
export class CheckoutPage {
  constructor(page) {
    this.page = page;
    this.coupon = page.getByLabel("Coupon");
    this.total = page.getByTestId("total");
  }
  async goto() {
    await this.page.goto("/checkout");
  }
  async applyCoupon(code) {
    await this.coupon.fill(code);
    await this.page
      .getByRole("button", { name: "Apply" }).click();
  }
}

// in the spec — reads as a user story:
const checkout = new CheckoutPage(page);
await checkout.goto();
await checkout.applyCoupon("SAVE10");
await expect(checkout.total).toHaveText("900");

The smell, concretely: twenty specs each build getByLabel("Coupon") and click the Apply button themselves. The knowledge “how the checkout screen works” is smeared across the whole suite — every spec knows the DOM personally.

the same locator, everywherespec 1.spec.tsgetByLabel("Coupon")spec 2.spec.tsgetByLabel("Coupon")spec 3.spec.tsgetByLabel("Coupon")spec 4.spec.tsgetByLabel("Coupon")…and 16 more files just like thesethe smell: twenty spec files, each containing getByLabel("Coupon") — the samelocator, copy-pasted twenty times
under the hood

The deeper story, with the real names for things — this part is what turns “I saw it” into “I can explain it.”

In TypeScript (8.5, and your scaffold’s choice) the class gets typed: constructor(private readonly page: Page), properties as readonly coupon: Locator. The types buy the usual: autocomplete on every page object across the suite and typo-death at the desk.

POMs pair beautifully with fixtures (11.7): define a checkoutPage fixture that builds new CheckoutPage(page), and specs just declare { checkoutPage } — injection all the way down. Most mature suites you’ll join wire POMs through fixtures exactly this way.

Methods that navigate somewhere often return the NEXT page object (login() { …; return new DashboardPage(this.page) }) — chaining screens the way users flow through them. Nice when natural; forced fluent-chaining everywhere is a style disease. Judgment, as always.

Job note: “explain the Page Object Model” is a top-three automation interview question. The answer that lands: locator duplication is a maintenance bug; one class per screen makes page knowledge single-sourced so UI change costs one edit; specs read as user stories; assertions stay in specs. Four sentences, and you’ve out-answered most candidates — because you can also say when NOT to use it.

your turn

⌨️ build a page object

The starter fakes a checkout screen. Wrap it in a page object — locate once, act with user verbs — and write a spec that reads as a user story.

requirements:

  • Keep the starter’s app — the fake screen with an apply(code) behavior and a total.
  • Write class CheckoutPage (5.6!): the constructor takes the app and stores it on this; an applyCoupon(code) method delegates to the app’s apply; a readTotal() method returns the app’s total.
  • The spec: new CheckoutPage(app), apply "SAVE10", print total: 900 (built from readTotal). Then a second instance on a fresh app — apply "BOGUS", print total: 1000.

when you press RUN, the console must show exactly:

total: 900
total: 1000

✏️ Quick check 1

The Coupon label is renamed. With a POM, how many FILES do you edit?

✏️ Quick check 2

Where do expect() assertions belong — in the page object’s methods, or in the spec?

✏️ Quick check 3

Storing twenty locators as properties in a constructor — expensive or cheap, and why? (one word for the reason)

teach it back

🗣️ Now teach it back

Explain the POM to a friend: the duplication problem, the class shape (what becomes a property, what becomes a method), what specs look like after, the one-edit-heals-all payoff (name the principle), and when POM is overkill.

Write it as if your friend is sitting next to you. Saved to your journal — future-you will use these notes to teach others.

a few sentences, minimum — you’ve got this
to remember
The target: locator duplication (copy-paste drift at suite scale). Cure: one CLASS per screen (5.6) — locators as constructor-built properties (cheap: descriptions, 11.4), user VERBS as methods.
Specs become user stories (zero selectors); a UI rename costs ONE edit that heals every importing spec (8.1’s graph) — the principle is DRY: knowledge in exactly one place.
Discipline: pages DO and LOCATE, specs ASSERT (failures belong to behaviors). POM earns at SCALE — skip the ceremony for three specs; compose component objects instead of god-pages.