A pass you can defend.

qa runs your web frontend in a real browser, in the states your users actually meet: empty, denied, 390px wide, dark, stuck behind a menu. It hands your coding agent the evidence, one fix at a time, and every pass states exactly what it covered.

Routes from Next.js, React Router, TanStack Router, Remix, SvelteKit, Astro and Nuxt. Rules for React and TypeScript. Browser checks on any URL.

Routes
2/2
Unmeasured
7
Exit
1
$ curl -fsSL https://qakraken.com/install.sh | sh
$ qa setup
$ npx --yes --allow-remote=root --package=https://qakraken.com/qa.tgz qa setup
$ bunx --bun --package=https://qakraken.com/qa.tgz qa setup

Download the archive for your platform, then check it and unpack it.

PlatformArchiveSizesha256
darwin-arm64qa-1.0.0-darwin-arm64.tar.gz43.3 MB5d445192d15fa6b7b5e04b0d0a559c24470c64c568928df2ed13a7d488181ba0
darwin-x64qa-1.0.0-darwin-x64.tar.gz46.7 MB0a91eb801a5ca21c1cf529c121f0a8ded32b1a1ddd8af592128e7bf23c6dca37
linux-arm64qa-1.0.0-linux-arm64.tar.gz47.9 MBd92264e1fe239deda0047c0efe1cf33357b34de7030ebccd41e022e7f0c0c72a
linux-x64qa-1.0.0-linux-x64.tar.gz47.9 MBa89cf1cf461a39491bdb91d094719c3f4eac90c3b378b41880eb5e25d58e5bf7
$ echo "<sha256>  <archive>" | shasum -a 256 -c
$ tar -xzf <archive> && mkdir -p ~/.local/bin && mv qa-1.0.0-*/qa ~/.local/bin/qa
$ qa setup

Manifest: manifest.json. On macOS a file saved by a browser is quarantined; run xattr -d com.apple.quarantine ~/.local/bin/qa. A curl download is not.

Install qa on this machine, then verify one app repo.
1. Run: curl -fsSL https://qakraken.com/install.sh | sh
   It downloads one executable, checks its sha256 against the manifest, and installs it to ~/.local/bin/qa. No sudo. If ~/.local/bin is not on PATH, add it.
2. Run: qa setup
   It installs the browser qa drives. This is the only step that needs Node.
3. From the app repo: qa init <repo>
   then: qa verify <repo> --preflight
Report each exit code. 0 is clean within the measured scope, 1 is findings, 2 is could not run and is never a pass. Do not edit the app's source to make a check pass.
macOS arm64 and x64Linux x64 and arm64, glibc with libatomic1No Windows. No Alpine.Node for npx and browser setup; Bun runs the shim directlyUpdate: run the install command againUninstall: remove ~/.local/bin/qa
Sheet 1 of 1 · qa runRev 1.0.0
$ qa run ./excalidraw --base http://127.0.0.1:4380 --once --prod
inventory 2 routes · 1 mutation · 1 persona
static 23 rules over 547 files
sweep 2 routes × 390 · 1440 · dark
kernel graph 30 nodes · 10 edges (deterministic)
✕ P0 unsanitized-html, 6 sites in 5 files static
✕ P0 runtime-env-in-client-code, 3 sites static
✕ P1 click-occluded at a.plus-banner /
report .verify/marathon-report.md
exit 1 46 findings (17 distinct) · 7 unmeasured
Routes
2/2
Personas
1/1
Modes
3 390·1440·D
Depth
11/12
Findings
46
Unmeasured
7 items
Verdict
exit 1 · gating findings
Measured · Excalidraw, 7 Oct 2026Top 3 checked by hand: none confirmed

Green CI. Broken states.

Your tests walk the happy path the app was built against. Your users meet everything else. qa draws the difference, marks each finding, and says which parts it could not reach.

An app screen with four redlined findings A dashboard drawn as a wireframe. An open menu covers the table, a table overflows its frame, a denied panel is blank, and one region is hatched as unmeasured. not measured · no fixture 1 390px viewport+212px 2 3 4
  1. 1

    A menu that never closes

    A modal menu stays open with pointer-events: none on the body, and every control after it becomes unreachable. qa dismisses it, records it once, and moves on.

  2. 2

    Overflow at 390px

    The invoices table pushes past the viewport on a phone. It is measured at the width, not inferred from CSS.

  3. 3

    Denied renders blank

    A persona without access gets an empty panel instead of a denial. Signed-in personas run through your own auth launcher, so credentials never enter the evidence.

  4. 4

    What it could not reach

    Unmeasured is drawn hatched, never painted green. It goes into the denominator, not around it.

