Skip to content

CLI Workflow

Cappa provides a focused CLI with commands that mirror the visual regression lifecycle. Each command operates on the screenshots defined in your cappa.config.ts file.

Runs Playwright to render every configured scenario and writes the resulting images to the configured outputDir. Use flags such as --log-level to adjust verbosity when diagnosing flaky tests.

Use --ci to run capture in CI mode. In CI mode, Cappa executes the onFail callback after capture, if any screenshots fail comparison.

Terminal window
# capture screenshots locally
cappa capture

Cappa also enables the same CI mode automatically when CI=true is set in the environment, so cappa capture behaves like cappa capture --ci in most CI/CD pipelines.

Use --filter (or -f) to capture only tasks whose id matches a glob pattern. This is useful when you want to iterate on a single component without re-capturing everything. The pattern is matched against each task’s id using Node’s built-in glob matching.

Terminal window
# capture only button stories
cappa capture --filter "button*"
# capture a single story by exact id
cappa capture -f "card--default"

When a filter is active, Cappa displays a hint box and logs how many tasks matched per plugin so you can verify the pattern is selecting what you expect.

When a run fails, Cappa prints a Changed Screenshots box after the failure report listing every changed screenshot with its diff percentage. When diff.interpret is enabled, the box also includes the severity, the human-readable summary and each detected region with its change type, position and bounding box:

Changed Screenshots
Button/Primary · MEDIUM · 1.87% · 2 regions
Moderate visual change detected (1.87% of image, 2 regions).
→ content at center · 0.90% · 360x200 at (420, 300)
→ added at right · 0.50% · 180x140 at (960, 120)

Regions are listed largest first, so the biggest change is always at the top. At most five are listed per screenshot; the rest are collapsed into a … and N more regions line. Because the report is printed to stdout, it gives you the where and what of a diff directly in your CI logs, without having to download the diff artifacts or open the review UI.

Use --max-regions to change how many regions are listed. It accepts any non-negative integer, where 0 omits the per-region breakdown and keeps only the severity line and summary. The same flag is available on cappa status.

Terminal window
# list up to 20 regions per screenshot when debugging a CI failure
cappa capture --max-regions 20
# severity and summary only
cappa capture --max-regions 0

Use --watch (or -w) to capture once and then re-capture whenever files change. The browser stays warm between iterations, so a save costs a capture rather than a Chromium start-up.

Terminal window
# capture, then re-capture on every save
cappa capture --watch
# narrow the whole session to one component
cappa capture --watch --filter "button*"

Each iteration prints one line for what changed, one line per non-passing task, and a summary:

↻ 2 tasks · src/components/Button.stories.tsx changed
changed Example/Button/Primary
2/2 captured, 1 to review — 1.42s

A failing screenshot does not stop the session or set a non-zero exit code — it is the thing you are working on. Press Ctrl-C to stop; that closes the browser and exits 0.

How much gets re-captured depends on what the plugins can tell Cappa about the file you saved:

You changed What runs
A story file, with @cappa/plugin-storybook Only the stories that file declares
A component, or any file no plugin can attribute Every task of the plugins that could not tell
Anything, with a plugin that has no watch support (@cappa/plugin-pages) Every task of that plugin
Hundreds of files at once (a branch switch, a formatter) Everything, under --filter

Changes are debounced for 300ms and coalesced, so a branch switch produces one run rather than a storm. A save that lands while a capture is still running is queued for the next iteration instead of being rejected. actual/ is never cleared between iterations, so the diffs you are looking at for screenshots you did not touch stay where they are.

--watch cannot be combined with --ci (CI captures once and exits) or with --server (a remote host cannot see the files on your machine). Both combinations fail with a message rather than quietly doing half of what you asked. See Custom Plugins for teaching your own plugin which tasks a file affects.

Use --server to run the browser on another machine — one running cappa serve — instead of starting one locally. The output, the failure report and the exit code are the same as a local run.

Terminal window
cappa capture --server http://build-host:3000 --token "$CAPPA_TOKEN"

See Remote capture for the full setup and the two ways a remote run differs.

Hosts a capture engine for cappa capture --server. It is cappa review without the assumption that a person is about to open a browser: it prints one structured line and then stays quiet until you stop it.

