What is the Page Object Model in Playwright? A 2026 Guide

Learn how to implement the Page Object Model (POM) in Playwright to build clean, maintainable, and scalable test automation.

Last updated: 27 July 2026 21 min read

Key Takeaways

  • Page Object Model (POM) is a design pattern that separates test logic from UI details by representing each web or component as a dedicated class in a different file.
  • POM works with external data (users.json), flows through page classes (login.page.js) where locators are inside a constructor, and executes clean action methods.
  • Standard POM organizes locators into classes but still requires manual newPageClass () setup in every test. Playwright fixtures automate this with ready-to-use objects in tests.

Imagine you’re testing a web app for insurance, but some changes have to go on the customer form. Like, the submit button is moved to a different place.

This means finding and updating the locator across every test case that touches the form.

By declaring a page object model, testers can simply declare a global object to make changes without touching test logic.

With a Page Object Model, you update it in one place, and every test that references it picks up the change automatically.

What is a Page Object Model?

Popularly known as POM, the Page Object Model is a design pattern that creates a centralized repository for storing web elements. It helps reduce code duplication and improves the maintainability of test scripts.

In Page Object Model, each web page of an application is represented as a separate class. These classes contain only the elements specific to their respective pages, along with methods to interact with them. Testers use these page objects to perform actions on the application, keeping tests clean and organized.

What is Page Object Model in Playwright?

The Page Object Model (POM) is an architectural design pattern that separates your test logic from UI elements and browser interactions.

Instead of hard-locating locators and user actions directly inside your test scripts, you encapsulate them inside a global class file. These class files are created for each page or UI component in your web application.

When an element on your site changes (like a username or a button ID), you can update the locator in one single page class instead of updating it in multiple test files. You can simply import the page class across all files so that the changes reflect everywhere and the code doesn’t break.

In short, the POM turns raw browser automation into clean, reusable code that makes large Playwright suites easy to read, scale and maintain.

Example:

class LoginPage {

  constructor(page) {

    this.page = page;

    this.username = page.locator('#username');

    this.password = page.locator('#password');

    this.loginBtn = page.locator('#login');

  }




  async login(user, pass) {

    await this.username.fill(user);

    await this.password.fill(pass);

    await this.loginBtn.click();

  }

}

Using POM in Playwright leads to cleaner tests, faster updates, and more scalable automation, especially for large test suites.

Why Inline Test Scripts Fail at Scale (The “Before POM” State)

Testers often make the mistake of declaring the same UI selectors again and again across multiple test cases for multiple browsers.

For example, if a tester wants to validate a “user login” test case, he will multiple times write the same script for all the pages, which will end up creating code confusion, class duplication and runtime failure.

Below is a JavaScript test file containing page elements like page goto(), page.locator (password) and fill calls.

import { test, expect } from '@playwright/test';



test('test', async ({ page }) => => {


  await page.goto('loginURL');


  await page. getByLabel('Username'). click();

  await page. getByLabel('Username'). fill('jamescarter');


  await page.getByLabel('Password'). click();


  await page.getByLabel('Password'). fill('new124');


  await page.getByRole('button', { name: 'Login' }).click();

});

Output –

01 before pom inline script fails at scale output

In this JavaScript file, we can cross-check whether our login action on the mainframe browser works or not.

However, this file contains all the objects, actions, data and test scripts in one place. If even one of the locators changes, we would have to make the change across all test cases for multiple browser calls.

Here is what will happen if this test case will be executed across all the web pages:

  • Duplication across tests: Every new page that needs to be tested (payment page, checkout page, and dashboard page) will all be validated through this same test script. This creates persistent code duplication that will confuse the Playwright test runner.
  • Fragile maintenance:  If the login page changes, say the “username” becomes an “email””, then the changes have to be made across all the UI test cases. Frequently writing the same script across multiple pages can really tangle the test logic.
  • No Single Source of Truth: If testers declare new objects and Playwright test hooks across new components, UI testing would suffer and page scripts will get mixed up.
  • Poor readability and buried intent: Someone reading this test has to parse the implementation details (click, fill, and goto) instead of understanding the intent. If the intent is to correct login elements, that won’t be achieved since test logic is tampered with.
  • Execution impact: Every new scenario means rewriting test logic from scratch instead of reusing a global file. Touching the underlying end user logic again and again will lead to longer test execution cycles that will increase the scope of flaky tests.
