playwright

Playwright E2E testing, page objects, fixtures, visual regression, accessibility testing, and CI integration patterns.

16 stars

Best use case

playwright is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Playwright E2E testing, page objects, fixtures, visual regression, accessibility testing, and CI integration patterns.

Teams using playwright should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/playwright/SKILL.md --create-dirs "https://raw.githubusercontent.com/diegosouzapw/awesome-omni-skill/main/skills/testing-security/playwright/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/playwright/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How playwright Compares

Feature / AgentplaywrightStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Playwright E2E testing, page objects, fixtures, visual regression, accessibility testing, and CI integration patterns.

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# Playwright Skill

Expert assistance for building comprehensive E2E test suites with Playwright, including page objects, fixtures, visual regression, and CI/CD integration.

## Capabilities

- Generate Playwright test project structure
- Create page object models for maintainable tests
- Implement custom fixtures and test utilities
- Configure visual regression testing
- Set up accessibility testing with axe-core
- Integrate with CI/CD pipelines (GitHub Actions, etc.)
- Generate API testing alongside UI tests

## Usage

Invoke this skill when you need to:
- Set up Playwright testing for a web application
- Create page object patterns for test organization
- Implement visual regression testing
- Configure cross-browser testing
- Set up CI/CD test automation

## Inputs

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| projectType | string | No | web, api, component (default: web) |
| framework | string | No | react, nextjs, vue, angular |
| browsers | array | No | chromium, firefox, webkit (default: all) |
| features | array | No | visual, a11y, api, component |
| ci | string | No | github, gitlab, jenkins |

### Test Configuration

```json
{
  "projectType": "web",
  "framework": "nextjs",
  "browsers": ["chromium", "firefox"],
  "features": ["visual", "a11y", "api"],
  "ci": "github",
  "baseUrl": "http://localhost:3000"
}
```

## Output Structure

```
tests/
├── playwright.config.ts           # Playwright configuration
├── fixtures/
│   ├── base.ts                   # Base test fixture
│   ├── auth.ts                   # Authentication fixture
│   └── api.ts                    # API helper fixture
├── pages/
│   ├── BasePage.ts               # Base page object
│   ├── LoginPage.ts              # Login page object
│   └── DashboardPage.ts          # Dashboard page object
├── e2e/
│   ├── auth/
│   │   ├── login.spec.ts
│   │   └── logout.spec.ts
│   ├── dashboard/
│   │   └── dashboard.spec.ts
│   └── api/
│       └── users.api.spec.ts
├── visual/
│   ├── homepage.visual.spec.ts
│   └── screenshots/              # Baseline screenshots
├── a11y/
│   └── accessibility.spec.ts
├── utils/
│   ├── helpers.ts
│   └── test-data.ts
└── .github/
    └── workflows/
        └── playwright.yml        # CI workflow
```

## Generated Code Patterns

### Playwright Configuration

```typescript
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  testDir: './tests',
  fullyParallel: true,
  forbidOnly: !!process.env.CI,
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 1 : undefined,
  reporter: [
    ['html', { open: 'never' }],
    ['json', { outputFile: 'test-results/results.json' }],
    ['junit', { outputFile: 'test-results/junit.xml' }],
  ],
  use: {
    baseURL: process.env.BASE_URL ?? 'http://localhost:3000',
    trace: 'on-first-retry',
    screenshot: 'only-on-failure',
    video: 'retain-on-failure',
  },
  projects: [
    { name: 'setup', testMatch: /.*\.setup\.ts/ },
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] },
      dependencies: ['setup'],
    },
    {
      name: 'firefox',
      use: { ...devices['Desktop Firefox'] },
      dependencies: ['setup'],
    },
    {
      name: 'webkit',
      use: { ...devices['Desktop Safari'] },
      dependencies: ['setup'],
    },
    {
      name: 'mobile-chrome',
      use: { ...devices['Pixel 5'] },
      dependencies: ['setup'],
    },
    {
      name: 'mobile-safari',
      use: { ...devices['iPhone 13'] },
      dependencies: ['setup'],
    },
  ],
  webServer: {
    command: 'npm run dev',
    url: 'http://localhost:3000',
    reuseExistingServer: !process.env.CI,
  },
});
```

