Supervised end-to-end testing

Watch the AI test your app. Take over the moment it matters.

Handoff runs end-to-end tests from a plain-language goal on your own model key, streams the browser to your screen while it works, and lets you take the controls mid-run. What you do by hand becomes a named test step. Replays never call a model.

Free plan included. Bring any OpenAI-compatible, Anthropic, Google or local model.

A Handoff run in progress: the live browser in the centre, the agent's steps on the left, the accessibility snapshot it saw on the right, and Pause, Take control and Hand back buttons above the video.
An actual run, captured from the product: the agent holds the control lease while a reviewer watches the live browser and can take over at any step.

Describe the goal

“Sign in as the standard user, add a todo, sign out.” Point it at a staging environment with an allowlist of origins and the secrets it may type.

Watch it work

The browser streams to the run page. Each step shows the action, the accessibility tree the model saw, and the outcome. Nothing happens off screen.

Take control when needed

Press Take control. The agent is locked out, your clicks and keystrokes land in the page, and every action is recorded with a durable locator. Hand back when done.

Replay without a model

Accept the proposed steps and the test replays deterministically, in CI or on a schedule, with zero model calls. Export it as plain Playwright any time.

Autonomy is only useful if you can interrupt it.

Most AI testing tools hand you a report after the fact. Handoff is built around a control lease: at every moment exactly one party drives the browser, and the human can take that lease at any step.

01

Live view with real takeover

Frames stream over the browser's own screencast protocol, so your clicks and the agent's clicks are the same event in the same coordinate space. A dropped connection never loses your control: the lease lives on the server, not in the socket.

02

Manual actions become test steps

Every click during a takeover is resolved to a locator before the page can change. The before-and-after accessibility diff proposes assertions. You review a named, parameterised step and confirm it before anything is saved.

03

Replay is deterministic, by design

Recorded steps replay with cached locators, web-first assertions and the app's own readiness signal. No fixed sleeps, no idle-network waits, no model in the loop. There is a test in our own suite that fails if a model call ever appears there.

How control works

One broker talks to the browser. Nobody else.

The agent and the viewer are both clients of a session broker that holds the only connection to Chrome. Control is a lease with an epoch that increments on every transfer. A late agent command carrying an old epoch is rejected, so the race between “the agent clicks” and “you clicked first” cannot happen.

  • Transfer waits for in-flight commands and navigation to settle, then snapshots the page.
  • Hardware-level input ignore is the backstop while control changes hands.
  • After a takeover the agent re-plans from a fresh observation. It never replays stale steps.
  • Each session gets its own browser context, and on plain servers its own hardened, sandboxed Chrome process.
// the control lease, as stored on the server
{
  sessionId: "01a094bb-5326-7e13-…",
  owner:     "human",      // 'agent' | 'human' | 'transferring' | 'none'
  epoch:     3,            // increments on every transfer
  holderId:  "user_…",
  expiresAt: 1789123456789 // heartbeat-renewed by the viewer
}

// an agent command with epoch 2 now returns:
{ code: "STALE_EPOCH", message: "Control changed hands. Re-observe before acting." }
No lock-in

Every test exports as plain Playwright. On every plan.

The export is generated code you can read, edit and run under stock @playwright/test: role-based locators, the readiness signal, console and network collectors, and an optional accessibility scan. Secrets become environment variables. Our own test suite runs the exported file to prove it.

How export works →

// Exported from TC-0001 — Sign in and manage todos
// Plain @playwright/test. No model calls,
// no fixed sleeps, no idle-network waits.
import { expect, test } from '@playwright/test';

test("TC-0001 Sign in and manage todos", async ({ page }) => {
  await page.goto(BASE_URL);
  await ready();        // the app's own readiness signal

  await test.step("1. Type into Username", async () => {
    await page
      .getByRole('textbox', { name: "Username", exact: true })
      .fill("standard_user");
  });

  await test.step("2. Enter the password secret", async () => {
    const pw = page.getByRole('textbox', { name: "Password" });
    await pw.fill(secret("password"));
    await pw.press('Enter');
    await ready();
  });

  await test.step("3. Assert URL contains /dashboard", async () => {
    await expect(page).toHaveURL(new RegExp("/dashboard"));
  });

  expect(consoleErrors, 'console/page errors').toEqual([]);
  expect(badResponses, 'unexpected 4xx/5xx').toEqual([]);
});
The paper trail

