ScreenshotNeo

BlogHow-to

How to use BackstopJS with a local development server

Run BackstopJS against your local app, capture a baseline, and make visual comparisons reliable with readiness settings and Docker networking tips.

By the ScreenshotNeo team4 October 20267 min read

To use BackstopJS with a local development server, start your app, set a scenario’s url to the exact local address and port, then run backstop reference to save a baseline. After a code change, run backstop test and review the comparison report. The scenario URL tells BackstopJS where to navigate; it does not start your server.

1. Install and initialize BackstopJS

From your project directory, install BackstopJS locally and initialize its configuration if you do not already have one:

npm install --save-dev backstopjs
npx backstop init

Initialization can create or overwrite files. Check your existing configuration and working tree before running it in a project that already has BackstopJS files. The official setup and configuration guidance is in the BackstopJS npm documentation.

You can add convenient project commands to package.json:

{
  "scripts": {
    "visual:init": "backstop init",
    "visual:reference": "backstop reference",
    "visual:test": "backstop test",
    "visual:approve": "backstop approve"
  }
}

These scripts run BackstopJS only. Start the development server separately unless your project already has a server orchestration command.

2. Start the app and set the scenario URL

Start the app with the command used by your project, then use the address it actually listens on. For example, use http://localhost:3000/ only if the app is listening on port 3000. A minimal scenario in backstop.json looks like this:

{
  "id": "local-app",
  "viewports": [
    { "label": "desktop", "width": 1440, "height": 900 }
  ],
  "scenarios": [
    {
      "label": "Local home page",
      "url": "http://localhost:3000/"
    }
  ],
  "engine": "puppeteer",
  "report": ["browser"],
  "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"
  },
  " misMatchThreshold": 0.1
}

Remove the space in the last key: the valid option name is misMatchThreshold. Here is the corrected ending of the configuration:

  "misMatchThreshold": 0.1
}

Keep the scenario label descriptive so reports make it clear which page and state were captured. Add a separate scenario for each important route or state. If the reference should come from another environment, set referenceUrl for that scenario while keeping url pointed at the local page under test.

3. Capture a reference and compare changes

  1. With the app running and reachable, run npx backstop reference to capture the baseline images.
  2. Make the code or style change you want to inspect.
  3. Run npx backstop test to capture fresh images and compare them with the references.
  4. Review the report and approve only intended visual changes with npx backstop approve. Approval updates reference images to the latest test images.

BackstopJS documents this reference, test, and approve workflow in its README. Treat approval as a deliberate baseline update, not as a way to make an unexplained diff disappear.

4. Make screenshots wait for the page

A page can answer at its URL before its useful content is ready. Use scenario readiness settings to coordinate capture with the app:

{
  "label": "Local dashboard",
  "url": "http://localhost:3000/dashboard",
  "readySelector": "[data-visual-ready='true']",
  "readyTimeout": 30000,
  "delay": 500
}

readySelector waits for a DOM selector, and readyTimeout sets the maximum wait; BackstopJS documents a default timeout of 30,000 ms. delay adds a fixed wait after readiness conditions. Use a selector that appears only after the content relevant to the screenshot is ready. A long delay can hide timing problems and slows every capture, so prefer a meaningful readiness condition.

You can also use readyEvent when the application can signal readiness from the page. The BackstopJS example uses the event name backstopjs_ready; your app must wait for its dependencies and then log the configured event. For browser state setup and interaction, scenario options include onBeforeScript and onReadyScript. Scenario-level settings override global settings, so make sure a scenario does not accidentally replace an inherited readiness or interaction value.

Dynamic timestamps, rotating promotions, randomized data, and asynchronous third-party content can produce differences unrelated to your code change. Use stable test data or representative stubs when appropriate, and keep cookies, viewport, browser engine, selectors, interactions, and data state consistent across reference and test runs. See the official scenario and readiness guidance.

5. Reach the host from Docker

When BackstopJS runs with --docker, its browser runs inside a container. In that setup, localhost refers to the container, so a scenario using http://localhost:3000/ may not reach the development server on your host. The BackstopJS README gives host.docker.internal as an example for Mac and Windows:

