aite plan (draft 1, 2026-10-09) #
aite = AI Integrated Test Environment. An AI writes small readable scripts that drive a real browser; when one fails, a human can tell in under a minute whether the app or the script is wrong.
Status: plan only, nothing built. Draft 1 turns draft 0 into a build plan: decisions with defaults, a design with concrete interfaces, phases with checklists and acceptance tests. Anything marked (assumed) is a default I chose; change it before the phase that depends on it starts.
Contents
- Principles
- What BITE gives us, what does not carry over
- Decisions (with defaults)
- Design
- Repo layout
- Phases and checklist
- How the AI writes and repairs scripts
- How aite itself is tested
- Security and accounts
- Risks
- Needs from Alex
- Out of scope
The plan is split across files. This one is the overview; the details live beside it. Where this file and docs/design/*.md disagree, the design file wins (and the task list in TASKS.md wins over both for scope and acceptance):
| File | What it holds |
|---|---|
docs/decisions.md |
D1 onward, each with status, the phase that needs it, and alternatives |
docs/design/script-format.md |
the grammar, every command and target, verify/wait forms, timing rules, error codes E001 to E007 (parser and runner), an example |
docs/design/protocol.md |
the runner and extension websocket protocol: handshake, messages, failure reasons, keep-alive, delivery rules, security |
docs/design/data-formats.md |
element descriptor and lock file, run.json, verdicts, repair requests, aite.config.json |
docs/design/locator-repair.md |
resolution order, scoring weights, outcomes (found, repaired, drifted, not-found, ambiguous), rules |
docs/journeys.md |
the mallbucks journeys the first scripts cover (send, gift, shop) with expected texts |
docs/TASKS.md |
the tasks (50 after the QA review) with acceptance criteria, test tiers, test cases, dependencies and labels; GitLab holds the live status |
docs/test-plan.md |
the QA test plan: risk tiers, cases with oracles, manual QA call-outs, gates, and the post-review task changes (section 6) |
docs/adversarial-review.md |
the independent review of this plan (20 findings, verified); where it conflicts with this file, the task list in TASKS.md and the changes in test-plan.md section 6 win |
1. Principles #
- A failure must be confirmable by a person fast. Every step carries its intent in words, so a failure reads as "wanted X, found Y", with a screenshot, not as a selector error.
- Real session, real browser. Scripts run in a signed-in browser profile, so flows that need a genuine session (Bluesky sign-in, the posting permission, passkey wallets, popups) are tested for real, by the AI, without waiting for a human tester.
- Scripts are plain text in git. Reviewable, diffable, written and repaired by the AI, readable by Qian.
- Small command vocabulary. If a step cannot be said in a few words, the script is too clever.
- Never fake a pass. A step that cannot find its element fails; waiting is explicit; no silent retries that hide flakiness (retries are counted and shown).
- Reuse ideas, not the stack. BITE's design is worth keeping; its code (Closure, App Engine, Manifest V2) is not.
2. What BITE gives us, what does not carry over #
Read from source-archive-bite.zip (unpacked outside the repo; nothing was run; the archive is left out of the published repository, see README). License: Apache 2.0 (COPYING).
Keep (as ideas, rewritten in TypeScript):
- A tiny command language:
click doubleClick drag enter type select change submit input find verify verifyNot wait sleep mouse move comparePosition changeUrl redirectTo run call validateBlock replaceHtml(tools/rpf/extension/src/libs/codegenerator.js). - Element descriptors (XPath plus the element's attributes) instead of a single selector, and a locator-repair step
(
locatorsupdater.js,getactioninfo.js,common/ElementDescriptor). - Record then edit:
recordmanager.js,eventsmanager.jsturn live clicks into commands, which a person then tidies. - Playback with waits and retries (
playbackmanager.js, 1,700 lines) and an action layer built on Selenium's browser atoms (bothelper.js,actionshelper.js).
Drop:
- The extension shell: Manifest V2 (Chrome no longer loads it), uses removed APIs (
chrome.extension.sendRequest, 17 uses ofexecuteScript, 5 ofeval; V3 forbidsevaland remote code). Rewrite for Manifest V3. - The server: Python 2.7 on Google App Engine. Replace with a small Node process.
- Closure Library, the compiler, GData and App Engine clients, the Ace editor: all of
deps/(89 MB). - Bug tracking, crawlers and the Google-specific parts of the server.
3. Decisions (with defaults) #
| # | Decision | Default (assumed) | Why / what would change it |
|---|---|---|---|
| D1 | Script format | A small line-based format for steps (section 4.1), plus optional TypeScript for custom checks | Easiest for people to read and for the AI to write and diff. Cost: a parser (about 300 lines). Alternative: TypeScript functions only. |
| D2 | Driver | MV3 Chrome extension, one runner interface so a Playwright backend can be added later | Alex's preference. An extension cannot answer an OS prompt (Touch ID) or run headless in CI; Playwright with a virtual passkey can. |
| D3 | Language | TypeScript, no UI framework, esbuild for the bundle | Small, typed, no Closure. |
| D4 | Where it runs | Headed Chrome with a persistent profile on balrog, started by a script; also runnable on a laptop |
Real session needs a real profile; headless comes later with Playwright. |
| D5 | Bridge | A local Node process, websocket on 127.0.0.1 only, a per-run token |
Lets Claude start a run and read results; never exposed to the network. |
| D6 | Results | JSON + screenshots on disk, rendered as one static HTML report; verdict buttons live in a small local page | Keeps v1 simple; a GitLab artifact can come later. |
| D7 | Repo | This repo (root/aite). BITE stays as an archive/reference; new code lives beside it |
|
| D8 | Large files | Decided 2026-10-09: leave the two zips in place; keep a short reference/ of files read |
84 MB sits in history already. |
4. Design #
Claude (AI) --writes--> scripts/*.aite (git)
| |
| start run, read result v
v +---------+ websocket (127.0.0.1) +----------------------+
bridge (Node) <---------> | runner | <------------------------> | MV3 extension |
| +---------+ | service worker |
| | content scripts |
v +----------+-----------+
results/<run>/ (json, png, report.html) | acts on
v
real signed-in Chrome profile
4.1 Script format (sketch, final syntax is a Phase 2 task) #
One file per flow, scripts/<area>/<name>.aite. Plain text; # starts a comment.
test "send 1 MALLBUCKS to a handle"
covers J-09 J-13
start https://mallbucks.malldao.xyz/
needs session test-qian
step "remember the starting balance"
capture before css:".balance"
step "find the recipient"
click text:"Send"
type placeholder:"Search" "alex"
click text~:"@distributed.camp"
step "review shows the right amount"
type label:"Amount" "1"
click role:button name:"Send"
verify text:"1 MALLBUCKS" within:"Review send"
step "balance goes down by one within 5 seconds"
verify css:".balance" number:changed-by from:before equals:-1 timeout:5s
- Targets are descriptors, written compactly:
text:,role:+name:,label:,placeholder:,testid:,css:,xpath:. The runner expands each to a full descriptor (section 4.2) when it first resolves it. - Commands (v1):
start goto click doubleClick type press select check uncheck verify verifyNot wait sleep capture set drag switchTab closeTab screenshot call run(the full table is indesign/script-format.md, which wins).callruns a TypeScript custom check;runincludes another script. - Waits are explicit (
wait, atimeout:modifier on a command, or atimeoutline in the file header). No hidden global sleeps. - Every step has a name (the intent). A script without step names does not parse.
4.2 Element descriptor #
When a target resolves, the runner stores a descriptor beside the script (scripts/<area>/<name>.aite.lock.json), so the next run can
find the element even if the text changed:
{ "text": "Send", "role": "button", "tag": "button", "attrs": { "aria-label": "...", "data-testid": null },
"xpath": "/html/body/.../button[2]", "css": "...", "near": { "heading": "Send money" }, "seen": "2026-10-09T13:00:00Z" } # sketch; design/data-formats.md has the full shape and wins
Repair order when the first locator fails: other attributes of the descriptor, then text similarity, then position near; if a candidate is
found with confidence below a threshold the step fails as "locator drifted" with the proposed element outlined, instead of passing.
4.3 Messages (runner <-> extension), v1 #
JSON over the websocket, request/response with an id:
{ "id": 7, "cmd": "resolve", "target": { "text": "Send" }, "timeoutMs": 5000 } -> { "id": 7, "ok": true, "descriptor": {...} }
{ "id": 8, "cmd": "act", "action": "click", "descriptor": {...} } -> { "id": 8, "ok": true }
{ "id": 9, "cmd": "verify", "kind": "text", "value": "1 MALLBUCKS", "within": "..." } -> { "id": 9, "ok": true, "found": "1 MALLBUCKS" }
Failures carry { "ok": false, "reason": <one of the reasons listed in design/protocol.md>, "detail": {...} } (the design file lists all twelve and wins).
4.4 Result schema #
results/<run-id>/run.json: the shape is defined in design/data-formats.md (which wins): tests contain steps, steps contain commands, each with its
status, reason, expected, found, timing, retries and screenshot path. The static report is generated from it.
4.5 The confirm loop (the human part) #
A failed step renders as:
x step "review shows the right amount" (test: send 1 MALLBUCKS to a handle, covers J-13)
wanted: verify text "1 MALLBUCKS" within "Review send"
found: "1 MUCK"
[screenshot with the "Review send" box outlined]
( App is broken ) ( Script is out of date ) ( Flaky, retry )
- App is broken files a GitLab issue in the right project with the screenshot, the step, and the run link.
- Script is out of date hands the failure to the AI, which proposes a script diff for the human to approve.
- Flaky reruns the whole test three times from a clean pre-state and records the pass rate (a step with side effects is never repeated alone).
5. Repo layout #
aite/
README.md docs/PLAN.md (this file)
NOTICE credit to Google's BITE (Apache 2.0) for derived ideas/code
LICENSE Apache 2.0 (matches BITE)
reference/ the few BITE files worth re-reading (copied, not built)
extension/ MV3 extension: manifest.json, src/{background,content,locator,actions}.ts
runner/ script parser, step engine, result writer (Node/TypeScript)
bridge/ local websocket server + CLI (`aite run scripts/...`)
report/ static report generator, confirm page
scripts/ the test scripts (mallbucks/, fixtures/)
fixtures/ tiny static pages used to test aite itself (including a deliberately broken one)
docs/
6. Phases and checklist #
Each phase ends with something running and a check a person can do. Do one phase at a time.
Phase 0: housekeeping (half a day) #
- Done when:
npm testpasses in CI on an empty skeleton, and a deliberately failing test turns the pipeline red. - The checklists in this file are summaries.
docs/TASKS.mdhas the full task list per phase (50 tasks, including the spikes, hardening and test tasks) and wins on scope and acceptance.
Phase 1: walking skeleton (1 to 2 days) #
- Done when:
aite run fixtures/hello.aiteclicks a button on the fixture page and reports a pass with a screenshot.
Phase 2: the script language (2 days) #
- Done when: the whole command set passes on fixtures, and a malformed script gives a one-line error pointing to its line.
Phase 3: element descriptors and repair (2 days) #
- Done when: a renamed button is found by repair and reported as "repaired (text changed)", not silently.
Phase 4: reporting and the confirm loop (2 days) #
- Done when: a person, given only the report, correctly says "app" or "script" for the broken and the drifted fixtures in under a minute each.
Phase 5: real session and test accounts (1 to 2 days, needs Alex) #
- Done when: a script runs signed in as the test account with no human present, and
session-checkcorrectly fails when the saved session is deliberately cleared.
Phase 6: first real milestone, mallbucks journeys (3 to 4 days) #
- Done when: all three pass on production, the negative run fails at the right step, and Qian confirms the report is clearer than a written test log.
Phase 7: authoring workflow and scheduling (2 days) #
- Done when: a new journey from
docs/qa-journey-test-plan.md(in the mallbucks repo) becomes a passing script with no hand-editing beyond approving it.
7. How the AI writes and repairs scripts #
- Author: given a journey (steps in words), write the script with a named step for each sentence; use
text:androle:targets first (they survive restyling),testid:where the app has one; run it; fix until green or report the blocker. - Ask the app for stable hooks: when a target is fragile, propose adding a
data-testidin the app repo rather than a long selector. - Repair: on a "script is out of date" verdict, read the failing step, the screenshot and the current page; change the smallest thing (a
text, a target); rerun that test only; show the diff for approval. Never weaken a
verifyto make it pass. - Never invent expected values. Expected text comes from the product spec or QA plan, not from whatever the page currently says.
8. How aite itself is tested #
- Unit tests: parser, step engine, descriptor matching, report generation (vitest).
- Fixture pages cover every command, plus
drift.htmlandbroken.html. - A "self-check" CI job that loads the extension in the pinned Chrome and runs the fixture scripts through
aite runitself (decisions.md D13: not Playwright, so the launch path under test is the real one), so a broken extension build is caught before it reachesbalrog. Each fixture script carries a# expect:line; the job runs each 20 times, and a second run with the extension broken on purpose must fail.
9. Security and accounts #
- The bridge listens on
127.0.0.1only, with a per-run token; the bridge closes any connection that lacks it. - Test accounts are dedicated throwaways with little value (the wallet is on Monad testnet). Their sessions live in the Chrome profile
on
balrog, never in git; the profile directory is mode 0700 and excluded from backups that leave the machine. - Scripts never contain secrets. A
needs session <name>line names a profile; the runner fails if it is missing. - Screenshots can show addresses and handles. Reports stay on the machine; sharing is an explicit step.
- The extension is loaded unpacked into one profile; it is not published.
10. Risks #
| Risk | Mitigation |
|---|---|
| MV3 service worker sleeps and drops the websocket | Reconnect with backoff; keep the websocket alive with a 15 s ping (a chrome.alarms heartbeat every 30 s is only the backup, see protocol.md); prove it in Phase 1 before building more |
| Locator drift makes the suite noisy | Repair loop in Phase 3, built early; drift is a distinct failure kind |
| OS-level prompts (Touch ID) cannot be answered by an extension | Use a test wallet created with a passphrase backup for the extension runner; passkey flows go to a Playwright backend later (D2) |
| The pay pop-up is a second window | Phase 6 covers switchTab; the extension already sees all tabs |
| Real accounts expire or get rate limited | session-check fails loudly; runs are serial and few |
| AI "fixes" a failing test by loosening it | Rule 7.3 and a CI check that flags removed verify lines in script diffs |
11. Needs from Alex #
- Approve or change D1 to D7 (the defaults above stand if you say nothing). D8 is decided; D9 and D10 are in
docs/decisions.md. - Create or name the Bluesky test accounts (Phase 5) and say where their sessions may live.
- Confirm
balrogcan run a headed Chrome (a display, or a virtual one such as Xvfb) for the runner.
12. Out of scope (for now) #
Bug tracking and crawling (BITE server features); a hosted multi-user dashboard; mobile browsers; load or performance testing; visual diffing of whole pages; headless CI for the signed-in flows (comes with the Playwright backend).