How to Compare Website Screenshots with Pixelmatch in Node.js
Compare website screenshots in Node.js with Pixelmatch: decode PNGs, create a diff image, choose tolerances, and handle common visual test failures.
To compare website screenshots with Pixelmatch in Node.js, decode both PNG files into pixel buffers, confirm their dimensions match, pass the buffers to pixelmatch, and check its mismatch count against a tolerance you choose. If you want an inspectable result, allocate a same-size output image and save the diff as a PNG.
Pixelmatch compares image data; it does not capture web pages or decide what differences your test should accept. Keep capture conditions consistent, then choose a threshold that reflects your application. The example below uses pngjs, as in the Pixelmatch README.
1. Install the packages
Start with a Node.js project. This example uses ECMAScript modules:
npm init -y
npm install pixelmatch pngjs
Set "type": "module" in your package.json, or save the example as .mjs. The project README documents installing Pixelmatch from npm and using pngjs to read and write PNGs.
2. Compare two screenshots and write a diff
Save this as compare.js. It reads baseline.png and actual.png, writes diff.png, and exits with a failing status when the mismatch count exceeds the example tolerance.
import fs from 'node:fs';
import pixelmatch from 'pixelmatch';
import { PNG } from 'pngjs';
const baseline = PNG.sync.read(fs.readFileSync('baseline.png'));
const actual = PNG.sync.read(fs.readFileSync('actual.png'));
if (baseline.width !== actual.width || baseline.height !== actual.height) {
throw new Error(
`Screenshots must have equal dimensions: baseline ${baseline.width}x${baseline.height}, ` +
`actual ${actual.width}x${actual.height}`
);
}
const diff = new PNG({ width: baseline.width, height: baseline.height });
const mismatchedPixels = pixelmatch(
baseline.data,
actual.data,
diff.data,
baseline.width,
baseline.height,
{ threshold: 0.1 }
);
fs.writeFileSync('diff.png', PNG.sync.write(diff));
console.log(`Mismatched pixels: ${mismatchedPixels}`);
const allowedMismatches = 0;
if (mismatchedPixels > allowedMismatches) {
console.error(`Visual comparison failed: allowed ${allowedMismatches}`);
process.exitCode = 1;
}
Run it with node compare.js. Pixelmatch returns a count of mismatched pixels. The allowed count above is an example policy, not a universal pass/fail rule. Decide whether your test should allow zero differences or a small project-specific tolerance.
3. Understand dimensions, data, and output
- Both inputs must have equal dimensions. Pixelmatch compares corresponding pixels, so differing widths or heights must be resolved before comparison. Resize or recapture only when that matches the test’s intent; otherwise treat a size change as a failure.
- Buffers contain decoded pixels. Pixelmatch accepts image data as a
Buffer,Uint8Array, orUint8ClampedArray. In this example,pngjsprovides the RGBA data inPNG.data. - The output image must match the input dimensions. Pass a same-size output buffer to get a visual diff. Pass
nullas the output argument if you need only the mismatch count. - Use a decoder appropriate to your format. The shown file workflow reads PNGs with
pngjs. Convert JPEG or WebP captures to PNG before this workflow, or use a decoder that produces the pixel data Pixelmatch expects.
4. Choose Pixelmatch options
Pixelmatch options control sensitivity and how the diff is drawn. Check the README for the release you install, especially when relying on newer options.
| Option | Effect | Practical use |
|---|---|---|
threshold |
Color difference sensitivity from 0 to 1. Smaller values are more sensitive; the documented default is 0.1. |
Set explicitly when you want the comparison policy visible in code. Tune it against representative pages and expected rendering variation. |
includeAA |
Defaults to false, so detected anti-aliased pixels are ignored. Set to true to count those pixels as differences. |
Keep the default when anti-aliasing variation should not dominate. Include those pixels when they are meaningful to the test. |
alpha |
Controls blending of unchanged pixels in the diff presentation. | Adjust readability of the output when reviewing diffs. |
aaColor, diffColor, diffColorAlt |
Set colors used to show anti-aliased and differing pixels. | Customize the visual diff for reviewers or tooling. |
diffMask |
Renders the diff over transparency instead of the original image. | Use when a transparent overlay fits your review workflow. |
windowSize |
The current main-branch README documents a finite window that reports the maximum mismatch count in any sliding square of that size; the default, Infinity, reports the total count. |
Use to focus a rule on locally concentrated differences. Verify support in the installed release; the package README snapshot for version 7.2.0 may not include this newer option. |
For example, pass options as the final argument:
const mismatchedPixels = pixelmatch(
baseline.data,
actual.data,
diff.data,
baseline.width,
baseline.height,
{
threshold: 0.1,
includeAA: false,
alpha: 0.1,
diffColor: [255, 0, 0],
diffMask: false
}
);
Start with the documented defaults unless you have a reason to change them. Increasing a tolerance can hide real regressions; lowering it can make harmless rendering variation fail a test.
5. Make the comparison reliable
Pixelmatch reports pixel differences in the images it receives. It does not know whether a changed pixel is a bug, an animation frame, a timestamp, or a legitimate content update. Reduce irrelevant variation at capture time and set an explicit acceptance rule.
- Capture the same route, viewport, device scale, browser environment, and scroll position for baseline and actual images.
- Wait for the page state your test intends to validate, including fonts and relevant content.
- Disable or stabilize animations, rotating banners, clocks, randomized content, and other changing regions where possible.
- Keep baseline updates deliberate: inspect the diff before replacing a known-good screenshot.
- Decide how dimension changes should behave. A different viewport or page height may indicate a real layout regression rather than a comparison input to normalize.
- For long pages or dynamic pages, consider comparing a stable element or region in the capture workflow, while retaining a separate check for overall dimensions if those matter.
These are test-design practices, not guarantees that two browser renders will be identical.
6. Use the Pixelmatch command-line tool for a quick diff
For a manual PNG comparison, the Pixelmatch project documents this command-line form:
pixelmatch image1.png image2.png output.png 0.1
The CLI is convenient for a one-off visual check. Use the Node.js API when you need to capture the mismatch count, enforce a test-specific tolerance, or integrate comparison into a test or build pipeline.
7. Capture screenshots with ScreenshotNeo
Pixelmatch compares images; you still need a reliable way to obtain the screenshots. ScreenshotNeo is a website screenshot API and MCP server. Its API returns an image or PDF from one GET request, and its screenshot options include full-page capture, CSS selector capture, viewport and device presets, retina scale, wait conditions, custom CSS and JavaScript, and request controls. See the ScreenshotNeo API documentation for parameters.
For a first capture, save the API response as a PNG and feed that file into the comparison script. Use a PNG response for the pngjs example; if you request JPEG or WebP, decode or convert it to PNG before reading it with this code.
Or skip the browser setup
Capture a page with one request, then compare the resulting PNG with Pixelmatch:
curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://stripe.com \
-o shot.png
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.png", "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(async ({ writeFile }) => {
await writeFile('shot.png', Buffer.from(await res.arrayBuffer()));
});
ScreenshotNeo accepts cookie and consent banners before capture and removes 60+ known consent platforms, newsletter popups, and chat widgets; each step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing status. Its MCP server gives AI agents tools for screenshots, page information, and PDF capture. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots. Every feature is on every plan.
Sign up free for 1,000 screenshots a month, with no card required.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Dimension mismatch error | The screenshots have different widths or heights. | Log both dimensions. Recapture with matching settings, or handle a size change as its own regression instead of silently comparing mismatched inputs. |
| Cannot read PNG or invalid file data | The file is not a valid PNG, is incomplete, or contains a non-image API error response. | Check that the capture request succeeded and inspect the saved file. Ensure the response is an image before passing it to PNG.sync.read. |
| Unexpected module import error | The project is using CommonJS while the example uses ES modules, or vice versa. | Set "type": "module" in package.json or use an .mjs file. Keep the package import style consistent with the project. |
| Too many mismatches on every run | Capture conditions or dynamic page content vary, or the comparison is more sensitive than intended. | Stabilize page state and rendering inputs first. Then review threshold and anti-aliasing policy; do not raise tolerance without inspecting what it would ignore. |
| Diff image is empty or hard to read | No pixels crossed the configured comparison criteria, or the diff presentation is not suited to review. | Log the count, verify the correct files were loaded, and adjust presentation options such as alpha or diff colors if needed. |
windowSize has no effect |
The installed Pixelmatch version may not support the option documented on the current main branch. | Check the README for the exact installed version and use a release that documents the option. |
Capture output cannot be decoded by pngjs |
The response was requested or returned as JPEG or WebP rather than PNG. | Request PNG output or convert the image to PNG with a decoder before using this example. |
Performance, reliability, and cost
Pixelmatch’s documented interface works on decoded image buffers, so your script must read and decode the files before comparison and may allocate another image-sized buffer for the diff. Large full-page screenshots therefore require more memory than small viewport captures. No benchmark or speed guarantee is established by the cited Pixelmatch documentation; measure with your own page sizes and runtime if comparison time matters.
For dependable CI results, make capture inputs repeatable, preserve the baseline and generated diff as artifacts when a comparison fails, and report both the mismatch count and image dimensions. Decide whether capture failures should fail the job separately from a valid screenshot that exceeds the visual tolerance.
Pixelmatch is distributed as software through npm; the research does not establish a need for special capture hardware or a particular paid service to run this comparison. If you use a hosted capture API, account for that service’s plan and usage rules. ScreenshotNeo’s stated plans are free for 1,000 shots monthly, then $5 for 3,000 on Starter, $15 for 15,000 on Growth, $39 for 60,000 on Pro, $99 for 250,000 on Scale, and $249 for 1,000,000 on Business; yearly billing gives two months free. Only clean shots are billed, and every feature is on every plan.
FAQ
Does Pixelmatch capture a website?
No. It compares image pixel data. Capture the page separately with a browser or screenshot API, then decode the resulting images for comparison.
Can I compare screenshots with different dimensions?
Not directly with this workflow. Pixelmatch requires equal-size inputs and output. Decide whether to recapture at matching dimensions or treat the size difference as a test failure.
What does Pixelmatch return?
It returns the number of mismatched pixels. Your test must define the allowed count or another acceptance rule.
What threshold should I use?
The documented default is 0.1. Choose based on the sensitivity your test needs, and review actual diffs before accepting a more permissive setting.
Can I omit the diff image?
Yes. Pixelmatch allows a null output argument when you need the mismatch count without creating an image.


