# How it works

> For the complete documentation index, see [llms.txt](/llms.txt)

unotest connects your editor's AI agent to your real application through an
**MCP server**, and turns what the agent does into a reviewable test.

## The loop

1. **You describe a flow** in plain English to your agent.
2. **The agent explores** your live app through ~37 MCP tools — reading a
   semantic snapshot, clicking, filling, recording each action.
3. **It writes a scenario** to `unotest/e2e/<name>.js` with stable selectors and
   `step("intent", …)` labels.
4. **It runs the scenario** through the sandboxed engine and, on failure, pauses
   to inspect, patch and resume.
5. **You review and commit** the `.js`.

## Semantic perception, not pixels

The agent doesn't look at screenshots. It reads a **semantic snapshot** of the
page (web) or the **accessibility tree** (iOS) — roles, names, labels, test IDs,
rendered as a token-cheap text outline. This is cheaper, more reliable, and
stable across visual redesigns.

## A sandboxed engine

Scenarios are plain JavaScript, but they don't run in Node. They execute in a
**sandboxed AST interpreter** — no `require`, no `fetch`, no filesystem, no
network except the typed `apiCall` helper. That's why AI-generated tests are
safe to run blindly.

## Stable by construction

Selectors follow a strict priority — `getByTestId → getByRole → getByLabel →
getByText → locator(css)` — and the linter flags brittle patterns. Tests survive
refactors because they target meaning, not markup.

## Local-first

The MCP server, the browser/Simulator, the runner and the viewer all run on your
machine. Your app never leaves it. No cloud, no account.

## The ecosystem

| Package | Role |
| --- | --- |
| `@unotest/web` | CLI · MCP server · runner (web) |
| `@unotest/mobile` | CLI · MCP server · runner (iOS) |
| `@unotest/viewer` | local results browser |
| `@unotest/dsl` | scenario parser + validator |
| `@unotest/protocol` | shared types |