### Base Page Object

```typescript
// tests/pages/BasePage.ts
import { Page, Locator, expect } from '@playwright/test';

export abstract class BasePage {
  readonly page: Page;
  readonly header: Locator;
  readonly footer: Locator;
  readonly loadingSpinner: Locator;

  constructor(page: Page) {
    this.page = page;
    this.header = page.locator('header');
    this.footer = page.locator('footer');
    this.loadingSpinner = page.locator('[data-testid="loading"]');
  }

  abstract get url(): string;

  async goto() {
    await this.page.goto(this.url);
    await this.waitForPageLoad();
  }

  async waitForPageLoad() {
    await this.loadingSpinner.waitFor({ state: 'hidden' });
  }

  async expectToBeVisible() {
    await expect(this.page).toHaveURL(new RegExp(this.url));
  }

  async getToastMessage(): Promise<string | null> {
    const toast = this.page.locator('[role="alert"]');
    if (await toast.isVisible()) {
      return toast.textContent();
    }
    return null;
  }
}
```

### Login Page Object

```typescript
// tests/pages/LoginPage.ts
import { Page, Locator, expect } from '@playwright/test';
import { BasePage } from './BasePage';

export class LoginPage extends BasePage {
  readonly emailInput: Locator;
  readonly passwordInput: Locator;
  readonly submitButton: Locator;
  readonly errorMessage: Locator;
  readonly forgotPasswordLink: Locator;

  constructor(page: Page) {
    super(page);
    this.emailInput = page.getByLabel('Email');
    this.passwordInput = page.getByLabel('Password');
    this.submitButton = page.getByRole('button', { name: 'Sign in' });
    this.errorMessage = page.locator('[role="alert"]');
    this.forgotPasswordLink = page.getByRole('link', { name: 'Forgot password?' });
  }

  get url() {
    return '/login';
  }

  async login(email: string, password: string) {
    await this.emailInput.fill(email);
    await this.passwordInput.fill(password);
    await this.submitButton.click();
  }

  async expectErrorMessage(message: string) {
    await expect(this.errorMessage).toContainText(message);
  }

  async expectLoginSuccess() {
    await expect(this.page).toHaveURL(/\/dashboard/);
  }
}
```

### Custom Fixtures

```typescript
// tests/fixtures/base.ts
import { test as base, expect } from '@playwright/test';
import { LoginPage } from '../pages/LoginPage';
import { DashboardPage } from '../pages/DashboardPage';

interface TestFixtures {
  loginPage: LoginPage;
  dashboardPage: DashboardPage;
}

interface WorkerFixtures {
  authenticatedPage: void;
}

export const test = base.extend<TestFixtures, WorkerFixtures>({
  loginPage: async ({ page }, use) => {
    const loginPage = new LoginPage(page);
    await use(loginPage);
  },

  dashboardPage: async ({ page }, use) => {
    const dashboardPage = new DashboardPage(page);
    await use(dashboardPage);
  },

  authenticatedPage: [
    async ({ browser }, use) => {
      const context = await browser.newContext({
        storageState: 'tests/.auth/user.json',
      });
      await use();
      await context.close();
    },
    { scope: 'worker' },
  ],
});

export { expect };
```

### Authentication Setup

```typescript
// tests/auth.setup.ts
import { test as setup, expect } from '@playwright/test';
import path from 'path';

const authFile = path.join(__dirname, '.auth/user.json');

setup('authenticate', async ({ page }) => {
  await page.goto('/login');
  await page.getByLabel('Email').fill(process.env.TEST_USER_EMAIL!);
  await page.getByLabel('Password').fill(process.env.TEST_USER_PASSWORD!);
  await page.getByRole('button', { name: 'Sign in' }).click();

  await expect(page).toHaveURL(/\/dashboard/);
  await page.context().storageState({ path: authFile });
});
```

