GrabShot Blog

Visual Regression Testing with an API

February 17, 2026 · 7 min read

You ship a CSS change and everything looks fine on the page you edited. But three pages away, a sidebar is now overlapping the main content. A button lost its padding. The footer shifted up by 12 pixels. Nobody notices until a customer screenshots the bug and posts it on Twitter.

Visual regression testing catches these problems automatically by comparing screenshots before and after changes. It's one of those things that feels optional until it saves you from a very visible, very embarrassing bug in production.

How Visual Regression Testing Works

The concept is simple:

  1. Capture a baseline — screenshot your pages in a known-good state
  2. Make changes — deploy your code update
  3. Capture new screenshots — same pages, same viewport
  4. Diff the images — pixel-by-pixel comparison highlights what changed
  5. Review the diff — intentional changes pass, unintended changes get fixed

The challenge is in the details: handling dynamic content (timestamps, ads), dealing with subpixel rendering differences, running this fast enough for CI/CD, and not drowning in false positives.

The DIY Approach

You can build basic visual diffing with Puppeteer and pixelmatch:

const puppeteer = require('puppeteer');
const { PNG } = require('pngjs');
const pixelmatch = require('pixelmatch');

async function visualDiff(url1, url2) {
  const browser = await puppeteer.launch({ headless: 'new' });

  const shot1 = await takeScreenshot(browser, url1);
  const shot2 = await takeScreenshot(browser, url2);

  const img1 = PNG.sync.read(shot1);
  const img2 = PNG.sync.read(shot2);
  const diff = new PNG({ width: img1.width, height: img1.height });

  const numDiffPixels = pixelmatch(
    img1.data, img2.data, diff.data,
    img1.width, img1.height,
    { threshold: 0.1 }
  );

  await browser.close();

  const totalPixels = img1.width * img1.height;
  return {
    diffPercent: ((numDiffPixels / totalPixels) * 100).toFixed(2),
    diffImage: PNG.sync.write(diff)
  };
}

This works for a proof of concept. In production, you'll need to handle different page heights, wait for fonts and images to load, mask dynamic regions, resize images to match, manage baseline storage, and integrate with your CI system. That's a lot of infrastructure for what should be a simple check.

The API Approach: DiffShot

DiffShot handles all the complexity. Send two URLs (or a URL and a baseline image), get back a diff with pixel-level accuracy.

curl -X POST "https://diffshot.grabshot.dev/v1/diff" \
  -H "X-Api-Key: ds_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "url1": "https://your-site.com",
    "url2": "https://staging.your-site.com"
  }'

Response includes:

CI/CD Integration

# In your deployment pipeline
- name: Visual regression check
  run: |
    # Compare production vs staging
    DIFF=$(curl -s -X POST "https://diffshot.grabshot.dev/v1/diff" \
      -H "X-Api-Key: ${{ secrets.DIFFSHOT_KEY }}" \
      -H "Content-Type: application/json" \
      -d "{\"url1\": \"https://your-site.com\", \"url2\": \"$STAGING_URL\"}")

    PERCENT=$(echo "$DIFF" | jq -r '.diffPercent')
    echo "Visual diff: ${PERCENT}%"

    # Fail if more than 5% of pixels changed
    if (( $(echo "$PERCENT > 5" | bc -l) )); then
      echo "Visual regression detected!"
      exit 1
    fi

Monitoring Website Changes

DiffShot isn't just for your own sites. Monitor competitor pages, track design changes on sites you depend on, or watch for unauthorized changes to your production site:

// Daily check: has our production site changed unexpectedly?
const baseline = await fetch(
  'https://diffshot.grabshot.dev/v1/screenshot?url=https://your-site.com',
  { headers: { 'X-Api-Key': 'ds_key' } }
);

// Store baseline, compare tomorrow
// Alert if diff > 0% when no deploy happened

Catch Visual Bugs Before Users Do

Compare any two URLs pixel-by-pixel. 25 free diffs per month.

Try DiffShot Free

When to Use Visual Regression Testing

Every CSS or layout change

CSS is global by nature. A change to a utility class can ripple across dozens of pages. Visual testing catches the ripples.

Dependency updates

Updating a UI library? Font package? Icon set? Run a visual diff before and after to catch unexpected changes.

Content changes

New images, longer text, different translations can all break layouts. Visual testing catches overflow, clipping, and alignment issues that unit tests miss.

Wrapping Up

Visual regression testing is the safety net between "it works on my machine" and "it works in production." The ROI is immediate: one caught bug pays for months of testing. Whether you build your own pipeline or use DiffShot, start testing visually. Your users are already doing it -- they're just not filing bug reports.


More from GrabShot

📧 Developer API Tips

Get practical API tutorials and tools. No spam, unsubscribe anytime.