ScreenshotNeo

BlogHow-to

How to Compare Website Screenshots and Highlight Changed Pixels

Build a reliable screenshot baseline workflow, compare pixels with Playwright, inspect visual diffs, and control noisy changes.

By the ScreenshotNeo team4 October 20269 min read

To compare website screenshots and highlight changed pixels, capture a page in a controlled browser environment, save an approved screenshot as the baseline, and compare later captures against it. Playwright Test can make this a repeatable assertion with toHaveScreenshot(); its visual comparison reports differences and supports tolerance settings. For visual review, inspect the diff as an overlay or side by side, then approve a new baseline only when the change is intentional.

This guide uses Playwright Test for a local, repeatable workflow. It covers baseline setup, full-page and element comparisons, tolerance, dynamic content, troubleshooting, and other ways to inspect differences.

1. How screenshot comparison works

A visual regression check compares two images of the same page state:

  1. Baseline: the reference screenshot you reviewed and accepted.
  2. Actual: a new screenshot captured by the test.
  3. Diff: the pixels or regions that differ, subject to the comparison tolerance.

A baseline is an expectation, not automatically a correct design. When a screenshot test fails, inspect the actual and diff images before deciding whether the code regressed or the design intentionally changed. Playwright creates a reference screenshot on the first toHaveScreenshot() run; later runs compare against it. Updating snapshots replaces that expectation, so treat the update as a review decision. Playwright screenshot assertions and snapshot workflow.

Screenshot comparison is useful for layout, color, typography, spacing, and rendering changes. It does not by itself establish that text, interaction, semantics, or accessibility are correct. Keep behavior and accessibility checks alongside visual assertions.

2. Set up a repeatable Playwright baseline

Install Playwright Test and its browser binaries in your project. The commands below are the standard setup for a new project; use the equivalent commands for your package manager if needed.

npm init playwright@latest

Create a test such as tests/homepage.visual.spec.ts. Replace the example origin with the URL of your running application.

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

test('homepage visual baseline', async ({ page }) => {
  await page.setViewportSize({ width: 1280, height: 800 });
  await page.goto('http://127.0.0.1:3000/', { waitUntil: 'networkidle' });
  await page.evaluate(() => document.fonts.ready);
  await expect(page).toHaveScreenshot('homepage.png', {
    fullPage: true,
  });
});

Run the test once to create its expected screenshot, then review and commit the generated snapshot with the test. On subsequent runs, Playwright compares the new capture with the saved reference. Keep the test environment and browser version stable; browser, operating system, hardware, power settings, and headless mode can affect rendering. Playwright’s visual comparison guide.

Control the state before capturing

  • Use deterministic data and a predictable starting state. Seed test data or use a fixture so the screenshot does not depend on changing records.
  • Set the viewport explicitly. Use the same viewport for the baseline and later runs.
  • Wait for the page content and fonts your screenshot depends on. document.fonts.ready helps avoid capturing before web fonts are ready.
  • Disable or neutralize animations, blinking cursors, live clocks, rotating banners, and other volatile regions where they are irrelevant to the assertion.
  • Run baseline generation and comparison in the same browser, operating system or container, and device-pixel ratio (DPR) whenever possible.

Playwright waits for two consecutive screenshots to match before comparing, which helps stabilize captures. It also documents a custom screenshot stylesheet for hiding or filtering volatile content. These measures reduce noise; they do not make genuinely changing page data deterministic. Playwright visual comparisons and Page assertion options.

3. Compare a full page or one element

Use fullPage: true when the page’s content below the fold matters. For a focused component check, locate the element and call the locator’s screenshot assertion instead:

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

test('pricing card visual baseline', async ({ page }) => {
  await page.setViewportSize({ width: 1280, height: 800 });
  await page.goto('http://127.0.0.1:3000/pricing');
  await page.evaluate(() => document.fonts.ready);
  const card = page.locator('[data-testid="pricing-card"]');
  await expect(card).toHaveScreenshot('pricing-card.png');
});

