Page Object Model
By Syncfusion®, Inc. · Last updated
Why repeated locators make tests hard to change, what a page object is, writing a page class with locators and actions, using it in tests, pages that lead to other pages, component objects for repeated parts, page objects as fixtures, where assertions belong, folder structure, and common anti-patterns.
Page Object Model
- As a test suite grows, the same locators and steps appear in many files.
- When the page changes, you must fix every copy. Miss one, and a test breaks.
- The Page Object Model (POM) puts each page's locators and actions in one class. Tests use the class.
Simple lesson: a page object is one class per page. It knows how to find things and do things on that page.
A Real-World Example
- Two tests both log in through the form. The login steps are copied.
- Now the team renames the Log in button to Sign in. Both tests break, and both need the same fix.
- With 50 tests, that would be 50 copies. A page object keeps it to one.
What You Will Learn
- The problem POM solves, and how it works.
- Writing a page class with locators and actions.
- Using page objects in tests.
- Pages that lead to other pages.
- Component objects for repeated parts.
- Page objects as fixtures.
- Where assertions go, and common anti-patterns.
What a Page Object Is
One Class per Page
- A page object is a class. It gets the
pagein its constructor. - It has locators as properties:
emailInput,loginButton. - It has actions as methods:
goto(),login(email, password). - Tests call the methods. They do not need to know the locators.
Animation: three test files all call loginPage.login(). The button text changes from Log in to Sign in, and only one line inside the LoginPage class is updated. All three tests stay green
login.spec.ts orders.spec.ts account.spec.ts
class LoginPage
emailInput = getByLabel('Email') button = getByRole('button', name: 'Log in' 'Sign in' login(email, password)
One line changed. Every test still passes.
Many tests share one page object. A page change means one fix in one place.
Your First Page Object
- Open the Explorer. The class lives in
pages/login-page.ts. The test imports it. readonlymeans the property is set once, in the constructor.
- Try it: change
'Log in'in the class to'Sign in'and run. Both tests fail in the same place, and one fix repairs both.
Locators are lazy. Creating them in the constructor does not search the page. They look for elements only when an action or assertion uses them.
Simple lesson: locators as properties, actions as methods, one class per page.
Building a Set of Page Objects
Pages That Lead to Other Pages
- An action that opens a new page can return that page's object.
productsPage.openCart()returns aCartPage. The test reads like the user's journey.
addToCart(name)takes the product name. One method works for every product.
Component Objects
- Some parts repeat on many pages: a header, a product card, a date picker.
- Model them as components. A component object takes a root locator and finds things inside it.
- Page objects can then use the component, so the header logic is written once.
Simple lesson: a component object wraps one repeated part, found from a root locator.
Page Objects as Fixtures
No More new in Every Test
- Writing
new LoginPage(page)in every test is repetition too. - A fixture can create the page object for you, as in Fixtures. The test just asks for
loginPage.
- The fixture also opens the login page, so each test starts in the right place.
Assertions and Good Design
Where Assertions Belong
- Most teams keep
expectin the tests. Then each test shows clearly what it checks. - A page object can still offer a small check helper for something many tests verify, like "the account page has loaded".
- Name such helpers
expect...so a reader knows they assert.
Folder Structure
- Keep page objects out of the
testsfolder, so Playwright does not look for tests in them. - A common layout:
Anti-Patterns to Avoid
- God objects: one huge class for the whole site. Split it by page and component.
- Hidden waits:
waitForTimeout()inside methods. Playwright already waits. Use web-first assertions in tests. - Every click as a method:
clickLoginButton(),clickEmail(). Methods should be user tasks, likelogin(). - Deep inheritance:
BasePagethenShopPagethenProductsPage. Prefer small components you combine. - Returning raw data for checks:
getTitleText()that returns a string loses auto-waiting. Expose the locator and usetoHaveText().
A method like async getCount() { return await this.count.textContent(); } reads the text once, with no retry. A test that checks it right after a click can be flaky. Expose this.count and use await expect(products.count).toHaveText('6 products').
POM is not required. A small suite with a few tests can stay simple. Add page objects when you see the same locators in three or more places.
Simple lesson: small classes, user-level methods, locators exposed, assertions mostly in tests.
Try It in Your Project
- These steps take the lesson from the lab to your own app.
- Write a page object for your login page and use it in every test that logs in.
- Move repeated locators from three specs into one page object.
- Provide your page objects through fixtures.
Simple lesson: use one idea from this lesson on your own app this week.
TL;DR
- A page object is a class with the page's locators and actions.
- Tests call methods like
login(). When the page changes, one class changes. - Methods that open another page can return that page's object.
- Component objects wrap repeated parts from a root locator.
- Fixtures can create page objects for each test.
- Keep
expectin tests, avoid sleeps and huge classes, and expose locators instead of strings.
Common mistakes
| Mistake | What happens | Fix |
|---|---|---|
Page objects inside the tests folder with .spec names | Playwright tries to run them as tests | Put them in pages/ and components/ |
Returning textContent() for checks | No auto-retry, flaky tests | Expose the locator, assert with toHaveText() |
waitForTimeout() inside page methods | Every test gets slower | Rely on auto-waiting and web-first assertions |
| One method per click | Classes get huge and tests stay noisy | Methods for user tasks, like login() |
| A single class for the whole site | Hard to read and change | One class per page, plus components |
Check Yourself
Exercises
What to Remember
- One class per page: locators as properties, user tasks as methods.
- When the page changes, fix the class, not every test.
- Return the next page object from navigation methods.
- Components wrap repeated parts. Fixtures hand out page objects.
- Keep
expectin tests, expose locators, and never hide sleeps.
Next: Visual, Snapshot, and Accessibility Testing