await loginPage.goto();

await loginPage.login('jamescarter', 'new124');

Output –

02 execution impact reusable loginpage flow output

In short, inline code scales fine for a demo. But in the actual production workflow, it can lead to major test inconsistencies and code duplication if any changes are made to the source file.

The moment you have a handful of UI tests on the same page, repeating code can lead to major inconsistencies that are solved single-handedly by POM.

What are the key advantages of the Page Object Model in Playwright?

Here are the key advantages of using the Page Object Model in Playwright:

  • Easy code maintenance: By declaring a global class file and importing it across multiple test cases, you maintain a clean code repository which makes the test cycle lean and efficient.
  • Increased readability and reusability:  POM promotes code reuse by allowing test scripts to access just one page object class file. Any changes made to the overall web code can be made to one file, which increases the readability and scalability.
  • Reduced code duplication: Defining all actions and data under one single page object model eliminates bulkiness in test scripts. Testers validating single UI elements or testing for cross-browser compatibility just reference 1 single file that gives them the required data.
  • Better test management: Organising page constructors and member functions within one global test file simplifies automation testing cycles, as users can simply locate and import the file and validate code usability.
  • Enhanced debugging: By simply creating a global reference object, testers are saved from tedious debugging timeouts. Whenever a user encounters any bug, they know that there is only one POM they need to recheck and run instead of debugging 50 files.

Disadvantages of Page Object Model in Playwright

While the Page Object Model offers many benefits, it also comes with some drawbacks to consider:

  • Initial Setup Time: Initial design and building framework take some time.
  • Advanced Skillset: Good coding skills are required to set the POM framework
  • Higher Risk: Elements are stored in a shared file, so even a tiny mistake in the page object file can lead to breaking the whole test suite.
  • Increased Complexity: For simple applications or small test suites, the POM can introduce unnecessary complexity by requiring additional classes and methods.
  • Tight Coupling of Interdependencies: If page objects are not well-designed, they can become tightly coupled, making it difficult to modify one without affecting others.
  • Limited Flexibility: The rigid structured nature of POM can make it harder to adapt to new testing strategies or tools without significant rework.

Implementing Page Object Model in Playwright: 8-Step FrameWork

Here are the prerequisites and steps to effectively implement the Page Object Model in Playwright:

Pre-Requisites:

  1. Install Visual Studio Code: Download and Install Visual Studio Code(VSCode).
  2. Install NodeJS: Download and Install Node JS

Steps to get started with Page Object Model in Playwright

Follow these steps get started with the POM in Playwright:

Step 1: Create a fresh new directory (ex: PlaywrightDemo) in VSCode

Step 2: Open Directory in Visual Studio Code. From VS code. Click on File > Open Folder > Choose newly Created Folder (PlaywrightDemo)

Step 3: From the VS Code, Click on Terminal Menu > Click on New Terminal

Step 4: Enter the below command to start the Playwright installation

npm init playwright@latest

Note: The above command asks a set of questions. Please provide appropriate inputs. In this tutorial, you are using typescript language.

Once you run the above command, the below set of files and folders will be automatically created.

  • tests folder: This folder contains actual test scripts. By default, an example.spec.ts file will be created inside this folder.
  • .gitignore: This file helps if you are using git repository
  • package.json and package-lock.json: This file helps to track dependencies, create a shortcut for running tests, etc.
  • playwright.config.ts: This is the global configuration file for the Playwright, which you can configure with available options.

Set up/Add additional folders for Playwright page object model

  • pages folder: Since the POM pattern is being used, the pages folder contains all the relevant page objects.
  • utility folder: The common code/function, which can be used in different tests can be placed here. For example, generating a random number, getting a date and time, etc.

Step 5: Install Browsers

Install browsers using the command

npx playwright install

Once you complete the above steps, your Playwright Test Automation Project/ Framework should look like the below.

