A clean implementation of the Page Object Model (POM) design pattern using Playwright — built for scalable, readable, and maintainable test automation.
This project is a reference implementation of the Page Object Model (POM) design pattern with Playwright. It demonstrates industry best practices for structuring automation test projects in a way that is easy to scale, read, and maintain across large QA teams.
The Page Object Model (POM) is a design pattern in test automation that:
- Creates an object repository for web UI elements
- Separates test logic from page interaction logic
- Makes tests easier to maintain — one change in UI = one change in code
- Improves code reusability across test files
- Makes tests more readable and self-documenting
PlayWright-POM/
│
├── pages/ # Page Object classes
│ ├── BasePage.js # Base class with shared methods
│ ├── LoginPage.js # Login page interactions
│ ├── DashboardPage.js # Dashboard page interactions
│ └── FormPage.js # Form page interactions
│
├── tests/ # Test specifications
│ ├── login.spec.js
│ ├── dashboard.spec.js
│ └── form.spec.js
│
├── utils/
│ ├── helpers.js # Reusable utility functions
│ └── constants.js # URLs and test data constants
│
├── playwright.config.js
├── package.json
└── README.md
- Framework: Playwright
- Language: JavaScript
- Pattern: Page Object Model (POM)
- Base Class: Shared BasePage for common actions
- Reporting: Playwright HTML Reporter
- Node.js (v16 or above)
- npm (v7 or above)
git clone https://github.com/usmanabbas-qa/PlayWright-POM.git
cd PlayWright-POMnpm install
npx playwright installnpx playwright testnpx playwright test --uinpx playwright show-report// pages/LoginPage.js — Page Object Class
export class LoginPage {
constructor(page) {
this.page = page;
this.emailInput = page.locator('#email');
this.passwordInput = page.locator('#password');
this.loginButton = page.locator('button[type="submit"]');
this.errorMessage = page.locator('.error-msg');
}
async goto() {
await this.page.goto('/login');
}
async login(email, password) {
await this.emailInput.fill(email);
await this.passwordInput.fill(password);
await this.loginButton.click();
}
}
// tests/login.spec.js — Clean Test using POM
import { test, expect } from '@playwright/test';
import { LoginPage } from '../pages/LoginPage';
test('Valid user can login successfully', async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login('user@test.com', 'pass123');
await expect(page).toHaveURL('/dashboard');
});- ✅ Zero duplication of locators across tests
- ✅ Single point of change for UI updates
- ✅ Human-readable test names and steps
- ✅ Reusable page methods across multiple test files
- ✅ Easy onboarding for new team members
⭐ If you found this project helpful, please give it a star!