agentsclimarketplace

Playwright e2e testing

Skill JPeetz/agent-skills/playwright-e2e-testing

The definitive collection of cross-platform Agent Skills. Compatible with Claude Code, Codex, Cursor, OpenClaw, Gemini CLI, Copilot, Hermes. Curated weekly. Higher quality than any alternative.

Install
npx -y skills add JPeetz/agent-skills --skill playwright-e2e-testing

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Production-grade Playwright end-to-end testing skill for AI coding agents. Provides specialized guidance for writing, debugging, and maintaining Playwright tests in TypeScript, JavaScript, and Python. Covers the full testing lifecycle: test structure and architecture (Page Object Model, fixtures, custom matchers), locator strategy best practices (role-based, test-ID, accessible selectors), auto-waiting and retry-ability patterns, API and network mocking, visual regression and screenshot comparison, component testing (React, Vue, Svelte), accessibility audits (axe-core integration), mobile and device emulation, authentication and session management (multi-profile, OAuth, 2FA), performance testing with Web Vitals and Lighthouse integration, CI/CD pipeline configuration (GitHub Actions, GitLab CI, sharding, parallelization), flaky test detection and auto-healing, test data management and fixtures, internationalization (i18n) and localization testing, Electron and browser extension testing, security testing (XSS, CSRF, CSP), and WebSocket/real-time application testing. Primary keyword clusters: Playwright E2E testing, Playwright best practices, browser automation testing, end-to-end test automation, Playwright TypeScript testing, visual regression testing Playwright, Playwright CI/CD configuration, flaky test prevention, Page Object Model Playwright, Playwright component testing. Designed for agentic platforms — Claude Code, Codex, Cursor, Gemini CLI, OpenClaw, GitHub Copilot, Windsurf, OpenCode, and all SKILL.md-compatible agents.

SKILL.md

24.0 KB, as published. Nobody here has run it

Playwright End-to-End Testing — Agent Skill

Production-grade Playwright testing guidance for AI coding agents. Use this skill whenever writing, reviewing, debugging, or configuring Playwright tests — E2E, component, API, visual regression, accessibility, performance, security, Electron, or browser extension tests.

Quick Decision Tree

User asks about Playwright testing?
├─ Writing new tests → §1 Test Architecture & §2 Locator Strategy
├─ Debugging a failure → §7 Debugging & Flaky Tests
├─ Setting up CI/CD → §8 CI/CD Configuration
├─ Test is flaky → §7 Flaky Test Detection & Auto-Healing
├─ Visual/UI changes → §4 Visual Regression Testing
├─ Testing components → §5 Component Testing
├─ Mobile/responsive → §6 Mobile & Device Emulation
├─ Auth/Login flows → §3 Authentication & Sessions
├─ Performance/Lighthouse → §10 Performance Testing
├─ Accessibility/a11y → §9 Accessibility Testing
├─ Security testing → §12 Security Testing
├─ Real-time/WebSocket → §13 WebSocket Testing
├─ i18n/L10n → §11 Internationalization Testing
└─ Electron/extensions → §14 Electron & Extensions

§1 Test Architecture & Structure

Core Principles

  1. One assertion per test when practical. Isolates failures to single causes.
  2. Test user-visible behavior, not implementation details. Assert on what the user sees.
  3. Use fixtures for shared setup. Fixtures are auto-initialized per test, avoiding shared mutable state between tests.
  4. Group related tests with test.describe. Use serial mode only when tests must run in order; prefer parallel by default.

File Organization

e2e/
├── fixtures/           # Custom fixtures, auth setup
│   └── auth.ts
├── pages/              # Page Object Models
│   ├── base-page.ts
│   ├── login-page.ts
│   └── dashboard-page.ts
├── tests/
│   ├── auth/
│   │   └── login.spec.ts
│   ├── checkout/
│   │   └── checkout-flow.spec.ts
│   └── admin/
│       └── user-management.spec.ts
├── utils/
│   ├── test-data.ts    # Test data factories
│   └── api-mocks.ts    # API route handlers
├── playwright.config.ts
└── global-setup.ts     # Auth, DB seeding, etc.

Page Object Model Pattern

// pages/base-page.ts — NEVER use in prod; example only
import { Page, Locator } from '@playwright/test';

export class BasePage {
  constructor(protected readonly page: Page) {}

  async navigate(path: string): Promise<void> {
    await this.page.goto(path);
    await this.page.waitForLoadState('networkidle');
  }

