How to Capture Screenshots and DOM Dumps During Browser Trace Sessions
The browser-trace skill runs three independent Node.js processes—Firehose, Sampler, and Bisector—to capture CDP protocol events, periodic screenshots, and DOM snapshots without blocking your primary automation.
The browserbase/skills repository provides a zero-interference observability pipeline for browser automation that lets you capture screenshots and DOM dumps during trace sessions in real time. By leveraging the Chrome DevTools Protocol (CDP) and detached child processes, the tracer records visual state and page structure independently of your main testing framework.
The Three-Component Architecture
The browser-trace skill splits observability into three isolated components that run concurrently. This design ensures that heavy I/O operations never stall your automation.
Firehose: Streaming CDP Events
The Firehose component opens a persistent WebSocket connection to the browser and streams every CDP event to cdp/raw.ndjson as newline-delimited JSON. In skills/browser-trace/scripts/start-capture.mjs (lines 45‑54), the script spawns browse cdp … as a detached child process, allowing the firehose to survive even if the parent shell exits.
Sampler: Periodic Screenshots and DOM Dumps
The Sampler runs a tight loop that captures visual and structural state every N seconds (default 2 s). Located in skills/browser-trace/scripts/snapshot-loop.mjs (lines 1‑20), this script:
- Calls
browse screenshotto generate PNG files - Dumps the DOM to HTML via temporary
.partialfiles that are renamed only on success (lines 39‑46) - Fetches the current URL using
browse --ws … --json get url(lines 52‑58) - Appends metadata to
index.jsonl(lines 61‑63)
Screenshots are validated before writing; empty files are discarded to prevent zero-byte artifacts (lines 35‑37).
Bisector: Post-Processing and Aggregation
After the session ends, skills/browser-trace/scripts/bisect-cdp.mjs parses the raw firehose and splits events into per-page buckets (network/*.jsonl, console/*.jsonl, page/*.jsonl, etc.). It also writes a high-level cdp/summary.json that aggregates per-page metrics, as documented in skills/browser-trace/SKILL.md (lines 44‑48).
Data Layout and File Structure
When you start a capture, the tracer creates a directory under .o11y/<run-id>/. The manifest is written by start-capture.mjs (lines 38‑44) immediately after directory creation.
A typical run produces this structure:
.o11y/<run-id>/
manifest.json # Run metadata: target port, interval, domains
index.jsonl # One line per sample: {ts, screenshot, dom, url}
cdp/
raw.ndjson # Full CDP firehose
summary.json # High-level session summary
network/*.jsonl # Request/response buckets
console/*.jsonl
page/*.jsonl
dom/all.jsonl
screenshots/<iso-ts>.png # One PNG per interval
dom/<iso-ts>.html # One HTML dump per interval
Starting and Stopping Capture Sessions
Starting the Capture
Run start-capture.mjs with the target debugging port and optional run ID:
node scripts/start-capture.mjs 9222 my-run
This script:
- Creates
.o11y/my-run/ - Writes
manifest.json - Spawns the Firehose and Sampler as detached processes
- Returns the PIDs for later termination
Running Your Automation
While the tracer runs, drive the browser using any CDP client (Playwright, Puppeteer, or browse CLI). Because the tracer uses its own read-only CDP connections, it never sends commands to the page and therefore cannot interfere with state or timing.
Stopping and Bisecting
Terminate the session and process the results:
node scripts/stop-capture.mjs my-run
node scripts/bisect-cdp.mjs my-run
The stop-capture.mjs script kills the Firehose and Sampler PIDs recorded in the manifest, then triggers the Bisector to generate cdp/summary.json.
Practical Code Examples
Quick Start with Local Chrome
Launch Chrome with remote debugging, then attach the tracer:
# 1. Start Chrome with CDP enabled
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" \
--remote-debugging-port=9222 \
--user-data-dir=/tmp/chrome-o11y \
about:blank &
# 2. Start the tracer (creates .o11y/<run-id>/)
node scripts/start-capture.mjs 9222 my-run
# 3. Run your automation
browse env local 9222
browse open https://example.com
# 4. Stop and process
node scripts/stop-capture.mjs my-run
node scripts/bisect-cdp.mjs my-run
Manual Sampler Mode
To capture screenshots and DOM dumps without the full CDP firehose:
node scripts/snapshot-loop.mjs ws://localhost:9222 \
.o11y/manual-run 3 # 3-second interval
Press Ctrl-C when finished; the loop cleans up its PID file automatically and leaves screenshots and DOM dumps in the specified directory.
Querying Captured Data
Inspect specific domains or errors using the query utility:
# Show failed network requests for page 0
node scripts/query.mjs my-run page 0 network/failed
# List all errors across pages
node scripts/query.mjs my-run errors
The query utility reads cdp/summary.json and the per-page JSONL buckets to filter results.
Key Implementation Details
- Zero Interference: The Sampler uses independent
browse --wsone-shot calls, so it never mutates page state or blocks the main automation thread. - Atomic Writes: DOM dumps are written to
*.partialfiles and renamed only after successful completion, preventing corrupted artifacts if the process is killed mid-write. - Screenshot Validation: The Sampler checks file size before appending to
index.jsonl; failed screenshots are omitted rather than recorded as empty entries. - Multiple Concurrent Clients: CDP permits multiple WebSocket connections; the tracer attaches as a read-only observer, making it safe to start and stop at any point during a test.
Summary
- Three-process architecture: Firehose streams CDP events, Sampler captures screenshots/DOM every 2 seconds, and Bisector post-processes into queryable buckets.
- File locations: Raw data lives in
.o11y/<run-id>/with subdirectories forscreenshots/,dom/, andcdp/. - Entry points: Use
start-capture.mjsto begin,stop-capture.mjsto end, andbisect-cdp.mjsto generate summaries. - Zero interference: The tracer runs in detached processes with read-only CDP connections, ensuring no impact on primary automation performance or state.
Frequently Asked Questions
How does the tracer avoid interfering with my primary automation?
The browser-trace skill spawns independent child processes that open their own CDP WebSocket connections to the browser. Because these connections are read-only (listening for events or capturing screenshots) and never send commands that mutate page state, they run in parallel with your test framework without blocking or altering the execution flow.
What happens if a screenshot or DOM dump fails during sampling?
Failed captures are handled gracefully. In snapshot-loop.mjs, screenshots are checked for non-zero file size immediately after capture (lines 35‑37), and empty files are discarded before logging. DOM dumps use atomic file operations: they write to a temporary .partial file and rename to the final .html extension only on successful completion (lines 39‑46), preventing zero-byte artifacts from appearing in the output directory.
Can I adjust the sampling interval for screenshots and DOM dumps?
Yes. Pass the interval in seconds as the third argument to start-capture.mjs or snapshot-loop.mjs. For example, node scripts/start-capture.mjs 9222 my-run 5 sets a 5-second interval between samples. The default is 2 seconds when omitted.
Is it possible to run only the sampler without capturing the full CDP firehose?
Absolutely. Execute node scripts/snapshot-loop.mjs <ws-url> <output-dir> <interval> directly to capture only screenshots and DOM dumps. This mode skips the Firehose entirely, reducing overhead and disk usage if you only need visual snapshots and HTML dumps without network logs or console events.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →