EDUCBA Logo

EDUCBA

MENUMENU
  • Explore
    • EDUCBA Pro
    • PRO Bundles
    • Featured Skills
    • New & Trending
    • Fresh Entries
    • Finance
    • Data Science
    • Programming and Dev
    • Excel
    • Marketing
    • HR
    • PDP
    • VFX and Design
    • Project Management
    • Exam Prep
    • All Courses
  • Blog
  • Enterprise
  • Free Courses
  • Log in
  • Sign Up
Home Software Development Software Development Tutorials Software Development Basics Page Object Model in Playwright
 

Page Object Model in Playwright

Narayan Bista
Article byNarayan Bista
EDUCBA
Reviewed byRavi Rathore

Page Object Model in Playwright

Introduction

As web applications grow in complexity, maintaining end-to-end test suites can become a daunting task, especially when selectors are scattered throughout test files. The Page Object Model (POM) design pattern addresses this by encapsulating page-specific locators and interaction methods within dedicated classes. When paired with playwright, POM allows QA engineers to write clean, reusable, and maintainable tests. This article walks EDUCBA learners through a step-by-step process for implementing POM in Playwright. We will create page classes (e.g., LoginPage, DashboardPage, FormPage), build example tests that use them, and share best practices for keeping your test suite organized as it scales.

 

 

Why Use the Page Object Model in Playwright?

Without POM, test files often mix locator definitions and interaction logic directly in the test steps. This approach leads to:

Watch our Demo Courses and Videos

Valuation, Hadoop, Excel, Mobile Apps, Web Development & many more.

  • High Maintenance: Every time a selector changes, you must update it in multiple test files.
  • Poor Readability: Tests read like code riddled with CSS selectors or XPath rather than describing user flows.
  • Duplication: The same sequence of clicks or form fills appears in many places.

