Skip to main content
Glama
Wadehl
by Wadehl
README.md
<div align="center">
  <img src="assets/logo.png" width="420" alt="Browser-Trace" />
  <p><b>Record browser actions as a replayable Trace. Replay them deterministically — no agent, no LLM — and assert on network requests.</b></p>
  <p><b>English</b> · <a href="README.zh-CN.md">简体中文</a></p>
</div>

---

Drive a browser once (by an agent, a script, or by hand). Every action is captured to a **Trace** (JSONL). Transpile that Trace into a declarative **Replay Spec** (YAML), then replay it deterministically forever — for regression checks, smoke tests, or reproducing a bug.

- **Record** — every command (navigate / click / fill / …) auto-writes one Trace line with a full locator descriptor (all candidate selectors + role/text + rect) and the network requests it produced.
- **Transpile** — mechanical Trace → Replay Spec. Drops failed attempts, carries the locator fallback chain, turns `wait_for_request` steps into assertions. Marks anything a human/LLM should review with `_review`.
- **Replay** — reads the spec, locates each target by the fallback chain (`selector → role+text → rect`), and verifies assertions against the request store. Deterministic and cheap.

Ships three ways to use it: a **CLI**, an **MCP server** (for agents), and a **Skill**.

## Requirements

- **[Bun](https://bun.sh)** — build tool + dev runtime (runs the TypeScript directly).
- **Node ≥ 18** — runtime for the shipped `dist/*.mjs` artifacts.
- **Chrome / Chromium** — the CLI launches and owns a managed instance.

## Install

### As a Claude Code plugin

The plugin bundles an MCP server whose tools an agent calls to drive + record.

```bash
git clone <this-repo> browser-trace
cd browser-trace
bun install
bun run build        # produces dist/cli.mjs + dist/mcp.mjs
```

`.claude-plugin/plugin.json` registers the MCP server as `node ${CLAUDE_PLUGIN_ROOT}/dist/mcp.mjs`. Install the plugin via your marketplace/local plugin path, or register the server directly:

```bash
claude mcp add browser-trace --scope local -- node /abs/path/to/browser-trace/dist/mcp.mjs
```

### As a standalone CLI

```bash
bun run build
node dist/cli.mjs help
```

## Quickstart (CLI)

```bash
# 1. launch a managed browser (headful; add --headless for CI)
node dist/cli.mjs launch --session demo

# 2. drive it — each action records one Trace line
node dist/cli.mjs navigate "https://example.com" --intent "open page"
node dist/cli.mjs snapshot                       # read-only: see clickable elements
node dist/cli.mjs click --role link --text "Learn more" --intent "open docs"
node dist/cli.mjs wait --url-pattern "*/api/track*" --intent "verify beacon"

# 3. stop (kills the browser)
node dist/cli.mjs stop

# 4. mechanical Trace → Replay Spec
node dist/cli.mjs transpile output/demo.trace.jsonl

# 5. deterministic replay (launches its own browser, checks assertions)
node dist/cli.mjs replay output/demo.replay.yaml
```

Replay writes a `<spec>.compare.json` with `recorded_ms` vs `replay_ms` (record-time command execution excludes agent think time, so a real agent recording is far slower than replay).

## Commands

| Command | Records? | Purpose |
|---|---|---|
| `launch` / `stop` / `status` | — | own the browser lifecycle (random port, temp profile) |
| `snapshot` / `screenshot` | no (read-only) | perceive the page to pick a locator |
| `navigate <url>` | yes | go to a URL |
| `click` / `fill` | yes | act via `--selector` or `--role`+`--text` (never coordinates) |
| `clear-cookies --patterns "reA,reB"` | yes | delete cookies by name regex (e.g. force a deduped beacon to re-fire) |
| `evaluate <js>` | yes | escape hatch |
| `wait --url-pattern P` | yes | sync point / assertion — did a matching request fire this session |
| `transpile <trace.jsonl>` | — | Trace → Replay Spec (YAML) |
| `replay <spec.yaml>` | — | deterministic replay + assertions + timing |

## MCP tools

Same surface as the CLI, prefixed `mcp__browser-trace__`: `trace_start` / `trace_stop` / `snapshot` / `screenshot` / `navigate` / `click` / `fill` / `clear_cookies` / `evaluate` / `wait_for_request`. The MCP server is a thin forwarder — every tool shells out to the CLI, so MCP-driven and CLI-driven recording share one code path.

## How it works

```
record (any driver, all via one entry)
  → Trace (JSONL, one line per command)
  → transpile (mechanical; _review flags for LLM/human)
  → Replay Spec (YAML)
  → replay (locator fallback chain + assertions) → pass/fail + timing
```

**Trust boundary.** The CLI is the sole entry that launches and owns the browser (random debugging port, private profile). Whatever drives it — agent, script, human — can only act through these commands, so the Trace is complete by construction. Don't run another browser-driving tool in the same session, or you get holes in the Trace.

## Local development

```bash
bun install
bun run typecheck              # tsc --noEmit
bun run cli help               # run the CLI from TypeScript (no build)
bun run mcp                    # run the MCP server from TypeScript (stdio)
bun run build                  # bundle to dist/*.mjs for shipping
```

Dev runs the TypeScript directly with bun; the MCP server auto-detects `.ts` (dev, spawns `bun`) vs bundled `.mjs` (prod, spawns `node`). Runtime output (traces, request stores, session state) lands under `output/` (gitignored).