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
- Reduces duplicated locators
- Improves test readability
- Makes maintenance easier
- Encourages reusable business actions
- Supports large automation frameworks
- Works naturally with Playwright fixtures
- Makes framework design easier to explain during interviews
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:
| Layer | Responsibility |
| Test | Business scenario and assertions |
| Page Object | Page interaction |
| Component | Reusable UI component |
| Fixture | Test setup and dependency injection |
| Utility | Generic helper functionality |
| API Client | Backend/API operations |
| Config | Environment and execution settings |
Playwright POM vs Traditional Test Scripts
| Traditional Test | Playwright POM |
| Locators inside tests | Locators inside Page Objects |
| Repeated actions | Reusable methods |
| Difficult maintenance | Centralized maintenance |
| Large test files | Smaller readable tests |
| Limited reuse | High reuse |
| Harder framework scaling | Better 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
β
β
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
+
β
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:
- Date generation
- File handling
- Random data
- Environment helpers
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:
- Installation
- Locators
- Assertions
- Test hooks
- Browser contexts
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:
- GitHub Actions
- Jenkins
- Azure DevOps
- Docker
Step 8 β Reporting and debugging
Learn:
- HTML reports
- Screenshots
- Videos
- Trace Viewer
- JUnit/JSON reporting
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.
