ScreenshotNeo

BlogHow-to

How to Compare Pages Across Browsers with BackstopJS

Configure BackstopJS to compare page screenshots across Chromium, Firefox, and WebKit, while keeping browser, viewport, and page state consistent.

By the ScreenshotNeo team4 October 20268 min read

BackstopJS compares screenshots from a test run with saved reference screenshots. To compare browser engines, configure an engine, capture and review its references, then repeat the workflow for the next engine. BackstopJS documents Puppeteer as its default engine and Playwright as an option for Chromium, Firefox, or WebKit; its documented workflow does not automatically run a browser matrix in one default command.

For a useful comparison, hold the URL, viewport, page state, readiness conditions, browser version, and operating system steady wherever possible. A difference can come from a real rendering change, but also from a different environment or a page captured before it was ready.

1. Install and initialize BackstopJS

Use the project’s documented setup workflow to initialize a configuration in your repository. The commands below assume BackstopJS is available in your project environment:

backstop init

Initialization creates a starter configuration and the files used for scenarios and reporting. Keep this configuration under version control so the team can review scenario changes alongside reference updates. Consult the BackstopJS README for current installation and configuration details.

2. Define the page and viewport to compare

A scenario identifies the URL and the page state BackstopJS should capture. Configure a small, representative set of viewports and scenarios first. The example below illustrates the shape of a configuration; retain the generated configuration’s other required fields and adapt its structure to the BackstopJS version installed in your project.

{
  "viewports": [
    { "label": "desktop", "width": 1440, "height": 900 },
    { "label": "mobile", "width": 390, "height": 844 }
  ],
  "scenarios": [
    {
      "label": "Home page",
      "url": "https://example.com/",
      "selectors": ["document"],
      "readySelector": "main"
    }
  ]
}

BackstopJS scenarios support URLs, selectors, readiness conditions, and interactions. Use these to capture the state users actually see: for example, a menu after it has been opened or a page after a target element appears. Ensure test data and content remain stable between reference and test runs.

3. Capture references, test changes, and review diffs

The documented command sequence records reference images, captures a test run, and opens a report for review:

backstop reference
backstop test

Inspect each viewport and scenario in the report. A visual diff identifies changed pixels; it does not determine whether a change is correct. Check whether the page loaded fully, whether dynamic content changed, and whether the capture environment matches the reference environment before accepting a change.

When a difference is intentional, update the reference collection:

backstop approve

Approval replaces the baseline used for later comparisons. Review the diff before approving, and treat reference changes like code changes so unintended visual regressions do not become the new expected result.

4. Configure an engine for cross-browser coverage

BackstopJS documents Puppeteer as its default rendering engine. Its README also documents a Playwright engine, with engineOptions.browser set to chromium, firefox, or webkit. When switching engines, use the Playwright-specific onBefore and onReady scripts described in the project documentation.

Set the Playwright engine and browser in the generated configuration according to the README for your installed version. The following fragment shows the relevant values; merge them into the generated configuration rather than treating this partial fragment as a complete config file:

{
  "engine": "playwright",
  "engineOptions": {
    "browser": "firefox"
  }
}

Run the reference and test workflow for each engine you want to evaluate. Keep the engine identity clear in your process and reference management: the documented commands do not imply that one ordinary run automatically captures and compares every engine.

  1. Configure Playwright for the first engine, such as chromium.
  2. Capture its reference screenshots and run a test against those references.
  3. Review and approve only intended changes for that engine.
  4. Repeat for firefox and/or webkit, using the matching engine configuration and a deliberate reference set.

Keep baselines associated with the engine and rendering environment that created them. Comparing a Firefox test screenshot to a Chromium reference mixes browser differences with application changes and makes the result difficult to interpret.

5. Choose the right browser target

What you need to assess Useful target Important limit
Broad engine behavior Playwright Chromium, Firefox, and WebKit configurations These are engine builds, not labels for every branded browser release.
Current Chrome behavior Playwright’s documented branded Chrome channel Use the channel option documented by Playwright for the installed version.
Current Edge behavior Playwright’s documented branded Edge channel Branded channels are distinct from the default Chromium build.
Safari-adjacent behavior WebKit, with macOS WebKit when platform-specific behavior matters Playwright says its WebKit is not branded Safari; macOS WebKit is closer for cases such as video playback.

Playwright states that its Firefox build uses patches and is not branded Firefox, and its WebKit comes from the latest WebKit main branch rather than branded Safari. It documents Chrome and Edge branded channels as options. Playwright browser binaries are tied to Playwright releases, so use the installation and channel instructions for the version in your project rather than pinning an unverified version from an old example. See Playwright’s browser documentation.