  async getByTestId(id: string): Promise<Locator> {
    return this.page.getByTestId(id);
  }
}
// pages/login-page.ts
import { BasePage } from './base-page';

export class LoginPage extends BasePage {
  private emailInput = () => this.page.getByLabel('Email address');
  private passwordInput = () => this.page.getByLabel('Password');
  private submitButton = () => this.page.getByRole('button', { name: 'Sign in' });
  private errorMessage = () => this.page.getByRole('alert');

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

  async getErrorMessage(): Promise<string> {
    return await this.errorMessage().textContent() ?? '';
  }
}

§2 Locator Strategy — The Priority Hierarchy

Rule: Always use the most specific, accessible locator first.

1. getByRole()       ← Best. Mirrors accessibility tree. Screen-reader friendly.
2. getByLabel()      ← Excellent for form inputs. Uses <label> association.
3. getByPlaceholder()← Good for inputs with placeholder text.
4. getByText()       ← Good for non-interactive text content.
5. getByTestId()     ← Last resort. Stable but requires test-id attributes.
6. locator('css')    ← Avoid. Brittle, couples tests to DOM structure.

Anti-Patterns (DO NOT USE)

❌ page.locator('.btn-primary')          // CSS class — brittle
❌ page.locator('#submit-button')        // ID — fragile on dynamic pages
❌ page.locator('button >> nth=0')       // Positional — breaks on reorder
❌ page.locator('div > div > button')    // DOM structure — extremely fragile
❌ page.locator('//button[text()="OK"]') // XPath — slow, fragile, avoid

Locator Best Practices

// ✅ Role-based — most resilient
await page.getByRole('button', { name: 'Submit' }).click();
await page.getByRole('heading', { name: 'Dashboard' }).toBeVisible();

// ✅ Label-based — best for forms
await page.getByLabel('Email').fill('[email protected]');

// ✅ Test ID — fallback when no semantic element exists
await page.getByTestId('checkout-total').toHaveText('$42.00');

// ✅ Text content — for static text verification
await page.getByText('Order confirmed').toBeVisible();

// ✅ Filtering and chaining
const row = page.getByRole('row', { name: 'Order #1234' });
await row.getByRole('button', { name: 'View' }).click();

§3 Authentication & Session Management

Multi-Profile Auth Setup

Use global-setup.ts to authenticate once and reuse sessions across tests:

// global-setup.ts
import { chromium, FullConfig } from '@playwright/test';

