@asrunjs/asrun 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/commands/explore-case.md +15 -0
- package/.claude/commands/gen-spec.md +13 -0
- package/.claude/commands/heal-spec.md +23 -0
- package/.claude/commands/make-case.md +8 -0
- package/.claude/settings.json +5 -0
- package/.claude/skills/exploring-cases/SKILL.md +132 -0
- package/.claude/skills/exploring-cases/references/assertion-targets.md +103 -0
- package/.claude/skills/exploring-cases/references/handoff-card.js +128 -0
- package/.claude/skills/exploring-cases/references/open-questions.md +112 -0
- package/.claude/skills/exploring-cases/references/readiness.md +137 -0
- package/.claude/skills/exploring-cases/references/user-handoff.md +147 -0
- package/.claude/skills/generating-specs/SKILL.md +207 -0
- package/.claude/skills/generating-specs/references/conventions.md +148 -0
- package/.claude/skills/making-cases/SKILL.md +64 -0
- package/.claude/skills/making-cases/references/gaps.md +36 -0
- package/.claude/skills/making-cases/references/normalizing.md +38 -0
- package/.claude/skills/making-cases/references/splitting.md +28 -0
- package/.claude/skills/making-cases/references/writes-and-email.md +36 -0
- package/AGENTS.md +154 -0
- package/CLAUDE.md +1 -0
- package/LICENSE +21 -0
- package/README.md +410 -3
- package/bin/asrun.mjs +49 -0
- package/dist/lib/account-lock.d.ts +6 -0
- package/dist/lib/account-lock.js +52 -0
- package/dist/lib/assertions.d.ts +14 -0
- package/dist/lib/assertions.js +39 -0
- package/dist/lib/baselines.d.ts +43 -0
- package/dist/lib/baselines.js +108 -0
- package/dist/lib/case-id.d.ts +11 -0
- package/dist/lib/case-id.js +17 -0
- package/dist/lib/case-ops.d.ts +26 -0
- package/dist/lib/case-ops.js +110 -0
- package/dist/lib/cases.d.ts +29 -0
- package/dist/lib/cases.js +124 -0
- package/dist/lib/cli.d.ts +10 -0
- package/dist/lib/cli.js +18 -0
- package/dist/lib/downloads.d.ts +42 -0
- package/dist/lib/downloads.js +84 -0
- package/dist/lib/email.d.ts +10 -0
- package/dist/lib/email.js +19 -0
- package/dist/lib/ffmpeg.d.ts +8 -0
- package/dist/lib/ffmpeg.js +50 -0
- package/dist/lib/fixtures.d.ts +40 -0
- package/dist/lib/fixtures.js +149 -0
- package/dist/lib/node-cli.d.ts +11 -0
- package/dist/lib/node-cli.js +26 -0
- package/dist/lib/paths.d.ts +25 -0
- package/dist/lib/paths.js +88 -0
- package/dist/lib/pdf.d.ts +71 -0
- package/dist/lib/pdf.js +140 -0
- package/dist/lib/profiles.d.ts +38 -0
- package/dist/lib/profiles.js +121 -0
- package/dist/lib/ready.d.ts +36 -0
- package/dist/lib/ready.js +53 -0
- package/dist/lib/records.d.ts +33 -0
- package/dist/lib/records.js +61 -0
- package/dist/lib/run-filters.d.ts +19 -0
- package/dist/lib/run-filters.js +30 -0
- package/dist/lib/run-summary.d.ts +90 -0
- package/dist/lib/run-summary.js +35 -0
- package/dist/lib/session.d.ts +18 -0
- package/dist/lib/session.js +45 -0
- package/dist/lib/summary-reporter.d.ts +16 -0
- package/dist/lib/summary-reporter.js +146 -0
- package/dist/lib/targets.d.ts +27 -0
- package/dist/lib/targets.js +51 -0
- package/dist/lib/ui-build.d.ts +10 -0
- package/dist/lib/ui-build.js +30 -0
- package/dist/lib/values.d.ts +29 -0
- package/dist/lib/values.js +85 -0
- package/dist/lib/write-gate.d.ts +41 -0
- package/dist/lib/write-gate.js +129 -0
- package/dist/playwright.config.d.ts +2 -0
- package/dist/playwright.config.js +92 -0
- package/dist/scripts/build.d.ts +1 -0
- package/dist/scripts/build.js +20 -0
- package/dist/scripts/check-cases.d.ts +1 -0
- package/dist/scripts/check-cases.js +81 -0
- package/dist/scripts/cleanup.d.ts +1 -0
- package/dist/scripts/cleanup.js +25 -0
- package/dist/scripts/cli.d.ts +1 -0
- package/dist/scripts/cli.js +28 -0
- package/dist/scripts/doctor.d.ts +1 -0
- package/dist/scripts/doctor.js +185 -0
- package/dist/scripts/explore.d.ts +1 -0
- package/dist/scripts/explore.js +54 -0
- package/dist/scripts/go.mjs +188 -0
- package/dist/scripts/heal-evidence.mjs +143 -0
- package/dist/scripts/init.d.ts +16 -0
- package/dist/scripts/init.js +284 -0
- package/dist/scripts/lint-specs.d.ts +1 -0
- package/dist/scripts/lint-specs.js +138 -0
- package/dist/scripts/login.d.ts +1 -0
- package/dist/scripts/login.js +47 -0
- package/dist/scripts/new-case.d.ts +1 -0
- package/dist/scripts/new-case.js +19 -0
- package/dist/scripts/render-pdf.d.ts +1 -0
- package/dist/scripts/render-pdf.js +44 -0
- package/dist/scripts/report.d.ts +1 -0
- package/dist/scripts/report.js +14 -0
- package/dist/scripts/review-baselines.d.ts +1 -0
- package/dist/scripts/review-baselines.js +30 -0
- package/dist/scripts/run.d.ts +1 -0
- package/dist/scripts/run.js +423 -0
- package/dist/scripts/status.d.ts +1 -0
- package/dist/scripts/status.js +31 -0
- package/dist/scripts/ui-build.d.ts +1 -0
- package/dist/scripts/ui-build.js +7 -0
- package/dist/scripts/ui-dev.d.ts +1 -0
- package/dist/scripts/ui-dev.js +3 -0
- package/dist/scripts/ui.d.ts +1 -0
- package/dist/scripts/ui.js +13 -0
- package/dist/ui/dev.d.ts +1 -0
- package/dist/ui/dev.js +36 -0
- package/dist/ui/server/actions.d.ts +91 -0
- package/dist/ui/server/actions.js +352 -0
- package/dist/ui/server/ai-stream.d.ts +41 -0
- package/dist/ui/server/ai-stream.js +202 -0
- package/dist/ui/server/app.d.ts +4 -0
- package/dist/ui/server/app.js +333 -0
- package/dist/ui/server/cases.d.ts +23 -0
- package/dist/ui/server/cases.js +170 -0
- package/dist/ui/server/files.d.ts +5 -0
- package/dist/ui/server/files.js +67 -0
- package/dist/ui/server/index.d.ts +1 -0
- package/dist/ui/server/index.js +33 -0
- package/dist/ui/server/jobs.d.ts +74 -0
- package/dist/ui/server/jobs.js +263 -0
- package/dist/ui/server/profiles.d.ts +16 -0
- package/dist/ui/server/profiles.js +195 -0
- package/dist/ui/server/watch-files.d.ts +4 -0
- package/dist/ui/server/watch-files.js +36 -0
- package/dist/ui/shared/ai-commands.d.ts +78 -0
- package/dist/ui/shared/ai-commands.js +154 -0
- package/dist/ui/shared/case-id.d.ts +1 -0
- package/dist/ui/shared/case-id.js +1 -0
- package/dist/ui/shared/jobs.d.ts +6 -0
- package/dist/ui/shared/jobs.js +7 -0
- package/dist/ui/shared/product.d.ts +3 -0
- package/dist/ui/shared/product.js +3 -0
- package/dist/ui/shared/run-filters.d.ts +1 -0
- package/dist/ui/shared/run-filters.js +1 -0
- package/dist/ui/shared/site-address.d.ts +6 -0
- package/dist/ui/shared/site-address.js +11 -0
- package/dist/ui/shared/types.d.ts +257 -0
- package/dist/ui/shared/types.js +1 -0
- package/docs/theme-guide.html +692 -0
- package/package.json +98 -3
- package/templates/.claude/commands/explore-case.md +21 -0
- package/templates/.claude/commands/gen-spec.md +20 -0
- package/templates/.claude/commands/heal-spec.md +30 -0
- package/templates/.claude/commands/make-case.md +12 -0
- package/templates/.claude/settings.json +5 -0
- package/templates/.mcp.json +8 -0
- package/templates/AGENTS.md +45 -0
- package/templates/CLAUDE.md +1 -0
- package/templates/cases/_template.md +63 -0
- package/templates/gitignore +13 -0
- package/templates/opencode.jsonc +11 -0
- package/templates/package.json +31 -0
- package/templates/playwright.config.ts +3 -0
- package/templates/pnpm-workspace.yaml +5 -0
- package/templates/profiles.yaml +29 -0
- package/templates/seed.spec.ts +9 -0
- package/templates/tsconfig.json +14 -0
- package/ui/dist/apple-touch-icon.png +0 -0
- package/ui/dist/assets/apl-gfMMiTNf.js +1 -0
- package/ui/dist/assets/asciiarmor-C_eisQCp.js +1 -0
- package/ui/dist/assets/asn1-DCl_8MCW.js +1 -0
- package/ui/dist/assets/asterisk-BEn0Cwpx.js +1 -0
- package/ui/dist/assets/brainfuck-D5EjA2JK.js +1 -0
- package/ui/dist/assets/clike-xvONpD0Z.js +1 -0
- package/ui/dist/assets/clojure-BflJGmDX.js +1 -0
- package/ui/dist/assets/cmake-84nbDH0I.js +1 -0
- package/ui/dist/assets/cobol-DHLQUbN7.js +1 -0
- package/ui/dist/assets/coffeescript-De_zI4J0.js +1 -0
- package/ui/dist/assets/commonlisp-DgAKBe0r.js +1 -0
- package/ui/dist/assets/crystal-Bl6YKCG_.js +1 -0
- package/ui/dist/assets/css-CnaYoQib.js +1 -0
- package/ui/dist/assets/cypher-p9eYesGn.js +1 -0
- package/ui/dist/assets/d-DWkQyNnU.js +1 -0
- package/ui/dist/assets/diff-ChtP43wD.js +1 -0
- package/ui/dist/assets/dist-BHaIOZdW.js +1 -0
- package/ui/dist/assets/dist-BSJMM_jp.js +1 -0
- package/ui/dist/assets/dist-BiE4suo9.js +1 -0
- package/ui/dist/assets/dist-BqAROxzI.js +1 -0
- package/ui/dist/assets/dist-Bw5-uxs9.js +1 -0
- package/ui/dist/assets/dist-BwYRbe5s.js +9 -0
- package/ui/dist/assets/dist-BzulKuec.js +1 -0
- package/ui/dist/assets/dist-CBV7_vDN.js +2 -0
- package/ui/dist/assets/dist-CgIQYY1o.js +1 -0
- package/ui/dist/assets/dist-DDJNvivM.js +1 -0
- package/ui/dist/assets/dist-DF3F_I8N.js +1 -0
- package/ui/dist/assets/dist-DHCY9KpV.js +1 -0
- package/ui/dist/assets/dist-DX8knLVr.js +1 -0
- package/ui/dist/assets/dist-D_I45xgJ.js +11 -0
- package/ui/dist/assets/dist-Dlorc3Sx.js +1 -0
- package/ui/dist/assets/dist-Dr9crbBz.js +13 -0
- package/ui/dist/assets/dist-gF2bFn-5.js +1 -0
- package/ui/dist/assets/dockerfile-DJkHQgkl.js +1 -0
- package/ui/dist/assets/dtd-DTF72YNO.js +1 -0
- package/ui/dist/assets/dylan-S7ucR4pR.js +1 -0
- package/ui/dist/assets/ebnf-D4c0_ac3.js +1 -0
- package/ui/dist/assets/ecl-BVIeWmwY.js +1 -0
- package/ui/dist/assets/eiffel-BEjRio4Q.js +1 -0
- package/ui/dist/assets/elm-Cshzl8qu.js +1 -0
- package/ui/dist/assets/erlang-BV0c4Hdn.js +1 -0
- package/ui/dist/assets/factor-LKH5gN8L.js +1 -0
- package/ui/dist/assets/fcl-ClOhbFR7.js +1 -0
- package/ui/dist/assets/forth-D6RzAl64.js +1 -0
- package/ui/dist/assets/fortran-Bt6PBEDR.js +1 -0
- package/ui/dist/assets/gas-BHEdbvp9.js +1 -0
- package/ui/dist/assets/gherkin-oBAE_ms0.js +1 -0
- package/ui/dist/assets/groovy-CUrWKPia.js +1 -0
- package/ui/dist/assets/haskell-BRsoo5mP.js +1 -0
- package/ui/dist/assets/haxe-DjVOVvNP.js +1 -0
- package/ui/dist/assets/http-CLVgA2GD.js +1 -0
- package/ui/dist/assets/idl-BUZw3wgd.js +1 -0
- package/ui/dist/assets/index-DmasQ6Rq.css +2 -0
- package/ui/dist/assets/index-I7uU93kk.js +294 -0
- package/ui/dist/assets/javascript-lS2r2ifg.js +1 -0
- package/ui/dist/assets/julia-CQfgDRfP.js +1 -0
- package/ui/dist/assets/livescript-BXxG0Yva.js +1 -0
- package/ui/dist/assets/lua-DPCEkfD_.js +1 -0
- package/ui/dist/assets/mathematica-u8YMmbU0.js +1 -0
- package/ui/dist/assets/mbox-BEMVcqjs.js +1 -0
- package/ui/dist/assets/mirc-C9zpzAZU.js +1 -0
- package/ui/dist/assets/mllike-B45xgp2S.js +1 -0
- package/ui/dist/assets/modelica-CqHI_q-D.js +1 -0
- package/ui/dist/assets/mscgen-qgSLujhx.js +1 -0
- package/ui/dist/assets/mumps-BSzj6Xyz.js +1 -0
- package/ui/dist/assets/nginx-C-ORUuei.js +1 -0
- package/ui/dist/assets/nsis-viPvXFiq.js +1 -0
- package/ui/dist/assets/ntriples-CzMBulkp.js +1 -0
- package/ui/dist/assets/octave-D2Q8cUp1.js +1 -0
- package/ui/dist/assets/oz-BwTct_5T.js +1 -0
- package/ui/dist/assets/pascal-Dp_KdFgX.js +1 -0
- package/ui/dist/assets/perl-BkSkiI9m.js +1 -0
- package/ui/dist/assets/pig-AF4Hwhju.js +1 -0
- package/ui/dist/assets/powershell-D8WpQPaX.js +1 -0
- package/ui/dist/assets/properties-C357ku5U.js +1 -0
- package/ui/dist/assets/protobuf-D1ij_kNL.js +1 -0
- package/ui/dist/assets/pug-BqCHZ5xT.js +1 -0
- package/ui/dist/assets/puppet-BZyAS7I4.js +1 -0
- package/ui/dist/assets/python-B_1Jd8HM.js +1 -0
- package/ui/dist/assets/q-B9NhS7L_.js +1 -0
- package/ui/dist/assets/r-CxDmU1I4.js +1 -0
- package/ui/dist/assets/rpm-gpDK5sbt.js +1 -0
- package/ui/dist/assets/ruby-ChfNwlCY.js +1 -0
- package/ui/dist/assets/sas-C2OyD2Y6.js +1 -0
- package/ui/dist/assets/scheme-D9qYN0V5.js +1 -0
- package/ui/dist/assets/shell-DwuoZtxw.js +1 -0
- package/ui/dist/assets/sieve-tLWDfEkZ.js +1 -0
- package/ui/dist/assets/simple-mode-DXiLM1-G.js +1 -0
- package/ui/dist/assets/smalltalk-BLp5-jwC.js +1 -0
- package/ui/dist/assets/solr-B78wSQJQ.js +1 -0
- package/ui/dist/assets/sparql-Dz-mlCAC.js +1 -0
- package/ui/dist/assets/spreadsheet-CTIncYxj.js +1 -0
- package/ui/dist/assets/sql-DuQMFDvy.js +1 -0
- package/ui/dist/assets/stex-DNzd62Q_.js +1 -0
- package/ui/dist/assets/stylus-BOVgKnng.js +1 -0
- package/ui/dist/assets/swift-DKB6_1j6.js +1 -0
- package/ui/dist/assets/tcl-BGfruO1u.js +1 -0
- package/ui/dist/assets/textile-C4jVoHG1.js +1 -0
- package/ui/dist/assets/tiddlywiki-VlUiK86J.js +1 -0
- package/ui/dist/assets/tiki-CvUEFusT.js +1 -0
- package/ui/dist/assets/toml-BPTmHmyx.js +1 -0
- package/ui/dist/assets/troff-C2dAgh7P.js +1 -0
- package/ui/dist/assets/ttcn-PPSTk1ow.js +1 -0
- package/ui/dist/assets/ttcn-cfg-2b4Gb5sE.js +1 -0
- package/ui/dist/assets/turtle-Cx3rYX2g.js +1 -0
- package/ui/dist/assets/vb-BthdM_4N.js +1 -0
- package/ui/dist/assets/vbscript-D9f6rSsU.js +1 -0
- package/ui/dist/assets/velocity-CqftRsjg.js +1 -0
- package/ui/dist/assets/verilog-CjI9Y6RI.js +1 -0
- package/ui/dist/assets/vhdl-kS471soi.js +1 -0
- package/ui/dist/assets/webidl-D5jiJvOU.js +1 -0
- package/ui/dist/assets/xquery-BKJx4mf9.js +1 -0
- package/ui/dist/assets/yacas-CXXgtw0p.js +1 -0
- package/ui/dist/assets/z80-Co3PbHdE.js +1 -0
- package/ui/dist/favicon.ico +0 -0
- package/ui/dist/favicon.svg +11 -0
- package/ui/dist/index.html +17 -0
- package/ui/dist/logos/claude.svg +1 -0
- package/ui/dist/logos/opencode.svg +1 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# Readiness: look at the result only after the content is there
|
|
2
|
+
|
|
3
|
+
Shared by `exploring-cases`, `generating-specs` and healing. It is binding in all three.
|
|
4
|
+
|
|
5
|
+
A page that has just reacted is not ready. Dialogs, composers, previews, lists and PDFs show a frame first and fill
|
|
6
|
+
in later. Reading it too early gives a false "missing", "empty" or "wrong", and exploration then writes the false fact
|
|
7
|
+
into the case, the spec encodes it, and healing believes it.
|
|
8
|
+
|
|
9
|
+
**The rule: after every action that loads something, wait for its key requests to finish and for the content itself
|
|
10
|
+
to be visible, before reading, asserting or recording anything.**
|
|
11
|
+
|
|
12
|
+
## What "ready" is
|
|
13
|
+
All of these, in this order:
|
|
14
|
+
1. **The key requests finished** with a success status. The key requests are the data calls the action starts
|
|
15
|
+
(the composer's content, the list query, the PDF render), not images, fonts or analytics.
|
|
16
|
+
2. **Nothing says it is still loading**: no spinner, progress bar, skeleton, `aria-busy="true"`, "Loading..." text.
|
|
17
|
+
3. **The content itself is visible**: a positive signal that only exists when the data is there (a field with its value, the
|
|
18
|
+
first row, an image whose `naturalWidth` is above 0), never "the frame is open".
|
|
19
|
+
4. **Stable**: for exploration, two snapshots one second apart say the same.
|
|
20
|
+
|
|
21
|
+
A check for something **missing, empty or hidden** is only valid after a positive ready signal. "The list has no X"
|
|
22
|
+
means: the list finished loading and has rows, and none is X.
|
|
23
|
+
|
|
24
|
+
## Exploring
|
|
25
|
+
- Know the key requests: after the action, call `browser_network_requests`, pick the data calls (XHR/fetch to the
|
|
26
|
+
app's API, the ones that return the content). Wait until they are finished; use `browser_network_clear` before the
|
|
27
|
+
action so the list only holds what the action started.
|
|
28
|
+
- Then wait for the content signal (`browser_wait_for` with text, or `browser_find` that the content is there), and
|
|
29
|
+
take the snapshot only then. A snapshot taken right after a click is a frame, not a fact.
|
|
30
|
+
- Observations record the ready signal: `(observed after <signal>, ...)`. A fact with no signal is not recorded.
|
|
31
|
+
- When the first read shows less than expected (empty, a missing field, a logo not there), do not record it yet and do
|
|
32
|
+
not raise a question yet. Wait for readiness, read again, and only then decide. Mention in the report that the first
|
|
33
|
+
read was too early, with what was missing.
|
|
34
|
+
- Do it for every profile: loading time differs between profiles and environments.
|
|
35
|
+
|
|
36
|
+
## In the case
|
|
37
|
+
Steps and targets state their ready signal: `Ready when: <signal>` under the step, or the target card's `Ready when`.
|
|
38
|
+
Example: `Ready when: the composer's "To" field is visible and the logo image has loaded.` It is what `/gen-spec` turns into
|
|
39
|
+
`ready` and `responses`.
|
|
40
|
+
|
|
41
|
+
## Generating (spec code)
|
|
42
|
+
Use `actAndWait` and `expectNotBusy` from `@asrunjs/asrun/ready`, and `verifyDownload` from `@asrunjs/asrun/downloads`:
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
// ready: the composer shows its recipient field and the logo image has loaded
|
|
46
|
+
const composer = page.getByRole('dialog', {name: 'Email PDF'});
|
|
47
|
+
await actAndWait(page, () => option.click(), {
|
|
48
|
+
responses: [/\/api\/documents\/[^/]+\/email-preview/],
|
|
49
|
+
ready: [composer.getByRole('textbox', {name: 'To'}), logo],
|
|
50
|
+
});
|
|
51
|
+
await expectImageLoaded(logo);
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
- Register the response wait **before** the action (`actAndWait` does).
|
|
55
|
+
- 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
|
|
56
|
+
signal. The request URL patterns come from `browser_network_requests` during exploration, not from guesses; if none was
|
|
57
|
+
seen (the action was not run), say "not verified on the page" in the report.
|
|
58
|
+
- Never `waitForTimeout`, never `networkidle`. They wait for time or for silence, not for the content. `pnpm spec:lint` fails both.
|
|
59
|
+
- Never `if (await x.isVisible()) ...` to decide something that may not have loaded yet. Wait for a ready signal, then branch.
|
|
60
|
+
Where an element is genuinely optional, say what proves the page finished (`// ready: ...`) before looking for it.
|
|
61
|
+
- `locator.isVisible()` and `isHidden()` answer for this instant only. Their `timeout` option is deprecated and ignored:
|
|
62
|
+
`isVisible({timeout: 3_000})` does not wait 3 seconds. Never write it, and never use `.catch(() => false)` to make it
|
|
63
|
+
look like a wait. `pnpm spec:lint` fails both.
|
|
64
|
+
- An optional confirmation (see below) is handled with `clickOptionalUntilHidden`, not with `isVisible`.
|
|
65
|
+
|
|
66
|
+
### Optional confirmations (a dialog that may or may not show up)
|
|
67
|
+
Closing a composer, leaving an editor or deleting something can raise "Discard draft?", "Are you sure?" or nothing at all,
|
|
68
|
+
and the dialog can show up a second or two after the click. A check made right after the click sees "no dialog" and skips
|
|
69
|
+
it, then the next check (the composer is closed) races a dialog that appears later and fails.
|
|
70
|
+
|
|
71
|
+
The click on the confirmation and the check that the action finished go into **one retry loop**:
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
await composer.getByRole('button', {name: 'close', exact: true}).click();
|
|
75
|
+
await clickOptionalUntilHidden(page.getByRole('button', {name: 'Discard', exact: true}), composer, {
|
|
76
|
+
message: 'The email composer did not close',
|
|
77
|
+
});
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
- Each round: click the confirmation if it is visible now, then check that the target is hidden. A late dialog is clicked
|
|
81
|
+
in a later round; no dialog passes in the first round.
|
|
82
|
+
- Never decide "there is no dialog" from one look, and never wait a fixed time for it.
|
|
83
|
+
- The same holds for any other optional modal (for example "Add to Business Tracker" after publishing): when the case says
|
|
84
|
+
it opens, assert it with a waiting `expect(...).toBeVisible({timeout})`; when it may or may not open, use a loop that
|
|
85
|
+
also checks the next page state, or wait for the signal that proves the action finished (URL, heading), then look.
|
|
86
|
+
- A check for absence needs a positive ready signal in front of it (`// ready: <signal>`).
|
|
87
|
+
- A new tab: wait for the page event first (`context.waitForEvent('page')`), then for its URL, then for its content signal.
|
|
88
|
+
- A download is verified with `verifyDownload` (below), never with a bare `waitForEvent('download')`.
|
|
89
|
+
|
|
90
|
+
## Downloads
|
|
91
|
+
Any download (PDF, image, CSV, ZIP), in exploration and in the spec, is verified in five parts, and the verification
|
|
92
|
+
step fails as a whole when any part fails or times out:
|
|
93
|
+
1. wait for the download to start after the click;
|
|
94
|
+
2. wait for the download to finish;
|
|
95
|
+
3. check that the file exists and has content;
|
|
96
|
+
4. check that the file is valid for its kind (an image has the right signature; a ZIP/CSV likewise; a PDF starts with `%PDF-`,
|
|
97
|
+
ends with `%%EOF` and **opens with at least one page**), and run the case's own validation if there is one;
|
|
98
|
+
5. any failure or timeout of 1 to 4 fails the verification with a message that names the stage.
|
|
99
|
+
|
|
100
|
+
`verifyDownload(page, trigger, {kind, fileName?, minBytes?, validate?})` does exactly this. Name the file type in the
|
|
101
|
+
case (`kind`), and describe in a target card what the file must contain.
|
|
102
|
+
|
|
103
|
+
### PDF content: read it like a person (rendered images and extracted text)
|
|
104
|
+
A PDF check never stops at "it is a PDF". `verifyDownload(..., {kind: 'pdf'})` returns `file.pdf`, and
|
|
105
|
+
`expectPdf(file.pdf!, {...})` from `@asrunjs/asrun/pdf` then:
|
|
106
|
+
- **renders the pages to images** (scale 2, white background) and, for every region check, fails when the region is blank
|
|
107
|
+
and compares it with the profile's baseline image (2% tolerance; the first run with `--update-snapshots` creates the
|
|
108
|
+
baseline and a person looks at it, like any other baseline);
|
|
109
|
+
- **extracts the text** of every page and checks `textContains` / `textNotContains` (the names of the other profiles, for
|
|
110
|
+
example).
|
|
111
|
+
Regions are fractions of the page from the top-left corner; `corner('top-left')` is the usual logo corner.
|
|
112
|
+
|
|
113
|
+
**Which pages are checked is the case's decision, case by case:**
|
|
114
|
+
- The case says ONLY certain pages in strong words (a requirement: "only", "exactly", "must be on the first page", "the
|
|
115
|
+
logo is on the first and last page and nowhere else"): check just those (`pages: 'first' | 'last' | [1, 3]`), and put
|
|
116
|
+
`// pages: <the case's words>` above the check so a reviewer can see where the limit comes from.
|
|
117
|
+
- The case names pages softly ("should", "always", "usually", "typically", "normally", "e.g.") or names no page: check
|
|
118
|
+
**every page** (`pages: 'all'`, the default). The named page is then only the place where the check was first seen.
|
|
119
|
+
- A page limit that comes from the agent's own reading of the case is not allowed. When it is unclear whether a wording is
|
|
120
|
+
a strong requirement, check all pages and put the doubt in a `[non-blocking]` TODO.
|
|
121
|
+
|
|
122
|
+
**While exploring, the agent looks at the PDF too:** save the downloaded file under `.explore/` (`download.saveAs(...)`
|
|
123
|
+
in the same `browser_run_code_unsafe` call that waits for the download), run
|
|
124
|
+
`pnpm pdf:render <file> --out .explore/targets/<case-id>/<name> --pages all --corner top-left`, then **open the page
|
|
125
|
+
images** and say in the card where the logo really is on each page, which pages have it and which do not, and what the
|
|
126
|
+
text shows. The rendered pages are the card's `Reference`. If the logo is not where the case says, that is a question.
|
|
127
|
+
|
|
128
|
+
While exploring, do the same by hand: click, wait for the download event, see the file finish, read its size and its first
|
|
129
|
+
bytes. Record "(observed: <file name>, <size>, valid <kind>)".
|
|
130
|
+
|
|
131
|
+
## Healing
|
|
132
|
+
A failure of the kind "missing", "empty", "not visible", "wrong count", "timeout waiting for X" is first a suspected
|
|
133
|
+
readiness problem, then a suspected product problem:
|
|
134
|
+
1. Read the failure screenshot and video: was the page still loading (a spinner, a skeleton, an empty frame)?
|
|
135
|
+
2. Run the failing step by hand with the readiness protocol above and compare.
|
|
136
|
+
3. Only when the content is complete and still not what the case says is it a test-expectation or product matter.
|
|
137
|
+
4. A readiness fix goes into the spec as `actAndWait` / a ready signal, never as a longer timeout alone and never as a sleep.
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# User handoff: a person works in the browser, the agent waits
|
|
2
|
+
|
|
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 `## Inputs` that an exploration recorded.
|
|
5
|
+
|
|
6
|
+
## The rule
|
|
7
|
+
**An agent never writes data.** Not in staging, not in production. Every step that creates, edits, publishes, shares,
|
|
8
|
+
deletes or otherwise changes data, and every step that needs a human's choice (an existing document, items, a contact
|
|
9
|
+
name), is done by the user in the browser window that Playwright opened. Writing is rare (only while preparing a
|
|
10
|
+
case) and a person succeeds where an agent guesses. The agent drives only what is read-only: open, look, read,
|
|
11
|
+
hover, switch tabs, open and close dialogs and menus.
|
|
12
|
+
|
|
13
|
+
The runtime permission is a different thing and stays as it is: `writes: [<env>, ...]` in the case front matter says
|
|
14
|
+
where the finished spec may write, and a human confirms a run in any environment except staging.
|
|
15
|
+
|
|
16
|
+
## When a handoff happens
|
|
17
|
+
Only in an attended run (`--attended`, a person is at the machine, the browser window is visible). A step is a
|
|
18
|
+
handoff when it is marked `[input]` or `[writes]` in the case, or when the agent finds out while exploring that the
|
|
19
|
+
next action would write data or needs a choice.
|
|
20
|
+
|
|
21
|
+
Without `--attended` the agent does not wait. It writes a `TODO [blocking]` under that step ("a person has to
|
|
22
|
+
perform this step; run /explore-case with --attended") and goes on with the rest. Never wait for a person in a run
|
|
23
|
+
that nobody watches.
|
|
24
|
+
|
|
25
|
+
## Before the handoff
|
|
26
|
+
1. Say what the user has to do and when it is finished, in one or two sentences ("Fill in the form, pick two
|
|
27
|
+
items, then press Done"). Name the finish condition first (below).
|
|
28
|
+
2. Put every text the user has to type into `copy` (the `[asrun] ...` name of a document, a title), so it can be
|
|
29
|
+
copied with one click.
|
|
30
|
+
3. Never ask the user to send email (production: never; other environments: only when `email` is `true` for the
|
|
31
|
+
profile). Never ask for login or 2FA. Name everything the user creates `[asrun] <something>`.
|
|
32
|
+
4. Start recording with `browser_start_recording` (see "Recording").
|
|
33
|
+
|
|
34
|
+
## The wait loop
|
|
35
|
+
Run it with one `browser_run_code_unsafe` call. A single call may wait for minutes (verified: 150 s without a
|
|
36
|
+
timeout), so no polling from outside is needed. The loop:
|
|
37
|
+
|
|
38
|
+
- **Shows a small floating card, not a bar.** It sits in the bottom-left corner and only the card takes clicks, so
|
|
39
|
+
nothing of the page is covered or blocked. The user can **drag** it by its title bar and **minimize** it to a small
|
|
40
|
+
pill that still has a Done button. Position and minimized state survive a page reload (kept in `sessionStorage`).
|
|
41
|
+
- **Click to copy.** Every text in `copy` (for example the `[asrun] ...` name the user has to type) is shown on its
|
|
42
|
+
own line with a copy icon; one click puts it on the clipboard and the icon shows "Copied". Pass only text that the
|
|
43
|
+
case says to type; leave `copy` empty when there is none.
|
|
44
|
+
- **Resets the flag first**: `window.__handoff = null` on every page it paints. A flag left over from an earlier
|
|
45
|
+
handoff ends the wait at once.
|
|
46
|
+
- **Paints the card on every open tab** and repaints after navigation (a new page has no card). The document
|
|
47
|
+
editor opens in a new tab, so the loop looks at `context.pages()`, not only at the first page.
|
|
48
|
+
- **Stops** when one of these happens, whichever is first: the user presses Done or "Cannot do this" on the card
|
|
49
|
+
(`window.__handoff` is `'done'` or `'skip'`), or the finish condition holds, or the time is up (default 10 minutes).
|
|
50
|
+
- **Ends visibly**: the card turns green and says control is back for two seconds, then it is removed, and the tab
|
|
51
|
+
titles are restored.
|
|
52
|
+
- The card lives in a Shadow DOM (the site's CSS cannot change it), does not take keyboard focus (the user can keep
|
|
53
|
+
typing in the page) and works even when the page locks all clicks behind a modal. The icons are inline SVG because
|
|
54
|
+
the card is injected into the page under test; the web UI's react-feather rule does not apply to it.
|
|
55
|
+
|
|
56
|
+
**Never write this code yourself and never shorten it.** It lives in `references/handoff-card.js` (next to this
|
|
57
|
+
file) and a rewritten, simplified card has already gone wrong once: it had no drag handle, no minimize button and
|
|
58
|
+
no copy icon. Use the file in two calls:
|
|
59
|
+
|
|
60
|
+
1. Set the options on the browser context (this call only sets data):
|
|
61
|
+
```js
|
|
62
|
+
async (page) => {
|
|
63
|
+
page.context().__handoffCfg = {
|
|
64
|
+
message: '<what the user does, one sentence>',
|
|
65
|
+
copy: [], // texts to type, e.g. ['[asrun] published document']
|
|
66
|
+
doneText: null, // text that appears when finished, or null
|
|
67
|
+
maxMs: 600000,
|
|
68
|
+
};
|
|
69
|
+
return 'cfg set';
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
2. Run the card and wait: `browser_run_code_unsafe` with `filename` set to the absolute path of
|
|
73
|
+
`<framework>/.claude/skills/exploring-cases/references/handoff-card.js` (use the absolute path of the repository, a
|
|
74
|
+
relative one is resolved against the workspace root, which may be the parent folder). It returns `{outcome: 'done' | 'skip' | 'condition' | 'timeout', waitedMs}`.
|
|
75
|
+
|
|
76
|
+
Check once, after the card shows, that it looks right: take a screenshot and see a title bar with a move icon, a
|
|
77
|
+
chevron button at its right and, when `copy` is not empty, a line per text with a copy icon. If not, stop and report
|
|
78
|
+
it; do not hand off with a broken card.
|
|
79
|
+
|
|
80
|
+
Tell the user in the chat (the terminal or the UI job log) as well: "Your turn in the browser window: <task>. Press
|
|
81
|
+
Done on the card (bottom-left; drag it if it is in the way) when you finish." When the run was started from the web
|
|
82
|
+
UI, the UI notices the handoff by itself (it sees the `__handoffCfg` call and the `handoff-card.js` call) and shows the
|
|
83
|
+
"Your turn" badge, a count in the tab title and, if the person switched notifications on, a browser notification;
|
|
84
|
+
this is why the message in `__handoffCfg` must be a plain string literal. A macOS notification is a bonus and may
|
|
85
|
+
fail in a sandbox: `osascript -e 'display notification "<task>" with title "AsRun" sound name "Glass"'`. If it
|
|
86
|
+
fails, go on.
|
|
87
|
+
|
|
88
|
+
## Finish condition
|
|
89
|
+
Pick one that the page can show, before the handoff, and write it into the message of the card:
|
|
90
|
+
- a text that appears (a "Saved" toast, the title of the next step) or one that goes away;
|
|
91
|
+
- a URL that the page reaches;
|
|
92
|
+
- an element that appears or disappears (an editor that closes).
|
|
93
|
+
|
|
94
|
+
The Done button is always there (also on the minimized card) as the fallback, and "Cannot do this" lets the user give up a step.
|
|
95
|
+
|
|
96
|
+
## After the handoff
|
|
97
|
+
- The card turns green and says control is back ("Done: control is back with Playwright. Do not touch the page.")
|
|
98
|
+
for two seconds before it is removed, so the user knows when they may let go.
|
|
99
|
+
- `outcome: 'timeout'` or `'skip'`: do not guess. Write a `TODO [blocking]` under the step and go on with what does
|
|
100
|
+
not depend on it.
|
|
101
|
+
- Take a snapshot and write down what the user did (see "Recording"), then continue read-only.
|
|
102
|
+
- At the end of the exploration hand off once more when the user created data: "Delete the [asrun] items you
|
|
103
|
+
created, then press Done". The case must clean up after itself, and so must the exploration.
|
|
104
|
+
|
|
105
|
+
## Recording
|
|
106
|
+
Between the start and the end of a handoff the agent records what the user does (`browser_start_recording`,
|
|
107
|
+
`browser_stop_recording`). The recorded Playwright code is **raw material, never saved as it is**. It is turned into
|
|
108
|
+
a general recipe and written into the case under `## Inputs` (format below). The recipe holds what the user did in
|
|
109
|
+
words, plus the **labels and roles of the controls** they used ("the textbox 'Title'", "the button 'Next'"),
|
|
110
|
+
because `/gen-spec` cannot write data and so cannot see those controls itself. It never holds selectors, ids or the
|
|
111
|
+
names of data that can change.
|
|
112
|
+
|
|
113
|
+
Make the recipe as general as possible, because lists and data change:
|
|
114
|
+
- **A choice from a list that can change** (items, search results, a contact list): record the position, never the
|
|
115
|
+
name or id. "Select items #1 to #3 from the list."
|
|
116
|
+
- **Random is allowed only when the user asks for it** ("pick any 3"). The default is the position, because it can be
|
|
117
|
+
repeated. A random pick must be written into the test's annotations when the spec runs.
|
|
118
|
+
- **Text the case creates**: `[asrun] <something>`.
|
|
119
|
+
- **A value that has to exist** (an existing name, a search term): write what it must be like ("a name that
|
|
120
|
+
returns a result") and the observed value as `(observed "<value>", 🔍 to be confirmed by QA)` plus a
|
|
121
|
+
`[non-blocking]` TODO block. Do not put a brittle literal in as the only truth.
|
|
122
|
+
- **Nothing that depends on the pick** may become an assertion (a total changes with the items). Say so in a
|
|
123
|
+
TODO when a check would.
|
|
124
|
+
- Settings that the user changed on purpose (a toggle, a template) are recorded as they were set, by label.
|
|
125
|
+
|
|
126
|
+
If the recording tool fails or returns nothing, describe what the user did from before/after snapshots instead.
|
|
127
|
+
|
|
128
|
+
## The `## Inputs` section of a case
|
|
129
|
+
One bullet per `[input]` step, in step order. The step number is the key the spec generator uses.
|
|
130
|
+
|
|
131
|
+
```
|
|
132
|
+
## Inputs
|
|
133
|
+
- Step 3 (start): search for an existing document and pick result #1 (observed search term "...", 🔍 to be confirmed by QA).
|
|
134
|
+
- ❓ TODO [non-blocking]: Is any result that opens fine as the starting point? (seen on example-staging)
|
|
135
|
+
- ✍️ Answer:
|
|
136
|
+
- Step 3 (items): select 3 items by position (#1 to #3) from the list.
|
|
137
|
+
- Step 3 (finish): press "Next" until the "Publish" button shows, then publish.
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
A step that is marked `[input]` and has no bullet here has never been explored. `generating-specs` stops on it.
|
|
141
|
+
|
|
142
|
+
## Hard rules
|
|
143
|
+
- Never write data yourself, never press a button that writes. In production the same, with no exception.
|
|
144
|
+
- Never wait in an unattended run.
|
|
145
|
+
- Never ask a person to log in, pass 2FA or send email through the handoff.
|
|
146
|
+
- Never save selectors, ids or the names of changing data into a case. Control labels and roles are fine.
|
|
147
|
+
- Never read or print anything under `.auth/`.
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: generating-specs
|
|
3
|
+
description: Turns a human-written test case (cases/<id>.md, the id may include a group folder such as documents/landing-page) into a Playwright spec (tests/<id>.spec.ts) for the multi-profile test suite, explores the real pages to learn the locators and values, and completes the case's own "## Values" block. It only generates: it never runs the tests. Use when asked to generate, update, fix or heal a spec, or for /gen-spec.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Generating specs from case documents
|
|
7
|
+
|
|
8
|
+
Read `references/conventions.md` first. It defines the spec shape, fixtures, assertion layers and
|
|
9
|
+
the read-only rules. Read `AGENTS.md` for hard rules. Read `../exploring-cases/references/open-questions.md`: it
|
|
10
|
+
defines how open questions are written into the case file, resolved and reported. Also read
|
|
11
|
+
`../exploring-cases/references/assertion-targets.md` (the exact thing each check looks at) and
|
|
12
|
+
`../exploring-cases/references/readiness.md` (wait for the content, verify downloads in five parts). All three are binding here.
|
|
13
|
+
|
|
14
|
+
This skill never stops to ask the user in the chat, because it may run without anybody present (a one-shot
|
|
15
|
+
`claude -p` / `opencode run`). Questions go into the case file; the final line is `RESULT: ready` or
|
|
16
|
+
`RESULT: needs-input`.
|
|
17
|
+
|
|
18
|
+
This skill trusts the case. What the pages show, what a person has to enter, and what the open questions are is
|
|
19
|
+
settled by `/explore-case` and recorded in the case (`(observed, ...)` marks, the `## Inputs` section). Generating
|
|
20
|
+
only finds locators and writes code: it replays the case and does not judge the case again.
|
|
21
|
+
|
|
22
|
+
Generating a spec and running it are separate processes. This skill only writes files: it does not run
|
|
23
|
+
`pnpm asrun`, does not create screenshot baselines and does not judge whether the spec passes. The user runs the
|
|
24
|
+
spec afterwards (see the report). Healing a failing spec (below) is the exception: it reads the last report first and runs only the profiles that are logged in, and only when the report is not enough.
|
|
25
|
+
|
|
26
|
+
## Inputs
|
|
27
|
+
- The case id and the environment to explore in (`staging` or `production`). There is no default. If the
|
|
28
|
+
environment is missing, stop before exploring and report `RESULT: needs-input` ("pass the environment").
|
|
29
|
+
- Optionally `--force`: regenerate the spec from scratch and overwrite the existing one. See "An existing spec".
|
|
30
|
+
|
|
31
|
+
A case id is its path below `cases/` without `.md`: `hub-landing`, or `documents/landing-page` when the case is in a
|
|
32
|
+
folder (one level only). The spec is at the same path below `tests/` (`tests/documents/landing-page.spec.ts`); both import
|
|
33
|
+
the framework through the `@asrunjs/asrun/*` alias, so the folder depth does not change the imports.
|
|
34
|
+
|
|
35
|
+
- `cases/<id>.md` (front matter: `id`, `login: required|none` (`none` = public page; the login page itself is not a valid case), `suites: [<name>, ...]` (the old `priority: <level>` means one suite), `writes: [<env>, ...]` (omitted = read-only; the environments the spec may write in, e.g. `[staging]`) + `writes-note`, optional `envs: [...]`; sections Steps / Expected / Values / Visual checks / Targets / Inputs). `## Values` holds one fenced yaml block keyed by `default` and by profile id, with the exact literals the spec asserts. The case document is the only place where expected values live.
|
|
36
|
+
- `profiles.yaml` (profiles: id, name, env, baseURL, account, tags, email). It holds no expected values.
|
|
37
|
+
|
|
38
|
+
## An existing spec
|
|
39
|
+
Decide this first, before exploring anything. The spec path is `tests/<id>.spec.ts`.
|
|
40
|
+
- **No spec yet:** generate it (the procedure below).
|
|
41
|
+
- **A spec exists and `--force` was not given:** do not touch it. Do not edit the spec, the case values or any baseline.
|
|
42
|
+
Report that the spec already exists, say that `--force` regenerates and overwrites it, and give the id and
|
|
43
|
+
environment to run it again with. End with `RESULT: ready` (the spec that exists is not in question). Nothing else
|
|
44
|
+
is written, not even the case file.
|
|
45
|
+
- **A spec exists and `--force` was given:** the user chose to overwrite it. Regenerate it from the case with the
|
|
46
|
+
procedure below, and write the new file over the old one (do not merge with it: the case file is the source of
|
|
47
|
+
truth). Do not read the old spec as a starting point and do not keep it just because the result looks the same:
|
|
48
|
+
write the file from the case and the exploration. If it ends up identical, say so in the report. Keep the `## Values`
|
|
49
|
+
of other cases untouched; only this case's `## Values` is yours. Leave the
|
|
50
|
+
baseline images in `tests/<id>.spec.ts-snapshots/` alone: a changed visual check needs a person to update them,
|
|
51
|
+
so list in the report which baselines may now be out of date. Say in the report that an existing spec was
|
|
52
|
+
overwritten.
|
|
53
|
+
|
|
54
|
+
## Procedure
|
|
55
|
+
1. **Make sure the case is in English.** All files in this repo are English (see `AGENTS.md`), and spec step titles
|
|
56
|
+
copy the case's step text. If `cases/<id>.md` is written in another language, translate it to English and
|
|
57
|
+
rewrite the file BEFORE anything else: keep the front matter keys and values, the section headings, the
|
|
58
|
+
`## Values` entries and the step numbering as they are, and keep quoted UI text exactly as it appears
|
|
59
|
+
on the page (do not translate strings the page shows). Do not add, drop or reinterpret any step or
|
|
60
|
+
expectation; if a sentence is unclear, translate it literally and raise it as a question in the next step.
|
|
61
|
+
Tell the user in the report that the case file was translated. Skip this when the case is already English.
|
|
62
|
+
2. **Validate the case.** Collect everything that is ambiguous, every check without an expected value and every
|
|
63
|
+
missing per-profile value, including the `TODO` items already in the case. Resolve them in the order of
|
|
64
|
+
`open-questions.md`; do not stop here:
|
|
65
|
+
- **Steps that write or need a choice (`[input]`, `[writes]`) need a recipe.** Each must have a bullet in the case's
|
|
66
|
+
`## Inputs` section (written by `/explore-case`). If one has none, the case was never explored with a person:
|
|
67
|
+
stop before opening a browser, do not write the spec, and report `RESULT: needs-input` with exactly this:
|
|
68
|
+
"step <n> has no recorded input; run `/explore-case <id> <env> --attended` first". Nothing else is written.
|
|
69
|
+
- Facts the page can answer and that the case does not hold yet are read in step 4 and written into the case as
|
|
70
|
+
`(observed, 🔍 to be confirmed by QA)`. A spec is generated from observed values.
|
|
71
|
+
- Safe defaults are applied: the suggestion is the sentence above the TODO block, and the block stays in the case
|
|
72
|
+
as `❓ TODO [non-blocking]` with an empty `✍️ Answer:`. A sentence with no TODO block under it is confirmed text.
|
|
73
|
+
- A non-empty `Answer:` is the user's answer: it wins over the sentence above it. Fold it into the case.
|
|
74
|
+
- Only what is always-blocking in `open-questions.md` blocks. Generate everything else; for a blocked check
|
|
75
|
+
(a `[blocking]` block), write `test.fixme(true, '<the question>')` inside its step (or skip that profile), never
|
|
76
|
+
an invented value and never a check built from the sentence above a `[blocking]` block.
|
|
77
|
+
- **Every check on a logo, image, icon, file, PDF, screenshot or repeated text needs a target card** (`## Targets`,
|
|
78
|
+
referenced as `[T<n>]`) that was observed (`Reference <profile-id>:` is a screenshot, not `none`) and has no open
|
|
79
|
+
`[blocking]` block. A check that has no card, or whose card is not observed, is not guessed here. Do not pick
|
|
80
|
+
an element yourself: report `RESULT: needs-input` with "check <text> has no observed target; run `/explore-case
|
|
81
|
+
<id> <env>` first" (or, when the card has a `[blocking]` TODO, write `test.fixme(true, '<the question>')` for that check as
|
|
82
|
+
the rule above says and go on with the rest).
|
|
83
|
+
- **Evidence gate:** a hard assertion (one that decides pass or fail) on something nobody has seen on a page is not
|
|
84
|
+
written. "Not verified on the page" is allowed for a locator of a page that only exists after a write step, but
|
|
85
|
+
never for what the check asserts about it: that goes to `test.fixme` with the reason, or `needs-input`.
|
|
86
|
+
- Do not invent expected values. If the case's `## Values` and the spec disagree, the case wins: fix the spec and
|
|
87
|
+
mention the change in the report.
|
|
88
|
+
3. **Check sessions** if `login: required`: run `pnpm status --env <env>`. If a profile needs login, tell the
|
|
89
|
+
user to run `pnpm signin <profile-id>` and continue with the profiles that are ready. If `pnpm status`
|
|
90
|
+
cannot run (a sandbox error such as `listen EPERM` or a Chromium start failure), judge by the page instead: a page
|
|
91
|
+
that lands on the login page means the profile is not logged in.
|
|
92
|
+
4. **Replay the case on the real page** with the `playwright-test` MCP: call `generator_setup_page` with
|
|
93
|
+
`project=<profile-id>` (for example `example-staging`) and `seedFile=tests/seed.spec.ts`, then perform the
|
|
94
|
+
steps in the environment you were given (never ask for it; there is no default) to learn the locators.
|
|
95
|
+
Do the read-only steps as written. **Never perform a step that writes or needs a choice**: the agent does not write
|
|
96
|
+
data, in staging or in production, and it does not hand off to a person here or wait for one. Write those steps
|
|
97
|
+
of the spec from the `## Inputs` recipe (positions, `[asrun]` names, control labels and roles that
|
|
98
|
+
`/explore-case` recorded). Pages that only exist after such a step cannot be seen from here: write their locators
|
|
99
|
+
from what the recipe and the exploration notes say, mark them in the report as "not verified on the page", and
|
|
100
|
+
leave the first run of the spec (or `/heal-spec`) to confirm them. Do this for at least one profile
|
|
101
|
+
per domain (example.com and exampleshop.com differ in markup). Profiles that are not logged in are skipped and
|
|
102
|
+
named in the report. Use this exploration to answer the open points of step 2, and look at every profile that has
|
|
103
|
+
an entry in `## Values` to read its value.
|
|
104
|
+
Keep every browser call short (one action, about 60 s at most, a snapshot between steps, `{timeout: 5000}` when
|
|
105
|
+
something may not appear, no third retry): see "Keep every browser call short" in `../exploring-cases/SKILL.md`.
|
|
106
|
+
**Ready before you read, here too** (`readiness.md`): `browser_network_clear`, act, wait for the key requests
|
|
107
|
+
(`browser_network_requests`) and the content signal, then snapshot. The request URL patterns for `actAndWait` and the
|
|
108
|
+
`Ready when` signals of the case are taken from what you see here.
|
|
109
|
+
**Check every target on the page:** for each target card, find its `Identified by` locator, confirm that it matches
|
|
110
|
+
exactly one element and that this element is what `What` says (highlight it and read the screenshot). A locator that
|
|
111
|
+
matches several elements or the wrong one is fixed or becomes a question, never `.first()`.
|
|
112
|
+
Prefer `getByTestId` (the project uses `data-tn`), then role/label locators. No CSS chains or XPath.
|
|
113
|
+
Update the case file with what you learned: fold observations into the right section marked
|
|
114
|
+
`(observed, 🔍 to be confirmed by QA)`, remove the TODO blocks they answer (rewrite the sentence above the block),
|
|
115
|
+
and narrow the ones that remain. Keep the shape of `open-questions.md` for every block. Edit the case
|
|
116
|
+
with targeted edits; never touch its `## Source` table.
|
|
117
|
+
5. **Complete the case's `## Values`**: for every profile the spec runs for, add the entry with the literals the spec
|
|
118
|
+
asserts (the same keys as `default`, only what differs). Do not move values into `profiles.yaml`; it holds connection
|
|
119
|
+
settings only. Mention every value you added or corrected in the report.
|
|
120
|
+
6. **Write `tests/<id>.spec.ts`** (inside the group folder if the id has one, creating it if needed) following the conventions. One `test()` per outcome, and every
|
|
121
|
+
case step wrapped in `test.step()` with the case's step text as the title, and the top-level
|
|
122
|
+
`test.describe` tagged with `caseTags('<id>')`. The spec must
|
|
123
|
+
contain no profile-specific literals: everything profile-specific comes from `requireValues('<id>', profile)`
|
|
124
|
+
(`@asrunjs/asrun/values`), which merges `default` with the running profile's entry.
|
|
125
|
+
Every action that loads something goes through `actAndWait` (`@asrunjs/asrun/ready`) with the key requests and the ready
|
|
126
|
+
signal; every download goes through `verifyDownload` (`@asrunjs/asrun/downloads`); every check on a target has
|
|
127
|
+
`// target: T<n>` on the line before it; every absence check has a `// ready: <signal>` before it. Never
|
|
128
|
+
`waitForTimeout`, `networkidle` or `.first()` to pick an image. A confirmation or modal that may or may not appear is
|
|
129
|
+
never decided by one `isVisible()` look (its `timeout` option is ignored): use `clickOptionalUntilHidden`
|
|
130
|
+
(`@asrunjs/asrun/ready`) or a waiting `expect(...).toBeVisible({timeout})`; see `readiness.md`, "Optional confirmations".
|
|
131
|
+
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
|
|
132
|
+
it fails with a sandbox error such as `listen EPERM`). Fix every error, including every `FAIL` of the spec lint (a lint failure is never silenced: fix the spec or turn the check
|
|
133
|
+
into a question). Every lint `WARN` is handled too: fix it, or name it in the report with the reason it is acceptable.
|
|
134
|
+
Then do the **self-review**: for each assertion answer in writing "if the product were broken, would this fail?"
|
|
135
|
+
and "if the product is right, could this fail by timing?" (a check that cannot fail, or that races the page, is rewritten).
|
|
136
|
+
For every branch that depends on something possibly not there yet (`if`, `.count()`, `.catch(() => false)`), ask "what
|
|
137
|
+
if it shows up a second later?"; the answer must be a retry loop or a waiting assertion, never a single look.
|
|
138
|
+
Do NOT run `pnpm asrun`, do not use
|
|
139
|
+
`--update-snapshots` and do not create baselines: the first run of the spec does that, and a person reviews them.
|
|
140
|
+
8. **Report** in the user's language: spec path, **an assertion table** (step, what is asserted, target card, ready signal, and
|
|
141
|
+
`observed on the page` or `not verified`; no row may be missing), profiles explored, profiles skipped (and why), whether the case file was
|
|
142
|
+
translated, what was added to the case file as observed, checks generated from observations (QA must confirm
|
|
143
|
+
them), visual checks that need a baseline on the first run, and the remaining TODO blocks (`[blocking]` ones with
|
|
144
|
+
the sentence that deleting the block would confirm). State that the spec was not run and give the command to run it:
|
|
145
|
+
`pnpm asrun --env <env> --profile <ids> --grep <id>` (with `--update-snapshots` once if the spec has visual
|
|
146
|
+
checks, then run `pnpm baselines:review <id> --new` and open every new image: it must show what its target card says). The last line is `RESULT: ready` (no `[blocking]` question left, spec
|
|
147
|
+
written and the checks above pass) or `RESULT: needs-input`, as defined in `open-questions.md`.
|
|
148
|
+
|
|
149
|
+
## Cases that write data
|
|
150
|
+
Only when the case front matter lists environments under `writes` (for example `writes: [staging]`). Never add
|
|
151
|
+
`--allow-writes` or set `ASRUN_ALLOW_WRITES`. Use the `records` fixture, `configureCase` and `emailStepAllowed` as
|
|
152
|
+
described in `references/conventions.md`. The agent does not perform write steps while generating (see step 4); the
|
|
153
|
+
spec does, when a person runs it in an environment that `writes` lists. Elsewhere the runner skips the case with a
|
|
154
|
+
notice. Never send email in production; in other environments only if `profile.email` is true, otherwise skip that step and say so
|
|
155
|
+
in the report.
|
|
156
|
+
|
|
157
|
+
## Healing a failing spec
|
|
158
|
+
Inputs: the case id and the environment (both required, there is no default: without an environment stop and say so),
|
|
159
|
+
optionally `--profiles a,b`, `--report <run-id>` and `--hints <file>` (or free text after the arguments). Healing is
|
|
160
|
+
the one case where this skill runs specs, and only in the environment given.
|
|
161
|
+
|
|
162
|
+
Heal from evidence, not from a habit of rerunning. A report that already exists says what failed.
|
|
163
|
+
|
|
164
|
+
1. **Read the user's clues first** (the `--hints` file, or the free text). They say what to look at and may hold
|
|
165
|
+
evidence the report lacks. They guide the diagnosis and nothing else: they never lower the bar for a product
|
|
166
|
+
finding and never allow an expectation to be changed just to make a test pass. Quote the clue you used, or say that
|
|
167
|
+
it did not hold up, in the report.
|
|
168
|
+
2. **Read the existing report**: run `pnpm heal:evidence --case <case-id> --env <env>` (add `--profile a,b` when `--profiles`
|
|
169
|
+
was given). It reads `reports/*/summary.json` and prints, per profile, the status, the failed step, the error, the call
|
|
170
|
+
log, the screenshots and videos, and ends with `EVIDENCE: fresh | stale | no-failure | none`. With `--report <run-id>`,
|
|
171
|
+
read `reports/<run-id>/summary.json` instead. Look at the screenshot when the error alone does not explain it. A profile
|
|
172
|
+
that failed only because it was not logged in is no evidence about the spec.
|
|
173
|
+
3. **Decide whether to rerun the case**. Do not rerun when the report is `fresh`, names the failing step and the error,
|
|
174
|
+
and that is enough to tell whether the product changed or the test is wrong. Rerun when:
|
|
175
|
+
- there is no report (`none`), or it is `stale` (the spec, the case or `profiles.yaml` changed after it);
|
|
176
|
+
- the report does not explain the failure (a bare timeout with no locator, a failure before any step, a truncated log);
|
|
177
|
+
- a clue from the user has to be checked against the real page;
|
|
178
|
+
- a profile that must be healed has no result in the report (for example it was not logged in then).
|
|
179
|
+
Rerun with one plain command, as few profiles as possible, and say why in the report:
|
|
180
|
+
`pnpm asrun --env <env> --logged-in --profile <ids> --grep <case-id>` (optionally piped to `tail`; do not chain it
|
|
181
|
+
with other commands, or it runs inside Claude's sandbox and fails with `listen EPERM`). Always add `--logged-in`: it
|
|
182
|
+
leaves out the profiles that are not logged in instead of failing them. A profile that is not logged in is never healed:
|
|
183
|
+
name it in the report with `pnpm signin <profile-id>`.
|
|
184
|
+
4. **Audit the assertion before you blame anyone.** Read the `assertion-targets.md` and `readiness.md` sections on healing.
|
|
185
|
+
- Is the locator still what the target card says (`What`, `Identified by`)? Open the failure screenshot and the
|
|
186
|
+
baseline (`pnpm baselines:review <case-id>`) and look at each: a baseline that shows something other than the card
|
|
187
|
+
(a content image instead of a logo) is a wrong test, and passing runs on it prove nothing. Delete it and report it; a
|
|
188
|
+
person creates the new baseline after the spec is fixed. Note: other profiles that pass on the same wrong target are also suspect.
|
|
189
|
+
- Is the failure a "missing / empty / not visible / timeout" kind? Then it is first a readiness suspect: was the page
|
|
190
|
+
still loading in the screenshot or video? Fix with `actAndWait` and a ready signal, never with a longer timeout alone or a sleep.
|
|
191
|
+
- Is the spec weak in the ways the lint knows? Run `pnpm spec:lint <case-id>` and fix every `FAIL` while you are there.
|
|
192
|
+
- A download that fails says which stage failed (start, finish, exists, valid): fix the stage, do not loosen the check.
|
|
193
|
+
- Doubt about which element is meant: write the question into the case (a `[blocking]` TODO naming the exact image,
|
|
194
|
+
text, place or UI) instead of choosing; do not rewrite the card quietly.
|
|
195
|
+
5. **Decide whether the PRODUCT changed or the TEST is wrong.**
|
|
196
|
+
- Locator/timing problem: fix the spec.
|
|
197
|
+
- A real product difference (wrong logo, wrong name): do NOT change the expectation to make it
|
|
198
|
+
pass. Report it as a finding with the trace/screenshot path.
|
|
199
|
+
6. **After editing the spec, run it once to confirm** (same command as in step 3, for the profiles you healed). That rerun
|
|
200
|
+
is the only one that is always allowed. Run `pnpm typecheck` too.
|
|
201
|
+
7. **Report** in the user's language: which report was read (or that there was none) and whether it was fresh or stale,
|
|
202
|
+
whether the case was run again and why (or why not), what was changed, what is a product finding (with paths),
|
|
203
|
+
which assertions were wrong or raced the page, which baselines are wrong and were deleted, what the user's clues contributed, which profiles were left out because they are not logged in, and the result of the
|
|
204
|
+
confirming run.
|
|
205
|
+
|
|
206
|
+
A case that writes data in `production` needs a person's confirmation at run time, so the rerun is refused there:
|
|
207
|
+
use the report, or heal in staging, and never add `--allow-writes`.
|