Claude
Skills
Sign in
Back

playwright-page-object-builder

Included with Lifetime
$97 forever

Create maintainable and reusable Page Object Models (POMs) for Playwright tests. Generates TypeScript classes that encapsulate page-specific locators and actions, following the Page Object Model design pattern with data-testid locators exclusively.

Design

What this skill does


## When to Use This Skill

Use this skill when you need to:

- Create a Page Object Model for a specific page or component
- Refactor tests to use the POM pattern
- Build reusable page classes for complex applications
- Encapsulate page-specific logic and locators
- Improve test maintainability and reduce duplication

Do NOT use this skill when:

- Writing simple one-off tests (use test-generator skill)
- Debugging existing tests (use test-debugger skill)
- Refactoring existing POMs (use test-maintainer skill)

## Prerequisites

Before using this skill:

1. Understanding of the page structure and elements
2. Knowledge of user interactions on the page
3. List of data-testid values for page elements (or ability to suggest them)
4. Playwright installed in the project
5. Basic understanding of TypeScript classes

## Instructions

### Step 1: Identify Page Information

Gather from the user:

- **Page name** or component name
- **Page URL** or route
- **Key elements** on the page (buttons, inputs, text, etc.)
- **Common actions** users perform on the page
- **data-testid values** for all elements (or help define them)

### Step 2: Plan Page Object Structure

Determine:

- **Class name** (e.g., `LoginPage`, `DashboardPage`, `CheckoutPage`)
- **Properties**: Locators for all page elements
- **Methods**: Actions users can perform (login, addToCart, etc.)
- **Getters**: Read-only properties for assertions
- **Navigation**: How to reach this page

### Step 3: Create Page Object Class

Generate a TypeScript class with:

**Structure:**

```typescript
import { Page, Locator } from "@playwright/test";

export class PageName {
  readonly page: Page;

  // Locators
  readonly elementName: Locator;

  constructor(page: Page) {
    this.page = page;
    this.elementName = page.locator('[data-testid="element-name"]');
  }

  // Navigation
  async goto() {
    await this.page.goto("/page-url");
  }

  // Actions
  async performAction() {
    await this.elementName.click();
  }

  // Getters for assertions
  getElement() {
    return this.elementName;
  }
}
```

**Key Requirements:**

1. All locators use data-testid (MANDATORY)
2. Locators are readonly properties
3. Constructor accepts Page object
4. Include goto() method for navigation
5. Action methods are async and return Promise<void>
6. Getter methods for elements that need assertions
7. Use TypeScript types
8. Add JSDoc comments for complex methods

### Step 4: Define Locators

For each element:

```typescript
readonly elementName: Locator;

constructor(page: Page) {
  this.page = page;
  this.elementName = page.locator('[data-testid="element-name"]');
}
```

**Naming Convention:**

- Use camelCase for properties
- Descriptive names (e.g., `submitButton`, `emailInput`, `errorMessage`)
- Suffix with element type when helpful (Button, Input, Message, Link)

### Step 5: Implement Action Methods

For each user action:

```typescript
/**
 * Descriptive action name
 * @param param - Parameter description if needed
 */
async actionName(param?: string): Promise<void> {
  // Wait for element if needed
  await this.element.waitFor({ state: 'visible' });

  // Perform action
  await this.element.click();
  // or
  await this.element.fill(param);
}
```

**Common Actions:**

- Form filling: `async fillForm(data: FormData)`
- Button clicks: `async clickButton()`
- Navigation: `async navigateTo(section: string)`
- Complex workflows: `async completeCheckout(details: CheckoutDetails)`

### Step 6: Add Getter Methods

For elements that tests will assert against:

```typescript
getElementName(): Locator {
  return this.elementName;
}

async getTextContent(): Promise<string> {
  return await this.element.textContent() || '';
}

async isElementVisible(): Promise<boolean> {
  return await this.element.isVisible();
}
```

### Step 7: Validate Page Object

Ensure the Page Object includes:

- [ ] All locators use data-testid
- [ ] Locators are readonly
- [ ] All methods are async
- [ ] Constructor accepts Page
- [ ] goto() method for navigation
- [ ] JSDoc comments for public methods
- [ ] TypeScript types for parameters
- [ ] Meaningful method names
- [ ] No test assertions in POM (only actions and getters)

### Step 8: Provide Usage Example

Show how to use the Page Object in tests:

```typescript
import { test, expect } from "@playwright/test";
import { PageName } from "./page-objects/PageName";

test("test description", async ({ page }) => {
  const pageName = new PageName(page);

  await pageName.goto();
  await pageName.performAction();
  await expect(pageName.getElement()).toBeVisible();
});
```

## Examples

### Example 1: Login Page Object

**Input:**
"Create a Page Object for the login page with username, password fields, login button, and error message."

**Output:**

```typescript
import { Page, Locator } from "@playwright/test";

/**
 * Page Object Model for the Login Page
 *
 * Handles user authentication flow including:
 * - Filling login credentials
 * - Submitting the form
 * - Accessing error messages
 */
export class LoginPage {
  readonly page: Page;

  // Locators
  readonly usernameInput: Locator;
  readonly passwordInput: Locator;
  readonly loginButton: Locator;
  readonly errorMessage: Locator;
  readonly forgotPasswordLink: Locator;

  constructor(page: Page) {
    this.page = page;
    this.usernameInput = page.locator('[data-testid="username-input"]');
    this.passwordInput = page.locator('[data-testid="password-input"]');
    this.loginButton = page.locator('[data-testid="login-button"]');
    this.errorMessage = page.locator('[data-testid="error-message"]');
    this.forgotPasswordLink = page.locator(
      '[data-testid="forgot-password-link"]',
    );
  }

  /**
   * Navigate to the login page
   */
  async goto(): Promise<void> {
    await this.page.goto("/login");
    await this.page.waitForLoadState("domcontentloaded");
  }

  /**
   * Perform login with credentials
   * @param username - User's username or email
   * @param password - User's password
   */
  async login(username: string, password: string): Promise<void> {
    await this.usernameInput.waitFor({ state: "visible" });
    await this.usernameInput.fill(username);
    await this.passwordInput.fill(password);
    await this.loginButton.click();
  }

  /**
   * Fill only the username field
   */
  async fillUsername(username: string): Promise<void> {
    await this.usernameInput.fill(username);
  }

  /**
   * Fill only the password field
   */
  async fillPassword(password: string): Promise<void> {
    await this.passwordInput.fill(password);
  }

  /**
   * Click the login button
   */
  async clickLogin(): Promise<void> {
    await this.loginButton.click();
  }

  /**
   * Click forgot password link
   */
  async clickForgotPassword(): Promise<void> {
    await this.forgotPasswordLink.click();
  }

  /**
   * Get the error message element for assertions
   */
  getErrorMessage(): Locator {
    return this.errorMessage;
  }

  /**
   * Check if error message is visible
   */
  async hasError(): Promise<boolean> {
    try {
      await this.errorMessage.waitFor({ state: "visible", timeout: 2000 });
      return true;
    } catch {
      return false;
    }
  }

  /**
   * Get the text content of the error message
   */
  async getErrorText(): Promise<string> {
    const text = await this.errorMessage.textContent();
    return text?.trim() || "";
  }
}
```

**Usage:**

```typescript
import { test, expect } from "@playwright/test";
import { LoginPage } from "./page-objects/LoginPage";

test.describe("Login Flow", () => {
  test("should login successfully with valid credentials", async ({ page }) => {
    const loginPage = new LoginPage(page);

    await loginPage.goto();
    await loginPage.login("[email protected]", "SecurePass123");

    // Assert navigation to dashboard
    await page.waitForURL("/dashboard");
  });

  test("should show error with invalid credentials", async ({ page }) => {
    const loginPage = new LoginPage(page);

    awa

Related in Design