---
title: "Frazer Browsers"
description: "Frazer Browsers: browsers and desktops for agents. A browser any agent drives, next to a preview, in its own cell or on a devbox: compact snapshots with stable refs, scripts of many steps in one call, a receipt for every step, logins as personas with no real credential, a flight recorder of every run, flows that rerun with no model, reproductions of issues that Frazer CI confirms, and desktops for apps outside the browser."
url: https://frazer.ai/docs/agents/browsers
index: https://frazer.ai/docs/previews/llms.txt
tokens: 5941
---

> For agents: this page is indexed at https://frazer.ai/docs/previews/llms.txt, and every page at https://frazer.ai/llms.txt. Copy commands from this file exactly.

# Frazer Browsers

**Frazer Browsers** gives agents browsers and desktops. Agents test and debug through the same screens people use. A **browser** here is a Chromium browser that one agent session drives, through a Rust executor that gives every harness the same compact view of the page, the same actions and a receipt for every step. It runs in the agent’s own cell, next to a [preview](https://frazer.ai/docs/previews/tests), or on a devbox or laptop, and it records everything it does. Agents use it to check a change through the UI against the real backend, to reproduce an issue a user hit, and to hand a page to a person when only a person may act.

> **The short version:** `frazer browser open` next to a preview, as a persona: no password ever reaches the agent. Read the page with `snapshot` (refs such as `@e12`), act with refs or a short script, and check **state** (a response, a row), not only how the page looks. Every run is recorded, every step has a receipt in the Fir session, and a recording becomes a flow that reruns with no model. A reproduction counts only when Frazer CI’s own rerun fails the same way.

## What an agent sees

| View | What it holds |
| --- | --- |
| **interactive** (the default) | One line per actionable element: its role, accessible name, state and ref, with landmarks and headings as context |
| **content** | The visible text in reading order, without refs |
| **full** | The whole accessibility tree with refs, on request |
| **delta** | What changed since the last snapshot of the same document, after every action |

A snapshot covers the viewport by default, or the full page, or one landmark or ref. Frames appear inline with their own refs, including cross-origin frames, and open shadow roots are included. Page text is built from what the page renders, never from raw source, so hidden text never reaches the model, and it is wrapped in **content boundaries** with a random nonce and its origin. A snapshot over its size limit returns its first part and a handle to page through. Screenshots are for layout, images and canvases, with `zoom` for small text; they carry no refs, so they are never the way to find a control.

```text
--- PAGE nonce=7f3a origin=https://pv-4718.preview.frazer.run ---
main "Checkout"
  textbox "Email" value="buyer@acme.example" @e4
  textbox "Gift card code" value="" @e11
  button "Apply" @e12
  region "Payment card" (cross-origin frame: the payments fake)
    textbox "Card number" @f1e2
  button "Pay $42.00" @e19
--- END nonce=7f3a ---
```

A ref names an element until the element is replaced or its document navigates; it is never reused for another element. Every action returns a **receipt** that the executor writes into the [Fir session](https://frazer.ai/docs/sessions): the step, the action and its resolved target, the outcome (`done`, `failed` with the reason, or `outcome_unknown` when the executor cannot tell whether a change ran, which is never retried blindly), the effects (navigation, popups, dialogs, downloads, requests and their statuses, new console errors, the delta), the rule that allowed it, the time and the browser seconds it used.

Errors name the next call:

| Error | Means | Next |
| --- | --- | --- |
| `STALE_REF` | The element was replaced or the page navigated | `snapshot` |
| `AMBIGUOUS_TARGET` | A locator matched several elements | `snapshot --scope <landmark>` |
| `NOT_ACTIONABLE` | Covered, disabled or outside the viewport | The receipt names what covers it |
| `ORIGIN_NOT_ALLOWED` | The origin is outside the run’s origin set | A person’s approval to widen it |
| `POLICY_ASK` | The action needs an approval | Wait for the approval card |
| `OUTCOME_UNKNOWN` | A change may have run | Observe (`snapshot`, `net`) before any retry |
| `NOT_SETTLED` | The page did not settle in time | `wait --settled`, or snapshot it as it is |
| `AUTH_REQUIRED` | The app asks for a login | `login --persona <p>`, or a hand-off for a real sign-in |
| `CAPTCHA_PRESENT` | A challenge blocks the page | Hand off: Frazer never solves one |
| `FUNDS_EXHAUSTED` | The budget or the balance cannot cover the run (“Not run: no funds”) | The balance call, then the top-up person task |
| `INJECTION_SUSPECTED` | Page content looked like instructions | The run’s policy: go on with the origin read-only, or hand off |
| `STEP_LIMIT_REACHED`, `BUDGET_REACHED` | The run stopped at a limit you set (exit 14); the result so far is kept | Raise the limit or continue from `haltedAt` |
| `STALE_CAPTURE` | A pixel action names a screenshot the screen no longer matches | Take a new screenshot |
| `METHOD_NOT_ALLOWED` | A protocol command the gate refuses | The receipt names the rule |

**Waits and checks.** `wait` takes a URL pattern, text, a ref’s state, a function or `settled`. A **check** records an assertion: a UI check (visible text, a value, a role’s state) or a **state check** (an API response, a row read through the preview’s data API, a page tool’s result). A test passes on state checks, never only on how the page looked, and each fact a plan item’s acceptance criteria expect is checked on its own. An agent’s “verified in the UI” counts only when its receipts show the path: the page opened, the action taken, the state read.

**Page tools and the app map.** Tools a page registers through WebMCP are offered as typed tools, and Frazer’s own console registers its actions that way. Agents set up and check through APIs and page tools, and drive the UI for what the test is about. A repository may keep an **app map** (`.frazer/app-map.yaml`: routes, landmarks, key flows, personas, readiness signals and page tools), written by an exploring agent and reviewed like code.

## Four ways to act, one executor

-   **Scripts.** An agent sends a short JavaScript program in a familiar end-to-end-test style (`page.goto`, `getByRole(...).click()`, `fill`, `check`). It runs in an isolated engine inside the executor with no file, network or process access; the call has one receipt and each step a child receipt; policy and budget are checked per step, so a script can never carry an action the rules would refuse one step at a time. It stops at the first failed step and reports what ran. Limits by default: 60 seconds, 200 steps, the run’s origin set; `--max-steps` and `--budget` set your own, and a run stopped at either exits 14 with what it did so far.
-   **Each model family’s own browser and computer tools.** Models are trained on their own labs’ tool shapes, so the executor is the driver behind each family’s toolset, and the harness picks the face per model family by default. The [harness Lab](https://frazer.ai/docs/agents/harnesses) measures that choice on your own flows.
-   **Frazer’s tools.** MCP tools (`browser_snapshot`, `browser_click`, `browser_run` and the rest), the CLI and the API, with the same verbs and JSON results, for any harness. Every action takes `--dry-run` and `--estimate`.
-   **The protocol endpoint.** `frazer browser open --cdp` returns a `wss://` endpoint bound to that browser and its Fir session, so existing end-to-end suites and agent frameworks connect unchanged; the same browser speaks WebDriver BiDi. A **method gate** refuses with `METHOD_NOT_ALLOWED` the commands that would step around the policy: a context with its own proxy, ignoring certificate errors, downloads outside the cell, attaching to another target, and, for agents, page scripts on third-party origins. Every input and navigation still writes a receipt, and the cell’s network still enforces egress below the browser.

**Suspended between steps.** Models take seconds between steps, so an idle browser is suspended while it waits and resumes on the next action; open streams stay inside step mode’s held clock.

## Logging in without real credentials

1.  **Reaching the preview.** Previews on `frazer.run` sit behind Frazer sign-in. A browser in the preview’s network reaches it with its cell’s workload identity: the egress gateway adds a short-lived token for that preview host only. No secret in the cell, no person task.
2.  **The app’s own login.** The preview’s recipe declares **personas** (`buyer`, `admin`). `frazer browser login --persona buyer` asks for a **login handle** that expires within 10 minutes and redeems it once at the preview’s own login path. For apps on Frazer sign-in, the preview’s front door mints the app’s session from the handle; for apps with their own login, Frazer seeds the persona in the app’s user store, runs the app’s own login once per persona and preview revision with generated credentials kept in the preview’s secrets, and installs that session. Passkeys come from a virtual authenticator seeded per persona, one-time codes from seeds Frazer holds, and email links and codes from the preview’s inbox. When the test is about the login itself, the persona signs in through the real login form against the real backend.

The session ends with the run (1 hour by default), cookies stay on the preview’s own host, and every mint is an audit row. A real third-party login is **Sign in here**: a [person task](https://frazer.ai/docs/source/person-tasks), with the recording paused while the person signs in. On an allowed third-party origin the agent asks with `frazer browser handoff --reason sign-in --origin <origin>`, and the person gets a **sign-in card**: it shows the verified origin and the form’s fields, the person types into Frazer’s own interface and never the page, the executor fills the form and the model never sees the values; it expires in 5 minutes and a retry never fills the form twice. A take-over remains for what a card cannot carry, such as a CAPTCHA. A persona’s passkey is answered from outside the browser’s VM, so its private key never enters it. An agent’s run never gets a password in a prompt, a credentials file in the cell or a person’s own browser profile.

## The flight recorder

| Captured | Format | By default |
| --- | --- | --- |
| Steps and receipts | JSON, in the Fir session | Always |
| Snapshots around each step | The executor’s text views | Always |
| Network | HAR 1.2, bodies capped at 64 KiB and scanned | Always |
| Console and page errors | Source-mapped stacks | Always |
| DOM recording | The same open format as Frazer’s session replays, so one player serves both | Always |
| Video | Frames at 5 per second as WebM, with the agent’s cursor and each step as a chapter | Always on Frazer’s fleet; on request locally |
| Screenshots | At checkpoints and on failure | Always |
| Versions | Engine build, app build digests, preview, data branch, persona, flow revision | Always |

Password and card fields, and fields the app map marks, are never captured: they are masked in the DOM stream, the video and the HAR. Secret-bearing headers are dropped, bodies pass the gateway’s data-loss checks, persona data is synthetic, and each recording carries a redaction report. Recordings are stored by content in Frazer Storage and kept 14 days for passing runs and 90 days for failing ones by default; a recording attached as evidence keeps the evidence’s retention. Playback syncs the DOM, the video, the network and the console by step and compares two runs. A video is a view of a run, never its verdict.

## Flows and reruns

A **flow** (`tests/flows/<name>.flow.yaml`) is a recording’s steps as an editable file with role and name targets, the persona and data as parameters, UI and state checks and readiness. Flows come from recordings, a person’s walkthrough or an agent, and they are reviewed like code.

| Rerun | Environment | Speed | Catches |
| --- | --- | --- | --- |
| **Fast** | The new frontend build, with the network answered from the recording | Seconds | Frontend regressions, with very little flake |
| **Deep** | A preview at the exact version and data branch, with the real backend | Minutes | Everything a user would see, the backend included |

Each rerun pins the clock, randomness, time zone, locale, fonts, animations, network, viewport and device as asked. Verdicts are `pass`, `fail` (with the failing step and the failure signature), `reproduced k of n`, `flaky`, `blocked` (the environment failed) and `Not run: no funds`. Flows map to the code they exercise, so only the flows a change reaches rerun. When a flow fails for a reason that is not the product (a renamed label), an agent proposes the healed flow as a pull request with before and after recordings; a flow that proves an acceptance criterion is read-only to the implementing agent, and its change goes to the plan’s approver. An agent’s successful run can be proposed as a flow, so the next run needs no model.

## Reproducing an issue

| Step | What happens |
| --- | --- |
| 1\. Evidence | The [issue’s](https://frazer.ai/docs/source/issues) evidence: a user’s session replay, a HAR, the error group, the release and the steps, redacted |
| 2\. Environment | A preview of the issue’s **moment**: the services that ran then, data branched at that second from the masked replica, the failing requests’ trace IDs, and services isolated so the past never mixes with today’s |
| 3\. Steps | A flow from the user’s replay, from the HAR, or written by an agent from the issue’s text; a masked field becomes a persona-filled parameter, never the original value |
| 4\. Rerun | Three reruns by default with the clock, randomness and network pinned; the **failure signature** (a status and error code, an assertion, a console error) must match the issue’s |
| 5\. Evidence back | The recording is attached to the issue as an attested reproduction: run by Frazer CI in a preview with these digests |
| 6\. The test | The exported test goes into the fix pull request, and Frazer CI reruns it on its own: it fails at the issue’s version and passes at the fix |

On `acme/storefront#4711` “Checkout fails with 500 on gift-card orders since v2.14.2”, filed by the `monitor` agent, the `fixer` agent’s session `s-4a71` opens browser `br-7a2c` next to the reproduction preview `pv-4718` as `buyer` and reruns the user’s steps: **reproduced 3 of 3**, signature `POST /api/checkout → 500 GIFT_CARD_STATE`. Recording `rec-4711a` goes on the issue; the exported test `tests/e2e/repro/4711-gift-card-checkout.spec.ts` fails at `v2.14.2` and passes on the fix, pull request `#4718` (demo data). A reproduction that fails every rerun ends `not_reproduced` with what was tried.

## Testing a change through the UI

For every version of a pull request that touches UI or a user flow, the [test plan](https://frazer.ai/docs/previews/tests) runs:

1.  **Flow reruns with no model**: the fast tier for every such pull request (two smoke flows by default), and the deep tier with the full flow set at the top of each stack.
2.  **An agent verifies what flows do not cover**: the plan item’s acceptance criteria, through the UI against the real backend, with state checks; new paths become proposed flows.
3.  **Evidence on the pull request**: the test run, its recordings and checks. Because a tester’s session produced them, not the author’s, they are an independent source of evidence for the second opinion and auto-merge.
4.  **Outcomes link back**: a UI bug that escapes links to the flows that ran on its pull request, and the gap becomes a flow.

## Safe by default, below the browser

| Default | How |
| --- | --- |
| Network | The cell’s egress allows the preview’s origins and its declared fakes only; private and link-local ranges are blocked and checked again after redirects. A project admin widens it per origin, as a plan |
| Your own apps by default | A run’s origins are your previews on `frazer.run`, the domains your organization verified and the fakes your recipes declare; anything else is `ORIGIN_NOT_ALLOWED`. A preview’s own third-party resources (map tiles, fonts, a payment frame) are declared in its recipe: the page loads them, and the agent never navigates to or acts on them |
| Outside sites, by opt-in | A project admin adds an outside origin to the project’s browser policy as a reviewed change, with its purpose (`vendor_portal`, `docs` or `research`), its mode (read-only or read-write) and its rate limit (by default 1 request a second and 2,000 a day). The policy file is a sealed path: an agent may propose an origin (`frazer browser policy propose`), and a project admin who did not write the change approves it |
| Origin sets | Per run: the origins an agent may read and the origins it may act on; frames from other origins are not shown |
| Schemes | `http` and `https` only, always |
| Actions | Seven action classes for every model: payments and legal terms Hand off; creating accounts, sending messages and changing sensitive data Ask; other data changes Ask outside the preview; consent Auto on your previews with test personas, Hand off elsewhere |
| The action reviewer | A model of another family checks an action against the task from the action’s metadata, never from page content |
| Injection probes | The gateway’s classifiers scan page text and screenshots before the model reads them |
| Page scripts | Off for agents by default, on for flows and CI |
| Downloads, uploads | Downloads stay in the cell and are scanned; uploads come only from the workspace |
| Profiles | A fresh profile per run |
| Identity | No stealth, no fingerprint spoofing, no challenge solving. On outside sites agents sign their requests with Web Bot Auth by purpose: as your organization on vendor portals and docs, as Frazer for research reading, with your own rate limit. Agents slow down on 429 and follow robots.txt on research sites |
| Kill switch | Your organization’s kill switch stops every browser and desktop |

The renderer’s own sandbox stays on inside a microVM per browser, and engine security fixes ship within 72 hours, or 24 when a flaw is exploited.

## Desktops

A **desktop** (`dsk-…`) is a Linux VM with a graphical session (Linux first; macOS on Macs dedicated to one customer and Windows on the customer’s own licenses come after) that one agent session drives, for desktop apps, flows that leave the browser (a file dialog, an OS prompt) and apps with no API. It has the same Fir session, receipts, policy, recording and live view as a browser. The agent uses the app’s own protocol first (Chromium-based desktop apps expose the same snapshots and refs), the operating system’s accessibility tree second, and pixels last. An allowlist names the apps a run may use; system settings, keychains and credential stores are off limits.

```sh
frazer desktop open --image ubuntu-26.04 --size 4x8
```

## Prices

Browsers bill **$0.02 per active browser-hour**, by the second, where a browser-hour is the wall time of an active browser session: UI flows, exploratory testing and reproductions through the UI. Warm pools and idle browsers are not billed. Recordings bill as Frazer Storage; the agent that drives the browser bills its own runtime and tokens; every response reports the browser time it used. A run whose estimate does not fit the budget or the balance does not start and says so (“Not run: no funds”); it never falls back to a cheaper run. The price is an estimate, calibrated against measured use. The price drops to $0.01 per browser-hour at the first quarterly re-read whose measured cost is at or below $0.006 a wall-hour.

## Commands

```sh
frazer browser open https://pv-4718.preview.frazer.run/checkout --persona buyer
frazer browser snapshot --view interactive
frazer browser fill @e11 GC-2Q7F
frazer browser click @e12
frazer browser run --script checkout.js --max-steps 200 --budget 0.05
frazer browser policy get --identity
frazer browser check --api 'GET /api/orders?last=1' --expect '{"state":"paid"}'
frazer browser rerun rec-4711a --preview pv-4718 --times 3 --pin clock,random,network
frazer browser repro --issue 4711 --preview pv-4718
```

`frazer browser …` runs the browser’s own binary, which also runs alone on a devbox or laptop and follows the [CLI contract](https://frazer.ai/docs/for-agents/cli): help as data, the error envelope with `next`, the exit codes, no prompts. `frazer browser list`, `get` and `close` act on hosted browsers (`br-…`), each bound to a Fir session.

### CLI

| Command | What it does |
| --- | --- |
| `frazer browser open` | Open a browser for your session at a URL, next to a preview and as one of its personas; returns the first snapshot. |
| `frazer browser snapshot` | The page as an agent reads it, interactive (the default), content, full or the delta since the last snapshot, with refs such as @e12. |
| `frazer browser run` | Run a short script of many steps in one call, with one receipt per step; it stops at the first failed step. |
| `frazer browser check` | Record a check on the page or on state behind it (an API response, a row); a test passes on state checks. |
| `frazer browser login` | Sign in as one of the preview's personas with a one-time login handle; no real credential reaches the agent. |
| `frazer browser handoff` | Hand the page to a person for a real sign-in, a challenge or a payment, as a person task. |
| `frazer browser policy get` | The project's browser policy, the outside origins it allows with their purpose, mode and rate limit. |
| `frazer browser policy propose` | Propose an outside origin as a reviewed change; a project admin who did not write it approves. |
| `frazer browser rerun` | Rerun a recording or a flow k times against a preview, with the clock, randomness and network pinned. |
| `frazer browser repro` | Reproduce an issue from its evidence in a preview at the issue's moment, and export the failing test. |
| `frazer browser export` | Export a recording as an editable flow or an end-to-end test file. |
| `frazer browser list` | List hosted browsers (br-…) with their sessions, states and causes; get and close complete the set. |
| `frazer desktop open` | Open a Linux desktop VM for your session, to drive apps outside the browser. |

All of them: [CLI reference](https://frazer.ai/docs/cli).

### MCP tools

| Tool | What it does |
| --- | --- |
| `browser_open` | Open a browser and return its first snapshot. |
| `browser_snapshot` | Read the page as compact text with refs. |
| `browser_run` | Run a script of many steps, with a receipt per step. |
| `browser_check` | Check the UI or the state behind it. |
| `browser_repro` | Reproduce an issue and return the recording and the exported test. |
| `browser_handoff` | Ask a person for a sign-in, a challenge or a payment; a sign-in card on an allowed outside origin. |
| `browser_policy_get` | The project's browser policy. |
| `desktop_screenshot` | A screenshot of a desktop, for what the accessibility tree misses. |

All of them: [MCP tools reference](https://frazer.ai/docs/mcp).

> **For agents:** Open the browser next to the preview and sign in with `login --persona`; never type a real password or ask a person for one. Read with `snapshot`, act on refs, and after `OUTCOME_UNKNOWN` observe before you retry. A test passes on state checks. Hand off sign-ins to other sites, challenges and payments: you never solve a challenge or pay. Set `--budget` on long runs.

## Related

-   [Frazer QA](https://frazer.ai/docs/previews/tests): the environments and test plans the browser runs against
-   [Issues](https://frazer.ai/docs/source/issues): evidence and reproductions
-   [Harnesses](https://frazer.ai/docs/agents/harnesses): which face each model family gets
-   [Sessions](https://frazer.ai/docs/sessions): where every receipt and recording is linked

## Pages in Frazer Agents

- [Overview](https://frazer.ai/docs/agents.md)
- [Harnesses](https://frazer.ai/docs/agents/harnesses.md)
- [Frazer Skills](https://frazer.ai/docs/agents/skills.md)
- [Frazer Browsers](https://frazer.ai/docs/agents/browsers.md) (this page)
