📦 Page Object Model Done Right

Page Object Model Done Right: The Page Object Model is the encapsulation principle from Java applied to the test layer: a class hides its internal implementation and exposes only

The Page Object Model is the encapsulation principle from Java applied to the test layer: a class hides its internal implementation and exposes only an API (set of methods) to the outside — just like you use HashMap's put() and get() without knowing how it works internally. A LoginPage class hides which attribute the "#email" locator is based on and exactly where the "Sign In" button sits in the DOM; the test simply calls loginPage.login("user@test.com", "pass123"). Why isn't writing locators directly in test files good enough? When the front-end team rearranges the login form, if locators are scattered across test files you have to scan every one of them — in Java an IDE can auto-refactor a renamed private field, but string locators spread across Playwright test files are invisible to the IDE. When the LoginPage class is updated, all tests update automatically. The QA reality: the "login flow changed and 47 tests went red" crisis almost always happens in projects without POM; with POM, the same change means updating one locator in LoginPage.ts and watching all 47 tests turn green on their own.

Without POM: the same login locators and login steps get copy-pasted across 20 test files. When the login button changes from "Sign in" to "Log in", you have to fix 20 files one by one. With POM: the locator and login logic are written once in a LoginPage class; when the button text changes, you update that one class, and every test automatically works again.

This is the exact same design pattern as the "Page Object" classes you wrote in Selenium projects — remember the LoginPage class with @FindBy fields and a login() method? In Playwright, instead of @FindBy you define locators in the constructor with page.getByRole(...); there's no extra initialization step like PageFactory.initElements() — locators are "lazy" (the actual lookup happens only when used).

Creating Your First Page Object

Micro Lab: Playwright — Locator selection

Replace the TODO line with the critical line from the expected solution. This is not a real runtime; the goal is to reinforce writing the correct structure in a controlled way.

Step by Step: Playwright — Locator selection

Try semantic locators first: getByRole, getByLabel, getByPlaceholder

If not semantic, use getByText or getByTestId

If CSS/XPath needed, write page.locator("css") or page.locator("xpath=...")

Verify locator works with await expect(locator).toBeVisible()

Narrow with filter()

If multiple elements match, narrow with .first() / .nth() / .filter()

What is the Playwright locator selection and verification order?