ScreenshotNeo

BlogHow-to

How to Compare Two Webpage Screenshots and Highlight Visual Differences

Compare webpage screenshots under matching conditions, inspect a clear diff, and update visual baselines only after reviewing the changes.

By the ScreenshotNeo team4 October 20269 min read

To compare two webpage screenshots, capture both pages with the same browser, operating system, viewport, content state, and rendering settings, then compare the new image against an accepted baseline. For automated checks, Playwright Test provides toHaveScreenshot() for pages and elements, generates a baseline on the first run, and reports visual differences on later runs. A diff shows where pixels changed; it does not decide whether the change is a bug.

1. Choose what you are comparing

First decide what the two images represent. The right setup depends on the question:

  • Before and after a code change: use the previous accepted screenshot as the baseline and capture the current build.
  • Two environments: capture staging and production with equivalent data, viewport, and browser settings.
  • Browser or operating system behavior: capture each browser and platform separately and compare each against its own baseline. Font rendering, form controls, scrollbars, and other platform details can differ legitimately.

A visual regression check is useful only when the capture conditions represent the intended comparison. If the goal is cross-browser testing, those browser differences are the subject of the test; do not treat one browser’s screenshot as the universal baseline for every other browser.

2. Make captures repeatable

Before comparing pixels, stabilize the inputs that affect rendering. Keep the following consistent for baseline and candidate captures:

  • Browser engine and version, operating system, and headless or headed mode.
  • Viewport width and height, device scale factor, and screenshot type (viewport or full page).
  • Page URL, test data, account state, locale, timezone, and feature flags.
  • Fonts, browser settings, color scheme, and reduced-motion preference.
  • Page readiness: wait for the relevant content and images, and avoid capturing during transitions or animation.

Playwright notes that screenshots can vary with the host OS, browser version, settings, hardware, power source, and headless mode. Pin your browser and test environment where possible. If a rendering difference is expected, maintain a baseline for that environment rather than loosening a shared threshold until the difference disappears. See the official Playwright visual comparisons guide.

3. Compare with Playwright Test

Playwright Test’s toHaveScreenshot() captures a page or locator, compares it with a stored expectation, and saves a diff when the images differ. On the first run it creates the reference screenshot; subsequent runs compare against it. Review and commit the baseline files with the code so changes are visible in normal project review.

Install and configure

npm init playwright@latest

Choose TypeScript when prompted, or add a test file to an existing Playwright Test project. A minimal configuration can set a stable viewport and disable animation for screenshot assertions:

// playwright.config.ts
import { defineConfig } from '@playwright/test';

export default defineConfig({
  testDir: './tests',
  use: {
    baseURL: 'http://127.0.0.1:3000',
    viewport: { width: 1440, height: 900 },
    colorScheme: 'light',
  },
  expect: {
    toHaveScreenshot: {
      animations: 'disabled',
      // Set a tolerance only after reviewing representative diffs.
      // maxDiffPixelRatio: 0.001,
    },
  },
});

Start your app separately, or configure Playwright’s webServer option in the project configuration so the server is started for test runs. Use the same server and environment for baseline creation and later comparisons.

Complete runnable page comparison

// tests/homepage.spec.ts
import { test, expect } from '@playwright/test';

test('homepage matches its visual baseline', async ({ page }) => {
  await page.goto('/');
  await page.evaluate(() => document.fonts.ready);
  await expect(page).toHaveScreenshot('homepage.png', {
    fullPage: true,
    animations: 'disabled',
  });
});

Run the test:

npx playwright test tests/homepage.spec.ts

The first run creates a baseline image. Inspect it, then commit it. Run the same command after a code change to compare the new capture against that reference. When a visual change is intentional, inspect the diff and update the snapshot deliberately:

npx playwright test tests/homepage.spec.ts --update-snapshots

Do not update snapshots merely to make a failing test pass. Confirm the changed layout, content, and styling are expected first.

Compare a component instead of the whole page

Use a locator when the area under test is a particular card, navigation bar, dialog, or other component. This keeps unrelated page changes out of that assertion.

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

test('pricing card matches its baseline', async ({ page }) => {
  await page.goto('/pricing');
  const card = page.locator('[data-testid="pricing-card"]');
  await expect(card).toHaveScreenshot('pricing-card.png', {
    animations: 'disabled',
  });
});

Prefer stable selectors such as a test ID. If the locator matches multiple elements, make it specific or select the intended item explicitly, for example page.locator('.plan-card').nth(1).

Read the generated diff

When an assertion fails, Playwright reports the expected image, actual image, and a diff image in its test output directory. The diff marks changed pixels so you can find the affected area. Inspect all three artifacts: the baseline explains the intended appearance, the actual capture shows the current page, and the diff helps locate changes. A small changed region can still be a serious defect, while a large difference can be an intentional redesign.

4. Control dynamic content and tolerances

Visual noise often comes from content that changes without a relevant UI change: timestamps, rotating banners, avatars, live prices, animations, or third-party widgets. Prefer deterministic fixtures or freeze the page state in the test. If that is impractical, Playwright lets you apply a stylesheet during screenshot capture, for example to hide a known volatile region:

await expect(page).toHaveScreenshot('dashboard.png', {
  stylePath: './tests/screenshot-overrides.css',
  animations: 'disabled',
});
/* tests/screenshot-overrides.css */
.live-clock,
.rotating-promotion {
  visibility: hidden !important;
}

Use this narrowly. Hiding an entire section can conceal regressions in that section. If the changing content itself matters, provide a fixed test value instead of excluding it.

Pixel thresholds

