Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude/skills/verify-debatelab/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ $H e2e cache ls # recorded agent flo

- `e2e` runs `doctor` first and refuses a broken stack, like `drive`. It turns e2e telemetry off and prints `EVIDENCE <dir>`, which holds `summary.md`, `report.json`, and per-attempt screenshots, traces, and failure videos.
- The suite is tool journeys (tag `journey`) and a page sweep (tag `pages`) of every student and public page in English, Spanish and Hungarian (not `/app/admin` or `/share/:code`). Every test fails on an uncaught error, `console.error` (React warnings included), a Supabase 5xx, horizontal overflow, or a request to a hosted project: `agentic/support/capture-errors.js` records them in the page. A failure that names `errors the page logged` is a real app error to fix, not test noise.
- The testing agent has its own OpenRouter models: `openai/gpt-6-luna` acts (test steps on a cache miss, and explorations) and `meta/muse-spark-1.3-contributor` judges. `E2E_AGENT_MODEL` and `E2E_JUDGE_MODEL` override them; the model is not part of the replay key. The key comes from `OPENROUTER_API_KEY`, `LIVE_OPENROUTER_API_KEY`, or the live keys file, and is needed in fake mode too. A recorded `agent.act` replays with no model call; every judgment and every exploration step is a call. Without a key, agent tests skip. An OpenRouter error such as `[Meta] The requested model was not found` is infrastructure: rerun with `--last-failed`. The Muse Spark judge cannot act.
- The testing agent has its own OpenRouter models: `inclusionai/ling-3.0-flash-vl` acts (test steps on a cache miss, and explorations) and `meta/muse-spark-1.3-contributor` judges. `E2E_AGENT_MODEL` and `E2E_JUDGE_MODEL` override them; the model is not part of the replay key. The key comes from `OPENROUTER_API_KEY`, `LIVE_OPENROUTER_API_KEY`, or the live keys file, and is needed in fake mode too. A recorded `agent.act` replays with no model call; every judgment and every exploration step is a call. Without a key, agent tests skip. An OpenRouter error such as `[Meta] The requested model was not found` is infrastructure: rerun with `--last-failed`. The Muse Spark judge cannot act.
- `--session student` starts an exploration signed in as a fresh demo user, minted by `agentic/sessions.e2e.ts`. `--agent` picks a persona: `first-timer`, `coach`, `edge-input`, or `hungarian`. `--target mobile` explores the phone-sized viewport.
- Explorations report `issue` (functional) and `warning` (cosmetic) findings, and exit 1 when there is an issue. Treat a finding as a lead: the stack serves Vite dev, so a route that is still compiling can look dead, and fake mode answers every motion with free-transport text. Prove a finding with a repro test tagged `repro` before you report it as a bug.
- Several explorations can run at once (a bug bash), because each gets its own evidence directory. They share this one stack, so give each a different area.
Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/verify-debatelab/bin/verify.sh
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ cmd_e2e() {
[ -n "$key" ] && export OPENROUTER_API_KEY="$key"
fi
if [ -n "${OPENROUTER_API_KEY:-}" ]; then
say "info testing agent: OpenRouter actor ${E2E_AGENT_MODEL:-openai/gpt-6-luna}, judge ${E2E_JUDGE_MODEL:-meta/muse-spark-1.3-contributor} (replayed steps make no model call)"
say "info testing agent: OpenRouter actor ${E2E_AGENT_MODEL:-inclusionai/ling-3.0-flash-vl}, judge ${E2E_JUDGE_MODEL:-meta/muse-spark-1.3-contributor} (replayed steps make no model call)"
else
say "info no OpenRouter key: tests with agent steps will skip"
fi
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ $H down # always, including after failures
- The stack holds the same lock as `./.superset/db-gate.sh`, so only one worktree can run it at a time.
- Most tools work inside a Current Debate. Recipes start one with `startDebate` or answer the motion dialog with `chooseForMotion`; see the skill's Drive section.
- Neither mode proves Stripe Checkout, real Stripe cancellation, ElevenLabs voice audio, Google sign-in or export, staging, or production.
- Agentic tests (`agentic/*.e2e.ts`) are an AI agent driving goals and judging screens, with exact assertions on the result. They cover a journey per tool and every student and public page in English, Spanish and Hungarian, and fail on page errors, `console.error`, and Supabase 5xx answers. They catch what selectors cannot: stray untranslated text, clipped layouts, off-topic model output in live mode. They also run explorations that hunt for bugs as a persona. The agent calls its own OpenRouter models (GPT-6 Luna acts, the Muse Spark contributor tier judges), even on a fake stack. A flow change that breaks an agentic test updates it in the same PR; commit new or re-recorded `agentic/replay/` entries with it. See [docs/testing.md](docs/testing.md#agentic-browser-tests).
- Agentic tests (`agentic/*.e2e.ts`) are an AI agent driving goals and judging screens, with exact assertions on the result. They cover a journey per tool and every student and public page in English, Spanish and Hungarian, and fail on page errors, `console.error`, and Supabase 5xx answers. They catch what selectors cannot: stray untranslated text, clipped layouts, off-topic model output in live mode. They also run explorations that hunt for bugs as a persona. The agent calls its own OpenRouter models (Ling 3.0 Flash VL acts, the Muse Spark contributor tier judges), even on a fake stack. A flow change that breaks an agentic test updates it in the same PR; commit new or re-recorded `agentic/replay/` entries with it. See [docs/testing.md](docs/testing.md#agentic-browser-tests).

### Keep the skill working

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
{
"schemaVersion": "trace-1",
"createdAt": "2026-10-07T12:08:33.739Z",
"payload": {
"actions": [
{
"name": "check",
"summary": "check switch \"Toggle Plastic bag levy study\"",
"target": {
"role": "switch",
"name": "Toggle Plastic bag levy study"
},
"checked": true
},
{
"name": "check",
"summary": "check switch \"Toggle Plastic bag levy study\"",
"target": {
"role": "switch",
"name": "Toggle Plastic bag levy study"
},
"checked": true
},
{
"name": "tap",
"summary": "tap switch \"Toggle Plastic bag levy study\"",
"target": {
"role": "switch",
"name": "Toggle Plastic bag levy study"
}
}
],
"executor": {
"name": "e2e-default-agent",
"version": "2"
},
"recordedFor": {
"testId": "agentic/workspace.e2e.ts::a%20student%20adds%20a%20source%20to%20the%20debate%27s%20Library%20and%20pauses%20it",
"targetId": "desktop",
"instructionDigest": "9d91feda70935783ebcf15c1b620bce9da676e8396f3bf892a209539bebb7dbb",
"paramsDigest": "5389157aca16270d663ba59055313812cc7ac445013bac8109e739f151f394e3",
"callIndex": 0,
"agent": "default"
},
"summary": "Toggled the switch for 'Plastic bag levy study' off. The Library now shows: Active sources = 0, Grounding Disabled with message 'AI responses are not grounded', and the source switch is no longer checked.",
"startPath": "/app/library",
"endPath": "/app/library",
"endAnchors": [
{
"role": "heading",
"name": "Grounding Disabled"
},
{
"text": "AI responses are not grounded"
},
{
"role": "switch",
"name": "Toggle Plastic bag levy study"
}
],
"goneAnchors": [
{
"role": "button",
"name": "Close"
},
{
"text": "Source Added"
},
{
"text": "Your source is now available for grounding."
},
{
"role": "heading",
"name": "Grounding Active"
},
{
"text": "AI responses use your sources"
},
{
"role": "listitem",
"name": "Close Source Added Your source is now available for grounding."
},
{
"role": "switch",
"name": "Toggle Plastic bag levy study",
"states": [
"checked"
]
}
],
"endWaitMs": 12649
}
}
2 changes: 1 addition & 1 deletion docs/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ How it runs:
- Always through `verify.sh e2e <run|explore|list|cache>`. The wrapper runs `doctor`, loads the stack environment, turns e2e telemetry off, and takes the testing agent's key from `OPENROUTER_API_KEY`, `LIVE_OPENROUTER_API_KEY`, or `~/.config/debatelab/live-verify.env`. It writes the report (`summary.md`, `report.json`), screenshots, traces, and failure videos to `.verify/artifacts/<run>-e2e-<command>-<pid>/`. The config refuses a non-loopback URL and never starts a dev server, because a plain `npm run dev` reaches production.
- Each signed-in test signs in a fresh demo-code user through the Login page and deletes it afterwards. Public-page tests use the `visitorTest` fixture with the same checks.
- There are two targets, `desktop` (1280 by 800) and `mobile` (393 by 851). e2e does not emulate touch. The app lays out by viewport width and never reads the user agent, so the viewport is what matters; the Playwright Pixel 5 project still owns touch behavior.
- The testing agent has two models on OpenRouter, separate from the app's provider mode, and a fake-mode stack still needs both. The actor (`agent.act` and explorations) is `openai/gpt-6-luna`. Recorded flows replay without it, so it runs only on a cache miss and in explorations, at a fraction of a cent per step. OpenRouter withdrew the free Qwen tier the suite used before, which now fails with `This model is unavailable for free`. The judge (`agent.assert`, `waitFor`, `extract`) is `meta/muse-spark-1.3-contributor` ($0.10 and $0.20 per million input and output tokens), the same tier as the app's live pin. It answers every judgment on every run. A full run of both viewports makes about 250 judgments and costs about ten cents. It takes about 45 minutes on one machine with three workers, which is why CI splits it into four jobs. The judge cannot act, because Meta's endpoint accepts only `tool_choice: "auto"`. `E2E_AGENT_MODEL` and `E2E_JUDGE_MODEL` override the two. The judge's tier may keep prompts, so use made-up debate content only. A transient OpenRouter error such as `[Meta] The requested model was not found` is infrastructure: rerun with `--last-failed`. The agent's context explains that fake AI answers are canned free-transport text.
- The testing agent has two models on OpenRouter, separate from the app's provider mode, and a fake-mode stack still needs both. The actor (`agent.act` and explorations) is `inclusionai/ling-3.0-flash-vl` ($0.021 and $0.062 per million input and output tokens on Novita, with DeepInfra as fallback). Recorded flows replay without it, so it runs only on a cache miss and in explorations. A small model is enough to click through these flows: with the cache off it passed the actor-heavy specs for about a fifth of GPT-6 Luna's price, while Gemma 3 12B, cheaper still, failed most of them. It sometimes calls `check` on a switch without `checked: false` and then reports the step done, so an unrecorded step that turns something off can fail; record such steps and commit the entry. OpenRouter withdrew the free Qwen tier the suite used before, which now fails with `This model is unavailable for free`. The judge (`agent.assert`, `waitFor`, `extract`) is `meta/muse-spark-1.3-contributor` ($0.10 and $0.20 per million input and output tokens), the same tier as the app's live pin. It answers every judgment on every run. A full run of both viewports makes about 250 judgments and costs about ten cents. It takes about 45 minutes on one machine with three workers, which is why CI splits it into four jobs. The judge cannot act, because Meta's endpoint accepts only `tool_choice: "auto"`. `E2E_AGENT_MODEL` and `E2E_JUDGE_MODEL` override the two. The judge's tier may keep prompts, so use made-up debate content only. A transient OpenRouter error such as `[Meta] The requested model was not found` is infrastructure: rerun with `--last-failed`. The agent's context explains that fake AI answers are canned free-transport text.
- Replay. When a later check passes, an `agent.act` flow is recorded in [`agentic/replay/`](../agentic/replay/). Later runs replay it with no model call and verify that its effect happened, until the app changes and the agent takes over and records again. The model is not part of the cache key, so a flow recorded with one actor replays under another. A step that ends on a passing toast or other transient state can hand off to the agent on replay (`end-mismatch` in the report); that costs a model call, not a failure. Commit new and changed entries with the test that made them. Local runs read and write the cache; CI only reads it. Changing an instruction, its params, the test or target name, the agent's `context`, or e2e's minor version re-records.
- Without a model key or the stack environment, agent tests skip, so a bare `npx e2e run` proves nothing. `failOnSkippedFailure` fails a run in which a test skipped after a failure.

Expand Down
7 changes: 4 additions & 3 deletions e2e.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,14 @@ const live = process.env.VERIFY_MODE === "live";
// The testing agent's models, separate from the app's own. Both need tool
// calling and image input. The actor drives agent.act and explorations; its
// recorded flows replay with no model call, so it runs only on a cache miss
// and in explorations, for a fraction of a cent per step. GPT-6 Luna is fast
// and reliable at it (OpenRouter withdrew the free Qwen tier used before).
// and in explorations. Ling 3.0 Flash VL is about a fifth of GPT-6 Luna's
// price and drives these flows fine; Gemma 3 12B, cheaper still, could not.
// OpenRouter withdrew the free Qwen tier used before.
// The judge answers every agent.assert, waitFor and extract on every run, so it
// must be cheap and reliable: the Muse Spark contributor tier, which like the
// app's live pin may keep prompts (made-up content only).
// `||`, not `??`: an unset Actions variable arrives as an empty string.
const model = openrouter(process.env.E2E_AGENT_MODEL || "openai/gpt-6-luna");
const model = openrouter(process.env.E2E_AGENT_MODEL || "inclusionai/ling-3.0-flash-vl");
const judge = openrouter(process.env.E2E_JUDGE_MODEL || "meta/muse-spark-1.3-contributor");

// `context` reaches every model call, judges included: what the app calls things.
Expand Down
Loading