Introduction: Why Page Object Model Is Important in Automation Testing
As automation projects grow, maintaining test scripts becomes increasingly difficult. Imagine writing the same locators and actions in dozens of test files. If the application’s UI changes, you must update every test individually, making the framework hard to maintain.
This is where the Playwright Page Object Model (POM) becomes essential.
The Playwright Page Object Model is one of the most widely used design patterns in automation testing. It helps organize your code by separating page interactions from test logic. Instead of writing locators and actions inside every test, you place them in dedicated page classes that can be reused across multiple test cases.
Whether you are:
- A QA Automation Engineer
- An SDET
- A Selenium engineer transitioning to Playwright
- A Software Testing Student
- A Developer
- Preparing for Playwright interviews
Learning the Playwright Page Object Model is a must for building scalable, maintainable, and enterprise-ready automation frameworks.
In this guide, you’ll learn:
- What the Playwright Page Object Model is
- Why it is important
- Enterprise project structure
- Creating page classes
- Using constructors and locators
- Writing reusable page methods
- TypeScript examples
- Best practices
- Interview questions
- FAQs
Let’s begin.
What Is the Playwright Page Object Model?
The Playwright Page Object Model (POM) is a design pattern where each web page is represented by a separate TypeScript class.
Instead of placing locators and actions inside test files, they are stored inside page classes.
Simple Definition
The Playwright Page Object Model is a framework design pattern that separates page interactions from test logic, improving code reuse, readability, and maintainability.
For example:
Without POM:
Login Test
↓
Find Username
↓
Find Password
↓
Click Login
Every test repeats these steps.
With POM:
Login Test
↓
LoginPage.login()
↓
Dashboard
The login logic is written once and reused everywhere.
Why Use the Playwright Page Object Model?
Without the Playwright Page Object Model:
- Duplicate locators
- Duplicate actions
- Difficult maintenance
- Large test files
- Poor scalability
With the Playwright Page Object Model:
- Reusable page classes
- Centralized locators
- Cleaner tests
- Easier maintenance
- Better framework design
Benefits of Using the Page Object Model
The Playwright Page Object Model offers several advantages.
1. Code Reuse
Write login functionality once and reuse it across hundreds of tests.
2. Easier Maintenance
If the Login button changes, update only the page class.
3. Better Readability
Tests become easier to understand because they focus on business scenarios instead of implementation details.
4. Scalability
Large enterprise projects often contain hundreds of pages. POM keeps them organized.
5. Separation of Concerns
- Page classes → UI interactions
- Test files → Business validation
Playwright POM Project Structure
A recommended enterprise project structure is:
playwright-framework/
│
├── pages/
│ ├── LoginPage.ts
│ ├── DashboardPage.ts
│ └── ProductsPage.ts
│
├── tests/
│ ├── login.spec.ts
│ ├── dashboard.spec.ts
│ └── orders.spec.ts
│
├── fixtures/
│ ├── auth.fixture.ts
│
├── utils/
│ ├── helpers.ts
│
├── data/
│ ├── users.json
│
├── playwright.config.ts
│
└── package.json
This structure keeps automation projects modular and maintainable.
Creating Your First Page Object
Create a new file:
pages/LoginPage.ts
Example:
import { Page } from ‘@playwright/test’;
export class LoginPage {
constructor(private page: Page) {}
async navigate() {
await this.page.goto(‘https://example.com/login’);
}
async login(username: string, password: string) {
await this.page.getByLabel(‘Username’).fill(username);
await this.page.getByLabel(‘Password’).fill(password);
await this.page.getByRole(‘button’, {
name: ‘Login’
}).click();
}
}
Constructor Explained
The constructor receives the Playwright Page object.
constructor(private page: Page) {}
This makes the page available throughout the class without passing it to every method.
Locators Explained
Instead of locating elements inside every test:
await page.getByLabel(‘Username’);
Place locators inside page methods.
Advantages:
- Easier updates
- Better readability
- Reusable actions
Creating Reusable Page Methods
Instead of:
await page.fill(‘#username’, ‘admin’);
await page.fill(‘#password’, ‘admin123’);
await page.click(‘#login’);
Create:
async login(username:string,password:string){
…
}
Now every test simply calls:
await loginPage.login(‘admin’,’admin123′);
Writing Tests Using Page Objects
Example:
import { test, expect } from ‘@playwright/test’;
import { LoginPage } from ‘../pages/LoginPage’;
test(‘Login Test’, async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.navigate();
await loginPage.login(‘admin’, ‘admin123’);
await expect(page).toHaveURL(/dashboard/);
});
Notice how the test is much shorter and easier to understand.
Real-World Playwright Page Object Model Example (TypeScript)
LoginPage.ts
import { Page } from ‘@playwright/test’;
export class LoginPage {
constructor(private page: Page) {}
async open() {
await this.page.goto(‘https://example.com/login’);
}
async login(user: string, pass: string) {
await this.page.getByLabel(‘Username’).fill(user);
await this.page.getByLabel(‘Password’).fill(pass);
await this.page.getByRole(‘button’, {
name: ‘Login’
}).click();
}
}
login.spec.ts
import { test, expect } from ‘@playwright/test’;
import { LoginPage } from ‘../pages/LoginPage’;
test(‘Successful Login’, async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.open();
await loginPage.login(‘admin’, ‘admin123’);
await expect(page).toHaveURL(/dashboard/);
await expect(
page.getByRole(‘heading’, { name: ‘Dashboard’ })
).toBeVisible();
});
This example demonstrates how to keep page interactions inside the page class while the test focuses only on validating the application’s behavior.
POM Workflow Diagram
│
▼
Page Object
│
▼
│
▼
│
▼
Response
│
▼
Assertions
The Page Object acts as a reusable layer between the test and the application.
Reusable Components in Playwright Page Object Model
As your automation framework grows, you’ll notice that many UI elements appear on multiple pages.
Examples include:
- Navigation menu
- Header
- Footer
- Search bar
- User profile menu
- Notification panel
Instead of duplicating their locators across multiple Page Objects, create reusable component classes.
For example:
pages/
LoginPage.ts
DashboardPage.ts
components/
HeaderComponent.ts
SidebarComponent.ts
FooterComponent.ts
Header Component Example
import { Page } from ‘@playwright/test’;
export class HeaderComponent {
constructor(private page: Page) {}
async search(product: string) {
await this.page.getByPlaceholder(‘Search’)
.fill(product);
await this.page.keyboard.press(‘Enter’);
}
async logout() {
await this.page.getByRole(‘button’, {
name: ‘Logout’
}).click();
}
}
Now every page can reuse this component.
Example:
const header = new HeaderComponent(page);
await header.search(“Laptop”);
This is a common enterprise framework design.
Enterprise Framework Example
A scalable Playwright framework generally follows this structure:
playwright-framework/
│
├── pages/
│ LoginPage.ts
│ DashboardPage.ts
│ ProductPage.ts
│ CheckoutPage.ts
│
├── components/
│ HeaderComponent.ts
│ SidebarComponent.ts
│ MenuComponent.ts
│
├── fixtures/
│ auth.fixture.ts
│ api.fixture.ts
│
├── tests/
│ login.spec.ts
│ order.spec.ts
│ payment.spec.ts
│
├── utils/
│ logger.ts
│ config.ts
│ helpers.ts
│
├── data/
│ users.json
│ products.json
│
├── reports/
│
├── playwright.config.ts
│
└── package.json
This framework separates:
- Test cases
- Page Objects
- Shared components
- Fixtures
- Test data
- Utility classes
making maintenance much easier.
How Playwright POM Improves Automation
| Without POM | With Playwright POM |
| Duplicate locators | Centralized locators |
| Duplicate login code | Reusable methods |
| Difficult maintenance | Easy updates |
| Long test files | Clean test files |
| Hard to scale | Enterprise-ready |
| Poor readability | Better readability |
Best Practices for Playwright Page Object Model
1. One Class Per Page
Each page should have its own class.
Example:
LoginPage
DashboardPage
OrdersPage
ProductsPage
Avoid putting multiple pages in one class.
2. Keep Assertions Inside Tests
Page Objects should perform actions.
Example:
await loginPage.login();
Tests should verify results.
Example:
await expect(page)
.toHaveURL(/dashboard/);
This keeps responsibilities separate.
3. Use Meaningful Method Names
Good:
login()
logout()
searchProduct()
placeOrder()
Avoid:
clickButton()
button1()
method2()
Descriptive methods improve readability.
4. Prefer Playwright Locators
Recommended:
getByRole()
getByLabel()
getByPlaceholder()
getByText()
Avoid long XPath expressions whenever possible.
5. Keep Page Objects Small
A LoginPage should only contain login-related functionality.
Don’t place:
- Product methods
- Payment methods
- Dashboard methods
inside LoginPage.
6. Combine Fixtures with POM
Enterprise projects usually combine:
Fixtures
↓
Page Objects
↓
Tests
This produces a clean and reusable framework.
7. Reuse Components
Menus, headers, and footers should become reusable component classes instead of being duplicated across pages.
Common Mistakes to Avoid
Mistake 1: Writing Locators Inside Tests
Avoid:
await page.locator(‘#username’)
Instead:
loginPage.login()
Mistake 2: Huge Page Classes
Don’t create a 1000-line Page Object.
Split functionality into:
- Components
- Multiple pages
- Helper classes
Mistake 3: Hardcoding Test Data
Instead of:
login(“admin”,”123″)
Read data from:
data/users.json
Mistake 4: Mixing Assertions with Page Logic
Bad:
loginPage.login();
expect(page).toHaveURL(…);
inside LoginPage.
Assertions belong in test files.
Mistake 5: Duplicate Components
If multiple pages contain the same navigation menu, create a shared component.
Selenium to Playwright POM Migration Tips
If you’re moving from Selenium, you’ll notice that the Page Object Model concept remains the same, but Playwright simplifies many tasks.
| Selenium POM | Playwright POM |
| WebDriver | Built-in Page object |
| PageFactory (optional) | Modern locator API |
| Explicit waits frequently required | Built-in Auto Waiting |
| Separate wait utilities | Automatic synchronization |
| More boilerplate | Cleaner code |
Migration tips:
- Replace WebDriver with Playwright’s Page.
- Use Playwright locators (getByRole, getByLabel) instead of complex XPath where possible.
- Remove unnecessary explicit waits that Auto Waiting already handles.
- Combine Page Objects with Playwright Fixtures for reusable setup.
Playwright Page Object Model Interview Questions
1. What is the Playwright Page Object Model?
Answer:
It is a design pattern that separates page interactions from test logic using reusable page classes.
2. Why should we use POM?
Answer:
- Code reuse
- Better maintenance
- Cleaner tests
- Improved scalability
3. What is stored inside a Page Object?
Answer:
- Locators
- Page methods
- Navigation methods
- Business actions
4. What should not be stored in a Page Object?
Answer:
Assertions and test validations should remain in test files.
5. What is the constructor used for?
Answer:
It receives the Playwright Page object so that all methods can interact with the browser.
6. How do you create reusable Page Objects?
Answer:
Create one class per page and expose reusable methods such as login(), searchProduct(), or logout().
7. What are reusable components?
Answer:
Shared UI elements such as headers, sidebars, menus, and footers that can be represented as separate classes.
8. Can POM be used with Fixtures?
Answer:
Yes. Fixtures prepare reusable resources, while Page Objects encapsulate page interactions.
9. Why is POM important in enterprise automation?
Answer:
It improves maintainability, supports team collaboration, and scales well for large applications.
10. How does Playwright improve POM compared to Selenium?
Answer:
Playwright provides Auto Waiting, modern locator APIs, and built-in test runner features that reduce boilerplate code.
Frequently Asked Questions (FAQs)
What is the Playwright Page Object Model?
The Playwright Page Object Model is a design pattern that organizes page interactions into reusable classes, separating them from test logic.
Is Playwright POM suitable for beginners?
Yes. Beginners can start with a single page class and gradually build reusable components as projects grow.
Can I use Playwright POM with TypeScript?
Yes. TypeScript is one of the most popular languages for implementing Playwright Page Object Model frameworks.
What are the advantages of using POM?
POM improves code reuse, readability, maintainability, scalability, and simplifies updates when the UI changes.
Should assertions be inside Page Objects?
Generally, no. Keep assertions in test files while Page Objects focus on page interactions.
Can POM be combined with Fixtures?
Yes. Combining Fixtures, Browser Contexts, and Page Objects is a common enterprise framework pattern.
