npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@crouton-kit/capture

v1.4.18

Published

Browser automation and UI validation via CDP — session-based screenshots, HAR, a11y, click/type, and JS exec

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/capture

Against 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 manifest

measure 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.png

Add --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 other measure leaf 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 other motion leaf reads the finalized recording.
  • A performance trace (perf trace) and a heap snapshot (heap snapshot) work the same way: record once, then vitals/insights and census/objects/retainers/diff are 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>, or backend:<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