---
name: playwright-e2e-tester
description: Expert in end-to-end testing with Playwright, the modern cross-browser testing framework. Specializes in test generation, page object patterns, visual regression testing, and CI/CD integration.
  Handles complex testing scenarios including authentication flows, API mocking, and mobile emulation.
version: 1.0.0
metadata:
  category: testing
  tags:
  - e2e
  - playwright
  - testing
  - automation
  - ci-cd
  - cross-browser
  pairs-with:
  - skill: test-automation-expert
    reason: Playwright E2E tests are one tier in a comprehensive test automation strategy
  - skill: webapp-testing
    reason: 'Both use Playwright but for different scopes: E2E test suites vs interactive debugging'
  - skill: vitest-testing-patterns
    reason: Unit tests (Vitest) and E2E tests (Playwright) form complementary test pyramid layers
  - skill: github-actions-pipeline-builder
    reason: E2E tests run in CI pipelines with browser installation and artifact upload steps
---

# Playwright E2E Tester

## Overview

Expert in end-to-end testing with Playwright, the modern cross-browser testing framework. Specializes in test generation, page object patterns, visual regression testing, and CI/CD integration. Handles complex testing scenarios including authentication flows, API mocking, and mobile emulation.

## When to Use

- Setting up Playwright in a new or existing project
- Writing E2E tests for critical user flows
- Debugging flaky tests or test failures
- Implementing visual regression testing
- Configuring Playwright for CI/CD pipelines
- Migrating from Cypress, Selenium, or Puppeteer
- Testing authenticated flows with session management
- Cross-browser testing (Chromium, Firefox, WebKit)

## Capabilities

### Test Generation & Writing
- Generate Playwright tests from user stories or acceptance criteria
- Write tests using best practices (locators, assertions, waits)
- Implement Page Object Model (POM) patterns
- Create reusable test fixtures and utilities
- Handle dynamic content and race conditions

### Configuration & Setup
- Configure `playwright.config.ts` for different environments
- Set up projects for multiple browsers and viewports
- Configure base URL, timeouts, and retries
- Implement global setup/teardown for auth
- Set up test reporters (HTML, JSON, JUnit)

### Advanced Patterns
- API mocking with `route()` and `fulfill()`
- Network interception and request validation
- Visual regression with `toHaveScreenshot()`
- Accessibility testing with `@axe-core/playwright`
- Mobile emulation and device testing
- Geolocation and permissions mocking

### CI/CD Integration
- GitHub Actions workflow configuration
- Parallel test execution with sharding
- Artifact collection (traces, screenshots, videos)
- Flaky test detection and retry strategies
- Test result reporting and notifications

### Debugging & Maintenance
- Use Playwright Inspector and Trace Viewer
- Debug with `page.pause()` and headed mode
- Analyze test traces for failures
- Reduce test flakiness with proper waits
- Maintain test stability over time

## Dependencies

Works well with:
- `vitest-testing-patterns` - Unit test patterns that complement E2E
- `github-actions-pipeline-builder` - CI/CD pipeline setup
- `accessibility-auditor` - Extended accessibility testing
- `api-architect` - API contract testing alongside E2E

## Examples

### Basic Test Structure
```typescript
import { test, expect } from '@playwright/test';

test.describe('User Authentication', () => {
  test('should allow user to sign in', async ({ page }) => {
    await page.goto('/login');

    await page.getByLabel('Email').fill('user@example.com');
    await page.getByLabel('Password').fill('securepassword');
    await page.getByRole('button', { name: 'Sign In' }).click();

    await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible();
    await expect(page).toHaveURL('/dashboard');
  });
});
```

### Page Object Pattern
```typescript
// 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.getByLabel('Email');
    this.passwordInput = page.getByLabel('Password');
    this.signInButton = page.getByRole('button', { name: 'Sign In' });
  }

  async goto() {
    await this.page.goto('/login');
  }

  async signIn(email: string, password: string) {
    await this.emailInput.fill(email);
    await this.passwordInput.fill(password);
    await this.signInButton.click();
  }
}
```

### Auth Setup Fixture
```typescript
// fixtures/auth.ts
import { test as base } from '@playwright/test';

export const test = base.extend({
  authenticatedPage: async ({ page }, use) => {
    // Perform authentication
    await page.goto('/login');
    await page.getByLabel('Email').fill(process.env.TEST_USER!);
    await page.getByLabel('Password').fill(process.env.TEST_PASS!);
    await page.getByRole('button', { name: 'Sign In' }).click();

    // Wait for auth to complete
    await page.waitForURL('/dashboard');

    // Use the authenticated page in tests
    await use(page);
  },
});
```

### GitHub Actions CI
```yaml
name: E2E Tests

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20

      - name: Install dependencies
        run: npm ci

      - name: Install Playwright browsers
        run: npx playwright install --with-deps

      - name: Run E2E tests
        run: npx playwright test

      - name: Upload test results
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: playwright-report
          path: playwright-report/
```

### Visual Regression Test
```typescript
test('homepage matches snapshot', async ({ page }) => {
  await page.goto('/');

  // Full page screenshot comparison
  await expect(page).toHaveScreenshot('homepage.png', {
    fullPage: true,
    maxDiffPixelRatio: 0.01,
  });

  // Component-level screenshot
  const hero = page.getByTestId('hero-section');
  await expect(hero).toHaveScreenshot('hero-section.png');
});
```

### API Mocking
```typescript
test('displays products from API', async ({ page }) => {
  // Mock the API response
  await page.route('**/api/products', async (route) => {
    await route.fulfill({
      status: 200,
      contentType: 'application/json',
      body: JSON.stringify([
        { id: 1, name: 'Product A', price: 29.99 },
        { id: 2, name: 'Product B', price: 49.99 },
      ]),
    });
  });

  await page.goto('/products');

  await expect(page.getByText('Product A')).toBeVisible();
  await expect(page.getByText('$29.99')).toBeVisible();
});
```

## Best Practices

1. **Use role-based locators** - Prefer `getByRole()`, `getByLabel()`, `getByText()` over CSS selectors
2. **Avoid hard waits** - Use `waitForSelector()`, `waitForURL()`, or assertions instead of `waitForTimeout()`
3. **Isolate tests** - Each test should be independent and not rely on state from other tests
4. **Use fixtures** - Share setup logic through fixtures rather than `beforeEach` hooks
5. **Keep tests focused** - Test one user flow per test, avoid testing multiple unrelated things
6. **Handle flakiness proactively** - Use proper waits, retries, and stable locators
7. **Organize with Page Objects** - Encapsulate page interactions for maintainability
8. **Run in CI** - Always run E2E tests in CI before merging

## Common Pitfalls

- **Flaky locators**: Avoid fragile selectors like `nth-child(3)` or auto-generated class names
- **Race conditions**: Always wait for elements/navigation before interacting
- **Shared state**: Tests should not depend on execution order
- **Slow tests**: Use API calls to set up state instead of UI interactions when possible
- **Missing cleanup**: Clean up test data to avoid pollution between runs
