@beryl-so/cli 0.67.0 → 0.77.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -13,10 +13,9 @@ npx @beryl-so/cli@latest init
13
13
  ```
14
14
 
15
15
  Signs you in (browser approval by default, emailed one-time code as fallback; new
16
- emails are signed up on the spot), wires the beryl + playwright MCP servers into the
16
+ emails are signed up on the spot), wires the beryl MCP server into the
17
17
  agent you pick with --claude, --cursor, --vscode or --opencode (it asks when you pass
18
- none), installs the beryl-test authoring skill, and installs Playwright for local runs.
19
- Safe to re-run.
18
+ none), and installs the beryl-test authoring skill. Safe to re-run.
20
19
 
21
20
  ## Install
22
21
 
@@ -78,11 +77,11 @@ Set up Beryl in this repo: sign in and wire up your coding agent
78
77
 
79
78
  ### guide
80
79
 
81
- Print the Beryl test-authoring guide
80
+ Print the Beryl test guide
82
81
 
83
82
  | Command | Summary | MCP tool |
84
83
  | --- | --- | --- |
85
- | `beryl guide` | Print the Beryl test-authoring guide | `guide` |
84
+ | `beryl guide` | Print the Beryl test guide | `guide` |
86
85
 
87
86
  ### login
88
87
 
@@ -177,9 +176,11 @@ Create and manage projects: a site you author tests for and run.
177
176
  | `beryl projects create [url]` | Create a project for a URL, or an empty one with just --name | `projects_create` |
178
177
  | `beryl projects rename <name>` | Rename a project | `projects_rename` |
179
178
  | `beryl projects quarantine [state]` | Turn auto quarantine on or off and tune when it mutes and releases a test | `projects_quarantine` |
179
+ | `beryl projects surfaces <surfaces...>` | Set which surfaces are enabled for this project | `projects_surfaces` |
180
+ | `beryl projects findings-gate <mode>` | Turn the findings gate on each run on or off | `projects_findings_gate` |
180
181
  | `beryl projects delete` | Delete a project and all its tests and runs | `projects_delete` |
181
182
  | `beryl projects re-explore` | Send the agent back in to run/heal existing tests and discover new flows | none |
182
- | `beryl projects report` | Aggregate quality report across recent runs (pass rates, flaky tests, trend) | `projects_report` |
183
+ | `beryl projects report` | Aggregate quality report across recent runs (pass rates, per-test history, trend) | `projects_report` |
183
184
  | `beryl projects reusable-auth <url>` | Check whether a saved login can be reused for a URL before creating a project | `projects_reusable_auth` |
184
185
 
185
186
  ### envs
@@ -191,7 +192,7 @@ Manage a project's environments: the URLs and auth Beryl runs tests against.
191
192
  | `beryl envs list` | List a project's environments | `envs_list` |
192
193
  | `beryl envs get <env-id>` | Show one environment | `envs_get` |
193
194
  | `beryl envs create <name> <url>` | Add an environment (e.g. staging) to a project | `envs_create` |
194
- | `beryl envs update <env-id>` | Update an environment's name, URL, auth settings, Site Health screen sizes, schedule, CI and pages, or issue tracker settings | `envs_update` |
195
+ | `beryl envs update <env-id>` | Update an environment's name, URL, app ids, auth settings, Site Health screen sizes, schedule, CI and pages, or issue tracker settings | `envs_update` |
195
196
  | `beryl envs delete <env-id>` | Delete an environment | `envs_delete` |
196
197
 
197
198
  ### schedule
@@ -213,13 +214,12 @@ Author, inspect, version, and heal a project's tests: the checks Beryl runs on e
213
214
 
214
215
  | Command | Summary | MCP tool |
215
216
  | --- | --- | --- |
216
- | `beryl tests lint` | Validate a plan JSON file offline, before sending it to the server | `tests_lint` |
217
217
  | `beryl tests list` | List the project's tests with their latest result | `tests_list` |
218
218
  | `beryl tests get <test-id>` | Show one test | `tests_get` |
219
219
  | `beryl tests plan <test-id>` | Print a test's current step plan (JSON) | `tests_plan` |
220
- | `beryl tests create` | Create a test case from a JSON action plan (for tests authored locally, e.g. by your coding agent) | `tests_create` |
221
220
  | `beryl tests set-plan <test-id>` | Replace a test's step plan from a JSON file (creates a new version) | `tests_set_plan` |
222
221
  | `beryl tests rename <test-id> <title>` | Rename a test | `tests_rename` |
222
+ | `beryl tests set-intent <test-id> <intent>` | Edit what a test proves, the target heal judges against | `tests_set_intent` |
223
223
  | `beryl tests set-groups <test-id>` | Replace the groups a test belongs to | `tests_set_groups` |
224
224
  | `beryl tests set-access <test-id> <access>` | Mark a test read or write | `tests_set_access` |
225
225
  | `beryl tests add-groups <test-ids...>` | Add groups to one or more tests, keeping the groups they already have | `tests_add_groups` |
@@ -234,7 +234,7 @@ Author, inspect, version, and heal a project's tests: the checks Beryl runs on e
234
234
  | `beryl tests reset <test-id>` | Discard user edits and return the test to its latest system-authored version | `tests_reset` |
235
235
  | `beryl tests heal <test-id> <state>` | Turn self-healing on or off for a test | `tests_heal` |
236
236
  | `beryl tests history <test-id>` | Pass/fail history, streak, and stability for a test | `tests_history` |
237
- | `beryl tests script [test-id]` | Print the rendered Playwright spec for a test (or an unbanked plan file) | `tests_script` |
237
+ | `beryl tests script <test-id>` | Print the rendered Playwright spec for a test | `tests_script` |
238
238
  | `beryl tests export <test-ids...>` | Export tests as Playwright .spec.ts files in a ZIP | `tests_export` |
239
239
 
240
240
  ### groups
@@ -259,7 +259,6 @@ Trigger a run of a project's tests (e.g. in CI), then watch, inspect, and downlo
259
259
  | Command | Summary | MCP tool |
260
260
  | --- | --- | --- |
261
261
  | `beryl runs trigger` | Trigger a test run (whole suite, a subset, or one environment) | `runs_trigger` |
262
- | `beryl runs local [test-ids...]` | Run tests on your machine with Playwright; results sync to Beryl | `runs_local` |
263
262
  | `beryl runs list` | List recent runs | `runs_list` |
264
263
  | `beryl runs get <run-id>` | Show one run with its per-test results | `runs_get` |
265
264
  | `beryl runs watch <run-id>` | Follow a run until it finishes | `runs_watch` |
@@ -286,6 +285,18 @@ Site Health, or how your site reads to search engines and visitors: content, spe
286
285
  | `beryl health tracker teams` | List the teams of the connected issue tracker | `health_tracker_teams` |
287
286
  | `beryl health tracker projects` | List a tracker team's projects | `health_tracker_projects` |
288
287
 
288
+ ### jobs
289
+
290
+ Test-creation jobs: the cloud agent reads a document and authors tests from it.
291
+
292
+ | Command | Summary | MCP tool |
293
+ | --- | --- | --- |
294
+ | `beryl jobs create [file]` | Create tests from a document (PRD, release notes) on Beryl's cloud | `jobs_create` |
295
+ | `beryl jobs list` | List the project's test-creation jobs | `jobs_list` |
296
+ | `beryl jobs get <job-id>` | Show a job's status and the tests it created | `jobs_get` |
297
+ | `beryl jobs clone <job-id>` | Run a job again: queue a new job with the same document and environment | `jobs_clone` |
298
+ | `beryl jobs delete <job-id>` | Delete a job; the flows and tests it created stay | `jobs_delete` |
299
+
289
300
  ### explorations
290
301
 
291
302
  Inspect the agent's exploration runs: how it crawled a site and authored its tests.
@@ -308,13 +319,13 @@ Manage the variables, secrets, and files the agent can use while exploring and r
308
319
  | `beryl config vars set <key> <value>` | Create or update a config variable | `config_vars_set` |
309
320
  | `beryl config vars get <key>` | Show one config variable | `config_vars_get` |
310
321
  | `beryl config vars delete <key>` | Delete a config variable | `config_vars_delete` |
311
- | `beryl config secrets list` | List the project's secrets (values are never returned) | `config_secrets_list` |
322
+ | `beryl config secrets list` | List the project's secrets (values are never returned); a test account's password is managed on the account, not here | `config_secrets_list` |
312
323
  | `beryl config secrets set <key> <value>` | Create a secret (write-only; re-setting a key replaces it) | `config_secrets_set` |
313
324
  | `beryl config secrets get <key>` | Show one secret's metadata, or reveal its value with --reveal | `config_secrets_get` |
314
325
  | `beryl config secrets delete <key>` | Delete a secret | `config_secrets_delete` |
315
- | `beryl config files list` | List files uploaded for the agent to use (e.g. CSVs, upload fixtures) | `config_files_list` |
326
+ | `beryl config files list` | List uploaded files: fixtures tests attach and Android and iOS app builds | `config_files_list` |
316
327
  | `beryl config files get <file>` | Show one uploaded file's metadata | `config_files_get` |
317
- | `beryl config files upload <file>` | Upload a file | `config_files_upload` |
328
+ | `beryl config files upload <file>` | Upload a file: a fixture an upload step attaches, or an Android or iOS app build | `config_files_upload` |
318
329
  | `beryl config files download <file-id>` | Get a short-lived download URL for a file | `config_files_download` |
319
330
  | `beryl config files delete <file-id>` | Delete an uploaded file | `config_files_delete` |
320
331
 
@@ -356,6 +367,7 @@ Durable identities on the site under test: what an authenticated test signs in a
356
367
  | `beryl accounts list` | List the test accounts an environment's tests sign in as | `accounts_list` |
357
368
  | `beryl accounts get <account-id>` | Show one test account: origin, lifetime, sign-in state | `accounts_get` |
358
369
  | `beryl accounts create` | Add a test account: the customer's own, or one Beryl signs up | `accounts_create` |
370
+ | `beryl accounts capture` | Store a signed-in session as a captured test account | `accounts_capture` |
359
371
  | `beryl accounts signup <account-id>` | Store a test account's signup plan and sign it up on its environment | `accounts_signup` |
360
372
  | `beryl accounts set-login <account-id>` | Store the sign-in plan a run replays once, plus the probe that proves it | `accounts_set_login` |
361
373
  | `beryl accounts get-login <account-id>` | Read the stored sign-in plan, its probe, and the hash a safe write must cite | `accounts_get_login` |
@@ -278,13 +278,15 @@ export function mcpInstructions() {
278
278
  "version, and whether this build is behind npm's latest). " +
279
279
  "Beryl authors, runs, and heals end-to-end tests for any web app: tests are JSON " +
280
280
  "action plans replayed in real cloud browsers, signing in as a durable test " +
281
- "account whose mail arrives at the project's own mailbox — so signup/OTP/" +
281
+ "account whose mail arrives at the project's own mailbox, so signup/OTP/" +
282
282
  "magic-link flows are self-contained, with no human login needed. " +
283
- "The full authoring guide (plan shape, outcome assertions, email/OTP wiring, " +
284
- "run-fix loop) ships as both the beryl-test skill and the `guide` tool — same " +
283
+ "New tests are authored only by Beryl's cloud agent: give it a document describing " +
284
+ "the flows with `jobs_create`; there is no local authoring. " +
285
+ "The guide (plan shape, outcome assertions, accounts, email/OTP wiring, the " +
286
+ "run-fix loop) ships as both the beryl-test skill and the `guide` tool, same " +
285
287
  `content. If a beryl-test skill stating v${version} is already loaded, do not ` +
286
288
  "call `guide`; if no beryl-test skill is available or it states another version, " +
287
- "call `guide` before authoring your first test plan. " +
289
+ "call `guide` before editing a test plan or a sign-in plan. " +
288
290
  "An account can hold several workspaces and projects: call `projects_list` first " +
289
291
  "and pass `project` (an id is enough) on every project-scoped tool, or the call fails.");
290
292
  }
@@ -1,4 +1,4 @@
1
- // The `beryl-test` authoring skill, installed by `beryl init` into
1
+ // The `beryl-test` skill, installed by `beryl init` into
2
2
  // `.agents/skills/beryl-test/SKILL.md` (vendor-neutral, editor-agnostic) — and, when
3
3
  // claude-code is a selected editor, ALSO into the `.claude/skills/beryl-test/SKILL.md`
4
4
  // that Claude Code actually indexes (see `writeSkills` in `commands/init.ts`). Kept as an
@@ -17,23 +17,24 @@ export const BERYL_TEST_SKILL_DIR = "beryl-test";
17
17
  // nor `.claude/skills/`, so `init` writes them their own pointer instead. Thin on purpose:
18
18
  // it sends the agent to the `guide` MCP tool rather than inlining the 500-line skill, and
19
19
  // `guide` ships with the CLI so the real guidance never drifts from the installed version.
20
- const BERYL_GUIDE_POINTER = `When writing, editing, or debugging Beryl tests in this repo, call the \`guide\`
21
- MCP tool first. It returns the full authoring guide: the ActionPlan shape, outcome
22
- assertions, test accounts and the project mailbox, and the local run-fix loop.
20
+ const BERYL_GUIDE_POINTER = `When creating, running, editing, or debugging Beryl tests in this repo, call the
21
+ \`guide\` MCP tool first. It returns the full guide: where new tests come from, the
22
+ ActionPlan shape, outcome assertions, test accounts and the project mailbox, and the
23
+ local run-fix loop.
23
24
 
24
- Then use the Beryl MCP tools: \`projects_list\` to find the project, and \`tests_create\`
25
- to author a plan and replay it locally before it is banked.
25
+ Then use the Beryl MCP tools: \`projects_list\` to find the project, \`jobs_create\` to
26
+ have Beryl's cloud agent author tests from a document, and \`runs_trigger\` to run them.
26
27
 
27
28
  The \`guide\` tool ships with the CLI and always matches the installed version, so treat
28
29
  it as the source of truth over anything cached here.`;