### E2E Test Example

```typescript
// tests/e2e/auth/login.spec.ts
import { test, expect } from '../../fixtures/base';

test.describe('Login', () => {
  test.beforeEach(async ({ loginPage }) => {
    await loginPage.goto();
  });

  test('should login with valid credentials', async ({ loginPage }) => {
    await loginPage.login('user@example.com', 'password123');
    await loginPage.expectLoginSuccess();
  });

  test('should show error with invalid credentials', async ({ loginPage }) => {
    await loginPage.login('invalid@example.com', 'wrongpassword');
    await loginPage.expectErrorMessage('Invalid email or password');
  });

  test('should show validation errors for empty fields', async ({ loginPage }) => {
    await loginPage.submitButton.click();
    await expect(loginPage.emailInput).toHaveAttribute('aria-invalid', 'true');
    await expect(loginPage.passwordInput).toHaveAttribute('aria-invalid', 'true');
  });
});
```

### Visual Regression Test

```typescript
// tests/visual/homepage.visual.spec.ts
import { test, expect } from '@playwright/test';

test.describe('Visual Regression', () => {
  test('homepage should match snapshot', async ({ page }) => {
    await page.goto('/');
    await expect(page).toHaveScreenshot('homepage.png', {
      fullPage: true,
      animations: 'disabled',
    });
  });

  test('login page should match snapshot', async ({ page }) => {
    await page.goto('/login');
    await expect(page).toHaveScreenshot('login-page.png');
  });

  test('dashboard should match snapshot @authenticated', async ({ page }) => {
    await page.goto('/dashboard');
    await expect(page).toHaveScreenshot('dashboard.png', {
      mask: [page.locator('[data-testid="user-avatar"]')],
    });
  });
});
```

### Accessibility Test

```typescript
// tests/a11y/accessibility.spec.ts
import { test, expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';

test.describe('Accessibility', () => {
  test('homepage should have no accessibility violations', async ({ page }) => {
    await page.goto('/');

    const accessibilityScanResults = await new AxeBuilder({ page })
      .withTags(['wcag2a', 'wcag2aa', 'wcag21aa'])
      .analyze();

    expect(accessibilityScanResults.violations).toEqual([]);
  });

  test('login form should be keyboard accessible', async ({ page }) => {
    await page.goto('/login');

    await page.keyboard.press('Tab');
    await expect(page.getByLabel('Email')).toBeFocused();

    await page.keyboard.press('Tab');
    await expect(page.getByLabel('Password')).toBeFocused();

    await page.keyboard.press('Tab');
    await expect(page.getByRole('button', { name: 'Sign in' })).toBeFocused();
  });
});
```

### API Test

```typescript
// tests/e2e/api/users.api.spec.ts
import { test, expect } from '@playwright/test';

test.describe('Users API', () => {
  test('should get user list', async ({ request }) => {
    const response = await request.get('/api/users');

    expect(response.ok()).toBeTruthy();

    const body = await response.json();
    expect(body.users).toBeInstanceOf(Array);
    expect(body.users.length).toBeGreaterThan(0);
  });

  test('should create a new user', async ({ request }) => {
    const response = await request.post('/api/users', {
      data: {
        name: 'Test User',
        email: 'test@example.com',
      },
    });

    expect(response.status()).toBe(201);

    const user = await response.json();
    expect(user.name).toBe('Test User');
    expect(user.email).toBe('test@example.com');
  });
});
```

### GitHub Actions Workflow

```yaml
# .github/workflows/playwright.yml
name: Playwright Tests

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  test:
    timeout-minutes: 60
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

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

      - name: Run Playwright tests
        run: npx playwright test
        env:
          BASE_URL: ${{ secrets.BASE_URL }}
          TEST_USER_EMAIL: ${{ secrets.TEST_USER_EMAIL }}
          TEST_USER_PASSWORD: ${{ secrets.TEST_USER_PASSWORD }}

      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: playwright-report
          path: playwright-report/
          retention-days: 30
```

## Dependencies

