How to Set Up BackstopJS Visual Regression Testing for a Website
Set up BackstopJS to capture screenshot baselines, compare website changes, review diffs, and automate visual regression checks.
BackstopJS runs browser screenshots for configured website scenarios, compares them with approved reference screenshots, and produces a report that helps you review visual changes. The basic workflow is: install and initialize it, define scenarios and viewports, capture references with backstop reference, run comparisons with backstop test, inspect the report, and approve only changes you have reviewed.
This guide sets up a small runnable project, explains the configuration choices that affect reliable comparisons, and shows how to add the checks to CI. Check the BackstopJS project guide for options supported by the version you install; configuration details can change between releases.
1. Install BackstopJS and initialize a project
Use Node.js and npm, or use a compatible Docker workflow if you need a consistent browser environment. For a local setup, make a directory for the visual tests and install BackstopJS there:
mkdir visual-tests
cd visual-tests
npm init -y
npm install --save-dev backstopjs
npx backstop init
The initialization command creates a configuration file and supporting directories. Keep the configuration and approved reference screenshots under version control so developers and CI compare against the same baseline.
Run commands through npx to use the project-local version:
npx backstop reference
npx backstop test
The first command captures the references. The second captures the configured test pages and compares them to those references. Do not approve a change until you have reviewed it.
2. Define scenarios and viewports
Replace the generated configuration with a small starting point like this. It defines two pages and desktop and mobile viewports. BackstopJS configuration is version-sensitive, so compare this shape and its supported properties with the guide for your installed version.
{
"id": "website-visual-regression",
"viewports": [
{ "label": "desktop", "width": 1365, "height": 900 },
{ "label": "mobile", "width": 390, "height": 844 }
],
"scenarios": [
{
"label": "home",
"url": "http://localhost:3000/",
"referenceUrl": "http://localhost:3000/",
"selectors": ["document"],
"delay": 500
},
{
"label": "pricing",
"url": "http://localhost:3000/pricing",
"referenceUrl": "http://localhost:3000/pricing",
"selectors": ["document"],
"delay": 500
}
],
"paths": {
"bitmaps_reference": "backstop_data/bitmaps_reference",
"bitmaps_test": "backstop_data/bitmaps_test",
"html_report": "backstop_data/html_report",
"ci_report": "backstop_data/ci_report"
},
"engine": "puppeteer",
"report": ["browser", "CI"],
"debug": false
}
Use the actual local or deployed URL that the test runner can reach. If your version’s generated config uses a different schema, retain its supported structure and add the same essential information: a unique scenario label, a URL, and at least one viewport.
Scenario and viewport choices
- Scenarios: Cover important page templates and states, not every URL by default. Give labels names that make a failing capture easy to identify.
- Viewports: Include sizes around the layout breakpoints that matter to your site. A mobile and desktop width is a useful starting point, but use the sizes needed to exercise your responsive layouts.
- Selectors: Capturing the whole document catches layout and content changes across a page. If you only need to compare a component, configure selectors supported by your version and understand what content the capture excludes.
- Reference URL: For ordinary regression checks, keep an approved reference set and compare new builds against it. When comparing two environments, configure a reference URL and test URL that represent those environments, where supported by your version.
3. Capture the initial reference set
Start the site under test, then capture the reference screenshots:
npx backstop reference
Review a sample of the generated captures before treating them as the project’s baseline. Confirm the intended content, viewport, fonts, and page state are present. Commit the approved references with the configuration so later runs have a stable comparison point.
A reference is a decision about what the team considers correct. Capturing a broken, partially loaded, or unintended state at this point makes later comparisons less useful.
4. Run tests and review the visual report
With the application available at the configured URLs, run:
npx backstop test
BackstopJS captures the test state, compares it to the reference set, and generates a report of differences. Inspect each reported difference and decide whether it is an unintended regression, an intentional design change, or capture noise caused by unstable content or timing. A pixel difference is a signal to investigate, not proof of a defect.
If a difference is intentional, update the reference only after review:
npx backstop approve
BackstopJS also documents filtering approval to selected captures. Check the command syntax for your installed version before using a filter, especially in automation. Broadly approving every difference can erase useful regression coverage.
5. Make captures repeatable
Visual tests are useful only when the page reaches the same meaningful state on each run. Fix unstable inputs before increasing the number of scenarios.
- Wait for readiness: Use a selector, readiness condition, delay, or browser script when the page needs time or interaction before it is ready. Prefer a specific ready signal over a long fixed delay where possible.
- Control dynamic content: Freeze or stabilize rotating banners, timestamps, randomized content, and personalized data in the test environment. If you mask or hide a region, do it narrowly and document why; a broad mask can conceal real regressions.
- Use stable data and state: Make test accounts, cookies, and page content predictable. If a page depends on authentication or browser state, configure it explicitly using options supported by your version.
- Keep fonts and assets available: Missing fonts or late-loading images change layout and can create widespread diffs. Ensure the test environment can fetch the same assets consistently.
- Choose an engine deliberately: The project guide documents Puppeteer as the default and also documents Playwright, including Chromium, Firefox, or WebKit engine options. Select the engine and browser behavior you want to check; one engine does not represent every browser your users may run.
6. Choose local execution or Docker
Local npm installation is straightforward for development. A container can make captures more consistent when developer machines and CI render differently, provided the container version matches the BackstopJS version and browser setup used by the project.
The Docker Hub listing linked from the project documentation describes a BackstopJS 3.x image with headless Chrome. Do not assume that image is current or compatible with every BackstopJS release. Verify its version and use a pinned, compatible image for repeatable runs. See the BackstopJS Docker image listing and project guide for current details.
7. Add BackstopJS to CI
Run visual checks after the application build is available and before changes are merged or released, according to your team’s workflow. The exact pipeline depends on the CI provider and how the app is started. Make sure the job:
- Checks out the repository, including the approved reference screenshots.
- Installs the locked project dependencies and a compatible browser runtime.
- Starts or deploys the site to a URL reachable from the test job.
- Runs
npx backstop testand fails or flags the change when comparisons fail, according to the project’s review policy. - Preserves the generated HTML report and screenshot artifacts so someone can inspect failures.
BackstopJS documents CI reporting and Docker execution, but service startup, networking, artifact retention, and failure handling are CI-specific. Avoid copying an old pipeline example without checking it against your current runner and BackstopJS version.
Configuration decisions at a glance
| Decision | Use this approach | Watch for |
|---|---|---|
| Coverage | Choose representative page templates and important states. | Too many redundant scenarios slow runs and make review harder. |
| Viewport set | Cover relevant responsive breakpoints. | Small viewport changes can alter wrapping and page height. |
| Reference model | Use an approved baseline for code regression; use separate environment URLs for environment comparisons. | Reference and test content must be comparable. |
| Browser engine | Choose the supported engine that matches the browser behavior you want to exercise. | Different engines may render differently. |
| Wait strategy | Wait for an explicit ready state, selector, or needed interaction. | Arbitrary delays can be slow and still flaky. |
| Rendering environment | Use a consistent local or containerized browser setup. | Pin compatible versions; the available Docker image may lag. |
| Approval | Review diffs before promoting them to references. | Unreviewed approval can normalize regressions. |
Performance, reliability, and cost
BackstopJS runs browser captures for each configured scenario and viewport, so adding coverage increases runtime and the volume of screenshots that reviewers may need to inspect. Start with important templates and breakpoints, then add cases where they catch a distinct class of change.
Reliability depends on stable pages, accessible assets, controlled browser versions, and repeatable data. Docker may reduce environment differences, but it does not fix nondeterministic page content or incorrect waits. No runtime or cost benchmark is implied here; resource use depends on the number and complexity of captures and on the machine or CI runner.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| BackstopJS cannot load the page | The app is not running, the URL is wrong, or the test runner cannot reach the host. | Start the app before the command and verify the URL from the runner’s network environment. |
| Every capture differs between runs | Dynamic content, late assets, animation, or unstable browser rendering. | Stabilize test data and animations, wait for a meaningful ready state, and keep the runtime consistent. |
| Large page-wide differences appear | A font or shared asset failed to load, or the page rendered at a different viewport or browser version. | Check asset requests, viewport configuration, and browser/runtime versions first. |
| The report shows a real design change as a failure | The baseline still represents the old approved design. | Review the changed capture with the team, then approve the intended update. |
| Docker run fails or behaves differently | The image may be incompatible with the installed BackstopJS version or may lack required access. | Check the image version, browser compatibility, mounted paths, and network access; pin a compatible image. |
| CI cannot find the report | The job does not collect the configured report directory, or the test exits before artifact upload. | Configure the CI job to preserve the report and screenshot paths even on a failed visual check. |
| Approval updates more captures than intended | The approval command was run without the desired selection filter. | Check the installed version’s approval filtering syntax and review which references will be promoted. |
Or skip the browser setup
If you need screenshots from URLs without maintaining a browser capture environment, ScreenshotNeo is a website screenshot API and MCP server. It is useful for screenshot capture workflows, while BackstopJS is the tool in this guide for maintaining reference images and visual regression reports.
See the ScreenshotNeo API documentation. One GET request captures a URL:
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}`);
- Cookie banners, popups, and chat widgets are removed before the shot; each cleanup step can be turned off.
- Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed; response headers report the page verdict and billing status.
- An MCP server lets AI agents use
take_screenshot,get_page_info, andcapture_pdf. - 1,000 screenshots a month are free with no card. Paid plans start at $5 for 3,000 screenshots; every feature is on every plan.
Sign up for ScreenshotNeo’s free 1,000 screenshots a month, with no card required.
FAQ
Does BackstopJS tell me whether a visual change is a bug?
No. It reports visual differences for review. You decide whether each change is intended or a regression.
Should I commit reference screenshots?
Yes, keep the approved baseline with the configuration so local and CI runs use the same comparison point.
Can I compare staging with production?
The documented workflow can compare a reference URL with a test URL. Use that model when environment comparison is your goal, and keep the two environments’ content and state comparable.
Does passing a BackstopJS run guarantee cross-browser consistency?
No. A run checks the configured engine, browser, scenarios, and viewports. Test additional engines or browsers when those rendering differences matter to your users.