Choose the smallest screenshot that answers the test’s question. A full-page capture can reveal page-wide shifts, while an element capture can make a component’s changes easier to diagnose. Keep the selector stable and specific; a selector that matches the wrong or multiple elements can make the test fail or capture an unintended region.

4. Tune pixel tolerance deliberately

Rendering can vary slightly even when the page is functionally unchanged. Playwright provides threshold for the perceived color difference allowed per pixel, plus maxDiffPixels and maxDiffPixelRatio to limit the total changed pixels. The documented default threshold is 0.2; the maximum-difference limits are unset by default in the API reference. Playwright PageAssertions API.

await expect(page).toHaveScreenshot('homepage.png', {
  fullPage: true,
  threshold: 0.2,
  maxDiffPixels: 80,
});

Use a tolerance only after identifying the source of acceptable rendering variation. A higher threshold can ignore more per-pixel color differences; a maximum-difference allowance can permit a limited number or ratio of changed pixels. Do not increase either setting just to make a failing test pass: small text, border, or spacing regressions can be meaningful. Choose limits that fit the visual risk of the page and review representative diffs.

5. Stabilize dynamic regions and page rendering

First remove the source of noise where practical: freeze test data, stop animations, and avoid capturing transient loading states. If a region is inherently volatile and is outside the purpose of the test, use a screenshot stylesheet to hide it or make its rendering stable. Playwright’s visual comparison guide documents stylePath for applying a custom stylesheet during capture. Playwright visual comparisons.

await expect(page).toHaveScreenshot('dashboard.png', {
  fullPage: true,
  stylePath: './tests/visual-stability.css',
});
/* tests/visual-stability.css */
[data-testid="live-clock"],
[data-testid="rotating-promotion"] {
  visibility: hidden !important;
}

Mask or hide only content that cannot affect the intended assertion. If a chart, timestamp, or rotating promotion is important to the user experience, test its behavior separately instead of concealing it from all checks. Also check that images and other important page resources have loaded before capturing; a missing asset can create a large diff that looks like a layout regression.

6. Inspect the diff and approve changes

When a comparison fails, examine the baseline, actual screenshot, and generated diff. An overlay helps reveal alignment shifts; side-by-side comparison helps judge the overall composition; toggling between versions can make subtle changes easier to spot. Chromatic documents these Diff Inspector views, including overlay, side-by-side, and toggled comparison. Chromatic Diff Inspector.

  1. Identify which route, state, and viewport failed.
  2. Open the actual and diff images beside the baseline.
  3. Decide whether the difference is intended, a real regression, or capture noise.
  4. If intentional, update the baseline and review the resulting snapshot change before committing it.
  5. If unexpected, fix the page or stabilize the test, then rerun the comparison.

To intentionally refresh Playwright snapshots, use its update option:

npx playwright test --update-snapshots

Review the changed image files in version control just as you would review source changes. Do not update snapshots automatically after every failure; that can turn a regression into the new expected output.

7. Other ways to compare screenshot changes

Approach What it offers Consider it when
Playwright Test Screenshot assertions in the test runner, reference images, pixel-based comparison, configurable tolerance, and screenshot styling. You want local test integration and control of the browser capture environment.
Chromatic A hosted snapshot workflow, Playwright integration, and a Diff Inspector with overlay, side-by-side, and toggled views. You want a browser-based interface for visual review.

The useful comparison axes supported by these tools’ documentation are test-runner integration, baseline handling, environment control, tolerance settings, and review presentation. The research available for this guide does not establish equivalent pricing or a complete feature matrix, so choose based on your workflow and verify current product documentation for the details you need. Chromatic with Playwright.

8. Or skip the browser setup

If you need a clean screenshot artifact without installing and maintaining a browser capture setup, ScreenshotNeo is a website screenshot API and MCP server. Its API returns an image or PDF from one GET request. A screenshot API creates the captures; you can then compare the returned files with your chosen visual-diff workflow.