Page Object Model in Playwright

Here is a simple scenario.

Navigate to the Browserstack home page.

Click on Products Menu

Verify All Submenus are Present

Step 6: Create a page object file inside the pages folder and name it home.page.ts

To achieve the above flow, you need a URL, menu element, etc.

//home.page.ts
import { expect, Locator, Page } from '@playwright/test';
export class BrowserstackHomePage {
readonly url ="https://www.browserstack.com/";
readonly page: Page;
readonly browserstackLogo: Locator;
readonly productsMenu: Locator;
readonly productmenudropdown:Locator

constructor(page: Page) {
this.page = page;
this.browserstackLogo = page.locator('#logo');
this.productsMenu = page.locator('#product-menu-toggle');
this.productmenudropdown = page.locator('#product-menu-dropdown >div > ul >li >a >div[class="dropdown-link-heading"]');
}

async goto(){
await this.page.goto(this.url);
}
async clickOnProducts(){
await this.productsMenu.waitFor({state:"visible"});
await this.productsMenu.click();
}
}

Step 7: Create a test using the above page object file.

Create a test file inside the tests folder and name it home.test.ts

To create a test, you need to import the page object file. Like below.

import { BrowserstackHomePage } from '../pages/home.page';

Once you import, you need to write the script and verify the submenus.

// home.test.ts
import { test, expect } from '@playwright/test';
import { BrowserstackHomePage } from '../pages/home.page';
test('Browserstack homepage verification', async ({ page }) => {
const homepage = new BrowserstackHomePage(page);
await homepage.goto();
await homepage.clickOnProducts();
await expect(homepage.productmenudropdown).toContainText(["Live", "Automate", "Percy", "App Live", "App Automate"])
});

After the creation of the above test file, your project looks like below

Page Object Model in Playwright

Step 8: Execute your test.

Execute you are using the below command

npx playwright test

Talk to an Expert

By default Playwright test runs in headless mode, to run in headed mode use -– headed flag.

npx playwright test -–headed

Page Object Model in Playwright

Now that you have the tutorial in place, know that Playwright is supported by Browserstack which provides thousands of real devices where you can verify applications on real devices. A few advantages of Playwright are:

  • Easy Setup and Configuration
  • Multi-Browser Support
  • Multi-Language Support
  • Parallel Browser Testingomes in handy when multiple web pages have to be tested simultaneously.
  • Built-in Reporters:
  • Typescript Support out of the box
  • CI/CD Integration Support
  • Debugging Tools Support

Using Browserstack Integration with Playwright you can integrate our Playwright tests and make automation testing easier through robust design patterns. Not only that, speed up your Playwright tests by 30x with parallel testing to expand your test and browser coverage without compromising on build times.

Run Playwright Tests On BrowserStack

When to Use Page Object Model in Playwright

Not every test needs a complete page object model upfront. Using POM for web testing is ideal if you are dealing with complex native web apps.

If you are just providing a test demo, checking endpoint connectivity or video recording a test session, you don’t really need to initiate a POM file.

Here are the instances where using POM is a must:

  • Test suite is exceeding 5-6 specs: When you have multiple specs within your project, i.e., scripts for testing a login, navigating to the dashboard, calculating quotes or customer chat service, using a page object model will really hold merit to improve efficiency.
  • Frequent UI changes and redesigns: If your app is upgraded or upscaled very frequently with new features, POM is the only file where you have to make the changes. You update a single selector inside your page class, and every test in your suite passes.
  • Multiple testers collaborating on one repo: POM creates a clean page separation which you can easily integrate into your specs, which makes it also easy for collaborators to find information.
  • Complex multi-setup user journeys:  Applications with intricate flows, like multi-tenant dashboards, role-based access controls, or candidate enrolment or visa applications, are ideal for POM testing, as they have complex test execution workflows.

To wrap it all, if you are just running a simple product demo for internal stakeholders or testing a beta version of your application on your own, POM might not be the best call.

However, if you are writing redundant test scripts, dealing with complex web elements on a single page, or struggling with code duplication, POM is the answer to your worries.

