> ## Documentation Index
> Fetch the complete documentation index at: https://jam.dev/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Record an agent's browser

> Capture the console, network, and clicks from an AI agent's browser into a Jam. Connect agent-browser, chrome-devtools-mcp, or a Playwright script to jam record.

When an AI agent drives a browser, the Jam should carry what the agent saw: the console errors, the failed requests, and every click and keystroke, lined up with the video. `jam record` gets these from the browser over the Chrome DevTools Protocol, the same protocol agents already use to drive Chrome. You point one flag at the agent's browser, and the Console, Network, and user-action panels fill themselves.

This page shows how to connect the agents and tools we have tried. The video is the browser window. The panels come from the browser itself, so nothing new runs on the page under test.

<Info>
  `jam record` runs on macOS and Linux. Recording a browser this way needs a Chromium-based browser, which covers Chrome, Edge, and the Chromium that agent tools launch.
</Info>

<Note>
  The video is the browser window on screen, so the browser has to be a visible one. A headless run has no window to record. Launch the browser with a window, and let the agent drive it through the proxy.
</Note>

## The two ways to connect

An agent's browser reaches a recording one of two ways. Both need a browser the CLI can reach on a debug port.

* **Tap.** `jam record start --cdp <port>` attaches to a Chrome the agent already drives. The Console and Network panels fill from every open tab.
* **Proxy.** With `--cdp`, `jam record start` also prints a `cdpProxy` address. Point the agent at that address instead of at Chrome, and its clicks and keystrokes become user actions in the Jam. This is how you record actions without changing the agent.

## Ask your agent to do it

Install the skills once, and the whole procedure below is already where your agent reads it:

```bash theme={"theme":"css-variables"}
jam skills install
```

Then ask in English:

```text theme={"theme":"css-variables"}
Record yourself reproducing the checkout bug in the browser, then send me the Jam link.
```

The agent launches its own Chrome, starts the recording, drives through the proxy, and hands back the link. Ask for `proof`, `evidence`, or `show me` and it reaches for the same skill.

### If your agent cannot load skills

Paste the procedure instead, with your own claim in the title.

```text theme={"theme":"css-variables"}
Record what you do in the browser as a Jam, and give me the link at the end.

1. Check the CLI is signed in with `jam auth status`, and run `jam auth login` if it is not.
2. Launch your own Chrome with a debug port and its own data dir. On macOS the binary is
   "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"; on Linux it is google-chrome.
   <chrome binary> --remote-debugging-port=9222 --user-data-dir=/tmp/jam-chrome about:blank &
3. Start the recording against that window, and keep `cdpProxy` from the receipt:
   jam record start --pid <the Chrome pid> --cdp 9222 --title "<what you are showing me>" --json
4. Point your browser tool at the `cdpProxy` address instead of at port 9222, then do the work.
5. Finish with `jam record stop --json` and return the `url` from the receipt.
```

Steps 2 and 4 are what put the clicks in the Jam. An agent that skips them still gets the video and the browser's console and network, but no user actions.

## agent-browser