29
30
  // Cursor reads its own `.cursor/rules/*.mdc`, auto-loaded when alwaysApply is set.
30
31
  export const BERYL_CURSOR_RULE_FILENAME = "beryl.mdc";
31
32
  export const BERYL_CURSOR_RULE = `---
32
- description: Authoring and editing Beryl end-to-end tests
33
+ description: Creating, running and editing Beryl end-to-end tests
33
34
  alwaysApply: true
34
35
  ---
35
36
 
36
- # Beryl test authoring
37
+ # Beryl tests
37
38
 
38
39
  ${BERYL_GUIDE_POINTER}
39
40
  `;
@@ -43,7 +44,7 @@ ${BERYL_GUIDE_POINTER}
43
44
  export const BERYL_COPILOT_MARK_BEGIN = "<!-- BEGIN beryl (managed by beryl init) -->";
44
45
  export const BERYL_COPILOT_MARK_END = "<!-- END beryl -->";
45
46
  export const BERYL_COPILOT_SECTION = `${BERYL_COPILOT_MARK_BEGIN}
46
- ## Beryl test authoring
47
+ ## Beryl tests
47
48
 
48
49
  ${BERYL_GUIDE_POINTER}
49
50
  ${BERYL_COPILOT_MARK_END}`;
@@ -89,60 +90,64 @@ export const BERYL_TEST_SKILL_OTP_EXAMPLE_PLAN = {
89
90
  };
