@unotest/web 0.5.0 → 0.6.2

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/package.json CHANGED
@@ -1,22 +1,14 @@
1
1
  {
2
2
  "name": "@unotest/web",
3
- "version": "0.5.0",
3
+ "version": "0.6.2",
4
4
  "description": "AI-native E2E testing for web applications. MCP server (run_test / step / resume / inspect_runtime / agent_fix) + CLI runner + JavaScript DSL scenarios on a sandboxed AST engine + semantic DOM snapshots.",
5
5
  "license": "MIT",
6
6
  "publishConfig": {
7
7
  "access": "public"
8
8
  },
9
9
  "author": "Ivan Volkov <ivan@volkov.io>",
10
- "repository": {
11
- "type": "git",
12
- "url": "git+https://github.com/unotest/web.git"
13
- },
14
- "homepage": "https://github.com/unotest/web#readme",
15
- "bugs": {
16
- "url": "https://github.com/unotest/web/issues"
17
- },
10
+ "homepage": "https://www.npmjs.com/package/@unotest/web",
18
11
  "type": "module",
19
- "packageManager": "pnpm@10.12.4",
20
12
  "engines": {
21
13
  "node": ">=20"
22
14
  },
@@ -51,20 +43,41 @@
51
43
  "files": [
52
44
  "bin",
53
45
  "dist",
54
- "examples/fixtures",
55
- "!examples/fixtures/**/*.test.*",
46
+ "guides",
47
+ "examples/fixtures/break-fix-app/index.html",
48
+ "examples/fixtures/break-fix-app/README.md",
49
+ "examples/fixtures/break-fix-app/serve.mjs",
56
50
  "src/mcp/prompts",
57
51
  ".claude/skills",
52
+ ".claude/hooks",
58
53
  "CHANGELOG.md",
59
54
  "LICENSE",
60
55
  "README.md"
61
56
  ],
57
+ "dependencies": {
58
+ "@modelcontextprotocol/sdk": "^1.0.4",
59
+ "chokidar": "^4.0.3",
60
+ "dotenv": "^16.4.5",
61
+ "playwright": "^1.49.0",
62
+ "tsx": "^4.19.2",
63
+ "yaml": "^2.6.0",
64
+ "zod": "^3.23.8",
65
+ "@unotest/dsl": "^0.6.2",
66
+ "@unotest/protocol": "^0.6.2",
67
+ "@unotest/viewer": "^0.6.2"
68
+ },
69
+ "devDependencies": {
70
+ "@types/node": "^22.10.0",
71
+ "javascript-obfuscator": "^4.1.1",
72
+ "tsup": "^8.5.1",
73
+ "typescript": "^5.7.2"
74
+ },
62
75
  "scripts": {
63
76
  "typecheck": "tsc --noEmit",
64
77
  "design": "npx serve -l 3111 design",
65
78
  "test": "node --import tsx --test --test-reporter=spec 'src/**/*.test.ts' 'scripts/**/*.test.mjs' 'evals/**/*.test.ts'",
66
79
  "compile": "tsup",
67
- "compile:all": "pnpm -C ../unotest-dsl compile && pnpm -C ../unotest-viewer compile && pnpm -C ../unotest-web compile",
80
+ "compile:all": "pnpm -C ../unotest-dsl compile && pnpm -C ../unotest-web compile && pnpm -C ../unotest-viewer compile",
68
81
  "lint:scenarios": "tsx src/runner/cli.ts lint",
69
82
  "smoke:bundle": "node scripts/smoke-bundle.mjs",
70
83
  "check:docs-sync": "node scripts/check-docs-sync.mjs",
@@ -74,6 +87,7 @@
74
87
  "check:skill-prompt-sync": "node scripts/check-skill-prompt-sync.mjs",
75
88
  "check:tarball": "node scripts/check-tarball.mjs",
76
89
  "check:sibling-versions": "node scripts/check-sibling-versions.mjs",
90
+ "build:guides": "node scripts/build-guides.mjs",
77
91
  "prepublish:check": "node scripts/prepublish-check.mjs",
78
92
  "evals:run": "tsx evals/run.ts",
79
93
  "evals:prepare": "tsx evals/prepare.ts",
@@ -83,33 +97,10 @@
83
97
  "perf:baseline": "node scripts/perf-baseline.mjs",
84
98
  "verify": "pnpm typecheck && pnpm lint:scenarios && pnpm check:surface && pnpm test",
85
99
  "obfuscate": "node scripts/obfuscate.mjs",
86
- "build": "pnpm verify && pnpm compile && pnpm obfuscate && pnpm smoke:bundle && pnpm check:tarball",
100
+ "build": "pnpm verify && pnpm compile && pnpm obfuscate && pnpm smoke:bundle && pnpm build:guides && pnpm check:tarball",
87
101
  "e2e": "tsx src/runner/cli.ts e2e",
88
102
  "m1:cross-browser": "node scripts/m1-cross-browser.mjs",
89
103
  "mcp": "tsx src/mcp/server.ts",
90
- "serve-fixture": "node examples/fixtures/break-fix-app/serve.mjs",
91
- "prepublishOnly": "pnpm build"
92
- },
93
- "dependencies": {
94
- "@modelcontextprotocol/sdk": "^1.0.4",
95
- "@unotest/dsl": "^0.5.0",
96
- "@unotest/protocol": "^0.5.0",
97
- "@unotest/viewer": "^0.5.0",
98
- "dotenv": "^16.4.5",
99
- "playwright": "^1.49.0",
100
- "tsx": "^4.19.2",
101
- "yaml": "^2.6.0",
102
- "zod": "^3.23.8"
103
- },
104
- "pnpm": {
105
- "onlyBuiltDependencies": [
106
- "esbuild"
107
- ]
108
- },
109
- "devDependencies": {
110
- "@types/node": "^22.10.0",
111
- "javascript-obfuscator": "^4.1.1",
112
- "tsup": "^8.5.1",
113
- "typescript": "^5.7.2"
104
+ "serve-fixture": "node examples/fixtures/break-fix-app/serve.mjs"
114
105
  }
115
- }
106
+ }
@@ -19,7 +19,7 @@ Anthropic-, OpenAI-, or Google-specific prompt syntax.
19
19
  You are writing and debugging end-to-end tests for a web application,
