Page Object Model#
The Page Object Model (POM) is a design pattern that creates a reusable abstraction of the UI pages. It helps you:
Reduce code duplication
Improve test maintenance
Increase readability of tests
Why Use Page Objects?#
Without page objects, your tests contain both the test logic and page interactions:
// Without Page Objects - everything mixed together
test("user can login", async ({ page }) => {
await page.goto("https://example.com/login");
await page.getByLabel("Username").fill("user1");
await page.getByLabel("Password").fill("password123");
await page.getByRole("button", { name: "Sign in" }).click();
await expect(page.getByText("Welcome back")).toBeVisible();
});
With page objects, you separate these concerns:
// With Page Objects - clean separation of concerns
test("user can login", async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.navigate();
await loginPage.login("user1", "password123");
await loginPage.expectSuccessfulLogin();
});
Creating a Page Object Class#
Here’s how to create a basic page object:
// tests/e2e/pages/login-page.ts
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: "Sign in" });
}
async navigate() {
await this.page.goto("https://example.com/login");
}
async login(username: string, password: string) {
await this.usernameInput.fill(username);
await this.passwordInput.fill(password);
await this.loginButton.click();
}
}
Using Page Objects in Tests#
Now we can use this page object in our tests:
// tests/e2e/login.spec.ts
import { test, expect } from "@playwright/test";
import { LoginPage } from "./pages/login-page";
test("successful login", async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.navigate();
await loginPage.login("testuser", "password123");
// Assert successful login
await expect(page.getByText("Welcome back")).toBeVisible();
});
test("failed login", async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.navigate();
await loginPage.login("testuser", "wrong-password");
// Add assertion for failed login
await expect(page.getByText("Invalid credentials")).toBeVisible();
});
Benefits of Page Object Model#
Reusability - Common UI interactions in one place
Maintainability - Changes to UI only require updates in one place
Readability - Tests focus on business logic, not implementation details
Important
Keep page objects focused on UI interaction, not assertions or business logic.