[agent-browser](https://github.com/vercel-labs/agent-browser) is a command-line tool that drives Chrome. Launch it with a window, tap its browser, then drive through the proxy so the clicks land.

<Steps>
  <Step title="Launch a Chrome with a debug port">
    ```bash theme={"theme":"css-variables"}
    "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" \
      --remote-debugging-port=9222 --user-data-dir=/tmp/jam-chrome about:blank &
    ```

    agent-browser closes the browser it launched itself the moment it connects elsewhere, so the tapped Chrome has to be one you launched.
  </Step>

  <Step title="Start recording and tap that Chrome">
    ```bash theme={"theme":"css-variables"}
    jam record start --app "Google Chrome" --cdp 9222 \
      --title "Checkout completes" --json
    ```

    The receipt includes a `cdpProxy` address. `--cdp` also takes a `ws://` DevTools URL, or `auto` for a Chrome 144 or later that allows local debugging under `chrome://inspect/#remote-debugging`.
  </Step>

  <Step title="Drive through the proxy">
    ```bash theme={"theme":"css-variables"}
    agent-browser connect <cdpProxy port>
    agent-browser open https://example.com/checkout
    agent-browser click "text=Pay now"
    ```

    Every command runs against Chrome as before, and passes the recorder on the way, so each click becomes a user action with the element it hit.
  </Step>

  <Step title="Stop and get the link">
    ```bash theme={"theme":"css-variables"}
    jam record stop --json
    ```
  </Step>
</Steps>

## chrome-devtools-mcp

[chrome-devtools-mcp](https://github.com/ChromeDevTools/chrome-devtools-mcp) is a Model Context Protocol server that connects to a running Chrome. Because it takes a browser URL, it takes the proxy's URL.

<Steps>
  <Step title="Start recording with the tap">
    Start `jam record start --cdp <port>` against the Chrome the MCP will drive, and read the `cdpProxy` address from the receipt.
  </Step>

  <Step title="Point the MCP at the proxy">
    ```bash theme={"theme":"css-variables"}
    chrome-devtools-mcp --browserUrl http://127.0.0.1:<cdpProxy port>
    ```

    The MCP's navigate, click, and type calls pass the recorder, so they land as user actions. Stop with `jam record stop` when the run is done.
  </Step>
</Steps>

## Playwright and Puppeteer scripts

A script that connects to a running Chrome drives it over the same protocol, so it takes the proxy's address in place of the browser's.

Read the address from the start receipt and pass it to the script:

```bash theme={"theme":"css-variables"}
proxy=$(jam record start --app "Google Chrome" --cdp 9222 --json | jq -r .cdpProxy)
JAM_CDP_PROXY="$proxy" node checkout.spec.js
jam record stop --json
```

```js theme={"theme":"css-variables"}
const browser = await chromium.connectOverCDP(process.env.JAM_CDP_PROXY);
const page = browser.contexts()[0].pages()[0];
await page.goto("https://example.com/checkout");
await page.getByRole("button", { name: "Pay now" }).click();
```

Puppeteer's `connect({ browserURL })` takes the same address. For a Playwright run you keep after the fact rather than recording live, `jam create jam` reads the video and trace directly. See [Turn a Playwright failure into a watchable Jam](/docs/cli#turn-a-playwright-failure-into-a-watchable-jam).

## Computer-use agents

An agent that moves the real mouse and reads the screen, rather than speaking the DevTools protocol, sends no commands for the recorder to see. If its browser was launched with a debug port, tap it anyway with `jam record start --cdp <port>`: the Console and Network panels still fill from the browser's own events, and the video still shows every step. Only the user-action rows are missing, because there is no command to record.

## Browsers the CLI cannot reach

An agent that runs as a Chrome extension drives your normal browser, and an app with a built-in browser pane, such as the Codex desktop app, keeps it to itself. Neither exposes a debug port, so neither path reaches them. Record the browser window for the video, and read the agent's own logs for the rest.

## Any other tool

The pattern is the same whatever drives the browser. Launch Chrome with `--remote-debugging-port` and its own `--user-data-dir`, start the recording with `--cdp` on that port, and hand the printed `cdpProxy` address to the tool wherever it takes a browser URL or a DevTools endpoint. Console and network come from the tap either way. User actions come from any tool that drives over the protocol.

## What lands in the Jam

The Jam's panels mirror the browser. Console lines, uncaught exceptions, and network requests with their headers and status come from the browser's own events. Bodies come from fetch and XHR requests, as text under 256 KB; a document, script, or image body reads as unavailable, and secret-bearing headers and parameters are redacted. Clicks and typed values come from the agent's own commands through the proxy, with the element each one hit. Typed text on a password, one-time-code, card, or sensitively named field is masked.
