Claude
Skills
Sign in
Back

playwright-test-maintainer

Included with Lifetime
$97 forever

Maintain, refactor, and improve existing Playwright E2E tests. Handles tasks like updating locators across test suites, extracting reusable utilities, improving test stability, removing code duplication, and enforcing best practices throughout the test codebase.

Code Review

What this skill does


## When to Use This Skill

Use this skill when you need to:

- Update data-testid locators across multiple tests
- Refactor duplicate code into utilities or Page Objects
- Improve flaky or unstable tests
- Extract common test patterns into reusable fixtures
- Update tests after UI changes
- Migrate tests to use Page Object Model
- Consolidate similar tests
- Improve test readability and maintainability

Do NOT use this skill when:

- Creating new tests from scratch (use test-generator skill)
- Building new Page Objects (use page-object-builder skill)
- Debugging test failures (use test-debugger skill)

## Prerequisites

Before using this skill:

1. Access to existing test files
2. Understanding of what changes are needed
3. Knowledge of the current test structure
4. Optional: Test execution results to identify flaky tests

## Instructions

### Step 1: Assess Current State

Gather information about:

- **Test files** requiring maintenance
- **Type of maintenance** needed (refactor, update locators, fix flakiness)
- **Scope** of changes (single file, multiple files, entire suite)
- **Current issues** (duplication, poor practices, flakiness)
- **Desired end state** (what should the tests look like after)

### Step 2: Identify Maintenance Type

Determine the maintenance task:

**Locator Updates:**

- Changing data-testid values
- Updating selectors after UI changes
- Migrating from CSS/XPath to data-testid

**Code Refactoring:**

- Extracting duplicate code to utilities
- Creating Page Objects from inline selectors
- Consolidating similar tests
- Improving test structure

**Stability Improvements:**

- Adding explicit waits
- Fixing race conditions
- Removing hardcoded waits
- Improving assertions

**Best Practices:**

- Enforcing data-testid usage
- Implementing AAA pattern
- Adding proper TypeScript types
- Improving test isolation

### Step 3: Plan the Changes

Before making changes:

1. **Identify all affected files**
2. **Backup or commit current state** (git commit)
3. **Create checklist** of changes to make
4. **Plan refactoring strategy** (bottom-up or top-down)
5. **Consider impact** on other tests

### Step 4: Apply Maintenance

Execute the maintenance based on type:

#### Locator Updates

```typescript
// Task: Update data-testid from "btn-submit" to "submit-button"

// Before (multiple files)
await page.locator('[data-testid="btn-submit"]').click();

// After (updated in all files)
await page.locator('[data-testid="submit-button"]').click();

// Use search and replace across files
// Find: '[data-testid="btn-submit"]'
// Replace: '[data-testid="submit-button"]'
```

#### Extract Utilities

```typescript
// Before: Duplicate login code in multiple tests
test("test 1", async ({ page }) => {
  await page.goto("/login");
  await page.locator('[data-testid="email"]').fill("[email protected]");
  await page.locator('[data-testid="password"]').fill("password");
  await page.locator('[data-testid="login-button"]').click();
  await page.waitForURL("/dashboard");
  // ... test continues
});

// After: Extract to utility function
// In utils/auth.ts
export async function login(page: Page, email: string, password: string) {
  await page.goto("/login");
  await page.locator('[data-testid="email"]').fill(email);
  await page.locator('[data-testid="password"]').fill(password);
  await page.locator('[data-testid="login-button"]').click();
  await page.waitForURL("/dashboard");
}

// In tests
test("test 1", async ({ page }) => {
  await login(page, "[email protected]", "password");
  // ... test continues
});
```

#### Migrate to Page Objects

