@blazediff/agent
v0.8.0
Published
Agentic visual regression for BlazeDiff. Auto-discovers routes, captures deterministic screenshots, runs CI checks.
Downloads
299
Maintainers
Readme
@blazediff/agent
Visual regression testing your coding agent can judge. Discovers routes, screenshots them with Playwright, diffs against committed baselines, and hands ambiguous diffs to Claude Code, Codex, or Cursor as compact region tiles, not full PNGs. Deterministic CLI, no embedded LLM, no API key.
Features:
- Deterministic CLI. No embedded LLM, no API key required
- Source-walking route discovery for Next.js / Vite / Remix (BFS fallback)
- Heuristic verdict:
regression-likely | intentional-likely | noise-likely | ambiguous - LangGraph pipeline with per-entry subgraphs, suspendable via
interrupt()and resumable from an on-disk checkpoint - Region-tile handoff to host agents (10 to 100x smaller than full PNGs)
- Auto-masking via
data-blazediff-agent-maskattribute - Auth-protected route capture via a codegen-recorded login harness; credentials live in env vars, never in LLM context
Installation
npm install --save-dev @blazediff/agentQuickstart
blazediff-agent onboard # interactive: config + chromium + playbook + baselines
blazediff-agent check --judge host # CI: re-capture, diff, judge
blazediff-agent rewrite home # accept an intentional changeonboard writes .blazediff/config.json, installs bundled Chromium, installs the
playbook for your coding agent, and offers to capture baselines on the spot. The
second command (check, or capture if you declined the baseline offer) starts
capturing. Commit .blazediff/ (config + manifest + baselines). All commands
accept --json; under --json/CI, onboard runs non-interactively (no prompts,
no capture) — a scriptable config + chromium step.
Commands
Pass --cwd <abs-path> to target a sub-package in a monorepo.
Onboard options
onboard auto-detects your coding agent; override or scope the playbook with --stack:
blazediff-agent onboard --stack codex # explicit
blazediff-agent onboard --stack all # claude + codex + cursor
blazediff-agent onboard --stack local # local judge, no host agent (Moondream + Qwen)
blazediff-agent onboard --no-browsers --no-capture # config + playbook onlyOther flags: --url <baseUrl> (external/running server), --dev-command <cmd> /
--port <n> / --dev-script <name> (override detection), --yes (accept prompts),
--force (rewrite config + playbook).
For coding-agent stacks, writes the playbook and sets config.judge: "host":
- Claude Code →
<project>/.claude/skills/blazediff/SKILL.md - Codex →
~/.codex/skills/blazediff/SKILL.md - Cursor →
<project>/.cursor/rules/blazediff.mdc
--stack local writes no skill file. It sets config.judge: "local" so check
judges diffs locally in two steps: Moondream 2 describes the change, then
Qwen3.5-0.8B classifies it using that description plus the deterministic
interpret summary (both via the optional peer dependency
@huggingface/transformers; install it with npm i @huggingface/transformers).
Each model loads once on the first judgment and is reused for the rest of the run.
local cannot be combined with the coding-agent stacks.
Masking
Mark non-deterministic content (carousels, clocks, randomized avatars) in source:
<div data-blazediff-agent-mask>...</div>
<div data-blazediff-agent-mask="report-carousel">...</div>For third-party embeds you can't annotate, use a per-entry manifest.entries[].mask CSS selector and re-capture.
Judging
Every non-match routes through the configured judge. With --judge host the judge node interrupt()s the LangGraph pipeline, writes a JudgmentRequest (region tiles + locator thumbnail) to .blazediff/judgments/<id>/, and the suspended graph is checkpointed to .blazediff/checkpoints/. The host agent reads the tiles, writes verdict.json, and check --apply-judgments resumes the same graph with the verdicts. No re-capture, no re-diff.
Configuration
.blazediff/config.json:
{
"devServer": { "command": "pnpm dev", "port": 3000, "readyTimeoutMs": 60000 },
"framework": "next",
"packageManager": "pnpm",
"baseUrl": "http://127.0.0.1:3000"
}.blazediff/manifest.json is written by capture; don't edit it directly.
Harnesses
A harness is a pluggable script in .blazediff/harnesses/<name>.js, attached to
an entry via harnesses: [{ name, params? }]. A setup harness runs before
navigation (e.g. login); an interact harness (the default) runs after the
base screenshot and may drive the page and emit extra named screenshots — each
becomes its own baseline entry (<id>__<name>). Harnesses default-export a
Harness and are ESM .js/.mjs (TypeScript is not auto-transpiled):
/** @type {import("@blazediff/agent").Harness} */
export default {
async run({ page, screenshot }) {
await page.getByRole("button", { name: "More options" }).click();
await screenshot("menu"); // -> baseline "<entry>__menu"
},
};Auth-protected routes
Login is just a phase:"setup" harness. For a simple form login, the agent writes
.blazediff/harnesses/auth.js directly — a goto(/login) → fill from
process.env.BLAZEDIFF_AUTH_<PERSONA>_EMAIL / ..._PASSWORD → submit → assert it
left /login. No credentials ever live in the file, only env refs. For flows it
can't author (OAuth/SSO, MFA, captcha), blazediff-agent harness record auth --login
records the login interactively via Playwright codegen and rewrites the typed creds
to env refs.
Attach it with "harnesses": [{ "name": "auth", "params": { "persona": "default" } }],
then put BLAZEDIFF_AUTH_<PERSONA>_EMAIL / ..._PASSWORD in an env file the CLI
auto-loads from the target dir — .blazediff/.env[.local] (blazediff-scoped,
auto-gitignored) or the project-root .env[.local] — or export them. Real env
wins; .blazediff/ files beat the root. check does the rest.
Full walkthrough: Auth-protected routes.
CI
Only check is allowed under CI=1. Exit codes:
0: all passed1: regression, intentional, or pending judgment- non-zero with structured error JSON on infra failures
