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#

  1. Reusability - Common UI interactions in one place

  2. Maintainability - Changes to UI only require updates in one place

  3. Readability - Tests focus on business logic, not implementation details

Important

Keep page objects focused on UI interaction, not assertions or business logic.