ScreenshotNeo

BlogComparisons

Best Tools to Compare Two Website Screenshots Pixel by Pixel

Compare two existing images with Pixelmatch, or add screenshot assertions and review workflows with Playwright, BackstopJS, or Applitools Eyes.

By the ScreenshotNeo team4 October 20269 min read

Short answer: If you already have two same-size image files, use Pixelmatch to count changed pixels and write a visual diff. If you use Playwright Test, start with its built-in toHaveScreenshot() assertion so capture, baselines, and comparison stay in your test workflow. For a configured capture-and-report workflow, consider BackstopJS; for hosted visual review, evaluate Applitools Eyes. These tools solve different problems, and the available research does not establish a universal winner.

For repeatable comparisons, control the browser, operating system, fonts, viewport, and page state. A pixel difference identifies a rendering change; a person still needs to decide whether that change is a bug or an intentional update.

Choose the right kind of comparison

Tool Best fit What it provides Decision point
ScreenshotNeo Developers who need clean website screenshots to compare or feed into a visual workflow Screenshot API and MCP server; consent banners, newsletter popups, and chat widgets can be removed before capture Only clean shots are billed; failed, blocked, blank, timed-out, and cached responses cost nothing
Playwright Test Teams already using Playwright Browser screenshot assertions, stored reference images, thresholds, and a stylesheet to hide volatile content Keep baseline and test rendering environments consistent
Pixelmatch Comparing two image files in a script or utility JavaScript API and command line; mismatch count and optional diff image It compares image data; it does not manage browser capture or a full review workflow
BackstopJS Teams wanting scenarios, reference and test captures, and a report Page and selector scenarios, viewport options, visual diff report, approval step, CI support, and mismatch threshold Inspect project maintenance and rendering setup; the repository README says it needs a new maintainer or owner
Applitools Eyes Teams evaluating hosted visual review beyond strict raw pixel equality Vendor-described checkpoints, baselines, visual-difference reporting, region exclusions, match levels, and cross-browser/device workflows Check current features, terms, privacy handling, and pricing directly; no independent performance comparison is available here

ScreenshotNeo is the first option to consider when capture quality and predictable billing matter: it removes common consent banners, popups, and chat widgets before capture, and bills only clean shots. It is a capture service, not a pixel-diff engine; pair its output with one of the comparison workflows below. See ScreenshotNeo.

What “pixel by pixel” means in practice

A strict comparison can flag every changed pixel, but browser rendering is not perfectly stable across machines. Playwright documents that operating system, browser version, settings, hardware, power source, and headless mode can change rendering. Its guidance is to run tests in the same environment used to create the baselines. Fonts, animation, current timestamps, randomized content, network-loaded assets, and responsive breakpoints can also make captures differ.

Thresholds help manage small rendering differences. They are a tradeoff: a more permissive threshold may reduce nuisance failures while allowing meaningful visual changes through. Tune it against representative pages and known dynamic regions; there is no universal best value. A diff image is evidence to review, not a verdict that a change is defective.

Compare two existing screenshots with Pixelmatch

Pixelmatch is suitable when both screenshots already exist and have equal dimensions. This runnable Node.js example reads two PNG files, compares RGBA pixel data, writes a red-highlighted diff image, and reports the mismatch count and ratio.

npm install pngjs pixelmatch
// compare.mjs
import fs from 'node:fs';
import { PNG } from 'pngjs';
import pixelmatch from 'pixelmatch';

const [beforePath, afterPath, diffPath = 'diff.png'] = process.argv.slice(2);
if (!beforePath || !afterPath) {
  console.error('Usage: node compare.mjs before.png after.png [diff.png]');
  process.exit(2);
}

const before = PNG.sync.read(fs.readFileSync(beforePath));
const after = PNG.sync.read(fs.readFileSync(afterPath));
if (before.width !== after.width || before.height !== after.height) {
  throw new Error(`Image sizes differ: ${before.width}x${before.height} vs ${after.width}x${after.height}`);
}

