Playwright POM Tutorial: Complete Page Object Model Guide with TypeScript

Introduction: Why Playwright POM Matters in 2026

If you are building a small Playwright test, putting locators and actions directly inside the test may seem simple. But as the application grows, duplicated locators, repeated login steps, and large test files quickly become difficult to maintain.

This is where the Playwright Page Object Model (POM) becomes useful.

The Page Object Model separates page interaction logic from test logic. Instead of writing the same locator and click operation in multiple tests, you create a reusable page class containing locators and business actions.

Playwright officially documents Page Object Model as a way to structure larger test suites, while its built-in fixtures provide isolated test setup and can be extended to provide Page Objects directly to tests.

This playwright pom tutorial explains how to create a maintainable Playwright POM framework using TypeScript, including Page Objects, fixtures, authentication, parallel execution, reporting, debugging, and CI/CD.


What Is Playwright Page Object Model?

Playwright Page Object Model is a design pattern in which each important application page or UI component is represented by a TypeScript class.

A typical architecture looks like this:

Test

  β†“

Page Object

  β†“

Locators + Page Actions

  β†“

Application UI

For example:

LoginPage

 β”œβ”€β”€ usernameInput

 β”œβ”€β”€ passwordInput

 β”œβ”€β”€ loginButton

 β””── login()

ProductPage

 β”œβ”€β”€ searchBox

 β”œβ”€β”€ productCard

 β””── searchProduct()

CartPage

 β”œβ”€β”€ cartItems

 β”œβ”€β”€ removeButton

 β””── removeProduct()

The test describes what should happen, while the Page Object handles how the application is used.

Benefits of Playwright POM

Playwright locators are particularly suitable for Page Objects because they provide auto-waiting and retry behavior. Playwright recommends user-facing locators such as getByRole(), getByLabel(), and getByTestId() instead of fragile CSS/XPath chains where possible.


Why Use Page Object Model With Playwright?

Consider two tests:

await page.getByLabel(‘Username’).fill(‘user’);

await page.getByLabel(‘Password’).fill(‘password’);

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

If this login sequence appears in 30 tests, maintenance becomes difficult.

With POM:

await loginPage.login(‘user’, ‘password’);

If the login button changes, you update the Page Object instead of modifying 30 test files.

This provides a clear separation:

LayerResponsibility
TestBusiness scenario and assertions
Page ObjectPage interaction
ComponentReusable UI component
FixtureTest setup and dependency injection
UtilityGeneric helper functionality
API ClientBackend/API operations
ConfigEnvironment and execution settings

Playwright POM vs Traditional Test Scripts

Traditional TestPlaywright POM
Locators inside testsLocators inside Page Objects
Repeated actionsReusable methods
Difficult maintenanceCentralized maintenance
Large test filesSmaller readable tests
Limited reuseHigh reuse
Harder framework scalingBetter enterprise structure

POM does not mean every line of code must be moved into a class. Good framework design uses abstraction only where it provides real value.


Playwright POM Project Setup

Create a Playwright TypeScript project:

npm init playwright@latest

Select TypeScript during setup.

Then run:

npx playwright test

Playwright’s current test runner supports TypeScript, assertions, tracing, parallel execution, and browser projects for Chromium, Firefox, and WebKit.

A useful configuration is:

import { defineConfig } from ‘@playwright/test’;

export default defineConfig({

  testDir: ‘./tests’,

  use: {

    baseURL: ‘https://example.com’,

    screenshot: ‘only-on-failure’,

    trace: ‘retain-on-failure’

  },

  reporter: [

    [‘html’, { open: ‘never’ }],

    [‘list’]

  ]

});


Recommended Playwright POM Project Structure

For a beginner project:

playwright-pom/

β”‚

β”œβ”€β”€ pages/

β”‚   β”œβ”€β”€ LoginPage.ts

β”‚   β”œβ”€β”€ HomePage.ts

β”‚   └── CartPage.ts

β”‚

β”œβ”€β”€ tests/

β”‚   β”œβ”€β”€ login.spec.ts

β”‚   └── cart.spec.ts

β”‚

β”œβ”€β”€ fixtures/

β”‚   └── test-fixtures.ts

β”‚

β”œβ”€β”€ utils/

β”‚   └── test-data.ts

β”‚

β”œβ”€β”€ playwright.config.ts

β”œβ”€β”€ package.json

└── tsconfig.json

An enterprise framework can later grow into:

pages/

components/

fixtures/

utils/

api/

data/

config/

reporters/

tests/


Creating Your First Playwright Page Class

Create:

pages/LoginPage.ts

Use the following complete example:

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

export class LoginPage {

  readonly page: Page;

  readonly usernameInput: Locator;

  readonly passwordInput: Locator;

  readonly loginButton: Locator;

