playwright-visual-regression

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

16 stars

Best use case

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

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

Teams using playwright-visual-regression 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-visual-regression/SKILL.md --create-dirs "https://raw.githubusercontent.com/diegosouzapw/awesome-omni-skill/main/skills/testing-security/playwright-visual-regression/SKILL.md"

Manual Installation

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

How playwright-visual-regression Compares

Feature / Agentplaywright-visual-regressionStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

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

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 Visual Regression Testing

This skill documents patterns for E2E testing with Playwright, including visual regression, cross-browser testing, and accessibility automation for OSCAR Export Analyzer.

## Setup and Configuration

### Installation

```bash
npm install -D @playwright/test
npx playwright install  # Install browsers
```

### Configuration (`playwright.config.js`)

```javascript
import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  testDir: './tests/e2e',
  fullyParallel: true,
  forbidOnly: !!process.env.CI,
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 1 : undefined,
  reporter: 'html',

  use: {
    baseURL: 'http://localhost:5173',
    trace: 'on-first-retry',
    screenshot: 'only-on-failure',
  },

  projects: [
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] },
    },
    {
      name: 'firefox',
      use: { ...devices['Desktop Firefox'] },
    },
    {
      name: 'webkit',
      use: { ...devices['Desktop Safari'] },
    },
  ],

  webServer: {
    command: 'npm run dev',
    url: 'http://localhost:5173',
    reuseExistingServer: !process.env.CI,
  },
});
```

## Basic E2E Test

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

test.describe('CSV Upload Flow', () => {
  test('uploads CSV and displays charts', async ({ page }) => {
    await page.goto('/');

    // Upload CSV file
    const fileInput = page.locator('input[type="file"]');
    await fileInput.setInputFiles('tests/fixtures/sample-cpap-data.csv');

    // Wait for parsing to complete
    await expect(page.locator('text=Parsing complete')).toBeVisible();

    // Verify charts are rendered
    await expect(page.locator('[data-testid="usage-chart"]')).toBeVisible();
    await expect(page.locator('[data-testid="ahi-chart"]')).toBeVisible();
  });
});
```

## Visual Regression Testing

### Capture Visual Baseline

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

test.describe('Visual Regression', () => {
  test('chart layout matches baseline', async ({ page }) => {
    // Load test data
    await page.goto('/');
    await page
      .locator('input[type="file"]')
      .setInputFiles('tests/fixtures/test-data.csv');
    await page.waitForLoadState('networkidle');

    // Take screenshot and compare with baseline
    await expect(page).toHaveScreenshot('usage-chart.png', {
      maxDiffPixels: 100, // Allow small differences
    });
  });

  test('dark mode appearance', async ({ page }) => {
    await page.goto('/');

    // Enable dark mode
    await page.locator('[aria-label="Toggle dark mode"]').click();

    // Wait for theme transition
    await page.waitForTimeout(300);

    // Compare with dark mode baseline
    await expect(page).toHaveScreenshot('dark-mode.png');
  });
});
```

### Update Baselines

```bash
# Update all baselines
npx playwright test --update-snapshots

# Update specific test baselines
npx playwright test visual-regression --update-snapshots
```

### Baseline Management

- **Store baselines in git**: `tests/e2e/*.spec.js-snapshots/`
- **Review visual diffs**: Check Playwright HTML report
- **Approve changes**: Commit updated baselines after verifying correctness

## Responsive Testing

```javascript
test.describe('Responsive Design', () => {
  test('mobile layout', async ({ page }) => {
    // Set mobile viewport
    await page.setViewportSize({ width: 375, height: 667 });
    await page.goto('/');

    // Verify mobile layout
    await expect(page.locator('[data-testid="mobile-menu"]')).toBeVisible();
    await expect(page).toHaveScreenshot('mobile-layout.png');
  });

  test('tablet layout', async ({ page }) => {
    await page.setViewportSize({ width: 768, height: 1024 });
    await page.goto('/');

    await expect(page).toHaveScreenshot('tablet-layout.png');
  });

  test('desktop layout', async ({ page }) => {
    await page.setViewportSize({ width: 1920, height: 1080 });
    await page.goto('/');

    await expect(page).toHaveScreenshot('desktop-layout.png');
  });
});
```

## Chart Interaction Testing

