llm-browser-testkit
Describe browser tests in plain English. The LLM figures out which elements to click and whether the page looks right — plus A2A agents, MCP tool-calling, cost tracking, and budgets.
llm-browser-testkit run smoke.toml
Contents
- Quick start
- Write your first test
- Step reference
- Assertion presets
- CLI reference
- Endpoints
- A2A agents
- Run as an A2A agent
- MCP tools
- MCP server
- Cost tracking & budgets
- How it works
- Use as a library
- LLM authentication
- License
Quick start
# Install
# Set your LLM credentials (OpenAI-compatible API)
# Run the built-in example (tests example.com — no account needed)
Write your first test
# hello.toml
[]
= "https://example.com"
= 30
= "/"
[[]]
= "no_errors"
= "no_error_on_page"
[[]]
= "Homepage loads"
[[]]
= "navigate"
= "/"
[[]]
= "assert"
= "no_errors"
Run it:
Step reference
Every step has a kind. Required fields depend on the kind.
kind |
What it does | Required | Optional |
|---|---|---|---|
navigate |
Open a URL | url |
wait_after_ms |
click |
Click an element | target |
selector, wait_after_ms, endpoint |
type |
Type into a field | target, text |
selector, wait_after_ms, endpoint |
wait |
Wait for an element | target |
selector, timeout_ms, endpoint |
assert |
Check the page | one of definition, preset, or prompt |
assert_text, endpoint |
screenshot |
Save a .png | — | path |
agent |
Call an A2A agent | agent, task |
definition |
mcp |
Call an MCP tool | server, tool |
args |
target is natural language ("the submit button", "the search input"). The
LLM looks at the page DOM and picks the right CSS selector at runtime. Skip the
LLM with an explicit selector.
endpoint routes this step to a specific endpoint. Use it to
send element targeting to one model and assertions to another.
Assertion presets
Built-in presets you can use inline or from [[definitions]].
| Preset | What it checks |
|---|---|
no_error_on_page |
No errors, stack traces, or broken UI on the page |
text_visible |
Specific text appears on the page (assert_text) |
element_exists |
A described UI element is present |
Custom assertions with prompt send any question to the LLM:
[[]]
= "assert"
= "Does the page have a heading that says 'Example Domain'?"
Custom presets with system + user_template let you define reusable assertion
logic with template variables {url}, {title}, {content}, {expected_text},
and {description}:
[[]]
= "text_matches"
= "You are a QA tester."
= "Does the page at {url} contain the text: {expected_text}?"
= "Welcome back"
CLI reference
llm-browser-testkit run <scenario.toml> [OPTIONS]
| Flag | Default | Description |
|---|---|---|
--llm-url |
$HARNESS_LLM_TEST_URL or http://localhost:8080 |
OpenAI-compatible endpoint |
--llm-model |
$HARNESS_LLM_TEST_MODEL or deepseek |
Model name |
--llm-api-key |
$HARNESS_LLM_API_KEY |
API key (Bearer token) |
--llm-header |
— | Custom header Name:Value (repeatable) |
--model-param |
— | Provider param key=value (repeatable) |
--base-url |
$HARNESS_BROWSER_BASE_URL or http://localhost:4200 |
App under test |
--headless |
true |
Run Chrome headlessly |
--timeout |
60 |
Seconds per action |
--viewport-width |
1280 |
Browser width |
--viewport-height |
720 |
Browser height |
--start-url |
/dashboard |
First page to load |
--max-cost |
— | Global budget: max USD across all tests |
--max-tokens |
— | Global budget: max tokens across all tests |
--budget-enforcement |
hard |
Budget mode: hard (abort) or soft (warn) |
CLI flags override the scenario [config].
Endpoints
Define multiple named endpoints — LLM providers, MCP servers, and A2A agents — each with their own pricing, and route test steps to them automatically or explicitly.
[]
= "llm"
= "https://api.openai.com"
= "gpt-4o-mini"
= "sk-..."
= { = 0.15, = 0.60 }
= ["targeting", "assertion"]
[]
= "llm"
= "https://api.openai.com"
= "gpt-4o"
= "sk-..."
= { = 2.50, = 10.00 }
= []
[]
= "mcp"
= "npx"
= ["-y", "@modelcontextprotocol/server-postgres", "postgresql://localhost/mydb"]
= { = 0.001 }
[]
= "a2a"
= "http://localhost:9090"
= { = 0.01 }
[[]]
= "Dashboard with vision"
[[]]
= "navigate"
= "/dashboard"
# Use the vision endpoint just for this assertion
[[]]
= "assert"
= "no_error_on_page"
= "vision"
Endpoint types:
llm— OpenAI-compatible chat completions API. Pricing is per-token (input_per_1m_tokens,output_per_1m_tokens).mcp— Model Context Protocol server. Launched as a subprocess viacommand+args. Pricing isper_call.a2a— Agent-to-Agent Protocol agent. Communicates via JSON-RPC over HTTP at the givenurl. Pricing isper_call.
Routing:
default_forlists which task types an endpoint serves automatically (targetingfor element resolution,assertionfor assertions).- Add
endpoint = "name"on any step or[[test]]group to override routing.
A2A agents
Call remote A2A agents in your test scenarios as steps, or use them inside assertion definitions for reusable agent-backed checks.
Agent step
[]
= "a2a"
= "http://localhost:9090"
= { = 0.01 }
[[]]
= "Audit trail check"
= [
{ = "navigate", = "/admin/audit" },
{ = "agent", = "audit_bot", = "Check if user 'admin' appears in the recent audit log" },
]
Agent-backed assertions
Define reusable agent assertions with task_template:
[[]]
= "audit_verify"
= "audit_bot"
= "Verify that {expected_text} is true for the page at {url}"
[[]]
= "assert"
= "audit_verify"
= "the user can see the dashboard"
Template variables available: {url}, {title}, {content}, {expected_text},
{description}, {task}.
Run as an A2A agent
Enable the a2a-server feature to expose the framework as an A2A agent that
other agents or orchestrators can call. The server listens on a port and accepts
tasks/send JSON-RPC requests.
[]
= true
= 3100
# Build and run with the a2a-server feature
Or via CLI without modifying the TOML:
Docker deployment
A Dockerfile is included in the repository — it uses a multi-stage build with
Alpine and Chromium.
MCP tools
Call MCP server tools directly from test steps to query databases, read files, or invoke any tool an MCP server exposes.
[]
= "mcp"
= "npx"
= ["-y", "@modelcontextprotocol/server-postgres", "postgresql://localhost/mydb"]
= { = 0.001 }
[[]]
= "Database smoke test"
= [
{ = "navigate", = "/dashboard" },
{ = "mcp", = "db", = "query", = { = "SELECT count(*) FROM users" } },
{ = "assert", = "no_error_on_page" },
]
MCP servers are launched as subprocesses via the configured command and args.
The framework handles the MCP initialize handshake, tool listing, and invocation
automatically.
MCP server exposure
Enable the mcp-server feature to expose the framework as an MCP server so
other tools can invoke it remotely.
[]
= true
= 3000
When enabled, other MCP clients can call tools like run_scenario and
get_page_state on port 3000.
Cost tracking & budgets
Every LLM call, agent invocation, and MCP tool call is tracked. After the run completes, a cost report is printed with per-test and per-endpoint breakdowns.
Per-test default budget
[]
= 1.0
= 100_000
= 50
= "hard"
Per-test override
[[]]
= "Expensive test"
= { = 2.0, = 200_000, = "soft" }
Global budget
[]
= 5.0
= 500_000
= "hard"
Enforcement
| Mode | Behavior |
|---|---|
hard |
Abort the test or run immediately when budget is exceeded |
soft |
Print a warning but continue executing remaining steps |
CLI budgets
Sample report output
═══════════════════════════════════════════════
COST REPORT
═══════════════════════════════════════════════
Test: "Homepage loads" — $0.0123 | 1,234 tokens | 4 calls
endpoint.default: 4 calls, 1,234 tokens, $0.0123
Test: "Dashboard smoke" — $0.0891 | 4,567 tokens | 6 calls
endpoint.vision: 2 calls, 3,000 tokens, $0.0450
endpoint.default: 3 calls, 1,567 tokens, $0.0441
endpoint.audit_bot: 1 call, 0 tokens, $0.0000
───────────────────────────────────────────────
GLOBAL SUMMARY
Total cost: $0.1014
Total tokens: 5,801
Total calls: 10
═══════════════════════════════════════════════
How it works
Four pieces:
-
Chrome — launched via the Chrome DevTools Protocol (
headless_chromecrate). It navigates, clicks, types, and extracts page content. -
LLM — any OpenAI-compatible API. Used in two places:
- Element targeting: when a step says
target = "the login button", the runner sends the page's interactive elements to the LLM and asks for a CSS selector. - Assertions: the runner sends page content to the LLM with a QA prompt
and expects
PASSorFAIL: <reason>.
- Element targeting: when a step says
-
A2A + MCP — connect to remote agents via the Agent-to-Agent Protocol and to MCP servers for tool-calling. Both are first-class step kinds.
-
TOML scenarios — declarative test files. No code, no CSS selectors required. Just describe what you want in English.
TOML file → CLI runner → Chrome (CDP) → LLM API
→ A2A agent
→ MCP server
Use as a library
[]
= { = "0.1", = ["macros", "mcp-server"] }
use ScenarioRunner;
use Scenario;
let scenario: Scenario = from_str?;
let runner = new;
let report = runner.run?;
println!;
// Access cost/usage data
let usage = runner.usage_tracker;
let global = usage.global_snapshot;
println!;
// Print the cost report
print_report;
Macros: #[browser_test] in cargo test
Enable the macros feature to write browser tests directly in your Rust test
modules:
[]
= { = "0.1", = ["macros"] }
use browser_test;
use browser_test_inline;
// Run a TOML scenario file
browser_test!;
// Inline small scenarios
browser_test_inline!;
Tests auto-skip when no LLM endpoint or Chrome is available — safe to include in
every CI run. They only execute with real PASS/FAIL when infrastructure is
present.
LLM authentication
The runner supports API keys and custom headers for SSO or alternative auth:
[]
= "sk-..."
= { = "acme", = "qa" }
Endpoints can also carry their own credentials:
[]
= "llm"
= "https://api.openai.com"
= "sk-prod-..."
= "gpt-4o"
Via CLI:
Via env:
License
Apache-2.0 OR MIT