@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
package/README.md
CHANGED
|
@@ -1,410 +1,221 @@
|
|
|
1
|
-
# AsRun
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
- **
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
-
|
|
17
|
-
`
|
|
18
|
-
- **
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
|
136
|
-
|---|---|
|
|
137
|
-
|
|
|
138
|
-
|
|
|
139
|
-
|
|
|
140
|
-
|
|
|
141
|
-
|
|
|
142
|
-
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
no default). The agent replays the case on the real pages with your saved sessions (it never writes data), writes `tests/header-logo.spec.ts`, fills the
|
|
223
|
-
case's `## Values` for the profiles it explored and checks the files (`pnpm typecheck`, `pnpm case:check`). It does not run the
|
|
224
|
-
spec and does not create screenshot baselines: running specs is a separate step (6). It does not stop to ask: what the
|
|
225
|
-
page answers is written into the case as observed (QA confirms it), and what is still unknown stays in the case as a
|
|
226
|
-
`TODO`. It ends with `RESULT: ready` or `RESULT: needs-input`. A case with a `[writes]` or `[input]` step that has no
|
|
227
|
-
recipe under `## Inputs` is not generated: it stops with `needs-input` and asks you to run step 2 with `--attended`.
|
|
228
|
-
If the spec already exists, the command leaves it alone and says so. Add `--force` (`/gen-spec header-logo staging
|
|
229
|
-
--force`) to regenerate it from the case and overwrite it (the case's `## Values` and baseline images are kept; the
|
|
230
|
-
report lists the ones that may be out of date). In the web UI, **Run** on a case that has a spec
|
|
231
|
-
asks first: "Overwrite the spec?". The server refuses to start `gen-spec` on an existing spec without that choice.
|
|
232
|
-
5. **Review what the AI produced.** Check: the spec reads like your case, and the `## Values` block of the case has the
|
|
233
|
-
right values for each profile (you normally don't edit these by hand, but you can).
|
|
234
|
-
6. **Run it:** `pnpm asrun --env <env> --grep <case-id>`. If the spec has visual checks, add `--update-snapshots`
|
|
235
|
-
the first time, then open the new images in `tests/<case>.spec.ts-snapshots/` and confirm each is the correct logo
|
|
236
|
-
for that profile. Baselines are the source of truth for pixel checks. Then run all profiles and `pnpm report`.
|
|
237
|
-
|
|
238
|
-
When the product changes and a test fails, run `/heal-spec <case-id> <env> [--profiles a,b] [--report <run-id>] [--hints <file>]`
|
|
239
|
-
(or **Heal spec** in the Cases toolbar, or **Heal this failure** on a failed result on the Reports page). The agent
|
|
240
|
-
decides whether the test needs updating or whether it found a real problem, and never edits an expectation just to
|
|
241
|
-
make a failure disappear. How it works:
|
|
242
|
-
- The environment is always required. Only the profiles that are logged in there are run (`pnpm asrun --logged-in`
|
|
243
|
-
leaves the others out instead of failing them).
|
|
244
|
-
- The web UI first opens a dialog: the profiles to heal (every logged-in profile ticked) and an optional text box for what
|
|
245
|
-
you want healed or any evidence and clues. The text is stored in `.tmp/hints/` (git-ignored) and passed as `--hints`.
|
|
246
|
-
Clues guide the diagnosis; they never lower the bar for a product finding.
|
|
247
|
-
- The agent reads the existing report first (`pnpm heal:evidence --case <id> --env <env>`: status, failed step, error,
|
|
248
|
-
call log and screenshots per profile, and whether the report is fresh or stale) and runs the case again only when
|
|
249
|
-
there is no report, it is stale, it does not explain the failure, or a clue has to be checked. After it edits the
|
|
250
|
-
spec it runs it once to confirm.
|
|
251
|
-
|
|
252
|
-
### Targets, readiness and downloads (why generated specs can be trusted)
|
|
253
|
-
Three rules keep an AI-written spec from passing or failing for the wrong reason. They hold in exploring, generating and
|
|
254
|
-
healing (`AGENTS.md`, `<framework>/.claude/skills/exploring-cases/references/assertion-targets.md` and `readiness.md`).
|
|
255
|
-
- **Targets.** A check on a logo, image, icon, file, PDF, screenshot or repeated text points to a card under `## Targets`
|
|
256
|
-
in the case (`[T1]`): what it is (and what it is not), where it is, how it is identified (never `.first()` or a bare
|
|
257
|
-
`img`), when it is ready, and a highlighted reference screenshot under `.explore/targets/`. When the agent is in doubt
|
|
258
|
-
it does not pick: it writes a `🚫 TODO [blocking]` that asks for the exact image, text, place or UI, with the
|
|
259
|
-
candidates listed. Answer it by editing the case (or, in an attended run, by outlining the target in the browser).
|
|
260
|
-
- **Readiness.** After an action that loads something, the key requests must finish and the content must be visible before
|
|
261
|
-
anything is read. Specs use `actAndWait` (`@asrunjs/asrun/ready`); "missing" and "empty" checks need a positive ready signal.
|
|
262
|
-
Healing treats a "missing / empty / timeout" failure as a readiness problem first.
|
|
263
|
-
- **Downloads.** `verifyDownload` (`@asrunjs/asrun/downloads`) checks that the download starts, finishes, the file exists and the
|
|
264
|
-
file is valid for its kind; any failure or timeout fails the whole step with a message that names the stage.
|
|
265
|
-
- **PDFs are read like a person reads them.** `expectPdf` (`@asrunjs/asrun/pdf`) renders the pages to images (a logo region must not
|
|
266
|
-
be blank and must match the profile's baseline, 2% tolerance) and extracts the text (for example, no other profile's name).
|
|
267
|
-
Every page is checked unless the case limits the pages in strong words ("only", "exactly", "must"); "should",
|
|
268
|
-
"always", "usually" or no wording mean every page.
|
|
269
|
-
`pnpm pdf:render <file.pdf> --out .explore/targets/<case-id>/<name> --corner top-left` lets you (or the exploring agent)
|
|
270
|
-
look at the rendered pages.
|
|
271
|
-
- **Checks you can run yourself:** `pnpm spec:lint` (fails on screenshots without a target, `.first()` on images, sleeps,
|
|
272
|
-
`networkidle`, raw download events, a `%PDF` check on its own), `pnpm baselines:review` (opens nothing; lists every
|
|
273
|
-
baseline with the card that says what it must show, so you can look at each new image), and `pnpm case:check`
|
|
274
|
-
(fails on an incomplete card, warns on an image check with no card).
|
|
275
|
-
|
|
276
|
-
### Updating baselines on purpose
|
|
277
|
-
If a profile really changed its logo, or a baseline is wrong: fix the spec first (the check must look at the element
|
|
278
|
-
its target card names; a wrong locator just makes a new wrong image), then
|
|
279
|
-
`pnpm asrun --env <env> --profile <id> --grep <case> --update-baselines <case-id>[,<case-id>]`, then look at the new
|
|
280
|
-
image before keeping it (`pnpm baselines:review <case-id>`).
|
|
281
|
-
- `--update-baselines` names the cases whose images are overwritten (`--update-snapshots=all`). They must be among the
|
|
282
|
-
cases the filters run. Playwright has one global switch, so they run as a second pass with their own report
|
|
283
|
-
(`<run-id>-baselines`); every other case is compared with its baselines as usual.
|
|
284
|
-
- Without `--update-baselines`, `--update-snapshots` still works as before, but it rewrites every image of the run.
|
|
285
|
-
- In the web UI, every case in **Cases to run** has an **Update baselines** box (nothing is ticked by default). A
|
|
286
|
-
confirm dialog follows, and when the run ends the job output shows each rewritten image next to the target card that
|
|
287
|
-
says what it must show.
|
|
288
|
-
- Not part of exploring, generating or healing: an agent never updates a baseline.
|
|
289
|
-
|
|
290
|
-
### Suites
|
|
291
|
-
Each case lists its suites in the front matter: `suites: [smoke, regression]` (any lowercase words). A case runs in
|
|
292
|
-
every suite it lists. The generated spec tags itself `@<suite>` for each one, which:
|
|
293
|
-
- shows as badges next to the test in the terminal and in `pnpm report`, and can be typed into the report's
|
|
294
|
-
search box (`@smoke`) to filter there;
|
|
295
|
-
- drives `pnpm asrun --suite <name>`, which can be combined with `--profile` and `--grep`. A case that is in two
|
|
296
|
-
of the chosen suites still runs once.
|
|
297
|
-
|
|
298
|
-
An unknown suite is rejected with the list of suites the cases use. The old `priority: <level>` is read as
|
|
299
|
-
`suites: [<level>]`.
|
|
300
|
-
|
|
301
|
-
### Other front matter
|
|
302
|
-
- `writes: [staging]` or `writes: [staging, production]` (omit it for a read-only case): the environments in which the
|
|
303
|
-
case may create or change data of the test accounts (and cleans it up). It then needs
|
|
304
|
-
`writes-note: <what it creates or changes>`. In an environment that is not listed, the case is **skipped with a
|
|
305
|
-
notice**, not failed. The old `writes: test-data` is refused with this hint.
|
|
306
|
-
- `envs: [production, staging]` (optional): the case runs only in these environments; in others it is skipped. Every
|
|
307
|
-
environment in `writes` must also be in `envs`.
|
|
308
|
-
|
|
309
|
-
Cases that write data get a red **WRITES** badge in the web UI and the report, and an `@writes` tag.
|
|
310
|
-
|
|
311
|
-
## What a case may and may not do
|
|
312
|
-
Tests run against production and staging. By default a case is read-only: look, read, compare. No creating,
|
|
313
|
-
editing, deleting, submitting or accepting anything.
|
|
314
|
-
|
|
315
|
-
A case may write only in the environments listed in its `writes` front matter (elsewhere it is skipped with a notice),
|
|
316
|
-
and only data of the test account (named with the prefix `[asrun]`), which it must clean up.
|
|
317
|
-
**AI agents never write data while they explore or generate**, in either environment: a person does those steps, in
|
|
318
|
-
the browser, in an attended exploration (step 2 above).
|
|
319
|
-
- **Staging:** no confirmation needed.
|
|
320
|
-
- **Production (and any environment that is not staging)**, for a case that lists it under `writes`: a person confirms every run. `pnpm asrun --env production` lists
|
|
321
|
-
the write cases and asks you to type `production`. The web UI shows a confirm dialog. Unattended runs use
|
|
322
|
-
`--allow-writes=production`; AI agents never do. `--read-only` leaves the write cases out. The permission is also
|
|
323
|
-
enforced inside the test itself, so running `playwright test` directly cannot bypass it.
|
|
324
|
-
- **Cleanup:** data is named `[asrun] ...`, registered with the `records` fixture and removed after the test, also
|
|
325
|
-
after a failure. Whatever could not be removed is shown in red in the report and listed by
|
|
326
|
-
`pnpm cleanup --env <env>`; delete it by hand, then `pnpm cleanup --env <env> --forget`.
|
|
327
|
-
- **No retries, one at a time:** write cases are never retried, and run one at a time per profile.
|
|
328
|
-
- **Email:** never sent from production. In other environments only if the profile has `email: true` in `profiles.yaml`.
|
|
329
|
-
|
|
330
|
-
## Author
|
|
331
|
-
Created by Zoro Chen.
|
|
332
|
-
|
|
333
|
-
## Layout
|
|
334
|
-
|
|
335
|
-
**In a project that uses the framework** (everything here is committed except the ignored folders):
|
|
336
|
-
|
|
337
|
-
```
|
|
338
|
-
profiles.yaml one entry per environment: URL, account label, tags, environment (no expected values)
|
|
339
|
-
cases/ human-written cases (+ _template.md); optional group folders: cases/<group>/<name>.md
|
|
340
|
-
tests/ generated specs (same folders as cases/), and *-snapshots/ baseline images per profile
|
|
341
|
-
tsconfig.json plain compiler options: specs import the framework by package name (`@asrunjs/asrun/fixtures`)
|
|
342
|
-
playwright.config.ts re-exports node_modules/@asrunjs/asrun/dist/playwright.config.js
|
|
343
|
-
AGENTS.md the project rules: they point at node_modules/@asrunjs/asrun/AGENTS.md and narrow it (CLAUDE.md imports AGENTS.md)
|
|
344
|
-
.claude/commands/ /make-case, /explore-case, /gen-spec, /heal-spec: thin shells that load the framework skill
|
|
345
|
-
.claude/skills/ optional project overrides of a skill: <skill>.md
|
|
346
|
-
.mcp.json, opencode.jsonc the playwright-test MCP server, for Claude Code and OpenCode
|
|
347
|
-
.env ASRUN_ROOT (which workspace the commands use) and local settings (git-ignored)
|
|
348
|
-
node_modules/@asrunjs/asrun/ the framework (installed dependency; never edited)
|
|
349
|
-
.auth/ saved logins, one per profile (git-ignored)
|
|
350
|
-
reports/ one folder per run; reports/latest points to the newest (git-ignored)
|
|
351
|
-
```
|
|
352
|
-
|
|
353
|
-
**In the framework repository** (no workspace content at the root: no `profiles.yaml`, `cases/`, `tests/` or reports):
|
|
354
|
-
|
|
355
|
-
```
|
|
356
|
-
lib/ fixtures, session guard, image assertions, case values (imported as asrun/<module>)
|
|
357
|
-
scripts/ the pnpm commands above, plus init (workspace files) and doctor (checks)
|
|
358
|
-
templates/ what `pnpm asrun init` writes into a project
|
|
359
|
-
ui/ local web UI: server/ (Hono API, job runner), web/ (React + Vite), shared/ (types)
|
|
360
|
-
docs/ static docs; theme-guide.html = the Inkstone theme (designed and created by Zoro Chen): colors, buttons, icons of the web UI (open in a browser, or via the "Inkstone theme" link in the UI's top bar)
|
|
361
|
-
examples/demo/ a local workspace for developing the framework (only README and .gitignore are committed)
|
|
362
|
-
.claude/ skills + slash commands for the AI workflow (shared by OpenCode and Claude Code)
|
|
363
|
-
.opencode/ symlink: commands -> ../.claude/commands (OpenCode only reads commands here)
|
|
364
|
-
AGENTS.md the framework baseline rules (OpenCode); CLAUDE.md imports it for Claude Code
|
|
365
|
-
opencode.jsonc MCP config for OpenCode; .mcp.json is the same server for Claude Code
|
|
366
|
-
```
|
|
367
|
-
|
|
368
|
-
## Developing the framework
|
|
369
|
-
|
|
370
|
-
`pnpm go`, `pnpm asrun`, `pnpm ui` work here too: without a workspace they use `examples/demo`. `init` writes the
|
|
371
|
-
framework's `.env` for that (`ASRUN_ROOT=examples/demo`), so the commands need no environment variable:
|
|
372
|
-
|
|
373
|
-
```bash
|
|
374
|
-
pnpm asrun init --root examples/demo # creates the demo workspace and the framework-root .env
|
|
375
|
-
pnpm asrun --env staging --read-only
|
|
376
|
-
```
|
|
377
|
-
|
|
378
|
-
The demo is a workspace of its own as well:
|
|
379
|
-
|
|
380
|
-
```bash
|
|
381
|
-
cd examples/demo && ASRUN_ROOT=. pnpm asrun doctor
|
|
382
|
-
```
|
|
383
|
-
|
|
384
|
-
`ASRUN_ROOT` still wins over `.env` (a real environment variable always does), so one checkout can be pointed at
|
|
385
|
-
another project for a single command:
|
|
386
|
-
|
|
387
|
-
```bash
|
|
388
|
-
ASRUN_ROOT=../my-real-project pnpm asrun --env staging --read-only
|
|
389
|
-
```
|
|
390
|
-
|
|
391
|
-
Two paths keep the two halves apart: `ROOT` (`lib/paths.ts`) is the workspace, `FRAMEWORK_ROOT` is this folder. The
|
|
392
|
-
package is compiled to `dist/` (`pnpm build`) and the web UI to `ui/dist` (`pnpm ui`); both are shipped by `prepack`.
|
|
393
|
-
Nothing writes into `FRAMEWORK_ROOT` at run time. After changing `lib/`, run `pnpm build` before exercising the demo's
|
|
394
|
-
specs (they import the package by name, which resolves to `dist/`). After changing `templates/`, bump `version` in
|
|
395
|
-
`package.json`; `pnpm asrun doctor` in a project then shows that it is behind.
|
|
396
|
-
|
|
397
|
-
## How profiles become tests
|
|
398
|
-
Every entry in `profiles.yaml` becomes a Playwright project (name = profile id) with its own `baseURL` and saved login.
|
|
399
|
-
A spec is written once and runs once per profile; the `profile` fixture exposes that profile's `id`, `env`, `baseURL`,
|
|
400
|
-
`account`, `tags` and `email`. The expected values come from the case document, through
|
|
401
|
-
`requireValues(caseId, profile)` (its `## Values` block, `default` merged with the profile's own entry). Adding a
|
|
402
|
-
profile = add a YAML entry (by hand or with the form on the Profiles page) + `pnpm signin <id>`, then add the values
|
|
403
|
-
for it to the cases (see the guide on the Profiles page).
|
|
404
|
-
|
|
405
|
-
## Troubleshooting
|
|
406
|
-
- `Profile "x" is not logged in`: `pnpm signin <profile-id>`, or run with `--skip-profile <profile-id>`.
|
|
407
|
-
- Screenshot mismatch: open the report; the diff view shows expected / actual / diff. If the change is
|
|
408
|
-
intended, update the baseline as above.
|
|
409
|
-
- Report empty or missing: run `pnpm asrun --env <env>` first.
|
|
410
|
-
- `Choose the environment to test`: there is no default; add `--env production` or `--env staging`.
|
|
1
|
+
# AsRun
|
|
2
|
+
|
|
3
|
+
End-to-end tests for web apps with more than one face. People write test cases in plain language; an AI agent turns
|
|
4
|
+
them into Playwright specs; every spec runs once per profile — production and staging, account A and account B — and
|
|
5
|
+
leaves a video, screenshots and a trace to review.
|
|
6
|
+
|
|
7
|
+
AsRun is a devDependency, not a service. Your profiles, cases, specs and baselines are files in your repository.
|
|
8
|
+
|
|
9
|
+
## What it does
|
|
10
|
+
|
|
11
|
+
- **Plain-language cases.** `cases/<id>.md` is the source of truth: numbered steps, expected behaviour, the exact
|
|
12
|
+
values, and the visual targets a check may look at. A person can review it without reading code.
|
|
13
|
+
- **One spec, every profile.** Each entry in `profiles.yaml` becomes a Playwright project with its own base URL,
|
|
14
|
+
saved login and screenshot baselines. Two accounts of one site give two independent runs.
|
|
15
|
+
- **An AI workflow, not a prompt dump.** `/make-case` turns raw notes into cases, `/explore-case` checks them against
|
|
16
|
+
the real pages, `/gen-spec` writes the spec, `/heal-spec` repairs it from the last report. Each command ends with
|
|
17
|
+
`RESULT: ready` or `RESULT: needs-input`, so it works unattended.
|
|
18
|
+
- **Evidence for every run.** One video per test (browser tabs joined), a trace with every action and request,
|
|
19
|
+
screenshots on failure, and a report that puts the failure first.
|
|
20
|
+
- **Safe by default.** A case is read-only unless it lists environments under `writes`; writing anywhere except
|
|
21
|
+
staging needs a person's confirmation on every run; email never leaves production; logins are done by a person, 2FA
|
|
22
|
+
included.
|
|
23
|
+
- **A local web UI.** Profiles and logins, a case editor with a form panel, AI runs with live logs, and reports. All
|
|
24
|
+
of it on `127.0.0.1`.
|
|
25
|
+
- **Fits a terminal and a pipeline.** Filter by environment, profile, tag, suite or case; one report folder per run;
|
|
26
|
+
a failing test fails the command.
|
|
27
|
+
- **No hidden state.** The framework lives in `node_modules/@asrunjs/asrun` and is never edited; a project commits
|
|
28
|
+
only its own test content.
|
|
29
|
+
|
|
30
|
+
## Requirements
|
|
31
|
+
|
|
32
|
+
- **Node.js 22+** and **pnpm**.
|
|
33
|
+
- **ffmpeg** to join the recordings of one test into a video. `pnpm go` installs it with Homebrew or winget; on Linux
|
|
34
|
+
run `sudo apt install ffmpeg`. When it is missing, the bundled `ffmpeg-static` is used; if neither works, the report
|
|
35
|
+
lists the videos separately.
|
|
36
|
+
- **Claude Code** or **OpenCode** for the AI steps. Everything else works without them.
|
|
37
|
+
|
|
38
|
+
## Install
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pnpm dlx @asrunjs/asrun init --install
|
|
42
|
+
pnpm go
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The first command adds AsRun and creates the workspace files (`profiles.yaml`, `cases/`, `tests/`, `AGENTS.md`, the
|
|
46
|
+
commands for both AI tools, `.env`, the configs); run it in the project folder, which may be empty. The second installs
|
|
47
|
+
the dependencies and Chromium, runs the checks, starts the web UI at http://127.0.0.1:4173 and opens it. After the
|
|
48
|
+
first time, `pnpm go` is enough.
|
|
49
|
+
|
|
50
|
+
To update:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pnpm add -D @asrunjs/asrun@latest
|
|
54
|
+
pnpm asrun init --update
|
|
55
|
+
pnpm asrun doctor
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
`init --update` refreshes only the generated files; it never overwrites `AGENTS.md`, `CLAUDE.md` or your skill
|
|
59
|
+
overrides. `doctor` checks profiles, generated files, the installed version and the project rules.
|
|
60
|
+
|
|
61
|
+
<details>
|
|
62
|
+
<summary>Manual install or a pinned version</summary>
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
pnpm add -D @asrunjs/asrun # or @asrunjs/asrun@<version>
|
|
66
|
+
pnpm asrun init
|
|
67
|
+
pnpm go
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
On pnpm 12, run `init` before adding the dependency: it writes the `allowBuilds` entry that `esbuild` and
|
|
71
|
+
`ffmpeg-static` need, or `pnpm add` stops with `ERR_PNPM_IGNORED_BUILDS`. Details and flags:
|
|
72
|
+
[install.md](./docs/install.md).
|
|
73
|
+
|
|
74
|
+
</details>
|
|
75
|
+
|
|
76
|
+
## Your first test
|
|
77
|
+
|
|
78
|
+
1. **Point at your site.** Edit `profiles.yaml`: `id`, `name`, `env` (`staging` or `production`), `baseURL`, optional
|
|
79
|
+
`account` and `tags`. Expected values never go here; they live in the case.
|
|
80
|
+
2. **Log in once.** `pnpm signin example-staging` opens a browser. Log in yourself, then press **Done, save the
|
|
81
|
+
session** on the card in the corner. The session is saved per profile in `.auth/`.
|
|
82
|
+
3. **Write a case.** `pnpm case:new header-logo` creates `cases/header-logo.md` from the template; or start from raw
|
|
83
|
+
notes with `/make-case notes.md`. Fill in the steps, what is expected and the exact values.
|
|
84
|
+
4. **Check it against the real pages.** `/explore-case header-logo staging` opens the pages (with your saved session),
|
|
85
|
+
writes what they show into the case and turns what it cannot know into questions. Answer them in the case file, or
|
|
86
|
+
in the UI's **Answer** sheet, and run it again.
|
|
87
|
+
5. **Generate, run, read.** `/gen-spec header-logo staging` writes `tests/header-logo.spec.ts` and fills the case's
|
|
88
|
+
`## Values`. Then:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
pnpm asrun --env staging --grep header-logo
|
|
92
|
+
pnpm report
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
The first run of a spec with visual checks writes its baselines: add `--update-snapshots` once, then look at every
|
|
96
|
+
new image before keeping it.
|
|
97
|
+
|
|
98
|
+
In the web UI you never type a command: the **?** icon has the same steps with screenshots, and a case toolbar runs
|
|
99
|
+
each AI step with **Run AI**. The full procedure is in [ai-workflow.md](./docs/ai-workflow.md).
|
|
100
|
+
|
|
101
|
+
## Customize the AI for your project
|
|
102
|
+
|
|
103
|
+
The commands are thin shells; the procedure is a skill in the framework package
|
|
104
|
+
(`.claude/skills/{making-cases,exploring-cases,generating-specs}/`). A project refines a skill with one Markdown file
|
|
105
|
+
named after the skill — not after the command:
|
|
106
|
+
|
|
107
|
+
```markdown
|
|
108
|
+
<!-- .claude/skills/generating-specs.md -->
|
|
109
|
+
- Step titles in `test.step` keep the case's wording, including the number.
|
|
110
|
+
- A helper used by more than one spec goes in `tests/helpers/<name>.ts`.
|
|
111
|
+
- The staging account has no published documents: assert the empty state there instead.
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
It is read after the framework skill and before your project rules. Write only what differs.
|
|
115
|
+
|
|
116
|
+
Rules combine in this order, and a later layer wins where they conflict:
|
|
117
|
+
|
|
118
|
+
1. `F1`–`F6` of the framework baseline (safety, in every project). They cannot be changed.
|
|
119
|
+
2. The framework baseline, `node_modules/@asrunjs/asrun/AGENTS.md`.
|
|
120
|
+
3. The framework skill.
|
|
121
|
+
4. Your override, `.claude/skills/<skill>.md`.
|
|
122
|
+
5. Your own `AGENTS.md` §2: a table of rule ids (`F7`–`F10`) with `narrows` or `replaces`, for example the artifact
|
|
123
|
+
language.
|
|
124
|
+
|
|
125
|
+
`pnpm asrun doctor` validates the table and lists the overrides it sees. An update never rewrites your rules:
|
|
126
|
+
`init --force` refuses `AGENTS.md`, `CLAUDE.md` and `.claude/skills/**` without `--reset-rules`. Never edit the
|
|
127
|
+
framework package itself: an update replaces it, and only the override files are yours. The AI tool stays configurable
|
|
128
|
+
too: `.mcp.json` (Claude Code), `opencode.jsonc` (OpenCode and its model), `.claude/settings.json` (sandbox
|
|
129
|
+
exceptions), or the model picker in the UI's top bar.
|
|
130
|
+
|
|
131
|
+
Details: [ai-workflow.md](./docs/ai-workflow.md#customizing-the-prompts).
|
|
132
|
+
|
|
133
|
+
## Every day (cookbook)
|
|
134
|
+
|
|
135
|
+
| I want to... | Command |
|
|
136
|
+
|---|---|
|
|
137
|
+
| Check the workspace | `pnpm asrun doctor` |
|
|
138
|
+
| See which profiles are logged in | `pnpm status [--env <env>]` |
|
|
139
|
+
| Log in a profile (a person, with 2FA) | `pnpm signin <profile-id>` |
|
|
140
|
+
| Run everything in one environment | `pnpm asrun --env staging` |
|
|
141
|
+
| Run one profile | `pnpm asrun --env production --profile example-production` |
|
|
142
|
+
| Leave out profiles (for example the ones not logged in) | `pnpm asrun --env production --skip-profile example-production` |
|
|
143
|
+
| Run only the profiles with a tag | `pnpm asrun --env staging --tag us` |
|
|
144
|
+
| Run only the profiles that are logged in | `pnpm asrun --env production --logged-in` |
|
|
145
|
+
| Run one case | `pnpm asrun --env production --grep header-logo` |
|
|
146
|
+
| Run a suite | `pnpm asrun --env production --suite smoke` |
|
|
147
|
+
| Leave out the cases that write data | `pnpm asrun --env production --read-only` |
|
|
148
|
+
| Look around a page as a profile | `pnpm explore example-staging /app/home` |
|
|
149
|
+
| Open the last report | `pnpm report` |
|
|
150
|
+
| Read what the last report says about a case | `pnpm heal:evidence --case <case-id> --env staging` |
|
|
151
|
+
| Check the specs | `pnpm spec:lint [case-id]` |
|
|
152
|
+
| List the baselines next to what they must show | `pnpm baselines:review [case-id] [--new]` |
|
|
153
|
+
| Update the baselines of some cases on purpose | `pnpm asrun --env staging --grep <case-id> --update-baselines <case-id>` |
|
|
154
|
+
| List test data that could not be removed | `pnpm cleanup --env staging` |
|
|
155
|
+
| Watch a run step by step | `pnpm asrun --env staging --ui` |
|
|
156
|
+
| Put the current step on the videos | `pnpm asrun --env production --step-text` |
|
|
157
|
+
|
|
158
|
+
The web UI: `pnpm go` (install, build, start, open), `pnpm ui` (build and serve), `pnpm ui:dev` (development with hot
|
|
159
|
+
reload). Each page covers one job: **Profiles** (login state, sign in, visit, add/duplicate/delete), **Cases** (list,
|
|
160
|
+
editor, form panel, spec view, AI runs, notes-to-cases), **Run tests** (filters, update baselines, production
|
|
161
|
+
confirmation), **Reports** (failure first, then video, screenshots, trace, full Playwright report), **Jobs** (live log
|
|
162
|
+
of every command), **Help** and **AI guide**. See [web-ui.md](./docs/web-ui.md).
|
|
163
|
+
|
|
164
|
+
## Safety
|
|
165
|
+
|
|
166
|
+
Tests run against real sites, so the defaults are strict:
|
|
167
|
+
|
|
168
|
+
- **Read-only unless the case says otherwise.** A case may create, edit or delete data only in the environments it
|
|
169
|
+
lists under `writes`, and only data of the test accounts, named `[asrun] ...`, which it must clean up. Elsewhere the
|
|
170
|
+
case is skipped with a notice, not failed.
|
|
171
|
+
- **A person confirms non-staging writes on every run** by typing the environment name, or in a dialog in the UI.
|
|
172
|
+
`--read-only` leaves those cases out; `--allow-writes=<env>` is for unattended runs, and AI agents never use it.
|
|
173
|
+
- **No email from production.** Elsewhere only when the profile has `email: true`.
|
|
174
|
+
- **AI agents never write while exploring or generating.** A step that writes or needs a choice is done by a person in
|
|
175
|
+
the browser, in an attended exploration. Logins are always a person's job, 2FA included.
|
|
176
|
+
- **A failing baseline is a person's decision.** An agent never updates a baseline to make a difference disappear.
|
|
177
|
+
|
|
178
|
+
The rules that enforce this are `F1`–`F10` in [AGENTS.md](./AGENTS.md); a project can tighten or replace the last
|
|
179
|
+
four, never the first six.
|
|
180
|
+
|
|
181
|
+
## Documentation
|
|
182
|
+
|
|
183
|
+
| I want to... | Read |
|
|
184
|
+
|---|---|
|
|
185
|
+
| Install, update, requirements, workspace location | [docs/install.md](./docs/install.md) |
|
|
186
|
+
| `profiles.yaml`, environments, logins, sessions | [docs/profiles.md](./docs/profiles.md) |
|
|
187
|
+
| Write or review a case | [docs/cases.md](./docs/cases.md) |
|
|
188
|
+
| The four AI commands in depth | [docs/ai-workflow.md](./docs/ai-workflow.md) |
|
|
189
|
+
| Every command and flag, reports, baselines | [docs/commands.md](./docs/commands.md) |
|
|
190
|
+
| The local web UI, page by page | [docs/web-ui.md](./docs/web-ui.md) |
|
|
191
|
+
| An error message | [docs/troubleshooting.md](./docs/troubleshooting.md) |
|
|
192
|
+
| Change AsRun itself | [docs/agents/framework-dev.md](./docs/agents/framework-dev.md) |
|
|
193
|
+
| The case document contract, for AI agents | [docs/agents/case-format.md](./docs/agents/case-format.md) |
|
|
194
|
+
|
|
195
|
+
## Maintainers
|
|
196
|
+
|
|
197
|
+
This section is for the people who maintain AsRun; the repository is private, so the instructions below assume
|
|
198
|
+
access to it. It is developed against `examples/demo`, a committed workspace whose site is a local Docker stack
|
|
199
|
+
(Conduit), so nothing runs against a public site.
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
pnpm install
|
|
203
|
+
pnpm asrun init --root examples/demo
|
|
204
|
+
pnpm build # the demo's specs import the package, which resolves to dist/
|
|
205
|
+
pnpm demo:up # Docker Desktop must run
|
|
206
|
+
pnpm demo:seed
|
|
207
|
+
pnpm signin conduit-a # password in examples/demo/README.md
|
|
208
|
+
pnpm asrun --env staging --read-only
|
|
209
|
+
pnpm ui
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
`pnpm demo:reset` returns the database to the state the cases expect (logins must be redone). Before a pull request:
|
|
213
|
+
`pnpm typecheck`, `pnpm build`, `pnpm ui:build`, and the checks above on the demo. A change to `templates/` or the
|
|
214
|
+
shipped docs bumps `version` in `package.json`.
|
|
215
|
+
|
|
216
|
+
Framework layout, the two agent CLIs, the UI theme and copy rules, and where documentation belongs:
|
|
217
|
+
[docs/agents/framework-dev.md](./docs/agents/framework-dev.md).
|
|
218
|
+
|
|
219
|
+
## License
|
|
220
|
+
|
|
221
|
+
MIT. Created by Zoro Chen.
|