  constructor(page: Page) {

    this.page = page;

    this.usernameInput = page.getByLabel(‘Username’);

    this.passwordInput = page.getByLabel(‘Password’);

    this.loginButton = page.getByRole(‘button’, {

      name: ‘Login’

    });

  }

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

    await this.usernameInput.fill(username);

    await this.passwordInput.fill(password);

    await this.loginButton.click();

  }

}

Understanding the constructor

The constructor receives the Playwright Page object:

constructor(page: Page)

This gives the Page Object access to the browser page.

Understanding locators

this.usernameInput = page.getByLabel(‘Username’);

The locator is defined once and reused.

Understanding reusable methods

async login(username: string, password: string)

This method represents a business action rather than individual UI operations.


Writing Tests Using Page Objects

Now create:

tests/login.spec.ts

import { test, expect } from ‘@playwright/test’;

import { LoginPage } from ‘../pages/LoginPage’;

test(‘user can log in successfully’, async ({ page }) => {

  const loginPage = new LoginPage(page);

  await page.goto(‘/login’);

  await loginPage.login(

    ‘testuser’,

    ‘password’

  );

  await expect(page).toHaveURL(/dashboard/);

});

Notice how readable the test becomes.

The test says:

Go to login, log in, and verify the dashboard.

The implementation details remain inside LoginPage.

This is one of the biggest benefits of a playwright pom tutorial example for beginners.


Playwright POM With TypeScript

TypeScript provides strong typing for Playwright POM frameworks.

For example:

import type { Page, Locator } from ‘@playwright/test’;

You can explicitly type:

readonly page: Page;

readonly usernameInput: Locator;

This helps IDEs provide autocomplete and catches many coding mistakes before execution.

A good Playwright POM with TypeScript should keep:

  • Page objects strongly typed
  • Method parameters meaningful
  • Reusable methods focused
  • Test data separate from page classes
  • Assertions primarily in tests

Playwright POM With Fixtures

For larger projects, repeatedly creating Page Objects can become repetitive.

Playwright fixtures are designed to establish the environment needed by a test, and custom fixtures can be created with test.extend(). Official Playwright examples specifically demonstrate combining fixtures with Page Object Model classes.

Create:

fixtures/test-fixtures.ts

import {

  test as base,

  expect

} from ‘@playwright/test’;

import { LoginPage } from ‘../pages/LoginPage’;

type Fixtures = {

  loginPage: LoginPage;

};

export const test = base.extend<Fixtures>({

  loginPage: async ({ page }, use) => {

    await use(new LoginPage(page));

  }

});

export { expect };

Now the test becomes:

import { test, expect } from ‘../fixtures/test-fixtures’;

test(‘valid login’, async ({ page, loginPage }) => {

  await page.goto(‘/login’);

  await loginPage.login(

    ‘testuser’,

    ‘password’

  );

  await expect(page).toHaveURL(/dashboard/);

});

Fixtures are especially useful when many tests need the same Page Objects or setup.


Handling Login and Authentication With Page Objects

For a small suite, a LoginPage class is enough.

For a large suite, repeatedly performing UI login can unnecessarily increase execution time.

Playwright supports reusable authenticated browser state through storageState. The official authentication guidance recommends storing authentication state separately and not committing it to source control because it can contain sensitive cookies and headers.

A typical architecture is:

Authentication Setup

        β†“

storageState

        β†“

Authenticated Test

        β†“

DashboardPage

Add authentication files to .gitignore:

playwright/.auth/

For tests that modify shared server-side data, authentication should also be isolated appropriately across parallel workers.


Real-World E-Commerce Playwright POM Project

A strong portfolio project can contain:

pages/

β”œβ”€β”€ LoginPage.ts

β”œβ”€β”€ HomePage.ts

β”œβ”€β”€ ProductSearchPage.ts

β”œβ”€β”€ ProductDetailsPage.ts

β”œβ”€β”€ CartPage.ts

└── CheckoutPage.ts

components/

β”œβ”€β”€ Header.ts

└── ProductCard.ts

fixtures/

└── test-fixtures.ts

tests/

β”œβ”€β”€ login.spec.ts

β”œβ”€β”€ search.spec.ts

β”œβ”€β”€ cart.spec.ts

└── checkout.spec.ts

LoginPage

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

  await this.usernameInput.fill(username);

  await this.passwordInput.fill(password);

  await this.loginButton.click();

}

Product Search

async searchProduct(product: string) {

  await this.searchBox.fill(product);

  await this.searchButton.click();

}

Cart

async addProductToCart() {

  await this.addToCartButton.click();

}

Checkout

async completeCheckout(

  name: string,

  address: string

) {

  await this.nameInput.fill(name);

  await this.addressInput.fill(address);

  await this.placeOrderButton.click();

}