Terminal window
cappa serve # loopback, UI included
cappa serve --no-ui # API only
cappa serve --host 0.0.0.0 --token <value> # reachable from other machines
cappa serve --read-only # serve a report; refuse captures
Flag Meaning
--port <port> Port to listen on. Defaults to review.port.
--host <host> Host to bind. A non-loopback host requires a token.
--token <token> Access token required on every /api/* request. Falls back to CAPPA_TOKEN.
--read-only Refuse capture, approval and every other mutation.
--no-ui Serve the API only; skip the review UI’s static files.

Unlike cappa review, serve never generates a token for you. review generates one because a human is reading the URL it prints; serve has no such reader, so a generated token would produce a daemon nobody can authenticate against. Off loopback it refuses to start without one:

ERROR Binding to 0.0.0.0 exposes capture control beyond this machine, so an access token
is required. Pass --token <token>, or set CAPPA_TOKEN to keep it out of the process list.

SIGINT and SIGTERM shut the server down cleanly, closing the browser before exiting — a SIGTERM’d serve leaves no orphaned Chromium processes behind.

Launches an interface for comparing the latest screenshots against the approved baseline. The review step highlights diffs so you can decide whether the change is expected. Screenshots are grouped and sorted by status so that new, deleted, changed, and finally passed screenshots appear in a predictable order while you browse the UI. Pair it with your preferred branching strategy to share reviews with teammates.

The review UI supports batch approval: in grid or list view you can select multiple screenshots (with checkboxes and “Select all”), then use Approve selected to promote them to the baseline in one action. On category pages (e.g. New or Changed), Approve all in category approves every screenshot in the current view at once.

A screenshot’s detail page has Prev and Next controls (and the ← / → keys) that walk the whole list in that same order. The order is fixed when you open the review and does not move under you: approving the screenshot you are looking at re-categorises it, but Next still goes to the one that came after it, so you can work straight through the changed screenshots by alternating Approve and Next. Approving a deleted screenshot accepts the deletion — the screenshot itself is gone at that point, so the UI moves on to the next one rather than staying on a page that no longer has anything to show.

When diff.interpret is enabled, changed screenshots also show a severity badge, a summary banner and interactive, color-coded region overlays on the diff view so you can see at a glance what changed and where.

The review UI can also drive the capture itself: start a run, watch it live, cancel it, or re-capture a single screenshot without leaving the page. See Interactive UI.

Terminal window
cappa review --port 4000 # override review.port
cappa review --read-only # serve a report with capture and approval disabled
cappa review --host 0.0.0.0 # expose beyond this machine (requires a token)
cappa review --token <value> # supply the token yourself instead of generating one

Binding to anything other than a loopback host exposes control of a browser that reads local files, so Cappa requires an access token there and generates one — printed as part of the URL — when you do not pass --token. --token also reads CAPPA_TOKEN from the environment, so the token need not appear in the process list.

Promotes the currently reviewed screenshots to the baseline. This command is typically run after a manual review or as a final step in an automated pipeline when the diffs are accepted.

During approval Cappa re-compares the latest actual image against the existing baseline and only updates the expected file when they truly differ. This prevents identical images with minor binary differences from needlessly churning in version control and keeps your baseline commits tidy.

Use --filter to approve only a subset of screenshots by name or path. Matching is a case-insensitive substring search, so --filter button matches Button/Primary, Button/Secondary, and so on. Pass the flag multiple times to supply more than one pattern — a screenshot is approved if it matches any of them.

Terminal window
cappa approve --filter Button --filter Input

Summarizes the state of your screenshot directory, showing how many files are pending approval and whether any captures are missing. When any screenshots are new, changed, or deleted, the command exits with code 1, making it suitable as a CI/script gate:

Terminal window
cappa status && deploy.sh

When diff.interpret is enabled, status also prints a per-screenshot breakdown of the changed screenshots, including the diff percentage, severity, region count, a human-readable summary of what changed and the individual regions — the same report cappa capture prints on failure, and it accepts the same --max-regions flag.

Tip: combine the commands with npm scripts to make common workflows easy to discover for the rest of your team.