```typescript
// Before: Inline selectors throughout tests
test("update profile", async ({ page }) => {
  await page.goto("/profile");
  await page.locator('[data-testid="name-input"]').fill("John Doe");
  await page.locator('[data-testid="email-input"]').fill("[email protected]");
  await page.locator('[data-testid="save-button"]').click();
  await expect(page.locator('[data-testid="success-message"]')).toBeVisible();
});

// After: Using Page Object
// Create ProfilePage.ts (see page-object-builder skill)

test("update profile", async ({ page }) => {
  const profilePage = new ProfilePage(page);
  await profilePage.goto();
  await profilePage.updateProfile({
    name: "John Doe",
    email: "[email protected]",
  });
  await expect(profilePage.getSuccessMessage()).toBeVisible();
});
```

#### Fix Flaky Tests

```typescript
// Before: Flaky due to race condition
await page.locator('[data-testid="submit"]').click();
await expect(page.locator('[data-testid="result"]')).toContainText("Success");

// After: Add proper waits
await page.locator('[data-testid="submit"]').click();
await page.waitForLoadState("networkidle");
await expect(page.locator('[data-testid="result"]')).toContainText("Success", {
  timeout: 10000,
});
```

### Step 5: Ensure Consistency

After changes:

- [ ] All tests use data-testid locators
- [ ] Consistent naming conventions
- [ ] Follow AAA pattern
- [ ] Proper TypeScript types
- [ ] No code duplication
- [ ] Tests are isolated
- [ ] Proper waits (no hardcoded timeouts)

### Step 6: Verify Changes

Run tests to ensure:

1. **All tests pass** after refactoring
2. **No regressions** introduced
3. **Improved stability** (run multiple times)
4. **Better readability** and maintainability
5. **Reduced code duplication**

## Examples

### Example 1: Update Locators Across Test Suite

**Input:**
"The development team changed all button data-testids from format 'btn-action' to 'action-button'. Update all tests."

**Changes:**

```typescript
// Create mapping of old to new testids
const locatorUpdates = {
  "btn-submit": "submit-button",
  "btn-cancel": "cancel-button",
  "btn-delete": "delete-button",
  "btn-edit": "edit-button",
  "btn-save": "save-button",
};

// Apply to all test files:
// Find all instances in: tests/**/*.spec.ts

// Example in login.spec.ts:
// Before
await page.locator('[data-testid="btn-submit"]').click();

// After
await page.locator('[data-testid="submit-button"]').click();

// Use global find and replace for each mapping
```

**Verification:**

```bash
# Search for old pattern to ensure all updated
grep -r "btn-" tests/

# Run all tests
npx playwright test

# Check for any failures
```

### Example 2: Extract Common Test Utilities

**Input:**
"Multiple tests have duplicate code for filling forms. Extract to reusable utilities."

**Solution:**

```typescript
// Identify duplicate pattern across tests:
// Pattern 1: Form filling
await page.locator('[data-testid="field1"]').fill(value1);
await page.locator('[data-testid="field2"]').fill(value2);
await page.locator('[data-testid="field3"]').fill(value3);

// Create utils/form-helpers.ts:
import { Page } from "@playwright/test";

export async function fillForm(
  page: Page,
  fields: Record<string, string>,
): Promise<void> {
  for (const [testId, value] of Object.entries(fields)) {
    await page.locator(`[data-testid="${testId}"]`).fill(value);
  }
}

export async function submitForm(
  page: Page,
  submitButtonTestId: string,
): Promise<void> {
  await page
    .locator(`[data-testid="${submitButtonTestId}"]`)
    .waitFor({ state: "visible" });
  await page.locator(`[data-testid="${submitButtonTestId}"]`).click();
}

// Update all tests to use utilities:
import { fillForm, submitForm } from "../utils/form-helpers";

test("contact form submission", async ({ page }) => {
  await page.goto("/contact");

  await fillForm(page, {
    "name-input": "John Doe",
    "email-input": "[email protected]",
    "message-input": "Hello!",
  });

  await submitForm(page, "submit-button");
  await expect(page.locator('[data-testid="success"]')).toBeVisible();
});
```

### Example 3: Consolidate Similar Tests

**Input:**
"We have 5 tests that test form validation with different invalid inputs. Consolidate using test.each."

**Before:**

```typescript
test("should show error for empty email", async ({ page }) => {
  await page.goto("/register");
  awai

Related in Code Review