6. Keep comparisons reproducible

  • Match viewport dimensions. Use the same width and height when attributing a difference to an engine.
  • Match page state. Configure selectors, readiness conditions, and interactions so captures represent the same content and state.
  • Control dynamic inputs. Use stable test accounts and data, and avoid comparing pages whose content changes between runs.
  • Keep versions and operating systems recorded. Browser builds and platform behavior affect rendering; Playwright notes that platform-dependent features such as media codec availability vary by operating system.
  • Use a consistent rendering environment. BackstopJS offers Docker rendering to help teams compare references across environments and warns that text can render differently across environments.
  • Separate environment questions from regression questions. If the goal is to compare browser rendering, use corresponding references per engine. If the goal is to detect a change within one target, compare runs with that target held constant.

Docker helps standardize the environment used to create and compare screenshots, but it does not make its output identical to every developer’s installed branded browser. Use branded Chrome or Edge channels when matching those releases matters, and select the operating system deliberately for platform-specific behavior.

7. Troubleshooting visual differences

Symptom Likely cause What to check
Large differences across nearly the whole page Different browser engine, version, operating system, viewport, or fonts Confirm the test uses the intended engine and dimensions, then align the reference and test environments.
Text-only or line-wrap differences Font availability, font rendering, or environment variation Use the same rendering environment; BackstopJS’s Docker option can help reduce cross-environment variation.
Blank, partial, or inconsistent captures The page was captured before the relevant content was ready Use a scenario readiness condition such as a selector, and verify the expected page state appears before capture.
Diffs change on every run Dynamic content, animation, asynchronous loading, or unstable test data Stabilize the scenario inputs and readiness conditions; inspect the captured images to identify moving content.
Firefox or WebKit engine setup fails Engine-specific scripts or browser setup are missing or mismatched Follow BackstopJS’s Playwright engine instructions, including its onBefore and onReady requirements, and use browser binaries supported by the installed Playwright release.
WebKit output does not match Safari behavior Playwright WebKit is not the branded Safari browser, and behavior can vary by OS Use macOS WebKit for closer Safari-like behavior where platform features matter, or verify in branded Safari for final platform-specific checks.
Approval makes a later regression disappear An unintended difference was accepted into the baseline Review diffs before approval and restore a known-good reference when an incorrect baseline was accepted.

8. Performance, reliability, and cost considerations

Visual comparison work grows with the number of scenarios, viewports, and browser targets: each combination needs a capture and a meaningful baseline. Start with the pages and states that matter most, then add coverage for specific risks. This is a workload consequence of the documented scenario and viewport model, not a BackstopJS benchmark.

Reliability depends on repeatable inputs and an explicit capture environment. A screenshot diff is useful evidence of a visual change, but it cannot explain whether the cause was an application edit, browser update, OS difference, changed content, or capture timing. Keep the environment and scenario configuration with the results, and review the images before approving.

BackstopJS is an open-source software workflow and uses browser engines or browser binaries; the cited documentation does not establish a usage price. Account for the engineering time and infrastructure needed to run the browser targets and maintain references. For hosted screenshot capture by URL, ScreenshotNeo offers a separate API option below.

Or skip the browser setup

For a one-call screenshot of a URL, ScreenshotNeo is a website screenshot API and MCP server from Yorker Media. The following cURL request saves a WebP screenshot; replace the key with your API key. See the ScreenshotNeo API documentation for request options.

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,
)
r.raise_for_status()
with open("shot.webp", "wb") as f:
    f.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 require('node:fs/promises').writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));
  • Cookie and consent banners are accepted and removed before capture, along with supported newsletter popups and chat widgets; each step can be turned off.
  • Bot checks, blank pages, failed loads, timeouts, and cache hits cost nothing; response headers identify the page verdict and billing status.
  • An MCP server provides take_screenshot, get_page_info, and capture_pdf tools for AI agents and MCP clients.
  • The free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 screenshots; every feature is available on every plan.

Sign up for ScreenshotNeo’s free 1,000 screenshots per month, with no card required.

FAQ

Does BackstopJS compare all browsers in one default run?

The documented default workflow does not describe an automatic all-browser matrix. Configure the engine and run a deliberate capture and comparison workflow for each target you need.

Is Playwright WebKit the same as Safari?

No. Playwright describes its WebKit build as distinct from branded Safari. The operating system can also affect platform-specific behavior.

Should I approve every reported visual difference?

No. Approve only changes you have inspected and judged intentional; approval updates the references used by future comparisons.

Is BackstopJS actively maintained?

The inspected BackstopJS README says the project needs a new maintainer or owner. Check the repository’s current status before making assumptions about its maintenance lifecycle.