@asrunjs/asrun 0.9.0 → 0.10.0
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/.claude/commands/explore-case.md +11 -6
- package/.claude/commands/gen-spec.md +10 -6
- package/.claude/commands/heal-spec.md +13 -9
- package/.claude/commands/make-case.md +1 -1
- package/.claude/skills/exploring-cases/SKILL.md +65 -31
- package/.claude/skills/exploring-cases/references/assertion-targets.md +8 -4
- package/.claude/skills/exploring-cases/references/case-rules.md +110 -0
- package/.claude/skills/exploring-cases/references/handoff-card.js +73 -11
- package/.claude/skills/exploring-cases/references/open-questions.md +58 -6
- package/.claude/skills/exploring-cases/references/readiness.md +4 -3
- package/.claude/skills/exploring-cases/references/tools.md +117 -0
- package/.claude/skills/exploring-cases/references/user-handoff.md +46 -17
- package/.claude/skills/generating-specs/SKILL.md +71 -33
- package/.claude/skills/generating-specs/references/conventions.md +4 -0
- package/.claude/skills/making-cases/SKILL.md +14 -7
- package/.claude/skills/making-cases/references/gaps.md +7 -4
- package/.claude/skills/making-cases/references/normalizing.md +1 -1
- package/AGENTS.md +51 -11
- package/README.md +4 -3
- package/bin/asrun.mjs +1 -0
- package/dist/lib/case-doc.d.ts +41 -4
- package/dist/lib/case-doc.js +109 -17
- package/dist/scripts/check-cases.js +89 -1
- package/dist/scripts/doctor.js +50 -3
- package/dist/scripts/markers-confirm.d.ts +1 -0
- package/dist/scripts/markers-confirm.js +139 -0
- package/dist/ui/server/actions.js +50 -16
- package/dist/ui/server/app.js +7 -2
- package/dist/ui/server/job-store.d.ts +60 -0
- package/dist/ui/server/job-store.js +198 -0
- package/dist/ui/server/jobs.d.ts +46 -4
- package/dist/ui/server/jobs.js +361 -59
- package/dist/ui/server/process-info.d.ts +17 -0
- package/dist/ui/server/process-info.js +126 -0
- package/dist/ui/server/result-state.d.ts +10 -0
- package/dist/ui/server/result-state.js +106 -0
- package/dist/ui/shared/ai-commands.d.ts +4 -1
- package/dist/ui/shared/ai-commands.js +14 -4
- package/dist/ui/shared/types.d.ts +17 -2
- package/docs/agents/case-format.md +39 -5
- package/docs/ai-workflow.md +47 -15
- package/docs/cases.md +28 -4
- package/docs/commands.md +6 -1
- package/docs/troubleshooting.md +40 -0
- package/docs/web-ui.md +29 -6
- package/package.json +2 -1
- package/templates/.claude/commands/explore-case.md +11 -6
- package/templates/.claude/commands/gen-spec.md +10 -5
- package/templates/.claude/commands/heal-spec.md +11 -7
- package/templates/.claude/commands/make-case.md +1 -1
- package/templates/AGENTS.md +9 -6
- package/templates/cases/_template.md +7 -0
- package/templates/package.json +1 -0
- package/ui/dist/assets/{dist-B7HeglDd.js → dist-2msWI-5M.js} +1 -1
- package/ui/dist/assets/{dist-DfBpjgZT.js → dist-4U_gPHXf.js} +1 -1
- package/ui/dist/assets/{dist-B9_GXR2G.js → dist-BMfJatHo.js} +1 -1
- package/ui/dist/assets/{dist-Bb36rNUw.js → dist-BVvHVJ3N.js} +1 -1
- package/ui/dist/assets/{dist-WvvCZeeu.js → dist-BmWaNjqO.js} +1 -1
- package/ui/dist/assets/{dist-G9KNeh6u.js → dist-BwNz_xW2.js} +1 -1
- package/ui/dist/assets/{dist-GvRUDL-2.js → dist-J36xayOv.js} +1 -1
- package/ui/dist/assets/{dist-Dqc_tfzC.js → dist-e-KhTWHJ.js} +1 -1
- package/ui/dist/assets/{dist-DevASegc.js → dist-sfZKBSMm.js} +1 -1
- package/ui/dist/assets/{dist-Dq4Vnw5N.js → dist-wjB7g6OJ.js} +1 -1
- package/ui/dist/assets/{index-BJ5D2mFK.js → index-CGbRJT3b.js} +134 -132
- package/ui/dist/assets/index-CI0i0LWc.css +2 -0
- package/ui/dist/index.html +2 -2
- package/ui/dist/assets/index-BXY-eKBn.css +0 -2
|
@@ -5,7 +5,7 @@ without anybody present (a one-shot `claude -p` / `opencode run`). The case file
|
|
|
5
5
|
talk to each other, so nothing is lost between runs, tools or sessions.
|
|
6
6
|
|
|
7
7
|
## How a question is written in the case file
|
|
8
|
-
Every marker starts with its emoji, which is part of the marker: `🚫 TODO [blocking]`, `❓ TODO [non-blocking]`, `✍️ Answer:`, `🔍 to be confirmed
|
|
8
|
+
Every marker starts with its emoji, which is part of the marker: `🚫 TODO [blocking]`, `❓ TODO [non-blocking]`, `✍️ Answer:`, `🔍 to be confirmed` and `👁️ not observed`. The web UI colors the emoji with the marker and offers an X button that deletes it. `(seen on <profile>)` is not a marker: it is a plain note, shown as ordinary text.
|
|
9
9
|
Every question is a small block that hangs under a normal sentence. The sentence is the agent's suggestion, written
|
|
10
10
|
exactly as it should read in the final case. The block holds the question and an empty answer slot:
|
|
11
11
|
|
|
@@ -15,7 +15,7 @@ exactly as it should read in the final case. The block holds the question and an
|
|
|
15
15
|
- ✍️ Answer:
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
The same shape is used under a step, under a bullet of "Expected" and
|
|
18
|
+
The same shape is used under a step, under a bullet of "Expected" and on the prose line above the `## Values` block:
|
|
19
19
|
|
|
20
20
|
```
|
|
21
21
|
11. Delete the document with the "Delete" option of its "..." menu on the list page. [writes]
|
|
@@ -40,8 +40,52 @@ Rules for writing it:
|
|
|
40
40
|
QA should confirm it later but nothing waits for it.
|
|
41
41
|
- One question per block; narrow broad questions down instead of copying them ("which profile elements?" becomes
|
|
42
42
|
"is the logo the only per-profile element in the left side bar?").
|
|
43
|
-
- A
|
|
44
|
-
|
|
43
|
+
- A fact read from a page is not a question: either say it as a plain note `(seen on <profile>)`, or mark it
|
|
44
|
+
`(observed on <profile>, 🔍 to be confirmed: <what a person must decide>)` when a wrong reading would still pass.
|
|
45
|
+
It needs no TODO block. See "Marker or question, never both" below.
|
|
46
|
+
|
|
47
|
+
## Where the block hangs
|
|
48
|
+
The parent line is the sentence the question is about: the panel shows it on its own, next to "The text above this
|
|
49
|
+
question", so it has to make sense without the rest of the document.
|
|
50
|
+
- **The immediate line, not an ancestor.** Hang the block under the one sentence that makes the claim. Under a
|
|
51
|
+
nested bullet, indent it under that bullet, not under the top-level one above it; when the claim only makes sense
|
|
52
|
+
with an umbrella sentence, write the claim itself as a top-level bullet and hang the block there.
|
|
53
|
+
- **One claim, one block.** Several bullets raising one question get one block, under the closest of them, with the
|
|
54
|
+
others' evidence in that block. Never end a long list with a single question that covers all of it.
|
|
55
|
+
- **A sentence that stands alone.** No "see above", no pronoun whose referent is in a sibling bullet.
|
|
56
|
+
- **Never inside the `## Values` yaml block.** A marker or a question there becomes part of the expected value: put
|
|
57
|
+
it on the prose line above the block.
|
|
58
|
+
|
|
59
|
+
## Marker or question, never both
|
|
60
|
+
A sentence carries at most one "not settled yet" signal, and the two marks are alternatives:
|
|
61
|
+
- `🚫/❓ TODO`: there **is** a question. The parent sentence is a suggestion, the block says what to decide.
|
|
62
|
+
- `(observed on <profile>, 🔍 to be confirmed: <what a person must decide>)`: there is **no** question, only a check
|
|
63
|
+
somebody has to make later. The part after the colon is required: "to be confirmed by QA" alone tells a person
|
|
64
|
+
nothing, and the panel shows that part as "why it needs you".
|
|
65
|
+
- `(observed on <profile>, evidence: .explore/observations/<case-id>/<image>.png)`: the same, with a screenshot the
|
|
66
|
+
reviewer opens instead of going back to the page. Save it while exploring.
|
|
67
|
+
- `(seen on <profile>)`: a plain note. The run checks it, so nobody has to; it gets no colour and no panel row.
|
|
68
|
+
|
|
69
|
+
- Never put a marker inside a TODO block: not in the question, not in the evidence, not in a wrapped line. The words
|
|
70
|
+
"to be confirmed", "TBC", "TBD" and "not observed" are marker words even in prose there, and `pnpm case:check` fails
|
|
71
|
+
on them.
|
|
72
|
+
- Never put a marker on the sentence a block hangs under: keep the question and drop the marker.
|
|
73
|
+
- `👁️ not observed` is for an expectation that came from the notes and was never seen on a page. It is never the
|
|
74
|
+
evidence of a question.
|
|
75
|
+
|
|
76
|
+
## Which sentences carry a marker
|
|
77
|
+
A marker is for exactly one thing: a fact a person must settle because a wrong reading would still pass. Ask yourself:
|
|
78
|
+
**if this line were read wrong, would the generated spec fail?**
|
|
79
|
+
- **It would fail** → no marker. Locators, the flow of a step, navigation, request URLs, readiness signals, whether an
|
|
80
|
+
element exists. The run is the confirmation. Leave `(seen on <profile>)` when where it was seen matters.
|
|
81
|
+
- **It would pass anyway** → marker, with the reason. The literals a check asserts (a `## Values` entry, the wording of
|
|
82
|
+
an `## Expected` bullet) and the identity of a `[T<n>]` target. This is the class that hides a wrong expectation
|
|
83
|
+
forever; `assertion-targets.md` records two real incidents of it.
|
|
84
|
+
- A `## Rules` bullet the agent inferred from a page carries a marker only when the rule changes what is asserted;
|
|
85
|
+
otherwise it is `(seen on <profile>)`. A rule a person stated never carries one.
|
|
86
|
+
|
|
87
|
+
After a passing run, `pnpm markers:confirm --case <id> --env <env>` settles the markers whose line the spec checks
|
|
88
|
+
(`// unconfirmed: <the sentence>`), so a person does not have to open the page again to see whether it is still true.
|
|
45
89
|
|
|
46
90
|
## How the user answers
|
|
47
91
|
Two ways, and the agent must understand both on the next run:
|
|
@@ -69,7 +113,9 @@ answer, adds `(page shows <x> on <profile-id>)` and reports it as a finding. It
|
|
|
69
113
|
## Resolution order (no question goes to the user while a better step exists)
|
|
70
114
|
1. **The page answers it.** Facts such as exact labels, which controls exist, where the logo is, what a menu holds
|
|
71
115
|
are answered by exploration (`/explore-case`; needs an environment and a logged-in profile; the agent only reads, a person does the steps that write). Rewrite the parent sentence
|
|
72
|
-
with the fact
|
|
116
|
+
with the fact and remove the block. Then decide the mark by "Which sentences carry a marker": a fact the run checks
|
|
117
|
+
gets `(seen on <profile>)`, a fact that would pass while wrong gets
|
|
118
|
+
`(observed on <profile>, 🔍 to be confirmed: <what a person must decide>)`.
|
|
73
119
|
- A check with no expected value ("check X", "can see Y") becomes: the observed behaviour, marked as observed.
|
|
74
120
|
A spec may be generated from it.
|
|
75
121
|
- Values that a check asserts (logo name, label, option list) are observed per profile and written into the case's
|
|
@@ -101,13 +147,19 @@ answer, adds `(page shows <x> on <profile-id>)` and reports it as a finding. It
|
|
|
101
147
|
|
|
102
148
|
## Final report
|
|
103
149
|
Report in the user's language. List every open block with its question and the parent sentence it would confirm, so
|
|
104
|
-
the user can see what deleting it accepts.
|
|
150
|
+
the user can see what deleting it accepts. List the markers separately, each with its reason and whether it changes an
|
|
151
|
+
assertion: that is the short list a person actually has to look at. The last line of the final message is plain text
|
|
152
|
+
and nothing else, exactly one of:
|
|
105
153
|
|
|
106
154
|
```
|
|
107
155
|
RESULT: ready
|
|
108
156
|
RESULT: needs-input
|
|
109
157
|
```
|
|
110
158
|
|
|
159
|
+
Never put that line in a code fence or inline code, never make it bold, a heading or a list item, and put no label,
|
|
160
|
+
number or emoji in front of it: the panel reads it to show how the run ended. (The block above only lists the two
|
|
161
|
+
tokens; write the one that applies as the last line of your message, not the block.)
|
|
162
|
+
|
|
111
163
|
- `ready`: no `[blocking]` question remains. Non-blocking ones may remain; list them so QA can confirm them.
|
|
112
164
|
- `needs-input`: at least one `[blocking]` question remains in the case file (list each with its parent sentence),
|
|
113
165
|
or the work could not be done (no environment, nobody logged in, or no permission to write files: a plan-mode run,
|
|
@@ -22,9 +22,10 @@ A check for something **missing, empty or hidden** is only valid after a positiv
|
|
|
22
22
|
means: the list finished loading and has rows, and none is X.
|
|
23
23
|
|
|
24
24
|
## Exploring
|
|
25
|
-
- Know the key requests: after the action, call `browser_network_requests
|
|
26
|
-
app's API, the ones that return the content). Wait until they are finished
|
|
27
|
-
|
|
25
|
+
- Know the key requests: after the action, call `browser_network_requests` (`static: false`), pick the data calls
|
|
26
|
+
(XHR/fetch to the app's API, the ones that return the content). Wait until they are finished. `browser_network_clear`
|
|
27
|
+
is not runnable in this MCP, so take a marker instead: read the list before the action, keep the highest number, and
|
|
28
|
+
after the action only the entries above it belong to it (`tools.md`, "Network requests without a clear").
|
|
28
29
|
- Then wait for the content signal (`browser_wait_for` with text, or `browser_find` that the content is there), and
|
|
29
30
|
take the snapshot only then. A snapshot taken right after a click is a frame, not a fact.
|
|
30
31
|
- Observations record the ready signal: `(observed after <signal>, ...)`. A fact with no signal is not recorded.
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# The browser tools this MCP can run, and the ones it only lists
|
|
2
|
+
|
|
3
|
+
Shared by `exploring-cases`, `generating-specs` and healing. It is binding in all three: read it before the first
|
|
4
|
+
browser call of a run.
|
|
5
|
+
|
|
6
|
+
`playwright run-test-mcp-server` answers `tools/list` with **every** browser tool Playwright has, but a call runs
|
|
7
|
+
inside a paused test, and that backend installs one capability set:
|
|
8
|
+
|
|
9
|
+
```js
|
|
10
|
+
const config = { capabilities: ["testing"] }; // playwright/lib/mcp/test/browserBackend.js
|
|
11
|
+
const toolList = tools.filteredTools(config); // core* + testing, minus `skillOnly`
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Everything outside that set is listed and still cannot run: it answers `Tool "<name>" not found`. Two groups:
|
|
15
|
+
- a capability other than `core`/`testing` (`devtools`, `storage`, `network`, `pdf`, `vision`, `config`);
|
|
16
|
+
- `skillOnly: true` tools, which the backend never installs (`browser_network_clear`, `browser_console_clear`).
|
|
17
|
+
|
|
18
|
+
**`Tool "<name>" not found` is not a broken MCP server.** The server is up and every other tool works. Never stop
|
|
19
|
+
with `RESULT: needs-input` for it, never report a missing MCP server, and never fall back to an ad-hoc Playwright
|
|
20
|
+
script (it sees a different browser and its network view is not `browser_network_requests`, see A2). Use the
|
|
21
|
+
substitute below instead, and say in the report which substitute you used.
|
|
22
|
+
|
|
23
|
+
`pnpm asrun doctor` prints how many tools the installed Playwright actually runs and warns when this file names one
|
|
24
|
+
it cannot (it happens when Playwright changes the filter; the file then needs the same edit).
|
|
25
|
+
|
|
26
|
+
## Runnable
|
|
27
|
+
|
|
28
|
+
Navigation and reading:
|
|
29
|
+
`browser_navigate`, `browser_navigate_back`, `browser_snapshot`, `browser_find`, `browser_evaluate`,
|
|
30
|
+
`browser_run_code_unsafe`, `browser_tabs`, `browser_resize`, `browser_close`, `browser_handle_dialog`,
|
|
31
|
+
`browser_console_messages`, `browser_emulate_media`, `browser_wait_for`.
|
|
32
|
+
|
|
33
|
+
Interaction: `browser_click`, `browser_type`, `browser_fill_form`, `browser_press_key`, `browser_hover`,
|
|
34
|
+
`browser_drag`, `browser_drop`, `browser_select_option`, `browser_file_upload`.
|
|
35
|
+
|
|
36
|
+
Screenshots: `browser_take_screenshot`.
|
|
37
|
+
|
|
38
|
+
Network: `browser_network_requests`, `browser_network_request`.
|
|
39
|
+
|
|
40
|
+
Test helpers: `browser_generate_locator`, `browser_verify_element_visible`, `browser_verify_text_visible`,
|
|
41
|
+
`browser_verify_list_visible`, `browser_verify_value`.
|
|
42
|
+
|
|
43
|
+
`browser_run_code_unsafe` takes Playwright's own API (a `page`, `context`, `expect` are in scope), which is what
|
|
44
|
+
every substitute below uses.
|
|
45
|
+
|
|
46
|
+
## Listed but not runnable
|
|
47
|
+
|
|
48
|
+
| Tool | Why | Do this instead |
|
|
49
|
+
|---|---|---|
|
|
50
|
+
| `browser_highlight`, `browser_hide_highlight` | `devtools` | [Outline a target](#outlining-a-target-without-browser_highlight) |
|
|
51
|
+
| `browser_network_clear` | `skillOnly` | [Network requests without a clear](#network-requests-without-a-clear) |
|
|
52
|
+
| `browser_console_clear` | `skillOnly` | read `browser_console_messages` and note the messages you already saw |
|
|
53
|
+
| `browser_start_recording`, `browser_stop_recording` | `devtools` | the handoff card (`user-handoff.md`, "Recording") |
|
|
54
|
+
| `browser_annotate` | `devtools` | let the person name the element in the handoff card, then outline it yourself |
|
|
55
|
+
| `browser_start/stop_tracing`, `browser_start/stop_video`, `browser_video_*`, `browser_resume` | `devtools` | not needed by a case |
|
|
56
|
+
| `browser_cookie_*`, `browser_localstorage_*`, `browser_sessionstorage_*`, `browser_storage_state`, `browser_set_storage_state` | `storage` | never: saved logins are passwords (F5) |
|
|
57
|
+
| `browser_route`, `browser_unroute`, `browser_route_list`, `browser_network_state_set` | `network` | never: a case checks the real page |
|
|
58
|
+
| `browser_pdf_save` | `pdf` | a page that serves a PDF is downloaded and rendered (`pnpm pdf:render`) |
|
|
59
|
+
| `browser_mouse_move_xy`, `browser_mouse_down`, `browser_mouse_up`, `browser_mouse_wheel`, `browser_mouse_click_xy`, `browser_mouse_drag_xy` | `vision` | `browser_click` with a locator |
|
|
60
|
+
| `browser_get_config` | `config` | `pnpm status --env <env>` |
|
|
61
|
+
|
|
62
|
+
## Arguments the schema requires
|
|
63
|
+
|
|
64
|
+
A call that looks complete still fails when one of these is missing. Both come from the MCP's own schema, and both
|
|
65
|
+
answer `Missing key`:
|
|
66
|
+
|
|
67
|
+
1. **`intent` on every action, input and assertion tool.** The test runner adds it so the recorded step has a name.
|
|
68
|
+
One short sentence, imperative, about what this call is for: `intent: "open the article preview"`. It belongs on
|
|
69
|
+
`browser_navigate`, `browser_navigate_back`, `browser_click`, `browser_type`, `browser_fill_form`,
|
|
70
|
+
`browser_press_key`, `browser_hover`, `browser_drag`, `browser_drop`, `browser_select_option`,
|
|
71
|
+
`browser_file_upload`, `browser_evaluate`, `browser_run_code_unsafe`, `browser_tabs`, `browser_close`,
|
|
72
|
+
`browser_resize`, `browser_handle_dialog`, `browser_emulate_media`, and on `browser_wait_for` and every
|
|
73
|
+
`browser_verify_*`. The read-only tools (`browser_snapshot`, `browser_find`, `browser_take_screenshot`,
|
|
74
|
+
`browser_console_messages`, `browser_network_requests`, `browser_network_request`, `browser_generate_locator`) take
|
|
75
|
+
none.
|
|
76
|
+
2. **A field with a default is published as required:**
|
|
77
|
+
- `browser_take_screenshot` → `scale: "css"` (CSS pixels, the size baselines expect).
|
|
78
|
+
- `browser_network_requests` → `static: false` (data calls only).
|
|
79
|
+
- `browser_network_request` → `index` (from the numbered list).
|
|
80
|
+
- `browser_tabs` → `action` (`"list"`, `"new"`, `"select"` or `"close"`).
|
|
81
|
+
- `browser_console_messages` → `level` (`"error"` is enough).
|
|
82
|
+
- `browser_handle_dialog` → `accept` (`true` to accept, `false` to dismiss).
|
|
83
|
+
- `generator_setup_page` → `project`, `seedFile` and `plan` (`plan: ""` when there is no plan).
|
|
84
|
+
|
|
85
|
+
## Outlining a target without `browser_highlight`
|
|
86
|
+
|
|
87
|
+
`browser_highlight` is a one-line wrapper over `locator.highlight()`, and that method is public Playwright API. The
|
|
88
|
+
overlay is the real one, not an approximation, and nothing of the page is changed.
|
|
89
|
+
|
|
90
|
+
1. Outline (one call, one element, the card's `Identified by` as the locator):
|
|
91
|
+
```js
|
|
92
|
+
// browser_run_code_unsafe({intent: 'outline the target for the reference screenshot', code: ...})
|
|
93
|
+
async (page) => {
|
|
94
|
+
const target = page.getByRole('img', {name: '<the Identified by value of the card>'});
|
|
95
|
+
await target.highlight({style: 'outline: 3px solid #e11d48; outline-offset: 2px'});
|
|
96
|
+
return await target.toString();
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
2. Screenshot the page (not the element) so the reader sees where it sits:
|
|
100
|
+
`browser_take_screenshot({scale: 'css', filename: '.explore/targets/<case-id>/T<n>-<profile-id>.png'})`
|
|
101
|
+
3. Remove it before the next step: `browser_run_code_unsafe` with `intent: 'remove the highlight overlay'` and
|
|
102
|
+
`async (page) => { await page.hideHighlight(); }`.
|
|
103
|
+
|
|
104
|
+
A highlight overlay is not page state: it never appears in a snapshot, it is not part of what the case checks, and
|
|
105
|
+
`page.hideHighlight()` clears all of them. Do this for every candidate when several elements are compared, and say in
|
|
106
|
+
the card what the screenshot shows.
|
|
107
|
+
|
|
108
|
+
## Network requests without a clear
|
|
109
|
+
|
|
110
|
+
`browser_network_requests` returns a numbered list that only grows while the page stays open, so a marker works:
|
|
111
|
+
|
|
112
|
+
1. Before the action: `browser_network_requests({static: false})`, note the highest number in the list (`mark`).
|
|
113
|
+
2. Do the action.
|
|
114
|
+
3. Read `browser_network_requests({static: false})` again and take the entries above `mark`: those, and only those,
|
|
115
|
+
were started by the action. `browser_network_request({index})` gives one request's detail and status.
|
|
116
|
+
|
|
117
|
+
Record the marker as a plain note in the report (`after 12 new requests; the data call was 7`), never in the case.
|
|
@@ -29,7 +29,9 @@ that nobody watches.
|
|
|
29
29
|
copied with one click.
|
|
30
30
|
3. Never ask the user to send email (production: never; other environments: only when `email` is `true` for the
|
|
31
31
|
profile). Never ask for login or 2FA. Name everything the user creates `[asrun] <something>`.
|
|
32
|
-
4.
|
|
32
|
+
4. Do not start a recording yourself: the card starts and stops it (see "Recording"). Never call
|
|
33
|
+
`browser_start_recording` or `browser_stop_recording`: the `playwright-test` MCP lists them but cannot run them
|
|
34
|
+
(`devtools` tools; see `tools.md`).
|
|
33
35
|
|
|
34
36
|
## The wait loop
|
|
35
37
|
Run it with one `browser_run_code_unsafe` call. A single call may wait for minutes (verified: 150 s without a
|
|
@@ -63,8 +65,9 @@ inside the call that runs the card: OpenCode reports a tool call only when it en
|
|
|
63
65
|
way the web UI can see that a person is needed while the card waits. With the card alone there is no "Your turn"
|
|
64
66
|
badge, no count in the tab title and no notification (a HINT in the job log says so).
|
|
65
67
|
|
|
66
|
-
1. Set the options on the browser context (this call only sets data):
|
|
68
|
+
1. Set the options on the browser context (this call only sets data; `browser_run_code_unsafe` needs `intent`):
|
|
67
69
|
```js
|
|
70
|
+
// browser_run_code_unsafe({intent: 'set the handoff card options', code: ...})
|
|
68
71
|
async (page) => {
|
|
69
72
|
page.context().__handoffCfg = {
|
|
70
73
|
message: '<what the user does, one sentence>',
|
|
@@ -77,11 +80,16 @@ badge, no count in the tab title and no notification (a HINT in the job log says
|
|
|
77
80
|
```
|
|
78
81
|
2. Run the card and wait: `browser_run_code_unsafe` with `filename` set to the absolute path of
|
|
79
82
|
`<framework>/.claude/skills/exploring-cases/references/handoff-card.js` (use the absolute path of the repository, a
|
|
80
|
-
relative one is resolved against the workspace root, which may be the parent folder)
|
|
83
|
+
relative one is resolved against the workspace root, which may be the parent folder) and an `intent` such as
|
|
84
|
+
`'hand off step 5 to the person'`. It returns
|
|
85
|
+
`{outcome: 'done' | 'skip' | 'condition' | 'timeout', waitedMs, recording, recorded}`: `recorded` holds the
|
|
86
|
+
actions the person performed, one Playwright line each, across every tab (`page`, `page1`, ...), and is empty when
|
|
87
|
+
the recording was unavailable (`recording: false`; `recordingError` says why).
|
|
81
88
|
|
|
82
89
|
Check once, after the card shows, that it looks right: take a screenshot and see a title bar with a move icon, a
|
|
83
|
-
chevron button at its right and, when `copy` is not empty, a line per text with a copy icon.
|
|
84
|
-
|
|
90
|
+
chevron button at its right and, when `copy` is not empty, a line per text with a copy icon. Playwright's own recording
|
|
91
|
+
toolbar at the top of the page is expected and is not part of the card. If the card is not right, stop and report it;
|
|
92
|
+
do not hand off with a broken card.
|
|
85
93
|
|
|
86
94
|
Tell the user in the chat (the terminal or the UI job log) as well: "Your turn in the browser window: <task>. Press
|
|
87
95
|
Done on the card (bottom-left; drag it if it is in the way) when you finish." When the run was started from the web
|
|
@@ -105,17 +113,21 @@ The Done button is always there (also on the minimized card) as the fallback, an
|
|
|
105
113
|
for two seconds before it is removed, so the user knows when they may let go.
|
|
106
114
|
- `outcome: 'timeout'` or `'skip'`: do not guess. Write a `TODO [blocking]` under the step and go on with what does
|
|
107
115
|
not depend on it.
|
|
108
|
-
- Take a snapshot
|
|
116
|
+
- Take a snapshot, write down what the user did from the card's `recorded` (see "Recording"), then continue read-only.
|
|
109
117
|
- At the end of the exploration hand off once more when the user created data: "Delete the [asrun] items you
|
|
110
118
|
created, then press Done". The case must clean up after itself, and so must the exploration.
|
|
111
119
|
|
|
112
120
|
## Recording
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
121
|
+
The card records what the person does: it starts the recording when it shows and stops it on every way out (Done,
|
|
122
|
+
"Cannot do this", the finish condition, the timeout, an error), in the same `browser_run_code_unsafe` call. The agent
|
|
123
|
+
neither starts nor stops it. The result is the card's `recorded`: one Playwright line per action, `page` for the first
|
|
124
|
+
tab that acted and `page1`, `page2`, ... for the others. The aliases follow the order in which actions first appear,
|
|
125
|
+
not the order in which the tabs were opened, so read the actions before you describe tabs.
|
|
126
|
+
|
|
127
|
+
The recorded Playwright code is **raw material, never saved as it is**. It is turned into a general recipe and written
|
|
128
|
+
under the step as `- Recipe (phase): ...` lines (format below). The recipe holds what the user did in words, plus the
|
|
129
|
+
**labels and roles of the controls** they used ("the textbox 'Title'", "the button 'Next'"), because `/gen-spec` cannot
|
|
130
|
+
write data and so cannot see those controls itself. It never holds selectors, ids or the names of data that can change.
|
|
119
131
|
|
|
120
132
|
Make the recipe as general as possible, because lists and data change:
|
|
121
133
|
- **A choice from a list that can change** (items, search results, a contact list): record the position, never the
|
|
@@ -123,14 +135,31 @@ Make the recipe as general as possible, because lists and data change:
|
|
|
123
135
|
- **Random is allowed only when the user asks for it** ("pick any 3"). The default is the position, because it can be
|
|
124
136
|
repeated. A random pick must be written into the test's annotations when the spec runs.
|
|
125
137
|
- **Text the case creates**: `[asrun] <something>`.
|
|
126
|
-
- **A value that has to exist** (an existing name, a search term):
|
|
127
|
-
returns a result") and the observed value as `(
|
|
128
|
-
|
|
138
|
+
- **A value that has to exist** (an existing name, a search term): the recipe says what it must be like ("a name
|
|
139
|
+
that returns a result") and notes the observed value as plain text `(seen on <profile>: "<value>")`. Do not put a
|
|
140
|
+
brittle literal in as the only truth. One claim carries one signal: when a `[non-blocking]` TODO under the recipe
|
|
141
|
+
asks whether any such value is acceptable, the observed value gets **no** marker (`open-questions.md`, "Marker or
|
|
142
|
+
question, never both").
|
|
129
143
|
- **Nothing that depends on the pick** may become an assertion (a total changes with the items). Say so in a
|
|
130
144
|
TODO when a check would.
|
|
131
145
|
- Settings that the user changed on purpose (a toggle, a template) are recorded as they were set, by label.
|
|
132
146
|
|
|
133
|
-
|
|
147
|
+
The recording is raw: merge and prune it before you write the recipe.
|
|
148
|
+
- **The same action can appear twice**, and clicking a checkbox or its label can give both a `click` and a `check`.
|
|
149
|
+
Write it once ("check 'Agree'").
|
|
150
|
+
- **Side products**: clicking a textbox to focus it, `Cmd+C`, `Scroll`, `Escape`. Keep only actions that change data
|
|
151
|
+
or the page's state.
|
|
152
|
+
- **The card's own buttons are filtered out** of `recorded`. A click on the card's text (not a button) can still show
|
|
153
|
+
up; ignore it.
|
|
154
|
+
- **Not recorded at all**: drag and drop, native file pickers, OS dialogs. Describe those from before/after snapshots.
|
|
155
|
+
|
|
156
|
+
If `recording` is `false` or `recorded` is empty, do not silently write a recipe from snapshots: write a
|
|
157
|
+
`🚫 TODO [blocking]` under the step that says the step was not recorded (and why, from `recordingError` when there is
|
|
158
|
+
one). A recipe from before/after snapshots may be added next to that block only, and must say that it came from
|
|
159
|
+
snapshots, not from a recording.
|
|
160
|
+
|
|
161
|
+
If Playwright's own recording toolbar shows at the top of the page while the card waits, it is not AsRun's: leave it
|
|
162
|
+
alone.
|
|
134
163
|
|
|
135
164
|
## Recipe lines under a step
|
|
136
165
|
One or more `- Recipe (phase): ...` lines directly under the `[input]` or `[writes]` step they belong to, indented by
|
|
@@ -139,7 +168,7 @@ keep in sync.
|
|
|
139
168
|
|
|
140
169
|
```
|
|
141
170
|
3. Select an existing document and pick 3 items. [input]
|
|
142
|
-
- Recipe (start): search for an existing document and pick result #1 (
|
|
171
|
+
- Recipe (start): search for an existing document and pick result #1 (seen on example-staging: "quarterly report").
|
|
143
172
|
- ❓ TODO [non-blocking]: Is any result that opens fine as the starting point? (seen on example-staging)
|
|
144
173
|
- ✍️ Answer:
|
|
145
174
|
- Recipe (items): select 3 items by position (#1 to #3) from the list.
|
|
@@ -8,8 +8,9 @@ description: Turns a human-written test case (cases/<id>.md, the id may include
|
|
|
8
8
|
Read `references/conventions.md` first. It defines the spec shape, fixtures, assertion layers and
|
|
9
9
|
the read-only rules. Read the workspace `AGENTS.md` for the hard rules and the project rules. Read `../exploring-cases/references/open-questions.md`: it
|
|
10
10
|
defines how open questions are written into the case file, resolved and reported. Also read
|
|
11
|
+
`../exploring-cases/references/case-rules.md` (how a case's `## Rules` are read, applied and maintained),
|
|
11
12
|
`../exploring-cases/references/assertion-targets.md` (the exact thing each check looks at) and
|
|
12
|
-
`../exploring-cases/references/readiness.md` (wait for the content, verify downloads in five parts). All
|
|
13
|
+
`../exploring-cases/references/readiness.md` (wait for the content, verify downloads in five parts). All four are binding here.
|
|
13
14
|
|
|
14
15
|
This skill never stops to ask the user in the chat, because it may run without anybody present (a one-shot
|
|
15
16
|
`claude -p` / `opencode run`). This holds whatever mode or agent the run is in: plan mode changes what you may write,
|
|
@@ -36,15 +37,18 @@ and the spec, and runs only the profiles that are logged in, and only when the r
|
|
|
36
37
|
## Before you start
|
|
37
38
|
Read the workspace `AGENTS.md`: its §2 holds the project rules, and it points at the framework baseline. If
|
|
38
39
|
`.claude/skills/generating-specs.md` exists in the workspace, read it and apply it after this skill. Then apply
|
|
39
|
-
`AGENTS.md` §2, which wins over both (the baseline, "Precedence"). F1–F6 always win. This is not a stop
|
|
40
|
-
continue with the procedure below.
|
|
40
|
+
`AGENTS.md` §2, which wins over both (the baseline, "Precedence"). F1–F6 and R2 always win. This is not a stop
|
|
41
|
+
condition: continue with the procedure below.
|
|
41
42
|
|
|
42
43
|
## Inputs
|
|
43
44
|
- The case id and the environment to explore in (`staging` or `production`). There is no default. If the
|
|
44
45
|
environment is missing, stop before exploring and report `RESULT: needs-input` ("pass the environment").
|
|
45
46
|
- Optionally `--force`: regenerate the spec from scratch and overwrite the existing one. See "An existing spec".
|
|
47
|
+
- Optionally `--hints <file>` (or free text after the arguments): what the person knows, or a change they want. Read it
|
|
48
|
+
before validating the case. It guides this run; after the work, a reusable instruction is written into `## Rules`
|
|
49
|
+
by the gate in `../exploring-cases/references/case-rules.md`, and a one-off clue stays in the report.
|
|
46
50
|
- No profile selection: the profile scope is the case's (see above). A case id and an environment are all this command
|
|
47
|
-
takes besides `--force`.
|
|
51
|
+
takes besides `--force` and `--hints`.
|
|
48
52
|
|
|
49
53
|
A case id is its path below `cases/` without `.md`: `hub-landing`, or `documents/landing-page` when the case is in a
|
|
50
54
|
folder (one level only). The spec is at the same path below `tests/` (`tests/documents/landing-page.spec.ts`); both import
|
|
@@ -80,14 +84,21 @@ Decide this first, before exploring anything. The spec path is `tests/<id>.spec.
|
|
|
80
84
|
2. **Validate the case.** Collect everything that is ambiguous, every check without an expected value and every
|
|
81
85
|
missing per-profile value, including the `TODO` items already in the case. Resolve them in the order of
|
|
82
86
|
`open-questions.md`; do not stop here:
|
|
87
|
+
- **Apply the rule pass (`R1`–`R4`).** Read `## Rules` before the steps. A rule that states an expectation, a
|
|
88
|
+
literal, a target or a real step belongs in that section: move it there (or ask). A rule that conflicts with the
|
|
89
|
+
case or the page is a `[blocking]` question, never an override. A rule that would write, send email or do a
|
|
90
|
+
person's step is never followed. Details: `case-rules.md`.
|
|
83
91
|
- **Steps that write or need a choice (`[input]`, `[writes]`) need a recipe.** Each must have at least one
|
|
84
92
|
`- Recipe (phase): ...` line under it in the case (written by `/explore-case`). An old case may still hold the
|
|
85
93
|
same recipe in `## Inputs`: use that line until it is moved under the step. If one has none, the case was
|
|
86
94
|
never explored with a person: stop before opening a browser, do not write the spec, and report `RESULT:
|
|
87
95
|
needs-input` with exactly this: "step <n> has no recorded input; run `/explore-case <id> <env> --attended`
|
|
88
96
|
first". Nothing else is written.
|
|
89
|
-
- Facts the page can answer and that the case does not hold yet are read in step 4 and written into the case
|
|
90
|
-
`(
|
|
97
|
+
- Facts the page can answer and that the case does not hold yet are read in step 4 and written into the case. Mark
|
|
98
|
+
them by the gate in `open-questions.md` ("Which sentences carry a marker"): a locator, a flow or a request is
|
|
99
|
+
`(seen on <profile>)`; a literal the spec asserts gets
|
|
100
|
+
`(observed on <profile>, 🔍 to be confirmed: <what a person must decide>)`, and that is the only thing a person
|
|
101
|
+
has to settle. A spec is generated from observed values.
|
|
91
102
|
- Safe defaults are applied: the suggestion is the sentence above the TODO block, and the block stays in the case
|
|
92
103
|
as `❓ TODO [non-blocking]` with an empty `✍️ Answer:`. A sentence with no TODO block under it is confirmed text.
|
|
93
104
|
- A non-empty `Answer:` is the user's answer: it wins over the sentence above it. Fold it into the case.
|
|
@@ -111,15 +122,19 @@ Decide this first, before exploring anything. The spec path is `tests/<id>.spec.
|
|
|
111
122
|
`not verified on the page` in the report. If `pnpm status` cannot run (a sandbox error such as `listen EPERM` or a
|
|
112
123
|
Chromium start failure), judge by the page instead: a page that lands on the login page means the profile is not
|
|
113
124
|
logged in.
|
|
114
|
-
4. **Replay the case on the real page** with the `playwright-test` MCP.
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
125
|
+
4. **Replay the case on the real page** with the `playwright-test` MCP. Read `../exploring-cases/references/tools.md`
|
|
126
|
+
first: that MCP lists tools it cannot run, and a call that answers `Tool "<name>" not found` means exactly that (the
|
|
127
|
+
server is up; use the substitute). If its tools are not registered in this session at all (no `browser_*` /
|
|
128
|
+
`generator_*` tool) or it reports that it failed to start, do not fall back to ad-hoc Playwright scripts: stop, tell
|
|
129
|
+
the user which MCP server is missing and to run `pnpm asrun doctor`, write no spec, and end with
|
|
130
|
+
`RESULT: needs-input`.
|
|
118
131
|
Call `generator_setup_page` with
|
|
119
|
-
`project=<profile-id>` (for example `example-staging`)
|
|
132
|
+
`project=<profile-id>` (for example `example-staging`), `seedFile=tests/seed.spec.ts` and `plan=""`, then perform the
|
|
120
133
|
steps in the environment you were given (never ask for it; there is no default) to learn the locators.
|
|
121
|
-
Do the read-only steps as written
|
|
122
|
-
|
|
134
|
+
Do the read-only steps as written, and follow the `## Rules` that say how to prepare, wait or which control to
|
|
135
|
+
avoid. **Never perform a step that writes or needs a choice**: the agent does not write
|
|
136
|
+
data, in staging or in production, and it does not hand off to a person here or wait for one. A rule never
|
|
137
|
+
authorizes one of those steps. Write those steps
|
|
123
138
|
of the spec from the `- Recipe:` lines under the step (positions, `[asrun]` names, control labels and roles that
|
|
124
139
|
`/explore-case` recorded). Pages that only exist after such a step cannot be seen from here: write their locators
|
|
125
140
|
from what the recipe and the exploration notes say, mark them in the report as "not verified on the page", and
|
|
@@ -130,16 +145,18 @@ Decide this first, before exploring anything. The spec path is `tests/<id>.spec.
|
|
|
130
145
|
the case's `## Values`: open that profile's page only when a value or a behavior still has to be resolved for it.
|
|
131
146
|
Keep every browser call short (one action, about 60 s at most, a snapshot between steps, `{timeout: 5000}` when
|
|
132
147
|
something may not appear, no third retry): see "Keep every browser call short" in `../exploring-cases/SKILL.md`.
|
|
133
|
-
**Ready before you read, here too** (`readiness.md`):
|
|
134
|
-
(`
|
|
135
|
-
`Ready when` signals of the case are taken from what you see here.
|
|
148
|
+
**Ready before you read, here too** (`readiness.md`): take a marker on `browser_network_requests`
|
|
149
|
+
(`static: false`), act, wait for the key requests above that mark and the content signal, then snapshot. The request
|
|
150
|
+
URL patterns for `actAndWait` and the `Ready when` signals of the case are taken from what you see here.
|
|
136
151
|
**Check every target on the page:** for each target card, find its `Identified by` locator, confirm that it matches
|
|
137
|
-
exactly one element and that this element is what `What` says (
|
|
138
|
-
matches several elements or the wrong one is fixed or becomes a
|
|
152
|
+
exactly one element and that this element is what `What` says (outline it with `locator.highlight()` and look at the
|
|
153
|
+
screenshot, see `tools.md`). A locator that matches several elements or the wrong one is fixed or becomes a
|
|
154
|
+
question, never `.first()`.
|
|
139
155
|
Prefer `getByTestId` (the project uses `data-tn`), then role/label locators. No CSS chains or XPath.
|
|
140
|
-
Update the case file with what you learned: fold observations into the right section marked
|
|
141
|
-
|
|
142
|
-
and narrow the ones that remain. Keep the shape of
|
|
156
|
+
Update the case file with what you learned: fold observations into the right section, marked `(seen on <profile>)`
|
|
157
|
+
or, for a literal the spec asserts, `(observed on <profile>, 🔍 to be confirmed: <what to decide>)`; remove the
|
|
158
|
+
TODO blocks they answer (rewrite the sentence above the block), and narrow the ones that remain. Keep the shape of
|
|
159
|
+
`open-questions.md` for every block, and never put a marker inside one. Edit the case
|
|
143
160
|
with targeted edits; never touch its `## Source` table.
|
|
144
161
|
5. **Complete the case's `## Values`**: for every profile the case holds information for, add the entry with the
|
|
145
162
|
literals the spec asserts (the same keys as `default`, only what differs); put a literal that holds for every profile
|
|
@@ -154,7 +171,10 @@ Decide this first, before exploring anything. The spec path is `tests/<id>.spec.
|
|
|
154
171
|
(`@asrunjs/asrun/values`), which merges `default` with the running profile's entry.
|
|
155
172
|
Every action that loads something goes through `actAndWait` (`@asrunjs/asrun/ready`) with the key requests and the ready
|
|
156
173
|
signal; every download goes through `verifyDownload` (`@asrunjs/asrun/downloads`); every check on a target has
|
|
157
|
-
`// target: T<n>` on the line before it; every absence check has a `// ready: <signal>` before it
|
|
174
|
+
`// target: T<n>` on the line before it; every absence check has a `// ready: <signal>` before it; code that exists
|
|
175
|
+
only because of a `## Rules` bullet carries `// rule: <short quote>` before it; an assertion whose literal comes
|
|
176
|
+
from a `🔍 to be confirmed` line carries `// unconfirmed: <that case sentence>` before it, so
|
|
177
|
+
`pnpm markers:confirm` can settle it from a passing run. Never
|
|
158
178
|
`waitForTimeout`, `networkidle` or `.first()` to pick an image. A confirmation or modal that may or may not appear is
|
|
159
179
|
never decided by one `isVisible()` look (its `timeout` option is ignored): use `clickOptionalUntilHidden`
|
|
160
180
|
(`@asrunjs/asrun/ready`) or a waiting `expect(...).toBeVisible({timeout})`; see `readiness.md`, "Optional confirmations".
|
|
@@ -170,12 +190,16 @@ Decide this first, before exploring anything. The spec path is `tests/<id>.spec.
|
|
|
170
190
|
8. **Report** in the user's language: spec path, **an assertion table** (step, what is asserted, target card, ready signal, and
|
|
171
191
|
`observed on the page` or `not verified`; no row may be missing), profiles explored, profiles skipped (and why),
|
|
172
192
|
the profiles of `profiles.yaml` the case holds no information for (they run on `default` alone), whether the case file was
|
|
173
|
-
translated, what was added to the case file as
|
|
174
|
-
|
|
175
|
-
the
|
|
193
|
+
translated, what was added to the case file as `(seen on <profile>)` and which lines still need a person
|
|
194
|
+
(`🔍 to be confirmed`, with the reason), checks generated from observations, visual checks that need a baseline on
|
|
195
|
+
the first run, and the remaining TODO blocks (`[blocking]` ones with
|
|
196
|
+
the sentence that deleting the block would confirm). Add the rule table of `case-rules.md`: every `## Rules`
|
|
197
|
+
bullet, where it was applied or why it was not, and which rules were added from `--hints`. State that the spec was
|
|
198
|
+
not run and give the command to run it:
|
|
176
199
|
`pnpm asrun --env <env> --profile <ids> --grep <id>` (with `--update-snapshots` once if the spec has visual
|
|
177
200
|
checks, then run `pnpm baselines:review <id> --new` and open every new image: it must show what its target card says). The last line is `RESULT: ready` (no `[blocking]` question left, spec
|
|
178
|
-
written and the checks above pass) or `RESULT: needs-input`, as defined in `open-questions.md
|
|
201
|
+
written and the checks above pass) or `RESULT: needs-input`, as defined in `open-questions.md`: plain text on the
|
|
202
|
+
last line, never inside a code fence or in emphasis.
|
|
179
203
|
|
|
180
204
|
## Cases that write data
|
|
181
205
|
Only when the case front matter lists environments under `writes` (for example `writes: [staging]`). Never add
|
|
@@ -197,7 +221,8 @@ reason to do nothing when the user asked for something.
|
|
|
197
221
|
Heal from evidence, not from a habit of rerunning. A report that already exists says what failed.
|
|
198
222
|
|
|
199
223
|
1. **Read the user's request first** (the `--hints` file, or the free text). It says what to repair or improve and may
|
|
200
|
-
hold evidence the report lacks.
|
|
224
|
+
hold evidence the report lacks. Read the case's `## Rules` before the steps and apply the rule pass (`R1`–`R4`,
|
|
225
|
+
`../exploring-cases/references/case-rules.md`). Decide which kind of work it is:
|
|
201
226
|
- a failure to repair (the last run failed, or the user names one);
|
|
202
227
|
- an improvement, even when the last run passed: a check that is too loose, a step, expectation, value or target
|
|
203
228
|
that no longer matches the page, wording a person cannot follow;
|
|
@@ -228,7 +253,9 @@ Heal from evidence, not from a habit of rerunning. A report that already exists
|
|
|
228
253
|
improvement request with no failure behind it. Use the `playwright-test` MCP exactly as `/gen-spec` step 4 describes
|
|
229
254
|
(`generator_setup_page` with `project=<profile-id>`, one action per call, ready before you read). Do the read-only
|
|
230
255
|
steps only: **never perform a step that writes or needs a choice**, in any environment. What you see goes into the
|
|
231
|
-
case
|
|
256
|
+
case by the gate in `open-questions.md` ("Which sentences carry a marker"): `(seen on <profile>)` for a locator, a
|
|
257
|
+
flow or a request, and `(observed on <profile>, 🔍 to be confirmed: <what a person must decide>)` only for a
|
|
258
|
+
literal the spec asserts, so the person settles that in the review gate below.
|
|
232
259
|
5. **Audit the assertion before you blame anyone.** Read the `assertion-targets.md` and `readiness.md` sections on healing.
|
|
233
260
|
- Is the locator still what the target card says (`What`, `Identified by`)? Open the failure screenshot and the
|
|
234
261
|
baseline (`pnpm baselines:review <case-id>`) and look at each: a baseline that shows something other than the card
|
|
@@ -250,12 +277,22 @@ Heal from evidence, not from a habit of rerunning. A report that already exists
|
|
|
250
277
|
more than a sentence (a new or removed step, different expected values, another target), regenerate the spec from
|
|
251
278
|
the case as "## Procedure" steps 5–6 describes instead of patching it; otherwise edit it in place. Say in the
|
|
252
279
|
report which of the two you did.
|
|
280
|
+
- A reusable instruction that is method, not an expectation: append it to `## Rules` under the four-question gate
|
|
281
|
+
of `case-rules.md`, in general terms, after checking that it is not already there. An agent-inferred rule
|
|
282
|
+
carries a marker only when it changes what is asserted; a method rule is `(seen on <profile>)` and a rule the
|
|
283
|
+
person stated carries nothing. A one-off request (this failure, this
|
|
284
|
+
account, this run) stays in the report. Never put a write, an email, a person's step, an expectation or a literal
|
|
285
|
+
into `## Rules`.
|
|
253
286
|
7. **Stop at the review gate.** After editing the case, read it back. If **this run** left a `🚫 TODO [blocking]` or
|
|
254
|
-
`❓ TODO [non-blocking]` block, or a `🔍 to be confirmed
|
|
287
|
+
`❓ TODO [non-blocking]` block, or a `🔍 to be confirmed` or `👁️ not observed` marker, do not edit or
|
|
255
288
|
regenerate the spec any further: a case that still asks something is not a source for a spec. Report exactly what
|
|
256
289
|
to review, that answering or deleting those blocks comes first, and that the next command is
|
|
257
|
-
`/explore-case <case-id> <env>` (with `--attended` when a step writes data)
|
|
258
|
-
|
|
290
|
+
`/explore-case <case-id> <env>` (with `--attended` when a step writes data), or
|
|
291
|
+
`pnpm markers:confirm --case <case-id> --env <env>` when a passing run already checks those lines. End with
|
|
292
|
+
`RESULT: needs-input`. Blocks and markers that were already in the case before this run and that it did not touch do
|
|
293
|
+
not stop it. A plain `(seen on <profile>)` note never stops it. One exception:
|
|
294
|
+
a `🔍 to be confirmed` marker inside `## Rules` does not stop the spec work unless the rule covers a literal the
|
|
295
|
+
spec asserts (a `🚫`/`❓` TODO block under a rule still does).
|
|
259
296
|
8. **After editing the spec, run it once to confirm** (same command as in step 3, for the profiles you healed). That rerun
|
|
260
297
|
is the only one that is always allowed. Run `pnpm typecheck`, `pnpm spec:lint <case-id>` and `pnpm case:check`, and
|
|
261
298
|
fix every failure of the checks you touched. An improvement may make the run fail on purpose: a tightened check that
|
|
@@ -264,8 +301,9 @@ Heal from evidence, not from a habit of rerunning. A report that already exists
|
|
|
264
301
|
whether the case was run again and why (or why not), what the request asked for and what came of it, what was
|
|
265
302
|
changed in the case and in the spec (and whether the spec was regenerated), what is a product finding (with paths),
|
|
266
303
|
which assertions were wrong or raced the page, which baselines are wrong and were deleted, what the user's request
|
|
267
|
-
contributed,
|
|
268
|
-
|
|
304
|
+
contributed, the rule table of `case-rules.md` (every `## Rules` bullet, where it was applied or why it was not,
|
|
305
|
+
and which rules this run added or changed), which profiles were left out because they are not logged in, and the
|
|
306
|
+
result of the confirming run. The last line is `RESULT: ready` (nothing left for a person, the spec changed and its checks pass)
|
|
269
307
|
or `RESULT: needs-input` (the case waits for a review, as in step 7, or a person has to decide something). A run
|
|
270
308
|
that could not write files at all (plan mode, a read-only sandbox, a refused permission) is `needs-input` too:
|
|
271
309
|
never end with a plan and a question instead.
|
|
@@ -108,6 +108,10 @@ Binding rules are in `../exploring-cases/references/readiness.md` and `.../asser
|
|
|
108
108
|
- A check on a logo, image, file or screenshot has `// target: T<n>` on the line before it (a card in the case's
|
|
109
109
|
`## Targets`). The element is found by the card's `Identified by` (role and name, test id, alt/title), never `.first()` or
|
|
110
110
|
a bare `img`/`svg`. Semantic assertion first (name/alt/src, where it is), pixels second, on that element only.
|
|
111
|
+
- An assertion whose literal comes from a `🔍 to be confirmed` line in the case carries
|
|
112
|
+
`// unconfirmed: <that case sentence>` before it (one line, the sentence without its marker). It is what
|
|
113
|
+
`pnpm markers:confirm --case <id> --env <env>` reads after a passing run to settle the marker, so the line must be
|
|
114
|
+
the case's own wording, not a paraphrase.
|
|
111
115
|
- A check for absence ("no", "missing", "empty", "hidden") has `// ready: <signal>` in front of it and a positive wait
|
|
112
116
|
before it. The page must have loaded before "nothing" means anything.
|
|
113
117
|
- A download is `await verifyDownload(page, () => option.click(), {kind: 'pdf'})`. It checks, and fails the step on
|