POM solves these issues by abstracting page behaviors into classes:

  • Reusability: Each page class exposes high-level methods (e.g., login(), submitForm()).
  • Maintainability: If a locator changes, update it in only one place.
  • Readability: Tests call methods like login(‘user’, ‘pass’) rather than page.locator(‘#username’).fill(‘user’).
  • Scalability: As the application grows, new page classes are added without cluttering the tests.

When you adopt POM in Playwright, you separate “what” the test does (the user journey) from “how” it finds elements, keeping your suite robust against UI changes. For deeper insights into Playwright’s locator strategies and API, see the playwright overview.

How to Set Up Page Object Model in Playwright?

Before we create page classes, let’s initialize a fresh Playwright project:

Step 1: Install Node.js

Ensure that you install Node.js (version 14 or higher) on your system. You can download it from nodejs.org.

Step 2: Initialize the Project

Open your terminal and run the following commands to set up a new Node.js project:

mkdir playwright-pom-demo

cd playwright-pom-demo

npm init -y

Step 3: Install Playwright

Install the Playwright testing framework and its dependencies:

npm install --save-dev @playwright/test

npx playwright install

Step 4: Create Folder Structure

Organize your project directories for maintainability:

playwright-pom-demo/

├── pages/

│   ├── LoginPage.ts

│   ├── DashboardPage.ts

│   └── FormPage.ts

├── tests/

│   ├── login.spec.ts

│   └── form.spec.ts

├── playwright.config.ts

└── package.json

Step 5: Configure Playwright

Create or edit playwright.config.ts with the following configuration:

import { defineConfig } from '@playwright/test'; 

export default defineConfig({

testDir: './tests',

use: {

headless: true,

viewport: { width: 1280, height: 720 },

ignoreHTTPSErrors: true,

video: 'retain-on-failure',

},

projects: [

{ name: 'chromium', use: { browserName: 'chromium' } },

{ name: 'firefox', use: { browserName: 'firefox' } },

{ name: 'webkit', use: { browserName: 'webkit' } },

],

});

With your project initialized and configured, you are now ready to start defining Page Object Model (POM) classes for your application pages.

Defining Page Classes

Page classes encapsulate locators and interaction methods for specific pages. This keeps tests clean and makes maintenance trivial when the UI changes.

1. LoginPage Class

Create pages/LoginPage.ts:

import { Page, Locator } from '@playwright/test'; 

export class LoginPage {

readonly page: Page;

readonly usernameInput: Locator;

readonly passwordInput: Locator;

readonly loginButton: Locator;

readonly errorMessage: Locator;

constructor(page: Page) {

this.page = page;

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

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

this.loginButton = page.locator('button[type="submit"]');

this.errorMessage = page.locator('.error-message');

}

async navigate() {

await this.page.goto('https://example.com/login');

}

async login(username: string, password: string) {

await this.usernameInput.fill(username);

await this.passwordInput.fill(password);

await this.loginButton.click();

}

async getErrorMessage(): Promise<string> {

return (await this.errorMessage.textContent())?.trim() || '';

}

}
  • Selectors are defined once in the constructor.
  • Methods like navigate() and login() encapsulate multi-step interactions.
  • If the login form’s HTML changes, you only update the locator in this class.

2. DashboardPage Class

Create pages/DashboardPage.ts:

import { Page, Locator } from '@playwright/test';

export class DashboardPage {

readonly page: Page;

readonly userMenu: Locator;

readonly logoutButton: Locator;

readonly welcomeBanner: Locator;

constructor(page: Page) {

this.page = page;

this.userMenu = page.locator('#user-menu');

this.logoutButton = page.locator('text=Logout');

this.welcomeBanner = page.locator('.welcome-banner');

}

async getBannerText(): Promise<string> {

return (await this.welcomeBanner.textContent())?.trim() || '';

}

async logout() {

await this.userMenu.click();

await this.logoutButton.click();

}

}
  • Exposes methods like getBannerText() and logout().
  • Keeps locator details out of test files, improving readability.

3. FormPage Class

Create pages/FormPage.ts:

import { Page, Locator } from '@playwright/test';

export class FormPage {

readonly page: Page;

readonly firstNameInput: Locator;

readonly lastNameInput: Locator;

readonly emailInput: Locator;

readonly submitButton: Locator;

readonly successMessage: Locator;

constructor(page: Page) {

this.page = page;

this.firstNameInput = page.locator('#first-name');

this.lastNameInput = page.locator('#last-name');

this.emailInput = page.locator('#email');

this.submitButton = page.locator('button:has-text("Submit")');

this.successMessage = page.locator('.success-message');

}

async navigate() {

await this.page.goto('https://example.com/form');

}

async fillForm(firstName: string, lastName: string, email: string) {

await this.firstNameInput.fill(firstName);

await this.lastNameInput.fill(lastName);

await this.emailInput.fill(email);

}

async submit() {

await this.submitButton.click();

}

async getSuccessMessage(): Promise<string> {

return (await this.successMessage.textContent())?.trim() || '';

}

}
  • Consolidates form-related selectors and methods.
  • Any DOM changes on the form page require editing only this class.

Writing Tests with Page Objects

With page classes defined, tests become concise, descriptive, and resilient. Example: tests/login.spec.ts:

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

import { LoginPage } from '../pages/LoginPage';

import { DashboardPage } from '../pages/DashboardPage';

test.describe('Authentication Flow', () => {

let loginPage: LoginPage;

let dashboardPage: DashboardPage;

test.beforeEach(async ({ page }) => {

loginPage = new LoginPage(page);

dashboardPage = new DashboardPage(page);

await loginPage.navigate();

});

test('Valid user can log in', async ({ page }) => {

await loginPage.login('validUser', 'validPassword');

const bannerText = await dashboardPage.getBannerText();

expect(bannerText).toContain('Welcome, validUser');

});

test('Invalid user sees error message', async ({ page }) => {

await loginPage.login('invalidUser', 'wrongPassword');

const error = await loginPage.getErrorMessage();

expect(error).toBe('Invalid credentials. Please try again.');

});

test.afterEach(async ({ page }) => {

const banner = await dashboardPage.getBannerText();

if (banner.includes('Welcome')) {

await dashboardPage.logout();

}

});

});
  • Tests instantiate page objects, making each step of the process readable.
  • Assertions call methods like getBannerText() instead of checking raw selectors.

Another example for form submission: tests/form.spec.ts:

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

import { FormPage } from '../pages/FormPage';

test.describe('Contact Form', () => {

let formPage: FormPage;

test.beforeEach(async ({ page }) => {

formPage = new FormPage(page);

await formPage.navigate();

});

test('User can submit contact form successfully', async ({ page }) => {

await formPage.fillForm('John', 'Doe', '[email protected]');

await formPage.submit();

const message = await formPage.getSuccessMessage();

expect(message).toBe('Thank you for contacting us, John!');

});

});
  • Clean structure: navigate, fill out the form, submit, and assert success.
  • Any change to the form UI requires updating only ts, not every test.

Best Practices for Page Object Model in Playwright

  1. Follow the Single Responsibility Principle: Each page class should represent one page or a cohesive component. Avoid mixing unrelated actions in the same class.
  2. Encapsulate Common Flows: If many tests require logging in as a valid user, add a method like loginAsValidUser() to LoginPage or create a helper utility so tests need not repeat credentials.
  3. Prefer Stable Locators: Use data-test-id, aria-label, or unique attributes rather than brittle CSS classes. The playwright overview demonstrates best practices for choosing locators.
  4. Lazy Initialization for Dynamic Elements: If a locator only appears after a specific action, initialize it inside a method rather than in the constructor. This prevents unnecessary waits or flaky errors.
  5. Use Fixtures for Shared Setup/Teardown: Leverage Playwright’s fixture system to instantiate page objects and manage browser contexts, reducing boilerplate in tests.
  6. Keep Tests Readable: Tests should describe high-level user behaviors. Avoid embedding multiple locator calls—let page methods like login() and submit() encapsulate the details.
  7. Separate Data from Code: Store test inputs (usernames, form data) in JSON or environment variables. This enables data-driven testing and simplifies maintenance when values need to be updated.
  8. Refactor Regularly: As your application evolves, revisit page classes to remove unused selectors, merge similar methods, and ensure that class responsibilities remain clear and concise.
  9. Modularize Component Classes: For reusable UI components (e.g., a navigation bar or footer), consider separate “component” classes that can be imported into multiple page classes for consistency.
  10. Use TypeScript for Strong Typing: Define method signatures and return types explicitly. This catches errors at compile time and helps IDEs provide better autocomplete.

Adhering to these best practices keeps your POM implementation strong and maintainable as your application and test suite expand.

Final Thoughts

The Page Object Model transforms chaotic and brittle test suites into maintainable and scalable assets. By encapsulating locators and interactions within dedicated page classes—such as LoginPage, DashboardPage, and FormPage—you isolate UI details from test logic, thereby improving test readability and maintainability. Tests themselves become clear, high-level scripts that describe user journeys without worrying about CSS selectors or DOM hierarchies. Whenever the application’s UI changes, you update only the relevant page class rather than hunting through dozens of test files. For an in-depth look at locator strategies, advanced selectors, and the full Playwright API reference, consult the Playwright overview. Armed with POM and Playwright’s powerful features, you can confidently scale your test suite, deliver reliable end-to-end coverage, and reduce maintenance overhead—freeing your team to focus on test scenarios rather than test plumbing.

FAQ

Q1: Can I use Page Object Model for API testing in Playwright?

While POM is primarily a UI pattern, you can apply a similar abstraction for API endpoints. Create service classes that encapsulate HTTP request methods (e.g., UserService.createUser()), keeping API tests organized.

Q2: How do I handle asynchronous loading or network delays in page classes?

Use Playwright’s built-in waiting mechanisms: locator.waitFor(), page.waitForLoadState(), or locator.click({ timeout: 5000 }). Encapsulate these waits within page methods so tests don’t need explicit waits.

Q3: What if a page has multiple states or modes?

If a page varies based on roles or data conditions, parameterize your page class constructor or add methods to switch modes (e.g., DashboardPage.asAdmin() vs. DashboardPage.asUser()), each configuring appropriate locators.

Q4: Should I version-control my page classes?

Absolutely. Keep page classes, test data templates, and test configurations under version control to ensure consistency and maintainability. This allows you to track changes to locators and interactions as the application evolves.

Recommended Articles

We hope this guide on the Page Object Model in Playwright helped you organize tests efficiently. For more testing tips, explore these related articles:

  1. Software Testing Books
  2. Maven POM File
  3. Maven Versions
  4. JMeter API Testing

Primary Sidebar

Footer

Follow us!
  • EDUCBA FacebookEDUCBA TwitterEDUCBA LinkedINEDUCBA Instagram
  • EDUCBA YoutubeEDUCBA CourseraEDUCBA Udemy
APPS
EDUCBA Android AppEDUCBA iOS App
Blog
  • Blog
  • Free Tutorials
  • About us
  • Contact us
  • Log in
Courses
  • Enterprise Solutions
  • Free Courses
  • Explore Programs
  • All Courses
  • All in One Bundles
  • Sign up
Email
  • [email protected]

ISO 10004:2018 & ISO 9001:2015 Certified

© 2025 - EDUCBA. ALL RIGHTS RESERVED. THE CERTIFICATION NAMES ARE THE TRADEMARKS OF THEIR RESPECTIVE OWNERS.

EDUCBA

*Please provide your correct email id. Login details for this Free course will be emailed to you
Loading . . .
Quiz
Question:

Answer:

Quiz Result
Total QuestionsCorrect AnswersWrong AnswersPercentage

Explore 1000+ varieties of Mock tests View more

EDUCBA

*Please provide your correct email id. Login details for this Free course will be emailed to you
EDUCBA
Free Software Development Course

Web development, programming languages, Software testing & others

By continuing above step, you agree to our Terms of Use and Privacy Policy.
*Please provide your correct email id. Login details for this Free course will be emailed to you
EDUCBA

*Please provide your correct email id. Login details for this Free course will be emailed to you

EDUCBA Login

Forgot Password?

🚀 Limited Time Offer! - 🎁 ENROLL NOW