If you copy or paste the same locator or interaction block twice across different .spec.js test files, it’s time to refactor it into a Page Object Model.

Playwright Page Object Model Folder Structure

A page object model can be created as an isolated file in the Playwright test runner.

Just like you create your spec.js file to run tests, you can create another isolated test project in your directory for your POM to call your function and define parameters.

A well-structured POM directory separates your page logic, test scenarios and test configurations. This prevents you from redeclaring the same variables again and again whenever you want to test the same page.

You can simply invoke the page object model and pass the logic as arguments, and you can validate the UI from any page to build test robustness.

Recommended Enterprise Directory Layout

Here is a clean, scalable folder layout for a modern Playwright POM framework:

Here is a breakdown of the key folders to configure POM:

  • pages/:  Contains class definitions for each major page. Each file encapsulates class-level locators (defined in the constructor) and user action methods like ‘Username’ or ‘Password’.
  • components/: Contains subviews or recurring UI controls, like navigation headers, modal dialogues or data tables, that appear across multiple pages. This keeps classes lean.
  • tests/: Contains your actual test specs (.specs.ts). These files should focus entirely on test scenario setup, calling page functions and making assertions (expect() or await()). This file is where you build and merge your test script.
  • fixtures/: Fixtures automatically instantiate page objects for you and then pass them straight into your test() blocks. This eliminates the “const loginPage = New LoginPage(Page)” declaration to link your POM with the test case.
  • test-data/: Keeps static user credentials, environment URLs and mock API data separated from your test code. This separates UI actions from actual test logic.
  • utils/: Includes reusable helpers like data test generators, configuration helpers, or common workflows that are not page-specific.

Creating Reusable Page Objects in Playwright

Reusable page objects are built around user behavior rather than UI structure. The goal is to expose meaningful actions that tests can reuse across different scenarios without duplicating logic or selectors.

Key principles for creating reusable page objects include:

  • Encapsulate interactions, not steps: Page objects should provide methods such as login(), searchProduct(), or submitForm() instead of exposing individual clicks or fills.
  • Parameterize actions: Methods should accept inputs so the same page object works for multiple test cases and data sets.
  • Hide locators inside the page object: Locators should remain private to prevent tests from relying on implementation details that may change.
  • Keep methods focused and composable: Small, single-purpose methods are easier to reuse and combine across different workflows.
  • Avoid test-specific logic: Page objects should represent how the page behaves, not how a specific test expects it to behave.

By following these practices, page objects remain flexible, readable, and resilient as the application and test coverage evolve.

How to Handle Page Locators and Assertions in the Playwright Page Object Model?

Both locators and assertions are key determinants of a clean and organised page object model. Locators are the corresponding IDs that identify and validate each UI element in the main output.

class LoginPage {

  constructor(page) {

    this.page = page;

    this.usernameInput = page.getByLabel('Username');

    this.passwordInput = page.getByLabel('Password');

    this.loginButton = page.getByRole('button', { name: 'Login' });

  }



  async login(username, password) {

    await this.usernameInput.fill(username);

    await this.passwordInput.fill(password);

    await this.loginButton.click();

  }

}

Output –

03 creating reusable page objects locators assertions output

The Assertion testing defines what the ideal outcome of every action is vs what the actual outcome is. Assertions are everything, from the class methods to the class object to the arguments passed within the method.

This just keeps the page reusable (it just performs actions) while the test decides what success looks like:

test('valid login', async ({ page }) => {

  const loginPage = new LoginPage(page);

  await loginPage.goto();

  await loginPage.login('jamescarter', 'New124');



  await expect(page.getByText('You logged into a secure area')). toBeVisible();

});

Output –

04 assertions define outcome page object reusable output

