Playwright Page Object Model (POM) Best Practices with Examples

Playwright has rapidly become the go-to automation testing framework for modern web applications. When combined with a well-designed Page Object Model (POM), Playwright delivers fast, reliable, and highly maintainable test suites that scale effortlessly. In this guide, you will learn Playwright POM best practices, complete with real code examples and advanced features like parallel execution, cross-browser testing, retries, tagging, and HTML reports.

This article is written for SDETs, QA engineers, and test automation leads who want production-grade Playwright automation.

What Is Page Object Model (POM) in Playwright?

The Page Object Model is a design pattern where each application page (or component) is represented as a class. This class encapsulates:

  • Locators

  • Page actions

  • Page-specific assertions

Instead of scattering selectors across tests, POM centralizes UI knowledge, making tests easier to read and maintain.

Why POM + Playwright works so well:

  • Playwright locators are resilient and auto-wait by default

  • Native async handling avoids flaky timing issues

  • Built-in test runner supports advanced execution patterns

Clean separation between test logic and UI details improves maintainability and reduces duplication.

Recommended Playwright POM Folder Structure


playwright-project/
├── pages/
│   ├── login.page.ts
│   ├── dashboard.page.ts
│   └── base.page.ts
├── tests/
│   ├── login.spec.ts
│   └── dashboard.spec.ts
├── playwright.config.ts
└── package.json

This structure keeps pages reusable and tests clean. Consider adding a components/ folder for reusable UI elements like headers or modals as your project grows.

Creating a Base Page (Best Practice)

A base page helps avoid duplication across page objects.

typescript
import { Page } from '@playwright/test';

export class BasePage {
  protected page: Page;

  constructor(page: Page) {
    this.page = page;
  }

  async navigate(url: string) {
    await this.page.goto(url);
  }
}

Extend this with common utilities like wait methods, screenshot capture, or navigation helpers to maximize reuse.

Example: Login Page Object

typescript
import { Locator, Page, expect } from '@playwright/test';
import { BasePage } from './base.page';

export class LoginPage extends BasePage {
  readonly username: Locator;
  readonly password: Locator;
  readonly loginButton: Locator;

  constructor(page: Page) {
    super(page);
    this.username = page.getByLabel('Username');
    this.password = page.getByLabel('Password');
    this.loginButton = page.getByRole('button', { name: 'Login' });
  }

  async login(user: string, pass: string) {
    await this.username.fill(user);
    await this.password.fill(pass);
    await this.loginButton.click();
  }

  async assertLoginSuccess() {
    await expect(this.page).toHaveURL(/dashboard/);
  }
}

Notice how all login-related logic is encapsulated in one class, making it easy to update when the UI changes.

Writing a Test Using POM

typescript
import { test } from '@playwright/test';
import { LoginPage } from '../pages/login.page';

test('User can login successfully', async ({ page }) => {
  const loginPage = new LoginPage(page);

  await loginPage.navigate('https://example.com/login');
  await loginPage.login('admin', 'password123');
  await loginPage.assertLoginSuccess();
});

Tests remain short, readable, and business-focused. The test reads like a user story, hiding complex selector logic.

Parallel Execution in Playwright POM

Playwright runs tests in parallel by default using workers.

typescript
// playwright.config.ts
import { defineConfig } from '@playwright/test';

export default defineConfig({
  workers: 4
});

POM advantage:

  • Each test gets a fresh browser context

  • Page objects remain stateless and thread-safe

Stateless POM classes ensure parallel tests don't interfere with each other, enabling faster test execution.

Cross-Browser Testing with POM

Playwright supports Chromium, Firefox, and WebKit out of the box.

typescript
projects: [
  { name: 'Chromium', use: { browserName: 'chromium' } },
  { name: 'Firefox', use: { browserName: 'firefox' } },
  { name: 'WebKit', use: { browserName: 'webkit' } }
]

Your same POM classes work across all browsers without changes. Write once, run everywhere—Playwright handles browser-specific quirks internally.

Built-In Retries for Stable Test Suites

Retries help handle intermittent infra or network issues.

typescript
retries: process.env.CI ? 2 : 0

Playwright retries only failed tests, not entire suites. This reduces flakiness in CI/CD pipelines where network issues are more common.

Test Tagging with Playwright POM

Tagging helps organize large test suites.

typescript
test('@smoke @login User login test', async ({ page }) => {
  const loginPage = new LoginPage(page);
  await loginPage.login('admin', 'password');
});

Run tagged tests:

bash
npx playwright test --grep @smoke

Tagging enables selective test execution—run smoke tests before deployments or regression tests nightly.

HTML Reports in Playwright

Playwright generates rich HTML reports automatically.

typescript
reporter: [['html', { open: 'never' }]]

View reports:

bash
npx playwright show-report

Reports include:

  • Step logs

  • Screenshots

  • Videos

  • Trace viewer

These reports are invaluable for debugging failed tests, showing exactly what happened during test execution.

POM Best Practices Checklist

  • Use getByRole and getByTestId locators for accessibility and stability

  • Avoid assertions inside page objects (except page-level checks)

  • Keep page objects action-focused

  • Do not store test data inside POMs

  • Prefer composition over inheritance when pages grow

Following these practices ensures your POM stays maintainable as your application evolves.

When NOT to Use POM

  • Very small prototypes

  • One-off UI checks

  • Heavy API-only test suites

In such cases, simple test scripts may be sufficient. Don't over-engineer—use POM when you need long-term maintainability.


👉 Suggested Article: Playwright Automation Testing: Complete Guide (2026)

Conclusion

Playwright Page Object Model best practices enable teams to build scalable, readable, and stable automation frameworks. When combined with Playwright's parallel execution, cross-browser support, retries, tagging, and HTML reports, POM becomes a powerful foundation for enterprise-grade test automation.

If you are building long-term UI automation, Playwright POM is a future-proof choice. The combination of Playwright's modern architecture with POM's organizational structure creates a robust testing solution that scales with your application's complexity.

Popular posts from this blog

18 Demo Websites for Selenium Automation Practice in 2026

Top 7 Web Development Trends in the Market (2026)

Mastering Selenium Practice: Automating Web Tables with Demo Examples

Selenium Automation for E-commerce Websites: End-to-End Testing Scenarios

Selenium WebDriver Integration with OpenAI, Sikuli, Appium, Python & Linux

Top 10 Highly Paid Indian-Origin CEOs in the USA

What is Java Class and Object?

14+ Best Selenium Practice Exercises to Master Automation Testing (with Code & Challenges)

Behavior-Driven Development (BDD) with Python Behave: A Complete Tutorial

25+ Selenium WebDriver Commands: The Complete Cheat Sheet with Examples