90
91
  export const BERYL_TEST_SKILL = `---
91
92
  name: beryl-test
92
- description: Author durable, healable end-to-end tests for a web app with Beryl. Use when writing, running, or fixing a Beryl test locally with your own coding agent, drafting the plan over the Playwright MCP, writing the natural-language intent, and running it with \`runs_local\`.
93
+ description: Create, run, and fix durable, healable end-to-end tests for a web app with Beryl. Use when asking Beryl's cloud agent to create tests from a document (\`jobs_create\`), running them in Beryl's cloud with \`runs_trigger\`, setting up test accounts, or fixing a banked test's plan with your own coding agent.
93
94
  ---
94
95
 
95
- # Authoring Beryl tests
96
+ # Beryl tests
96
97
 
97
- beryl-test v${cliVersion()} — installed by \`beryl init\`; the \`guide\` tool always returns the current version of this guide.
98
+ beryl-test v${cliVersion()}, installed by \`beryl init\`; the \`guide\` tool always returns the current version of this guide.
98
99
 
99
100
  A Beryl test drives a real browser through a flow and asserts the flow reached its
100
- meaningful outcome. \`beryl init\` wired two MCP servers: **beryl** (the tools you author
101
- with: \`accounts_list\`, \`tests_create\`, \`runs_local\`, and the rest) and **playwright**
102
- (drive a real browser). You do everything below by calling those tools. Beryl **heals**
103
- tests as the app's markup drifts, but only when you supply the three things that make a
104
- test durable: a real **outcome assertion**, a strong **natural-language intent**, and the
105
- **local run-fix loop**.
101
+ meaningful outcome. \`beryl init\` wired the **beryl** MCP server (\`jobs_create\`,
102
+ \`accounts_list\`, \`runs_trigger\`, and the rest). You do everything below by
103
+ calling those tools; tests are authored and run in Beryl's cloud. Beryl **heals**
104
+ tests as the app's markup drifts, but only when a test has the three things that make
105
+ it durable: a real **outcome assertion**, a strong **natural-language intent**, and a
106
+ **verified plan** (every save is proven by a replay).
106
107
 
107
108
  ## 0. Start here
108
109
 
109
- **Everything happens on your machine first**: you drive the flow here, and
110
- \`tests_create\` proves the plan here too, so a working local browser IS the loop.
111
- Keep \`tests_create\`'s verify on (banking unproven is a trap); replay mechanics and the
112
- cloud-session exception are in the \`tests_create\` tool's own description.
110
+ **New tests are authored only by Beryl's cloud agent.** There is no local authoring: you
111
+ do not draft a new plan here. A human writes a document (a PRD, release notes, a list of
112
+ flows) and \`jobs_create\` hands it to the agent, which explores the environment and
113
+ authors one test per scenario and surface. \`jobs_get\` shows the job's status, the tests
114
+ it produced and any scenario it skipped. The document is sent to the LLM that drives the
115
+ agent, so it must not hold anything the user would not share with it.
113
116
 
114
- Set up (once): if you cannot call the Beryl tools yet, run \`beryl init\` in a terminal to
115
- install the browser and wire the tools. Then, before the first plan:
117
+ What you do here: set up the accounts tests sign in as (§1), trigger and read runs
118
+ (§4), and fix a banked test's plan when it needs it (§2).
119
+
120
+ Set up (once): if you cannot call the Beryl tools yet, run \`beryl init\` in a terminal
121
+ to wire them. Then, before the first job:
116
122
 
117
123
  - \`envs_list\`: the root URL gotos resolve against (§2). If it is empty, set it with
118
124
  \`envs_update\` to a URL Beryl's cloud can reach, never \`localhost\`, \`127.0.0.1\`
119
- or a private IP (refused). For a local dev server, pass \`url-override\` to
120
- \`tests_create\` and \`runs_local\` instead.
125
+ or a private IP (refused). A local dev server is not testable until it is reachable
126
+ from the cloud (deploy a preview, or tunnel it).
121
127
  - \`accounts_list\`: who do authenticated tests sign in as (§1)? Store its sign-in with
122
128
  \`accounts_set_login\`, prove it with \`accounts_check\`.
123
129
  - \`mailbox_get\`: the address the account receives mail at (§5).
124
130
 
125
- \`accounts_list\` comes FIRST: standing account vs new identity changes the plan you
126
- write (discovering it afterwards means rewriting), and an authenticated test cannot be
127
- created until its account's login plan is stored.
131
+ \`accounts_list\` comes FIRST: an authenticated test cannot be saved until its account's
132
+ login plan is stored.
128
133
 
129
- Then, per test:
134
+ Then:
130
135
 
131
- 1. **Drive the flow** in a real browser over the Playwright MCP, never from imagination (§2).
132
- 2. **Write the ActionPlan**, with one real outcome assertion (§2).
133
- 3. **Lint it** with \`tests_lint\` (the plan plus its \`read\`/\`write\` access): schema and
134
- access check, offline.
135
- 4. **Bank it** with \`tests_create\` (the title, the plan, its \`access\`, and the
136
- \`description\` intent): it replays the plan locally, banks only on green, records no
137
- run; a red replay hands back the failure to fix and re-run (§3 for the intent, §4 for
138
- the loop).
139
- 5. When the batch is banked, run the new ids together ONCE with \`runs_local\` (or
140
- \`runs_trigger\` for the cloud) so they land as one run with real first results.
136
+ 1. **Create tests** with \`jobs_create\` (the document, and the environment to
137
+ explore). \`jobs_get\` shows progress, the tests it banked (each one already
138
+ proven by a clean replay), and every scenario it skipped, with why.
139
+ 2. A job can pause as \`awaiting_input\` when the app's login wall blocks it (no
140
+ self-serve signup). Its \`needs\` field says what to do: capture a signed-in
141
+ session with the Beryl browser extension from the webapp. The job resumes by
142
+ itself and keeps everything it already banked.
143
+ 3. When the job is done, run the new ids together ONCE with \`runs_trigger\` so they
144
+ land as one run with real first results.
145
+ 4. **A test is red or wrong?** Fix its plan with the run-fix loop (§4).
141
146
 
142
147
  ## 1. First: who do your tests sign in as?
143
148
 
144
149
  An authenticated test **signs in as an account that already exists** — it does not sign
145
- one up. Start every authoring session with \`accounts_list\`:
150
+ one up. Start every session with \`accounts_list\`:
146
151
 
147
152
  \`\`\`
148
153
  label email type login status
@@ -152,17 +157,17 @@ admin qa+admin@x7k2p9.email.beryl.so provisioned (permanent) otp rea
152
157
 
153
158
  Author the sign-in as ordinary opening steps citing the reserved handles — fill
154
159
  \`{{login_email}}\`, fill \`{{login_password}}\`, submit, assert the logged-in shell.
155
- Set \`requires_auth: true\` AND \`auth_mode\` — both, always; \`auth_mode\` has NO
156
- default and create rejects a \`requires_auth\` plan without it. \`"inline"\` = the plan
160
+ Set \`requires_auth: true\` AND \`auth_mode\`, both, always; \`auth_mode\` has NO
161
+ default and a save rejects a \`requires_auth\` plan without it. \`"inline"\` = the plan
157
162
  signs itself in like this; \`"session"\` = no sign-in steps, rides the account's
158
163
  once-per-run session (§ Session mode). \`auth_label\` picks a non-default identity
159
164
  (the \`*\` row is the default). Handles resolve at run time to the named account, so
160
165
  one plan stays correct across environments.
161
166
 
162
- **The account needs a stored login plan before any authenticated test can be created.**
167
+ **The account needs a stored login plan before any authenticated test can be saved.**
163
168
  A run replays that plan to produce the session its tests ride, and replays it again
164
- when the session expires, so \`tests_create\` rejects a \`requires_auth\` plan whose
165
- account has none, naming the fix. Store it once per account with
169
+ when the session expires, so Beryl rejects a \`requires_auth\` plan whose account has
170
+ none, naming the fix. Store it once per account with
166
171
  \`accounts_set_login\` (§ Session mode).
167
172
 
168
173
  **NEVER paste a real email or password into a plan.** Handles resolve at run time —
@@ -239,9 +244,9 @@ An empty \`accounts_list\` means nothing is set up. In order of preference:
239
244
  let the plan \`await_email\` through; later sign-ins read the same mailbox.
240
245
  4. **Neither** → the flow is not testable authenticated. Say so rather than guessing.
241
246
 
242
- A project with no accounts falls back to the \`LOGIN_EMAIL\` variable +
243
- \`LOGIN_PASSWORD\` secret with the same handle resolution, so existing tests are
244
- unaffected — but new work should create an account.
247
+ Every saved login is a test account: \`{{login_email}}\` and \`{{login_password}}\` resolve
248
+ only from the environment's account, and a login password is never set with
249
+ \`config secrets set\`. The webapp's Test accounts tab edits the same accounts.
245
250
 
246
251
  ### Session mode: sign in once per run, not once per test
247
252
 
@@ -264,26 +269,21 @@ descriptions.
264
269
  Then write the tests with \`requires_auth: true\`, \`auth_mode: "session"\`, and NO
265
270
  sign-in steps.
266
271
 
267
- **Creates share the sign-in.** \`tests_create\` replays session-mode plans against the
268
- account's established session (reused while it stays live), so a batch of creates costs
269
- at most one sign-in — but still create one at a time: racing creates race the same
270
- mailbox for mail.
271
-
272
272
  An expired session is never your problem: every run proves the stored session and, on a
273
273
  miss, signs in again from the login plan. A "reconnect" message only appears when there
274
274
  is no stored login plan to refresh from.
275
275
 
276
276
  When \`accounts_check\` fails, its description explains which half broke and each
277
- \`SESSION_*\` reason. The one that changes your authoring: an account marked
277
+ \`SESSION_*\` reason. The one that changes your plans: an account marked
278
278
  unsupported (its session cannot survive a fresh browser) is not your bug, but it IS
279
- your move — re-author its session-mode tests with \`auth_mode: "inline"\` and their own
279
+ your move: switch its session-mode tests to \`auth_mode: "inline"\` and their own
280
280
  sign-in steps.
281
281
 
282
282
  ### Two identities in one test
283
283
 
284
- Invite-a-teammate-and-accept-as-them needs two identities mid-flow — not bankable as
285
- one test. Bank the half the app shows to account A ("the invitation is listed as
286
- pending", "the share link is issued") — a real, strong outcome. While AUTHORING you can
284
+ Invite-a-teammate-and-accept-as-them needs two identities mid-flow, so it is not bankable
285
+ as one test. Bank the half the app shows to account A ("the invitation is listed as
286
+ pending", "the share link is issued"): a real, strong outcome. While fixing a plan you can
287
287
  drive the full handshake live: a second account (\`accounts_create\`, labelled), or a
288
288
  second mailbox (\`mailbox_create\`, read with \`mailbox_read\`).
289
289
 
@@ -291,15 +291,18 @@ SSO-only sites are not a dead end: with a dedicated Google test account they fol
291
291
  item 2 like any password account. The captured session is the FALLBACK — no test account
292
292
  can be provided, or Google challenges the scripted sign-in: a human signs in once
293
293
  through a live browser view in the Beryl webapp, Beryl stores the session encrypted
294
- server-side and injects it into cloud runs, and \`tests_create\` verifies such plans
295
- server-side, \`runs_trigger\` runs them, \`runs_local\` skips them (the session never
296
- leaves Beryl's cloud).
294
+ server-side and injects it into cloud runs; the session never leaves Beryl's cloud.
295
+
296
+ ## 2. Fixing a test's plan
297
297
 
298
- ## 2. Author locally over the Playwright MCP
298
+ New tests come from \`jobs_create\` (§0). You write plan JSON only to fix a banked test
299
+ (\`tests_plan\` returns its current plan) or to store an account's sign-in or signup
300
+ plan (§1). Either way, the rules below hold.
299
301
 
300
- 1. **Drive the flow in a real browser first.** Walk it by hand over the Playwright MCP
301
- — log in, fill, submit — acting on elements by their accessibility ref from the
302
- latest page snapshot, never a guessed selector. Watch what actually happens.
302
+ 1. **Start from evidence, not memory.** Pull the failed run first: \`runs_get\` for
303
+ the failing step and error, \`runs_explain\` for the run's own diagnosis,
304
+ \`tests_events\` for how the test behaved over time. Fix what the evidence shows,
305
+ never what you assume the page looks like.
303
306
  2. **Write it as an ActionPlan** — a JSON object whose \`steps\` are
304
307
  \`{action, selector, url, value, ...}\`. Two structural rules:
305
308
  - the **first executed step is a \`goto\`**, and
@@ -357,17 +360,13 @@ ${JSON.stringify(BERYL_TEST_SKILL_EXAMPLE_PLAN, null, 2)
357
360
  .map((line) => ` ${line}`)
358
361
  .join("\n")}
359
362
  \`\`\`
360
- 3. **Validate offline, then create.** Lint with \`tests_lint\` (schema check, no network),
361
- then bank with \`tests_create\` (the title, the plan, and the \`description\` intent, see
362
- §3). \`tests_create\` banks the plan only on a green local replay; a red one hands back
363
- the failure to fix and re-run. It records no run. Replay mechanics (where it runs over
364
- MCP, its verify option, the cloud-session exception) are in the \`tests_create\` tool's
365
- own description. Inspect the exact spec with \`tests_script\`.
363
+ 3. **Save through the replay gate.** \`tests_recompile\` replays the edited plan
364
+ against the live site first and rejects it on a red replay; prefer it always.
365
+ \`tests_set_plan\` saves with no replay (the next run proves it); reserve it for
366
+ when the flow cannot replay right now (for example a third-party step). Pass a new
367
+ \`description\` only when the edit changes what the test proves (§3).
366
368
  Full ActionPlan JSON Schema:
367
369
  https://api.beryl.so/api/v1/schemas/action-plan.schema.json.
368
- Groups are optional and never block: after the test is banked, if \`groups_list\`
369
- shows any, offer them once (\`tests_set_groups\`); no answer = leave it ungrouped and
370
- move on. Never create a group yourself, that is the user's call.
371
370
 
372
371
  ### Testing a mobile or tablet viewport
373
372
  A test runs at a desktop viewport unless you say otherwise. To cover a responsive layout,
@@ -376,9 +375,32 @@ full Chromium device emulation, not a resize: the viewport, a mobile Chrome user
376
375
  touch and the device pixel ratio all change together, so a hamburger or a nav that only
377
376
  exists on small screens actually renders and is testable. \`mobile\` emulates a Pixel-class
378
377
  phone, \`tablet\` a Galaxy-class tablet; both keep a Chrome user-agent so the emulated UA
379
- matches the engine. Author the flow at that size over the Playwright MCP (resize the MCP
380
- browser to match) so the refs you act on are the ones the small-screen layout renders. The
381
- account, session and teardown rules are unchanged; \`device\` only sets the frame it runs in.
378
+ matches the engine. A test's surface is fixed when it is created, so when fixing a plan
379
+ keep \`device\` as it is: a small-screen test's selectors must come from what the
380
+ small-screen layout renders (a hamburger, not the desktop nav). The account, session and
381
+ teardown rules are unchanged; \`device\` only sets the frame it runs in.
382
+
383
+ ### App tests (Android and iOS)
384
+ \`device: "android"\` and \`device: "ios"\` are not browsers: the plan drives the environment's
385
+ app (its \`app_ids.android\` from the newest APK, or \`app_ids.ios\` from the newest simulator
386
+ build, uploaded to Files) on an emulator or simulator in Beryl's cloud. App tests are written
387
+ and verified by a test-creation job (\`jobs_create\`), never run locally. When you edit one,
388
+ its rules differ from a web plan's:
389
+
390
+ - The first step is \`launch\` (open the app), not \`goto\`. A later \`launch\` relaunches
391
+ with the app's data kept, which is how a plan proves something persisted.
392
+ - Steps: launch, back (Android only; iOS has no back button), fill, click, press, scroll,
393
+ wait_for, expect, await_email.
394
+ - Selectors are \`id=<resource id>\` (Android) or \`id=<accessibility identifier>\` (iOS), or
395
+ \`text=<visible text or accessibility label>\`; no CSS. React Native's \`testID\` shows up as
396
+ the id on both; a Flutter icon button needs a \`Semantics\` label to have text.
397
+ - \`have_text\` needs an \`id=\` selector; to check text is on screen, use \`visible\` with
398
+ \`text=\`. No URL or title expects: an app has no URL.
399
+ - Keys: Enter and Backspace, plus Tab on Android. Never put \`\${\` in plan text.
400
+ - A signed-in app test sets \`requires_auth\` and \`auth_mode: "session"\` and rides the
401
+ account's sign-in on that surface. The test-creation job saves that sign-in itself when
402
+ the app has a login screen; \`accounts_set_login\` with \`surface: "android"\` or
403
+ \`surface: "ios"\` only replaces it.
382
404
 
383
405
  ### The outcome assertion is the whole game
384
406
 
@@ -408,17 +430,16 @@ observable proof the flow worked.
408
430
  - **Don't work around a real app failure to make a test go green.** A genuinely broken
409
431
  flow is a finding to report, not something to paper over with a weaker assertion.
410
432
 
411
- ### Traps when authoring against a real app
433
+ ### Traps when writing a plan against a real app
412
434
 
413
- Every one of these has produced a wrong plan or a red \`tests_create\`. Check them.
435
+ Every one of these has produced a wrong plan or a red run. Check them.
414
436
 
415
- 1. **Your browser may already be signed in.** The Playwright MCP keeps a persistent
416
- profile. Author while signed in and you never see the gate — you will mark gated
417
- pages as public. **Log out first**, then confirm the gated page really redirects to
418
- the login.
437
+ 1. **A gated page can look public in signed-in evidence.** Run artifacts captured
438
+ while a session was live never show the login wall, so do not mark a flow public
439
+ (no \`requires_auth\`) from them; check how the page behaves logged out.
419
440
  2. **The sign-UP flow is not the sign-IN flow.** A brand-new address often gets an
420
441
  extra "create your account" step a returning address skips. A stored login plan must
421
- be the **returning** path — drive it twice: once to create, once to sign in again.
442
+ be the **returning** path: what the app shows the SECOND time that account arrives.
422
443
  3. **Read the DOM, not just the accessibility tree, before picking a selector.** Two
423
444
  buttons can share a label ("Continue" / "Continue with Google"), and Beryl relaxes
424
445
  \`text=X\` to a case-insensitive SUBSTRING — \`text=Continue\` is ambiguous. Use
@@ -442,8 +463,9 @@ Every one of these has produced a wrong plan or a red \`tests_create\`. Check th
442
463
 
443
464
  ## 3. Writing the natural-language intent
444
465
 
445
- Give the intent as the \`description\` of \`tests_create\` (or \`tests_set_plan\`
446
- when you re-author). 1-3 sentences. This is the immutable anchor from §6.
466
+ The cloud agent drafts each new test's intent. When a fix changes what a test proves,
467
+ pass the new intent as the \`description\` of \`tests_set_plan\`. 1-3 sentences. This is
468
+ the immutable anchor from §6.
447
469
 
448
470
  - **State the purpose, not the steps.** Not "clicks Sign in, types email and password,
449
471
  clicks submit" — the trajectory will change. Instead: *what does a green run prove is
@@ -459,19 +481,18 @@ Good:
459
481
  Weak (describes steps + asserts nothing meaningful):
460
482
  > "Goes to /login, fills the form, and checks the page loaded."
461
483
 
462
- ## 4. The local run-fix loop
484
+ ## 4. The run-fix loop
463
485
 
464
- Iterate on your machine before you rely on the cloud. \`runs_local\` runs banked tests
465
- with the Playwright runner bundled in the CLI: no cloud, no waiting for a scheduled run.
466
- Run it un-synced with a report directory against one test id while you iterate, or with
467
- no id to run the whole suite and record the results in Beryl.
486
+ Every run happens in Beryl's cloud. \`runs_trigger\` against one test id (or a list)
487
+ starts a run; \`runs_watch\` follows it live; \`runs_get\` returns each result's
488
+ failing step, error and artifacts; \`runs_explain\` turns a red run into a diagnosis.
468
489
 
469
- The loop: draft → \`tests_lint\` → \`tests_create\` (proves, records nothing) → \`runs_local\`
470
- un-synced while iterating → read the run's \`report.json\` to see which step or assertion
471
- failed and why → fix the plan → \`tests_set_plan\` → run again. When the batch is done, one
472
- synced \`runs_local\` (or \`runs_trigger\`) records it as a single run. Its options,
473
- session-mode behavior, and \`await_email\` / authenticated-test handling are in the
474
- \`runs_local\` tool's own description.
490
+ The loop: \`runs_trigger\` against the test id, read the failure with \`runs_get\` /
491
+ \`runs_explain\`, fix the plan (§2), save with \`tests_recompile\` (which already
492
+ replays it against the live site, so a green recompile IS the fix proven), then one
493
+ final \`runs_trigger\` so the suite records a clean run. If recompile keeps rejecting
494
+ an edit, the evidence disagrees with your theory: pull more of it rather than trying
495
+ variations.
475
496
 
476
497
  ## 5. Testing an OTP / signup flow (\`await_email\`)
477
498
 
@@ -527,12 +548,8 @@ For a magic-link flow, replace the code steps with
527
548
 
528
549
  Two caveats:
529
550
 
530
- - \`tests_create\` verifies an \`await_email\` plan like any other: the local
531
- replay receives at the project mailbox and answers each step over the API, so the
532
- app's mail really is received and extracted before the test is accepted. The replay
533
- signs up / sends mail FOR REAL, so turn \`tests_create\`'s verify off only if that side
534
- effect is unwanted. \`runs_local\` serves \`await_email\` the same way, so the local loop
535
- covers OTP/signup flows end to end.
551
+ - Every run serves \`await_email\` from the project mailbox. It signs up / sends
552
+ mail FOR REAL, like every run.
536
553
  - §2's outcome discipline still applies: the green signal is the post-verification
537
554
  state (the welcome screen, the dashboard), not "an email arrived".
538
555
 
@@ -571,14 +588,19 @@ Two shapes:
571
588
  \`third_party_integrations_target\` names, trigger the app's message, then
572
589
  \`{ "action": "await_third_party", "provider": "slack", "expect_text": "..." }\`.
573
590
  - **Integration intact**: no declaration. Trigger the app's message, then
574
- \`await_third_party\`.
591
+ \`await_third_party\`. When the app only answers its bot, ping the bot instead:
592
+ \`{ "action": "send_third_party", "provider": "slack", "value": "hello" }\` mentions it in
593
+ the project's channel, then \`await_third_party\` waits for its reply (in the channel or
594
+ in the thread). The bot's app must be recorded for the environment first:
595
+ \`third_party_integrations_target\` with \`env\` and \`app\`. A reply only the sender can
596
+ see never reaches Beryl, so such a bot can't be tested this way.
575
597
 
576
598
  Rules:
577
599
 
578
600
  - Always tear down what the test connected in \`after\`: disconnect the integration in
579
601
  the app.
580
- - These steps run only in Beryl's cloud: \`tests_create\` verifies server-side and
581
- \`runs_local\` skips them, so run them with \`runs_trigger\`.
602
+ - These steps need Beryl's provider accounts, which live in Beryl's cloud; they run
603
+ like any other cloud test.
582
604
  - A setup error saying Beryl's access "is being restored" is on Beryl's side: do not
583
605
  edit the test.
584
606
  - Never add steps that browse Slack itself; the actions above do all of it.