@asrunjs/asrun 0.10.0 → 0.11.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/skills/exploring-cases/SKILL.md +45 -58
- package/.claude/skills/exploring-cases/references/notes.md +87 -0
- package/.claude/skills/exploring-cases/references/readiness.md +3 -2
- package/.claude/skills/exploring-cases/references/tools.md +34 -0
- package/.claude/skills/exploring-cases/references/user-handoff.md +1 -1
- package/.claude/skills/generating-specs/SKILL.md +44 -37
- package/AGENTS.md +10 -9
- package/README.md +6 -5
- package/bin/asrun.mjs +1 -0
- package/dist/lib/explore-notes.d.ts +74 -0
- package/dist/lib/explore-notes.js +242 -0
- package/dist/lib/paths.d.ts +2 -0
- package/dist/lib/paths.js +2 -0
- package/dist/scripts/cli.js +9 -2
- package/dist/scripts/explore-notes.d.ts +1 -0
- package/dist/scripts/explore-notes.js +131 -0
- package/docs/agents/case-format.md +1 -0
- package/docs/agents/framework-dev.md +4 -0
- package/docs/ai-workflow.md +9 -1
- package/docs/cases.md +4 -4
- package/docs/commands.md +11 -0
- package/docs/theme-guide.html +56 -0
- package/docs/web-ui.md +30 -9
- package/package.json +2 -1
- package/templates/package.json +1 -0
- package/ui/dist/assets/{dist-BmWaNjqO.js → dist--BXJInTq.js} +1 -1
- package/ui/dist/assets/{dist-2msWI-5M.js → dist-BMmNFIUH.js} +1 -1
- package/ui/dist/assets/{dist-sfZKBSMm.js → dist-BXNaYHeh.js} +1 -1
- package/ui/dist/assets/{dist-BVvHVJ3N.js → dist-ByLbEOlF.js} +1 -1
- package/ui/dist/assets/{dist-e-KhTWHJ.js → dist-C9ra6_BD.js} +1 -1
- package/ui/dist/assets/{dist-4U_gPHXf.js → dist-CK8mow8u.js} +1 -1
- package/ui/dist/assets/{dist-BwNz_xW2.js → dist-CQS0So-i.js} +1 -1
- package/ui/dist/assets/{dist-J36xayOv.js → dist-CTrwRc3Q.js} +1 -1
- package/ui/dist/assets/{dist-BMfJatHo.js → dist-DTYj2vUD.js} +1 -1
- package/ui/dist/assets/{dist-wjB7g6OJ.js → dist-mV0_nQZe.js} +1 -1
- package/ui/dist/assets/{index-CGbRJT3b.js → index-BzMqkmQ9.js} +41 -41
- package/ui/dist/assets/index-DcJR-8Mt.css +2 -0
- package/ui/dist/index.html +2 -2
- package/ui/dist/assets/index-CI0i0LWc.css +0 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: exploring-cases
|
|
3
|
-
description: Explores the real pages for one case document (cases/<id>.md) before its spec is generated, for every logged-in profile or only the ones named with --profiles. Checks the case against profiles.yaml and the pages, fixes wording mistakes, records what the pages show, turns unknowns into answerable TODO blocks, and, in an attended run, lets the person do the steps that write data or need a choice while it waits and records a general recipe under that step's `- Recipe:` lines. Ends with RESULT ready or needs-input. Use when asked to explore, review, check, clean up or narrow down a case, or for /explore-case. Its open-question rules are shared with generating-specs.
|
|
3
|
+
description: Explores the real pages for one case document (cases/<id>.md) before its spec is generated, for every logged-in profile or only the ones named with --profiles. Checks the case against profiles.yaml and the pages, fixes wording mistakes, records what the pages show, turns unknowns into answerable TODO blocks, and, in an attended run, lets the person do the steps that write data or need a choice while it waits and records a general recipe under that step's `- Recipe:` lines. Also writes a local exploration note (locators and data-call paths) under `.explore/` for `/gen-spec` to verify instead of rediscovering. Ends with RESULT ready or needs-input. Use when asked to explore, review, check, clean up or narrow down a case, or for /explore-case. Its open-question rules are shared with generating-specs.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Exploring a case document
|
|
@@ -9,8 +9,10 @@ Read the workspace `AGENTS.md` (hard rules and project rules), `cases/_template.
|
|
|
9
9
|
(how a case's `## Rules` are read, applied and maintained), `references/open-questions.md` (how
|
|
10
10
|
questions are written, resolved and reported), `references/assertion-targets.md` (which exact image, text, file or
|
|
11
11
|
place a check looks at, and when to ask instead of picking), `references/readiness.md` (wait for the content before
|
|
12
|
-
you read it)
|
|
13
|
-
|
|
12
|
+
you read it), `references/tools.md` (the browser tools this MCP can run, and the substitutes for the ones it cannot),
|
|
13
|
+
`references/notes.md` (the local exploration note `/gen-spec` reads) and `references/user-handoff.md` (how a person works
|
|
14
|
+
in the browser while the agent waits, and how it is recorded). All but the last are binding for `/gen-spec` and healing
|
|
15
|
+
too.
|
|
14
16
|
|
|
15
17
|
## Before you start
|
|
16
18
|
Read the workspace `AGENTS.md`: its §2 holds the project rules, and it points at the framework baseline. If
|
|
@@ -23,14 +25,14 @@ Three steps, each learns one thing:
|
|
|
23
25
|
1. `/make-case` (making-cases): the **intent**. Turns raw notes into case documents. No browser.
|
|
24
26
|
2. `/explore-case` (this skill): the **facts and the inputs**. What the pages really show, and what a person has to
|
|
25
27
|
do for the steps that write or need a choice. The only step where a person and the agent work together.
|
|
26
|
-
3. `/gen-spec` (generating-specs): the **implementation**. Replays the case
|
|
27
|
-
writes the spec. It does not judge the case again.
|
|
28
|
+
3. `/gen-spec` (generating-specs): the **implementation**. Replays the case, its step recipes and the exploration note to
|
|
29
|
+
find locators and writes the spec. It does not judge the case again.
|
|
28
30
|
|
|
29
31
|
The purpose of this skill is to leave a case that can be replayed without anybody: no ambiguity, every fact marked
|
|
30
32
|
as observed, every input step described as a general recipe.
|
|
31
33
|
|
|
32
|
-
This skill edits `cases/<id>.md`
|
|
33
|
-
under `.auth/`.
|
|
34
|
+
This skill edits `cases/<id>.md` and writes its local exploration note under `.explore/notes/`. It never edits
|
|
35
|
+
`profiles.yaml`, `tests/`, the framework folder or anything under `.auth/`.
|
|
34
36
|
**The agent never writes data**, in staging or in production: a person does every step that writes or needs a
|
|
35
37
|
choice (see `references/user-handoff.md`).
|
|
36
38
|
|
|
@@ -77,22 +79,14 @@ one exception is a handoff in an attended run, where waiting for the person is t
|
|
|
77
79
|
3. **Repair the wording** (edit the file). Fix typos and grammar, make steps one action or one check each, put
|
|
78
80
|
assumptions in `## Notes` (not in `## Expected`), remove duplicates and template placeholders, make paths relative
|
|
79
81
|
to the app. Do not add checks the case did not ask for and do not drop any. Never edit the `## Source` table.
|
|
80
|
-
4. **Explore**. The exploration runs through the `playwright-test` MCP only
|
|
81
|
-
first browser call
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
Run `pnpm status --env <env>`. If it cannot run (a sandbox error such as
|
|
89
|
-
`listen EPERM` or a Chromium start failure), do not give up: open the profile's page through the MCP, and a page
|
|
90
|
-
that lands on the login page means the profile is not logged in. Explore each profile `--profiles` names that is
|
|
91
|
-
logged in, and, without `--profiles`, at least one logged-in profile per domain, with the `playwright-test` MCP
|
|
92
|
-
(`generator_setup_page` with `project=<profile-id>`, `seedFile=tests/seed.spec.ts` and `plan=""`). Never explore a
|
|
93
|
-
profile the selection leaves out, and never add a profile to fill
|
|
94
|
-
a gap. Profiles that are selected but not logged in are skipped and named in the report; a human runs
|
|
95
|
-
`pnpm signin <profile-id>`.
|
|
82
|
+
4. **Explore**. The exploration runs through the `playwright-test` MCP only: read `references/tools.md` before the
|
|
83
|
+
first browser call and follow its "Starting a session" and "Short calls" sections. If the tools are not registered
|
|
84
|
+
in this session at all (no `browser_*` / `generator_*` tool) or the server reports that it failed to start, do not
|
|
85
|
+
fall back to ad-hoc Playwright scripts: stop, tell the user which MCP server is missing and to run `pnpm asrun
|
|
86
|
+
doctor`, make no observations, and end with `RESULT: needs-input`.
|
|
87
|
+
Explore each profile `--profiles` names that is logged in, and, without `--profiles`, at least one logged-in profile
|
|
88
|
+
per domain. Never explore a profile the selection leaves out, and never add a profile to fill a gap. Profiles that
|
|
89
|
+
are selected but not logged in are skipped and named in the report; a human runs `pnpm signin <profile-id>`.
|
|
96
90
|
- Do the read-only steps yourself: open, look, read, hover, switch tabs, open and close dialogs and menus.
|
|
97
91
|
- **A step that writes or needs a choice is done by the person**, per `references/user-handoff.md`: announce it,
|
|
98
92
|
name the finish condition, run the wait loop (the card records what the person does), then write down what
|
|
@@ -109,38 +103,23 @@ one exception is a handoff in an attended run, where waiting for the person is t
|
|
|
109
103
|
avoid is followed on every profile. Re-check each rule against the page: one that no longer holds gets a TODO
|
|
110
104
|
with evidence under it, never silent deletion or rewriting. A rule never lets you write data or do a person's
|
|
111
105
|
step.
|
|
112
|
-
- **Ready before you read
|
|
113
|
-
`
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
it
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
place or UI wanted and lists the candidates. In an attended run let the person name the element in the handoff
|
|
126
|
-
card, then outline what they named (`browser_annotate` is not runnable here).
|
|
127
|
-
- **Downloads and email composers are opened for real** (the composer is closed with "Discard"). A download is
|
|
128
|
-
verified in five parts: starts, finishes, file exists, file valid, and a failure or timeout fails the check
|
|
129
|
-
(see `references/readiness.md`). Record the file name, size and kind as observed. A downloaded PDF is rendered
|
|
130
|
-
(`pnpm pdf:render`) and its page images are opened and looked at: record which pages show the target, where, and
|
|
131
|
-
the text. Which pages the check covers follows the case: every page unless the case says ONLY (strong wording).
|
|
106
|
+
- **Ready before you read**: follow `references/readiness.md`, "Exploring". Record the signal it proves as a plain
|
|
107
|
+
note, `(seen on <profile>, after <signal>)`: the spec waits for it, so no person has to confirm it. When the first
|
|
108
|
+
read is empty or missing something, wait and read again before you record or ask anything.
|
|
109
|
+
- **Targets, not guesses**: follow `references/assertion-targets.md`, "How a card is made (exploring)". List ALL
|
|
110
|
+
candidates, outline the one that is meant, take the screenshot to `.explore/targets/<case-id>/`, **open it and look
|
|
111
|
+
at it**, clear the highlight and write the card under `## Targets`. When candidates cannot be told apart, or only a
|
|
112
|
+
position, a generic locator or dynamic data would decide it, write a `[blocking]` TODO that names the exact image,
|
|
113
|
+
text, place or UI wanted and lists the candidates. In an attended run let the person name the element in the
|
|
114
|
+
handoff card, then outline what they named (`browser_annotate` is not runnable here).
|
|
115
|
+
- **Downloads and email composers are opened for real** (the composer is closed with "Discard"). Verify a download
|
|
116
|
+
in the five parts of `references/readiness.md`, "Downloads", and record the file name, size and kind. Render a
|
|
117
|
+
downloaded PDF (`pnpm pdf:render`), open its page images and record which pages show the target, where, and the
|
|
118
|
+
text; the pages follow the case (every page unless the case says ONLY).
|
|
132
119
|
- Compare every step with what is really there.
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
click, wait for a dialog and read it in one call: split it, and read the page (`browser_snapshot`) between the
|
|
137
|
-
steps. Only the handoff wait loop may take minutes.
|
|
138
|
-
- The browser times out an action or a lookup after 15 s (the config sets it). Pass a shorter `{timeout: 5000}` when
|
|
139
|
-
you wait for something that may not appear, and use `.catch()` or `isVisible()` for a one-time look while exploring only.
|
|
140
|
-
`isVisible()` answers for this instant and ignores any `timeout` passed to it, so after an action take a snapshot or
|
|
141
|
-
wait for a signal first. Never copy this pattern into a spec (see `references/readiness.md`, "Optional confirmations").
|
|
142
|
-
- After a click that should open something, take a snapshot and see what opened before you wait for it by role. A
|
|
143
|
-
dialog that did not appear is a finding (what did open instead?), not something to wait for or retry.
|
|
120
|
+
- **Record the exploration note** for this profile as you go, in the shape of `references/notes.md`: the locator of
|
|
121
|
+
each step's main control, the data-call paths of each loading action, and each target it confirmed. It is local
|
|
122
|
+
cache under `.explore/`, never case content, and `/gen-spec` reads it; step 8 writes the file.
|
|
144
123
|
- A step that fails or times out twice is not tried a third time: write what you saw, add a `TODO [blocking]` under
|
|
145
124
|
the step and go on with the next one.
|
|
146
125
|
5. **Write the `## Targets` section and the ready signals.** One card per target, in the format of
|
|
@@ -162,12 +141,18 @@ one exception is a handoff in an attended run, where waiting for the person is t
|
|
|
162
141
|
the rules: apply what holds, move what belongs in another section, and leave a question under a rule that conflicts
|
|
163
142
|
or is obsolete. Then apply the `--hints` gate of `references/case-rules.md`: a reusable, general instruction goes
|
|
164
143
|
into `## Rules` (with a marker only when it changes what is asserted); a one-off clue stays in the report.
|
|
165
|
-
8. **
|
|
166
|
-
|
|
144
|
+
8. **Write the exploration note, then check.** After the case edits are done (so `caseSha256` is the case's final hash;
|
|
145
|
+
`pnpm asrun explore:notes <case-id> --hash`), write `.explore/notes/<case-id>/<profile-id>.json` for every profile
|
|
146
|
+
explored in this run, in the shape of `references/notes.md` and with only what was seen. Then run `pnpm case:check`
|
|
147
|
+
(it fails on an incomplete target card, a rule that names a missing step, and warns on a check that has none) and
|
|
148
|
+
`pnpm asrun explore:notes <case-id> --check` (it fails on a malformed note and warns on a stale one; the summary
|
|
149
|
+
line of every profile you explored must say `fresh`), and fix every FAIL. If
|
|
150
|
+
`case:check` fails with a sandbox error (`listen EPERM`), run
|
|
167
151
|
`node --import tsx <framework>/scripts/check-cases.ts` instead; it checks the same thing.
|
|
168
152
|
9. **Report** in the user's language: what was changed (wording fixes, observations added, TODOs closed or narrowed),
|
|
169
153
|
what was explored (profile ids), which profiles `--profiles` left out (and that a run with them covers the rest),
|
|
170
|
-
which steps the person did and what was recorded for them, what
|
|
154
|
+
which steps the person did and what was recorded for them, what the exploration note covers (and what it does not),
|
|
155
|
+
what was skipped and
|
|
171
156
|
why, the target cards written (and which ones are still questions), the steps whose first read was too early,
|
|
172
157
|
the steps that could not be observed (and why), the questions that remain
|
|
173
158
|
with the sentence that deleting each block would confirm, the markers with their reason and whether each changes an
|
|
@@ -177,7 +162,9 @@ one exception is a handoff in an attended run, where waiting for the person is t
|
|
|
177
162
|
requires: plain text on the last line, never inside a code fence or in emphasis.
|
|
178
163
|
|
|
179
164
|
## Hard rules
|
|
180
|
-
- Edit `cases/<id>.md` only. Do not touch the `## Source` table.
|
|
165
|
+
- Edit `cases/<id>.md` only. Do not touch the `## Source` table. The exploration note under `.explore/notes/` is the
|
|
166
|
+
other file this run writes, and a locator never moves from there into the case.
|
|
167
|
+
- Never put a cookie, a token, a header or a URL with a query string into a note (`references/notes.md`).
|
|
181
168
|
- A `## Rules` bullet is a person's instruction: never delete it or change what it says, and never treat it as
|
|
182
169
|
permission to write data, send email or do a person's step (`R1`–`R4`, `references/case-rules.md`).
|
|
183
170
|
- The agent never writes data (see above). Never add `--allow-writes`, never set `ASRUN_ALLOW_WRITES`.
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# The exploration note: what a run saw, in a form `/gen-spec` can read
|
|
2
|
+
|
|
3
|
+
Local, git-ignored and optional. `exploring-cases` writes it; `generating-specs` (and healing) read it to **verify**
|
|
4
|
+
instead of rediscovering the page. It is not part of the case and never becomes part of it: expected values stay in
|
|
5
|
+
`## Values`, behaviour in `## Expected`, ready signals in `Ready when:`, and assertions never appear here.
|
|
6
|
+
|
|
7
|
+
## Where it lives
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
.explore/notes/<case-id>/<profile-id>.json
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
One file per profile. A `--profiles a,b` run writes only `a.json` and `b.json`; a later run with `c` adds `c.json`
|
|
14
|
+
next to them, so nothing is merged and an earlier observation is never overwritten. A fresh checkout has no notes, and
|
|
15
|
+
that is a normal state: generation falls back to replaying the case.
|
|
16
|
+
|
|
17
|
+
## The shape
|
|
18
|
+
|
|
19
|
+
```json
|
|
20
|
+
{
|
|
21
|
+
"version": 1,
|
|
22
|
+
"caseId": "conduit/header-navigation",
|
|
23
|
+
"profile": "example-staging",
|
|
24
|
+
"env": "staging",
|
|
25
|
+
"caseSha256": "<pnpm asrun explore:notes <case-id> --hash>",
|
|
26
|
+
"capturedAt": "2026-11-09T10:20:31Z",
|
|
27
|
+
"steps": {
|
|
28
|
+
"2": {
|
|
29
|
+
"url": "/",
|
|
30
|
+
"locators": [{"purpose": "the header navigation link", "locator": "page.getByRole('link', {name: 'Home'})"}],
|
|
31
|
+
"requests": ["/api/articles"]
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"targets": {"T1": {"locator": "page.getByRole('img', {name: 'Example'})", "matches": 1}}
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
- Keys of `steps` are the case's step numbers, keys of `targets` are its card ids (`T1`).
|
|
39
|
+
- `steps` may be `{}` and `targets` may be omitted; every other key is required (`env` and `updatedAt` are optional).
|
|
40
|
+
No other field is accepted.
|
|
41
|
+
- `caseSha256` is the 64-character hash `pnpm asrun explore:notes <case-id> --hash` prints (not the placeholder above).
|
|
42
|
+
- `pnpm asrun explore:notes <case-id> --check` validates the files: it fails on a malformed note and warns on a note
|
|
43
|
+
whose `caseSha256` no longer matches the case (the summary already prints it as `stale`; re-run the exploration, or
|
|
44
|
+
delete the note and let the case be replayed). A file that fails is ignored by `/gen-spec`.
|
|
45
|
+
|
|
46
|
+
## What goes in
|
|
47
|
+
|
|
48
|
+
`steps.<n>`
|
|
49
|
+
- `url`: the path the step runs on, relative to `baseURL` (`/documents/`), when it is not the current page.
|
|
50
|
+
- `locators`: the element the step acts on, not every element on the page. `purpose` says what it is in words; `locator`
|
|
51
|
+
is exactly what `browser_generate_locator` returned. Prefer the stable controls (`getByRole`, `getByTestId`,
|
|
52
|
+
`getByLabel`) and one entry per action.
|
|
53
|
+
- `requests`: the data-call paths the step's action started, taken from `browser_network_requests` above the marker
|
|
54
|
+
(`tools.md`, "Network requests without a clear"). Path only: `/api/articles`, never a host, a query string or a token.
|
|
55
|
+
Images, fonts and analytics are not data calls.
|
|
56
|
+
|
|
57
|
+
`targets.T1`
|
|
58
|
+
- `locator`: the card's `Identified by`, confirmed on the page.
|
|
59
|
+
- `matches`: how many elements it matched — `1` for a completed card. A card with open doubts gets no entry.
|
|
60
|
+
|
|
61
|
+
## What never goes in
|
|
62
|
+
|
|
63
|
+
- Expected values, counts, texts a check asserts: they belong in the case's `## Values` and `## Expected`.
|
|
64
|
+
- Ready signals: they belong in `Ready when:` (the note holds the request paths, not the promise).
|
|
65
|
+
- Assertions, test steps or a copy of the spec.
|
|
66
|
+
- Cookies, headers, storage state, tokens or a URL with a query string. `.explore/` is local, but a note is still not a
|
|
67
|
+
place for a credential (`F5`).
|
|
68
|
+
- A selector or an id that a future run would copy into `cases/*.md`. The case stays implementation-free; the note is
|
|
69
|
+
the only place a locator lives.
|
|
70
|
+
|
|
71
|
+
## Lists and changing data
|
|
72
|
+
|
|
73
|
+
A list, a row or a search result is recorded by its container and position (`page.getByRole('row').nth(0)`), never by
|
|
74
|
+
the name of the item, because the data changes between runs. The same rule as a recipe (`user-handoff.md`).
|
|
75
|
+
|
|
76
|
+
## Freshness and the write-back
|
|
77
|
+
|
|
78
|
+
- `caseSha256` is the sha256 of `cases/<case-id>.md` at the moment the note was written. If the case changes after that,
|
|
79
|
+
the note is **stale**: `/gen-spec` replays the case and does not use it.
|
|
80
|
+
- `/gen-spec` confirms an entry of a fresh note on the page and may write the verified locator or request back
|
|
81
|
+
(`updatedAt` gets the time). It never adds a step or a target, and it never touches a stale note.
|
|
82
|
+
- Deleting `.explore/notes/` is always safe: generation then re-derives everything from the page.
|
|
83
|
+
|
|
84
|
+
## Who reads it
|
|
85
|
+
|
|
86
|
+
Both skills read this file and `tools.md`, `readiness.md` and `assertion-targets.md`; the details of a browser call are
|
|
87
|
+
in `tools.md`, never repeated here.
|
|
@@ -63,8 +63,9 @@ await expectImageLoaded(logo);
|
|
|
63
63
|
|
|
64
64
|
- Register the response wait **before** the action (`actAndWait` does).
|
|
65
65
|
- Every action that opens a dialog, a tab, a menu with data, a list or a preview waits for its key requests and its content
|
|
66
|
-
signal. The request URL patterns come from `
|
|
67
|
-
seen (the action was not run), say
|
|
66
|
+
signal. The request URL patterns come from the exploration note (`notes.md`) or, when it holds none, from
|
|
67
|
+
`browser_network_requests` during this replay; never from guesses. If none was seen (the action was not run), say
|
|
68
|
+
"not verified on the page" in the report.
|
|
68
69
|
- Never `waitForTimeout`, never `networkidle`. They wait for time or for silence, not for the content. `pnpm spec:lint` fails both.
|
|
69
70
|
- Never `if (await x.isVisible()) ...` to decide something that may not have loaded yet. Wait for a ready signal, then branch.
|
|
70
71
|
Where an element is genuinely optional, say what proves the page finished (`// ready: ...`) before looking for it.
|
|
@@ -23,6 +23,37 @@ substitute below instead, and say in the report which substitute you used.
|
|
|
23
23
|
`pnpm asrun doctor` prints how many tools the installed Playwright actually runs and warns when this file names one
|
|
24
24
|
it cannot (it happens when Playwright changes the filter; the file then needs the same edit).
|
|
25
25
|
|
|
26
|
+
## Starting a session
|
|
27
|
+
|
|
28
|
+
Both `exploring-cases` and `generating-specs` (and healing) drive the browser through this MCP, one profile at a time.
|
|
29
|
+
|
|
30
|
+
1. Check the session with `pnpm status --env <env>` (or `pnpm status` when the environment is the profile's). It can fail
|
|
31
|
+
with a sandbox error (`listen EPERM`, a Chromium start failure): then open the profile's page and judge by the login
|
|
32
|
+
page instead.
|
|
33
|
+
2. Start the session with `generator_setup_page` (`project=<profile-id>`, `seedFile=tests/seed.spec.ts`, `plan=""`).
|
|
34
|
+
Every later call belongs to that profile.
|
|
35
|
+
3. A profile that is not logged in is skipped and named in the report; a human runs `pnpm signin <profile-id>`. Never
|
|
36
|
+
explore a profile a selection leaves out (`--profiles` in exploration), and never add one to fill a gap.
|
|
37
|
+
|
|
38
|
+
If no `browser_*` / `generator_*` tool is registered at all, or the server reports that it failed to start, stop: tell
|
|
39
|
+
the user which MCP server is missing and to run `pnpm asrun doctor`, write nothing, and end with `RESULT: needs-input`.
|
|
40
|
+
|
|
41
|
+
## Short calls
|
|
42
|
+
|
|
43
|
+
A call that hangs is the normal failure mode of this MCP, so keep every call small.
|
|
44
|
+
|
|
45
|
+
- One `browser_run_code_unsafe` call does one thing and finishes within about 60 seconds. Do not close a tab, click,
|
|
46
|
+
wait for a dialog and read it in one call: split it and take a `browser_snapshot` between the steps. Only the handoff
|
|
47
|
+
wait loop may take minutes.
|
|
48
|
+
- The browser times out an action or a lookup after 15 s (the config sets it). Pass `{timeout: 5000}` when you wait for
|
|
49
|
+
something that may not appear, and use `.catch()` or `isVisible()` for a one-time look **while exploring only**.
|
|
50
|
+
`isVisible()` answers for this instant and ignores any `timeout` passed to it, so take a snapshot or wait for a signal
|
|
51
|
+
after an action first. Never copy this pattern into a spec (`readiness.md`, "Optional confirmations").
|
|
52
|
+
- After a click that should open something, take a snapshot and see what opened before you wait for it by role. A dialog
|
|
53
|
+
that did not appear is a finding (what did open instead?), not something to wait for or retry.
|
|
54
|
+
- A step that fails or times out twice is not tried a third time: record what you saw and go on (a `TODO [blocking]`
|
|
55
|
+
under the step in a case, a `needs-input` report when writing a spec).
|
|
56
|
+
|
|
26
57
|
## Runnable
|
|
27
58
|
|
|
28
59
|
Navigation and reading:
|
|
@@ -115,3 +146,6 @@ the card what the screenshot shows.
|
|
|
115
146
|
were started by the action. `browser_network_request({index})` gives one request's detail and status.
|
|
116
147
|
|
|
117
148
|
Record the marker as a plain note in the report (`after 12 new requests; the data call was 7`), never in the case.
|
|
149
|
+
|
|
150
|
+
4. While exploring, write the data-call paths of the action into the profile's exploration note (`notes.md`): path only,
|
|
151
|
+
no host, no query string. `/gen-spec` turns them into the `responses` of `actAndWait`.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# User handoff: a person works in the browser, the agent waits
|
|
2
2
|
|
|
3
3
|
Used by `exploring-cases` (and by `making-cases` never: it does not open a browser). `generating-specs` never hands
|
|
4
|
-
off; it replays the step recipes that an exploration recorded.
|
|
4
|
+
off; it replays the step recipes and the exploration note (`notes.md`) that an exploration recorded.
|
|
5
5
|
|
|
6
6
|
## The rule
|
|
7
7
|
**An agent never writes data.** Not in staging, not in production. Every step that creates, edits, publishes, shares,
|
|
@@ -9,8 +9,10 @@ Read `references/conventions.md` first. It defines the spec shape, fixtures, ass
|
|
|
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
11
|
`../exploring-cases/references/case-rules.md` (how a case's `## Rules` are read, applied and maintained),
|
|
12
|
-
`../exploring-cases/references/assertion-targets.md` (the exact thing each check looks at)
|
|
13
|
-
`../exploring-cases/references/readiness.md` (wait for the content, verify downloads in five parts)
|
|
12
|
+
`../exploring-cases/references/assertion-targets.md` (the exact thing each check looks at),
|
|
13
|
+
`../exploring-cases/references/readiness.md` (wait for the content, verify downloads in five parts),
|
|
14
|
+
`../exploring-cases/references/notes.md` (the local exploration note this run reads first) and
|
|
15
|
+
`../exploring-cases/references/tools.md` (the browser tools this MCP can run). All six are binding here.
|
|
14
16
|
|
|
15
17
|
This skill never stops to ask the user in the chat, because it may run without anybody present (a one-shot
|
|
16
18
|
`claude -p` / `opencode run`). This holds whatever mode or agent the run is in: plan mode changes what you may write,
|
|
@@ -116,42 +118,42 @@ Decide this first, before exploring anything. The spec path is `tests/<id>.spec.
|
|
|
116
118
|
never for what the check asserts about it: that goes to `test.fixme` with the reason, or `needs-input`.
|
|
117
119
|
- Do not invent expected values. If the case's `## Values` and the spec disagree, the case wins: fix the spec and
|
|
118
120
|
mention the change in the report.
|
|
119
|
-
3. **Check sessions** if `login: required`:
|
|
120
|
-
|
|
121
|
-
information for is never dropped because it is not logged in: write its part from
|
|
122
|
-
`not verified on the page` in the report.
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
121
|
+
3. **Check sessions** if `login: required`: follow `../exploring-cases/references/tools.md`, "Starting a session". A
|
|
122
|
+
profile that needs login is named with `pnpm signin <profile-id>`, and the run continues with the profiles that are
|
|
123
|
+
ready. A profile the case holds information for is never dropped because it is not logged in: write its part from
|
|
124
|
+
the case and mark it `not verified on the page` in the report.
|
|
125
|
+
4. **Replay the case on the real page** with the `playwright-test` MCP, following
|
|
126
|
+
`../exploring-cases/references/tools.md` ("Starting a session", "Short calls") and `readiness.md` ("Exploring"). If
|
|
127
|
+
its tools are not registered in this session at all (no `browser_*` / `generator_*` tool) or it reports that it
|
|
128
|
+
failed to start, do not fall back to ad-hoc Playwright scripts: stop, tell the user which MCP server is missing and
|
|
129
|
+
to run `pnpm asrun doctor`, write no spec, and end with `RESULT: needs-input`.
|
|
130
|
+
**Read each profile's exploration note first** (`../exploring-cases/references/notes.md`): run
|
|
131
|
+
`pnpm asrun explore:notes <case-id> --json`. A fresh note (its `caseSha256` matches the case) supplies the locator,
|
|
132
|
+
the request pattern and the target's match count for a step; confirm each on the page instead of rediscovering it,
|
|
133
|
+
and skip what it does not hold. A missing or stale note means the plain replay below. After confirming an entry of
|
|
134
|
+
a fresh note, you may write the verified locator or request back into that file (`updatedAt` gets the time); never
|
|
135
|
+
add a step or a target, and never touch a stale note. Say in the report what came from a note and what was
|
|
136
|
+
refreshed. Perform the steps in the environment you were given (never ask for it; there is no default).
|
|
134
137
|
Do the read-only steps as written, and follow the `## Rules` that say how to prepare, wait or which control to
|
|
135
138
|
avoid. **Never perform a step that writes or needs a choice**: the agent does not write
|
|
136
139
|
data, in staging or in production, and it does not hand off to a person here or wait for one. A rule never
|
|
137
140
|
authorizes one of those steps. Write those steps
|
|
138
141
|
of the spec from the `- Recipe:` lines under the step (positions, `[asrun]` names, control labels and roles that
|
|
139
142
|
`/explore-case` recorded). Pages that only exist after such a step cannot be seen from here: write their locators
|
|
140
|
-
from
|
|
141
|
-
leave the first run of the spec (or `/heal-spec`) to confirm
|
|
142
|
-
per domain (example.com and exampleshop.com differ in markup). A profile the case holds
|
|
143
|
-
not logged in is skipped and named in the report; its part is written from the case and
|
|
144
|
-
the assertion table. Use this exploration to answer the open points of step 2, and read each
|
|
145
|
-
the case's `## Values`: open that profile's page only when a value or a behavior still has to be
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
question, never `.first()`.
|
|
143
|
+
from a fresh note first, then from the recipe, and mark them in the report as "observed by explore on <profile>" or
|
|
144
|
+
"not verified on the page"; leave the first run of the spec (or `/heal-spec`) to confirm the latter. Do this for at
|
|
145
|
+
least one logged-in profile per domain (example.com and exampleshop.com differ in markup). A profile the case holds
|
|
146
|
+
information for that is not logged in is skipped and named in the report; its part is written from the case and
|
|
147
|
+
marked `not verified` in the assertion table. Use this exploration to answer the open points of step 2, and read each
|
|
148
|
+
profile's value from the case's `## Values`: open that profile's page only when a value or a behavior still has to be
|
|
149
|
+
resolved for it.
|
|
150
|
+
**Ready before you read, here too**: follow `readiness.md`. The request URL patterns for `actAndWait` come from a
|
|
151
|
+
fresh note or from `browser_network_requests` during this replay, never from a guess; the `Ready when` signal comes
|
|
152
|
+
from the case.
|
|
153
|
+
**Check every target on the page:** follow `../exploring-cases/references/assertion-targets.md`, "What the spec does
|
|
154
|
+
with a card (generating)". Confirm that the card's `Identified by` matches exactly one element and that this element
|
|
155
|
+
is what `What` says (outline it with `locator.highlight()` and look at the screenshot, see `tools.md`). A locator that
|
|
156
|
+
matches several elements or the wrong one is fixed or becomes a question, never `.first()`.
|
|
155
157
|
Prefer `getByTestId` (the project uses `data-tn`), then role/label locators. No CSS chains or XPath.
|
|
156
158
|
Update the case file with what you learned: fold observations into the right section, marked `(seen on <profile>)`
|
|
157
159
|
or, for a literal the spec asserts, `(observed on <profile>, 🔍 to be confirmed: <what to decide>)`; remove the
|
|
@@ -179,7 +181,8 @@ Decide this first, before exploring anything. The spec path is `tests/<id>.spec.
|
|
|
179
181
|
never decided by one `isVisible()` look (its `timeout` option is ignored): use `clickOptionalUntilHidden`
|
|
180
182
|
(`@asrunjs/asrun/ready`) or a waiting `expect(...).toBeVisible({timeout})`; see `readiness.md`, "Optional confirmations".
|
|
181
183
|
7. **Check the files**: run `pnpm typecheck`, `pnpm spec:lint <id>` and `pnpm case:check` (or `node --import tsx <framework>/scripts/check-cases.ts` if
|
|
182
|
-
it fails with a sandbox error such as `listen EPERM`).
|
|
184
|
+
it fails with a sandbox error such as `listen EPERM`). When you wrote an exploration note back, also run
|
|
185
|
+
`pnpm asrun explore:notes <id> --check --profile <profile-id>` for each profile you refreshed. Fix every error, including every `FAIL` of the spec lint (a lint failure is never silenced: fix the spec or turn the check
|
|
183
186
|
into a question). Every lint `WARN` is handled too: fix it, or name it in the report with the reason it is acceptable.
|
|
184
187
|
Then do the **self-review**: for each assertion answer in writing "if the product were broken, would this fail?"
|
|
185
188
|
and "if the product is right, could this fail by timing?" (a check that cannot fail, or that races the page, is rewritten).
|
|
@@ -188,7 +191,8 @@ Decide this first, before exploring anything. The spec path is `tests/<id>.spec.
|
|
|
188
191
|
Do NOT run `pnpm asrun`, do not use
|
|
189
192
|
`--update-snapshots` and do not create baselines: the first run of the spec does that, and a person reviews them.
|
|
190
193
|
8. **Report** in the user's language: spec path, **an assertion table** (step, what is asserted, target card, ready signal, and
|
|
191
|
-
`observed on the page` or `not verified`; no row may be missing), profiles explored,
|
|
194
|
+
`observed on the page` or `not verified`; no row may be missing), profiles explored, which steps were written from a
|
|
195
|
+
fresh exploration note and which notes were refreshed (or that there were none), profiles skipped (and why),
|
|
192
196
|
the profiles of `profiles.yaml` the case holds no information for (they run on `default` alone), whether the case file was
|
|
193
197
|
translated, what was added to the case file as `(seen on <profile>)` and which lines still need a person
|
|
194
198
|
(`🔍 to be confirmed`, with the reason), checks generated from observations, visual checks that need a baseline on
|
|
@@ -250,12 +254,15 @@ Heal from evidence, not from a habit of rerunning. A report that already exists
|
|
|
250
254
|
leaves out the profiles that are not logged in instead of failing them. A profile that is not logged in is never healed:
|
|
251
255
|
name it in the report with `pnpm signin <profile-id>`.
|
|
252
256
|
4. **Look at the pages when the change depends on them.** Reading a page is not a run and is always allowed, also for an
|
|
253
|
-
improvement request with no failure behind it. Use the `playwright-test` MCP exactly as `/gen-spec` step 4 describes
|
|
254
|
-
|
|
257
|
+
improvement request with no failure behind it. Use the `playwright-test` MCP exactly as `/gen-spec` step 4 describes,
|
|
258
|
+
following `../exploring-cases/references/tools.md` ("Starting a session", "Short calls"), `readiness.md` and the
|
|
259
|
+
exploration note (`../exploring-cases/references/notes.md`). Do the read-only
|
|
255
260
|
steps only: **never perform a step that writes or needs a choice**, in any environment. What you see goes into the
|
|
256
261
|
case by the gate in `open-questions.md` ("Which sentences carry a marker"): `(seen on <profile>)` for a locator, a
|
|
257
262
|
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.
|
|
263
|
+
literal the spec asserts, so the person settles that in the review gate below. If the note was stale, edit the case
|
|
264
|
+
as usual; if it was fresh, a verified locator may be written back
|
|
265
|
+
(`../exploring-cases/references/notes.md`).
|
|
259
266
|
5. **Audit the assertion before you blame anyone.** Read the `assertion-targets.md` and `readiness.md` sections on healing.
|
|
260
267
|
- Is the locator still what the target card says (`What`, `Identified by`)? Open the failure screenshot and the
|
|
261
268
|
baseline (`pnpm baselines:review <case-id>`) and look at each: a baseline that shows something other than the card
|
package/AGENTS.md
CHANGED
|
@@ -184,15 +184,16 @@ Three steps, each learns one thing: the intent, the facts and inputs, the implem
|
|
|
184
184
|
1. `making-cases` (or `/make-case <notes>`) turns raw notes into case documents. It writes `cases/*.md` only and
|
|
185
185
|
never opens a browser. Check them with `pnpm case:check`.
|
|
186
186
|
2. `exploring-cases` (or `/explore-case <case-id> <env> [--attended] [--profiles a,b]`) explores the real pages for one
|
|
187
|
-
case. It edits that case file
|
|
188
|
-
of the steps that write or need a choice
|
|
189
|
-
can be explored in
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
187
|
+
case. It edits that case file: wording, observations, TODO blocks, and, with `--attended`, the `- Recipe:` lines
|
|
188
|
+
of the steps that write or need a choice, and it writes a local note under `.explore/notes/` with the locators and
|
|
189
|
+
data-call paths it saw. `--profiles` narrows the run to those profiles; runs add up, so the case can be explored in
|
|
190
|
+
passes.
|
|
191
|
+
3. `generating-specs` (or `/gen-spec <case-id> <env>`) turns the case into a spec. It replays the case, its
|
|
192
|
+
step recipes and the exploration note, and `## Values`, writes the spec (values stay in the case document), but never
|
|
193
|
+
runs the tests, never writes data and never creates baselines. There is no profile selection here: it covers every
|
|
194
|
+
profile the case names, which is why a case explored in passes generates one spec for all of them. A tagged step with
|
|
195
|
+
no `- Recipe:` line (an old `## Inputs` line counts until it is moved) stops with `needs-input`: run
|
|
196
|
+
`/explore-case` first. Running specs is a separate step (`pnpm asrun`, or the Run page).
|
|
196
197
|
Workspace maintenance is separate from the AI steps: `pnpm asrun upgrade` installs the newest framework, refreshes the
|
|
197
198
|
generated files and applies the release's workspace migrations (it checks the target release for breaking changes
|
|
198
199
|
before installing), `pnpm asrun init [--update] [--force]` writes or refreshes the workspace files, `pnpm asrun
|
package/README.md
CHANGED
|
@@ -99,11 +99,12 @@ exempts `@asrunjs/asrun` from pnpm's 24-hour release age, so a fresh release ins
|
|
|
99
99
|
3. **Write a case.** `pnpm case:new header-logo` creates `cases/header-logo.md` from the template; or start from raw
|
|
100
100
|
notes with `/make-case notes.md`. Fill in the steps, what is expected and the exact values.
|
|
101
101
|
4. **Check it against the real pages.** `/explore-case header-logo staging` opens the pages (with your saved session),
|
|
102
|
-
writes what they show into the case and turns what it cannot know into questions.
|
|
103
|
-
|
|
104
|
-
with the others adds theirs, and the case
|
|
105
|
-
|
|
106
|
-
|
|
102
|
+
writes what they show into the case and turns what it cannot know into questions. It also saves a local note of the
|
|
103
|
+
locators and data calls it saw. Answer the questions in the case file, or in the UI's **Answer** sheet, and run it
|
|
104
|
+
again. With many profiles, `--profiles a,b` opens only those; a later run with the others adds theirs, and the case
|
|
105
|
+
keeps both.
|
|
106
|
+
5. **Generate, run, read.** `/gen-spec header-logo staging` reads those notes, writes `tests/header-logo.spec.ts` and
|
|
107
|
+
fills the case's `## Values`. Then:
|
|
107
108
|
|
|
108
109
|
```bash
|
|
109
110
|
pnpm asrun --env staging --grep header-logo
|
package/bin/asrun.mjs
CHANGED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The local exploration note: what `/explore-case` saw on the page, in a form `/gen-spec` can read.
|
|
3
|
+
*
|
|
4
|
+
* It lives under `.explore/notes/<case-id>/<profile-id>.json`, one file per profile, so a run with `--profiles` never
|
|
5
|
+
* has to merge with an earlier one. `.explore/` is git-ignored local state: a note is optional, may be deleted at any
|
|
6
|
+
* time, and a fresh checkout simply has none (generation then replays the case as before).
|
|
7
|
+
*
|
|
8
|
+
* A note holds only what the case does not: locators the agent confirmed on the page, the data-call paths of the
|
|
9
|
+
* actions that load something, and the match count of a target card's element. Expected values stay in the case's
|
|
10
|
+
* `## Values`, ready signals in the case's `Ready when:` lines, and assertions nowhere near this file.
|
|
11
|
+
*/
|
|
12
|
+
export declare const NOTES_VERSION = 1;
|
|
13
|
+
export interface ExploreLocator {
|
|
14
|
+
/** What the locator is for, in words ("the header navigation link"). */
|
|
15
|
+
purpose: string;
|
|
16
|
+
/** A Playwright locator as `browser_generate_locator` returned it, for example `page.getByRole('link', {name: 'Home'})`. */
|
|
17
|
+
locator: string;
|
|
18
|
+
}
|
|
19
|
+
export interface ExploreNoteStep {
|
|
20
|
+
/** The path the step runs on, relative to `baseURL` (for example `/documents/`). */
|
|
21
|
+
url?: string;
|
|
22
|
+
locators?: ExploreLocator[];
|
|
23
|
+
/** Data-call paths the step's action starts, path only (`/api/articles`). */
|
|
24
|
+
requests?: string[];
|
|
25
|
+
}
|
|
26
|
+
export interface ExploreNoteTarget {
|
|
27
|
+
locator: string;
|
|
28
|
+
/** How many elements the locator matched when it was confirmed (1 for a target that is not a question). */
|
|
29
|
+
matches?: number;
|
|
30
|
+
}
|
|
31
|
+
export interface ExploreNote {
|
|
32
|
+
version: typeof NOTES_VERSION;
|
|
33
|
+
caseId: string;
|
|
34
|
+
profile: string;
|
|
35
|
+
env?: string;
|
|
36
|
+
/** sha256 of `cases/<case-id>.md` when the note was written; a different one makes the note stale. */
|
|
37
|
+
caseSha256: string;
|
|
38
|
+
capturedAt: string;
|
|
39
|
+
/** Set when a later `/gen-spec` refreshed an entry it confirmed on the page. */
|
|
40
|
+
updatedAt?: string;
|
|
41
|
+
steps: Record<string, ExploreNoteStep>;
|
|
42
|
+
targets?: Record<string, ExploreNoteTarget>;
|
|
43
|
+
}
|
|
44
|
+
export interface ExploreNoteError {
|
|
45
|
+
file: string;
|
|
46
|
+
problems: string[];
|
|
47
|
+
}
|
|
48
|
+
/** The folder that holds one case's notes; `<case-id>` may contain its group folder (`documents/landing-page`). */
|
|
49
|
+
export declare function exploreNotesDir(caseId: string): string;
|
|
50
|
+
/** The note of one profile: `.explore/notes/<case-id>/<profile-id>.json`. */
|
|
51
|
+
export declare function exploreNotesFile(caseId: string, profileId: string): string;
|
|
52
|
+
/** sha256 of the case document, or null when it does not exist. */
|
|
53
|
+
export declare function caseSha256(caseId: string): string | null;
|
|
54
|
+
export type NoteFreshness = 'fresh' | 'stale' | 'no-case';
|
|
55
|
+
/**
|
|
56
|
+
* Whether a note still describes the case. A stale note is not an error: `/gen-spec` replays the case and ignores it.
|
|
57
|
+
*/
|
|
58
|
+
export declare function noteFreshness(note: ExploreNote): {
|
|
59
|
+
state: NoteFreshness;
|
|
60
|
+
reason?: string;
|
|
61
|
+
};
|
|
62
|
+
/** Every reason the note is not usable, in plain words. An empty list means it is valid. */
|
|
63
|
+
export declare function validateExploreNote(value: unknown, expected: {
|
|
64
|
+
caseId: string;
|
|
65
|
+
profile: string;
|
|
66
|
+
}): string[];
|
|
67
|
+
/**
|
|
68
|
+
* Reads every note of a case (or one profile's), parses and validates it. A malformed file is reported, never thrown:
|
|
69
|
+
* the caller decides whether it matters, and generation treats an unreadable note as a missing one.
|
|
70
|
+
*/
|
|
71
|
+
export declare function readExploreNotes(caseId: string, profileId?: string): {
|
|
72
|
+
notes: ExploreNote[];
|
|
73
|
+
errors: ExploreNoteError[];
|
|
74
|
+
};
|