CLI reference
qain snap <url> [options] capture a snapshot
qain diff <before> <after> [opts] compare two snapshots
qain view <snapshot> [opts] rebuild the page from a snapshot, as HTML
qain shot <before> <after> [opts] render before.png, after.png and diff.pngqain --help prints the same. Exit codes across all commands:
| code | meaning |
|---|---|
0 |
success — for diff, an empty diff |
1 |
diff only: the diff is non-empty |
2 |
usage error, capture error, or unreadable snapshot |
qain snap <url>
Opens the URL in a fresh headless Chromium, waits for it to settle, and writes a snapshot as JSON — to stdout by default, so it pipes.
qain snap http://localhost:3000 --selector '[data-testid=card]' --rules -o card.json| option | default | what |
|---|---|---|
-o, --out <file> |
stdout | Where to write the JSON. With -o, a one-line summary and any capture warnings go to stderr. |
--selector <css> |
whole document | Scope the snapshot to the first element matching this selector, and its descendants. Errors if nothing matches. |
--states <list> |
none | Comma-separated pseudo-states to force and capture, each as its own state: hover, focus, focus-visible, focus-within, active, target. |
--viewport <WxH> |
1280x720 |
Viewport size, e.g. 390x844. |
--wait <ms> |
0 |
Extra settle time after load. |
--wait-for <css> |
— | Wait for this selector to appear before capturing. |
--strategy <mode> |
auto |
Pseudo-state capture strategy: auto, bulk, or isolated. See below. |
--rules |
off | Also record matched CSS declarations, so qain diff can name the file:line behind each change. One CDP round-trip per node, ~3× snapshot size. |
--ua-rules |
off | Include the user-agent stylesheet in --rules. Large, and nobody edited it. |
--replay |
off | Record per-line text rectangles, so view, diff --replay, and shot can rebuild the page without re-running layout. Pass it on both snapshots you intend to diff visually. |
--browser <path> |
Playwright's managed Chromium | Chromium/Chrome executable to launch. |
--headed |
off | Run with a visible window — useful for debugging what the page looked like at capture time. |
What "settled" means
snap captures after, in order: the load event, --wait-for (if given),
document.fonts.ready — webfonts swap in late and would otherwise change every
font-family in the snapshot — and finally --wait milliseconds. If the page
animates or streams in content, give it an explicit --wait-for or --wait;
qain does not guess.
Pseudo-state strategy
Forcing every button into :hover at once is a page state that never occurs
in a real browser — safe only if hovering changes nothing outside the hovered
elements. --strategy auto (the default) takes one bulk snapshot, verifies
nothing outside the forced subtrees moved or restyled, and silently falls back
to one snapshot per element when it did. bulk and isolated pin the choice:
bulk is fast, isolated is always correct.
qain diff <before> <after>
Compares two snapshot files and prints a human-readable report to stdout.
Exit code 1 when the diff is non-empty.
qain diff before.json after.json --omit-derived
qain diff before.json after.json --json > diff.json
qain diff before.json after.json --replay report.html| option | default | what |
|---|---|---|
--omit-derived |
off | Drop changes that are only collateral movement — a node that moved because something above it grew. The terse view. |
--json |
off | Emit the diff as JSON instead of text. |
--html <file> |
— | Also write a standalone HTML report. |
--replay <file> |
— | Also write a before/after visual replay you can fade between. Requires both snapshots to carry snap --replay data; errors otherwise. |
--serve |
off | Host the view on localhost instead of writing a file, and stay up until Ctrl-C. Serves the before/after replay when both snapshots carry --replay data, otherwise the HTML report. |
--port <n> |
4179 |
Port for --serve. Falls back to a free port if this one is taken. |
--tolerance <px> |
0.5 |
Sub-pixel box movement below this is not a change. Raise it if a font renders a fraction of a pixel differently across machines. |
--no-color |
auto | Plain text. Color is on only when stdout is a TTY. |
The two snapshots do not need to come from the same URL — comparing
staging against production, or ?theme=light against ?theme=dark,
works because elements are matched by identity key,
not by URL or DOM position.
qain view <snapshot>
Rebuilds the page from a snapshot as a standalone HTML file — a reconstruction, not a screenshot. Every element is placed at the rectangle Chromium gave it; nothing reflows.
qain view page.json -o page.html
qain view page.json --serve # open it on localhost| option | default | what |
|---|---|---|
-o, --out <file> |
stdout | Where to write the HTML. |
--state <name> |
default |
Which captured state to draw: default or one of the captured pseudo-states. |
--serve |
off | Host the rebuilt page on localhost instead of writing it, and stay up until Ctrl-C. |
--port <n> |
4179 |
Port for --serve. Falls back to a free port if this one is taken. |
A snapshot captured without --replay still renders, but as boxes with no
text placed in them — view warns when that is the case.
The replay and view pages are interactive: ⌘/Ctrl + scroll or the
− 100% + control zooms the canvas, drag pans it, and clicking a
change — in the canvas or the causes list — spotlights every box for that
node (Esc clears it).
qain shot <before> <after>
Renders both snapshots in a headless Chromium and writes three PNGs:
before.png, after.png, and diff.png — the base render faded to
near-white with every differing pixel in solid red. For eyes and multimodal
models; the semantic diff is still qain diff.
qain shot before.json after.json -o shots/ --state hover| option | default | what |
|---|---|---|
-o, --out-dir <dir> |
. |
Directory for the three PNGs. Created if missing. |
--state <name> |
default |
Which captured state to render. |
--browser <path> |
Playwright's managed Chromium | Chromium/Chrome executable to launch. |
Both snapshots must carry snap --replay data.