Below are the best practices to follow for UI locators and test assertions:

  • Prefer user-facing locators: Prefer user-centric locators (getByRole, getByLabel) over CSS/XPath to keep the code clean, readable and easy to understand.
  • Keep locators as class properties: Declare your locators within your Playwright classes. A good way to do it is to record with the Playwright inspector and then reuse that code in case you wish to build a POM, although carefully.
  • Keep assertions out of the page object: Page objects should expose actions or, optionally, state retrieval messages like getErrorMessage. Do not let anything apart from that be a part of your global POM file.
  • Use auto-waiting; don’t add manual waits: Playwright locators auto-wait, so avoid using page. waitforTimeout().
  • Use codegen to discover locators: Use npx playwright codegen to discover locators first. Although it’s great for first drafts, it generates brittle locators. Refine before committing.
  • Centralise repeated assertions into reusable methods: If a check (has the user logged in?) appears in many tests, it can be centralised for the main authentication and not in every other test case.

Keeping the locators and assertions defined across spec files or POM is the ideal way to streamline the test pipeline.

A well-structured POM recognises the locators via the object and executes page-wise tests smoothly.

While POM recognises the locators and action methods into clean, reusable classes, it doesn’t solve the problem of faster text execution much. That’s where Playwright Fixture comes in.

With Playwright fixtures, you can directly use the built-in Dependency Injection (DI) system.

Instead of creating a page object model, building setup and teardowns or getting confused between browser configurations, use fixtures like page or browser route tests on demand.

Page Object Model vs Fixtures vs Screenplay Pattern in Playwright

Choosing the right architectural pattern in Playwright depends on your team’s size, application complexity, and maintainability requirements.

AspectPage Object Model (POM)POM + Playwright FixturesScreenplay Pattern
Core IdeaGroups UI locators & actions into page classes.Combines page classes with Playwright’s native dependency injection.Models users as actors performing tasks.
Primary AbstractionPages & ScreensPages + Injected Test ContextsActors, Tasks, Interactions & Questions
Setup OverheadManually create a new PageClass in every testZero internal tests. Auto-injected by Playwright.High upfront structure (Actors & Abilities).
Test ReadabilityCall methods on a pageCall methods on auto-injected pagesActor attempts tasks and asks questions.
Playwright FitNative, basic OOP alignmentNative & Recommended (test.extend)Requires 3rd-party wrappers (Serenity/JS).
Best Suited ForSmall suites (1–5 test files).Scalable enterprise frameworks.Complex apps with multi-role user flows
OnboardingEasy for any QA/Dev.Easy (requires basic JS destructuring).Steep (requires advanced design patterns).

What method you use in Playwright depends on the complexity of your web application testing lifecycle.

If you have a small application with 4-5 pages, using Playwright POM is the best way to authenticate your code.

For larger, more complex enterprise apps, you can use fixtures. Fixtures eliminates setup and boilerplate issues and auto-sets browser configuration to preserve memory.

Conclusion

The POM or Page Object Model is a powerful design pattern that significantly improves the maintainability, readability, and scalability of Playwright test automation. By organizing page elements and actions into dedicated classes, it reduces code duplication and simplifies updates when application interfaces change.

Integrating Playwright tests with BrowserStack Automate further enhances automation efficiency by combining robust design patterns with a seamless cloud infrastructure. BrowserStack offers access to thousands of real devices and browsers, ensuring comprehensive test coverage across multiple platforms.

Features like parallel testing accelerate execution without compromising speed, while built-in debugging tools and detailed test reports facilitate faster issue identification and resolution. Support for CI/CD pipelines enables continuous testing and quicker delivery cycles, making Playwright automation more scalable and reliable.

Adopting this approach empowers teams to build resilient test suites that scale effortlessly with evolving web applications.

Version History

  1. Jul 27, 2026 Current Version

    Updates fixtures vs POM, updated intro, added 2-3 new sections on inline problems, advantages and when to use, edited and added contextual links and CTAs.

    Yashraj Shrivastava
    Reviewed by Yashraj Shrivastava Product Manager
Tags
Automation Frameworks Automation Testing
Rushabh Shroff
Rushabh Shroff

Lead - Software Development Engineer

Rushabh Shroff is a Test Automation and Quality Engineering leader with 5+ years of experience helping teams build scalable, reliable testing strategies. He specializes in test automation, AI-assisted QA, and enterprise software quality, enabling faster and more confident releases.

Are Messy Page Objects Hiding Real Issues?
Run POM-based Playwright tests on real browsers at scale with BrowserStack.