Cross-Browser Visual Testing Strategies — automated screenshot comparison for catching visual regressions.
Cross-Browser Visual Testing Strategies enables catching unintended visual changes automatically. Instead of manually reviewing UI after every change, screenshot comparison detects pixel-level differences and reports them in CI. This prevents visual regressions from shipping to users undetected.
1npm install --save-dev @playwright/test
2npx playwright install --with-deps chromium
3
4# Generate initial baseline screenshots:
5npx playwright test tests/visual/ --update-snapshots
6
7# Run visual comparison tests:
8npx playwright test tests/visual/
9
10# On failure, screenshots are in test-results/
11# The diff image shows exactly what changed1// playwright.config.ts
2export default defineConfig({
3 expect: {
4 toHaveScreenshot: {
5 maxDiffPixelRatio: 0.02, // 2% pixel difference allowed
6 threshold: 0.2, // per-pixel color tolerance
7 animations: 'disabled', // freeze CSS animations
8 },
9 },
10 snapshotDir: './tests/visual/__screenshots__',
11});1await expect(page).toHaveScreenshot('page.png', {
2 // Mask elements that change between test runs
3 mask: [
4 page.locator('[data-testid="timestamp"]'),
5 page.locator('[data-testid="user-avatar"]'),
6 page.locator('.advertisement'),
7 ],
8 animations: 'disabled',
9});
10
11// Freeze dynamic content before screenshot:
12await page.evaluate(() => {
13 document.querySelectorAll('[data-testid="live-count"]')
14 .forEach(el => el.textContent = '1,234');
15 const style = document.createElement('style');
16 style.textContent = '*, *::before, *::after { transition-duration: 0s !important; }';
17 document.head.appendChild(style);
18});1- name: Run visual tests
2 run: npx playwright test tests/visual/
3 continue-on-error: true
4
5- name: Upload visual test artifacts
6 uses: actions/upload-artifact@v4
7 if: always()
8 with:
9 name: visual-results
10 path: |
11 playwright-report/
12 test-results/
13 retention-days: 30Workflow for baseline updates:
npx playwright test --update-snapshots locally1test('dark mode visual consistency', async ({ page }) => {
2 // Light mode baseline
3 await page.goto('/courses');
4 await expect(page).toHaveScreenshot('courses-light.png');
5
6 // Dark mode — emulate prefers-color-scheme: dark
7 await page.emulateMedia({ colorScheme: 'dark' });
8 await page.reload();
9 await expect(page).toHaveScreenshot('courses-dark.png');
10
11 // Verify dark mode is actually applied
12 const bodyBg = await page.evaluate(() =>
13 getComputedStyle(document.body).backgroundColor
14 );
15 // Dark backgrounds have low luminance
16 const [r, g, b] = bodyBg.match(/\d+/g)?.map(Number) || [];
17 const luminance = 0.299 * r + 0.587 * g + 0.114 * b;
18 expect(luminance).toBeLessThan(128); // dark background
19});1const BREAKPOINTS = [
2 { name: 'mobile', width: 375, height: 812 },
3 { name: 'tablet', width: 768, height: 1024 },
4 { name: 'desktop', width: 1280, height: 800 },
5];
6
7for (const { name, width, height } of BREAKPOINTS) {
8 test(`homepage at ${name}`, async ({ page }) => {
9 await page.setViewportSize({ width, height });
10 await page.goto('/');
11 await expect(page).toHaveScreenshot(`homepage-${name}.png`, {
12 fullPage: true,
13 animations: 'disabled',
14 });
15 });
16}
17
18// Also check for horizontal overflow:
19test('no horizontal scroll on mobile', async ({ page }) => {
20 await page.setViewportSize({ width: 375, height: 812 });
21 await page.goto('/');
22 const hasScroll = await page.evaluate(() =>
23 document.documentElement.scrollWidth > document.documentElement.clientWidth
24 );
25 expect(hasScroll).toBe(false);
26});Production visual regression tests with Playwright covering pages, components, dark mode, and responsive breakpoints.
1// Cross-browser visual testing with Playwright projects
2// playwright.config.ts
3import { defineConfig, devices } from '@playwright/test';
4
5export default defineConfig({
6 projects: [
7 { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
8 { name: 'firefox', use: { ...devices['Desktop Firefox'] } },
9 { name: 'webkit', use: { ...devices['Desktop Safari'] } },
10 { name: 'mobile-chrome', use: { ...devices['Pixel 5'] } },
11 { name: 'mobile-safari', use: { ...devices['iPhone 12'] } },
12 ],
13 // Separate snapshot directories per browser
14 snapshotPathTemplate: '{snapshotDir}/{testFilePath}/{arg}-{projectName}{ext}',
15});
16
17// Browser-specific baselines are stored separately:
18// tests/visual/__screenshots__/homepage.spec.ts/homepage-chromium.png
19// tests/visual/__screenshots__/homepage.spec.ts/homepage-firefox.png
20// tests/visual/__screenshots__/homepage.spec.ts/homepage-webkit.pngThis visual test implementation covers cross-browser visual testing strategies with Playwright's built-in screenshot comparison. Baseline screenshots are stored in __screenshots__ and compared on every test run. The 2% maxDiffPixelRatio allows for minor rendering differences across environments. CI uploads failure artifacts for review, and developers update baselines by running npx playwright test --update-snapshots when changes are intentional.
Continue with the next visual regression topic to build comprehensive screenshot coverage across your entire UI.
Your progress is saved in this browser, on this device only.