Visual Regression Testing with Chromatic
Set up Chromatic visual tests with Storybook, review pixel diffs, gate pull requests in CI, and understand when Playwright or Cypress fits.
Chromatic runs visual regression tests by rendering your UI in cloud browsers, capturing screenshots, comparing each capture with an accepted baseline, and sending the differences to your team for review. The usual entry point is Storybook: each story becomes a visual test. You can also send Vitest browser-mode, Playwright, and Cypress tests to Chromatic’s Capture Cloud.
This guide covers setup, baseline review, CI merge gates, browser and viewport variation, test authoring, troubleshooting, performance, reliability, cost decisions, and an API alternative when you only need screenshots.
How Chromatic visual testing works
- Cloud rendering: Chromatic loads your Storybook or supported test in a standardized browser environment.
- Snapshot capture: It waits for the test state, then captures rendered pixels (and, where configured, accessibility data).
- Automated diffing: The new snapshot is compared with the previously accepted baseline.
- Review and verification: Reviewers inspect the diff, accept intentional changes, or reject regressions. A result can be reported back to a pull request as a status check.
Visual tests catch appearance defects that functional tests can miss: a shifted layout, wrong font, clipped text, missing image, changed color, or broken responsive state. Storybook describes the core idea as comparing the rendered pixels of every story against known baselines (Storybook visual testing documentation). Chromatic’s workflow is documented in its visual testing documentation.
Prerequisites and project choices
- A Storybook project. The official Chromatic Storybook addon supports Storybook 7.6 or later according to the addon registry.
- A Chromatic project connected to the repository.
- Stable stories with deterministic data, fonts, and asset URLs.
- A CI secret containing the Chromatic project token.
Decide which states deserve coverage before adding tests. Include loading, empty, error, authenticated, dark-mode, long-content, and responsive stories when those states can regress independently. Keep stories focused: a small, isolated story produces a diff that is easier to review than an entire application page.
Set up Chromatic with Storybook
1. Install the official addon
npx storybook add @chromatic-com/storybook
The CLI installs and configures the addon. If your project uses a different package manager, run the equivalent command with that package manager. The addon adds a Visual Tests panel inside Storybook and connects the project to Chromatic.
2. Create or connect a Chromatic project
Open the Chromatic project setup flow, connect the repository, and copy the project token. Store the token in your local environment while developing and in your CI secret store for pull requests. Do not commit the token to source control.
export CHROMATIC_PROJECT_TOKEN=your_project_token
npx chromatic --project-token="$CHROMATIC_PROJECT_TOKEN"
The command builds Storybook, uploads the build, renders stories in Chromatic’s cloud browsers, and returns a build URL for review. The exact command can be placed in a package script:
{
"scripts": {
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build",
"chromatic": "chromatic --project-token=$CHROMATIC_PROJECT_TOKEN"
}
}
3. Review the first baseline
- Run the command once from a clean branch.
- Open the build in Chromatic and inspect every changed story.
- Accept only changes that are intentional and visually correct.
- Use the accepted build as the baseline for later comparisons.
Do not bulk-accept a first run without inspection. An incorrect baseline makes later diffs less useful.
Write stories that produce useful visual tests
Chromatic treats each Storybook story as a test when visual testing is enabled. A story should render the same pixels every time it runs.
import type { Meta, StoryObj } from '@storybook/react';
import { PricingCard } from './PricingCard';
const meta = {
title: 'Billing/PricingCard',
component: PricingCard,
parameters: {
layout: 'centered',
},
} satisfies Meta<typeof PricingCard>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Monthly: Story = {
args: {
plan: 'Growth',
price: 15,
interval: 'month',
highlighted: true,
},
};
export const LongPlanName: Story = {
args: {
plan: 'Enterprise annual with custom seats',
price: 249,
interval: 'year',
highlighted: false,
},
};
Make rendering deterministic
- Use fixed fixture data instead of the current time, random IDs, or live API responses.
- Wait for fonts and images before capture. A fallback font can move every text node and create a noisy diff.
- Mock network calls and keep the response payload stable.
- Give animations a disabled or completed state for screenshot stories.
- Use explicit viewport and theme parameters when a component changes at those boundaries.
- Keep browser extensions, local CSS, and developer-machine fonts out of the test assumptions.
Run Chromatic in CI and gate pull requests
Run Chromatic after dependencies are installed and before the pull request is considered mergeable. Storybook documents integrations for GitHub Actions, GitLab Pipelines, Bitbucket Pipelines, CircleCI, Travis CI, Jenkins, Azure Pipelines, and custom CI providers (Storybook CI guidance).
GitHub Actions example
name: Visual tests
on:
pull_request:
jobs:
chromatic:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: npm ci
- run: npx chromatic --project-token=${{ secrets.CHROMATIC_PROJECT_TOKEN }}
env:
CHROMATIC_PROJECT_TOKEN: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
Mark the resulting UI Tests check as required in your repository branch protection rules when a visual regression must block merging. Keep the token in the CI secret store and limit who can change that secret.
Local development versus CI
- Local: use the Visual Tests panel while authoring a story and inspect the diff immediately.
- Pull request: run the same command in CI so reviewers see the exact branch result.
- Default branch: accept intentional changes there to establish the next baseline.
Test inputs beyond Storybook
Chromatic supports Storybook stories plus Vitest browser-mode, Playwright, and Cypress tests. This is useful when a visual state requires navigation, a real interaction sequence, or an end-to-end fixture that is awkward to express as a story. The test produces a snapshot in Chromatic’s Capture Cloud, where browser, viewport, theme, and device variation can be standardized (Capture Cloud documentation).
When to use each input
| Input | Best fit | Typical example |
|---|---|---|
| Storybook | Component and page states | Button variants, tables, dialogs, error states |
| Vitest browser mode | Browser-aware component tests | Rendered assertions plus a visual snapshot |
| Playwright | Multi-step browser journeys | Sign-in flow, navigation, responsive page |
| Cypress | Existing Cypress application tests | Interactive workflows with visual checkpoints |
Keep the authoring tool aligned with the question. A component regression belongs in a story; a route-level regression may be clearer in Playwright or Cypress.
Browsers, devices, themes, and viewports
Capture the states that your users actually receive. A responsive component can look correct at a desktop width and fail at a narrow mobile width. A theme-aware component can pass in light mode while its dark-mode contrast or icon asset is wrong.
- Choose a representative desktop viewport and the narrowest supported mobile viewport.
- Add a dark-theme story or test when colors, shadows, or assets change by theme.
- Use device or mobile simulator coverage when touch layout differs from desktop layout.
- Capture more than one browser when browser-specific CSS or font rendering matters.
Each additional browser, device, theme, or viewport creates more snapshots to review. Start with the smallest matrix that covers a real risk, then expand when a defect or support requirement justifies it.
Baselines, branches, and reviewing diffs
Use the previous accepted result as the comparison point. In a pull request, review the changed region and the surrounding context: a one-pixel border change may be intentional, while a large diff can indicate a font, viewport, or asset-loading problem.
- Read the story name and test state before looking at the pixels.
- Check whether the diff is isolated or repeated across every story.
- Inspect the actual page at the same state if the cause is unclear.
- Accept the change only when the product requirement changed.
- Reject or fix the change when it is caused by flaky data, missing assets, layout shift, or an unintended style.
TurboSnap and capture efficiency
Chromatic documents TurboSnap as an optimization that avoids unnecessary work when associated code has not changed. It can reduce captured work in large Storybook projects. Check the current Chromatic pricing documentation before making quota or billing assumptions, because plan limits and snapshot accounting can change.
Other practical ways to keep runs manageable:
- Split unrelated component families into focused stories.
- Remove duplicate stories that exercise the same pixels and state.
- Use deterministic fixtures so retries do not produce false diffs.
- Run the broadest browser and viewport matrix in CI, while using a focused matrix during local iteration.
Or skip the browser setup
If you need a clean screenshot of a URL instead of a Storybook test suite, ScreenshotNeo provides a single GET request. It accepts the consent banner like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each cleanup step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers. Its MCP server gives Claude, Cursor, and other MCP clients take_screenshot, get_page_info, and capture_pdf tools.
See the ScreenshotNeo API documentation for all options. This is a URL capture workflow, so it complements Chromatic rather than replacing story-level baseline review.
cURL
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()
open("shot.webp", "wb").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 failed: ${res.status}`);
const data = Buffer.from(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', data));
ScreenshotNeo includes full-page capture with lazy images loaded, CSS-selector element capture, dark mode, 12 device presets or any viewport, retina scale, PDF output, custom CSS and JavaScript, clicks, selector or network-idle waits, request and resource blocking, custom headers and cookies, timezone and geolocation, transparent backgrounds, resizing, TTL caching, signed links, asynchronous jobs with signed webhooks, bulk capture for up to 100 URLs per call, usage data, and an OpenAPI specification. Parameter names used by other screenshot APIs also work, which can simplify migration.
Plans include 1,000 screenshots per month free with no card. Paid plans start at $5 for 3,000 shots; yearly billing gives two months free, and every feature is available on every plan. Create a free ScreenshotNeo account to try the capture API.
Troubleshooting Chromatic visual tests
| Symptom | Likely cause | Fix |
|---|---|---|
| Every story has a large text diff | Font did not load or a different font version is present | Load the font in the test build, wait for it before capture, and pin the font asset/version. |
| Only images differ | Unstable remote assets, timestamps, or image optimization | Use fixed fixtures or a stable asset host; mock dynamic URLs. |
| Stories time out | A network request, animation, or selector never reaches a settled state | Mock the request, disable the animation, and make the ready condition explicit. |
| Local run passes but CI fails | Different environment, viewport, browser, or environment variable | Reproduce with the CI build, pin dependencies, and check required secrets and asset access. |
| Unexpected changes after a dependency update | Browser, font, CSS, or transitive package rendering changed | Review the diff as a dependency change; accept only if the new rendering is intended. |
| Visual Tests panel is missing | Addon was not installed or Storybook version is unsupported | Run the Storybook addon command and confirm Storybook meets the documented version requirement. |
| Pull request is not blocked | The CI check is not required in branch protection | Add the UI Tests check as a required status check for the protected branch. |
| Too many snapshots to review | Redundant stories or an unnecessarily broad matrix | Remove duplicate states and keep browser/device coverage tied to supported user environments. |
Performance, reliability, and cost considerations
Performance
Cloud capture adds upload, build, queue, rendering, and diff time. Reduce avoidable work with deterministic stories, TurboSnap where appropriate, focused test matrices, and stable assets. Parallelization and hosted browsers remove the need to operate your own screenshot workers, but the total number of captures still grows with stories multiplied by browsers, devices, themes, and viewports.
Reliability
Visual tests are only as reliable as their inputs. Eliminate time-dependent content, random values, live API responses, layout shifts, and animation. When a diff is noisy, fix the source of nondeterminism instead of lowering review standards. Keep baseline acceptance auditable through pull-request review.
Cost
Verify current Chromatic plan prices, snapshot quotas, browser coverage, and overage rules on its pricing page before publishing a budget or setting a large matrix. TurboSnap may reduce captured work, but quota and billing effects should be checked against the live terms. For one-off URL screenshots, ScreenshotNeo’s free monthly allowance and fixed paid tiers can be simpler to estimate.
Recommended adoption checklist
- Confirm Storybook is on a supported version.
- Install
@chromatic-com/storybookwithnpx storybook add. - Create or connect the Chromatic project and store its token as a secret.
- Run an initial build and review every baseline.
- Add stories for important visual states, including responsive and theme variants.
- Mock data, fonts, images, and animations that would otherwise change between runs.
- Add Chromatic to pull-request CI.
- Make the UI Tests check required before merge when regressions must block merging.
- Review current pricing and snapshot accounting before expanding coverage.
FAQ
Does Chromatic replace unit or functional tests?
No. Functional tests verify behavior; Chromatic verifies rendered appearance. Use both when a change can break interaction and pixels.
Can I use Chromatic without Storybook?
Yes. Chromatic supports Vitest browser-mode, Playwright, and Cypress inputs in addition to Storybook stories.
What should I accept as a baseline?
Accept a baseline only after confirming that the visual change is an intentional product change and that fonts, assets, data, and viewport are correct.
Should every component have a story?
Cover components whose appearance or states can regress. Prioritize shared components and high-risk layouts, then expand coverage as defects show where it is valuable.
Is ScreenshotNeo the same kind of tool as Chromatic?
No. Chromatic manages visual-test inputs, baselines, diffs, and review. ScreenshotNeo is a website screenshot API and MCP server for capturing clean URL images or PDFs, with billing protections for failed or unusable captures.