async function globalSetup(config: FullConfig) {
  const { baseURL, storageState } = config.projects[0].use;

  const browser = await chromium.launch();
  const page = await browser.newPage();

  await page.goto(`${baseURL}/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 page.waitForURL('**/dashboard');

  await page.context().storageState({ path: storageState as string });
  await browser.close();
}

export default globalSetup;

Per-Role Authentication

For apps with multiple user roles, create separate auth profiles:

// playwright.config.ts (excerpt)
projects: [
  {
    name: 'admin',
    use: {
      storageState: 'playwright/.auth/admin.json',
      ...devices['Desktop Chrome'],
    },
  },
  {
    name: 'customer',
    use: {
      storageState: 'playwright/.auth/customer.json',
      ...devices['Desktop Chrome'],
    },
  },
],

OAuth / 2FA Handling

For flows requiring manual interaction:

  1. Create auth profiles manually once via npx playwright codegen --save-storage=auth.json
  2. Store the profile in CI as a secured artifact or encrypted file
  3. Rotate auth profiles weekly or when passwords change
  4. Never commit auth storage files to version control

§4 Visual Regression Testing

Screenshot Comparison

import { test, expect } from '@playwright/test';

test('checkout page matches baseline', async ({ page }) => {
  await page.goto('/checkout');
  await expect(page).toHaveScreenshot('checkout-page.png', {
    fullPage: true,
    maxDiffPixelRatio: 0.02,  // Allow 2% pixel difference
  });
});

Visual Comparison Strategies

StrategyWhen to UseTrade-off
Full-pageLayout regressionsSensitive to any change
Element-levelSpecific component changesMore targeted, less noise
Masked regionsDynamic content (dates, ads)Requires maintaining mask list
Threshold-basedSlightly dynamic UIsRisk of missing real regressions

Element-Level Snapshots (Preferred)

// Prefer element-level over full-page for stability
await expect(page.getByTestId('pricing-card')).toHaveScreenshot();
await expect(page.getByRole('navigation')).toHaveScreenshot('nav.png');

// Mask dynamic content
await expect(page.getByTestId('dashboard')).toHaveScreenshot({
  mask: [page.getByTestId('timestamp'), page.getByTestId('avatar')],
});

§5 Component Testing

Playwright supports component testing for React, Vue, Svelte, and Solid:

// Button.spec.tsx — React component test
import { test, expect } from '@playwright/experimental-ct-react';
import { Button } from './Button';

test('fires click handler', async ({ mount }) => {
  let clicked = false;
  const component = await mount(
    <Button onClick={() => { clicked = true; }}>Click me</Button>
  );
  await component.click();
  expect(clicked).toBe(true);
});

test('renders disabled state', async ({ mount }) => {
  const component = await mount(<Button disabled>Disabled</Button>);
  await expect(component).toBeDisabled();
});

When to Use Component Tests vs E2E

  • Component tests: Interaction logic, states, edge cases, accessibility of isolated components
  • E2E tests: User flows spanning multiple pages, API integration, auth flows, critical paths
  • Rule of thumb: Test at the lowest level that provides confidence. Component tests for component logic, E2E for integration points.

§6 Mobile & Device Emulation

// playwright.config.ts — device projects
projects: [
  {
    name: 'mobile-chrome',
    use: { ...devices['Pixel 7'] },
  },
  {
    name: 'mobile-safari',
    use: { ...devices['iPhone 14 Pro'] },
  },
  {
    name: 'tablet',
    use: { ...devices['iPad Pro 11'] },
  },
],

// Custom viewport for responsive breakpoints
test.use({ viewport: { width: 375, height: 812 } });

test('mobile navigation collapses to hamburger', async ({ page }) => {
  await page.goto('/');
  await expect(page.getByRole('navigation')).not.toBeVisible();
  await page.getByRole('button', { name: 'Menu' }).click();
  await expect(page.getByRole('navigation')).toBeVisible();
});

Touch & Gesture Testing

// Swipe gestures
await page.getByTestId('carousel').swipe('left');
await page.getByTestId('carousel').tap({ position: { x: 100, y: 50 } });

// Pinch zoom
await page.getByTestId('map').pinch({ scale: 2.0 });

// Geolocation mocking
await page.context().grantPermissions(['geolocation']);
await page.context().setGeolocation({ latitude: 48.8566, longitude: 2.3522 });

§7 Debugging & Flaky Tests

Debugging Checklist (Follow in Order)

  1. Run with --debug: npx playwright test --debug — step through visually
  2. Add page.pause(): Insert await page.pause() before failing line
  3. Check trace: npx playwright test --trace on then npx playwright show-trace
  4. Review video: Enable video: 'on-first-retry' in config
  5. Isolate the test: Run with .only to check for test interaction
  6. Check for race conditions: Missing await, missing waitFor, incorrect waitFor state
  7. Verify locators: Use --ui mode to inspect selectors live

Flaky Test Detection Pattern

// Detect flakes by running repeatedly
test.describe('Flaky detection', () => {
  test.describe.configure({ retries: 0 }); // Disable retries to expose flakes

  test('suspicious test', async ({ page }) => {
    // ... test body ...
  });
});

// CLI: npx playwright test --repeat-each=10 suspicious.spec.ts

Common Flakiness Causes & Fixes

CauseSymptomFix
No auto-waitTimeout waiting for elementUse auto-waiting locators (getByRole, etc.)
Hard page.waitForTimeout()Unreliable timingReplace with waitForSelector, waitForResponse
Shared state between testsTests pass/fail based on orderUse fixtures, reset state in beforeEach
Network-dependentPasses locally, fails in CIMock API responses with page.route()
Animation interferenceElements not interactableawait element.scrollIntoViewIfNeeded()
Timezone-sensitiveDate assertions fail at certain timesMock Date or use fixed timezone

Auto-Healing Test Pattern

test('resilient form submission with retry', async ({ page }) => {
  await page.goto('/contact');

  // Auto-heal: if submit is temporarily disabled, wait for it
  const submit = page.getByRole('button', { name: 'Send' });
  await expect(submit).toBeEnabled({ timeout: 10000 });

  await page.getByLabel('Name').fill('Test User');
  await page.getByLabel('Email').fill('[email protected]');
  await submit.click();

  // Wait for either success or retry-able error
  await expect(
    page.getByText(/message sent|try again/i)
  ).toBeVisible({ timeout: 15000 });
});

§8 CI/CD Configuration

GitHub Actions — Optimal Configuration

# .github/workflows/playwright.yml
name: Playwright E2E Tests
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  test:
    timeout-minutes: 30
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        shardIndex: [1, 2, 3, 4]  # 4-way sharding
        shardTotal: [4]
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'npm'
      - run: npm ci
      - run: npx playwright install --with-deps chromium
      - run: npx playwright test --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: playwright-report-${{ matrix.shardIndex }}
          path: playwright-report/
          retention-days: 7

Parallelization & Sharding

// playwright.config.ts — CI-optimized
export default defineConfig({
  workers: process.env.CI ? 4 : undefined,
  retries: process.env.CI ? 2 : 0,
  reporter: [
    ['html', { open: 'never' }],
    ['json', { outputFile: 'test-results/results.json' }],
    ['github'],  // GitHub Actions annotations
  ],
  use: {
    screenshot: 'only-on-failure',
    video: 'on-first-retry',
    trace: 'on-first-retry',
    actionTimeout: 15000,
  },
});

CI Best Practices

  • Shard tests across multiple runners for large suites (>100 tests)
  • Cache Playwright browsers between runs for speed
  • Use fail-fast: false to see all failures, not just the first
  • Set timeout-minutes to catch hung test suites
  • Store reports as artifacts for post-run debugging
  • Run full suite on main, smoke tests on PRs
  • Use retries in CI but not locally (to detect flakes early)

§9 Accessibility Testing

// integrate axe-core for automated a11y checks
import { test, expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';

test('homepage has no critical a11y violations', async ({ page }) => {
  await page.goto('/');

  const results = await new AxeBuilder({ page })
    .withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa'])
    .exclude('[data-testid="third-party-widget"]')  // Skip third-party
    .analyze();

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

// Test keyboard navigation
test('full keyboard navigation through checkout', async ({ page }) => {
  await page.goto('/checkout');
  await page.keyboard.press('Tab');
  await expect(page.getByLabel('Name')).toBeFocused();
  await page.keyboard.press('Tab');
  await expect(page.getByLabel('Email')).toBeFocused();
  // ... continue through entire flow
});

§10 Performance Testing

import { test } from '@playwright/test';
import { playAudit } from 'playwright-lighthouse';
import playwright from 'playwright';

test('homepage meets Lighthouse performance thresholds', async () => {
  const browser = await playwright.chromium.launch();
  const page = await browser.newPage();
  await page.goto('http://localhost:3000/');

  await playAudit({
    page,
    thresholds: {
      performance: 90,
      accessibility: 95,
      'best-practices': 90,
      seo: 90,
    },
    port: 9222,
  });

  await browser.close();
});

Web Vitals Assertions

test('LCP is under 2.5s', async ({ page }) => {
  await page.goto('/heavy-page');
  const lcp = await page.evaluate(() => {
    return new Promise<number>((resolve) => {
      new PerformanceObserver((list) => {
        const entries = list.getEntries();
        resolve(entries[entries.length - 1].startTime);
      }).observe({ type: 'largest-contentful-paint', buffered: true });
    });
  });
  expect(lcp).toBeLessThan(2500);
});

§11 Internationalization (i18n) Testing

test('supports RTL layout for Arabic', async ({ page }) => {
  // Override browser locale
  await page.context().route('**/*', (route) => {
    const headers = { ...route.request().headers(), 'Accept-Language': 'ar' };
    route.continue({ headers });
  });

  await page.goto('/');

  // Verify RTL direction
  const html = page.locator('html');
  await expect(html).toHaveAttribute('dir', 'rtl');

  // Verify content is translated
  await expect(page.getByText('مرحباً')).toBeVisible();
});

§12 Security Testing

test('XSS payload is sanitized in search', async ({ page }) => {
  await page.goto('/search');
  const xssPayload = '<img src=x onerror=alert(1)>';

  await page.getByRole('searchbox').fill(xssPayload);
  await page.getByRole('button', { name: 'Search' }).click();

  // Verify the payload is rendered as text, not executed
  const results = page.getByTestId('search-results');
  await expect(results).toContainText(xssPayload);
  // Alert should NOT have appeared (would fail if XSS succeeds)
});

test('CSP headers are present', async ({ page }) => {
  const response = await page.goto('/');
  const csp = response?.headers()['content-security-policy'];
  expect(csp).toBeDefined();
  expect(csp).toContain("default-src 'self'");
});

test('CSRF token present on forms', async ({ page }) => {
  await page.goto('/profile/edit');
  const csrfInput = page.locator('input[name="csrf_token"], input[name="_csrf"]');
  await expect(csrfInput).toBeAttached();
  const token = await csrfInput.inputValue();
  expect(token.length).toBeGreaterThan(32);
});

test('Auth redirects work correctly', async ({ page }) => {
  await page.goto('/admin/dashboard');
  // Should redirect to login if unauthenticated
  await expect(page).toHaveURL(/\/login/);
});

§13 WebSocket & Real-Time Testing

test('receives real-time order update via WebSocket', async ({ page }) => {
  const wsPromise = page.waitForEvent('websocket');
  await page.goto('/orders/live');

  const ws = await wsPromise;
  const messagePromise = ws.waitForEvent('framereceived');

  // Trigger an update from another source
  await page.evaluate(() => {
    fetch('/api/orders/123/status', {
      method: 'PATCH',
      body: JSON.stringify({ status: 'shipped' }),
    });
  });

  const message = await messagePromise;
  const data = JSON.parse(message.payload as string);
  expect(data.status).toBe('shipped');
});

§14 Electron & Browser Extension Testing

Electron Testing

import { _electron as electron } from '@playwright/test';

test('Electron app launches and shows main window', async () => {
  const app = await electron.launch({
    args: ['main.js'],
  });

  const window = await app.firstWindow();
  await expect(window.getByText('My App')).toBeVisible();

  await app.close();
});

Extension Testing

test('browser extension icon appears', async ({ page, context }) => {
  // Load extension via background page
  const background = await context.newPage();
  await background.goto(`chrome-extension://${EXTENSION_ID}/background.html`);
  // Verify extension functionality
  await page.goto('https://example.com');
  // ... verify extension modifies page as expected
});

