Install BackstopJS in your project, keep its approved reference screenshots under version control, make the app reachable from the GitLab runner, and run npx backstop test in a CI job. Enable BackstopJS’s CI report and publish its JUnit XML with GitLab’s artifacts:reports:junit. The test command’s exit status—not the report upload—must fail the job when comparisons fail.
How the integration works
BackstopJS captures configured pages and compares them with approved reference screenshots. Its workflow is init, test, then approve: approval promotes the latest test captures into the baseline used by future runs. Treat approval as a reviewed change, not an automatic reaction to every failed CI run. See the BackstopJS README.
GitLab can ingest BackstopJS’s JUnit XML and show test results in pipeline and merge request views. However, GitLab explicitly says JUnit report artifacts do not determine job status: “Unit test reports require the JUnit XML format and do not affect job status. To make a job fail when tests fail, your job’s script must exit with a non-zero status.” See GitLab’s unit test reports documentation.
Prepare BackstopJS and its baselines
- Add the dependency. Install BackstopJS as a project dependency and commit the lockfile. The package metadata for version 6.3.25 lists Node.js 16 or later and npm 8 or later; use the version pinned by your own lockfile to choose a compatible CI image. Check the BackstopJS package metadata and your installed version’s documentation for version-sensitive behavior.
- Initialize and configure it locally. Run
npx backstop init. Add at least one viewport and one or more scenarios; each scenario needs a label and URL. The URL must resolve from the runner’s network context in CI, not merely from your laptop. - Create references deliberately. Run the initial capture and review the screenshots before establishing the approved reference set. Commit those reference files, or otherwise make the approved set available to the test job. Review later baseline updates as code changes.
- Make the application available to the test job. Build or start the site before the visual test runs, or connect the job to an environment where it is already running. If another job builds or deploys the app, arrange job order and network access accordingly. The correct host name and route depend on your GitLab runner and deployment setup.
Configure a GitLab CI job and JUnit report
Enable BackstopJS’s CI report in its configuration, for example with "report": ["CI"]. The README documents a default JUnit filename of xunit.xml and lets you customize the report directory, filename, and test suite name. Set GitLab’s report path to the actual output location. For example, configure paths.ci_report as backstop_data/ci_report, then use:
The Tool Desk
Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →#1 Best Overall
visual_regression:
stage: test
script:
- npm ci
- npm run build
# Start the application or connect to it here; it must be reachable by this job.
- npx backstop test
artifacts:
when: always
paths:
- backstop_data/ci_report/
reports:
junit: backstop_data/ci_report/xunit.xml
This is a starting pattern, not a drop-in configuration: supply the project’s actual build and app-start steps, a Node image compatible with the pinned BackstopJS version, and the report path configured for the project. Including the report directory under artifacts:paths makes its files browsable as artifacts; artifacts:when: always requests upload even when the job fails. GitLab’s JUnit setting accepts an XML filename, glob, or array of report paths, not a directory by itself. The XML filename must end in .xml. GitLab documents limits of less than 30 MB per report file and less than 100 MB total per job; duplicate test names are ignored after the first occurrence. Consult GitLab’s report documentation for current behavior.
Check the failure gate
Keep npx backstop test in the job’s script and verify the pinned BackstopJS version returns a non-zero exit code on a visual-test failure. Do not infer that the job is a merge gate merely because GitLab displays a failed JUnit test or uploads an artifact: report ingestion is separate from the command’s exit status.
Rank #2
Choose a rendering environment
| Approach | What to expect | Trade-offs to check |
|---|---|---|
| Run directly in the CI job | BackstopJS runs with the browser and dependencies available in the job’s environment. | Differences between local and CI rendering may affect comparisons. Ensure the runner image has the required browser environment and that generated files are accessible to the job. |
Use BackstopJS’s --docker option |
BackstopJS documents this option as a way to reduce rendering differences. It invokes Docker and uses a versioned BackstopJS image by default. | The runner must be able to use Docker; check container permissions, file ownership, artifact access, and app networking. For CI-style piped output, the README notes removing -t from the default Docker command template. |
Docker is an option, not a requirement. BackstopJS’s README warns that in the cited Mac/Windows setup, localhost does not reach the host from its Docker rendering environment and suggests host.docker.internal there. That hostname is not a universal GitLab runner solution: verify the actual runner’s network route to the app before copying it. See the BackstopJS README.
Make visual failures easier to inspect
GitLab’s JUnit report gives structured test feedback, while job logs remain useful for diagnosing command and setup failures. To retain evidence after a failed comparison, upload the report and relevant screenshot files as artifacts with artifacts:when: always. GitLab also documents JUnit system-out attachment tags for screenshot attachments; upload the associated screenshot files as artifacts so they are available to reviewers. Follow the formatting requirements in GitLab’s unit test report documentation.
Free tools Windows power users keep installed
One-click scans. No signup required.
Rank #3
Troubleshoot common CI failures
- The test cannot open a scenario URL: The URL may work on your machine but not from the runner. Confirm the app is running before the test and use a hostname and port reachable in that job’s network context.
- Docker rendering cannot reach the app: A container’s
localhostmay refer to the container itself, not the host or another service. Configure a route appropriate to the runner. The BackstopJS README’shost.docker.internalnote applies to its cited Mac/Windows setup, not every CI environment. - The report is missing from GitLab: Confirm CI reporting is enabled, that
paths.ci_reportand the generated filename match the YAML path, and that the XML file exists when the job ends. Use an XML filename—not only a directory—inreports:junit. - The job succeeds despite a reported visual failure: GitLab does not fail jobs based on JUnit report contents. Check the test process’s exit code and the behavior of the version pinned in your lockfile.
- Reports or screenshots disappear after a failed test: Use
artifacts:when: alwaysand include the output files underartifacts:pathsas well as the JUnit report declaration. - Results differ between local runs and CI: Rendering environments can differ. Consider BackstopJS’s Docker option if the runner supports it, then verify browser dependencies, network access, and generated-file permissions.
- JUnit results are incomplete: Check the XML extension, documented file-size limits, total report size, and duplicate test names. GitLab ignores duplicate test names after their first occurrence.
Or skip the browser setup
For one-off screenshots or a separate screenshot workflow, ScreenshotNeo offers a website screenshot API and MCP server. Its API can return an image or PDF; it is not a replacement for BackstopJS’s approved-reference comparison and CI failure gate. A one-call capture looks like this:
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. ScreenshotNeo accepts cookie and consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each of those steps can be turned off. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for AI agents. The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots. Sign up for ScreenshotNeo’s free plan.
Frequently Asked Questions
Can BackstopJS use local scenario URLs?
Yes, but a local URL must resolve from the GitLab runner’s network context when the CI job runs.
Does GitLab require a particular JUnit report filename?
The report must be JUnit XML with an .xml extension; GitLab accepts a file path, glob, or array of paths.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




