@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.
Files changed (103) hide show
  1. package/.claude/commands/explore-case.md +18 -15
  2. package/.claude/commands/gen-spec.md +16 -13
  3. package/.claude/commands/heal-spec.md +26 -23
  4. package/.claude/commands/make-case.md +11 -8
  5. package/.claude/skills/exploring-cases/SKILL.md +146 -132
  6. package/.claude/skills/exploring-cases/references/open-questions.md +116 -112
  7. package/.claude/skills/exploring-cases/references/readiness.md +146 -137
  8. package/.claude/skills/exploring-cases/references/user-handoff.md +157 -147
  9. package/.claude/skills/generating-specs/SKILL.md +219 -207
  10. package/.claude/skills/generating-specs/references/conventions.md +148 -148
  11. package/.claude/skills/making-cases/SKILL.md +72 -64
  12. package/.claude/skills/making-cases/references/normalizing.md +39 -38
  13. package/.claude/skills/making-cases/references/writes-and-email.md +37 -36
  14. package/AGENTS.md +169 -154
  15. package/CLAUDE.md +2 -1
  16. package/README.md +221 -410
  17. package/dist/lib/build-artifacts.d.ts +8 -0
  18. package/dist/lib/build-artifacts.js +10 -0
  19. package/dist/lib/case-doc.d.ts +311 -0
  20. package/dist/lib/case-doc.js +1117 -0
  21. package/dist/lib/cases.d.ts +2 -15
  22. package/dist/lib/cases.js +5 -78
  23. package/dist/lib/command.d.ts +24 -0
  24. package/dist/lib/command.js +90 -0
  25. package/dist/lib/fixtures.js +2 -2
  26. package/dist/lib/login-card.d.ts +22 -0
  27. package/dist/lib/login-card.js +183 -0
  28. package/dist/lib/node-cli.d.ts +6 -2
  29. package/dist/lib/node-cli.js +16 -6
  30. package/dist/lib/profiles.d.ts +25 -1
  31. package/dist/lib/profiles.js +54 -1
  32. package/dist/lib/run-filters.js +10 -2
  33. package/dist/lib/session.d.ts +13 -1
  34. package/dist/lib/session.js +73 -7
  35. package/dist/lib/targets.d.ts +5 -13
  36. package/dist/lib/targets.js +4 -29
  37. package/dist/lib/values.js +2 -2
  38. package/dist/scripts/build.js +2 -1
  39. package/dist/scripts/check-cases.js +74 -6
  40. package/dist/scripts/doctor.js +115 -4
  41. package/dist/scripts/explore.js +22 -1
  42. package/dist/scripts/go.mjs +188 -188
  43. package/dist/scripts/init.d.ts +7 -1
  44. package/dist/scripts/init.js +62 -14
  45. package/dist/scripts/lint-specs.js +4 -1
  46. package/dist/scripts/login.js +30 -2
  47. package/dist/ui/server/actions.d.ts +4 -0
  48. package/dist/ui/server/actions.js +25 -12
  49. package/dist/ui/server/ai-models.d.ts +3 -0
  50. package/dist/ui/server/ai-models.js +347 -0
  51. package/dist/ui/server/ai-stream.d.ts +34 -9
  52. package/dist/ui/server/ai-stream.js +283 -77
  53. package/dist/ui/server/app.js +74 -12
  54. package/dist/ui/server/jobs.d.ts +7 -1
  55. package/dist/ui/server/jobs.js +69 -15
  56. package/dist/ui/server/profiles.d.ts +20 -1
  57. package/dist/ui/server/profiles.js +214 -38
  58. package/dist/ui/shared/ai-commands.d.ts +16 -6
  59. package/dist/ui/shared/ai-commands.js +35 -12
  60. package/dist/ui/shared/types.d.ts +114 -0
  61. package/docs/agents/case-format.md +193 -0
  62. package/docs/agents/framework-dev.md +167 -0
  63. package/docs/ai-workflow.md +235 -0
  64. package/docs/cases.md +189 -0
  65. package/docs/commands.md +175 -0
  66. package/docs/install.md +174 -0
  67. package/docs/profiles.md +139 -0
  68. package/docs/theme-guide.html +922 -692
  69. package/docs/troubleshooting.md +101 -0
  70. package/docs/web-ui.md +139 -0
  71. package/package.json +20 -1
  72. package/templates/.claude/commands/explore-case.md +22 -21
  73. package/templates/.claude/commands/gen-spec.md +21 -20
  74. package/templates/.claude/commands/heal-spec.md +31 -30
  75. package/templates/.claude/commands/make-case.md +13 -12
  76. package/templates/.mcp.json +8 -8
  77. package/templates/AGENTS.md +66 -45
  78. package/templates/cases/_template.md +57 -63
  79. package/templates/opencode.jsonc +16 -11
  80. package/templates/profiles.yaml +42 -29
  81. package/ui/dist/assets/{dist-D_I45xgJ.js → dist-BudNAjOv.js} +1 -1
  82. package/ui/dist/assets/{dist-CBV7_vDN.js → dist-C-eCSFja.js} +1 -1
  83. package/ui/dist/assets/{dist-gF2bFn-5.js → dist-CO4heSzh.js} +1 -1
  84. package/ui/dist/assets/{dist-DX8knLVr.js → dist-CPLhRzom.js} +1 -1
  85. package/ui/dist/assets/{dist-BqAROxzI.js → dist-DEPJZ3Me.js} +1 -1
  86. package/ui/dist/assets/{dist-BwYRbe5s.js → dist-DZ7RiqmA.js} +1 -1
  87. package/ui/dist/assets/{dist-BHaIOZdW.js → dist-LixzGniF.js} +1 -1
  88. package/ui/dist/assets/{dist-DHCY9KpV.js → dist-O_RVi4tP.js} +1 -1
  89. package/ui/dist/assets/{dist-BiE4suo9.js → dist-jPS6HCJ5.js} +1 -1
  90. package/ui/dist/assets/{dist-BSJMM_jp.js → dist-vmf3fnkM.js} +1 -1
  91. package/ui/dist/assets/index-BIGt8Hfz.js +338 -0
  92. package/ui/dist/assets/index-CTmnF0Wp.css +2 -0
  93. package/ui/dist/help/ai-tools.png +0 -0
  94. package/ui/dist/help/case-explore.png +0 -0
  95. package/ui/dist/help/case-generate.png +0 -0
  96. package/ui/dist/help/cases-memo.png +0 -0
  97. package/ui/dist/help/profile-form.png +0 -0
  98. package/ui/dist/help/profiles-login.png +0 -0
  99. package/ui/dist/help/reports.png +0 -0
  100. package/ui/dist/help/run-tests.png +0 -0
  101. package/ui/dist/index.html +22 -16
  102. package/ui/dist/assets/index-DmasQ6Rq.css +0 -2
  103. package/ui/dist/assets/index-I7uU93kk.js +0 -294