Tests can then cover:

  • Valid login
  • Invalid login
  • Product search
  • Product filtering
  • Product details
  • Add to cart
  • Remove from cart
  • Quantity validation
  • Successful checkout
  • Invalid checkout data

This becomes an excellent QA Automation/SDET portfolio project because it demonstrates framework design rather than isolated test scripting.


Playwright POM for Forms, Search, Cart, and Checkout

A good rule is:

Put reusable page interactions inside Page Objects and keep scenario-specific business assertions inside tests.

For example:

await checkoutPage.enterAddress(‘123 Main Street’);

await checkoutPage.selectPaymentMethod(‘Card’);

await checkoutPage.placeOrder();

await expect(

  checkoutPage.successMessage

).toHaveText(‘Order placed successfully’);

The Page Object performs actions.

The test verifies behavior.


Playwright POM With API Testing

Playwright can also support API testing through its API request functionality.

A framework can separate:

UI Page Objects

        +

API Clients

        +

Test Data

        β†“

End-to-End Tests

For example, an API client can create test data before a UI test:

const response = await request.post(‘/api/products’, {

  data: {

    name: ‘Laptop’,

    price: 1000

  }

});

Then the UI Page Object verifies that the product appears.

This is particularly useful for SDET-level framework design because test data can be created through APIs instead of navigating through multiple UI screens.


Playwright POM With Parallel Execution

Playwright Test runs test files in parallel by default, with separate worker processes and isolated browser contexts. You can configure worker counts or enable fully parallel execution.

For example:

export default defineConfig({

  fullyParallel: true,

  workers: process.env.CI ? 2 : undefined

});

The important rule is:

Do not make Page Objects store shared mutable state between tests.

Avoid:

let currentUser;

let currentOrder;

at module level.

Instead, create independent test data and state.

Playwright recommends keeping tests independent because shared external state can cause conflicts when tests execute concurrently.


Playwright POM With Reporting and Debugging

A professional framework should collect useful failure evidence:

use: {

  screenshot: ‘only-on-failure’,

  video: ‘retain-on-failure’,

  trace: ‘retain-on-failure’

},

reporter: [

  [‘html’, { open: ‘never’ }],

  [‘list’]

]

Run:

npx playwright test

Open the report:

npx playwright show-report

For debugging:

npx playwright test –debug

You can also use UI mode:

npx playwright test –ui

Playwright’s current tooling supports headed execution, UI mode, HTML reports, and debugging workflows.


Playwright POM With CI/CD and GitHub Actions

A GitHub Actions pipeline can run the framework automatically:

name: Playwright Tests

on:

  push:

    branches: [main]

  pull_request:

jobs:

  test:

    runs-on: ubuntu-latest

    steps:

      – uses: actions/checkout@v6

      – uses: actions/setup-node@v6

        with:

          node-version: lts/*

      – run: npm ci

      – run: npx playwright install –with-deps

      – run: npx playwright test

      – name: Upload Playwright report

        if: ${{ !cancelled() }}

        uses: actions/upload-artifact@v5

        with:

          name: playwright-report

          path: playwright-report/

The report, screenshots, videos, and traces can then be retained as CI artifacts.


Advanced Playwright POM Architecture and Framework Design

An enterprise Playwright POM framework can be organized like this:

playwright-framework/

β”‚

β”œβ”€β”€ pages/

β”œβ”€β”€ components/

β”œβ”€β”€ fixtures/

β”œβ”€β”€ api/

β”œβ”€β”€ utils/

β”œβ”€β”€ data/

β”œβ”€β”€ config/

β”œβ”€β”€ reporters/

β”œβ”€β”€ tests/

β”œβ”€β”€ playwright.config.ts

└── .github/

    β””── workflows/

Pages

Represent complete application pages.

Components

Represent reusable UI sections such as:

  • Header
  • Navigation menu
  • Product card
  • Date picker
  • Modal

Fixtures

Create and inject dependencies.

Utilities

Contain generic functionality such as:

API Clients

Handle backend operations.

Test Data

Store reusable test input.

Reporters

Handle custom reporting requirements.

Configuration

Controls:

  • Browsers
  • Base URL
  • Retries
  • Workers
  • Timeouts
  • Reporting
  • Artifacts

Do not put everything into one enormous BasePage or Utils class. That creates abstraction without real reuse.


Common Playwright POM Errors and Solutions

Locator failure

Problem: Locator cannot find the element.

Solution: Prefer stable user-facing locators such as:

page.getByRole()

page.getByLabel()

page.getByTestId()

instead of fragile DOM chains.

Incorrect Page initialization

Make sure the Page Object receives the correct Playwright page:

new LoginPage(page);

Fixture errors

Check that custom fixtures are imported from your fixture file rather than directly from @playwright/test.

Authentication failures

Verify the storage-state path and make sure authentication files have not expired.

Parallel conflicts

Use isolated test data and avoid shared mutable state.

Oversized Page Objects

Split reusable sections into components rather than creating a 2,000-line page class.


Playwright POM Best Practices

Use this checklist when designing your framework:

  • Use meaningful Page Object names.
  • Keep locators close to the page/component they belong to.
  • Prefer role, label, and test-id locators.
  • Avoid long CSS/XPath selectors.
  • Create reusable business methods.
  • Keep assertions primarily in tests.
  • Use fixtures for dependency setup.
  • Keep test data separate.
  • Keep authentication state secure.
  • Make tests independent.
  • Design for parallel execution.
  • Capture screenshots/traces for failures.
  • Generate HTML reports.
  • Integrate reporting with CI/CD.
  • Avoid unnecessary abstraction.
  • Split large Page Objects into components.

Playwright’s locator model re-resolves elements when actions are performed, which helps when modern applications re-render their DOM.


Playwright POM Interview Questions With Answers

1. What is Playwright POM?

Playwright POM is a design pattern where application pages are represented as reusable classes containing locators and page-specific actions.

2. What are the benefits of Playwright Page Object Model?

The main benefits are maintainability, reuse, readability, reduced locator duplication, and easier framework scaling.

3. Should assertions be inside Page Objects?

Generally, keep business assertions in test files. Page Objects should primarily expose page interactions and meaningful state or locators.

4. What is the difference between a Page Object and a fixture?

A Page Object models application behavior. A fixture creates and provides test dependencies such as Page Objects, authentication, or test data.

5. How do you implement POM in TypeScript?

Create TypeScript classes containing a Page, Locator properties, a constructor, and reusable methods.

6. How would you design an enterprise Playwright framework?

A strong answer includes:

β€œI would use Playwright with TypeScript, Page Objects for pages, component objects for reusable UI, fixtures for dependency injection, API clients for test-data setup, authentication state for logged-in scenarios, HTML/JUnit reporting, and GitHub Actions or another CI/CD platform for execution.”

7. How do you make POM tests parallel-safe?

Keep tests independent, avoid shared mutable state, isolate test data, and use appropriate authentication and worker-level fixtures. Playwright’s workers run independently, so external shared state is the main area requiring careful design.


Playwright POM Learning Roadmap for Beginners

If you are new to the framework, follow this order:

Step 1 β€” Playwright fundamentals

Learn:

Step 2 β€” Playwright TypeScript

Learn:

  • Classes
  • Interfaces
  • Types
  • Async/await
  • Modules

Step 3 β€” Page Object Model

Build:

  • LoginPage
  • HomePage
  • ProductPage
  • CartPage

Step 4 β€” Fixtures

Learn custom fixtures and dependency injection.

Step 5 β€” Authentication

Learn storageState and authenticated test setup.

Step 6 β€” Advanced framework design

Learn:

  • Components
  • API testing
  • Test data
  • Utilities
  • Configuration
  • Parallel execution

Step 7 β€” CI/CD

Learn:

Step 8 β€” Reporting and debugging

Learn:

Related topics to learn next include Playwright Tutorial, Playwright Tutorial Step by Step, Playwright TypeScript Tutorial, Playwright Python Tutorial, Playwright Java Tutorial, Playwright Test Fixtures, Playwright Auto Waiting, Playwright Parallel Execution, Playwright Reporting, Playwright API Testing, Playwright CI/CD Pipeline, Playwright GitHub Actions, Playwright Docker Tutorial, Playwright Framework Design, and Playwright Interview Questions.


FAQs About Playwright POM

What is Playwright POM?

Playwright POM is a Page Object Model design pattern that organizes locators and reusable UI actions into classes.

Is Playwright POM suitable for beginners?

Yes. Beginners can start with a simple LoginPage class and gradually introduce fixtures, components, authentication, and CI/CD.

What are the benefits of Playwright Page Object Model?

The primary benefits are reusable page actions, centralized locators, better readability, easier maintenance, and improved framework scalability.

How do I get started with Playwright POM?

Install Playwright, create a pages directory, create your first Page Object class, and consume it from a .spec.ts test.

What is a Playwright POM framework?

A Playwright POM framework combines Page Objects with supporting architecture such as fixtures, test data, utilities, API clients, authentication, configuration, reporting, and CI/CD.

Can Playwright POM be used with TypeScript?

Yes. TypeScript is an excellent choice for Playwright POM because Page, Locator, fixture, and method types can be explicitly defined.

Is Page Object Model mandatory in Playwright?

No. Playwright does not require POM. It is a design choice that becomes increasingly useful as the automation suite grows.

Leave a Comment

Your email address will not be published. Required fields are marked *