Visual regression at a glance. Reglance screenshots a site across viewports,
compares each page against a saved baseline, and opens an HTML report of the
visual and HTML differences. It installs per-project and is configured with one
reglance.json file.
-
Install (downloads a headless Chromium on install):
npm install @happyprime/reglance --save-dev
-
Add a
reglance.jsonto the project root. List page paths relative to a defaultdomain:{ "name": "My Site", "domain": "https://site.test", "paths": { "home": "/", "blog": "/blog", "single-post": "/blog/sample-post" } } -
Run the workflow:
npx reglance capture # screenshot every path (against the good state) npx reglance control # save those captures as the baseline # ...make your changes... npx reglance capture # screenshot again npx reglance compare # diff against the baseline and open the report
control is run once to set the baseline; capture + compare are repeated to
check changes against it. Output (captures, baseline, report) goes to a
self-ignored .reglance/ directory — nothing to add to .gitignore.
| Field | Required | Description |
|---|---|---|
paths |
yes | Map of key → path. A value may be a full URL to point at a different domain. |
domain |
capture | Default domain. A bare host (site.test) becomes https://site.test. |
name |
no | Label shown in the report. Defaults to the domain host. |
viewports |
no | [{ name, width, height, deviceScaleFactor? }]. Defaults to desktop (1920×1080), mobile (390×844). |
output |
no | Output directory. Defaults to .reglance. |
pixelmatchOptions |
no | pixelmatch options, e.g. { "threshold": 0.1 }. |
timeouts |
no | { goto, settle } in ms. goto bounds navigation; settle bounds each post-scroll wait (network idle, then image load/decode). Defaults { goto: 15000, settle: 8000 }. Raise settle for slow, lazy-loading pages. |
blockHosts |
no | Hostnames to block requests to during capture, e.g. ["captcha.example.com"]. Each entry also blocks its subdomains. |
imageCache |
no | Serve repeat image requests from a local cache during capture. true for a per-run cache, { "persist": true } to keep it across runs. Off by default. |
domain is only needed by capture; control and compare work on the files
already captured. See reglance.example.json for a full
example.
blockHosts aborts every request to the listed hosts (and their subdomains)
before it leaves the browser. Use it for third-party embeds that keep the
network busy and stall capture — CAPTCHA widgets, ad tech, analytics — or
that render differently on every load and pollute diffs. Captures wait for the network to go idle, so a widget that polls or
retries indefinitely will otherwise time out every viewport on pages that
embed it. Entries are bare hostnames; "example.org" blocks example.org and
sub.example.org alike.
imageCache keeps a capture run from swarming the origin with the same image
requests once per viewport per parallel context. Image requests are intercepted
in the browser: the first request for a URL is fetched from the origin and
stored under .reglance/image-cache/, and every repeat is answered locally —
simultaneous requests for the same URL share a single origin fetch. The full
URL, query string included, is the cache key, so CDN resize variants
(photo.jpg?w=400 vs photo.jpg?w=800) stay distinct. Nothing in the page is
rewritten and only images are cached — the HTML, CSS, and JS under test always
load from the origin. With true the cache is cleared at the start of every
run, so within-run traffic drops with zero risk of a stale image masking a real
change. { "persist": true } keeps the cache across runs — useful when
re-capturing repeatedly while iterating on CSS — but a changed origin image
will then go unnoticed until you clear it with --fresh-images.
A viewport's optional deviceScaleFactor (device pixel ratio) renders the page
as it would appear on a higher-density display — use 2 for a retina capture,
3 for some phones. It defaults to 1. Captures sharing a DPR run in one
browser context; a new DPR opens a fresh context, so prefer grouping retina and
non-retina variants rather than scattering them. Note that a 2× screenshot is
twice the pixel dimensions of its 1× counterpart, so changing the
deviceScaleFactor of an existing viewport will diff against its controls as
fully changed until you re-run reglance control.
A paths value may be a full URL pointing at a different host than domain.
This is supported, but capture prints a warning listing such paths so
off-domain navigation is a conscious choice — keep configs from trusted
sources, since the report renders captured content.
| Command | Description |
|---|---|
reglance capture |
Screenshot every path across all viewports into .reglance/captures. |
reglance control |
Promote the latest captures to the baseline (.reglance/controls). |
reglance compare |
Diff the latest captures against the baseline and open an HTML report. |
Append path keys to limit a command to specific pages:
npx reglance capture home blog.
| Flag | Command | Description |
|---|---|---|
--domain=<host> |
capture | Override the configured domain for this run. |
--concurrency=<n> |
capture | Parallel browser contexts (default: 4). Must be a positive integer. |
--stagger=<ms> |
capture | Delay between starting contexts (default: 500). 0 disables staggering. |
--skip-reload |
capture | Reuse the page between viewports instead of reloading. |
--fail-on-degraded |
capture | Exit non-zero if any page failed to load cleanly (for CI). Default: warn, exit 0. |
--fresh-images |
capture | Clear a persistent image cache before capturing (see imageCache). |
--insecure |
capture | Ignore TLS certificate errors for non-local hosts (already ignored for .test/localhost). |
--compare-concurrency=<n> |
compare | Parallel diff workers (default: CPU count − 1). Lower it for very tall pages. |
--no-open |
compare | Don't open the report when finished. |
--config=<path> |
any | Path to the config file (default: reglance.json). |
domain is the shared default; override it per run so teammates can point at
their own local site without editing the config:
npx reglance capture --domain=site2.testA baseline is only useful if it reflects pages that actually loaded. reglance guards against silently baselining bad data:
- If a page never loads cleanly (after retries),
capturereports it as degraded instead of treating it as a success. Add--fail-on-degradedto make the run exit non-zero in CI. capturescrolls each page one viewport at a time so every lazy-loaded image is triggered, then waits (bounded bytimeouts.settle) for all images to load and decode before screenshotting — and warns per capture when any image was still loading, instead of silently shipping a partial screenshot.controlrecords each promotion in.reglance/controls/manifest.jsonand warns when it promoted fewer captures than expected (so the untouched controls are now stale).comparewarns when the baseline mixes controls from more than onecontrolrun.
.reglance/
captures/ Latest screenshots + HTML snapshots
controls/ Baseline screenshots + HTML (+ manifest.json)
compares/ Diff images
reports/ The report — open reports/index.html
assets/ Report stylesheet + script
image-cache/ Cached image responses (only with imageCache enabled)
The report is a single reports/index.html that embeds every result as JSON
and renders three views client-side from the URL hash — a triage overview
(grouped by page, changed-only by default, keyboard-navigable), a comparison
view (swipe, side-by-side, onion skin, diff overlay, blink), and a unified HTML
diff. It opens straight from disk with no network access.
npm run lint # check style
npm run fix # auto-fix style