Reference Files

This skill includes detailed reference documents loaded on-demand:

  • references/locator-strategies.md — Complete locator priority guide with anti-patterns
  • references/ci-cd-patterns.md — CI/CD configurations for GitHub Actions, GitLab CI, CircleCI, Bitbucket
  • references/testing-types-matrix.md — When to use E2E vs component vs API vs visual vs a11y testing

Scripts

  • scripts/validate-playwright-setup.sh — Validates Playwright installation and browser availability
  • scripts/generate-auth-profile.ts — Generates and saves authentication profiles for test use
  • scripts/flake-detector.sh — Runs a test suite repeatedly to detect intermittent failures

Trigger Guidance

This skill activates when the user or agent is:

  • Writing, reviewing, or refactoring Playwright test files
  • Debugging test failures or investigating flaky tests
  • Setting up Playwright in a new project or CI pipeline
  • Asking about browser automation testing patterns
  • Migrating from Cypress, Selenium, or Puppeteer to Playwright
  • Implementing Page Object Model patterns
  • Configuring test parallelization and sharding
  • Setting up visual regression or component testing with Playwright
  • Testing mobile-responsive layouts or device emulation
  • Performing accessibility or performance audits via Playwright
  • Testing Electron applications or browser extensions
  • Writing security tests for web applications

Gives 0 of the 12 instructions most auth identity skills give

Counted across 409 of the 410 authors here whose files we hold, read 2026-08-06

  • hash passwords with bcrypt or argon2in 53 of 409, across 43 files
  • use parameterized queriesin 47 of 409, across 39 files
  • load SECRET_KEY from environment variablesin 23 of 409, across 14 files
  • validate all input server-sidein 19 of 409, across 11 files
  • refresh access tokens before expiryin 17 of 409, across 9 files
  • store tokens in httponly cookiesin 17 of 409, across 16 files
  • store refresh tokens securelyin 16 of 409, across 6 files
  • validate webhook signatures before processingin 15 of 409, across 5 files
  • sanitize user inputsin 15 of 409, across 9 files
  • implement rate limiting on auth endpointsin 14 of 409, across 9 files
  • encrypt sensitive data at restin 13 of 409, across 10 files
  • validate uploaded file extensions and sizesin 12 of 409, across 5 files

Said here and by no other author read

  • assert one thing per test
  • test user-visible behavior
  • use fixtures for shared setup
  • group related tests with test.describe
  • prefer parallel test execution
  • use role-based locators first

Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.