Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Comprehensive Playwright testing guide covering E2E, component, API, visual, accessibility, and security tests.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
core/page-object-model.md
1# Page Object Model (POM)23## Table of Contents451. [Overview](#overview)62. [Basic Structure](#basic-structure)73. [Component Objects](#component-objects)84. [Composition Patterns](#composition-patterns)95. [Factory Functions](#factory-functions)106. [Best Practices](#best-practices)1112## Overview1314Page Object Model encapsulates page structure and interactions, providing:1516- **Maintainability**: Change selectors in one place17- **Reusability**: Share page interactions across tests18- **Readability**: Tests express intent, not implementation1920## Basic Structure2122### Page Class2324```typescript25// pages/login.page.ts26import { Page, Locator, expect } from "@playwright/test";2728export class LoginPage {29readonly page: Page;30readonly emailInput: Locator;31readonly passwordInput: Locator;32readonly submitButton: Locator;33readonly errorMessage: Locator;3435constructor(page: Page) {36this.page = page;37this.emailInput = page.getByLabel("Email");38this.passwordInput = page.getByLabel("Password");39this.submitButton = page.getByRole("button", { name: "Sign in" });40this.errorMessage = page.getByRole("alert");41}4243async goto() {44await this.page.goto("/login");45}4647async login(email: string, password: string) {48await this.emailInput.fill(email);49await this.passwordInput.fill(password);50await this.submitButton.click();51}5253async expectError(message: string) {54await expect(this.errorMessage).toContainText(message);55}56}57```5859### Usage in Tests6061```typescript62// tests/login.spec.ts63import { test, expect } from "@playwright/test";64import { LoginPage } from "../pages/login.page";6566test.describe("Login", () => {67test("successful login redirects to dashboard", async ({ page }) => {68const loginPage = new LoginPage(page);69await loginPage.goto();70await loginPage.login("[email protected]", "password123");71await expect(page).toHaveURL("/dashboard");72});7374test("shows error for invalid credentials", async ({ page }) => {75const loginPage = new LoginPage(page);76await loginPage.goto();77await loginPage.login("[email protected]", "wrong");78await loginPage.expectError("Invalid credentials");79});80});81```8283## Component Objects8485For reusable UI components:8687```typescript88// components/navbar.component.ts89import { Page, Locator } from "@playwright/test";9091export class NavbarComponent {92readonly container: Locator;93readonly logo: Locator;94readonly searchInput: Locator;95readonly userMenu: Locator;9697constructor(page: Page) {98this.container = page.getByRole("navigation");99this.logo = this.container.getByRole("link", { name: "Home" });100this.searchInput = this.container.getByRole("searchbox");101this.userMenu = this.container.getByRole("button", { name: /user menu/i });102}103104async search(query: string) {105await this.searchInput.fill(query);106await this.searchInput.press("Enter");107}108109async openUserMenu() {110await this.userMenu.click();111}112}113```114115```typescript116// components/modal.component.ts117import { Locator, expect } from "@playwright/test";118119export class ModalComponent {120readonly container: Locator;121readonly title: Locator;122readonly closeButton: Locator;123readonly confirmButton: Locator;124125constructor(container: Locator) {126this.container = container;127this.title = container.getByRole("heading");128this.closeButton = container.getByRole("button", { name: "Close" });129this.confirmButton = container.getByRole("button", { name: "Confirm" });130}131132async expectTitle(title: string) {133await expect(this.title).toHaveText(title);134}135136async close() {137await this.closeButton.click();138}139140async confirm() {141await this.confirmButton.click();142}143}144```145146## Composition Patterns147148### Page with Components149150```typescript151// pages/dashboard.page.ts152import { Page, Locator } from "@playwright/test";153import { NavbarComponent } from "../components/navbar.component";154import { ModalComponent } from "../components/modal.component";155156export class DashboardPage {157readonly page: Page;158readonly navbar: NavbarComponent;159readonly newProjectButton: Locator;160161constructor(page: Page) {162this.page = page;163this.navbar = new NavbarComponent(page);164this.newProjectButton = page.getByRole("button", { name: "New Project" });165}166167async goto() {168await this.page.goto("/dashboard");169}170171async createProject() {172await this.newProjectButton.click();173return new ModalComponent(this.page.getByRole("dialog"));174}175}176```177178### Page Navigation179180```typescript181// pages/base.page.ts182import { Page } from "@playwright/test";183184export abstract class BasePage {185constructor(readonly page: Page) {}186187abstract goto(): Promise<void>;188189async getTitle(): Promise<string> {190return this.page.title();191}192}193```194195```typescript196// Return new page object on navigation197export class LoginPage extends BasePage {198async login(email: string, password: string): Promise<DashboardPage> {199await this.emailInput.fill(email);200await this.passwordInput.fill(password);201await this.submitButton.click();202return new DashboardPage(this.page);203}204}205206// Usage207const loginPage = new LoginPage(page);208await loginPage.goto();209const dashboardPage = await loginPage.login("[email protected]", "pass");210await dashboardPage.expectWelcomeMessage();211```212213## Factory Functions214215Alternative to classes for simpler pages:216217```typescript218// pages/login.page.ts219import { Page } from "@playwright/test";220221export function createLoginPage(page: Page) {222const emailInput = page.getByLabel("Email");223const passwordInput = page.getByLabel("Password");224const submitButton = page.getByRole("button", { name: "Sign in" });225226return {227goto: () => page.goto("/login"),228login: async (email: string, password: string) => {229await emailInput.fill(email);230await passwordInput.fill(password);231await submitButton.click();232},233emailInput,234passwordInput,235submitButton,236};237}238239// Usage240const loginPage = createLoginPage(page);241await loginPage.goto();242await loginPage.login("[email protected]", "password");243```244245## Best Practices246247### Do248249- **Keep locators in page objects** - Single source of truth250- **Return new page objects** when navigation occurs251- **Expose elements** for custom assertions in tests252- **Use descriptive method names** - `submitOrder()` not `clickButton()`253- **Keep methods focused** - One action per method254255### Don't256257- **Don't include assertions in page methods** (usually) - Keep in tests258- **Don't expose implementation details** - Hide complex interactions259- **Don't make page objects too large** - Split into components260- **Don't share state** between page object instances261262### Directory Structure263264```265tests/266├── pages/267│ ├── base.page.ts268│ ├── login.page.ts269│ ├── dashboard.page.ts270│ └── settings.page.ts271├── components/272│ ├── navbar.component.ts273│ ├── modal.component.ts274│ └── table.component.ts275├── fixtures/276│ └── pages.fixture.ts277└── specs/278├── login.spec.ts279└── dashboard.spec.ts280```281282### Using with Fixtures283284```typescript285// fixtures/pages.fixture.ts286import { test as base } from "@playwright/test";287import { LoginPage } from "../pages/login.page";288import { DashboardPage } from "../pages/dashboard.page";289290type Pages = {291loginPage: LoginPage;292dashboardPage: DashboardPage;293};294295export const test = base.extend<Pages>({296loginPage: async ({ page }, use) => {297await use(new LoginPage(page));298},299dashboardPage: async ({ page }, use) => {300await use(new DashboardPage(page));301},302});303304// Usage in tests305test("can login", async ({ loginPage }) => {306await loginPage.goto();307await loginPage.login("[email protected]", "password");308});309```310311## Related References312313- **Locator strategies**: See [locators.md](locators.md) for selecting elements314- **Fixtures**: See [fixtures-hooks.md](fixtures-hooks.md) for advanced fixture patterns315- **Test organization**: See [test-suite-structure.md](test-suite-structure.md) for structuring test suites316