ScreenshotNeo

BlogHow-to

How to test a staging site against production with BackstopJS

Use BackstopJS to capture production as the visual baseline, compare staging against it, and review changes before approving a new baseline.

By the ScreenshotNeo team4 October 20267 min read

To test a staging site against production with BackstopJS, set each scenario’s referenceUrl to the production page and url to its staging counterpart. Run backstop reference to capture production, then backstop test to capture staging and compare the images. Review the report, and run backstop approve only when the differences are intentional and should become the new baseline.

This workflow checks rendered appearance. It does not decide whether a visual difference is a defect: a person must review the report and determine whether the change is acceptable.

1. Install and initialize BackstopJS

Use your project’s existing BackstopJS installation if it has one. Otherwise, install the package using the project’s package manager, then initialize its configuration as described in the official BackstopJS README. The exact commands can vary with the version and how your project manages dependencies, so follow the README or installed package documentation for your setup.

Before capturing, make both environments reachable from the machine or container that runs BackstopJS. Check that staging and production load without an interactive login prompt. If authentication is required, configure the test environment and capture setup accordingly; do not assume the default scenario can reach a protected page.

2. Configure production and staging scenarios

For each route, create a scenario with a stable label, a production referenceUrl, a staging url, and one or more viewports. The scenario’s url is used for test captures; referenceUrl is the production counterpart used when creating references.

// backstop.config.js — illustrative scenario structure
module.exports = {
  id: "site-visual-checks",
  viewports: [
    { label: "desktop", width: 1440, height: 900 },
    { label: "mobile", width: 390, height: 844 }
  ],
  scenarios: [
    {
      label: "Home page",
      referenceUrl: "https://www.example.com/",
      url: "https://staging.example.com/",
      selectors: ["document"]
    },
    {
      label: "Pricing page",
      referenceUrl: "https://www.example.com/pricing",
      url: "https://staging.example.com/pricing",
      selectors: ["document"]
    }
  ]
};

This is a configuration example, not a guarantee that every field or module format is identical across BackstopJS versions. Use the configuration format supported by your installed version. Replace the example domains and add the routes that matter to your release.

Choose what each scenario captures

  • document captures the whole page and is useful when the overall page layout is in scope.
  • viewport limits the capture to the current viewport. Use it when the visible first screen is the intended scope.
  • A CSS selector can target a component or region when that is the area at risk. Choose selectors that exist in both environments and remain stable across the change.

Keep scenario labels and viewport names consistent. They make reports and generated screenshots easier to identify across runs. Include only routes and viewport sizes that match the product’s actual visual risks; there is no single coverage set that fits every site.

3. Capture production references and compare staging

  1. Confirm that the production URLs load the intended pages and that your configuration points to the correct production and staging hosts.
  2. Run backstop reference. This captures the configured reference pages, which in this workflow are production.
  3. Run backstop test. BackstopJS captures the scenario’s staging url and compares those images with the current references.
  4. Open the generated report and inspect the captures and differences for every route and viewport you intend to check.
  5. If the visual changes are expected and accepted, run backstop approve to promote the latest test captures into the reference collection.

Approving changes replaces the comparison baseline for later runs. The BackstopJS README describes approval as updating reference files with the results of the last test. Do not approve simply to make a failing comparison disappear: first establish that the new appearance is intended.

4. Make the comparison useful

Keep the rendering environment consistent

Browser, operating-system, font, and rendering differences can create image variation unrelated to the code change. Run reference and test captures in the same rendering environment where practical. The BackstopJS README recommends Docker when cross-machine rendering differences are a concern. If the browser runs in Docker, localhost inside the container may not point to the host machine; on Mac and Windows, the README gives host.docker.internal as an alternative.

Choose routes, viewports, and capture scope deliberately

Decision How to choose
Routes Cover pages affected by the change and representative important flows. Add routes where a shared component or layout change could have different effects.
Viewports Include viewport sizes relevant to your users and layout breakpoints. Keep labels stable so comparisons remain easy to follow.
Capture scope Use the whole document for page-wide changes, the viewport for above-the-fold checks, or a selector for component-level risk.
Approval Review the report and accept only changes that are intended. Approval updates the baseline used in subsequent comparisons.

Use the report as a review artifact

BackstopJS documents reporting and CI options in its README. Decide which visual changes should block a release, who reviews them, and when reference updates are allowed. A visual diff can identify a changed rendering, but your team defines whether that difference is a regression or an approved design change.

5. Troubleshooting

Symptom Likely cause What to check
Reference and test images show the same environment The scenario’s url and referenceUrl may point to the same host, or the wrong configuration is being run. Check both URLs for each scenario. Production belongs in referenceUrl; staging belongs in url.
A reference capture fails to load The production page may be unreachable from the runner, redirecting, or gated behind authentication. Open the exact URL from the runner’s environment and inspect redirects and access requirements.
A staging capture fails to load The staging host may be unavailable to the runner, especially when the runner is inside Docker. Check network reachability from the browser/container. If using Docker, account for the container’s meaning of localhost; the README notes host.docker.internal for Mac and Windows.
Many unrelated pixels differ across machines The captures may use different rendering environments. Run both captures with a consistent browser and environment; consider the README’s Docker guidance for cross-machine consistency.
A selector capture is empty or misses the changed area The selector may not exist on both pages or may target a different region than intended. Verify the selector against both environments and choose a stable component or page region.
A change disappears from future comparisons The latest test captures may have been approved and promoted to references. Check whether backstop approve was run. Restore or recreate the intended baseline before relying on later comparisons.
Configuration fields behave differently than expected BackstopJS documentation and supported configuration can vary by installed version. Check the README or package documentation for the exact version used in the project.

6. Performance, reliability, and cost considerations

Capture work grows with the number of scenarios and viewports because each combination needs a rendered comparison. Start with the routes and sizes tied to the change, then expand coverage where the risk warrants it. Full-document captures cover more content than viewport captures, while selector captures focus on a narrower area; select the scope that answers the review question.

For reliable comparisons, keep reference and test rendering conditions aligned, make both endpoints available to the runner, and review changes before approving. The research sources provide no topic-specific benchmark or fixed runtime, so execution time depends on the configured pages and environment. BackstopJS’s core visual comparison workflow does not establish a cost per screenshot in the cited README; account for the infrastructure and browser environment your team uses.

Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server. For a one-request capture, send the target URL to its API. This captures a page; it does not replace BackstopJS’s production-reference, staging-test, and visual-diff approval workflow.

See the ScreenshotNeo API documentation for request 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}`);
const fs = await import('node:fs/promises');
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));

ScreenshotNeo removes cookie banners, 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. Sign up for free and get 1,000 screenshots a month with no card.

FAQ

Does BackstopJS decide whether a difference is a bug?

No. It captures and compares rendered images; a reviewer decides whether a difference is an unintended regression or an accepted change.

Can I compare a component instead of a whole page?

Yes. Use an element selector when the component or region is the intended scope, and make sure the selector is valid in both staging and production.

When should I run backstop approve?

After reviewing the latest test report and deciding that the visible changes are intentional. Approval makes those test captures the reference baseline for later comparisons.