Requirements to report, without a second tool.

Link requirements to test cases, group cases into plans, run a plan as a cycle. The traceability matrix and the cycle report are derived from what actually ran: which requirement is verified, failing, untested or uncovered, which execution failed at which step, and what it cost in model calls.

  • A failed execution opens exactly one incident, with the failing step's message.
  • Incidents export to Jira Cloud with one click once a connection is configured.
  • Reports download as Markdown so they travel with the release.
The requirements traceability matrix: each requirement with its priority, verdict, and the test case and run that covers it.

Everything a run touches, accounted for.

Bring your own model

Anthropic and Google natively; anything OpenAI-compatible by base URL and key; local models through a small edge runner that never opens an inbound port. Capabilities are probed when you bind, and shown, not assumed.

Cost and token accounting

Every model call is recorded with input, output and cache-read tokens and a cost in cents. Usage per month is on the settings page, next to the plan limits.

Self-healing with guardrails

Locators heal, assertions never do. A candidate must keep the same role and a similar accessible name, it is proposed as a diff for approval, and three heals in a month escalates as a design defect instead of a quiet fix.

Visual regression, advisory

Each replayed step keeps a baseline picture. Later runs compare with a perceptual hash and a pixel diff. Changes are shown in a separate lane and never fail the step on their own.

Approval for dangerous actions

Payments, deletions, sending mail and navigation off the allowlist pause the run and wait for a person to approve or deny. The verdict is deterministic; no model decides what is dangerous.

Free riders on every step

Console errors, uncaught exceptions and unexpected 4xx/5xx responses are collected on every observation. A replayed step fails on a page error or a 5xx without any extra configuration.

Numbers we design against

Published analyses of flaky tests and our own measurements. They decide where the engineering effort goes.

45%of flaky tests trace to async waits. That is where the budget goes: app readiness signals, not sleeps.
7%trace to selectors. Self-healing is useful, but it is not the main event, so it stays guarded.
<150 msmedian replayed step in our benchmark, which fails the build if it regresses.
$0.00model cost per replayed test. Authoring uses your key; replays use none.
Security

Assume every page is hostile.

A browser under AI control visits pages that may try to instruct it. Handoff treats page content as data, keeps secrets out of prompts entirely, constrains the agent to a typed action schema, and isolates each session's browser.

Read the security overview →

  • Secrets are typed from placeholders at keystroke time inside the browser driver, only on the origins you allow. Prompts, logs, traces and exports only ever see {{secret:name}}.
  • Passwords and card-shaped fields are masked at capture, never after the fact.
  • Navigation is limited to the environment's allowlist; other URLs need approval.
  • User-supplied URLs are resolved and checked against private ranges on every request.
  • Your model key is stored in a file-backed secret store, never in the database.

Questions people ask first

Does it need our model key, or do you host one?
Yours. Bind any OpenAI-compatible endpoint, Anthropic, Google, or a local model. Calls go straight from our runner to your provider on your key; we record tokens and cost per call. There is no hosted model tier, which also means your DOM and screenshots never pass through a third party we chose.
Does it solve CAPTCHAs or log in through MFA?
No, deliberately. Test against a staging flag, a test key or an IP allowlist agreed with the app owner. For authenticated targets, environments hold scoped secrets that the runner types for the agent, and exported tests use a test-only session mint endpoint or storage state.
What happens when the app changes and a locator breaks?
The replay looks for a control with the same role and a similar accessible name, runs with the candidate, and records a heal proposal you can approve as a diff. It refuses when the name differs beyond a threshold (that is a product change, not drift), when the heal would turn a failing assertion green, or after three heals in a month for the same test.
Can we leave?
Yes. Every test case exports as a standalone Playwright spec, on every plan, including the free one. The cycle report downloads as Markdown.
Where does it run?
Hosted, with each browser session in its own hardened process that is destroyed when the session ends. Local models stay local: the edge runner makes an outbound connection only, so nothing on your network is exposed.

Run your first supervised test today.

Create an organisation, point Handoff at a staging app, bind your model key, and watch the first run in your browser.

Handoff — Supervised end-to-end testing: watch the AI test, take over when it matters.