Getting started
qain captures the browser's used values — resolved computed styles, layout boxes, paint order, composited colors — as a JSON snapshot, and diffs two snapshots semantically. This page takes you from install to your first diff.
Install
pnpm add -D @qain/cli # or npm i -D / yarn add -D / bun add -dThis installs the qain binary. It needs a Chromium to drive: qain launches
one through playwright-core, which looks for its matching Playwright-managed
Chromium. If snap fails with a browser-not-found error, either install one —
npx playwright install chromium— or point --browser at any Chrome/Chromium binary already on disk:
qain snap http://localhost:3000 --browser /usr/bin/google-chromeChromium only, by design. DOMSnapshot.captureSnapshot and
CSS.forcePseudoState have no Firefox or WebKit equivalent, and qain is built
on both.
Capture, change, diff
Point qain snap at any URL a browser can open — a dev server, a Storybook
story, a deployed page:
qain snap http://localhost:3000 --rules --replay -o before.json--rules records the matched CSS declarations so the diff can name the
file:line behind each change. --replay records per-line text rectangles so
the page can be rebuilt later. Both are opt-in because they cost snapshot size;
both are worth passing by default.
Now change the code, and capture again:
qain snap http://localhost:3000 --rules --replay -o after.json
qain diff before.json after.jsondefault state
html > body > div > p[data-testid=note]
color: rgb(90, 90, 90) → rgb(190, 190, 190)
contrast 6.9 → 1.86 ✗ falls below WCAG AA-normal
← .note { color: rgb(90, 90, 90) → rgb(190, 190, 190) } theme.css:12:3
6 derived changes (a consequence of the above)
... border-top-color, outline-color, text-decoration-color (follows color)
8 changes: 2 primary, 6 derivedReading the diff
Every change is primary or derived. A primary change is a cause: the
node's own styles, attributes, or text changed, or it resized for reasons of
its own. A derived change is a consequence: the node only moved because
something above it grew, or only recolored because it inherits currentColor.
--omit-derived shows the causes alone — usually the view you want:
qain diff before.json after.json --omit-derivedThe exit code is 1 when the diff is non-empty and 0 when it is clean, so
CI and coding agents can gate on it. --json emits the diff as structured
data instead of text.
Looking at it
A textual diff tells you what; sometimes you want to see it:
qain diff before.json after.json --replay report.htmlwrites a standalone HTML page with both snapshots rebuilt pixel-exactly, side by side or stacked with an opacity slider, causes outlined in red. And
qain shot before.json after.json -o shots/renders before.png, after.png, and diff.png — useful when the consumer
of the report is a multimodal model or a PR comment.
Where to next
- CLI reference — every flag, including
--selectorto scope the snapshot to one element and--states hover,focus-visibleto capture forced pseudo-states. - Recipes — Storybook stories, extracting computed styles as data, CI gating.
- Prefer assertions over snap-and-diff? The Playwright, Vitest, and Storybook matchers manage baselines for you.