Worked example — authoring over MCP
For the complete documentation index, see llms.txtThis is the orchestration an agent runs to author a test end-to-end — the literal tool-call trace, plus the exact input schema for the recording tools.
The loop
explore_start → goto → get_page_snapshot → explore_step×N (with refs) → save_exploration_as_test → run_test → (step / resume / inspect_runtime on pause)Trace: record a sign-in
// 1. Start a recording session.explore_start({ scenario_name: "auth/login", title: "Sign in", description: "Demo user signs in"})// → { explorationId, availableVariables, availableFlows }// If availableFlows already has "signin", DON'T re-record it — call explore_run_flow.
// 2. Navigate. goto needs no element ref.explore_step({ explorationId, action: "goto", url: "/login", section: "Sign in", description: "Open the login page"})
// 3. Snapshot to get element ref handles ([ref=eN]).get_page_snapshot()// → outline containing [ref=e3] email input, [ref=e5] password input, [ref=e8] submit button
// 4. Record actions, addressing elements by ref.explore_step({ explorationId, action: "fill", locator: { kind: "locator", steps: [{ kind: "ref", ref: "e3" }] }, value: "demo@example.com", section: "Sign in", description: "Type the email"})explore_step({ explorationId, action: "fill", locator: { kind: "locator", steps: [{ kind: "ref", ref: "e5" }] }, value: "hunter2", section: "Sign in", description: "Type the password"})explore_step({ explorationId, action: "click", locator: { kind: "locator", steps: [{ kind: "ref", ref: "e8" }] }, section: "Sign in", description: "Submit the form"})
// 5. Save to disk. flow:-marked steps are extracted into _helpers/<flow>.js.save_exploration_as_test({ explorationId, scenarioName: "auth/login" })// → writes unotest/e2e/auth/login.js
// 6. Verify it runs green.run_test({ scenario: "auth/login" })// → { runtimeId }. If it pauses (breakpoint/failure): inspect_runtime → patch → resume.explore_step / explore_record — input schema
explore_step runs one action. With an explorationId it records the step;
without one it executes ad-hoc (no recording). explore_record is the same
envelope but section + description are required and locators must be
ref-form.
| Field | Type | Notes |
|---|---|---|
action | string | required — one of the actions below |
explorationId | string | present → record · absent → ad-hoc run |
locator | ref locator | element to act on (see below) |
url | string | for goto |
pattern | string | for wait_for_url (substring match) |
value | string | string[] | for fill, select_option |
key | string | for press (e.g. "Enter") |
text | string | for wait_for_text |
options | object | action options (e.g. { force: true }, nav options) |
section | string | group label · required when recording |
description | string | step intent · required when recording |
flow | string | mark step as part of a reusable flow_<name> |
allowNoRef | boolean | allow a non-ref locator (last resort) |
Actions and their fields
| Action | Fields |
|---|---|
goto | url |
reload, go_back, go_forward | — |
click, double_click, hover, check, uncheck, scroll_into_view, wait_for | locator |
fill | locator, value (string) |
press | locator, key |
select_option | locator, value (string | string[]) |
wait_for_text | text |
wait_for_url | pattern |
enter_frame | locator |
exit_frame, set_page | tab/frame switch — see the live tool description |
Ref locators
While recording, address elements by the [ref=eN] handles from
get_page_snapshot (or get_aria_snapshot):
{ "kind": "locator", "steps": [{ "kind": "ref", "ref": "e8" }] }Recording rejects non-ref locators (so saved tests get stable
getByRole/getByTestId selectors, not brittle ones). Pass allowNoRef: true
only as a last resort for a hand-written locator. On save, refs are resolved to
the stable selector form per the selector priority.
After saving
The generated unotest/e2e/auth/login.js is plain .js with step("…")
blocks. Add assertions, run run_test, then npx @unotest/web lint. On failure,
read the failure bundle and propose a diff — the
human approves it (self-healing is never silent).