Refactoring Selectors into Page Objects

As your Playwright suite grows, repeating selectors across many tests makes maintenance harder. Refactoring selectors into page objects or helper modules gives you a single place to update when the UI changes.

Refactoring Selectors into Page Objects

A simple page object wraps locators and actions for a given page or component. Tests then call methods on the object instead of hard-coding selectors everywhere.

// pages/LoginPage.ts
import { Page, Locator } from '@playwright/test';

export class LoginPage {
  readonly page: Page;
  readonly emailInput: Locator;
  readonly passwordInput: Locator;
  readonly signInButton: Locator;

  constructor(page: Page) {
    this.page = page;
    this.emailInput = page.getByRole('textbox', { name: 'Email' });
    this.passwordInput = page.getByRole('textbox', { name: 'Password' });
    this.signInButton = page.getByRole('button', { name: 'Sign in' });
  }

  async goto() {
    await this.page.goto('https://demo.myshop.com/login');
  }

  async login(email: string, password: string) {
    await this.emailInput.fill(email);
    await this.passwordInput.fill(password);
    await this.signInButton.click();
  }
}
// login.spec.ts
import { test, expect } from '@playwright/test';
import { LoginPage } from './pages/LoginPage';

test('user can log in', async ({ page }) => {
  const loginPage = new LoginPage(page);
  await loginPage.goto();
  await loginPage.login('user@example.com', 'SuperSecret123');

  await expect(page.getByText('Welcome back')).toBeVisible();
});
Note: Page objects are an organisational pattern, not a Playwright requirement; use them when they reduce duplication, not as a rigid rule.
Tip: Start by extracting page objects only for high-traffic flows like login, checkout, or key dashboards, then expand as patterns stabilise.
Warning: Over-abstracting every tiny element into its own object can make tests harder to understand; keep the design pragmatic.

Selectors centralised in page objects make refactors simpler: when a label or role changes, you update the locator once, and all tests that use the object automatically benefit.

Common Mistakes

Mistake 1 β€” Creating page objects before understanding real test needs

This leads to over-engineering.

❌ Wrong: Designing a huge page object layer upfront without any tests.

βœ… Correct: Let patterns emerge from tests, then refactor shared selectors into objects.

Mistake 2 β€” Mixing assertions and navigation logic everywhere

This hurts readability.

❌ Wrong: Duplicating login steps and selectors across many specs.

βœ… Correct: Encapsulate repeated flows in methods and keep assertions focused in tests.

🧠 Test Yourself

Why refactor selectors into page objects or helpers?