@@ -0,0 +1,174 @@
1
+ # Installing AsRun
2
+
3
+ AsRun is a normal devDependency. A **workspace** is the folder that owns `profiles.yaml`, `cases/` and `tests/`;
4
+ everything AsRun needs at run time is read through it. The framework itself lives in `node_modules/@asrunjs/asrun`
5
+ and is never edited: a project commits only its own test content.
6
+
7
+ ## Requirements
8
+
9
+ - **Node.js 22 or newer** and **pnpm**.
10
+ - **ffmpeg** joins the recordings of one test (a new browser tab has its own video) into one video for the report.
11
+ `pnpm go` installs it with Homebrew on macOS or winget on Windows; on Linux run `sudo apt install ffmpeg`. When it
12
+ is missing, the optional `ffmpeg-static` package is used instead; if neither works, the report lists the videos
13
+ separately. Not fatal.
14
+ - **Claude Code or OpenCode** for the AI steps (make cases, explore, generate, heal). Everything else works without
15
+ them; `pnpm go` warns when neither command is found.
16
+
17
+ ## Install in a project
18
+
19
+ ```bash
20
+ pnpm dlx @asrunjs/asrun init --install
21
+ pnpm go
22
+ ```
23
+
24
+ `init --install` scaffolds the workspace and then adds the dependency. Running `init` first matters on pnpm 12: it
25
+ writes `pnpm-workspace.yaml` with the `allowBuilds` entry (`esbuild` through `tsx`, `ffmpeg-static`), and without it
26
+ `pnpm add` exits with `ERR_PNPM_IGNORED_BUILDS`. An existing `pnpm-workspace.yaml` that lacks the entry gets a note
27
+ instead: add the block by hand (see "Manual install").
28
+
29
+ `pnpm go` then does the rest and is the command to remember:
30
+
31
+ 1. checks Node (22+) and ffmpeg, and warns when no AI CLI is installed;
32
+ 2. installs the dependencies and the Chromium browser (`pnpm install` + `playwright install chromium`);
33
+ 3. runs `asrun init` (workspace files and `.env`) and `asrun doctor`;
34
+ 4. starts the web UI at http://127.0.0.1:4173 and opens it.
35
+
36
+ Run it again on any day: the install step then takes seconds, and if the UI is already running it only opens it.
37
+ `pnpm go --no-open` does not open a browser. Set `ASRUN_UI_PORT` to change the port.
38
+
39
+ The UI is optional: all commands in [commands.md](./commands.md) work without it.
40
+
41
+ ### Manual install
42
+
43
+ Same result, step by step:
44
+
45
+ ```bash
46
+ pnpm add -D @asrunjs/asrun
47
+ pnpm asrun init
48
+ pnpm go
49
+ ```
50
+
51
+ If pnpm refuses the build scripts of `esbuild` / `ffmpeg-static`, add this to `pnpm-workspace.yaml` and run the
52
+ install again:
53
+
54
+ ```yaml
55
+ allowBuilds:
56
+ esbuild: true
57
+ ffmpeg-static: true
58
+ ```
59
+
60
+ To pin a version:
61
+
62
+ ```bash
63
+ pnpm add -D @asrunjs/asrun@<version>
64
+ ```
65
+
66
+ `pnpm asrun init` is safe to run many times: it never overwrites a file the project owns.
67
+
68
+ ## What `init` writes
69
+
70
+ | Kind | Files | On `--update` |
71
+ |---|---|---|
72
+ | Generated | `playwright.config.ts`, `tests/seed.spec.ts`, `.claude/commands/*`, `.opencode/commands/*` | rewritten |
73
+ | Once | `AGENTS.md`, `CLAUDE.md`, `.mcp.json`, `opencode.jsonc`, `.claude/settings.json`, `tsconfig.json`, `pnpm-workspace.yaml`, `profiles.yaml`, `cases/_template.md`, `package.json` (scripts and devDependencies merged in) | kept |
74
+
75
+ It also writes the asrun block in `.gitignore`, the `ASRUN_ROOT` line in `.env`, and `.asrun.json` (the marker with
76
+ the framework version and the generated file list).
77
+
78
+ Flags:
79
+
80
+ | Flag | Meaning |
81
+ |---|---|
82
+ | `--root <dir>` | initialize that folder instead of the current one |
83
+ | `--update` | refresh only the generated files |
84
+ | `--force <path>` | overwrite one file init owns (repeatable) |
85
+ | `--reset-rules` | allow `--force` on `AGENTS.md`, `CLAUDE.md` or `.claude/skills/**` |
86
+ | `--no-env` | do not write `.env` |
87
+ | `--install [spec]` | also add the dependency (default `@asrunjs/asrun`) |
88
+
89
+ `AGENTS.md`, `CLAUDE.md` and `.claude/skills/**` belong to the project. `init --force` refuses them without
90
+ `--reset-rules`, so a framework update can never replace a project's rules.
91
+
92
+ ## Update
93
+
94
+ ```bash
95
+ pnpm add -D @asrunjs/asrun@latest
96
+ pnpm asrun init --update
97
+ pnpm asrun doctor
98
+ ```
99
+
100
+ `init --update` rewrites only the generated files and leaves project rules, case files, values and baselines alone.
101
+ `doctor` tells you when the workspace is behind the installed version.
102
+
103
+ There is no folder to move: the framework lives in `node_modules`, which the package manager owns. To change the
104
+ framework itself, `pnpm patch @asrunjs/asrun` (a committed patch).
105
+
106
+ ## `pnpm asrun doctor`
107
+
108
+ Run it after changing profiles, rules or overrides, and after an update. It checks:
109
+
110
+ - every entry in `profiles.yaml` (ids, environments, URLs, session check settings);
111
+ - `tsconfig.json`, `playwright.config.ts` (its re-export of the framework config) and the generated files;
112
+ - that `.gitignore` ignores `.auth/` and `reports/`;
113
+ - whether the installed version is behind the package's templates, and whether `dist/` is out of date;
114
+ - the project rule table in `AGENTS.md` §2 (unknown ids, a missing `Effect`, a non-negotiable rule);
115
+ - the framework skills and the project overrides under `.claude/skills/<skill>.md`, and lists the ones it sees.
116
+
117
+ ## Which workspace a command runs against
118
+
119
+ The order is:
120
+
121
+ 1. a real environment variable `ASRUN_ROOT` (it wins over everything);
122
+ 2. `ASRUN_ROOT` in the `.env` next to the command, or in the framework package's own `.env` (relative to that file);
123
+ 3. otherwise the nearest folder at or above the working directory that has `profiles.yaml` or `.asrun.json`;
124
+ 4. in the framework repository, the framework's own `.env` points at `examples/demo`.
125
+
126
+ A real environment variable always wins, so one checkout can point at another project for a single command:
127
+
128
+ ```bash
129
+ ASRUN_ROOT=../my-real-project pnpm asrun --env staging --read-only
130
+ ```
131
+
132
+ ## Workspace layout
133
+
134
+ ```
135
+ profiles.yaml one entry per environment: URL, account label, tags, environment (no expected values)
136
+ cases/ human-written cases (+ _template.md); optional group folders: cases/<group>/<name>.md
137
+ tests/ generated specs (same folders as cases/) and *-snapshots/ baselines per profile
138
+ playwright.config.ts re-exports node_modules/@asrunjs/asrun/dist/playwright.config.js
139
+ tsconfig.json plain compiler options (specs import the framework by package name)
140
+ AGENTS.md project rules: they point at the framework baseline; §2 says what differs
141
+ CLAUDE.md imports AGENTS.md for Claude Code
142
+ .claude/commands/ /make-case, /explore-case, /gen-spec, /heal-spec (thin shells that load the framework skill)
143
+ .claude/skills/ optional project overrides of a skill: <skill>.md
144
+ .opencode/commands/ the same commands for OpenCode (it does not read .claude/commands)
145
+ .mcp.json the playwright-test MCP server for Claude Code
146
+ opencode.jsonc the same for OpenCode, plus an optional model
147
+ .asrun.json marker: framework version and the generated file list
148
+ package.json the workspace scripts (`asrun`, `signin`, ...) and devDependencies
149
+ .env ASRUN_ROOT (which workspace the commands use) and local settings
150
+ node_modules/@asrunjs/asrun/ the framework (installed dependency; never edited)
151
+ .auth/ saved logins, one per profile
152
+ reports/ one folder per run; reports/latest points at the newest
153
+ .tmp/ .explore/ job logs, hints, reference screenshots (local state)
154
+ ```
155
+
156
+ The framework repository's own layout is in [agents/framework-dev.md](./agents/framework-dev.md).
157
+
158
+ ## What to commit
159
+
160
+ Commit `profiles.yaml`, `cases/`, `tests/` (with `*-snapshots/`), `AGENTS.md`, `CLAUDE.md`, `.claude/`,
161
+ `.opencode/commands/`, `.mcp.json`, `opencode.jsonc`, `.asrun.json`, the generated files, `package.json`, `tsconfig.json`
162
+ and `pnpm-workspace.yaml`.
163
+
164
+ Never commit or share `.auth/` (one saved login per profile, as sensitive as a password), `reports/`, `.tmp/`,
165
+ `.explore/`, `.env` or `node_modules/`. The asrun block of `.gitignore` covers them.
166
+
167
+ ## Uninstall
168
+
169
+ ```bash
170
+ pnpm remove @asrunjs/asrun
171
+ ```
172
+
173
+ Then delete the workspace files you no longer want (`profiles.yaml`, `cases/`, `tests/`, the generated commands and
174
+ configs, `.asrun.json`). They are ordinary files: keep them if another tool still uses them.
@@ -0,0 +1,139 @@
1
+ # Profiles and environments
2
+
3
+ A **profile** is one environment of the system under test: a URL, an account label, optional tags and the environment
4
+ name. Each entry in `profiles.yaml` becomes one Playwright project named after its `id`, and every spec runs once per
5
+ profile. Two profiles of the same site give you two independent views, logins and baselines.
6
+
7
+ ```yaml
8
+ profiles:
9
+ - id: example-staging
10
+ name: Example (staging)
11
+ env: staging
12
+ baseURL: https://staging.example.com
13
+ account: qa@example.com
14
+ tags: [core, us]
15
+ email: false
16
+ startPath: /app/home
17
+ sessionCheckPath: /settings
18
+ sessionCheck:
19
+ loggedOutUrl: /login
20
+ loggedInSelector: "input[name='username']"
21
+ timeoutMs: 5000
22
+ ```
23
+
24
+ ## Fields
25
+
26
+ | Field | Meaning |
27
+ |---|---|
28
+ | `id` | Lowercase words with single dashes. It names the Playwright project, the saved session (`.auth/<id>.json`) and the baseline files. |
29
+ | `name` | What people see in the UI and the reports. |
30
+ | `env` | The environment this profile points at (`production`, `staging`, ...). `--env <env>` selects the profiles of one environment. Any environment except `staging` protects writing: a case that writes there needs a person's confirmation on every run. |
31
+ | `baseURL` | The site address, without a path. Must be a full `http(s)` URL. |
32
+ | `account` | A label for humans (a mailbox or username). Never a password: logins live in `.auth/`. |
33
+ | `tags` | Optional lowercase words, for grouping and `pnpm asrun --tag <tag>`. Duplicates are refused. |
34
+ | `email` | `true` only when a real mailbox is connected, so tests may send email. Defaults to `false`; `true` is refused for `production`. |
35
+ | `startPath` | The page "Visit" opens after a login, for example `/app/home`. Empty (the default) opens the site root. A path on the site: one leading `/`, no spaces, never another host. |
36
+ | `sessionCheckPath` | A page a signed-in visitor sees (default `/account/`). "Verify" and the pre-run check use it to tell whether a saved login still works. |
37
+ | `sessionCheck` | How that page shows a missing login. See below. |
38
+
39
+ Expected values are **not** here. They live in each case's `## Values` block: `profiles.yaml` holds connection
40
+ settings only, so adding a profile never touches a case and two profiles can never fight over one case's values.
41
+
42
+ ## There is no default environment
43
+
44
+ `pnpm asrun` and every AI command require `--env <env>`; the Run tests page requires one radio button. A run selects
45
+ the profiles whose `env` matches.
46
+
47
+ `pnpm signin <profile-id>` and `pnpm explore <profile-id>` take no `--env`: the profile already names its environment.
48
+
49
+ ## Logins
50
+
51
+ Sessions are saved per profile in `.auth/<profile-id>.json`, one file per profile, and are as sensitive as passwords.
52
+
53
+ ```bash
54
+ pnpm signin example-staging
55
+ ```
56
+
57
+ A normal browser opens with a small card in its bottom-left corner. Log in yourself, including 2FA, wait for the
58
+ logged-in page, then press **Done, save the session** on the card (or press Enter in the terminal, which does the
59
+ same). The session is saved and verified. Nothing about 2FA is automated.
60
+
61
+ `pnpm status` shows the login state of every profile; `pnpm status --env staging` only of one environment.
62
+
63
+ Before every run, `pnpm asrun` checks the session of each profile in the chosen environment. A profile without a
64
+ saved session, or with an expired one, fails right away (no retries) and the run prints what to do:
65
+
66
+ ```
67
+ Not logged in (tests for these profiles will FAIL):
68
+ example-production expired -> pnpm signin example-production
69
+ Or skip them for this run: pnpm asrun --env production --skip-profile example-production
70
+ ```
71
+
72
+ Either log in or leave the profile out (`--skip-profile`, or `--logged-in` to keep only the working ones). If a
73
+ session expires in the middle of a run, the failed test is marked "session expired".
74
+
75
+ Login itself is not a test case. Never write a spec for the login page or the login flow.
76
+
77
+ ### Making the session check fit your site
78
+
79
+ The defaults fit a site that redirects a signed-out visitor to `/login`. Sites differ: a single-page app redirects
80
+ after it has started, some answer `401` or `403`, some show the login form at the same address. `sessionCheck` names
81
+ what your site does:
82
+
83
+ | Key | What it says |
84
+ |---|---|
85
+ | `loggedOutUrl` | Where a signed-out visitor ends up (default `/login`): a path on the site or a full URL for single sign-on. A `401`/`403` response counts too. |
86
+ | `loggedInSelector` | A selector that only exists while signed in, for example `"input[name='username']"`. |
87
+ | `loggedOutSelector` | A selector that proves the visitor is signed out, for example the password field of a login form shown at the same address. |
88
+ | `timeoutMs` | How long to wait for one of those signals (default 3000, 500–60000). |
89
+
90
+ Without a `loggedInSelector`, "not sent to the login page" counts as signed in, which can mislead on a slow
91
+ single-page app.
92
+
93
+ ## Visit and explore
94
+
95
+ **Visit** (Profiles page, or `pnpm explore <profile-id> [path]`) opens a logged-in browser window at the profile's
96
+ `startPath`, or at the site root when it is empty. Files downloaded in that window go to `~/Downloads` (an existing
97
+ file is never overwritten; a ` (1)` is added) and the saved path is printed in the terminal or the job log.
98
+
99
+ Use it when you need to see what a logged-in person sees, without writing anything. The exploring agent uses the same
100
+ mechanism, with the Playwright MCP server and the profile's project.
101
+
102
+ ## Adding a profile
103
+
104
+ 1. Add an entry to `profiles.yaml` (or use **Add a new profile** on the Profiles page, which appends it for you).
105
+ 2. `pnpm signin <id>` and log in.
106
+ 3. Add the values for that profile to the cases it runs: a column in each `## Values` block, or nothing when
107
+ `default` already fits.
108
+
109
+ Deleting a profile removes its entry and its saved login; screenshot baselines and old reports keep the id in their
110
+ file names and stay on disk. Duplicating one copies the settings under a new id and does **not** copy the saved login.
111
+
112
+ ## How a profile becomes a test
113
+
114
+ The profile id is the Playwright project name. The `profile` fixture exposes the running profile to a spec:
115
+
116
+ ```ts
117
+ // case: cases/example.md
118
+ import {caseTags} from '@asrunjs/asrun/cases';
119
+ import {configureCase, expect, test} from '@asrunjs/asrun/fixtures';
120
+ import {requireValues} from '@asrunjs/asrun/values';
121
+
122
+ test.describe('Example', {tag: caseTags('example')}, () => {
123
+ configureCase('example');
124
+ test('one outcome', async ({page, profile}) => {
125
+ // profile.id, profile.env, profile.baseURL, profile.account, profile.tags, profile.email
126
+ const expected = requireValues<{homeTitle: string}>('example', profile);
127
+ await test.step('1. Open the home page', async () => {
128
+ await page.goto('/');
129
+ await expect(page).toHaveTitle(expected.homeTitle);
130
+ });
131
+ });
132
+ });
133
+ ```
134
+
135
+ Expected values come from the case document through `requireValues(caseId, profile)`: its `## Values` block with
136
+ `default` deep-merged under the profile's own entry. A case with no values for the running profile, or with empty or
137
+ `TODO` values, fails with a plain-language message instead of a crash.
138
+
139
+ See [cases.md](./cases.md) for the document format and [commands.md](./commands.md) for the run filters.