const diff = new PNG({ width: before.width, height: before.height });
const mismatched = pixelmatch(
  before.data,
  after.data,
  diff.data,
  before.width,
  before.height,
  { threshold: 0.1, includeAA: false, alpha: 0.5 }
);
fs.writeFileSync(diffPath, PNG.sync.write(diff));
const total = before.width * before.height;
console.log(`${mismatched}/${total} pixels differ (${(100 * mismatched / total).toFixed(3)}%); diff saved to ${diffPath}`);
node compare.mjs baseline.png current.png diff.png

The threshold above is an example starting point, not a universal recommendation. Pixelmatch exposes a color threshold and anti-aliasing handling; decide how to treat those based on the pages and rendering environment. Its return value is a count of different pixels. Inspect the generated diff as well as the number.

Pixelmatch command line

For a quick local comparison without writing a script, Pixelmatch also provides a command-line interface. Check its current README for installation and exact CLI options; the library comparison still requires matching image dimensions.

Add screenshot comparison to Playwright Test

Playwright Test creates a reference screenshot on the first run and compares subsequent captures against it. Add an assertion in a normal test:

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

test('home page visual baseline', async ({ page }) => {
  await page.setViewportSize({ width: 1280, height: 800 });
  await page.goto('http://127.0.0.1:3000');
  await expect(page).toHaveScreenshot('home.png', {
    fullPage: true,
    animations: 'disabled',
    maxDiffPixelRatio: 0.01
  });
});

Run the test with your project’s normal Playwright command. On the initial run, review the generated expected image and commit the approved baseline with the test. When a deliberate design change updates the expected appearance, use Playwright’s documented snapshot update command, review the changed images, and commit only the approved updates. The example ratio is illustrative: set a threshold based on the acceptable change for your pages.

Playwright’s screenshot assertion supports full-page capture and threshold controls, including differing pixel count or ratio and color-difference threshold. Its snapshot assertion API documents a default threshold of 0.2 in YIQ color space. That is a color-difference setting, not a universal mismatch percentage. Consult the snapshot documentation and API reference for current options and semantics.

Hide volatile content

Prefer stabilizing the page in the test: freeze test data, wait for required content, and disable animations. If a region is inherently dynamic and irrelevant to the assertion, use Playwright’s screenshot stylesheet option to hide or neutralize it. Keep exclusions narrow; hiding a large region can conceal real layout regressions. The documented screenshot assertion options include a custom stylesheet.

Capture and compare scenarios with BackstopJS

BackstopJS provides scenarios that describe pages, viewports, selectors, and reference/test captures, plus a visual report and approval workflow. Its configuration changes across versions, so use the current project README for the exact setup and command syntax rather than copying an unverified configuration. In general, define the URL and viewport for each scenario, capture a reference set, run test captures in the same rendering environment, then review the report and approve intentional baseline changes.

BackstopJS supports selector-based scenarios, hide/remove selectors, mismatch tolerance, CI use, and report generation. Its repository README says it needs a new maintainer or owner, so check project activity and maintenance fit before making it a long-term dependency.

Evaluate hosted visual review with Applitools Eyes

Applitools’ vendor guide describes checkpoint and baseline comparison, significant visual-difference reporting, region exclusions, match levels, and cross-browser/device workflows. Those vendor-described features may suit teams that need hosted review and broader visual matching than raw exact-pixel comparison. Verify current product capabilities, supported browsers, data handling, collaboration model, terms, and pricing directly before choosing it. The cited guide dates from July 2022, and this research includes no independent comparison of performance or accuracy.

Or skip the browser setup

