@asrunjs/asrun 0.5.2 → 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/AGENTS.md +10 -0
- package/README.md +178 -469
- package/dist/scripts/go.mjs +188 -188
- package/dist/scripts/init.js +1 -1
- package/docs/agents/case-format.md +193 -0
- package/docs/agents/framework-dev.md +94 -6
- 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/troubleshooting.md +101 -0
- package/docs/web-ui.md +139 -0
- package/package.json +9 -1
- package/templates/AGENTS.md +2 -0
- package/ui/dist/assets/{dist-DSy3dVf4.js → dist-BudNAjOv.js} +1 -1
- package/ui/dist/assets/{dist-Ba-HC662.js → dist-C-eCSFja.js} +1 -1
- package/ui/dist/assets/{dist-C-Oyo1ZT.js → dist-CO4heSzh.js} +1 -1
- package/ui/dist/assets/{dist-C8MdhqgG.js → dist-CPLhRzom.js} +1 -1
- package/ui/dist/assets/{dist-Dq6sW5Ur.js → dist-DEPJZ3Me.js} +1 -1
- package/ui/dist/assets/{dist-CmpteMYY.js → dist-DZ7RiqmA.js} +1 -1
- package/ui/dist/assets/{dist-BGbe0-Aa.js → dist-LixzGniF.js} +1 -1
- package/ui/dist/assets/{dist-CG1pHn5m.js → dist-O_RVi4tP.js} +1 -1
- package/ui/dist/assets/{dist-BtMLMthJ.js → dist-jPS6HCJ5.js} +1 -1
- package/ui/dist/assets/{dist-jjxQutEm.js → dist-vmf3fnkM.js} +1 -1
- package/ui/dist/assets/{index-P_Yo0ZZn.js → index-BIGt8Hfz.js} +3 -3
- package/ui/dist/index.html +1 -1
package/docs/commands.md
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# Command reference
|
|
2
|
+
|
|
3
|
+
Every command runs in a workspace (the folder that owns `profiles.yaml`); see
|
|
4
|
+
[install.md](./install.md#which-workspace-a-command-runs-against). In a project the scripts of `package.json` are
|
|
5
|
+
thin wrappers, so `pnpm asrun`, `pnpm signin` and the rest work as below. The same commands through the package are
|
|
6
|
+
`pnpm exec asrun ...`.
|
|
7
|
+
|
|
8
|
+
| I want to... | Command |
|
|
9
|
+
|---|---|
|
|
10
|
+
| Install / update the framework | `pnpm dlx @asrunjs/asrun init --install`, `pnpm add -D @asrunjs/asrun@latest` |
|
|
11
|
+
| Prepare or check the workspace | `pnpm asrun init [--update]`, `pnpm asrun doctor` |
|
|
12
|
+
| Start the web UI | `pnpm go`, `pnpm ui`, `pnpm ui:dev` |
|
|
13
|
+
| See which profiles are logged in | `pnpm status [--env <env>]` |
|
|
14
|
+
| Log in a profile (a person, with 2FA) | `pnpm signin <profile-id>` |
|
|
15
|
+
| Run tests | `pnpm asrun --env <env> [filters]` |
|
|
16
|
+
| Open the last report | `pnpm report` |
|
|
17
|
+
| Look around a page as a profile | `pnpm explore <profile-id> [path]` |
|
|
18
|
+
| Read what the last report says about a case | `pnpm heal:evidence --case <case-id> --env <env>` |
|
|
19
|
+
| List or forget test data that could not be removed | `pnpm cleanup --env <env> [--forget]` |
|
|
20
|
+
| Create or validate a case | `pnpm case:new <id>`, `pnpm case:check [case-id]` |
|
|
21
|
+
| Check a spec | `pnpm spec:lint [case-id]`, `pnpm baselines:review [case-id] [--new]` |
|
|
22
|
+
| Look at a PDF | `pnpm pdf:render <file.pdf> --out <dir> [--pages all] [--corner top-left]` |
|
|
23
|
+
| Type-check the workspace | `pnpm typecheck` |
|
|
24
|
+
|
|
25
|
+
## Running tests: `pnpm asrun`
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pnpm asrun --env staging
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
`--env` is **required**; there is no default. A run selects the profiles whose `env` matches, checks their sessions,
|
|
32
|
+
runs the selected cases once per profile, and writes one report folder per run.
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pnpm asrun --env production --profile example-production
|
|
36
|
+
pnpm asrun --env production --skip-profile example-staging,example-production
|
|
37
|
+
pnpm asrun --env staging --tag us
|
|
38
|
+
pnpm asrun --env production --logged-in
|
|
39
|
+
pnpm asrun --env production --suite smoke
|
|
40
|
+
pnpm asrun --env production --suite smoke,regression
|
|
41
|
+
pnpm asrun --env production --grep landing
|
|
42
|
+
pnpm asrun --env production --read-only
|
|
43
|
+
pnpm asrun --env staging --ui
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
| Flag | Effect |
|
|
47
|
+
|---|---|
|
|
48
|
+
| `--env <env>` | Required. The environment to run. |
|
|
49
|
+
| `--profile a,b` | Only these profiles. |
|
|
50
|
+
| `--skip-profile a,b` | Every profile except these (for example the ones that are not logged in). |
|
|
51
|
+
| `--tag <tag>` | Only the profiles that carry the tag (comma-separated for several). |
|
|
52
|
+
| `--logged-in` | Only the profiles whose saved session works; the others are left out instead of failing. |
|
|
53
|
+
| `--suite <name>` | Only the cases whose front matter lists the suite (comma-separated; a case in two chosen suites runs once). `--priority` is the old name. |
|
|
54
|
+
| `--grep <text>` | Only the cases whose title matches (Playwright `--grep`). |
|
|
55
|
+
| `--read-only` | Leave out every case that writes data. |
|
|
56
|
+
| `--allow-writes=<env>` | Run the write cases in that environment without the prompt. For unattended runs only; AI agents never use it. |
|
|
57
|
+
| `--step-text` | Overlay the current test step on the videos (`=1\|true\|yes\|on`; off by default). |
|
|
58
|
+
| `--update-baselines <case,case>` | Overwrite the screenshot baselines of exactly these cases. See below. |
|
|
59
|
+
| `--report-id <id>` | Name of the report folder (default: the timestamp). |
|
|
60
|
+
| `--ui`, other Playwright options | Passed through to Playwright. `--ui` opens its interactive mode. |
|
|
61
|
+
|
|
62
|
+
A case that writes data runs only in the environments it lists under `writes`; elsewhere it is skipped with a notice.
|
|
63
|
+
In **staging** no confirmation is needed. In any other environment a person confirms every run: `pnpm asrun` lists
|
|
64
|
+
the write cases and asks you to type the environment name; the web UI asks in a dialog. Unattended runs combine the
|
|
65
|
+
prompt with `--allow-writes=<env>`; AI agents must not. `--read-only` leaves the cases out instead.
|
|
66
|
+
|
|
67
|
+
## Reports and evidence
|
|
68
|
+
|
|
69
|
+
Every run writes to `reports/<run-id>/`, where the run id is the timestamp and the environment
|
|
70
|
+
(`reports/2026-10-28T07-30-22-staging`), and points `reports/latest` at the newest one.
|
|
71
|
+
|
|
72
|
+
- **Video:** one per test, with the recordings of every browser tab joined in order into `reports/<run>/videos/`.
|
|
73
|
+
Joining needs ffmpeg; without it the report lists the recordings separately. `--step-text` writes the current step
|
|
74
|
+
on the video.
|
|
75
|
+
- **Trace:** the page before and after every action, plus every network request. Open a trace directly with
|
|
76
|
+
`pnpm exec playwright show-trace <trace.zip>`.
|
|
77
|
+
- **Screenshots:** on failure, and the baselines of visual checks with their diff.
|
|
78
|
+
- **Leftover test data:** listed in red in the report and in the terminal.
|
|
79
|
+
|
|
80
|
+
`pnpm report` opens the HTML report of the latest run. `pnpm asrun` also prints the paths of the videos and traces
|
|
81
|
+
when it finishes. Recordings contain real pages: do not share them outside the team.
|
|
82
|
+
|
|
83
|
+
## Baselines
|
|
84
|
+
|
|
85
|
+
Baselines are per profile (`...-example-staging-darwin.png`) and are the source of truth for pixel checks.
|
|
86
|
+
|
|
87
|
+
Fix the spec first (the check must look at the element its target card names), then:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
pnpm asrun --env staging --profile example-staging --grep header-logo --update-baselines header-logo
|
|
91
|
+
pnpm baselines:review header-logo
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
- `--update-baselines` lists the cases whose images are overwritten; they must be among the cases the filters run.
|
|
95
|
+
Playwright has one global switch, so those cases run as a second pass with their own report
|
|
96
|
+
(`<run-id>-baselines`, for example `2026-10-28T07-30-22-staging-baselines`); every other case is compared with its
|
|
97
|
+
baselines as usual.
|
|
98
|
+
- Without it, Playwright's own `--update-snapshots` still rewrites every image of the run.
|
|
99
|
+
- In the web UI every case in **Cases to run** has an **Update baselines** box (unticked by default), with a
|
|
100
|
+
confirmation, and the new images are shown next to their target cards when the run ends.
|
|
101
|
+
- An agent never updates a baseline: that is a person's decision.
|
|
102
|
+
|
|
103
|
+
## Login and sessions
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
pnpm status # every profile, or: pnpm status --env staging
|
|
107
|
+
pnpm signin example-staging
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
The login is done by a person, including 2FA; the session is saved to `.auth/<profile-id>.json` and verified. See
|
|
111
|
+
[profiles.md](./profiles.md).
|
|
112
|
+
|
|
113
|
+
## Cases and checks
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
pnpm case:new header-logo
|
|
117
|
+
pnpm case:new documents/header-logo
|
|
118
|
+
pnpm case:check
|
|
119
|
+
pnpm spec:lint header-logo
|
|
120
|
+
pnpm baselines:review --new
|
|
121
|
+
pnpm pdf:render report.pdf --out .explore/targets/header-logo --corner top-left
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
- `case:new` creates the case from the template. One optional group folder, created on demand.
|
|
125
|
+
- `case:check` validates front matter, values and target cards; see [cases.md](./cases.md).
|
|
126
|
+
- `spec:lint` is a static check of the specs: it fails on sleeps, `networkidle`, image checks without a target,
|
|
127
|
+
`.first()` on images, raw download events, a magic-number-only PDF check. `pnpm spec:lint <case-id>` limits it.
|
|
128
|
+
- `baselines:review` opens nothing; it lists every baseline next to the target card that says what it must show.
|
|
129
|
+
`--new` lists only the images that are new or changed (by git).
|
|
130
|
+
- `pdf:render` writes page images and prints the text, so a person (or an exploring agent) can look at a PDF.
|
|
131
|
+
|
|
132
|
+
## Exploring and healing evidence
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
pnpm explore example-staging /app/home
|
|
136
|
+
pnpm heal:evidence --case header-logo --env staging
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
`explore` opens a logged-in browser at the profile's `startPath` (or the path you pass, or the site root). Downloads
|
|
140
|
+
are saved to `~/Downloads` and never overwrite an existing file. `heal:evidence` prints the last run's status, failed
|
|
141
|
+
step, error, call log and screenshots for a case, and whether the report is stale; it is what `/heal-spec` reads
|
|
142
|
+
first.
|
|
143
|
+
|
|
144
|
+
## Cleanup
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
pnpm cleanup --env staging
|
|
148
|
+
pnpm cleanup --env staging --forget
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
A write case registers everything it creates (named `[asrun] ...`) and removes it after the test, also after a
|
|
152
|
+
failure. Whatever could not be removed is listed in the report and by `cleanup`. Delete it by hand in the product,
|
|
153
|
+
then run `--forget` so it is not listed again. `--env` is required.
|
|
154
|
+
|
|
155
|
+
## Web UI
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
pnpm go # install + build + start + open the browser (the usual command)
|
|
159
|
+
pnpm ui # build the web app and serve http://127.0.0.1:4173
|
|
160
|
+
pnpm ui:dev # Vite on :5173 with hot reload, API in watch mode
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
The UI only listens on `127.0.0.1`. Set `ASRUN_UI_PORT` to change the port (`ASRUN_UI_WEB_PORT` for the Vite dev
|
|
164
|
+
server). See [web-ui.md](./web-ui.md).
|
|
165
|
+
|
|
166
|
+
## Workspace
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
pnpm asrun init [--update] [--force <file>] [--reset-rules] [--root <dir>] [--no-env] [--install [spec]]
|
|
170
|
+
pnpm asrun doctor
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
`init` writes the workspace files; `--update` refreshes only the generated ones. `doctor` checks profiles,
|
|
174
|
+
`tsconfig.json`, `playwright.config.ts`, the generated files, the installed version, the project rule table and the
|
|
175
|
+
skill overrides. Full details in [install.md](./install.md).
|
package/docs/install.md
ADDED
|
@@ -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.
|
package/docs/profiles.md
ADDED
|
@@ -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.
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Troubleshooting
|
|
2
|
+
|
|
3
|
+
## Login
|
|
4
|
+
|
|
5
|
+
**`Profile "x" is not logged in`** — run `pnpm signin <profile-id>` and log in. To leave that profile out instead:
|
|
6
|
+
`pnpm asrun --env <env> --skip-profile <profile-id>`, or `--logged-in` to keep only the working ones. Login itself is
|
|
7
|
+
never a test; a missing or expired session fails right away, without retries.
|
|
8
|
+
|
|
9
|
+
**A saved session expires in the middle of a run** — the failed test is marked "session expired". Log in again and
|
|
10
|
+
rerun.
|
|
11
|
+
|
|
12
|
+
**`pnpm signin` opened a browser but nothing was saved** — the session is saved when you press **Done, save the
|
|
13
|
+
session** on the card in the bottom-left corner of that window (or press Enter in the terminal). Nothing about 2FA is
|
|
14
|
+
automated.
|
|
15
|
+
|
|
16
|
+
## Running
|
|
17
|
+
|
|
18
|
+
**`Choose the environment to test`** — there is no default. Add `--env production` or `--env staging`; the Run tests
|
|
19
|
+
page needs one radio button.
|
|
20
|
+
|
|
21
|
+
**A case is skipped, not failed** — a case that writes data runs only in the environments under `writes`; a case with
|
|
22
|
+
`envs` runs only there. The run prints "skipped" with the reason.
|
|
23
|
+
|
|
24
|
+
**`This case writes data and has no permission to run in PRODUCTION`** — a person confirms production writes on every
|
|
25
|
+
run (type the environment name, or use the dialog in the web UI). `--read-only` leaves the write cases out. Unattended
|
|
26
|
+
runs use `--allow-writes=<env>`; AI agents must not.
|
|
27
|
+
|
|
28
|
+
**`--update-baselines` was refused** — the named cases must be among the cases the filters run, and cannot be combined
|
|
29
|
+
with Playwright's own `--update-snapshots`.
|
|
30
|
+
|
|
31
|
+
**The report is empty or missing** — run `pnpm asrun --env <env>` first. `pnpm report` opens `reports/latest`.
|
|
32
|
+
|
|
33
|
+
**Screenshot mismatch** — open the report: the diff view shows expected, actual and diff. If the change is intended,
|
|
34
|
+
update the baseline on purpose ([commands.md](./commands.md#baselines)); if not, fix the spec or the product. Never let
|
|
35
|
+
an agent overwrite a baseline to hide a difference.
|
|
36
|
+
|
|
37
|
+
**Videos are separate per tab, or there is no merged video** — ffmpeg is missing. `pnpm go` tries to install it
|
|
38
|
+
(Homebrew, winget); on Linux run `sudo apt install ffmpeg`. Without it, the optional `ffmpeg-static` package is used;
|
|
39
|
+
if neither works, the report lists the recordings separately.
|
|
40
|
+
|
|
41
|
+
**Downloads did not arrive** — files downloaded in a **Visit** window (the UI) or with `pnpm explore` are saved to
|
|
42
|
+
`~/Downloads` and never overwrite an existing file; the saved path is printed in the job log or terminal.
|
|
43
|
+
|
|
44
|
+
**The UI port is busy** — set `ASRUN_UI_PORT` (and `ASRUN_UI_WEB_PORT` for `pnpm ui:dev`).
|
|
45
|
+
|
|
46
|
+
## Cases and specs
|
|
47
|
+
|
|
48
|
+
**`Missing values: cases/<id>.md has no "## Values" for profile ...`** — add the profile's values (or a `default`
|
|
49
|
+
entry) to the case's `## Values` block. See [cases.md](./cases.md#values).
|
|
50
|
+
|
|
51
|
+
**`Incomplete values: ... still has empty or TODO values`** — fill in the named keys, or answer the `TODO` in the case
|
|
52
|
+
and regenerate the spec.
|
|
53
|
+
|
|
54
|
+
**`pnpm case:check` fails on a target card** — the card is missing a required field (`What`, `Where`,
|
|
55
|
+
`Identified by`), refers to a reference image outside `.explore/targets/<case-id>/`, or a check refers to a `[T<n>]`
|
|
56
|
+
that has no card. A blocking question (`🚫 TODO [blocking]`) is the normal way to wait for a person's answer.
|
|
57
|
+
|
|
58
|
+
**`pnpm spec:lint` fails** — read the rule name in the message: `no-sleep` (`waitForTimeout`), `no-networkidle`,
|
|
59
|
+
download handling, a screenshot or image check without `// target: T<n>`, a PDF check on a magic number alone. The
|
|
60
|
+
fixes are in the framework's `generating-specs` conventions; do not silence the rule.
|
|
61
|
+
|
|
62
|
+
**A case step marked `[input]` or `[writes]` stops `/gen-spec` with `needs-input`** — that step has no
|
|
63
|
+
`- Recipe:` line. Run `/explore-case <case-id> <env> --attended` and do the step in the browser once, so the recipe
|
|
64
|
+
is recorded.
|
|
65
|
+
|
|
66
|
+
**`pnpm case:check` warns that a reference image is not on this machine** — `.explore/` is git-ignored: a fresh
|
|
67
|
+
checkout has the card but not the screenshot. Run the exploration again on this machine (or look at the card and the
|
|
68
|
+
report's baseline).
|
|
69
|
+
|
|
70
|
+
## AI steps
|
|
71
|
+
|
|
72
|
+
**An AI command says the `playwright-test` MCP server is missing** — run `pnpm asrun doctor`. OpenCode reads only
|
|
73
|
+
`opencode.jsonc`; Claude Code reads only `.mcp.json`. `pnpm asrun init` writes both. Check with `opencode mcp list` /
|
|
74
|
+
`claude mcp list`; `failed: ... path specified` means the `playwright` path in that file does not resolve (repair it
|
|
75
|
+
with `pnpm asrun init --force <file>`).
|
|
76
|
+
|
|
77
|
+
**An AI job runs but uses a slow or limited model** — the top bar's **Model** picker sets the model for UI runs;
|
|
78
|
+
without one, a one-shot OpenCode run uses its built-in free model, which has a daily usage limit. Set `"model"` in
|
|
79
|
+
`opencode.jsonc` to stop depending on the browser's choice. For Claude Code, the picker lists what its settings,
|
|
80
|
+
environment, aliases or gateway expose.
|
|
81
|
+
|
|
82
|
+
**Thinking does nothing** — it is an OpenCode flag. Claude Code has no such flag; its model and settings decide.
|
|
83
|
+
|
|
84
|
+
**A project rule does nothing** — `AGENTS.md` §2 needs the rule id and an `Effect` of `narrows` or `replaces`.
|
|
85
|
+
`F1`..`F6` cannot be changed. `pnpm asrun doctor` names a wrong id, a missing `Effect` or a non-negotiable rule.
|
|
86
|
+
|
|
87
|
+
**A skill override does nothing** — it must be `.claude/skills/<skill>.md` (the skill name, for example
|
|
88
|
+
`generating-specs`, not the command name), and it is not discovered as a standalone skill: the command and the skill
|
|
89
|
+
read it. `doctor` lists the overrides it sees.
|
|
90
|
+
|
|
91
|
+
**`pnpm asrun init --force AGENTS.md` is refused** — `AGENTS.md`, `CLAUDE.md` and `.claude/skills/**` belong to the
|
|
92
|
+
project. Add `--reset-rules` to replace them with the template, and expect to lose the project's rules.
|
|
93
|
+
|
|
94
|
+
**`ERR_PNPM_IGNORED_BUILDS` while installing** — pnpm refused the build scripts of `esbuild` / `ffmpeg-static`. Run
|
|
95
|
+
`pnpm dlx @asrunjs/asrun init` first (it writes the `allowBuilds` entry), or add it to `pnpm-workspace.yaml` by hand;
|
|
96
|
+
see [install.md](./install.md#manual-install).
|
|
97
|
+
|
|
98
|
+
**The AI did something you did not expect** — exploring and generating only read pages and edit the case file; healing
|
|
99
|
+
edits the spec and runs it under the same write gate as any run (staging writes are allowed, any other environment
|
|
100
|
+
needs a person's confirmation). None of them writes product data on its own, and none updates a baseline. If a step
|
|
101
|
+
needs a person to write, run it with `--attended`; that is where a person acts, in the browser.
|