What it reads.

qa is built for React and TypeScript front ends. The browser half runs against any URL. Each part below says what it covers.

Routes

Found from your source

Next.js app and pages routers, React Router, TanStack Router, Remix, SvelteKit, Astro, Nuxt and Vue Router, Expo Router. Vite apps through their router. Any other app: declare the routes in a file.

Static rules

23 rules, JS and TS

Read .js, .jsx, .ts and .tsx. Missing empty, error and loading states, stale closures, unsafe HTML, env leaks, double submit, missing error boundaries.

Data

TanStack Query and SWR

Which queries ignore their error or loading state, and which writes leave another page stale after they succeed.

Browser

Real Chromium

Playwright per route, width, colour scheme and persona. Works behind a service worker. Sign-in goes through a launcher you provide.

Results export as SARIF, GitHub annotations and JUnit. A local MCP server hands the same run to any MCP client.

Not just pixels.

Most UI testing stops at the screenshot. qa follows a failure to where it starts: the build that is actually served, the contract between client and server, the request that never fired, the cache that went stale, the permission the client assumed. 135 failure classes in eleven layers. Only three of those layers are what you see.

below the pixelon screenCounted from the failure atlas qa resolves every finding against.

Backend edges, judged per endpoint.

For every route (method, path, input schema, permission, writes), Jev screens the handler for five failure shapes:

  • Null or empty input reaches logic unchecked
  • Ownership or tenant not checked before a read or write
  • A write that is not idempotent on retry
  • A partial failure that leaves inconsistent state
  • An error path that leaks internals or returns 200

Then code checks exactly what code can: the frontend gate against the backend permission, and the schema's nullability against the model's. Every proven lead gets a test that fails on today's code.

After a write, every surface that renders the data must agree →

The instrument measures.
Your agent fixes.

qa never edits your source. It produces evidence and one brief at a time, and your coding agent owns the commit. Rerun the same command and the exit code tells you whether the fix held.

Detect

Static rules over source, Playwright probes in a real browser, per persona and mode. Jev screens every surface for what rules can’t see.

23 rules · probes · Jev

Reduce

A Rust kernel folds every observation into one deterministic graph.

nodes.jsonl · edges.jsonl

Brief

Findings are ranked into lanes. Your agent gets one brief with its repro and evidence.

.verify/lanes/<class>/brief.md

Fix

Claude Code, Codex or Cursor edits and commits. qa waits for the source change.

your agent · your commit

Recheck

Same command, same scope. A ratchet catches any regression.

exit 1 → 0
one command · resumable for hours with --hours N

One graph. Every observation.

The kernel folds every finding, route, component, query, endpoint and piece of evidence into one graph of typed nodes and edges. Hover or focus a node to read what it is tied to. Click or press Enter to hold it.

.verify/autonomous/nodes.jsonl · edges.jsonl findingroute, rolecomponentquery, mutationendpoint, entityevidence
Sample · synthetic dataAn excerpt: 23 of 318 nodes, 23 of 702 edges.Ten node kinds, nine relations.

Hand it to your agent.

Paste this into Claude Code or Codex, inside the app you want checked. It says what qa is, which skill to read, and the first commands to run.

  1. Paste the prompt into your agent.
  2. It runs the static pass first, no browser needed.
  3. With the dev server up, it runs one measured session and reads the brief.
  4. It fixes the cause, commits, and reruns the same command.
Copy for your agent
qa checks a web frontend by running it. It runs on this machine and never edits source. Exit codes: 0 clean within the measured scope, 1 findings or a regression, 2 could not run (never a pass).

Read the frontend-verify skill (SKILL.md) first. If the qa command is not on PATH, stop and tell me.

From the app's directory, start with the static run, no browser needed:
  qa verify .
  qa show .

Then, with the dev server running:
Use frontend-verify in this host worktree. Run `qa run <repo> --base URL --once`, read the generated `.verify/lanes/<class>/brief.md`, own the source fixes and commits, then rerun the same command to recheck.

Replace <repo> with this app's path and URL with the dev server address. Report the exit code and what was measured.