@unotest/web 0.9.1 → 0.11.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/skills/write-e2e-test/SKILL.md +118 -38
- package/.claude/skills/write-e2e-test-ground/SKILL.md +787 -0
- package/CHANGELOG.md +297 -0
- package/dist/config/schema.d.ts +29 -10
- package/dist/config/schema.js +1 -1
- package/dist/driver/index.d.ts +5 -2
- package/dist/driver/index.js +1 -1
- package/dist/dsl/index.d.ts +19 -3
- package/dist/dsl/index.js +1 -1
- package/dist/dsl/web-dsl-language-service.js +1 -1
- package/dist/inspection/page-inject.d.ts +11 -35
- package/dist/inspection/page-inject.js +173 -1032
- package/dist/{interfaces-dbZG10RS.d.ts → interfaces-MxHi1Air.d.ts} +50 -3
- package/dist/mcp/server.js +1 -1
- package/dist/runner/cli.d.ts +11 -1
- package/dist/runner/cli.js +1 -1
- package/dist/runner/init/chrome-probe.js +1 -1
- package/dist/runner/init.js +1 -1
- package/dist/runner/install-chromium.js +1 -1
- package/dist/runner/prepare-fix.js +1 -1
- package/dist/runner/scaffold-workspace.js +1 -1
- package/dist/runner/serve-fixture.js +1 -1
- package/dist/runner/web-runner-adapter.js +1 -1
- package/guides/agent-integration.md +103 -2
- package/guides/dsl-reference.md +150 -13
- package/package.json +10 -38
- package/src/mcp/prompts/agent-test-author.md +71 -3
|
@@ -69,6 +69,65 @@ Disambiguate multi-matches with `.filter({hasText: '…'})` or
|
|
|
69
69
|
`.nth(N)`. The linter flags index-based picking
|
|
70
70
|
(`lint:disambig-by-index`) because element order is brittle.
|
|
71
71
|
|
|
72
|
+
## Discovery: snapshot mode vs ground mode
|
|
73
|
+
|
|
74
|
+
Which discovery tool exists depends on the server's mode — check the
|
|
75
|
+
tool list once. `get_page_snapshot` present — snapshot mode. Absent
|
|
76
|
+
(the server runs with `UNOTEST_SNAPSHOT=0`, ground mode) — it does not
|
|
77
|
+
exist, do NOT search or retry for it. In ground mode you normally need
|
|
78
|
+
no discovery call at all — act directly with an **intent locator**;
|
|
79
|
+
the runner grounds it inside the same call:
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
locator: {kind:"locator", steps:[{kind:"intent",
|
|
83
|
+
intent:"<the element in the user's words>",
|
|
84
|
+
ordinal?: "first" | "last" | N}]}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Positions go in `ordinal` (typed), never in the intent text. Name the
|
|
88
|
+
target itself, not its page region. Lookups for when you must look
|
|
89
|
+
before acting: `ground_element {intent, ordinal?}` (element line +
|
|
90
|
+
top-20 candidates) and `find_element {role, name, near?}` (exact ARIA
|
|
91
|
+
query). Any "re-`get_page_snapshot`" advice below reads as "retry the
|
|
92
|
+
intent step / re-ground via `ground_element`" in ground mode.
|
|
93
|
+
|
|
94
|
+
## Batching: `explore_steps`
|
|
95
|
+
|
|
96
|
+
When the next several steps are known in advance, send ONE
|
|
97
|
+
`explore_steps {explorationId?, steps:[…]}` call — up to 25 steps, each
|
|
98
|
+
with the same shape as `explore_step`. Sequential; stops at the first
|
|
99
|
+
failure; the reply carries per-step status + `firstError`; only the
|
|
100
|
+
steps that executed are recorded.
|
|
101
|
+
|
|
102
|
+
Every step whose locator was an intent replies with `groundedTo` — the
|
|
103
|
+
element line the intent resolved to. Check the target there rather than
|
|
104
|
+
pre-probing with `ground_element`. A step that CHANGED the address also
|
|
105
|
+
replies with `url`: the last one in the batch is where you ended up, so
|
|
106
|
+
`get_active_context` / `get_url` afterwards re-reads what you have.
|
|
107
|
+
|
|
108
|
+
Record checks as assert steps — `assert_text {locator, text, options?:
|
|
109
|
+
{exact}}`, `assert_visible {locator}`, `assert_hidden {locator}`,
|
|
110
|
+
`assert_value {locator, value}`, `assert_count {locator, count}`,
|
|
111
|
+
`assert_url {pattern}`. When the brief names no check, verify with the
|
|
112
|
+
text it DID give you — the last thing you were told to click is on the
|
|
113
|
+
page you land on, so assert that name inside the same batch, quoting
|
|
114
|
+
the text alone (`"<name>"`, not `heading "<name>"` — the words often
|
|
115
|
+
live in a row or a link, and a role word then grounds to nothing);
|
|
116
|
+
don't spend calls hunting for something assertable.
|
|
117
|
+
`assert_url` is for briefs that name the destination URL. On a checkbox
|
|
118
|
+
/ radio / switch `assert_value`
|
|
119
|
+
takes `"true"` / `"false"` and asserts the checked state — no CSS
|
|
120
|
+
`:checked` selector. They execute live, so a recorded assert has
|
|
121
|
+
already passed against the real page; never hand-edit assertions into
|
|
122
|
+
the generated file. The final batch of a scenario can pass `autoRun:
|
|
123
|
+
true`: when every step succeeds, a verification step is recorded and
|
|
124
|
+
the draft has no blocking warnings (FRAGILE_LOCATOR passes and is
|
|
125
|
+
reported in the reply; DYNAMIC_TEXT / NO_DSL_PRIMITIVE block), the same
|
|
126
|
+
call saves the scenario, resets the context and runs it
|
|
127
|
+
(`autoRun.run.next.outcome === "completed"` is the pass signal);
|
|
128
|
+
otherwise `autoRun.status: "skipped"` + reason and the manual
|
|
129
|
+
generate → save → run flow applies.
|
|
130
|
+
|
|
72
131
|
## Recording: name the `section` like a test-plan line
|
|
73
132
|
|
|
74
133
|
Each `explore_step`/`explore_record` carries a `section` that becomes a
|
|
@@ -176,6 +235,11 @@ result = evaluate(
|
|
|
176
235
|
);
|
|
177
236
|
```
|
|
178
237
|
|
|
238
|
+
Index access reads data off a **bare variable** only — `obj.prop[i]`
|
|
239
|
+
does not parse; assign the member first:
|
|
240
|
+
`m = state.matches; last = m[m.length - 1];`. `arr[i] = v` stays
|
|
241
|
+
rejected.
|
|
242
|
+
|
|
179
243
|
## Sandbox primitives
|
|
180
244
|
|
|
181
245
|
For test setup / teardown that touches the host machine, the registry
|
|
@@ -187,9 +251,13 @@ exposes four primitives — **but the consumer project's
|
|
|
187
251
|
- **`dbQuery(sql, …params)`** / **`dbExec(sql, …params)`** —
|
|
188
252
|
parameterized SQL against `sandbox.database` (postgres / mysql /
|
|
189
253
|
sqlite, picked by URL scheme).
|
|
190
|
-
- **`apiCall(method, path, body?, headers?)`** — HTTP fetch
|
|
191
|
-
`sandbox.apiBaseUrl`. **Path-only** — absolute URLs throw to
|
|
192
|
-
scenarios from hitting arbitrary hosts.
|
|
254
|
+
- **`apiCall(method, path, body?, headers?, opts?)`** — HTTP fetch
|
|
255
|
+
against `sandbox.apiBaseUrl`. **Path-only** — absolute URLs throw to
|
|
256
|
+
prevent scenarios from hitting arbitrary hosts. Multipart upload:
|
|
257
|
+
pass `upload('fixtures/doc.pdf', {field?, fields?})` as the body
|
|
258
|
+
(relative path inside the project / `sandbox.uploadDir`; don't set
|
|
259
|
+
Content-Type yourself). A JSON body with a `file` key posts as plain
|
|
260
|
+
JSON — there is no key-name magic.
|
|
193
261
|
|
|
194
262
|
If a primitive throws with "sandbox.X is not configured", tell the
|
|
195
263
|
user to add it to `unotest.config.*`. Do not infer secrets from the
|