```javascript
test.describe('Chart Interactions', () => {
  test('zoom chart with mouse wheel', async ({ page }) => {
    await page.goto('/');
    await loadTestData(page);

    const chart = page.locator('[data-testid="ahi-chart"]');

    // Get initial axis range
    const initialRange = await page.evaluate(() => {
      const plotly = window.Plotly;
      const chartDiv = document.querySelector('[data-testid="ahi-chart"]');
      return chartDiv.layout.xaxis.range;
    });

    // Zoom in with mouse wheel
    await chart.hover();
    await page.mouse.wheel(0, -100);

    // Wait for zoom animation
    await page.waitForTimeout(500);

    // Verify axis range changed
    const newRange = await page.evaluate(() => {
      const chartDiv = document.querySelector('[data-testid="ahi-chart"]');
      return chartDiv.layout.xaxis.range;
    });

    expect(newRange).not.toEqual(initialRange);
  });

  test('toggle legend series', async ({ page }) => {
    await page.goto('/');
    await loadTestData(page);

    // Click legend item to hide series
    await page.locator('.legend .traces:has-text("AHI")').click();

    // Verify series hidden
    const isVisible = await page.evaluate(() => {
      const trace = document.querySelector('.trace.scatter');
      return trace.style.opacity === '0.5';
    });

    expect(isVisible).toBe(true);
  });
});
```

## Accessibility Testing

### Built-in Accessibility Checks

```javascript
import { test, expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';

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

    // Run axe accessibility scan
    const accessibilityScanResults = await new AxeBuilder({ page }).analyze();

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

### Keyboard Navigation

```javascript
test('keyboard navigation works', async ({ page }) => {
  await page.goto('/');

  // Tab to file input
  await page.keyboard.press('Tab');
  await expect(page.locator('input[type="file"]')).toBeFocused();

  // Tab to upload button
  await page.keyboard.press('Tab');
  await expect(page.locator('button:has-text("Upload")')).toBeFocused();

  // Activate with Enter
  await page.keyboard.press('Enter');
});
```

### Screen Reader Testing

```javascript
test('has proper ARIA labels', async ({ page }) => {
  await page.goto('/');
  await loadTestData(page);

  // Check chart has accessible name
  const chartLabel = await page
    .locator('[data-testid="ahi-chart"]')
    .getAttribute('aria-label');

  expect(chartLabel).toContain('AHI Trends');

  // Check form labels
  await expect(page.locator('label[for="start-date"]')).toHaveText(
    'Start Date',
  );
});
```

## Print Layout Testing

```javascript
test.describe('Print Layout', () => {
  test('print stylesheet applied', async ({ page }) => {
    await page.goto('/');
    await loadTestData(page);

    // Emulate print media
    await page.emulateMedia({ media: 'print' });

    // Verify print-specific styles
    await expect(page).toHaveScreenshot('print-layout.png');

    // Check elements hidden in print
    const navVisible = await page.locator('nav').isVisible();
    expect(navVisible).toBe(false);
  });

  test('PDF export quality', async ({ page }) => {
    await page.goto('/');
    await loadTestData(page);

    // Trigger print dialog
    await page.locator('button:has-text("Export PDF")').click();

    // Generate PDF
    const pdf = await page.pdf({
      format: 'A4',
      printBackground: true,
    });

    // Verify PDF generated
    expect(pdf.length).toBeGreaterThan(0);
  });
});
```

## Cross-Browser Testing

```javascript
// Run same test across browsers
test.describe('Cross-Browser Compatibility', () => {
  test('works in all browsers', async ({ page, browserName }) => {
    test.skip(browserName === 'webkit', 'Known webkit issue #123');

    await page.goto('/');
    await loadTestData(page);

    // Verify core functionality works
    await expect(page.locator('[data-testid="usage-chart"]')).toBeVisible();
  });
});
```

## Large File Stress Testing

```javascript
test.describe('Performance', () => {
  test('handles large CSV upload', async ({ page }) => {
    // Increase timeout for large file
    test.setTimeout(60000);

    await page.goto('/');

    // Upload 30MB CSV
    await page
      .locator('input[type="file"]')
      .setInputFiles('tests/fixtures/large-cpap-data.csv');

    // Wait for parsing (with progress updates)
    await expect(page.locator('text=Parsing')).toBeVisible();
    await expect(page.locator('text=Parsing complete')).toBeVisible({
      timeout: 30000,
    });

    // Verify app still responsive
    await page.locator('button:has-text("Filter")').click();
    await expect(page.locator('[data-testid="date-filter"]')).toBeVisible();
  });

  test('memory usage stays reasonable', async ({ page }) => {
    await page.goto('/');
    await loadTestData(page);

    // Measure memory usage
    const metrics = await page.evaluate(() => {
      return (performance as any).memory
        ? {
            usedJSHeapSize: (performance as any).memory.usedJSHeapSize,
            totalJSHeapSize: (performance as any).memory.totalJSHeapSize,
          }
        : null;
    });

    if (metrics) {
      // Ensure memory usage under 200MB
      expect(metrics.usedJSHeapSize).toBeLessThan(200 * 1024 * 1024);
    }
  });
});
```

## Fixture Management

```javascript
// tests/e2e/fixtures/index.ts
import { test as base } from '@playwright/test';

