@crouton-kit/capture
v1.4.18
Published
Browser automation and UI validation via CDP — session-based screenshots, HAR, a11y, click/type, and JS exec
Maintainers
Readme
capture
Browser automation that measures instead of guessing. A CDP command line built for agents.
A screenshot shows you what a page looks like. It does not show you why the button doesn't work.
capture answers questions about a live page with numbers and coordinates: what is covering this button, which CSS rule moved it, how long the page took to answer a click, what changed between two states, and which pixels moved and when. Every finding comes back with the element, the geometry, and the evidence it was read from.
npm install -g @crouton-kit/captureAgainst the alternatives
Compared against @playwright/mcp 0.0.79 and chrome-devtools-mcp 1.8.0 — the Chrome DevTools MCP surface Claude Code drives — read at those published versions.
Both ship a JavaScript escape hatch, so much of this is reachable in either one — by writing the code yourself. ❌ below means the answer is not returned to you as a fact; it means you write and maintain the measurement.
| | capture | Playwright MCP | Chrome DevTools MCP |
|---|:---:|---|---|
| Bounding box of every element | ✅ | ✅ browser_snapshot boxes:true | ❌ Must use raw JS |
| Names the element covering a target | ✅ | ❌ Must use raw JS | ❌ Must use raw JS |
| Percentage of the target's box it covers | ✅ | ❌ Must use raw JS | ❌ Must use raw JS |
| Full receiver stack at a point | ✅ | ❌ Must use raw JS | ❌ Must use raw JS |
| Winning CSS declaration for a property | ✅ | ❌ Must use raw CDP | ❌ Not reachable — no CSS tool, no CDP passthrough |
| Its specificity and source location | ✅ | ❌ Must use raw CDP | ❌ Not reachable |
| Tap-target and contrast findings | ✅ | ❌ Must use raw JS | ✅ lighthouse_audit, written to a report file |
| A rect on every finding | ✅ | ❌ Must use raw JS | ❌ Inside the report file |
| A PNG crop of every finding | ✅ | ❌ One screenshot call per element | ❌ No crops |
| Per-element style and geometry delta between two states | ✅ | ❌ Must use raw JS | ❌ Must use raw JS |
| Cascade provenance on the property that changed | ✅ | ❌ Must use raw CDP | ❌ Not reachable |
| Raster diff of two states | ✅ | ❌ Diff two screenshots yourself | ❌ Diff two screenshots yourself |
| Steps a width range in one command | ✅ | ❌ Your own resize loop | ❌ Your own resize loop |
| Brackets the exact pixel where layout state changes | ✅ | ❌ Your own resize loop | ❌ Your own resize loop |
| Input → mutation → paint → settle for one interaction | ✅ | ❌ Video file only | ❌ Trace insights are page-level |
| Per-frame geometry of one element through an animation | ✅ | ❌ Video file only | ❌ Must use raw JS |
| Changed pixel regions attributed to elements | ✅ | ❌ Must use raw JS | ❌ Must use raw JS |
| A whole interaction as one image | ✅ | ❌ | ❌ |
| Stub network responses from ordered rules | ✅ tab mock | ✅ browser_route | ❌ No interception tools |
| Core Web Vitals with subparts and attribution | ✅ perf vitals | ❌ Trace file for the Trace Viewer | ✅ Trace insights |
| DevTools insight set — render-blocking, forced reflow, shift culprits | ✅ perf insights | ❌ | ✅ |
| Lighthouse, failing nodes in the reply | ✅ Per-node DOM path, selector, snippet | ❌ | ❌ Scores and counts; findings in the report file |
| Heap census, retaining paths, snapshot diff | ✅ heap | ❌ | ✅ Behind --memoryDebugging |
Both still do things capture does not. Playwright MCP is cross-browser — the sharpest structural gap — and has storage-state save/restore, test-code generation, device presets, PDF export, and richer input verbs (hover, drag, file upload). Chrome DevTools MCP automates Chrome extensions and PWAs and has CPU-throttling presets. capture is Chromium-only and points at one question — what is this page actually doing, in numbers.
See it
Eleven short clips. Every overlay is real: the text is extracted from the command's actual stdout, the boxes are drawn at the coordinates the command printed, and the inset images are files a command actually wrote.
Each player below contains the full clip. Use its controls to play, pause, scrub, or fullscreen.
1. Every command succeeded. The page is still broken.
An agent checks out. The promo code applies and the total drops $691.20 → $518.40, so the page is clearly working. Then it clicks Complete purchase — reported clicked, nothing happens. Rather than guess, it measures. The hit test resolves the click to something that is not the button, and measure map paint names it: #consent, z-index 99, covering 100.00% of the button's ink box.
Playwright throws "element intercepts pointer events" and names nothing. A screenshot shows a perfect page.
capture measure map paint <snap> --selector "#pay" · full clip, 22s
2. The page looks finished. The measurements disagree.
One command measures a team-access page against thresholds and draws every failure onto it at real coordinates: a row control at 28×28 under the 44×44 floor, a helper line at 2.16:1 contrast, an identity column clipping the address it is showing you. Each finding carries a cropped PNG of its own evidence, and the gate exits nonzero.
capture measure check <snap> --for tap-targets,contrast,truncation --gate · full clip, 22s
3. Two changes shipped. Which one moved the button?
A report switches to Weekly, then a design-system stylesheet lands live. The Publish button is somewhere new. A pixel diff can only say "3% of pixels differ" — this names the element, both of its boxes, how far it moved, and the declaration that now wins, down to tokens-v2.css line 6.
capture measure diff --before <snap> --after <snap> · full clip, 27s
4. The layout flips between two pixel widths. Which two?
Instead of dragging a window edge until something snaps, sweep the axis. The viewport steps 619 → 620 → 621 → 622, the grid goes one column to two, and the command brackets the exact pair it happened between.
capture measure sweep --axis width --from 619 --to 622 · full clip, 21s
5. Name a component. Get the component.
The agent asks for a card and gets a cropped, padded, zoomed image of exactly that card — no coordinates, no full-viewport screenshot to squint at. It can ask by CSS selector or by the visible label a screen reader would read. Ask for button when six match, and it refuses to guess and lists all six.
capture page shot --crop-selector "#revenue-card" --pad 8 --zoom 1.5 · full clip, 24s
6. How fast did that click actually answer?
Record a real interaction, then read its timeline off the recording rather than eyeballing a screenshot: input dispatch, DOM mutation at +7.20ms, first paint at +18.76ms across 6276 changed pixels, settled at +325.06ms. Every row names the evidence it came from.
capture motion response <rec> --action "click:Run sweep" · full clip, 23s
7. Seven seconds of motion, in one still.
A model cannot watch a video. Hand it a screen recording and you are really handing it dozens of separate frames to reason across one at a time — expensive, and still hopeless, because the thing you care about is the motion between frames and no single frame contains it.
motion mask collapses the whole interaction into one image, which is the format a model reads well. Colour encodes when each pixel changed — blue is early, red is late — so the entire route of the payload is legible at a glance. Underneath it, the changed regions ranked largest first with the window each one moved in. The agent gets to see the animation.
capture motion mask <rec> --limit 4 · full clip, 22s
8. Drive the browser you are already signed into.
No storage-state dance and no separate automation profile. Attach over --port, adopt the tab already in front of you, drive it, and finish with one bundle: the recording, the HAR, the shots and the measurements together. The network panel is drawn from the captured HAR — every field read from that entry, nothing inferred, and credential headers withheld with their length marked in place.
capture session start --target <tab-id> --port 9860 · full clip, 30s
9. The cards paint first. Then one image moves them 400 pixels.
Record the load once, then ask it different questions. perf vitals reads the largest paint off the trace — nearly all of it one image's load, named by element and URL — and lists the shifts that image caused. perf insights returns the element each shift moved, with the rectangle it was in before and after: old_rect [28,99,1224,174] → new_rect [28,499,1224,174]. Then measure explain measures that same element on the settled page and prints x=28 y=499.75 w=1224 h=172.64. The trace engine and the DOM agree on where the KPI row ended up.
capture perf insights <trace> --name CLSCulprits --full · full clip, 24s
10. The panels are closed. The browser is still holding all five.
A console opens and closes five session panels. The deck ends empty and the page reports zero open — but two heap snapshots disagree. heap diff lists ten detached nodes still reachable and names each one by the constructor that built it: <div class="panel" id="session-1">. heap census puts 1,049,008 retained bytes behind one of them, and heap retainers walks the edge keeping it alive — a ClosedSession object holding the panel on its element property.
capture heap retainers <snap> --node <node-id> · full clip, 24s
11. The API never answered. The dashboard didn't change by one pixel.
The agent reads $518.40 off a revenue dashboard, installs one rule document that fails the metrics API, and reloads. The skeletons shimmer, the cards fill, and the page shows $518.40 again. measure diff --pixels reports 0 changed pixels across 1280×800, while the HAR for that one URL carries both loads side by side: 200 · 56 bytes, then response incomplete: failed. Mocking the route is the setup. The measurement is what shows the failure left nothing on screen to see.
capture tab mock start --rules <rules.json> · full clip, 26s
Quick start
Needs Node and a Chrome. capture tab launch starts a headless browser that capture owns and reaps — it looks for $CAPTURE_BROWSER, then a ~/.cache/puppeteer Chrome, then a system Chrome. If a CDP-enabled browser is already running, capture can attach to that instead.
capture tab launch # start a browser capture owns
capture session start --url https://example.com # open a tab, begin recording traffic
capture measure snap # → snap-mtdpfnkr-08fbabdd
capture measure check snap-mtdpfnkr-08fbabdd --for contrast,tap-targets
capture session stop <session-id> # write the bundle manifestmeasure check reports real measurements, not opinions:
1. tap-targets — html > body > div > p:nth-of-type(2) > a measures 82×18px; threshold is 44×44px
Rect: x=160 y=186.078125 w=82 h=18
crop: snap-mtdpfnkr-08fbabdd/findings/1-tap-targets.pngAdd --gate to check or diff and it exits nonzero on findings, so the same command works in CI.
The command surface
Ten roots. Every leaf renders prose by default and mirrors the same result under --json.
| root | what it owns |
|---|---|
| session | the artifact container — records HAR, bundles artifacts, sets the active context |
| page | verbs against the live tab — click, type, scroll, navigate, exec, repeat, shot, elements, inspect |
| tab | browser and tab plumbing — launch, quit, list, open, close, reset, network, mock |
| measure | settled-snapshot substrate plus read-only queries — snap, check, diff, explain, sweep, census, map, text |
| motion | recorder lifecycle plus queries over a recording — rec, mask, timeline, response, jank |
| perf | performance-trace substrate — trace records, vitals and insights read it |
| heap | V8 heap-snapshot substrate — snapshot, census, objects, retainers, diff |
| lighthouse | runs Lighthouse against a URL and stores its report unmodified |
| cdp | raw Chrome DevTools Protocol escape hatch |
| lib | vault-lib introspection, for running forked libs in the tab |
Run any of them bare for the subcommand list, or capture <root> <leaf> -h for a leaf's usage.
How it fits together
A session is the container; recorded substrates are what keep queries cheap.
- A session is the container. It opens or adopts a tab, records HAR while it is active, and writes a bundle manifest when it stops. Everything produced while it runs lands in one directory.
- A snapshot (
measure snap) drives the page once and writes a settled substrate — geometry, styles, accessibility, layers, hit testing, text, forms, screenshot. Every othermeasureleaf is a cheap read over that artifact and never re-drives the browser. - A recording (
motion rec) captures an interaction, one-shot or composed across several commands. Every othermotionleaf reads the finalized recording. - A performance trace (
perf trace) and a heap snapshot (heap snapshot) work the same way: record once, thenvitals/insightsandcensus/objects/retainers/diffare read-only queries over the artifact.
Findings exit 0 — they are a report, not a crash. Only check and diff accept --gate, which turns findings into exit 2.
Targeting elements
The driving verbs resolve exactly one element through a single grammar, and reject an ambiguous target with the list of candidates rather than picking one:
- a bare CSS selector (takes precedence)
- an exact accessible name, when CSS finds nothing
ax:<name>,axid:<id>, orbackend:<id>
For agents
The CLI is designed to be read by a model: errors are structured, every error carries a follow_up naming the command that would fix it, and help is available at every level.
License
MIT
