Recipes
These are small, real patterns built from the documented tool surface. Each one assumes browxai is already wired into your MCP client; see Getting started if not. The calls are written as TypeScript to show the tool names and arguments. In an MCP client you describe the task and the agent makes the calls. To run them from a script, see Run it from a script.
Log in once, resume later
Section titled “Log in once, resume later”Use a persistent session so the cookie jar survives across runs.
await open_session({ session: "work", mode: "persistent", profile: "acme" });await navigate({ session: "work", url: "https://app.example.com/login" });
const email = await find({ session: "work", query: "the email field" });await fill({ session: "work", ref: email.candidates[0].ref, value: "ada@example.com" });
const pw = await find({ session: "work", query: "the password field" });await fill({ session: "work", ref: pw.candidates[0].ref, value: process.env.APP_PASSWORD });
const signIn = await find({ session: "work", query: "the sign in button" });await click({ session: "work", ref: signIn.candidates[0].ref });The next run with the same profile starts already logged in. For state that
must survive an MCP-server restart, attach to a Chrome you launched yourself;
see Sessions and lifecycle.
Fill and submit a form in one call
Section titled “Fill and submit a form in one call”fill_form sets several fields and optionally clicks a submit target, which is
fewer round trips than one fill per field. Each field targets a ref,
selector, or named ref. Resolution is atomic: if any target misses, nothing
is typed and the result names the missing field.
await fill_form({ fields: [ { selector: '[data-testid="first-name"]', value: "Ada" }, { selector: '[data-testid="last-name"]', value: "Lovelace" }, { selector: '[data-testid="email"]', value: "ada@example.com" }, ], submit: { selector: '[data-testid="save"]' },});Extract structured data
Section titled “Extract structured data”To read without acting, text_search, inspect, and extract keep
the result scoped. extract is schema-driven: each
property name doubles as the query, and arrays take a collection selector for
the repeated container.
await navigate({ url: "https://example.com/pricing" });const res = await extract({ schema: { type: "object", properties: { plans: { type: "array", "x-browx-source": { collection: ".plan-card" }, items: { type: "object", properties: { name: { type: "string" }, price: { type: "number" }, }, }, }, }, },});// res.data = { plans: [{ name: "Starter", price: 9 }, ...] }// res.evidence.partialMisses names anything the schema asked for but missed.// res.warnings names any selector that matched no element.Pair it with the verify_* family when you want an assertion, for example
verify_text or verify_count.
Handle flaky or transient UI
Section titled “Handle flaky or transient UI”browxai has tools for waiting on a condition and for sampling an unstable surface. Use them in place of fixed sleeps.
// Wait for a specific thing to exist, with a real deadline.await wait_for({ text: "Payment confirmed", timeoutMs: 15000 });
// Act and trace a metric across the transition, in one call.const res = await act_and_sample({ action: { tool: "click", args: { ref: "e12" } }, metric: "scrollHeight", durationMs: 2000,});// res.action is the click's ActionResult; res.sample.summary shows// whether (and when) the metric moved during the window.wait_for’s timeoutMs is both its maximum wait and its deadline. If a
session keeps timing out, discard it (close_session then open_session).
Run flows in parallel
Section titled “Run flows in parallel”Give each agent or each flow its own session id. Different ids are isolated
browser contexts, so they cannot stomp each other, even logged in as different
users of the same app.
await open_session({ session: "agentA-checkout", mode: "incognito" });await open_session({ session: "agentB-checkout", mode: "incognito" });// drive both independently by session id ...
// reap one agent's sessions when it is doneawait close_sessions({ prefix: "agentA-" });Test a mobile breakpoint
Section titled “Test a mobile breakpoint”await open_session({ session: "mobile", device: "iPhone 14" });await navigate({ session: "mobile", url: "https://example.com" });
// resize mid-session to check a breakpoint; the result shows what re-renderedawait set_viewport({ session: "mobile", width: 768, height: 1024 });Run it from a script
Section titled “Run it from a script”The same tools run from a Node script through the typed SDK, with no MCP client
in between. The SDK gates tools by capability exactly as the server does, and
the script’s capabilities option and the server’s BROWX_CAPABILITIES each
have to allow a gated tool.
import { createBrowxai } from "browxai";
const browxai = await createBrowxai(); // in-process, one scriptawait browxai.navigate({ url: "https://example.com" });const page = await browxai.snapshot(); // paragraphs carry their text on the rowconsole.log(page);await browxai.close();Import needs a local install in the script’s project (npm install browxai), as
a global install is not importable. Save the script as an ES module (.mjs, or
"type": "module" in package.json) and run it with Node 22 or newer. Any other
tool is reachable with browxai.callTool(name, args). createBrowxai also takes
a transport of "stdio-child" to run the browxai bin as a subprocess, or an
endpoint to attach to a browxai serve --socket <path> server that several
scripts share.
For the full set of tools and their exact inputs and outputs, see the tool reference.