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.
