Back to blog
TestingCI/CDQuality

Visual Regression Testing: How to Compare Screenshots Across Deployments

Catch UI bugs before users do. Learn how to set up automated visual regression testing using screenshot comparison in your CI/CD pipeline.

2025-03-189 min read

Visual regression testing catches the bugs that unit tests miss — broken layouts, CSS regressions, overlapping elements, and styling issues that only become visible in the rendered page. By comparing screenshots between deployments, you can detect unintended visual changes before they reach production.

How Visual Regression Testing Works

  1. Baseline capture — Take screenshots of key pages in your known-good state
  2. Comparison capture — Take the same screenshots after code changes
  3. Pixel diff — Compare the two sets and highlight differences
  4. Review — A human reviews the diff to approve or reject changes

Setting Up with a Screenshot API

Step 1: Define Your Critical Pages

const pages = [
  { name: 'home', url: '/' },
  { name: 'pricing', url: '/pricing' },
  { name: 'docs', url: '/docs' },
  { name: 'dashboard-empty', url: '/dashboard' },
  { name: 'blog-listing', url: '/blog' },
];

Step 2: Capture Screenshots in CI

async function captureBaselines(baseUrl: string) {
  for (const page of pages) {
    const url = encodeURIComponent(baseUrl + page.url);
    const res = await fetch(
      `https://pxshot.dev/api/capture?url=${url}&width=1280&height=800&format=png&api_key=${API_KEY}`
    );
    const buffer = await res.arrayBuffer();
    await fs.writeFile(`screenshots/${page.name}.png`, Buffer.from(buffer));
  }
}

Step 3: Compare with Baselines

import pixelmatch from 'pixelmatch';
import { PNG } from 'pngjs';

function compareScreenshots(baseline: Buffer, current: Buffer): number {
  const img1 = PNG.sync.read(baseline);
  const img2 = PNG.sync.read(current);
  const diff = new PNG({ width: img1.width, height: img1.height });
  const mismatchedPixels = pixelmatch(
    img1.data, img2.data, diff.data,
    img1.width, img1.height, { threshold: 0.1 }
  );
  return mismatchedPixels / (img1.width * img1.height);
}

Step 4: Fail the Build on Unexpected Changes

const diffPercentage = compareScreenshots(baseline, current);
if (diffPercentage > 0.01) {
  console.error(`Visual regression detected: ${(diffPercentage * 100).toFixed(2)}% change`);
  process.exit(1);
}

Best Practices

  • Consistent viewport — Always use the same width and height
  • Disable animations — Inject CSS to turn off transitions during capture
  • Mock dynamic content — Replace dates and user-specific data with static content
  • Multiple breakpoints — Test mobile (375px), tablet (768px), and desktop (1280px)
  • Threshold tuning — Anti-aliasing differences are normal; set threshold to ~0.1

PxShot's API makes it easy to capture consistent screenshots across environments. See the API docs for all available parameters.