@asrunjs/asrun 0.2.0 → 0.5.3
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 +18 -15
- package/.claude/commands/gen-spec.md +16 -13
- package/.claude/commands/heal-spec.md +26 -23
- package/.claude/commands/make-case.md +11 -8
- package/.claude/skills/exploring-cases/SKILL.md +146 -132
- package/.claude/skills/exploring-cases/references/open-questions.md +116 -112
- package/.claude/skills/exploring-cases/references/readiness.md +146 -137
- package/.claude/skills/exploring-cases/references/user-handoff.md +157 -147
- package/.claude/skills/generating-specs/SKILL.md +219 -207
- package/.claude/skills/generating-specs/references/conventions.md +148 -148
- package/.claude/skills/making-cases/SKILL.md +72 -64
- package/.claude/skills/making-cases/references/normalizing.md +39 -38
- package/.claude/skills/making-cases/references/writes-and-email.md +37 -36
- package/AGENTS.md +169 -154
- package/CLAUDE.md +2 -1
- package/README.md +221 -410
- package/dist/lib/build-artifacts.d.ts +8 -0
- package/dist/lib/build-artifacts.js +10 -0
- package/dist/lib/case-doc.d.ts +311 -0
- package/dist/lib/case-doc.js +1117 -0
- package/dist/lib/cases.d.ts +2 -15
- package/dist/lib/cases.js +5 -78
- package/dist/lib/command.d.ts +24 -0
- package/dist/lib/command.js +90 -0
- package/dist/lib/fixtures.js +2 -2
- package/dist/lib/login-card.d.ts +22 -0
- package/dist/lib/login-card.js +183 -0
- package/dist/lib/node-cli.d.ts +6 -2
- package/dist/lib/node-cli.js +16 -6
- package/dist/lib/profiles.d.ts +25 -1
- package/dist/lib/profiles.js +54 -1
- package/dist/lib/run-filters.js +10 -2
- package/dist/lib/session.d.ts +13 -1
- package/dist/lib/session.js +73 -7
- package/dist/lib/targets.d.ts +5 -13
- package/dist/lib/targets.js +4 -29
- package/dist/lib/values.js +2 -2
- package/dist/scripts/build.js +2 -1
- package/dist/scripts/check-cases.js +74 -6
- package/dist/scripts/doctor.js +115 -4
- package/dist/scripts/explore.js +22 -1
- package/dist/scripts/go.mjs +188 -188
- package/dist/scripts/init.d.ts +7 -1
- package/dist/scripts/init.js +62 -14
- package/dist/scripts/lint-specs.js +4 -1
- package/dist/scripts/login.js +30 -2
- package/dist/ui/server/actions.d.ts +4 -0
- package/dist/ui/server/actions.js +25 -12
- package/dist/ui/server/ai-models.d.ts +3 -0
- package/dist/ui/server/ai-models.js +347 -0
- package/dist/ui/server/ai-stream.d.ts +34 -9
- package/dist/ui/server/ai-stream.js +283 -77
- package/dist/ui/server/app.js +74 -12
- package/dist/ui/server/jobs.d.ts +7 -1
- package/dist/ui/server/jobs.js +69 -15
- package/dist/ui/server/profiles.d.ts +20 -1
- package/dist/ui/server/profiles.js +214 -38
- package/dist/ui/shared/ai-commands.d.ts +16 -6
- package/dist/ui/shared/ai-commands.js +35 -12
- package/dist/ui/shared/types.d.ts +114 -0
- package/docs/agents/case-format.md +193 -0
- package/docs/agents/framework-dev.md +167 -0
- package/docs/ai-workflow.md +235 -0
- package/docs/cases.md +189 -0
- package/docs/commands.md +175 -0
- package/docs/install.md +174 -0
- package/docs/profiles.md +139 -0
- package/docs/theme-guide.html +922 -692
- package/docs/troubleshooting.md +101 -0
- package/docs/web-ui.md +139 -0
- package/package.json +20 -1
- package/templates/.claude/commands/explore-case.md +22 -21
- package/templates/.claude/commands/gen-spec.md +21 -20
- package/templates/.claude/commands/heal-spec.md +31 -30
- package/templates/.claude/commands/make-case.md +13 -12
- package/templates/.mcp.json +8 -8
- package/templates/AGENTS.md +66 -45
- package/templates/cases/_template.md +57 -63
- package/templates/opencode.jsonc +16 -11
- package/templates/profiles.yaml +42 -29
- package/ui/dist/assets/{dist-D_I45xgJ.js → dist-BudNAjOv.js} +1 -1
- package/ui/dist/assets/{dist-CBV7_vDN.js → dist-C-eCSFja.js} +1 -1
- package/ui/dist/assets/{dist-gF2bFn-5.js → dist-CO4heSzh.js} +1 -1
- package/ui/dist/assets/{dist-DX8knLVr.js → dist-CPLhRzom.js} +1 -1
- package/ui/dist/assets/{dist-BqAROxzI.js → dist-DEPJZ3Me.js} +1 -1
- package/ui/dist/assets/{dist-BwYRbe5s.js → dist-DZ7RiqmA.js} +1 -1
- package/ui/dist/assets/{dist-BHaIOZdW.js → dist-LixzGniF.js} +1 -1
- package/ui/dist/assets/{dist-DHCY9KpV.js → dist-O_RVi4tP.js} +1 -1
- package/ui/dist/assets/{dist-BiE4suo9.js → dist-jPS6HCJ5.js} +1 -1
- package/ui/dist/assets/{dist-BSJMM_jp.js → dist-vmf3fnkM.js} +1 -1
- package/ui/dist/assets/index-BIGt8Hfz.js +338 -0
- package/ui/dist/assets/index-CTmnF0Wp.css +2 -0
- package/ui/dist/help/ai-tools.png +0 -0
- package/ui/dist/help/case-explore.png +0 -0
- package/ui/dist/help/case-generate.png +0 -0
- package/ui/dist/help/cases-memo.png +0 -0
- package/ui/dist/help/profile-form.png +0 -0
- package/ui/dist/help/profiles-login.png +0 -0
- package/ui/dist/help/reports.png +0 -0
- package/ui/dist/help/run-tests.png +0 -0
- package/ui/dist/index.html +22 -16
- package/ui/dist/assets/index-DmasQ6Rq.css +0 -2
- package/ui/dist/assets/index-I7uU93kk.js +0 -294
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
# The AI workflow
|
|
2
|
+
|
|
3
|
+
Four commands carry a test case from rough notes to a Playwright spec, and repair it when the product changes. They
|
|
4
|
+
work the same in Claude Code and OpenCode: each command is a thin shell that loads a skill from the framework package,
|
|
5
|
+
so the procedure lives in one place and a project can refine it (see
|
|
6
|
+
[Customizing the prompts](#customizing-the-prompts)).
|
|
7
|
+
|
|
8
|
+
| Command | What it does |
|
|
9
|
+
|---|---|
|
|
10
|
+
| `/make-case <notes-or-path>` | Turn raw notes into case documents. Optional step. |
|
|
11
|
+
| `/explore-case <case-id> <env> [--attended]` | Open the real pages and write what they show into the case. |
|
|
12
|
+
| `/gen-spec <case-id> <env> [--force]` | Write `tests/<case-id>.spec.ts` and fill the case's `## Values`. |
|
|
13
|
+
| `/heal-spec <case-id> <env> [--profiles a,b] [--report <run-id>] [--hints <file>]` | Diagnose a failing spec from its last report and fix it. |
|
|
14
|
+
|
|
15
|
+
The environment is **required** by every command except `/make-case`, which does not open pages. There is no default.
|
|
16
|
+
|
|
17
|
+
Every command ends with `RESULT: ready` or `RESULT: needs-input`. `needs-input` means somebody has to answer a
|
|
18
|
+
question in the case file (or, in the web UI, in the job's **Answer** sheet). A one-shot run cannot ask in the chat:
|
|
19
|
+
it writes its questions into the case and stops.
|
|
20
|
+
|
|
21
|
+
In the web UI you never type a command: choose the step in a case toolbar and press **Run AI**, or paste notes on
|
|
22
|
+
**Cases > From memo**. The UI runs the selected AI tool and streams the output into the Jobs bar.
|
|
23
|
+
|
|
24
|
+
## 0. Optional: start from raw notes
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
/make-case path/to/notes.md
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
or paste the text after the command. The agent normalizes the notes into steps in the project's artifact language
|
|
31
|
+
(English by default), marks the steps that write data (`[writes]`) or send email (`[email]`), keeps the original lines
|
|
32
|
+
in a `## Source` table, and lists the gaps with a suggested default for each. It never invents expected values
|
|
33
|
+
(unknown ones become a sentence with a `TODO` block under it) and never opens a browser. What the pages really show
|
|
34
|
+
is found in step 2.
|
|
35
|
+
|
|
36
|
+
It writes `cases/<id>.md` only, and never overwrites an existing case. `pnpm case:check` validates the result.
|
|
37
|
+
|
|
38
|
+
## 1. Write or open the case
|
|
39
|
+
|
|
40
|
+
`pnpm case:new header-logo` creates `cases/header-logo.md` from the template, or use a case you already have. The
|
|
41
|
+
format is in [cases.md](./cases.md). Write the steps, the expectations and — when you know them — the values.
|
|
42
|
+
|
|
43
|
+
## 2. Explore the case (only where a person and the agent work together)
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
/explore-case header-logo staging
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The agent opens the real pages of the profiles that are logged in and only **reads** them. It never creates, edits,
|
|
50
|
+
publishes, shares or deletes anything, in staging or in production. It:
|
|
51
|
+
|
|
52
|
+
- fixes wording in the case file and writes what the pages show into it, marked `observed, 🔍 to be confirmed by QA`;
|
|
53
|
+
- turns what it cannot know into `🚫 TODO [blocking]` / `❓ TODO [non-blocking]` questions, each under a sentence that
|
|
54
|
+
is its suggestion and can stand in the case as it is, with a last line `- ✍️ Answer:`;
|
|
55
|
+
- lists the candidates instead of guessing a target (`## Targets`);
|
|
56
|
+
- edits the case file only (plus reference screenshots under `.explore/targets/`).
|
|
57
|
+
|
|
58
|
+
**To accept the suggestion, delete the whole block.** **To answer, write after `Answer:`.** Then run the command
|
|
59
|
+
again. In the web UI, the run's **What to do next** bar offers **Answer**: the question list, an answer box,
|
|
60
|
+
**Use the suggested defaults** and **Generate spec**. A continued job keeps the same conversation and permissions, and
|
|
61
|
+
can be continued once per turn, only while the UI server has not restarted.
|
|
62
|
+
|
|
63
|
+
### Attended exploration (`--attended`)
|
|
64
|
+
|
|
65
|
+
Use it for a case that creates or edits data, or needs a choice (an existing document, items). Stay at the computer:
|
|
66
|
+
the browser window opens and the agent waits while you do those steps.
|
|
67
|
+
|
|
68
|
+
- The window shows a small draggable card in the bottom-left corner with what to do, **Done** (hand control back) and
|
|
69
|
+
**Cannot do this**. The agent also continues by itself when the finish condition it named shows up (for example a
|
|
70
|
+
"Saved" message); the card turns green when control is back. It waits up to 10 minutes per step.
|
|
71
|
+
- What you did is recorded in the case under the step as **general recipe lines**
|
|
72
|
+
(`- Recipe (phase): ...`): positions instead of names of changeable data, `[asrun]` names for created data, control
|
|
73
|
+
labels but no selectors.
|
|
74
|
+
- Delete the `[asrun]` items you created before you press Done on the last step.
|
|
75
|
+
- Without `--attended` the agent never waits: a step that needs a person becomes a `TODO [blocking]`.
|
|
76
|
+
|
|
77
|
+
In the web UI the **Attended** box is ticked by default for Explore.
|
|
78
|
+
|
|
79
|
+
## 3. Log in the profiles the case needs
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
pnpm status
|
|
83
|
+
pnpm signin example-staging
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Skip this for a `login: none` case. Sessions are per profile; see [profiles.md](./profiles.md).
|
|
87
|
+
|
|
88
|
+
## 4. Generate the spec
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
/gen-spec header-logo staging
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
The agent replays the case on the real pages with your saved sessions (it never writes data) and writes
|
|
95
|
+
`tests/header-logo.spec.ts`. It fills the case's `## Values` for the profiles it explored and checks the files
|
|
96
|
+
(`pnpm typecheck`, `pnpm case:check`; `pnpm spec:lint <case-id>` must have no failure).
|
|
97
|
+
|
|
98
|
+
It does **not** run the spec and does **not** create screenshot baselines: running is a separate step (6).
|
|
99
|
+
|
|
100
|
+
- A case must be in the project's artifact language. If it is in another language, `/gen-spec` translates the case
|
|
101
|
+
file first and tells you.
|
|
102
|
+
- A `[writes]` or `[input]` step with no `- Recipe:` line under it is not generated: the command stops with
|
|
103
|
+
`needs-input` and asks you to run step 2 with `--attended`.
|
|
104
|
+
- An existing spec is left alone; `--force` regenerates it from the case. The case's `## Values` and the baseline
|
|
105
|
+
images are kept, and the report lists the baselines that may be out of date. In the web UI, **Run** on a case that
|
|
106
|
+
has a spec asks "Overwrite the spec?" first.
|
|
107
|
+
|
|
108
|
+
## 5. Review what the AI produced
|
|
109
|
+
|
|
110
|
+
Check that the spec reads like the case, and that `## Values` has the right values for each profile. The spec imports
|
|
111
|
+
`test`/`expect` from `@asrunjs/asrun/fixtures`, never from `@playwright/test`, and every step is a `test.step` named
|
|
112
|
+
after the case's step. You normally do not edit either file by hand, but you can.
|
|
113
|
+
|
|
114
|
+
## 6. Run it and read the evidence
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
pnpm asrun --env staging --grep header-logo
|
|
118
|
+
pnpm report
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
The first run of a spec with visual checks writes its baselines. Run it once with `--update-snapshots`, then open
|
|
122
|
+
every new image under `tests/<case>.spec.ts-snapshots/` and confirm it shows the target card's element for that
|
|
123
|
+
profile. Baselines are the source of truth for pixel checks.
|
|
124
|
+
|
|
125
|
+
Then run all profiles and read the report: the video of each test, screenshots on failure, and the trace with the page
|
|
126
|
+
and the network requests. See [commands.md](./commands.md) for filters and baselines.
|
|
127
|
+
|
|
128
|
+
## Healing a failing spec
|
|
129
|
+
|
|
130
|
+
```
|
|
131
|
+
/heal-spec header-logo staging
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
or **Heal spec** in the case toolbar, or **Heal this failure** on a failed result of the Reports page.
|
|
135
|
+
|
|
136
|
+
The agent decides whether the test needs updating or whether it found a real product problem. It **never** edits an
|
|
137
|
+
expectation just to make a failure disappear.
|
|
138
|
+
|
|
139
|
+
- The environment is required. Only the profiles that are logged in there run (`--logged-in` leaves the others out
|
|
140
|
+
instead of failing them).
|
|
141
|
+
- The web UI first opens a dialog: which profiles to heal (every logged-in profile ticked) and an optional text box
|
|
142
|
+
for what you want healed, with evidence or clues. The text is stored in `.tmp/hints/` and passed as `--hints`.
|
|
143
|
+
Clues guide the diagnosis; they never lower the bar for a product finding.
|
|
144
|
+
- It reads the existing report first:
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
pnpm heal:evidence --case header-logo --env staging
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
It prints status, failed step, error, call log and screenshots per profile, and whether the report is fresh or
|
|
151
|
+
stale. The case is run again only when there is no report, the report is stale, it does not explain the failure, or
|
|
152
|
+
a clue has to be checked. After editing the spec, it runs the case once to confirm.
|
|
153
|
+
|
|
154
|
+
## Why a generated spec can be trusted
|
|
155
|
+
|
|
156
|
+
Three binding rules hold in exploring, generating and healing. They live in the framework's `AGENTS.md` and in
|
|
157
|
+
`.claude/skills/exploring-cases/references/assertion-targets.md` and `readiness.md`.
|
|
158
|
+
|
|
159
|
+
- **Targets.** A check on a logo, image, icon, file, PDF, screenshot or repeated text points to a `[T1]` card under
|
|
160
|
+
`## Targets`: what it is (and what it is not), where it is, how it is identified (never `.first()` or a bare `img`),
|
|
161
|
+
when it is ready, and a highlighted reference screenshot under `.explore/targets/`. When in doubt the agent does not
|
|
162
|
+
pick: it writes a `🚫 TODO [blocking]` that names the exact image, text, place or UI, with the candidates listed.
|
|
163
|
+
- **Readiness.** After an action that loads something, the key requests must finish and the content must be visible
|
|
164
|
+
before anything is read. Specs use `actAndWait` (`@asrunjs/asrun/ready`); "missing" and "empty" checks need a
|
|
165
|
+
positive ready signal first. Healing treats a "missing / empty / timeout" failure as a readiness problem first.
|
|
166
|
+
- **Downloads.** `verifyDownload` (`@asrunjs/asrun/downloads`) checks that the download starts, finishes, the file
|
|
167
|
+
exists and the file is valid for its kind. Any failure or timeout fails the whole step with a message that names the
|
|
168
|
+
stage.
|
|
169
|
+
- **PDFs are read like a person reads them.** `expectPdf` (`@asrunjs/asrun/pdf`) renders the pages to images (a logo
|
|
170
|
+
region must not be blank and must match the profile's baseline, 2% tolerance) and extracts the text. Every page is
|
|
171
|
+
checked unless the case limits the pages in strong words ("only", "exactly", "must"); "should", "always", "usually"
|
|
172
|
+
or no wording mean every page.
|
|
173
|
+
|
|
174
|
+
Checks you can run yourself:
|
|
175
|
+
|
|
176
|
+
| Command | What it checks |
|
|
177
|
+
|---|---|
|
|
178
|
+
| `pnpm spec:lint [case-id]` | fails on screenshots without a target, `.first()` on images, sleeps, `networkidle`, raw download events, a `%PDF` check on its own |
|
|
179
|
+
| `pnpm baselines:review [case-id] [--new]` | lists every baseline with the card that says what it must show, so you can look at each new image |
|
|
180
|
+
| `pnpm case:check [case-id]` | fails on an incomplete card or bad values; warns on an image check with no card |
|
|
181
|
+
| `pnpm pdf:render <file.pdf> --out <dir> [--pages all] [--corner top-left]` | renders the pages to images and prints the text, to look at a PDF by hand |
|
|
182
|
+
|
|
183
|
+
## Customizing the prompts
|
|
184
|
+
|
|
185
|
+
The commands are shells; the procedure is a skill in the framework package:
|
|
186
|
+
|
|
187
|
+
- commands: `.claude/commands/*.md` (Claude Code) and `.opencode/commands/*.md` (OpenCode);
|
|
188
|
+
- skills: `<framework>/.claude/skills/{making-cases,exploring-cases,generating-specs}/`.
|
|
189
|
+
|
|
190
|
+
A project refines a skill with a **flat override file**, named after the skill (not the command):
|
|
191
|
+
|
|
192
|
+
```
|
|
193
|
+
.claude/skills/generating-specs.md
|
|
194
|
+
.claude/skills/exploring-cases.md
|
|
195
|
+
.claude/skills/making-cases.md
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
The command and the skill read it; it is not discovered as a standalone skill. Rules combine in this order, and a
|
|
199
|
+
later one wins where they conflict:
|
|
200
|
+
|
|
201
|
+
1. `F1`–`F6` of the framework baseline: safety, in every project. Nothing can change them.
|
|
202
|
+
2. The framework baseline, `<framework>/AGENTS.md`.
|
|
203
|
+
3. The framework skill.
|
|
204
|
+
4. The project override `.claude/skills/<skill>.md`.
|
|
205
|
+
5. The project's own `AGENTS.md` §2 (Project rules).
|
|
206
|
+
|
|
207
|
+
An override is plain Markdown; write only what differs:
|
|
208
|
+
|
|
209
|
+
```markdown
|
|
210
|
+
# generating-specs in this project
|
|
211
|
+
|
|
212
|
+
- Step titles in `test.step` keep the case's wording, including the number.
|
|
213
|
+
- A helper used by more than one spec goes in `tests/helpers/<name>.ts`.
|
|
214
|
+
- The staging account has no published documents: assert the empty state there instead.
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
The project's `AGENTS.md` §2 is the other place: a table of rule ids (`F7`–`F10`; `F1`–`F6` are fixed) with `narrows`
|
|
218
|
+
(stricter) or `replaces` (a different default). For example, a project that writes cases in Simplified Chinese
|
|
219
|
+
`replaces` `F10`. `pnpm asrun doctor` fails on an unknown id, a missing `Effect`, or a non-negotiable rule, and lists
|
|
220
|
+
the overrides it sees.
|
|
221
|
+
|
|
222
|
+
An update never overwrites `AGENTS.md`, `CLAUDE.md` or `.claude/skills/**`: `pnpm asrun init --force` refuses them
|
|
223
|
+
without `--reset-rules`. Never edit the framework package itself.
|
|
224
|
+
|
|
225
|
+
Project-level AI tool configuration:
|
|
226
|
+
|
|
227
|
+
| File | What it sets |
|
|
228
|
+
|---|---|
|
|
229
|
+
| `.mcp.json` | the `playwright-test` MCP server for Claude Code |
|
|
230
|
+
| `opencode.jsonc` | the same MCP server for OpenCode, plus an optional `"model"` for one-shot runs |
|
|
231
|
+
| `.claude/settings.json` | the sandbox exceptions for `pnpm asrun`, `pnpm status`, `pnpm heal:evidence` |
|
|
232
|
+
|
|
233
|
+
In the web UI, the top bar chooses the AI tool and the model for every AI run, and **Thinking** passes `--thinking`
|
|
234
|
+
to OpenCode. The choice is remembered per CLI in the browser. Without a model, a one-shot OpenCode run uses its
|
|
235
|
+
built-in free model, which has a daily usage limit.
|
package/docs/cases.md
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# Writing case documents
|
|
2
|
+
|
|
3
|
+
A case is a Markdown file that a person writes and reviews. It is the source of truth for the test: steps,
|
|
4
|
+
expectations, the exact values and the visual targets. The spec is generated from it and never replaces it.
|
|
5
|
+
|
|
6
|
+
## Where cases live
|
|
7
|
+
|
|
8
|
+
- `cases/<name>.md`, or one group folder: `cases/<group>/<name>.md`.
|
|
9
|
+
- The **id** is the path below `cases/` without `.md`: `header-logo` or `documents/landing-page`. Names are lowercase
|
|
10
|
+
words with single dashes.
|
|
11
|
+
- The spec of a case lives at the same path below `tests/` (`tests/documents/landing-page.spec.ts`), with its
|
|
12
|
+
`*-snapshots/` folder next to it.
|
|
13
|
+
- Files starting with `_` are ignored (`cases/_template.md` is the template).
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pnpm case:new header-logo # creates cases/header-logo.md from the template
|
|
17
|
+
pnpm case:new documents/header-logo # also creates the group folder
|
|
18
|
+
pnpm case:check # validates every case (or: pnpm case:check <id>)
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## The document
|
|
22
|
+
|
|
23
|
+
````markdown
|
|
24
|
+
---
|
|
25
|
+
id: header-logo
|
|
26
|
+
title: The header shows the product logo
|
|
27
|
+
login: required
|
|
28
|
+
suites: [smoke, regression]
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Goal
|
|
32
|
+
Why this case exists, in one or two sentences.
|
|
33
|
+
|
|
34
|
+
## Steps
|
|
35
|
+
1. Open `/`.
|
|
36
|
+
- Ready when: the header has rendered its menu.
|
|
37
|
+
2. The header shows the product logo. [T1]
|
|
38
|
+
|
|
39
|
+
## Expected
|
|
40
|
+
- The logo is the product logo for this profile, not a customer or partner logo.
|
|
41
|
+
|
|
42
|
+
## Values
|
|
43
|
+
```yaml
|
|
44
|
+
default:
|
|
45
|
+
logoAlt: Acme
|
|
46
|
+
example-staging:
|
|
47
|
+
logoAlt: Acme Staging
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Visual checks
|
|
51
|
+
- The header logo matches its baseline. [T1]
|
|
52
|
+
|
|
53
|
+
## Targets
|
|
54
|
+
### T1 Header logo
|
|
55
|
+
- What: the product logo, not the customer logo in the banner.
|
|
56
|
+
- Where: top-left of the header.
|
|
57
|
+
- Identified by: role `img`, name "Acme".
|
|
58
|
+
- Not: the customer logo in the banner and the favicon.
|
|
59
|
+
- Ready when: the image has a non-zero natural width.
|
|
60
|
+
- Reference example-staging: .explore/targets/header-logo/T1-example-staging.png
|
|
61
|
+
|
|
62
|
+
## Notes
|
|
63
|
+
Anything a reviewer should know.
|
|
64
|
+
````
|
|
65
|
+
|
|
66
|
+
The headings are fixed English tokens (`Goal`, `Steps`, `Expected`, `Values`, `Visual checks`, `Targets`, `Notes`,
|
|
67
|
+
and `Source` when the case was made from raw notes). A project language never translates them.
|
|
68
|
+
|
|
69
|
+
## Front matter
|
|
70
|
+
|
|
71
|
+
| Key | Meaning |
|
|
72
|
+
|---|---|
|
|
73
|
+
| `id` | The case id (the path without `.md`). |
|
|
74
|
+
| `title` | One-line summary, shown in the editor and the spec. |
|
|
75
|
+
| `login` | `required` (the default) or `none` for a public page that needs no saved session. A `login: none` spec sets `test.use({mustLogin: false})`. |
|
|
76
|
+
| `suites` | Required. One or more lowercase words: `suites: [smoke, regression]`. A case runs in every suite it lists. |
|
|
77
|
+
| `writes` | The environments in which the case may create or change data, for example `writes: [staging]`. Omit it for a read-only case. |
|
|
78
|
+
| `writes-note` | Required with `writes`: what the case creates or changes, and that it is removed again. |
|
|
79
|
+
| `envs` | Optional: run the case only in these environments; elsewhere it is skipped. Must include every environment in `writes`. |
|
|
80
|
+
|
|
81
|
+
`priority: <level>` is the old name of `suites`; it is read as `suites: [<level>]`. Do not use both.
|
|
82
|
+
|
|
83
|
+
## Steps
|
|
84
|
+
|
|
85
|
+
Numbered lines. Keep them short and in plain language: the spec uses each step's text verbatim in `test.step`, so the
|
|
86
|
+
report and the trace read like the case.
|
|
87
|
+
|
|
88
|
+
- **`- Ready when:`** under a step says what positive signal shows that what the step loads is really there (the list
|
|
89
|
+
has its first row, the editor is visible). The generated spec waits for it before it reads anything. It is not an
|
|
90
|
+
assertion; what must be true goes under `## Expected`.
|
|
91
|
+
- **Step tags**: `[writes]` for a step that creates or changes data, `[input]` for a step that needs a person's
|
|
92
|
+
choice (an existing document, items), `[email]` for a step that sends email. They are written at the end of the
|
|
93
|
+
step line.
|
|
94
|
+
- **`- Recipe (phase):`** under a tagged step records what a person did in the browser during an attended
|
|
95
|
+
exploration: positions instead of names of changeable data (`select items #1 to #3`), `[asrun]` names for created
|
|
96
|
+
data, control labels but no selectors. Several lines per step are normal. A tagged step with no recipe is not
|
|
97
|
+
generated: `/gen-spec` stops with `needs-input`.
|
|
98
|
+
- **Open questions** go in a `🚫 TODO [blocking]` or `❓ TODO [non-blocking]` block under the sentence they are about,
|
|
99
|
+
with the agent's suggestion written above and a last line `- ✍️ Answer:`. Accepting a question means deleting the
|
|
100
|
+
whole block; answering means writing after `Answer:`. The marker `🔍 to be confirmed by QA` / `👁️ not observed`
|
|
101
|
+
marks something seen but not settled.
|
|
102
|
+
|
|
103
|
+
## Expected
|
|
104
|
+
|
|
105
|
+
Bullets: behaviour that must hold for every profile. Profile differences belong in `## Values`, not in prose.
|
|
106
|
+
|
|
107
|
+
## Values
|
|
108
|
+
|
|
109
|
+
The exact literals the spec asserts, in one fenced `yaml` block: names, labels, options, test data names.
|
|
110
|
+
|
|
111
|
+
```yaml
|
|
112
|
+
default:
|
|
113
|
+
createButton: Create New
|
|
114
|
+
emptyList: No documents yet
|
|
115
|
+
example-staging:
|
|
116
|
+
createButton: Create a document
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
- `default` applies to every profile; an entry named after a profile id is **deep-merged over it**. An empty cell in
|
|
120
|
+
the UI removes the key from that profile.
|
|
121
|
+
- Only profile ids from `profiles.yaml` (or `default`) may appear at the top level. An unknown entry is an error.
|
|
122
|
+
- The case document is the only place where expected values live. `profiles.yaml` holds connection settings only.
|
|
123
|
+
- A profile with no entry (and no `default`) fails with "Missing values"; an empty value or one that starts with `TODO`
|
|
124
|
+
fails with "Incomplete values". Both messages say what to fill in.
|
|
125
|
+
- Specs read them with `requireValues('<id>', profile)` and never parse the YAML themselves.
|
|
126
|
+
|
|
127
|
+
## Visual checks and targets
|
|
128
|
+
|
|
129
|
+
A visual check names the element that must match a screenshot baseline. A check on a logo, image, icon, file, PDF,
|
|
130
|
+
screenshot or repeated text refers to a card under `## Targets` as `[T1]`, and the spec carries `// target: T1` on the
|
|
131
|
+
line before the check.
|
|
132
|
+
|
|
133
|
+
Each card has these fields:
|
|
134
|
+
|
|
135
|
+
- `What`: the thing itself, and what it is **not** when a look-alike exists.
|
|
136
|
+
- `Where`: the region of the page, dialog, tab or file page.
|
|
137
|
+
- `Identified by`: role and name, test id, alt text or title; never a position.
|
|
138
|
+
- `Not`: the look-alikes that must not be matched.
|
|
139
|
+
- `Ready when`: what shows the content has loaded.
|
|
140
|
+
- `Reference <profile-id>:` a screenshot under `.explore/targets/<case-id>/`, or `none: <reason>`.
|
|
141
|
+
|
|
142
|
+
Do not guess a target: when it is unclear which element is meant, the exploring agent writes a `🚫 TODO [blocking]`
|
|
143
|
+
that names the candidates, and a person settles it. The binding rules are in
|
|
144
|
+
`../.claude/skills/exploring-cases/references/assertion-targets.md` of the framework package.
|
|
145
|
+
|
|
146
|
+
## Data writing
|
|
147
|
+
|
|
148
|
+
By default a case is read-only: look, read, compare. No creating, editing, deleting, submitting or accepting
|
|
149
|
+
anything.
|
|
150
|
+
|
|
151
|
+
A case may write only in the environments listed under `writes`, and only data of the test account:
|
|
152
|
+
|
|
153
|
+
- everything it creates is named `[asrun] ...` (the `records` fixture refuses other names);
|
|
154
|
+
- created data is registered with `records.track` and removed after the test, also after a failure;
|
|
155
|
+
- the case's own last step is normally the deletion; `track` is the safety net;
|
|
156
|
+
- staging needs no confirmation. In any other environment a person confirms every run (typing the environment name,
|
|
157
|
+
or the dialog in the web UI). `--read-only` leaves the write cases out, and `--allow-writes=<env>` skips the prompt
|
|
158
|
+
for unattended runs (AI agents never use it);
|
|
159
|
+
- write cases are never retried and run one at a time per profile;
|
|
160
|
+
- a case that is not allowed to write in the current environment is **skipped with a notice**, not failed;
|
|
161
|
+
- whatever could not be removed is listed in red in the report and by `pnpm cleanup --env <env>`; delete it by hand,
|
|
162
|
+
then `--forget`.
|
|
163
|
+
|
|
164
|
+
Email is never sent in production, and elsewhere only when the profile has `email: true`. A step that sends email is
|
|
165
|
+
wrapped in the spec with `emailStepAllowed(profile, '<step title>')`, which records the skip in the report.
|
|
166
|
+
|
|
167
|
+
Never write a case for the login page or the login flow: login is a person's job (`pnpm signin`), not a test.
|
|
168
|
+
|
|
169
|
+
## What `pnpm case:check` validates
|
|
170
|
+
|
|
171
|
+
It reads every case (or the ids you pass) and prints one line per case. It **fails** on:
|
|
172
|
+
|
|
173
|
+
- invalid front matter (missing `suites`, a bad `writes`/`envs` combination, `writes` without `writes-note`);
|
|
174
|
+
- a target card without its required fields (`What`, `Where`, `Identified by`), or a reference image outside
|
|
175
|
+
`.explore/targets/<case-id>/`;
|
|
176
|
+
- a `[T<n>]` reference with no card;
|
|
177
|
+
- an invalid `## Values` block (bad YAML, an unknown profile, a non-map entry).
|
|
178
|
+
|
|
179
|
+
It **warns** (usable, but something to settle) on: a reference image missing on this machine (`.explore/` is
|
|
180
|
+
git-ignored), a card without a reference line, a check on an image or file with no target card, no `## Values`, a
|
|
181
|
+
`[input]`/`[writes]` step with no `- Recipe:` line, the old `## Inputs` / `## Data` sections, and question blocks
|
|
182
|
+
that cannot be answered in place.
|
|
183
|
+
|
|
184
|
+
## Rename or move a case
|
|
185
|
+
|
|
186
|
+
Use the Cases page (click the file name or the pencil icon). The spec and the baseline images move with it and the
|
|
187
|
+
spec is rewritten to match. By hand, move the three things together (`cases/...md`, `tests/...spec.ts`, its
|
|
188
|
+
`*-snapshots/`), and keep the spec's `// case:` line and its imports unchanged: specs import the framework by package
|
|
189
|
+
name, so their folder depth does not change the imports.
|
package/docs/commands.md
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# Command reference
|
|
2
|
+
|
|
3
|
+
Every command runs in a workspace (the folder that owns `profiles.yaml`); see
|
|
4
|
+
[install.md](./install.md#which-workspace-a-command-runs-against). In a project the scripts of `package.json` are
|
|
5
|
+
thin wrappers, so `pnpm asrun`, `pnpm signin` and the rest work as below. The same commands through the package are
|
|
6
|
+
`pnpm exec asrun ...`.
|
|
7
|
+
|
|
8
|
+
| I want to... | Command |
|
|
9
|
+
|---|---|
|
|
10
|
+
| Install / update the framework | `pnpm dlx @asrunjs/asrun init --install`, `pnpm add -D @asrunjs/asrun@latest` |
|
|
11
|
+
| Prepare or check the workspace | `pnpm asrun init [--update]`, `pnpm asrun doctor` |
|
|
12
|
+
| Start the web UI | `pnpm go`, `pnpm ui`, `pnpm ui:dev` |
|
|
13
|
+
| See which profiles are logged in | `pnpm status [--env <env>]` |
|
|
14
|
+
| Log in a profile (a person, with 2FA) | `pnpm signin <profile-id>` |
|
|
15
|
+
| Run tests | `pnpm asrun --env <env> [filters]` |
|
|
16
|
+
| Open the last report | `pnpm report` |
|
|
17
|
+
| Look around a page as a profile | `pnpm explore <profile-id> [path]` |
|
|
18
|
+
| Read what the last report says about a case | `pnpm heal:evidence --case <case-id> --env <env>` |
|
|
19
|
+
| List or forget test data that could not be removed | `pnpm cleanup --env <env> [--forget]` |
|
|
20
|
+
| Create or validate a case | `pnpm case:new <id>`, `pnpm case:check [case-id]` |
|
|
21
|
+
| Check a spec | `pnpm spec:lint [case-id]`, `pnpm baselines:review [case-id] [--new]` |
|
|
22
|
+
| Look at a PDF | `pnpm pdf:render <file.pdf> --out <dir> [--pages all] [--corner top-left]` |
|
|
23
|
+
| Type-check the workspace | `pnpm typecheck` |
|
|
24
|
+
|
|
25
|
+
## Running tests: `pnpm asrun`
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pnpm asrun --env staging
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
`--env` is **required**; there is no default. A run selects the profiles whose `env` matches, checks their sessions,
|
|
32
|
+
runs the selected cases once per profile, and writes one report folder per run.
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pnpm asrun --env production --profile example-production
|
|
36
|
+
pnpm asrun --env production --skip-profile example-staging,example-production
|
|
37
|
+
pnpm asrun --env staging --tag us
|
|
38
|
+
pnpm asrun --env production --logged-in
|
|
39
|
+
pnpm asrun --env production --suite smoke
|
|
40
|
+
pnpm asrun --env production --suite smoke,regression
|
|
41
|
+
pnpm asrun --env production --grep landing
|
|
42
|
+
pnpm asrun --env production --read-only
|
|
43
|
+
pnpm asrun --env staging --ui
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
| Flag | Effect |
|
|
47
|
+
|---|---|
|
|
48
|
+
| `--env <env>` | Required. The environment to run. |
|
|
49
|
+
| `--profile a,b` | Only these profiles. |
|
|
50
|
+
| `--skip-profile a,b` | Every profile except these (for example the ones that are not logged in). |
|
|
51
|
+
| `--tag <tag>` | Only the profiles that carry the tag (comma-separated for several). |
|
|
52
|
+
| `--logged-in` | Only the profiles whose saved session works; the others are left out instead of failing. |
|
|
53
|
+
| `--suite <name>` | Only the cases whose front matter lists the suite (comma-separated; a case in two chosen suites runs once). `--priority` is the old name. |
|
|
54
|
+
| `--grep <text>` | Only the cases whose title matches (Playwright `--grep`). |
|
|
55
|
+
| `--read-only` | Leave out every case that writes data. |
|
|
56
|
+
| `--allow-writes=<env>` | Run the write cases in that environment without the prompt. For unattended runs only; AI agents never use it. |
|
|
57
|
+
| `--step-text` | Overlay the current test step on the videos (`=1\|true\|yes\|on`; off by default). |
|
|
58
|
+
| `--update-baselines <case,case>` | Overwrite the screenshot baselines of exactly these cases. See below. |
|
|
59
|
+
| `--report-id <id>` | Name of the report folder (default: the timestamp). |
|
|
60
|
+
| `--ui`, other Playwright options | Passed through to Playwright. `--ui` opens its interactive mode. |
|
|
61
|
+
|
|
62
|
+
A case that writes data runs only in the environments it lists under `writes`; elsewhere it is skipped with a notice.
|
|
63
|
+
In **staging** no confirmation is needed. In any other environment a person confirms every run: `pnpm asrun` lists
|
|
64
|
+
the write cases and asks you to type the environment name; the web UI asks in a dialog. Unattended runs combine the
|
|
65
|
+
prompt with `--allow-writes=<env>`; AI agents must not. `--read-only` leaves the cases out instead.
|
|
66
|
+
|
|
67
|
+
## Reports and evidence
|
|
68
|
+
|
|
69
|
+
Every run writes to `reports/<run-id>/`, where the run id is the timestamp and the environment
|
|
70
|
+
(`reports/2026-10-28T07-30-22-staging`), and points `reports/latest` at the newest one.
|
|
71
|
+
|
|
72
|
+
- **Video:** one per test, with the recordings of every browser tab joined in order into `reports/<run>/videos/`.
|
|
73
|
+
Joining needs ffmpeg; without it the report lists the recordings separately. `--step-text` writes the current step
|
|
74
|
+
on the video.
|
|
75
|
+
- **Trace:** the page before and after every action, plus every network request. Open a trace directly with
|
|
76
|
+
`pnpm exec playwright show-trace <trace.zip>`.
|
|
77
|
+
- **Screenshots:** on failure, and the baselines of visual checks with their diff.
|
|
78
|
+
- **Leftover test data:** listed in red in the report and in the terminal.
|
|
79
|
+
|
|
80
|
+
`pnpm report` opens the HTML report of the latest run. `pnpm asrun` also prints the paths of the videos and traces
|
|
81
|
+
when it finishes. Recordings contain real pages: do not share them outside the team.
|
|
82
|
+
|
|
83
|
+
## Baselines
|
|
84
|
+
|
|
85
|
+
Baselines are per profile (`...-example-staging-darwin.png`) and are the source of truth for pixel checks.
|
|
86
|
+
|
|
87
|
+
Fix the spec first (the check must look at the element its target card names), then:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
pnpm asrun --env staging --profile example-staging --grep header-logo --update-baselines header-logo
|
|
91
|
+
pnpm baselines:review header-logo
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
- `--update-baselines` lists the cases whose images are overwritten; they must be among the cases the filters run.
|
|
95
|
+
Playwright has one global switch, so those cases run as a second pass with their own report
|
|
96
|
+
(`<run-id>-baselines`, for example `2026-10-28T07-30-22-staging-baselines`); every other case is compared with its
|
|
97
|
+
baselines as usual.
|
|
98
|
+
- Without it, Playwright's own `--update-snapshots` still rewrites every image of the run.
|
|
99
|
+
- In the web UI every case in **Cases to run** has an **Update baselines** box (unticked by default), with a
|
|
100
|
+
confirmation, and the new images are shown next to their target cards when the run ends.
|
|
101
|
+
- An agent never updates a baseline: that is a person's decision.
|
|
102
|
+
|
|
103
|
+
## Login and sessions
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
pnpm status # every profile, or: pnpm status --env staging
|
|
107
|
+
pnpm signin example-staging
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
The login is done by a person, including 2FA; the session is saved to `.auth/<profile-id>.json` and verified. See
|
|
111
|
+
[profiles.md](./profiles.md).
|
|
112
|
+
|
|
113
|
+
## Cases and checks
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
pnpm case:new header-logo
|
|
117
|
+
pnpm case:new documents/header-logo
|
|
118
|
+
pnpm case:check
|
|
119
|
+
pnpm spec:lint header-logo
|
|
120
|
+
pnpm baselines:review --new
|
|
121
|
+
pnpm pdf:render report.pdf --out .explore/targets/header-logo --corner top-left
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
- `case:new` creates the case from the template. One optional group folder, created on demand.
|
|
125
|
+
- `case:check` validates front matter, values and target cards; see [cases.md](./cases.md).
|
|
126
|
+
- `spec:lint` is a static check of the specs: it fails on sleeps, `networkidle`, image checks without a target,
|
|
127
|
+
`.first()` on images, raw download events, a magic-number-only PDF check. `pnpm spec:lint <case-id>` limits it.
|
|
128
|
+
- `baselines:review` opens nothing; it lists every baseline next to the target card that says what it must show.
|
|
129
|
+
`--new` lists only the images that are new or changed (by git).
|
|
130
|
+
- `pdf:render` writes page images and prints the text, so a person (or an exploring agent) can look at a PDF.
|
|
131
|
+
|
|
132
|
+
## Exploring and healing evidence
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
pnpm explore example-staging /app/home
|
|
136
|
+
pnpm heal:evidence --case header-logo --env staging
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
`explore` opens a logged-in browser at the profile's `startPath` (or the path you pass, or the site root). Downloads
|
|
140
|
+
are saved to `~/Downloads` and never overwrite an existing file. `heal:evidence` prints the last run's status, failed
|
|
141
|
+
step, error, call log and screenshots for a case, and whether the report is stale; it is what `/heal-spec` reads
|
|
142
|
+
first.
|
|
143
|
+
|
|
144
|
+
## Cleanup
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
pnpm cleanup --env staging
|
|
148
|
+
pnpm cleanup --env staging --forget
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
A write case registers everything it creates (named `[asrun] ...`) and removes it after the test, also after a
|
|
152
|
+
failure. Whatever could not be removed is listed in the report and by `cleanup`. Delete it by hand in the product,
|
|
153
|
+
then run `--forget` so it is not listed again. `--env` is required.
|
|
154
|
+
|
|
155
|
+
## Web UI
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
pnpm go # install + build + start + open the browser (the usual command)
|
|
159
|
+
pnpm ui # build the web app and serve http://127.0.0.1:4173
|
|
160
|
+
pnpm ui:dev # Vite on :5173 with hot reload, API in watch mode
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
The UI only listens on `127.0.0.1`. Set `ASRUN_UI_PORT` to change the port (`ASRUN_UI_WEB_PORT` for the Vite dev
|
|
164
|
+
server). See [web-ui.md](./web-ui.md).
|
|
165
|
+
|
|
166
|
+
## Workspace
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
pnpm asrun init [--update] [--force <file>] [--reset-rules] [--root <dir>] [--no-env] [--install [spec]]
|
|
170
|
+
pnpm asrun doctor
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
`init` writes the workspace files; `--update` refreshes only the generated ones. `doctor` checks profiles,
|
|
174
|
+
`tsconfig.json`, `playwright.config.ts`, the generated files, the installed version, the project rule table and the
|
|
175
|
+
skill overrides. Full details in [install.md](./install.md).
|