```json
{
  "devDependencies": {
    "@playwright/test": "^1.50.0",
    "@axe-core/playwright": "^4.10.0"
  }
}
```

## Workflow

1. **Setup configuration** - Create playwright.config.ts
2. **Create page objects** - Model application pages
3. **Define fixtures** - Set up test utilities
4. **Write tests** - E2E, visual, a11y tests
5. **Configure CI** - GitHub Actions workflow
6. **Generate reports** - HTML, JSON, JUnit

## Best Practices Applied

- Page Object Model for maintainability
- Custom fixtures for reusability
- Parallel test execution
- Cross-browser testing
- Visual regression baselines
- Accessibility testing integration
- Proper test isolation

## References

- Playwright Documentation: https://playwright.dev/docs/intro
- playwright-skill: https://github.com/lackeyjb/playwright-skill
- mcp-playwright: https://github.com/executeautomation/mcp-playwright
- Axe-core: https://www.deque.com/axe/

## Target Processes

- e2e-testing-setup
- visual-regression-testing
- accessibility-testing
- api-testing
- ci-cd-integration

Related Skills

qa-testing-playwright

16
from diegosouzapw/awesome-omni-skill

End-to-end web application testing with Playwright: scope control, stable selectors, parallelization/sharding, flake control, network mocking vs real services, visual testing tradeoffs, and CI/CD integration.

playwright-visual-regression

16
from diegosouzapw/awesome-omni-skill

Playwright browser automation patterns for E2E testing, visual regression, screenshot capture, and accessibility validation. Use when implementing or debugging browser-based tests.

playwright-test

16
from diegosouzapw/awesome-omni-skill

Generate robust, zero-flakiness Playwright E2E tests following OpenMetadata patterns. Creates comprehensive test files with proper waits, API validation, multi-role permissions, and complete entity lifecycle management.

playwright-reviewing

16
from diegosouzapw/awesome-omni-skill

Review Playwright E2E tests for best practices violations. Detects mocked app data, explicit timeouts, CSS selectors, skipped tests, and assertion anti-patterns. Use when reviewing Playwright PRs or auditing test quality.

Playwright E2E Testing

16
from diegosouzapw/awesome-omni-skill

Comprehensive Playwright end-to-end testing patterns with Page Object Model, fixtures, and best practices

playwright-e2e-tester

16
from diegosouzapw/awesome-omni-skill

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.

playwright-best-practices

16
from diegosouzapw/awesome-omni-skill

Review code for Playwright Guidelines compliance. Use when asked to "review my playwright tests", "check accessibility", or "check my tests against best practices".

azure-resource-manager-playwright-dotnet

16
from diegosouzapw/awesome-omni-skill

Azure Resource Manager SDK for Microsoft Playwright Testing in .NET.

playwright-expert

16
from diegosouzapw/awesome-omni-skill

Use when writing E2E tests with Playwright, setting up test infrastructure, or debugging flaky browser tests. Invoke for browser automation, E2E tests, Page Object Model, test flakiness, visual testing, CI/CD pipeline optimization.

azure-microsoft-playwright-testing-ts

16
from diegosouzapw/awesome-omni-skill

Run Playwright tests at scale using Azure Playwright Workspaces (formerly Microsoft Playwright Testing). Use when scaling browser tests across cloud-hosted browsers, integrating with CI/CD pipeline...

vue-playwright-testing

16
from diegosouzapw/awesome-omni-skill

Comprehensive guide for testing Vue 3 applications with Playwright (2025). This skill should be used when writing end-to-end tests or component tests for Vue apps, testing Vue Router navigation, reactive state changes, authentication flows, or setting up Playwright in Vue projects.

playwright-skill

16
from diegosouzapw/awesome-omni-skill

Complete browser automation with Playwright. Auto-detects dev servers, writes clean test scripts to /tmp. Test pages, fill forms, take screenshots, check responsive design, validate UX, test login flows, check links, automate any browser task. Use when user wants to test websites, automate browser interactions, validate web functionality, or perform any browser-based testing.