In CI
Sweep a whole page, compare against a baseline, and fail on new findings.
The overlay answers "what is wrong with this element". xray record answers "what
is wrong with this app, and is it getting worse" — which is the question a build can
act on.
xray record http://localhost:5173 --baseline xray.baseline.jsonExit 0 when nothing new appeared, 1 when it did, 2 when the sweep could not
run at all.
Create the baseline with --update-baseline, commit it, and every run afterwards
compares against it:
New (1):
+ LOCKED src/Card.tsx:36
padding: 8px — 8px is --salt-spacing-100 at medium only.
Failing: 1 new finding. Fix it, or re-record with --update-baseline.Playwright is optional
The analysis needs a resolved cascade, so it needs a real browser — but only this command does, so Playwright is an optional peer dependency rather than a dependency.
pnpm add -D playwright && pnpm exec playwright install chromiumPoint it at a dev server running the plugin and it reuses the client already on the page; point it at a preview build or a static page and it injects the same bundle itself, so both paths report identically.
A baseline survives editing the files it describes
Findings are compared by file, property, kind, token and value — deliberately not by line number.
A baseline keyed on lines goes stale the moment someone adds an import, and a diff that reports every finding in a reformatted file as both removed and added is a diff nobody reads. Line numbers still travel in the report, as the place to look rather than as part of a finding's name.
Flags
--baseline <file> | Compare against a recorded baseline and fail on new findings |
--update-baseline | Write the baseline from this run and exit successfully |
--out <file> | Write the recording as JSON |
--fail-on <severity> | high, medium or low. Default high |
--per-source <n> | Elements to analyse per source location. Default 3 |
--max-elements <n> | Cap on elements swept. Default 2000 |
--wait <selector> | Wait for this selector before sweeping |
--timeout <ms> | How long to wait for the page. Default 30000 |
--full | Print the whole report as well as the diff |
From the console
The same sweep is available without the CLI:
__xray.record(); // aggregated by source location
__xray.report(); // the same thing as textElements are sampled per source location — the fiftieth row of a list is the same JSX line as the first — and token resolution is cached per theme context, which is what keeps a full sweep in the tens of milliseconds.
Element counts are a sample
Because reporting is per source location, a value hardcoded in a shared component appears once against the component rather than once per usage. That is usually what you want, but it means a finding's element count is a sample, not a census.