20
20
  using the `@unotest/web` MCP tools. Your job is to:
21
21
 
22
- 1. Author `.js` scenarios under `unotest/e2e/` that follow this
22
+ 1. Author `.js` scenarios under `unotest/e2e/<feature>/` that follow this
23
23
  project's DSL — Playwright-vocabulary primitives (`goto`, `click`,
24
24
  `fill`, `getByRole`, `assertText`, …).
25
25
  2. Run them through the debugger tools (`run_test`, `step`, `resume`,
@@ -35,6 +35,20 @@ is at
35
35
  [dsl-reference.md](https://github.com/unotest/web/blob/main/docs/dsl-reference.md).
36
36
  Read that before writing more than a one-line scenario.
37
37
 
38
+ ## Layout — scenarios live in feature subfolders (hard rule)
39
+
40
+ Never place a scenario directly in `unotest/e2e/`. Every test file goes in a
41
+ feature/page subfolder that mirrors the site structure:
42
+
43
+ - `unotest/e2e/auth/login.js`
44
+ - `unotest/e2e/checkout/apply-coupon.js`
45
+ - `unotest/e2e/catalog/open-first-car-card.js`
46
+
47
+ The linter rejects a test in the e2e root (`lint:scenario-in-root`), and
48
+ `save_exploration_as_test` requires a `<feature>/<name>` path. Pick the folder
49
+ from the area of the app under test. Shared helpers live in
50
+ `unotest/e2e/_helpers/`; `_template.js` is a starting point to copy.
51
+
38
52
  ## Locator hierarchy (D-22, hard rule)
39
53
 
40
54
  When choosing a selector, prefer in this exact order:
@@ -169,6 +183,62 @@ If a primitive throws with "sandbox.X is not configured", tell the
169
183
  user to add it to `unotest.config.*`. Do not infer secrets from the
170
184
  environment yourself.
171
185
 
186
+ ## External variables & secrets
187
+
188
+ Don't hard-code logins, passwords, base URLs, or tokens in a scenario.
189
+ Reference them by a bare **UPPER_SNAKE** identifier — the runtime resolves it
190
+ from the project's variable store:
191
+
192
+ ```js
193
+ goto(APP_BASE_URL);
194
+ fill(getByLabel('Email'), TEST_USER_EMAIL);
195
+ fill(getByLabel('Password'), TEST_USER_PASSWORD);
196
+ ```
197
+
198
+ - Non-secret values live in `unotest/.env`; secret values in
199
+ `unotest/.secrets` (both gitignored). No `env()` / `secret()` wrapper and no
200
+ `${...}` — just the bare name.
201
+ - A lowercase / mixed-case identifier is an in-scenario binding (a variable you
202
+ assigned earlier), not an external one.
203
+ - An UPPER_SNAKE name that isn't in either file fails the run with
204
+ "external variable X not found — add it to unotest/.env". When you need a
205
+ value the project hasn't defined, tell the user to add it; don't inline a
206
+ literal.
207
+ - Secret values are auto-masked (`‹secret:NAME›`) in the debug log, so a
208
+ password passed to `fill` never lands on disk verbatim.
209
+
210
+ **While recording (`explore_*`):** `explore_start` returns
211
+ `availableVariables` (the project's declared names). When a step's value is one
212
+ of them, pass it as **`{{NAME}}`** — `explore_step({action:"fill", …,
213
+ value:"{{PASSWORD}}"})`. (A bare `NAME` also works when it's a declared
214
+ variable, but `{{NAME}}` is unambiguous in a plain string value, so prefer
215
+ it.) The runner substitutes the real value LIVE (so the login actually works)
216
+ and records a bare reference, so the saved test reads `fill(loc, PASSWORD)` and
217
+ the secret never enters the file or logs. The same applies to any
218
+ value/locator-text that is a variable (`goto` URL via `{{APP_BASE_URL}}`, a row
219
+ found by `getByText({{PARK}})`). If you pass a name that isn't declared, the
220
+ step returns a `variableHint` — define it in `.env`/`.secrets` or use a literal.
221
+
222
+ ## Reusable flows (auth/setup you record once)
223
+
224
+ A login (or any setup block) that many tests share should be a reusable
225
+ `flow_<name>()` helper, not re-recorded per test. `explore_start` returns
226
+ `availableFlows` — the flows already on disk.
227
+
228
+ - **Flow already exists** (it's in `availableFlows`): call
229
+ `explore_run_flow({ explorationId, name })`. It replays the flow LIVE (you end
230
+ up logged in) and records a single `flow_<name>()` call — don't re-record the
231
+ steps.
232
+ - **First time** (no flow yet): record the block normally but put
233
+ `flow:"signin"` on every step of it (`explore_step({…, flow:"signin"})`).
234
+ On `save_exploration_as_test`, those steps are extracted into
235
+ `unotest/e2e/_helpers/signin.js` as `flow_signin()`, and the test calls
236
+ `flow_signin()` in their place. One save writes both files.
237
+
238
+ Use the SAME flow name on every step of the block. Variables inside a flow
239
+ (`{{LOGIN}}` etc.) resolve at replay/run time, so the secret never lives in the
240
+ flow file either.
241
+
172
242
  ## When a locator matches `N elements`
173
243
 
174
244
  Every locator must resolve to exactly one element. When you see