See the ScreenshotNeo API documentation for the request options. This cURL example saves a WebP capture:

curl -G "https://api.screenshotneo.com/v1/shot" \
  -d access_key=YOUR_API_KEY \
  --data-urlencode url=https://stripe.com \
  -o shot.webp

Python:

import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
open("shot.webp", "wb").write(r.content)

Node.js:

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await Bun.write('shot.webp', new Uint8Array(await res.arrayBuffer()));

ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before the shot. Bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots. Sign up for free and get 1,000 screenshots a month with no card.

9. Troubleshooting screenshot diffs

Symptom Likely cause Fix
The first run fails because no snapshot exists. The test has not created its baseline yet. Run the test to generate the expected screenshot, inspect it, and commit it as the reviewed baseline.
Diffs appear on every machine or CI run. Different browser or OS versions, viewport, device-pixel ratio, fonts, rendering settings, or headless environment. Use a consistent browser and container, set viewport explicitly, and keep capture density consistent. Chromatic notes that DPR differences can flag snapshots even when the UI is otherwise the same. Chromatic configuration.
Text looks shifted or has different wrapping. A font has not loaded, the viewport differs, or font rendering differs by environment. Wait for fonts, verify the viewport and DPR, and compare in the same environment used to generate the baseline.
Only timestamps, ads, or rotating content differ. The page includes volatile content unrelated to the assertion. Use deterministic test data or hide the specific region with a screenshot stylesheet. Keep separate checks for content whose behavior matters.
A large area is blank or images are missing. Capture happened before resources loaded, an image request failed, or the page rendered a transient loading state. Wait for the required content, check the browser console and network behavior, and capture after the intended page state is ready.
A tiny visual change passes unexpectedly. The per-pixel threshold or total-difference allowance is too permissive. Lower the tolerance or maximum-difference allowance, then review the baseline and actual captures.
The screenshot dimensions unexpectedly differ. Viewport, full-page content height, page state, or DPR changed. Set a fixed viewport, ensure the same content is present, and use consistent device-pixel ratio and environment.
The test captures the wrong component. The locator is too broad, unstable, or matches an unexpected element. Use a stable, specific selector and confirm the target exists in the intended state before asserting its screenshot.

10. Performance, reliability, and maintenance

  • Limit the capture matrix. Start with important routes, states, and viewports; add combinations where responsive behavior or user impact justifies them.
  • Prefer focused assertions. An element screenshot can make a component check easier to review, while a full-page screenshot catches page-wide changes. Use both where each covers a distinct risk.
  • Keep test inputs stable. Deterministic fixtures reduce noisy baseline updates and make failures easier to reproduce.
  • Pin the capture environment. Browser and operating-system consistency reduces false diffs, and matching DPR is important for stable snapshots.
  • Review updates as code. Snapshot changes should have an explanation and reviewer approval when the expected visual output changes.
  • Keep tolerance meaningful. Broad tolerance may reduce noisy failures but can also hide small defects. Tune it to the page and test purpose.

Screenshot comparison costs include the time to capture each selected route and viewport, store and review baselines, and investigate diffs. The cited documentation does not provide a comparable performance benchmark or pricing basis for Playwright and Chromatic, so measure your own suite and consult current vendor pricing before making a cost comparison.

11. Frequently asked questions

Does Playwright create a diff image?

Playwright’s screenshot assertions compare against a reference and produce visual comparison artifacts when a test fails. Inspect the expected, actual, and diff output produced by your test run.

Should every pixel difference fail the test?

Not necessarily. Rendering variation can create small differences, so use the documented tolerance controls carefully. Any allowance should remain small enough to catch changes that matter to the page.

Can screenshots from different DPR settings be compared?

They can be compared as images, but they may differ because the capture density changes the resulting pixels. Keep DPR consistent between baseline and actual captures for dependable visual regression checks.

When should I update a baseline?

Update it after confirming that the new appearance is intentional and reviewing the resulting image change. A snapshot update records a new expectation; it does not explain why the page changed.