type Fixtures = {
  loadTestData: () => Promise<void>;
};

export const test = base.extend<Fixtures>({
  loadTestData: async ({ page }, use) => {
    const loader = async () => {
      await page.goto('/');
      await page
        .locator('input[type="file"]')
        .setInputFiles('tests/fixtures/sample-cpap-data.csv');
      await page.waitForLoadState('networkidle');
    };

    await use(loader);
  },
});
```

**Usage:**

```javascript
import { test, expect } from './fixtures';

test('uses fixture', async ({ page, loadTestData }) => {
  await loadTestData();

  // Test with data already loaded
  await expect(page.locator('[data-testid="usage-chart"]')).toBeVisible();
});
```

## Resilient Selectors

### Good Selectors (Stable)

```javascript
// ✅ Data test IDs (most stable)
page.locator('[data-testid="usage-chart"]');

// ✅ Accessible roles and names
page.getByRole('button', { name: 'Upload' });
page.getByRole('textbox', { name: 'Start Date' });

// ✅ Text content (user-facing)
page.locator('text=Usage Patterns');
```

### Bad Selectors (Brittle)

```javascript
// ❌ CSS classes (change frequently)
page.locator('.chart-container-wrapper-inner');

// ❌ Deeply nested selectors
page.locator('div > div > div:nth-child(3) > span.label');

// ❌ Positional selectors
page.locator('.chart').nth(2);
```

## Documentation Automation

### Screenshot Generation for README

```javascript
test.describe('Documentation Screenshots', () => {
  test('generate README screenshots', async ({ page }) => {
    await page.goto('/');
    await loadTestData(page);

    // Full page screenshot for README hero
    await page.screenshot({
      path: 'screenshots/app-overview.png',
      fullPage: true,
    });

    // Specific chart screenshots
    await page.locator('[data-testid="usage-chart"]').screenshot({
      path: 'screenshots/usage-chart.png',
    });

    await page.locator('[data-testid="ahi-chart"]').screenshot({
      path: 'screenshots/ahi-chart.png',
    });

    // Dark mode screenshot
    await page.locator('[aria-label="Toggle dark mode"]').click();
    await page.waitForTimeout(300);
    await page.screenshot({
      path: 'screenshots/dark-mode.png',
    });
  });
});
```

## CI Integration

### GitHub Actions

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

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

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: 20
      - name: Install dependencies
        run: npm ci
      - name: Install Playwright Browsers
        run: npx playwright install --with-deps
      - name: Run Playwright tests
        run: npx playwright test
      - uses: actions/upload-artifact@v3
        if: always()
        with:
          name: playwright-report
          path: playwright-report/
          retention-days: 30
```

## Debugging Tests

### Interactive Mode

```bash
# Open Playwright Inspector
npx playwright test --debug

# Run in headed mode (see browser)
npx playwright test --headed

# Run specific test
npx playwright test tests/e2e/upload.spec.js --headed
```

### Trace Viewer

```bash
# Generate trace on failure (configured in playwright.config.js)
# View trace after test failure:
npx playwright show-trace trace.zip
```

### Pause Execution

```javascript
test('debug test', async ({ page }) => {
  await page.goto('/');

  // Pause execution, open inspector
  await page.pause();

  // Continue manually in inspector
});
```

## Resources

- **Playwright docs**: https://playwright.dev/
- **Axe accessibility**: https://github.com/dequelabs/axe-core-npm
- **Test fixtures**: `tests/e2e/fixtures/`
- **Config**: `playwright.config.js`
- **CI workflow**: `.github/workflows/playwright.yml`

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

16
from diegosouzapw/awesome-omni-skill

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

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".

divek-bi-visual-audit

16
from diegosouzapw/awesome-omni-skill

Visual compliance auditing for DiveK brand identity. Use when reviewing UI screens, component libraries, landing pages, design handoff specs, CSS tokens, or visual QA reports for alignment with DiveK color palette, typography, and cinematic-minimal style direction.

azure-resource-manager-playwright-dotnet

16
from diegosouzapw/awesome-omni-skill

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

u0577-observability-regression-sentinel

16
from diegosouzapw/awesome-omni-skill

Operate the "Observability Regression Sentinel" capability in production for workflows. Use when mission execution explicitly requires this capability and outcomes must be reproducible, policy-gated, and handoff-ready.

Analyze Regressions

16
from diegosouzapw/awesome-omni-skill

Grade component health based on regression triage metrics for OpenShift releases

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.