Use ScreenshotNeo to capture a page through one GET request, then pass the image to Pixelmatch or your existing visual test workflow. The parameter names used by other screenshot APIs also work, which can make switching easier. See the API docs for the available formats and options.

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}`);
const bytes = Buffer.from(await res.arrayBuffer());
await import('node:fs').then(({ default: fs }) => fs.promises.writeFile('shot.webp', bytes));

Cookie banners, newsletter popups, and chat widgets are removed before the shot. Bot checks, blank pages, failed loads, timeouts, and cache hits are never billed; response headers report the page verdict and billing status. Its MCP server lets AI agents use take_screenshot, get_page_info, and capture_pdf. The free plan includes 1,000 screenshots a month with no card, and paid plans start at $5 for 3,000 shots. Sign up for 1,000 free screenshots a month, with no card required.

Make comparisons reliable

  1. Pin the rendering environment. Use the same browser version, operating system, fonts, settings, viewport, and headless mode for baseline and comparison runs. Containerizing CI can help make that environment repeatable.
  2. Control page state. Use stable test data and wait for the page content that matters. Disable animation and avoid captures while fonts, images, or network requests are still changing the layout.
  3. Choose a consistent capture boundary. Compare the same page, viewport, scroll position, and full-page or element region. Different dimensions are invalid for direct Pixelmatch comparison.
  4. Handle dynamic regions deliberately. Hide only content that should not be tested, using screenshot stylesheets, selector controls, or documented region exclusions. Review whether exclusions could hide meaningful changes.
  5. Review and version baselines. Treat baseline updates like code changes: inspect the diff, associate it with an intentional change, and keep the update in version control or the selected tool’s review workflow.
  6. Set thresholds with examples. Run known-good and known-bad changes through the workflow. Record why a tolerance is acceptable and revisit it when the rendering environment changes.

Performance, reliability, and cost

A local image diff such as Pixelmatch adds a focused comparison step; the browser capture and baseline storage are separate work you must supply. Playwright reuses an existing test runner, while BackstopJS adds scenario and report workflow. Hosted tools add a vendor service and its current plan, data, and review terms. This dossier contains no comparable benchmark or pricing evidence, so evaluate actual execution time, CI behavior, storage, and current plan terms on your own representative pages.

For stable CI, avoid sharing mutable baseline directories between concurrent jobs unless the workflow isolates them. Keep screenshot artifacts for failed comparisons so reviewers can inspect both inputs and the diff. A retry can help identify intermittent capture failures, but repeated retries should not silently turn flaky visual assertions into passing checks. With ScreenshotNeo, use the documented verdict and billing headers to distinguish clean shots from blocked, blank, failed, timed-out, or cached responses.

Troubleshooting

Symptom Likely cause Fix
Every run shows many differences Browser, OS, fonts, viewport, or rendering mode differs from baseline Run baseline and test in the same pinned environment and dimensions
Only small edges or text look different Anti-aliasing or minor color variation Keep environments consistent first; then tune the tool’s documented threshold and anti-aliasing behavior against reviewed examples
Differences move between runs Animation, timestamps, random content, delayed assets, or live data Stabilize test data, wait for the relevant state, disable animations, or narrowly exclude the dynamic area
Pixelmatch throws or images do not compare The images have different widths or heights, or are not decoded as expected Normalize capture dimensions and decode both inputs to the same supported pixel format before comparison
Diff count is high but page looks unchanged Large blank/background area, subtle rendering shifts, or capture boundary mismatch Inspect the diff image, compare dimensions and viewport, and decide whether a narrow exclusion or environment fix is appropriate
Intentional UI change fails the test The baseline still represents the previous design Review the expected change and update the committed baseline using the tool’s documented workflow
ScreenshotNeo response is not a usable page image The destination may show a bot check, blank page, or failed load Inspect X-Page-Verdict and X-Billed response headers; correct access or page readiness and retry as appropriate

Frequently asked questions

Can I compare screenshots with different dimensions?

Not directly with Pixelmatch: both decoded images must have matching width and height. Decide on a shared viewport and capture boundary, then recapture or normalize inputs before comparison.

Does a pixel diff tell me whether a change is a bug?

No. It shows where pixels differ under the chosen capture and comparison settings. Review the changed regions and determine whether they match the intended design.

Should I set the threshold to zero?

Only if your rendering environment and page state are sufficiently deterministic for exact equality to be useful. Begin with controlled captures and tune against known changes; a zero threshold can make inconsequential rendering variation fail, while a loose threshold can miss changes.

Can ScreenshotNeo replace Playwright or Pixelmatch?

No. ScreenshotNeo captures pages and provides an MCP server; use a comparison tool or test assertion to compare the resulting images. Its capture controls include full-page or selector capture, viewport and device settings, waiting behavior, and request blocking.