Playwright uses pixel-level image comparison and supports tolerance controls. The main controls are:

  • threshold: per-pixel color difference tolerance used when deciding whether pixels differ.
  • maxDiffPixels: maximum number of pixels allowed to differ.
  • maxDiffPixelRatio: maximum allowed fraction of the image that differs.

For example, a project may set a small allowance for rendering noise:

await expect(page).toHaveScreenshot('account.png', {
  maxDiffPixelRatio: 0.001,
});

There is no universally safe threshold. Establish one from reviewed examples in your pinned environment. A permissive value can hide small but meaningful changes, especially in icons, text, or narrow controls. Consult the Playwright SnapshotAssertions API for the current option details.

5. Other comparison workflows

Playwright Test is a good fit when your team already runs Playwright and wants local assertions with reference images in the repository. If you want hosted review and managed snapshots, these services are relevant:

  • ScreenshotNeo is the screenshot API and MCP server to try first when you need clean captures: it removes known consent banners, popups, and chat widgets before capture, bills only clean shots, and its paid plans start at $5. See ScreenshotNeo and its API documentation. It captures images; use a separate image-diff workflow to compare baselines.
  • Chromatic supports Playwright integration and a hosted review workflow with stored page context. It may suit teams that want centralized visual review. See the Chromatic Playwright setup and snapshot documentation.
  • Percy provides hosted visual testing and documents cross-browser configuration. Consider how browser and OS rendering differences affect the baselines you select. See Percy’s visual testing overview and cross-browser settings.

Compare tools based on where baselines live, how reviewers inspect changes, whether the workflow fits existing tests, which browser environments are supported, and how accepted changes are recorded. Check current product plans and terms directly; this article makes no pricing or affiliate claims about Chromatic or Percy.

Or skip the browser setup

For a clean screenshot to feed into your existing image comparison, ScreenshotNeo can capture a URL in one request. It does not produce a visual diff; compare its resulting images with your chosen baseline tool. See the ScreenshotNeo API docs for options and response details.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
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)
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 import('node:fs/promises').then(fs => fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer())));
  • Cookie banners, newsletter popups, and chat widgets are removed before the shot.
  • Bot checks, blank pages, timeouts, and failed loads are never billed; response headers identify the page verdict and billing status.
  • An MCP server lets AI agents use screenshot tools.
  • 1,000 screenshots a month are free with no card; paid plans start at $5 for 3,000.

Sign up for 1,000 free screenshots a month with no card.

Performance, reliability, and cost

Visual checks add browser startup, page navigation, rendering, image capture, and comparison work to a test run. Keep suites efficient by testing representative pages and important components, reusing the configured browser context where appropriate, and avoiding redundant full-page captures. A full-page screenshot can take more time and produce a larger artifact than a focused component capture.

Reliability depends on stable inputs and a stable capture environment. Pin browser versions, use deterministic fixtures, wait for fonts and relevant content, and keep separate baselines for distinct browser/platform combinations. Store artifacts from failures so reviewers can examine the baseline, actual image, and diff. Thresholds are a noise-control tool, not a substitute for reviewing changes.

Repository-managed Playwright baselines have no separate hosted review workflow, but the team owns snapshot storage, updates, and environment consistency. Hosted services add a centralized review and capture workflow; evaluate their current plans and terms directly. For ScreenshotNeo, the stated plans are Free: 1,000 shots/month with no card; Starter: $5 for 3,000; Growth: $15 for 15,000; Pro: $39 for 60,000; Scale: $99 for 250,000; and Business: $249 for 1,000,000. Yearly billing gives two months free, and every feature is on every plan. Only clean shots are billed.

Troubleshooting

Symptom Likely cause Fix
Snapshots differ on every run Dynamic content, animation, delayed fonts, or inconsistent test data. Use fixtures, wait for fonts and the relevant content, disable animations, or narrowly hide irrelevant volatile elements with a capture stylesheet.
Text wraps differently on CI Different OS, browser build, installed fonts, viewport, or device scale factor. Pin the browser and CI image, install matching fonts, and keep viewport and scale factor fixed. Regenerate a baseline only after confirming the environment change is intended.
Full-page capture has missing or shifted content Lazy-loaded content has not appeared, or layout changes as the page scrolls. Wait for the target content and images to load; use a controlled scroll or application-ready signal before capture. Consider a locator screenshot if the full page is not the behavior under test.
The diff reports a large change after an expected redesign The baseline still represents the previous design. Inspect the actual and diff images, then update the snapshots in a deliberate review after accepting the redesign.
A small defect passes the comparison The pixel threshold or changed-pixel allowance is too permissive, or the region is excluded. Reduce the tolerance, remove overly broad exclusions, and add a focused component assertion for the critical region.
A locator screenshot fails because the locator is ambiguous The selector matches more than one element or the target is not present. Use a stable test ID or a more specific locator; assert the locator count or visibility before capture.
The page capture is blank or incomplete Navigation completed before client rendering, the target request failed, or the page is blocked. Wait for an application-specific ready condition, check network and console errors, and confirm the same URL and credentials work in the test environment.

Frequently asked questions

Does Playwright create an image that highlights the differences?

Yes. A failed screenshot assertion includes a diff artifact alongside the expected and actual screenshots. Use the diff to locate changes, then inspect the source images to judge their impact.

Should I compare screenshots from different browsers?

Only when cross-browser rendering is the behavior you want to test. Keep a separate baseline for each browser and platform so expected rendering differences do not obscure regressions.

When should I update a visual baseline?

After reviewing the actual capture and diff and deciding the change is intended. Treat snapshot updates as code changes that deserve review.

Can a screenshot diff tell whether a change is correct?

No. It identifies image differences. A developer or reviewer must decide whether those differences match the intended design.