The local agent (vera-agent)
Run tests on your own machine against localhost with the vera-agent CLI: install, commands, configuration, environment targeting and troubleshooting.
Run your Vera tests on your own machine while the cloud stays the control
plane. vera-agent is a small runner you install locally: it leases queued runs
over an authenticated HTTPS API, drives Playwright against your app, uploads the
video/trace/screenshots, and reports results back. Vera keeps everything you
already use — the dashboard, projects, run history, AI generation and self-heal —
but the browser executes next to your app instead of in the cloud.
Why this exists:
- Test
http://localhostwith zero tunnels. The browser runs where your app runs, so private/loopback hosts are reachable directly — no ngrok, no exposing a preview URL. - Your machine, your setup. Setup
commandfixtures (seed scripts, fixture users) run on the agent path because it's your own machine; the same actions stay disabled on shared cloud workers. - No database credentials on the agent — ever. The agent never holds
DATABASE_URL. Every persistence touch (run rows, progress frames, artifact keys) is server-mediated over the token-authenticated/api/agent/v1API. The agent is org-trusted but platform-untrusted.
1. Quickstart
The package is published: npx vera-agent <command> works with no checkout, and
npm install -g vera-agent gives you the binary permanently. Every command below
is written vera-agent on purpose — the package also installs a short vera
alias, but npx vera fetches an unrelated package squatted on that name in 2022,
so the alias is safe only once vera-agent is installed, never in an npx
one-liner.
Mint an enrollment token. In the dashboard, open Settings → Agents and create an agent. The
vera_agent_…token is shown once — copy it now. (This is not the same as avera_…CI Token — see Configuration.)Enroll this machine:
npx vera-agent login --url https://app.vera.dev --token vera_agent_xxxThis validates the token against the server (a
/hellohandshake) and writes~/.vera/agent.jsonwith owner-only permissions (chmod 600).Start leasing runs:
npx vera-agent startThe first run auto-installs Playwright Chromium if it's missing. Leave it running; press
Ctrl+Cto drain and exit.Point an environment at the agent. On a project environment, set the run target to agent (see Environment targeting). Now hit Run in the dashboard: the run is queued, your agent claims it, and the steps stream live into the run view just like a cloud run.
Want a one-off check right now instead of a standing worker? vera-agent verify
runs a test — or a step list that was never saved — in this process against your
own localhost, no queue involved. Linking the repo itself (and the coding-agent
wiring around it) is vera-agent init.
2. Commands
Execution commands (start, run, watch, verify, status, doctor) read
config from ~/.vera/agent.json with environment overrides; init, check and
affected are the odd ones out and use .vera/config.json + ~/.vera/cli.json
(see Configuration). check --run --local is the one command
that needs both: the CLI token to read the map, the enrollment to execute.
| Command | What it does |
|---|---|
init --url <apiUrl> --token <vera_…> | Link this repo to a Vera project: writes .vera/config.json (commit it) + the token in ~/.vera/cli.json, registers the vera MCP server, and adds the agent-instructions block to AGENTS.md/CLAUDE.md. Idempotent. Flags: --project <id>, --mcp-scope local|project, --no-mcp, --no-agents-md. |
login --url <apiUrl> --token <vera_agent_…> | Enroll this machine. Validates against /hello, then writes ~/.vera/agent.json (chmod 600). Omit the flags in a terminal to be prompted interactively. |
start | Lease and execute runs continuously until Ctrl+C (drain). This is the normal mode. |
run --test <testId> | One-shot: lease and execute a single queued run for that test, then exit with a status code. For scripts/CI-style invocation. |
run --drain [--timeout <s>] | Lease-execute until the queue is empty or the timeout elapses, then exit by aggregate result. The CI-rider batch mode — see CI-rider. |
verify --test <testId> | Run a saved test here, now, in this process — no queue, no lease wait. Exit 0 pass / 1 fail / 2 no run. |
verify --project <id> --steps <file|-> | The same for an ad-hoc step list that was never saved. Also --env, --role, --label, --json. |
watch <dir> [--tag <t>|--suite <id>] | Watch a local dir and re-run tests on save. Defaults to the watch tag. Streams to the dashboard. Ctrl+C to stop. |
check [--base <ref>] [--run [--local]] [--strict] | Map your git diff onto the tests that cover it, and optionally run exactly that set. --run --local executes here, in this process, so it reaches localhost. Full guide: change impact. |
affected <file> | The reverse lookup: which tests cover this one file, and which rules it touches. |
status | Print enrollment, connectivity (a live /hello round-trip), and Playwright readiness. Never mutates anything. |
doctor | Diagnose Node / enrollment / Playwright / the native-mobile toolchain, each line with a fix-it. |
install-browsers | Install Playwright Chromium (npx playwright install chromium). |
Also available: --help / help, --version / version.
init
vera-agent init --url https://app.vera.dev --token vera_xxxxxPoints the repo you run it in at a Vera project and wires up the coding-agent
surface around it. It resolves the project from the repo's origin (GitHub
linkage first, then a name match; ambiguous ⇒ it asks for --project <id>, no
match ⇒ it links out to the dashboard, because a token cannot create projects),
then writes:
.vera/config.json—{apiUrl, projectId, repoPathPrefix}. Commit it; it deliberately holds no credential.~/.vera/cli.json— thevera_…token,chmod 600, keyed byapiUrl, so a second repo pointed at the same deployment needs only--url.
…and, unless told otherwise, registers the vera MCP server
(--mcp-scope local writes your private ~/.claude.json; --mcp-scope project
merges a ${VERA_TOKEN}-based entry into the repo's committed .mcp.json;
--no-mcp skips it) and adds a sentinel-guarded instructions block to the repo's
AGENTS.md or CLAUDE.md (--no-agents-md skips it). Re-running changes
nothing and says so.
The token is a CI Token (vera_…, Settings → CI Tokens) — not the
vera_agent_… enrollment token login takes. On cloud it needs the Pro
api-access feature; a free workspace's token works on MCP but init will
report the token as not accepted. Full walkthrough:
Getting started.
login
vera-agent login --url https://app.vera.dev --token vera_agent_xxx--url— your Vera API root (no trailing slash needed).--token— thevera_agent_…enrollment token from Settings → Agents.
If the agent is older than the server's minAgentVersion, login still saves your
credentials but warns loudly (enrollment is valid; the agent just needs an
upgrade). A bad token or unreachable URL fails without writing anything.
start
Leases work in a 25-second long-poll loop and executes each claimed run with a
heartbeat and live progress relay. On SIGINT/SIGTERM it drains: stops
leasing, finishes the in-flight run, closes browsers, then exits (a 60-second
hard deadline guarantees exit even if a run hangs).
run --test <testId>
Leases until the target test's queued job is claimed, executes it, and exits
0 on pass / 1 on fail. If it happens to claim a different test's job first it
executes that one too (leases aren't returnable), then keeps looking. Gives up
with a non-zero exit after ~60 seconds of no leasable work.
run --drain [--timeout <s>]
The batch shape for CI: leases and executes queued runs continuously, then
exits on its own — when the queue goes quiet (--idle-exits consecutive
no-work polls, default 2 ⇒ ~50s) or the --timeout <seconds> wall clock elapses
(0/absent = no cap; a run in flight always finishes first). Exit code is the
aggregate: 0 all-passed or nothing-queued, 1 any failed, 2 a fatal agent
error (auth/handshake). Prints a per-run summary line and a final aggregate line.
See the CI-rider guide for the GitHub Action and cost math.
verify --test <id> / verify --project <id> --steps <file|->
The developer-in-front-of-the-app shape: one run, in this process, against
your dev server, finishing in seconds. No queue, no lease, no waiting for a
worker to pick it up — which is what makes it usable in an edit → check → edit
loop, and what makes http://localhost:3000 testable from a cloud control plane.
# an ad-hoc check straight from stdin
echo '[{"action":"goto","url":"/orders"},
{"action":"assert","selector":"#total","assertType":"text","value":"42 orders"}]' \
| vera-agent verify --project prj_123 --steps - --role admin
# or replay something already saved
vera-agent verify --test tst_456 --json--stepstakes a JSON file, or-for stdin, holding either a bare array or{"steps": [...]}(the shape therun_scenarioMCP tool takes). Missing stepids are minted for you — they're run-local identifiers, so hand-writing nanoids is not your job.--stepsrequires--project(a loose step list has no test to inherit it from). Provide exactly one of--testor--steps.--env <name>/--role <roleId>pick the environment profile and the captured auth role;--label <text>names the run in the feed.--jsonprints one machine-readable object on stdout and moves every human line to stderr, so it pipes intojqor an agent.- Exit codes match
run --drain:0passed,1failed,2the run never happened (bad flags, no enrollment, no Chromium).
It is still a real run: the control plane creates the row, receives the step results and artifacts, and counts the invocation — so a local check and a cloud check share one history rather than splitting in two. What runs locally is the browser, not the bookkeeping.
verify needs the enrollment credential (~/.vera/agent.json, written by
login), not init's token. If you have only run init, it exits 2 with
Cannot verify — agent handshake failed.
watch <dir> [--tag <tag> | --suite <id>] [--debounce <ms>]
The inner-loop mode: watch a local source directory and, on each save, re-run a
set of tests against the app running on your machine — results stream to the
dashboard in real time (they are ordinary agent runs on the same run_events
SSE bus).
# Re-run every test tagged `watch` on save (the default):
vera-agent watch ./src
# Re-run a specific tag, or a suite, with a custom debounce:
vera-agent watch ./src --tag smoke
vera-agent watch . --suite suite_9f2 --debounce 800How it works on each debounced save-burst:
- Debounce — file-change events are coalesced over a quiet window
(
--debounce, default 500ms), so a save-all across many files fires one trigger, not dozens. - Trigger — the agent calls
POST /api/agent/v1/watch-triggerwith exactly one of{tag}/{suiteId}. The server resolves the matching tests and enqueues one run per test, pinned to this agent (payloadpinnedAgentId), returning{runIds, testCount}. Only this agent can claim those jobs. - Drain — the agent leases and executes its own pinned jobs through the normal loop, printing a compact per-run line, then returns to watching (the process does not exit). Bursts arriving mid-drain are coalesced into one follow-up pass.
Resolution scope (the agent carries no project context, so):
--tagmatches org-wide — every test in your org carrying that (normalized) tag, across all projects. Awatchtag is a deliberate opt-in, so spanning projects is intentional; tag the handful of tests you want your save-loop to run.--suitere-runs the suite's membership (statictestIds∪ its dynamic filters — tags / recently-failed / flaky), exactly like a normal suite run.
Each trigger enqueues at most 50 tests (the tail is dropped) so a broad tag
can't flood the queue. Ctrl+C stops the watcher, lets the in-flight run
finish, closes browsers, and exits (60-second hard deadline).
Ignored paths: node_modules, .git, dist, and any dotfile/dotdir are
never watched. Platform note: recursive watching uses native
fs.watch(..., { recursive: true }) on macOS/Windows; on Linux (where recursive
mode is unsupported) the agent falls back to per-directory watchers armed across
the tree and re-armed when new subdirectories appear.
check / affected
The diff-shaped question rather than the file-shaped one: which tests cover what
I just changed? Both read the repo link (.vera/config.json + the vera_… CLI
token), ask the server's impact map, and print the tests with the evidence that
selected them.
vera-agent check # read-only: map and report
vera-agent check --run --local # …and run exactly that set, HERE
vera-agent affected src/lib/pricing.tsThe local-agent-specific part is --run --local. It executes the impacted tests
in this process through the same path as verify — so they reach
http://localhost:3000 — which means it needs the enrollment credential on
top of the CLI token. Without one it exits 1 and tells you to login; it never
falls back to draining the queue, because an empty queue would aggregate to a
green that proves nothing. Plain --run (no --local) runs cloud-side instead,
regardless of the project's run target.
Everything else — the three joins, what the map cannot see, --strict, the exit
codes, the CI shape — is in change impact (vera check).
status
vera-agent 0.4.0
Enrollment:
config file: /Users/you/.vera/agent.json
api url: https://app.vera.dev
token: …x9f2
name: my-laptop
Connectivity:
reachable: yes (protocol v1)
version: 0.4.0 ≥ min 0.1.0 — OK
Playwright:
chromium: installed (/Users/you/Library/Caches/ms-playwright)doctor
Diagnoses the machine's readiness with a check table and actionable fix-its:
Node (≥ 20), enrollment + server reachability, Playwright browsers, and the
native-mobile toolchain — adb + attached devices, the emulator binary +
AVDs, appium + installed drivers (uiautomator2 / xcuitest), and on macOS
Xcode + available simulators. Each line reads OK / WARN / FAIL with a
one-line fix-it (brew install --cask android-platform-tools, npm i -g appium, appium driver install uiautomator2, …).
vera-agent 0.4.0 — doctor
[OK ] Node v20.x
[OK ] Enrollment https://app.vera.dev (token …x9f2)
[OK ] Server reachable — protocol v1, min agent 0.1.0
[WARN] Playwright Chromium not found
↳ vera-agent install-browsers
[OK ] Mobile caps android=yes, ios=no
[OK ] adb on PATH — 1 usable device(s) attached
[FAIL] Appium not on PATH
↳ npm i -g appium && appium driver install uiautomator2
Summary: 4 OK, 1 warnings, 1 failures.Exit code: 0 when all required checks pass. Mobile checks are
WARN-only on a desktop-only agent (a missing emulator never fails doctor);
they become FAIL (exit 1) only when this machine advertises that
capability — so a mobile-capable agent that can't actually run mobile (e.g.
Appium or the platform driver is missing) is caught in CI.
install-browsers
Wraps npx playwright install chromium. start/run also auto-prompt to
install on first use (and install unattended in a non-interactive shell), so you
rarely call this directly.
3. Configuration
Two credentials, three files
The most common setup mistake is assuming one token does everything. It does not:
| File | Written by | Holds | Used by |
|---|---|---|---|
~/.vera/agent.json | login | vera_agent_… enrollment token (Settings → Agents), chmod 600 | start, run, watch, verify, status, doctor |
~/.vera/cli.json | init | vera_… CI Token (Settings → CI Tokens), keyed by apiUrl, chmod 600 | init (and the MCP server it registers) |
.vera/config.json | init | {apiUrl, projectId, repoPathPrefix} — no credential, commit it | the repo, your coding agent, CI |
Neither token file is ever committed; .vera/config.json is meant to be. If you
plan to both link the repo and execute runs on this machine, run init and
login — they are separate steps writing separate credentials.
Environment overrides
Config lives in ~/.vera/agent.json (written by login, chmod 600).
Environment variables override the file per field:
| Env var | File key | Meaning |
|---|---|---|
VERA_API_URL | apiUrl | Vera API root. |
VERA_AGENT_TOKEN | token | Enrollment token (vera_agent_…). |
VERA_AGENT_NAME | name | Friendly name reported to the dashboard. |
QA_MAX_BROWSERS | (env only) | Browser-pool cap. Default 4. Never stored in the file. |
Precedence (highest first): environment variable → ~/.vera/agent.json.
Env values win per-field, so you can enroll once and override just the URL or
name for a given invocation. The token is a bearer credential — keep the file
600 and prefer VERA_AGENT_TOKEN (e.g. from a secret store) in CI.
The agent executes one leased run at a time regardless of QA_MAX_BROWSERS
(the cap governs parallelism within a run). Videos and traces are staged under
~/.vera/data before upload.
4. Environment targeting
Routing is per project environment (per plan §10 H4). A ProjectEnvironment
carries:
runTarget?: 'cloud' | 'agent'— where runs against this environment execute.- an optional
agentIdpin — restrict this environment to one specific enrolled agent (otherwise any of the org's online agents can claim it).
There's also a project-level default run target that an environment inherits
when it doesn't set its own. When an environment resolves to agent, the
tests-run path pre-inserts a queued run row plus an agent_jobs row and
returns; the run then waits for an agent to lease it. Cloud-targeted environments
keep the existing in-process / queue path unchanged. Either way the run records
who executed it in runs.executor ({kind:'agent', agentId, agentName} for
agent runs), and cloud and agent runs render identically in the run view.
This means one project can have, side by side, a staging environment that runs in the cloud and a local environment that runs on your machine — same tests, different execution target.
5. Native mobile runs on the agent
An agent can drive a native app (.apk/.ipa) on a local emulator, USB
device, or iOS simulator — the local-first counterpart of a device cloud.
Capability advertising. On every
start/runhandshake the agent detects and advertises{ mobileAndroid, mobileIos }(Android whenadbreports a device or theemulatorbinary is present; iOS on macOS with the simulator toolchain). The control plane routes a mobile job only to an agent that advertises the job's platform — a desktop-only agent never claims a mobile run.App download. When a mobile test's
config.mobile.appArtifactIdresolves to an uploaded app, the lease carries the app descriptor; the agent downloads the binary over the authenticated agent API and caches it under~/.vera/apps/<appId>.<ext>(re-download only when missing). The server validates the download key against the leased run's project — an agent can never fetch arbitrary org artifacts.Local Appium. On a mobile lease the agent ensures a reachable Appium 2 server: it probes
http://127.0.0.1:4723, reuses one you already have running, or spawnsappiumif it's on PATH (stopped when the loop drains). If no server is reachable andappiumisn't installed, the run fails with an actionable message — the agent never silently installs anything. Install it yourself:npm i -g appium appium driver install uiautomator2 # Android appium driver install xcuitest # iOS (macOS)Local-Appium only. Agent mobile runs target a local Appium; device clouds (BrowserStack / LambdaTest) are the server-side path and never run on an agent.
Verify first. Run
vera-agent doctorto confirm adb/emulator/Appium (and Xcode/simulators on macOS) before dispatching a mobile run.iOS specifics. iOS runs against simulators (free Apple ID; the first session builds WebDriverAgent — slow, one-time). The agent resolves the test's
deviceProfileto a simulator, boots it if needed, and pins the session to it. iOS requires your Mac or your device-cloud account — see the dedicated iOS native testing guide.
6. Lifecycle facts worth knowing
- Lease + heartbeat. A claimed run's lease lives 60 seconds without a heartbeat; the agent heartbeats every ~20s while executing. If your agent crashes or loses its network, the lease expires within a minute.
- Requeue once, then "agent lost". The scheduler sweeps expired leases: the
first expiry requeues the job (so another agent — or your restarted agent —
can pick up the same run row and finish it); a second expiry gives up and
fails the run with
agent lost. - Offline / queued timeout. If a run is queued and no agent claims it
within 10 minutes, the scheduler fails the run with:
"No agent picked up this run within 10 minutes — start
vera-agenton your machine." A requeued job gets a fresh 10-minute window from the requeue, not from when it was first created. (If you see this, your agent isn't running or isn't enrolled in the same org / pinned to that environment.) - Drain is graceful.
Ctrl+Cfinishes the current run before exiting, so you don't strand a half-done run inrunning.
7. Limitations in this phase
- Visual regression and accessibility findings are not reported from agent
runs.
screenshotsteps still capture and upload, but there's no baseline diff, and a11y findings are dropped. Run those on the cloud target for now. - Cross-project chains are cloud-only. Chains never execute on an agent in this phase.
- Mobile is local-Appium only. Agent mobile runs drive a local Appium server; device clouds are the server-side path.
8. Troubleshooting
| Symptom | Cause & fix |
|---|---|
401 Unauthorized on every request | The token is invalid or was revoked in Settings → Agents. Mint a new one and vera-agent login again. |
| Agent refuses to run: "the server requires at least X" | Your agent version is below the server's minAgentVersion. Upgrade it: npm install -g vera-agent@latest (or just use npx vera-agent@latest). |
init: "Vera token not accepted…" | Three indistinguishable causes, by design: the vera_… token is revoked, it has expired, or the workspace's plan no longer includes api-access (Pro), which /api/v1 requires. Check the plan as well as the token — a free cloud workspace's token is valid for MCP but not for init. |
verify: "Cannot verify — agent handshake failed" | verify runs on the enrollment credential, not init's. Run vera-agent login --url … --token vera_agent_… (Settings → Agents) on this machine first. |
check --run --local: "needs an enrolled agent on this machine" | Same cause, checked up front: check reads the map with the vera_… CLI token but executes with the vera_agent_… enrollment. Run login, or drop --local to run cloud-side. |
check --run --local: "enrolled with X, but the impact map came from Y" | This machine is enrolled against a different deployment, so the impacted test ids do not exist there. Re-enrol against the URL in .vera/config.json. |
| "Playwright Chromium is not installed" | Run vera-agent install-browsers (or let start auto-install). vera-agent status shows the detected browser cache path. |
| Run stuck queued, then fails after 10 min | No agent claimed it. Check vera-agent status shows reachable: yes, that the agent is enrolled in the same org, and — if the environment pins an agentId — that this agent is the pinned one. The dashboard's Agents panel shows an online LED per agent. |
Could not reach … / timeouts | Network/URL problem. Verify --url (or VERA_API_URL) and that the server is reachable from this machine. |
| Agent shows Offline in the dashboard shortly after stopping | Expected — presence is a 60s heartbeat; it dims once the agent stops leasing/heartbeating. |
| A mobile run stays queued and never claims | This agent isn't advertising that platform. Run vera-agent doctor — Android needs adb + a device or the emulator binary; iOS needs macOS + simulators. Restart the agent so the fresh capabilities are advertised on /hello. |
| Mobile run fails: "No Appium server is running …" | Install and start Appium: npm i -g appium && appium driver install uiautomator2 (or xcuitest). The agent probes/spawns 127.0.0.1:4723; vera-agent doctor shows what's missing. |
See also
- Getting started —
vera-agent init: linking a repo to a project, the three config files, and the verify → keep → triage loop. - Change impact (
vera check) — the three joins behindcheck/affected, what they cannot see, and the--run --localcontract. - MCP server — the tool surface
initregisters, and the manual setup for clients other than Claude Code. - CI-rider — run queued Vera tests on your own CI runners via
vera-agent run --drain+ a composite GitHub Action (scheduled iOS/web regression, with cost math). - CI integration — token-driven runs from CI (the cloud path).
- iOS native testing — the honest iOS story: simulators on a Mac (free Apple ID + WebDriverAgent), real devices, and the device-cloud/CI alternatives.