{
  "label": "Local app from Docker",
  "url": "http://host.docker.internal:3000/"
}

Use a host name supported by your Docker host environment. Do not change an ordinary non-Docker local URL just because this Docker caveat exists.

6. Choose settings that keep comparisons useful

Setting or condition What to keep consistent Why it matters
Viewport Use the same width and height for reference and test. Responsive layouts change with viewport size.
Browser engine and browser Use the same configured engine and browser choice. Rendering differences can appear as visual diffs.
URL and data Capture the same route and representative data state. Content changes can look like layout regressions.
Readiness and interactions Use the same readiness conditions and UI interactions. Capturing at different page states makes comparisons noisy.
Selectors Keep any configured capture or exclusion selectors stable. Changing the captured region changes what is compared.
Mismatch threshold Set a threshold deliberately and review the diff. A threshold affects whether differences are reported; it does not explain them.
Reference refresh Approve only after reviewing intended changes. Approval replaces the baseline images.

7. Troubleshoot common failures

Symptom Likely cause Fix
Connection refused or navigation fails The server is stopped, the port is wrong, or the server is bound to an address the browser cannot reach. Start the app, confirm the actual scheme, host, and port, then visit that URL from the same environment where BackstopJS runs.
Works locally but fails with --docker localhost points to the container. Use the host-access name supported by your environment, such as the documented host.docker.internal example on Mac and Windows.
Screenshot is blank or missing page content The app returned a shell before rendering useful content, or readiness was not synchronized. Set a suitable readySelector or readyEvent, increase readyTimeout if loading legitimately takes longer, and confirm the marker is actually reached.
Readiness timeout The selector never appears or the configured event is never emitted. Check selector spelling and application state; ensure the app logs the event after dependencies finish; use a longer timeout only when the page genuinely needs it.
Intermittent visual diffs Unstable data, animation, timestamps, third-party widgets, or inconsistent browser conditions. Use static or representative data where appropriate, align viewport and browser settings, and wait for the same state each run.
Unexpected scenario behavior A scenario-level value overrides a global setting. Inspect the effective scenario configuration, particularly readiness selectors, events, and scripts.
Approved references changed unexpectedly backstop approve updates reference images to the latest test captures. Review the report before approving; restore references from version control if an unintended update was committed.

8. Performance, reliability, and cost

Capture time depends on the number of scenarios and viewports, page load behavior, and any readiness waits or delays. Keep the scenario set focused on routes and states that provide useful regression coverage. A selector or event tied to real application readiness is generally more predictable than an arbitrary long delay. Stable inputs make the report easier to interpret and reduce reruns caused by unrelated page variation.

For reliable runs, start the server before BackstopJS, confirm the URL from the browser’s execution environment, keep reference and test conditions aligned, and review diffs before updating references. BackstopJS is a project dependency and runs against your app; the cited setup material does not prescribe a universal hosted service price for this local workflow. Any infrastructure cost depends on how your project runs its development server and browser.

Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server for developers. For a one-off screenshot without configuring a local browser workflow, send a GET request to its API; use a publicly reachable URL such as Stripe’s homepage:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

See the ScreenshotNeo API documentation for request options. ScreenshotNeo accepts cookie banners like a visitor and removes 60+ known consent platforms, newsletter popups, and chat widgets before capture; each step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing, 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 a month with no card; paid plans start at $5 for 3,000 screenshots.

Create a free ScreenshotNeo account to get 1,000 screenshots a month with no card.

FAQ

Does BackstopJS start my development server?

No. Start it separately or use your project’s own orchestration, then point the scenario URL at the running app.

Should I use localhost or 127.0.0.1?

Use the host name that resolves to the app from the environment running the BackstopJS browser. In Docker, that may be a host-access name instead of localhost.

When should I approve a reference?

After reviewing the comparison and confirming that the appearance change is intentional.

Can I compare a local page with a deployed reference?

Yes. Configure the scenario’s referenceUrl for the reference environment and url for the local page under test.