Skip to main content

Overview

Playwright provides powerful visual comparison capabilities to detect visual regressions. Compare screenshots pixel-by-pixel to ensure your UI remains consistent across changes.

Screenshot Assertions

The toHaveScreenshot() assertion compares screenshots and highlights differences.
import { test, expect } from '@playwright/test';

test('visual regression test', async ({ page }) => {
  await page.goto('https://example.com');
  
  // First run will generate the baseline screenshot
  // Subsequent runs will compare against the baseline
  await expect(page).toHaveScreenshot();
});

Screenshot Options

Customize screenshot behavior with various options.
import { test, expect } from '@playwright/test';

test('capture full page', async ({ page }) => {
  await page.goto('https://example.com');
  
  await expect(page).toHaveScreenshot('full-page.png', {
    fullPage: true,
  });
});

Comparison Thresholds

Control the sensitivity of visual comparisons.
import { test, expect } from '@playwright/test';

test('allow minor differences', async ({ page }) => {
  await page.goto('https://example.com');
  
  await expect(page).toHaveScreenshot('homepage.png', {
    // Allow up to 100 pixels to differ
    maxDiffPixels: 100,
  });
});

Animation Handling

Handle animations and transitions in screenshots.
import { test, expect } from '@playwright/test';

test('screenshot without animations', async ({ page }) => {
  await page.goto('https://example.com');
  
  await expect(page).toHaveScreenshot('static-page.png', {
    animations: 'disabled',
  });
});

Multi-Page Screenshots

Compare screenshots across different page states.
Page State Comparisons
import { test, expect } from '@playwright/test';

test('compare different states', async ({ page }) => {
  await page.goto('https://example.com');
  
  // Initial state
  await expect(page).toHaveScreenshot('state-initial.png');
  
  // Click button to change state
  await page.click('button.toggle');
  await expect(page).toHaveScreenshot('state-toggled.png');
  
  // Hover state
  await page.hover('.card');
  await expect(page).toHaveScreenshot('state-hover.png');
});

Viewport Variations

Test visual consistency across different viewport sizes.
import { test, expect, devices } from '@playwright/test';

const viewports = [
  { name: 'mobile', ...devices['iPhone 13'] },
  { name: 'tablet', width: 768, height: 1024 },
  { name: 'desktop', width: 1920, height: 1080 },
];

for (const viewport of viewports) {
  test(`visual test ${viewport.name}`, async ({ browser }) => {
    const context = await browser.newContext({
      viewport: { 
        width: viewport.width, 
        height: viewport.height 
      },
    });
    const page = await context.newPage();
    
    await page.goto('https://example.com');
    await expect(page).toHaveScreenshot(`homepage-${viewport.name}.png`);
    
    await context.close();
  });
}

Dark Mode Testing

Compare screenshots in both light and dark themes.
Theme Comparisons
import { test, expect } from '@playwright/test';

test('compare light and dark themes', async ({ browser }) => {
  // Light mode
  const lightContext = await browser.newContext({
    colorScheme: 'light',
  });
  const lightPage = await lightContext.newPage();
  await lightPage.goto('https://example.com');
  await expect(lightPage).toHaveScreenshot('light-theme.png');
  await lightContext.close();
  
  // Dark mode
  const darkContext = await browser.newContext({
    colorScheme: 'dark',
  });
  const darkPage = await darkContext.newPage();
  await darkPage.goto('https://example.com');
  await expect(darkPage).toHaveScreenshot('dark-theme.png');
  await darkContext.close();
});

Update Snapshots

Update baseline screenshots when changes are intentional.
# Update all snapshots
pnpx playwright test --update-snapshots

Cross-Browser Screenshots

Ensure visual consistency across different browsers.
playwright.config.ts
import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  projects: [
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] },
    },
    {
      name: 'firefox',
      use: { ...devices['Desktop Firefox'] },
    },
    {
      name: 'webkit',
      use: { ...devices['Desktop Safari'] },
    },
  ],
  
  // Each project will have its own set of snapshots
});

Diff Output

Playwright generates diff images when screenshots don’t match.
Understanding Diff Images
// When a test fails, Playwright creates:
// 1. {name}-actual.png   - Current screenshot
// 2. {name}-expected.png - Baseline screenshot  
// 3. {name}-diff.png     - Difference highlighted

// These are saved in:
// test-results/{test-name}/

Manual Screenshot Capture

Capture screenshots programmatically for custom comparisons.
import { test } from '@playwright/test';
import path from 'path';

test('save custom screenshot', async ({ page }) => {
  await page.goto('https://example.com');
  
  await page.screenshot({ 
    path: path.join('screenshots', 'custom.png'),
    fullPage: true,
  });
});

Configuration

Configure screenshot defaults globally.
playwright.config.ts
import { defineConfig } from '@playwright/test';

export default defineConfig({
  expect: {
    toHaveScreenshot: {
      // Default settings for all screenshot assertions
      maxDiffPixels: 100,
      threshold: 0.2,
      animations: 'disabled',
    },
  },
  
  // Where to store snapshots
  snapshotDir: './visual-snapshots',
  
  // Snapshot path template
  snapshotPathTemplate: '{snapshotDir}/{testFileDir}/{testFileName}-snapshots/{arg}{-projectName}{ext}',
});

Best Practices

Mask Dynamic Content

Always mask timestamps, user-specific data, and ads that change frequently.

Wait for Stability

Ensure page is fully loaded and stable before taking screenshots. Wait for network idle.

Appropriate Thresholds

Set reasonable thresholds - too strict causes false positives, too loose misses issues.

Organized Snapshots

Use descriptive names and organize snapshots by feature or component.
Visual tests can be flaky due to font rendering differences across platforms. Consider running them in Docker or CI with consistent environments.

Screenshots

Learn more about screenshot capabilities

Visual Debugging

Debug visual test failures effectively

Build docs developers (and LLMs) love