Skip to main content
Glama
README.md
# Vedha

[![npm](https://img.shields.io/npm/v/@vedhajs/mcp?label=%40vedhajs%2Fmcp)](https://www.npmjs.com/package/@vedhajs/mcp) [![CI](https://github.com/HMAKT99/VEDHA/actions/workflows/ci.yml/badge.svg)](https://github.com/HMAKT99/VEDHA/actions/workflows/ci.yml) [![License: Apache-2.0](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE)

### Is Playwright alone enough for agentic testing?

![Same order flow, same "Order placed" check: Playwright passes, Vedha fails the step because POST /api/orders returned 500, and the server stored 0 orders](examples/demo/demo.gif)

<sub>[Run this demo](examples/demo/) in about 3 seconds: `npx tsx examples/demo/run.ts`</sub>

**Not when your agent trusts the screen.** Your agent clicks "Save", the page says "Saved", and it reports success. But the server returned an error, or stored nothing. **Vedha catches that.** It runs on Playwright and checks that each action really happened before it reports a pass. It is built for AI agents driving a browser and for people writing tests.

|                           |                                                                                                    |
| ------------------------- | -------------------------------------------------------------------------------------------------- |
| **~3x fewer missed bugs** | when an AI agent tests: 9 seeded defects missed vs 26 for Playwright MCP (held-out sets 2-4)       |
| **60 vs 0**               | broken features caught with everyday text/URL checks (140 seeded defects, scripted)                |
| **30% fewer tokens**      | for the agent on healthy tasks (set 4; 12-46% across sets)                                         |
| **0 false passes**        | in a 1,120-trial audit, with every seeded defect caught (210/210)                                  |
| **Within 5%**             | of hand-written Playwright's speed on healthy flows (median 1.05x): the checks cost almost nothing |

```bash
npx -y @vedhajs/mcp --allow-domain localhost     # MCP server for your agent
npm install -D @vedhajs/cli                      # or the CLI / SDK for your tests
npm install -D @vedhajs/playwright-test          # guard an existing Playwright suite: change one import
```

### Head to head: AI agent testing a web app

Same model, same tasks, written before any run on them. Playwright MCP reports success whenever the page _looks_ right; Vedha also checks what the server did.

| Held-out set 4 (pilot 8)                | Playwright MCP | **Vedha MCP**      |
| --------------------------------------- | -------------- | ------------------ |
| Broken features caught (seeded defects) | 10 / 18        | **16 / 18**        |
| Healthy tasks completed                 | 18 / 18        | 17 / 18            |
| Median tokens, healthy tasks            | 76,018         | **52,990 (-30%)**  |
| Median tokens, defect tasks             | 210,000        | **139,029 (-34%)** |
| Median wall time, healthy tasks         | 169 s          | 176 s (+4%)        |

The lead held on every held-out set: **10 vs 4 of 14** (set 2), **15 vs 10 of 18** (set 3) and **16 vs 10 of 18** (set 4), or 41 vs 24 caught overall.

<sub>Fine print: LLM results are from one free model (`minimax/minimax-m3.1-flash`) at 3 trials per cell. When an expert writes every check by hand, Vedha and Playwright tie; Vedha's lead is with typical checks and with AI agents. All numbers come from raw data in `benchmarks/results/`, and the known misses are listed under [Measured results](#measured-results).</sub>

## Why use Vedha instead of Playwright alone

|                                                                       | Playwright (expert use)                                                   | Vedha                                                                                           |
| --------------------------------------------------------------------- | ------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
| Server rejected the write, but the page still says "Saved"            | passes unless you wrote a network assertion                               | **fails the step**: page-health and write checks run on every mutating step                     |
| Same test with typical (text/URL-only) checks, 140 seeded defects     | **0 / 140** caught                                                        | **60 / 140** caught (every rejected-write defect)                                               |
| REST write silently dropped (200, nothing stored)                     | 0 / 10                                                                    | **10 / 10** via automatic read-back                                                             |
| MCP origin allowlist under redirects and WebSockets (5 hostile cases) | redirect and WebSocket escapes 3 / 3 each                                 | **0 / 15** escapes                                                                              |
| Outcome unknown after a click (timeout, flaky network)                | nothing tracks it; re-running the step or test can submit the order twice | **never re-issues**: reconciles with a read-only probe or reports `outcome_unknown`             |
| Result after a fallback was needed                                    | no per-step fallback; a test that passes on retry is marked `flaky`       | `passed_with_recovery`, with the original failure kept                                          |
| Output for an agent                                                   | full accessibility snapshot after each action (Playwright MCP)            | bounded observations and deltas (**12-46% fewer tokens** on healthy held-out tasks, pilots 6-8) |
| Speed on healthy flows                                                | baseline                                                                  | median **1.05x** expert Playwright (6 scenarios, r11)                                           |
| Everything declared by an expert                                      | ties: 200/200 healthy, 138-140/140 defects                                | ties                                                                                            |

Vedha does not replace Playwright, it builds on it. It reuses Playwright's auto-waiting and actionability, and can wrap an existing `page` in a Playwright test (see [Migrating a Playwright test](#migrating-a-playwright-test)).

## Core ideas

- **Verified semantic actions.** An action is `verified` only when framework-run, deterministic postconditions pass (URL, element state/value/text, network response and JSON fields, read-only API probes). No postcondition ⇒ `unverified`, never silently "passed".
- **Bounded recovery that keeps the evidence.** Alternate locators, known-interstitial dismissal and a single safe retry are tried in a fixed ladder; every attempt is a `RecoveryRecord` containing the _original_ failure. A run that needed recovery reports `passed_with_recovery`.
- **Uncertainty is a first-class result.** For `mutating` actions with an unobserved outcome the framework does not re-issue the action: it reconciles via a read-only probe, or reports `outcome_unknown`.
- **Bounded observations.** `controls` (interactive elements only), `outline` (ARIA tree, refs from Playwright), region/detail on demand, deltas between observations. Page-derived text is flagged `untrusted`.
- **Explore → promote → replay.** An actor (scripted, rule-based, or your LLM adapter) explores through the same verified runner; the result becomes a reviewable `TestProposal` and a plain `@playwright/test` spec, which can be replay-checked.
- **Failure bundles.** Goal, steps, verdicts, recovery records, network summary, redacted trace; `replay` re-drives the steps and reports divergences (and states what cannot be replayed).
- **Agents can make mistakes without breaking the run.** Failures come back with a hint (`fill` on a dropdown: "use select"); the explore loop continues, but a repeat guard refuses to run the same mutating action again while its earlier outcome is unresolved, and an independent check (`confirms`) can clear an unknown outcome.
- **Server errors cannot hide behind an optimistic page.** A page-health check fails a mutating step if a request returned 5xx, a write was rejected, or the page threw, even when the declared checks passed.
- **Real-app reach.** Beyond click/fill/select/check/press: hover, double click, uncheck, upload (fail-closed `uploadRoots`), scroll, drag, wait, back, reload, tab switching, iframe targets, dialog policy.
- **Usable from any MCP client.** `@vedhajs/mcp` exposes the same engine as a stdio MCP server.
- **No LLM required.** The core has no model dependency; LLM actors implement a small `Actor` interface outside the core.

It is **not** a Playwright wrapper that forwards commands: it uses Playwright as a replaceable browser substrate (`BrowserAdapter`) and owns the action/verification/recovery/promotion/bundle layers. It does not replace Playwright Test, and it reuses Playwright's actionability and auto-waiting (the strongest part of Playwright, per `research/playwright-execution-model.md`).

> **Status: v0.1 research MVP.** Every number here comes from raw `trials.jsonl` files in `benchmarks/results/`, and each held-out set was written before any run on it. Vedha **ties** expert Playwright when the author declares every check. It **leads** when checks are typical or missing, and when an LLM agent does the testing. The LLM results come from one free model (`minimax/minimax-m3.1-flash`) at 3 trials per cell. Known misses are listed under [Measured results](#measured-results).

## Quick start

Install from npm (Node 20 or later):

```bash
npm install -D @vedhajs/cli          # the `vedha` command
npx playwright install chromium      # browser for Playwright, if you don't have it yet
npx vedha doctor                     # checks Node and the Playwright browser
```

| Package                                                                                    | What it is                                                                |
| ------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------- |
| [`@vedhajs/cli`](https://www.npmjs.com/package/@vedhajs/cli)                               | `vedha run / explore / promote / replay / doctor`                         |
| [`@vedhajs/mcp`](https://www.npmjs.com/package/@vedhajs/mcp)                               | MCP server (`vedha-mcp`) for LLM agents                                   |
| [`@vedhajs/adapter-playwright`](https://www.npmjs.com/package/@vedhajs/adapter-playwright) | SDK: run checked steps on a Playwright page                               |
| [`@vedhajs/playwright-test`](https://www.npmjs.com/package/@vedhajs/playwright-test)       | One-line guard for existing `@playwright/test` suites, plus checked steps |
| [`@vedhajs/core`](https://www.npmjs.com/package/@vedhajs/core)                             | Engine (installed with the others)                                        |

Agent skill (Claude Code, Codex, Cursor and other agents that read `SKILL.md`): `npx skills add HMAKT99/VEDHA` installs [`vedha-verified-browser-testing`](skills/vedha-verified-browser-testing/SKILL.md), which teaches the agent to declare checks, mark writes and never repeat an unknown outcome.

From source instead: `nvm use 22 && pnpm install && npx playwright install chromium && pnpm build`, then use `node packages/cli/dist/bin.js` wherever this README says `npx vedha`.

### Deterministic spec (no LLM)

`order.json`:

```json
{
  "goal": "place an order",
  "steps": [
    {
      "id": "open",
      "intent": "open the form",
      "op": "goto",
      "value": "/",
      "postconditions": [{ "type": "state", "target": { "label": "Name" }, "state": "visible" }]
    },
    {
      "id": "name",
      "intent": "enter name",
      "op": "fill",
      "target": { "label": "Name" },
      "value": "Ada",
      "postconditions": [{ "type": "value", "target": { "label": "Name" }, "equals": "Ada" }]
    },
    {
      "id": "submit",
      "intent": "place the order",
      "op": "click",
      "target": { "role": "button", "name": "Place order" },
      "altTargets": [{ "testId": "place-order" }],
      "dismiss": [{ "role": "button", "name": "Accept" }],
      "mutating": true,
      "postconditions": [
        { "type": "text", "target": { "role": "status" }, "contains": "Order placed" },
        {
          "type": "network",
          "method": "POST",
          "urlPattern": "/api/orders",
          "status": "2xx",
          "json": { "path": "qty", "equals": 3 }
        }
      ],
      "reconcile": { "type": "api", "get": "/api/orders", "matchesAny": { "name": "Ada" } }
    }
  ]
}
```

```bash
npx vedha run order.json --base-url http://localhost:3000 --out ./failure-bundle
# exit 0 passed | 1 failed | 2 blocked | 4 outcome_unknown | 5 unverified ; JSON report on stdout
npx vedha replay ./failure-bundle --base-url http://localhost:3000
```

### SDK

`npm install -D @vedhajs/adapter-playwright playwright`

```ts
import { openSession } from "@vedhajs/adapter-playwright";

const session = await openSession({ allowedDomains: ["localhost"] });
const report = await session.run(steps); // ActionSpec[]
console.log(
  report.verdict,
  report.steps.map((s) => [s.actionId, s.status, s.recovered]),
);
await session.close();
```

Wrap an existing Playwright page: `sessionFromPage(page, policy)`.

### Agent-driven exploration → regression test

```bash
npx vedha explore goal.json --start-url http://localhost:3000/ \
  --promote order.spec.ts --check http://localhost:3000   # generates the spec and replay-checks it
```

The shipped `RuleActor` is a heuristic form-filler used for tests and benchmarks. To use an LLM, implement `Actor.decide({goal, observation, history, step})` and return an `ActionSpec` or `done`; the framework enforces policy, verification and budgets regardless of what the actor proposes. No provider is bundled and nothing is sent anywhere by default.

### Use it from an MCP client

Claude Code:

```bash
claude mcp add vedha -- npx -y @vedhajs/mcp --allow-domain localhost --out ./vedha-out
```

Any client that takes an `mcpServers` config (Claude Desktop, Cursor, VS Code and others):

```json
{
  "mcpServers": {
    "vedha": {
      "command": "npx",
      "args": ["-y", "@vedhajs/mcp", "--allow-domain", "localhost", "--out", "./vedha-out"]
    }
  }
}
```

Run `npx playwright install chromium` once first if Playwright's browser is not installed. `npx -y @vedhajs/mcp --help` lists the options (browser, headed, uploads, secrets to redact, step cap).

Tools: `vedha_act` (one action with the checks you declare in `expect`), `vedha_observe`, `vedha_probe` (GET a same-origin JSON path without moving the page), `vedha_report`, `vedha_promote` (verified steps to a Playwright spec), `vedha_bundle`, `vedha_close`. The server refuses to start without `--allow-domain HOST` or an explicit `--allow-any-domain`. Mark data-changing steps `mutating: true`; after an unknown outcome Vedha refuses to repeat them until a check confirms the effect (`confirms: "<step id>"`).

### Migrating a Playwright test

**One line, no rewrite.** Install `@vedhajs/playwright-test` and change the import in your existing tests:

```ts
// before
import { test, expect } from "@playwright/test";
// after
import { test, expect } from "@vedhajs/playwright-test";
```

![The same Playwright test with two imports: @playwright/test passes, @vedhajs/playwright-test fails with "POST /api/orders returned 500"](examples/guard-demo/demo.gif)

<sub>[Run this demo](examples/guard-demo/): `npx tsx examples/guard-demo/run.ts`</sub>

Your tests run exactly as before, with guard mode watching each one. A test whose own checks pass still **fails** if the app answered a request with a 5xx, or a write request (POST/PUT/PATCH/DELETE) failed outright: the page said "Saved", the server didn't. A 4xx answer to a write and an uncaught page error are added to the report as warnings, because validation tests and third-party scripts produce them on purpose. Requests to other sites (analytics, CDNs) are not watched. The findings are attached to the test as `vedha-guard.json`.

```ts
test.use({ vedhaGuard: "strict" }); // 4xx writes and page errors fail too
test.use({ vedhaGuard: { ignore: ["/api/flaky", /\/v2\//], hosts: ["api.other-domain.com"] } });
test.use({ vedhaGuard: false }); // off for these tests (for example a test that expects a 500)
```

**Checked steps.** For steps that must hold, Vedha can also run the action itself and verify it:

```ts
// before
await page.getByRole("button", { name: "Save" }).click();
await expect(page.getByRole("status")).toHaveText("Saved");
```

```ts
// after (wraps your existing page; the rest of the test is unchanged)
const af = await sessionFromPage(page);
const r = await af.step({
  id: "save",
  intent: "save the form",
  op: "click",
  target: { role: "button", name: "Save" },
  altTargets: [{ testId: "save" }],
  postconditions: [{ type: "text", target: { role: "status" }, equals: "Saved" }],
});
expect(r.status).toBe("verified"); // r.recoveries explains any recovery that was needed
```

More in `design/migration-and-compatibility.md`.

## Measured results

Everything below is from raw `trials.jsonl` files under `benchmarks/results/`; protocols, limits and amendments are in `benchmarks/preregistration.md` and `benchmarks/methodology.md`. Scripted tracks use no model. "Expert" baselines (`pw-strong`, `sel-strong`) were given every technique the scenarios' declared checks allow, including state probes.

| Track                                                                                | What was run                                                                           | Result                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| ------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Held-out widget families, full declarations                                          | 17 scenarios x 7 drivers x 20 trials (`heldout-n20-final`)                             | Vedha, expert Playwright and expert Selenium tie: 200/200 healthy, 138-140/140 defects, 0 false passes. Weaker baselines detect 0/140.                                                                                                                                                                                                                                                                                                                                                                                               |
| Held-out widget families, minimal declarations (text/url only)                       | same, 20 trials (`heldout-min-n20-final`)                                              | **Vedha detects 60/140** (every rejected-write defect); every Playwright and Selenium baseline 0/140; healthy 200/200 for Vedha and the expert baselines; false-pass 19-28% vs 36-58%. All frameworks miss silent drops (HTTP 200, nothing recorded) on these fixtures.                                                                                                                                                                                                                                                              |
| Cross-implementation: 16 third-party TodoMVC implementations                         | 4 user-level scenarios, 3 trials (`multi-n3-final`); re-audit at 5 trials (`r9-multi`) | Vedha 189/192 (96-99%); expert Playwright 182, expert Selenium 183, `pw-test` 183 (all 91-98%): best point estimate, **intervals overlap**. Expert Playwright 6 false fails (its default `toHaveText` also reads CSS-hidden text). The r9 re-audit found a rare Vedha false pass on M2 complete-and-filter (9 of 320 at 20 trials per framework): a check passed on a list that the app then re-rendered. Text and value checks now confirm a match with a second read, and r11 has **0 of 320** on M2 and 0 of 320 on the full set. |
| Independent app with mutants (TodoMVC)                                               | 10 scenarios, n=5, Vedha vs expert Playwright                                          | both 50/50 after idle-aware classification (before it Vedha reported "uncertain" on client-only defects). n=20 and the other drivers still to run.                                                                                                                                                                                                                                                                                                                                                                                   |
| Conventional-REST silent drop (S16, added after the feature: a positive control)     | 10 trials                                                                              | Vedha 10/10 detected via read-back inference; `pw-trust`, `pw-test`, `pw-strong` 0/10 with the same typical declarations. On the pre-registered widget silent drops (non-conventional endpoints) read-back caught **0/20**; that planned target was not met.                                                                                                                                                                                                                                                                         |
| Generated regression test (B8)                                                       | explore -> promote, run on 3 healthy and 4 broken variants                             | promoted test: 9/9 healthy, 12/12 defects caught; recorder-style references 9/9 and 3/12 (constructed references, one app).                                                                                                                                                                                                                                                                                                                                                                                                          |
| Security red-team over MCP (B7)                                                      | 5 hostile cases, 3 trials, each MCP server configured as documented                    | Playwright MCP's origin allowlist let a GET redirect, a POST redirect and a WebSocket through 3/3 each; Vedha 0/15; both blocked subresources and redacted a secret. Consistent with Playwright MCP's own docs that origin filtering is not a security boundary.                                                                                                                                                                                                                                                                     |
| Scripted re-audit after every change (r11)                                           | all 8 scripted sets, 5 trials (M2 at 20 per framework), Vedha                          | 1,120 trials: **0 false passes, 0 false fails**. Every seeded defect detected (210/210); 25 honest fails, all on the dijon TodoMVC implementation.                                                                                                                                                                                                                                                                                                                                                                                   |
| Overhead (B5)                                                                        | 6 healthy scenarios, 12 trials, concurrency 1 (`b5-overhead-r11`)                      | Median **1.05x** expert Playwright (S01 1.04, S02 1.00, S03 1.05, S11 1.29, S12 1.05, S15 1.08). The <=15% target is met as a median; S11 (search, paginate, open) is above it.                                                                                                                                                                                                                                                                                                                                                      |
| Time to verdict on a server error (S17)                                              | POST /api/orders 500, page shows the error honestly; n=10                              | Vedha 1.3 s median (was 9.4 s before fail-fast waiting); expert Playwright 0.28 s (its check awaits the response directly); expert Selenium 13.2 s. Vedha is **slower than expert Playwright** here.                                                                                                                                                                                                                                                                                                                                 |
| LLM, MCP vs MCP, held-out widget sets 2-4 (pilots 6-8, one free model, n=3 per cell) | Playwright MCP vs Vedha MCP, each set written before any LLM run on it                 | Defects caught: set 2 **10 vs 4 /14**, set 3 **15 vs 10 /18**, set 4 **16 vs 10 /18**. Healthy tasks completed: 17 vs 17, 16 vs 17, 17 vs 18 (Vedha first). Tokens on healthy tasks -46%, -12%, -30%. Vedha makes more tool calls (set 4 defects: 43 vs 23), and latency was not better (+4% on set 4). Remaining misses: W20 search-filter twin (3) and W26 carousel twin (2). Earlier pilots 4-5 (older builds) favoured Playwright MCP on completion; see each `llm-mcp-pilot*/README.md`.                                        |

What this does **not** show: that Vedha beats expert Playwright or expert Selenium when the author declares everything (it ties); anything about models beyond one free model family; anything about real production apps; Chromium-only for Selenium. Several results are mechanism-specific by design (read-back needs a conventional REST convention; the page-health check needs a request that returned an error).

Reproduce a scripted track: `pnpm bench -- --set held-out --trials 20 --concurrency 3 --run-id my-run` (sets: `seen`, `held-out`, `held-out-minimal`, `independent`, `multi`; the last two need `VEDHA_TODOMVC_DIR` pointing at a clone of tastejs/todomvc checked out at `ff43b02`; Selenium drivers are opt-in via `--drivers sel-trust,sel-strong`). Merge valid parts of several runs with `benchmarks/analysis/merge.ts`.

## Repository map

| Path                          | What                                                                                          |
| ----------------------------- | --------------------------------------------------------------------------------------------- |
| `research/`                   | Reverse-engineering of Playwright with REA, ecosystem study, evidence ledger, open questions  |
| `design/`                     | Gap matrix-driven architecture proposals, selected architecture, ADRs                         |
| `packages/core`               | Types/schemas, runner, verifiers, observation, policy, bundle, promotion, explorer            |
| `packages/adapter-playwright` | Playwright `BrowserAdapter` (+ policy via routing incl. redirect hops, generated-spec runner) |
| `packages/cli`                | `vedha` CLI                                                                                   |
| `benchmarks/`                 | Fixture app, scenarios, drivers, harness, analysis, methodology, raw results                  |
| `IMPLEMENTATION_STATUS.md`    | Phase status, tests run, blockers, unverified assumptions                                     |

## Security

Page and repository content is treated as untrusted data; see [SECURITY.md](SECURITY.md) for the threat model and known limits (notably: Playwright route handlers do not see redirect hops - Vedha validates them itself; WebSockets are gated and service workers blocked in launched sessions; Chromium only was tested).

## Contributing / License

See [CONTRIBUTING.md](CONTRIBUTING.md). Licensed under Apache-2.0 (see `LICENSE`).