@unotest/web 0.43.1 → 0.44.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 +7 -6
- package/.claude/skills/write-e2e-test-ground/SKILL.md +7 -6
- package/CHANGELOG.md +165 -3
- package/dist/config/schema.d.ts +93 -31
- package/dist/config/schema.js +1 -1
- package/dist/driver/index.d.ts +5 -3
- package/dist/driver/index.js +1 -1
- package/dist/dsl/index.d.ts +229 -6
- package/dist/dsl/index.js +1 -1
- package/dist/dsl/web-dsl-language-service.js +1 -1
- package/dist/inspection/page-inject.d.ts +23 -6
- package/dist/inspection/page-inject.js +57 -26
- package/dist/{interfaces-7nMNCr2h.d.ts → interfaces-DEmGI704.d.ts} +108 -10
- package/dist/mcp/server.js +1 -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 +30 -5
- package/guides/dsl-reference.md +433 -9
- package/package.json +9 -8
- package/types/unotest-dsl.d.ts +49 -0
|
@@ -427,9 +427,10 @@ Assertions:
|
|
|
427
427
|
|
|
428
428
|
State reads:
|
|
429
429
|
- `getTitle()`, `getUrl()`, `getAttribute(loc, name)`, `getInnerText(loc)`,
|
|
430
|
-
`getInputValue(loc)`, `count(loc)
|
|
431
|
-
|
|
432
|
-
(`assertTrue(
|
|
430
|
+
`getInputValue(loc)`, `count(loc)`. A string you already hold answers
|
|
431
|
+
`s.length`, `s.trim()`, `s.includes(x)`, `s.startsWith(x)`
|
|
432
|
+
(`assertTrue(out.stdout.includes('ready'))`); `textContains(haystack,
|
|
433
|
+
needle)` is the same probe as a function.
|
|
433
434
|
- A `textarea` / `input` VALUE is not page text: `waitForText` and
|
|
434
435
|
`assertText` never see it. Check it with `assertValue(loc, expected)`
|
|
435
436
|
— it polls until the timeout (5 s by default, `{timeout}` to raise
|
|
@@ -440,9 +441,9 @@ State reads:
|
|
|
440
441
|
- Only the vocabulary above is expression language. `!` is rejected by
|
|
441
442
|
the parser (`unotest-web lint`: `parse error — Invalid token`);
|
|
442
443
|
`JSON.stringify` / `Date.now()` / `indexOf` and other JS globals and
|
|
443
|
-
string methods are rejected by the validator
|
|
444
|
-
named (`use json(value)`, `use nowMs()`, `use
|
|
445
|
-
needle)`). Express the check as an assertion or a locator
|
|
444
|
+
string methods beyond the four above are rejected by the validator
|
|
445
|
+
with the replacement named (`use json(value)`, `use nowMs()`, `use
|
|
446
|
+
s.includes(needle)`). Express the check as an assertion or a locator
|
|
446
447
|
(`assertHidden`, `assertCount`, `.filter({hasText})`, `textContains`).
|
|
447
448
|
|
|
448
449
|
Sandbox primitives:
|
|
@@ -558,9 +558,10 @@ Assertions:
|
|
|
558
558
|
|
|
559
559
|
State reads:
|
|
560
560
|
- `getTitle()`, `getUrl()`, `getAttribute(loc, name)`, `getInnerText(loc)`,
|
|
561
|
-
`getInputValue(loc)`, `count(loc)
|
|
562
|
-
|
|
563
|
-
(`assertTrue(
|
|
561
|
+
`getInputValue(loc)`, `count(loc)`. A string you already hold answers
|
|
562
|
+
`s.length`, `s.trim()`, `s.includes(x)`, `s.startsWith(x)`
|
|
563
|
+
(`assertTrue(out.stdout.includes('ready'))`); `textContains(haystack,
|
|
564
|
+
needle)` is the same probe as a function.
|
|
564
565
|
- A `textarea` / `input` VALUE is not page text: `waitForText` and
|
|
565
566
|
`assertText` never see it. Check it with `assertValue(loc, expected)`
|
|
566
567
|
— it polls until the timeout (5 s by default, `{timeout}` to raise
|
|
@@ -571,9 +572,9 @@ State reads:
|
|
|
571
572
|
- Only the vocabulary above is expression language. `!` is rejected by
|
|
572
573
|
the parser (`unotest-web lint`: `parse error — Invalid token`);
|
|
573
574
|
`JSON.stringify` / `Date.now()` / `indexOf` and other JS globals and
|
|
574
|
-
string methods are rejected by the validator
|
|
575
|
-
named (`use json(value)`, `use nowMs()`, `use
|
|
576
|
-
needle)`). Express the check as an assertion or a locator
|
|
575
|
+
string methods beyond the four above are rejected by the validator
|
|
576
|
+
with the replacement named (`use json(value)`, `use nowMs()`, `use
|
|
577
|
+
s.includes(needle)`). Express the check as an assertion or a locator
|
|
577
578
|
(`assertHidden`, `assertCount`, `.filter({hasText})`, `textContains`).
|
|
578
579
|
|
|
579
580
|
Sandbox primitives:
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,167 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.44.0] - 2026-09-30
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- eb7be5d: Page clock, time zone and locale.
|
|
8
|
+
|
|
9
|
+
- **New DSL functions `clockFreeze(iso)`, `clockTick(ms)`, `clockRun()`.** `clockFreeze` stops time on the page: `Date` reads the given instant, and `setTimeout`, `setInterval` and `requestAnimationFrame` stand still. A "typing…" indicator or a countdown then gives the same screenshot on every run. `clockTick` moves frozen time forward and fires the timers that fall due. `clockRun` lets time run again. `clockFreeze` works at any point, including after `goto`, and takes past dates as well as future ones.
|
|
10
|
+
- **When the clock is installed.** A run puts the fake clock on the browser before the first page loads, and only for scenarios that can reach a clock call, directly or through a helper. Every other scenario keeps real time, as before. Playwright's spelling (`clock.freeze(…)`, `pauseAt`, `runFor`, `resume`) is refused with the DSL name to use instead.
|
|
11
|
+
- **MCP.** `new_context` and `explore_start` take `clock: true`. `explore_step` gains `clock_freeze {time}`, `clock_tick {ms}` and `clock_run`, recorded as the DSL calls. On a browser opened without the clock they are refused, and the refusal says how to open one with it.
|
|
12
|
+
- **Config `timezone` and `locale`** (`"Europe/Madrid"`, `"es-ES"`) for every page. `UNOTEST_TIMEZONE` and `UNOTEST_LOCALE` still override them for one run. An unknown zone or a malformed locale is refused when the config loads.
|
|
13
|
+
- Screenshots taken while time is frozen no longer wait for an animation frame that cannot come. Each used to cost up to two seconds.
|
|
14
|
+
|
|
15
|
+
- 816e2ea: A second browser context in one test: two clients of the same app.
|
|
16
|
+
|
|
17
|
+
- **New DSL functions `newContext()` and `setContext(index)`.** `newContext()` opens another browser context with the same options as the first: base URL, viewport, `device`, time zone, locale and page clock. It has its own cookies, storage and tabs, makes it the active one and returns its number (the test's own context is `0`). `setContext(i)` switches. Each context keeps its own tab and frame, so a switch back lands where the scenario left off. At the end of the test every context is closed.
|
|
18
|
+
- **Config `maxContexts`** (default 4) caps contexts per test. The next `newContext()` is refused and the refusal names the key. On a browser profile (`launch.userDataDir`) `newContext()` is refused: every page there shares one context.
|
|
19
|
+
- **Run journal and failure bundle.** Once a second context exists, each step records the context it started in (`contextIndex`), and the viewer's Trace shows it as a `ctx N` chip. A failure bundles the active context's page, console and network, with `contextIndex` in `failure.json`. A one-context test writes exactly what it did before. Both fields are additive: an older viewer reads the new journal and a newer one reads the old.
|
|
20
|
+
- **MCP.** `explore_step` gains `newContext` and `setContext {index}`, recorded as the same DSL calls. The `new_context` tool still resets the browser. Intent grounding follows the active context. An agent joining a paused debug run (`take_wheel`, `attach_debug_session`) lands in the context the run is on (Chromium).
|
|
21
|
+
- **New lint rule `lint:shadows-dsl-function`** (warning): a function or variable of your own that has a DSL function's name.
|
|
22
|
+
|
|
23
|
+
**If your scenarios define their own `newContext` or `setContext`:** a call by that name still runs your function, so nothing changes at run time. The new DSL function is out of reach until you rename yours. `lint:shadows-dsl-function` points at the definition.
|
|
24
|
+
|
|
25
|
+
- 565ef67: Strings, object literals and indexes in scenarios.
|
|
26
|
+
|
|
27
|
+
- A string value answers `s.length`, `s.trim()`, `s.includes(search)` and `s.startsWith(prefix)` — in asserts, in conditions, on literals and on call results (`assertTrue(getTitle().trim().length > 0)`). Any other member (`s.padStart(3)`) is a lint error, `validator:unsupported-string-member`, that names it and lists the four. A member of the list used on a value that is not a string at run time fails the step with the value's type in the message.
|
|
28
|
+
- An object literal is a value: `h = {x: 1, y: "z"}` can be kept in a variable, nested (`{a: {b: [1]}}`) and passed to a helper. A slot that wants a specific kind (a string, a locator) still rejects an object with its `arg-kind` error.
|
|
29
|
+
- An index works anywhere in a chain: `res.body.items[0].id`, `dbQuery(sql)[0].id`, `a.b[1][0]` — no temporary variable. Indexing a locator is still rejected.
|
|
30
|
+
- The lint hints for JS string habits now point at the string members (`indexOf` → `use s.includes(needle)`).
|
|
31
|
+
|
|
32
|
+
- 5ac6c72: `teardown("label", () => { … })` — give the stand back whatever happens.
|
|
33
|
+
|
|
34
|
+
- A scenario registers a rollback block where it turns something on; after the test body every registered block runs, newest first, whether the test passed, failed, ran out of time or was stopped — including a run stopped while paused at its failure (the debugger, `run_test`). One failing block does not stop the others. Flows and helpers can carry their own rollback.
|
|
35
|
+
- A green test whose teardown failed is failed; a failed test keeps its own error and lists the teardown failures next to it (CLI, `run_test` → `next.teardownFailures`, `runtime.json`). The failure bundle is taken before the rollback, so it shows the page the test broke on.
|
|
36
|
+
- The CLI prints `↺ teardown "…"` per block; the journal gets `teardown:started` / `teardown:finished` / `teardown:skipped` events and a `teardown` flag on the block frame; the viewer marks those frames and shows "returning the stand…" while a stopped run rolls back.
|
|
37
|
+
- Stop now rolls back first: the first Stop (viewer, `abort_runtime`, SIGTERM, Ctrl-C) plays the teardown blocks and then ends the run; a second Stop skips what is left and says so. `abort_runtime` answers `status: "returning the stand…"` while that happens.
|
|
38
|
+
- New config key `teardownTimeoutMs` (default 60000): the ceiling for all teardown blocks of a run, separate from the run's own time budget. A killed process, or blocks cut by this ceiling, leave the stand as the test set it — the run result names them.
|
|
39
|
+
- Lint: `teardown-shape` (string-literal label, no `return` / `break` out of the block, no nested teardown) and `teardown-name-conflict`.
|
|
40
|
+
|
|
41
|
+
**Breaking for scenarios that define their own `teardown`.** A function, parameter or variable named `teardown` is now a lint error (`teardown-name-conflict`), and a statement-level call `teardown(x)` no longer parses as a call. Rename it, e.g. to `undo_setup`.
|
|
42
|
+
|
|
43
|
+
- d1605b2: `assertScreenshot` — the page, or an element, against a PNG baseline in the repo.
|
|
44
|
+
|
|
45
|
+
- `assertScreenshot(name, options?)` / `assertScreenshot(locator, name, options?)` compares a snapshot with `unotest/e2e/<scenario>.screenshots/<name>.png` — a Figma export or a snapshot you accepted. A pixel differs when any channel differs by more than `threshold` (default 32); the assert fails when the share of differing pixels is more than `maxDiffRatio` (default 0) — exactly the limit passes. `mask: [locator…]` and `ignore: [{x, y, width, height}…]` leave areas out of the comparison on both sides. A different size fails at once with both sizes; nothing is resampled. `fullPage: true` compares the whole page.
|
|
46
|
+
- No baseline yet: the assert fails and the snapshot is saved in the run's artifacts as a candidate under the baseline's file name. `e2e --update-screenshots` (MCP: `run_test {updateScreenshots: true}`) writes the snapshots as baselines; it is refused on a box.
|
|
47
|
+
- A failed assert keeps `expected`, `actual` and `diff` pictures (per retry attempt); the viewer shows them side by side on the failed step with a "Download as baseline" button, the failure bundle points at them, and `run_test` returns them in `next.visual`. `box run --download` / `box_run_download` unpack baseline candidates next to the zip.
|
|
48
|
+
- A baseline is read as untrusted input: an image over 25 megapixels, or one whose data unpacks to more than its header allows, is refused before it is decoded.
|
|
49
|
+
- The journal gets a `visual-assert` event per assert; `runtime.json` carries the last failed one as `visual`.
|
|
50
|
+
- A suite over 128 MB is warned about before the upload — boxes accept 128 MB by default — naming its biggest files and baselines.
|
|
51
|
+
|
|
52
|
+
New dependency of `@unotest/web`: `pngjs` 7.0.0 (MIT, no dependencies of its own), the PNG codec behind the comparison.
|
|
53
|
+
|
|
54
|
+
- 3939ae6: Window size and scale: set them in the config, per MCP call, or mid-scenario.
|
|
55
|
+
|
|
56
|
+
- **`viewport.deviceScaleFactor`** in `unotest.config`. `{ width: 390, height: 844, deviceScaleFactor: 2 }` makes every screenshot 780×1688, the scale design mockups are usually drawn at. Every `viewport` field is now optional: a missing one comes from `device` or from the 1280×720 default. A config without `viewport` opens at 1280×720, as before.
|
|
57
|
+
- **`device`** in the config: a Playwright device preset by its exact name (`"iPhone 14"`, `"Pixel 7"`), applied whole — size, scale, touch, mobile mode, user agent. `viewport` next to it overrides the preset field by field. An unknown name is refused with the nearest ones. A mobile preset on Firefox is refused: Firefox has no mobile mode. A preset's viewport is the page area, smaller than the screen: `"iPhone 14"` is 390×664. To match a 390×844 mockup, set `viewport` explicitly.
|
|
58
|
+
- **MCP `new_context`** takes `viewport` and `device`, and opens the context at that window. **`explore_start`** takes them too when no browser is open yet. With a browser already open it refuses and points to `new_context`. Before this, `new_context` accepted any argument, ignored it and answered success. Now an unknown argument is refused and the reply names the accepted fields.
|
|
59
|
+
- **New DSL function `setViewport({width, height})`.** It resizes every open tab in place, with no reload, and tabs opened later start at the same size. `deviceScaleFactor` is refused here: the scale is fixed when the browser opens.
|
|
60
|
+
|
|
61
|
+
### Patch Changes
|
|
62
|
+
|
|
63
|
+
- 3780074: A finished run opened from history keeps its rollback pass: the viewer no longer drops `teardown:started` / `teardown:finished` / `teardown:skipped` from a stored journal, so the teardown frames of a stopped run show next to the step it was stopped in — not inside it — as they do while the run is live.
|
|
64
|
+
- 99ba57f: The run budget (`UNOTEST_MAX_DURATION_MS`, `maxDurationMs`) is now a deadline, not a check between statements.
|
|
65
|
+
|
|
66
|
+
- **Behaviour change:** a run whose last action took it past the budget used to finish green, and so did a run whose single action outlasted the whole budget. Both are now red with `maxDurationMs budget (…ms) exhausted`, reported on the action that spent it.
|
|
67
|
+
- An action still running when the budget runs out is abandoned at that moment instead of being waited for. Its browser call is not killed: it ends at its own timeout or when the context closes, and its late failure is not reported. Teardown blocks still run afterwards, on their own `teardownTimeoutMs`.
|
|
68
|
+
- Every budget now fails with a typed `BudgetExhaustedError` (`setting: "maxSteps" | "maxDurationMs" | "teardownTimeoutMs"`, `kind: "steps" | "duration"`). The message names the setting that was spent: a teardown cut by its own ceiling says `teardownTimeoutMs (…ms) exhausted — the rollback did not finish`, as before, not the run's budget.
|
|
69
|
+
|
|
70
|
+
- d66cf72: `readBounded(handle, size, max)` reads an open file into a buffer of its measured size plus one byte: a file over `max` is refused before anything is allocated, and one that grew after it was measured is refused rather than read in part (`BoundedReadError`). Reading a screenshot baseline no longer reserves memory for the largest file allowed — only for the file there is.
|
|
71
|
+
- 832c101: `resume` and `step` now answer with the stop that comes after the command, not the pause the run was already in.
|
|
72
|
+
|
|
73
|
+
A paused run keeps refreshing its runtime file, and the tools took a fresh timestamp on a paused state for the next pause. An agent could be told the run had moved on while it still sat at the old breakpoint, and take the wheel there. The runner now counts its pauses in `runtime.json` (`pauseSeq`, one more on every pause, the same line again included), and `resume` / `step` wait for that count to grow or for the run to end. A run from an older runner, without the count, is waited on as before.
|
|
74
|
+
|
|
75
|
+
- b613684: A screenshot the browser refuses to capture is asked for once more. Chromium now and then refuses a frame on a busy machine, and `assertScreenshot`, `screenshot()` and the MCP screenshot step used to fail on the first refusal with a raw protocol error. A second refusal is now `ScreenCaptureError`, which names the browser's reason and what to do. A closed page or a timeout is not retried.
|
|
76
|
+
- e417c08: A stopped run never ends silently: if the first Stop's rollback — or what runs before it, such as framing a paused failure — is still going at `teardownTimeoutMs` plus 15 seconds, the runner prints that it is exiting without finishing the rollback, then exits. A rollback block cut as hung is named in the output. The DSL reference now states both Stop limits: the rollback budget with its 15-second exit margin, and the viewer's 5-minute force-kill as a safety net only.
|
|
77
|
+
- Updated dependencies [816e2ea]
|
|
78
|
+
- Updated dependencies [565ef67]
|
|
79
|
+
- Updated dependencies [5ac6c72]
|
|
80
|
+
- Updated dependencies [d1605b2]
|
|
81
|
+
- Updated dependencies [3780074]
|
|
82
|
+
- Updated dependencies [99ba57f]
|
|
83
|
+
- Updated dependencies [d66cf72]
|
|
84
|
+
- Updated dependencies [832c101]
|
|
85
|
+
- @unotest/protocol@0.44.0
|
|
86
|
+
- @unotest/viewer@0.44.0
|
|
87
|
+
- @unotest/dsl@0.44.0
|
|
88
|
+
- @unotest/core@0.44.0
|
|
89
|
+
- @unotest/grounder-client@0.44.0
|
|
90
|
+
|
|
91
|
+
## [0.43.2] - 2026-09-30
|
|
92
|
+
|
|
93
|
+
### Patch Changes
|
|
94
|
+
|
|
95
|
+
- 1547ae7: `abort_runtime` now takes a run that is still waiting in the queue out of it. It used to answer ok for such a run while its place in line stayed, and the run started later all the same. It now removes the run from the queue and answers `{ok: true, state: "withdrawn"}`; nothing is run. A run that was taken out of the queue, here or in the viewer's Active panel, no longer ends its `unotest-web e2e` process with a stack trace: it prints that it was taken out and that nothing was run, and exits with 130, as Ctrl-C does.
|
|
96
|
+
|
|
97
|
+
What changes for you: `abort_runtime` on a runtime id with no run behind
|
|
98
|
+
it at all now answers `no-runner`, marked as an error, as `take_wheel` and
|
|
99
|
+
`release_wheel` already do, instead of `ok`. A finished run still answers
|
|
100
|
+
`ok`. A queued run taken out of the queue exits with 130 instead of 1.
|
|
101
|
+
|
|
102
|
+
- c04cf8b: When a person answered a request is now shown outside the cabinet too. The
|
|
103
|
+
cloud API adds `decidedAt` to an approved, declined or failed request. The
|
|
104
|
+
`cloud_approval_wait` MCP tool returns it, and the CLI prints it — "approved
|
|
105
|
+
in the cabinet at 2026-09-27 09:14 UTC" — in `approvals <id>` and while
|
|
106
|
+
`box create`, `box destroy` and `box picker on` wait; with `--json` it is an
|
|
107
|
+
`approval` field in the object the command already prints. A request that
|
|
108
|
+
expired shows the deadline it was last given, marked as such, since the
|
|
109
|
+
cloud's answer for an expired request carries no time. A service that does
|
|
110
|
+
not send the field yet is read as before, without a time.
|
|
111
|
+
- 2e70ce0: The times the CLI and the MCP tools show for an approval request (when a person answered it, when it expires) are now only taken from the service when they are a real date and time. Anything else is treated as if the service had sent no time: nothing is printed for it, and an expired request names no deadline it cannot vouch for.
|
|
112
|
+
- 665da8f: `box runs` and the other box read commands now say when the token is of the wrong kind (a project push token instead of a read token) and how to get the right one, instead of reporting that the box did not list its environments.
|
|
113
|
+
- 3639067: Text the cloud or a box sends can no longer send instructions to your terminal. The `login`, `logout`, `whoami`, `approvals`, `box`, `picker`, `bundle` and `env` commands used to print box names, request ids, error messages and other text from the service as they came, so control characters in them, from a service with a bug or a `UNOTEST_CLOUD_URL` pointed elsewhere, could clear your screen or retitle the window. They are now shown escaped (`\x1b`), and line breaks stay line breaks.
|
|
114
|
+
- 0b29243: `explore_run_flow` now says where a failed replay broke. The reply used to carry the error message alone, so a flow that failed inside a helper gave no file and no line, and the agent had to work out the cause from a screenshot. A failed replay now answers JSON: `error` (the same text as before), `flow`, `at` with the file, line and column of the statement that failed (the helper's file when it failed inside one, project-relative), its step label and the call chain from the flow down, and `artifacts` with a capture of the page as the flow left it.
|
|
115
|
+
|
|
116
|
+
What changes for you: a failed `explore_run_flow` answers JSON instead of
|
|
117
|
+
plain text, with the old text under `error`. Helpers now load the way
|
|
118
|
+
`run_test` loads them, so a function defined in two helper files is refused,
|
|
119
|
+
naming both files, in `explore_run_flow` as it already was in `run_test`.
|
|
120
|
+
|
|
121
|
+
- 7a0671e: An agent without a grounder is now told how to get one. When intent locators have no grounder (ollama not running, grounding off), `explore_start`'s `grounder` and a failed intent step carry `{state: "no-grounder", next, orElse?, why}`: `next` is the Picker command (`box picker on` for a project with a box, `login && picker on` without one), `orElse` is `ollama serve` in local mode, and `why` says the MCP server needs a reconnect after the Picker is turned on. A remote grounder that is configured but down gets the reason only, since turning a Picker on would not fix it. With `UNOTEST_SNAPSHOT=0`, `explore_start` now says the page snapshot tools are not served, how to bring them back, and how to locate elements in the meantime. The server instructions describe the new state.
|
|
122
|
+
|
|
123
|
+
What changes for you: an intent step that fails for want of a grounder now
|
|
124
|
+
answers `{status: "failed", error, grounder}` like other steps that fail
|
|
125
|
+
before they act, instead of an error reply carrying the text alone; the
|
|
126
|
+
`error` text is unchanged.
|
|
127
|
+
|
|
128
|
+
- ebcfbe0: A `run_test` that waits in the queue now says who holds the machine. The `queued` reply used to say only that the machine was busy, so an agent could not tell whether to wait or to stop a run of its own it no longer needed. It now carries `heldBy`: each run holding the machine with its run id, kind, scenario or collection name, how long it has been running (left out while that is not known yet) and, for a scenario run, the `abort_runtime` call that stops it. A blocking `run_test` (the default) used to wait for its turn for up to three minutes, longer than an MCP client waits for any answer, so the agent was told only that the request timed out. It now waits up to 30 seconds for a place and then answers `queued` with `heldBy`, the same as `wait: false`; the run stays in the queue and starts on its own. `abort_runtime` takes a queued run back out.
|
|
129
|
+
|
|
130
|
+
What changes for you: a blocking `run_test` behind a busy machine may now
|
|
131
|
+
answer `status: "queued"` after about 30 seconds instead of waiting for the
|
|
132
|
+
run to start; call `inspect_runtime` on its `runtimeId` to follow it.
|
|
133
|
+
|
|
134
|
+
- 6e4d598: A tool that throws something other than an `Error`, such as a plain string,
|
|
135
|
+
now has registered secrets masked in the message the agent reads, as an
|
|
136
|
+
`Error` already did.
|
|
137
|
+
- 56c899c: `resume` and `step` now answer with the stop that comes after the command, not the pause the run was already in.
|
|
138
|
+
|
|
139
|
+
A paused run keeps refreshing its runtime file, and the tools took a fresh timestamp on a paused state for the next pause. An agent could be told the run had moved on while it still sat at the old breakpoint, and take the wheel there. The runner now counts its pauses in `runtime.json` (`pauseSeq`, one more on every pause, the same line again included), and `resume` / `step` wait for that count to grow or for the run to end. A run from an older runner, without the count, is waited on as before.
|
|
140
|
+
|
|
141
|
+
This ships as a patch: it fixes how the existing `resume` and `step` wait, and adds no new tool, flag or input. `pauseSeq` is an optional field that only ever appears in addition: an older reader ignores it, and a newer reader facing a file without it keeps the previous timestamp check.
|
|
142
|
+
|
|
143
|
+
- 70c37be: Screenshots and saved page HTML leave out what a page keeps secret.
|
|
144
|
+
|
|
145
|
+
- Password and one-time-code fields are painted over on every screenshot: the agent's screenshots, per-step captures, and the screenshot taken when a run fails.
|
|
146
|
+
- The `page.html` saved when a run fails no longer contains the content of `<script>` elements or the values of hidden form fields; each is replaced by a label with its length. The same applies to the element HTML written in debug mode.
|
|
147
|
+
- Text in elements that are not rendered, and in `noembed` and `noframes`, no longer reaches element names.
|
|
148
|
+
|
|
149
|
+
What changes for you: if you read `page.html` for script data or hidden field values, set `failureBundle.pageHtml: "full"` in your config to keep them. Password fields stay hidden either way.
|
|
150
|
+
|
|
151
|
+
- 0e15c53: An element's text, wherever the page is read for it — the locator a ref becomes in a recorded scenario, `check_locator`'s matches and the failure's semantic snapshot — no longer includes the content of `<noscript>`, `<script>`, `<style>` or `<template>`: with JavaScript on, a page could repeat a secret inside `<noscript>` and it went into the customer's scenario (or into the refusal the agent read) in a locator that, since Playwright's own text matching skips `<noscript>`, did not even find the element.
|
|
152
|
+
|
|
153
|
+
The same goes for a failed `assertText` (and `assert_text`): it still compares against the element's full text, as before, but the "got" it quotes — in the MCP reply, the run's output, `runtime.json`, `steps.jsonl` and `failure.json` — leaves those blocks out, and when they alone made the comparison fail it says so. **What changes for you:** a failure message may now read `got "API key"` where it used to show the text of a `<noscript>` as well, followed by a note that the comparison includes hidden text; very long texts are quoted up to 10,000 characters, with a note when cut.
|
|
154
|
+
|
|
155
|
+
- Updated dependencies [665da8f]
|
|
156
|
+
- Updated dependencies [56c899c]
|
|
157
|
+
- Updated dependencies [70c37be]
|
|
158
|
+
- Updated dependencies [0e15c53]
|
|
159
|
+
- @unotest/core@0.43.2
|
|
160
|
+
- @unotest/protocol@0.43.2
|
|
161
|
+
- @unotest/grounder-client@0.43.2
|
|
162
|
+
- @unotest/dsl@0.43.2
|
|
163
|
+
- @unotest/viewer@0.43.2
|
|
164
|
+
|
|
3
165
|
## [0.43.1] - 2026-09-29
|
|
4
166
|
|
|
5
167
|
### Patch Changes
|
|
@@ -1292,7 +1454,7 @@ picker on` fetches this project's own access and writes the three grounder
|
|
|
1292
1454
|
|
|
1293
1455
|
That screen named one command:
|
|
1294
1456
|
|
|
1295
|
-
|
|
1457
|
+
npx @unotest/web bundle push --run --env ENVIRONMENT
|
|
1296
1458
|
|
|
1297
1459
|
In a project that has just been created, the command refuses with `no box
|
|
1298
1460
|
address` before it does anything: the project holds neither the box's address
|
|
@@ -1301,8 +1463,8 @@ address` before it does anything: the project holds neither the box's address
|
|
|
1301
1463
|
|
|
1302
1464
|
The screen now names both steps, in the order they have to happen:
|
|
1303
1465
|
|
|
1304
|
-
|
|
1305
|
-
|
|
1466
|
+
npx @unotest/web box access larch
|
|
1467
|
+
npx @unotest/web bundle push --run --env ENVIRONMENT
|
|
1306
1468
|
|
|
1307
1469
|
The box's own name is filled in when its licence carries one. `box access`
|
|
1308
1470
|
with no name refuses, and its refusal offers to create a box — to somebody who
|
package/dist/config/schema.d.ts
CHANGED
|
@@ -20,14 +20,19 @@ declare const BROWSER_CHANNEL_WORDS: readonly ["chrome", "msedge", "chrome-beta"
|
|
|
20
20
|
declare const BrowserChannelSchema: z.ZodOptional<z.ZodUnion<[z.ZodEnum<["chrome", "msedge", "chrome-beta", "chromium"]>, z.ZodEffects<z.ZodLiteral<"bundled">, null, "bundled">, z.ZodNull]>>;
|
|
21
21
|
type BrowserChannel = z.infer<typeof BrowserChannelSchema>;
|
|
22
22
|
declare const ViewportSchema: z.ZodObject<{
|
|
23
|
-
width: z.ZodNumber
|
|
24
|
-
height: z.ZodNumber
|
|
23
|
+
width: z.ZodOptional<z.ZodNumber>;
|
|
24
|
+
height: z.ZodOptional<z.ZodNumber>;
|
|
25
|
+
/** CSS pixel → device pixel ratio: 2 makes a 390×844 page a 780×1688
|
|
26
|
+
* screenshot, the scale design mockups are usually drawn at. */
|
|
27
|
+
deviceScaleFactor: z.ZodOptional<z.ZodNumber>;
|
|
25
28
|
}, "strict", z.ZodTypeAny, {
|
|
26
|
-
width
|
|
27
|
-
height
|
|
29
|
+
width?: number | undefined;
|
|
30
|
+
height?: number | undefined;
|
|
31
|
+
deviceScaleFactor?: number | undefined;
|
|
28
32
|
}, {
|
|
29
|
-
width
|
|
30
|
-
height
|
|
33
|
+
width?: number | undefined;
|
|
34
|
+
height?: number | undefined;
|
|
35
|
+
deviceScaleFactor?: number | undefined;
|
|
31
36
|
}>;
|
|
32
37
|
type Viewport = z.infer<typeof ViewportSchema>;
|
|
33
38
|
declare const RetryReasonSchema: z.ZodEnum<["transient", "network", "crash"]>;
|
|
@@ -69,6 +74,11 @@ declare const FailureBundleConfigSchema: z.ZodObject<{
|
|
|
69
74
|
days: number;
|
|
70
75
|
}>;
|
|
71
76
|
storageDir: z.ZodString;
|
|
77
|
+
/** What page.html keeps. `redacted`: a script's content and a hidden
|
|
78
|
+
* field's value become a label with their length. `full`: both as the
|
|
79
|
+
* page had them — an app's bootstrap data and request-forgery tokens
|
|
80
|
+
* included. A secret field's value is hidden either way. */
|
|
81
|
+
pageHtml: z.ZodEnum<["redacted", "full"]>;
|
|
72
82
|
}, "strict", z.ZodTypeAny, {
|
|
73
83
|
tier1: true;
|
|
74
84
|
tier2: boolean;
|
|
@@ -81,6 +91,7 @@ declare const FailureBundleConfigSchema: z.ZodObject<{
|
|
|
81
91
|
days: number;
|
|
82
92
|
};
|
|
83
93
|
storageDir: string;
|
|
94
|
+
pageHtml: "redacted" | "full";
|
|
84
95
|
}, {
|
|
85
96
|
tier2: boolean;
|
|
86
97
|
tier3: {
|
|
@@ -92,24 +103,25 @@ declare const FailureBundleConfigSchema: z.ZodObject<{
|
|
|
92
103
|
days: number;
|
|
93
104
|
};
|
|
94
105
|
storageDir: string;
|
|
106
|
+
pageHtml: "redacted" | "full";
|
|
95
107
|
tier1?: true | undefined;
|
|
96
108
|
}>;
|
|
97
109
|
type FailureBundleConfig = z.infer<typeof FailureBundleConfigSchema>;
|
|
98
110
|
declare const DialogPolicySchema: z.ZodEnum<["accept", "dismiss", "manual"]>;
|
|
99
111
|
type DialogPolicy = z.infer<typeof DialogPolicySchema>;
|
|
100
|
-
declare const LinterRuleIdSchema: z.ZodEnum<["lint:deep-css", "lint:xpath", "lint:obfuscated-class", "lint:pause-explicit", "lint:disambig-by-index", "lint:evaluate-discouraged", "lint:scenario-in-root", "lint:mustache-in-dsl", "lint:goto-concat", "lint:regex-invalid", "lint:echo-assert", "lint:api-call-file-body", "lint:lint-ok-missing-reason", "lint:flow-returns-value", "lint:legacy-layout", "lint:external-variable-undeclared", "lint:env-host-from-base", "lint:one-test-per-file", "validator:unknown-function", "validator:arity", "validator:arg-kind", "validator:step-coverage", "validator:step-shape", "validator:meta-shape", "validator:function-shape", "validator:for-shape", "validator:if-shape", "validator:var-shape", "validator:unsupported-statement", "validator:unsupported-expression", "validator:string-concat", "validator:semantic-loss", "validator:list-arg", "validator:wrapped-format"]>;
|
|
112
|
+
declare const LinterRuleIdSchema: z.ZodEnum<["lint:deep-css", "lint:xpath", "lint:obfuscated-class", "lint:pause-explicit", "lint:disambig-by-index", "lint:evaluate-discouraged", "lint:scenario-in-root", "lint:mustache-in-dsl", "lint:goto-concat", "lint:regex-invalid", "lint:echo-assert", "lint:api-call-file-body", "lint:lint-ok-missing-reason", "lint:flow-returns-value", "lint:legacy-layout", "lint:external-variable-undeclared", "lint:env-host-from-base", "lint:one-test-per-file", "lint:shadows-dsl-function", "validator:unknown-function", "validator:arity", "validator:arg-kind", "validator:step-coverage", "validator:step-shape", "validator:meta-shape", "validator:function-shape", "validator:for-shape", "validator:if-shape", "validator:var-shape", "validator:unsupported-statement", "validator:unsupported-expression", "validator:string-concat", "validator:semantic-loss", "validator:list-arg", "validator:wrapped-format"]>;
|
|
101
113
|
type LinterRuleId = z.infer<typeof LinterRuleIdSchema>;
|
|
102
114
|
declare const LinterSeveritySchema: z.ZodEnum<["off", "warn", "error"]>;
|
|
103
115
|
type LinterSeverity = z.infer<typeof LinterSeveritySchema>;
|
|
104
116
|
declare const LinterConfigSchema: z.ZodObject<{
|
|
105
117
|
enabled: z.ZodBoolean;
|
|
106
|
-
rules: z.ZodRecord<z.ZodEnum<["lint:deep-css", "lint:xpath", "lint:obfuscated-class", "lint:pause-explicit", "lint:disambig-by-index", "lint:evaluate-discouraged", "lint:scenario-in-root", "lint:mustache-in-dsl", "lint:goto-concat", "lint:regex-invalid", "lint:echo-assert", "lint:api-call-file-body", "lint:lint-ok-missing-reason", "lint:flow-returns-value", "lint:legacy-layout", "lint:external-variable-undeclared", "lint:env-host-from-base", "lint:one-test-per-file", "validator:unknown-function", "validator:arity", "validator:arg-kind", "validator:step-coverage", "validator:step-shape", "validator:meta-shape", "validator:function-shape", "validator:for-shape", "validator:if-shape", "validator:var-shape", "validator:unsupported-statement", "validator:unsupported-expression", "validator:string-concat", "validator:semantic-loss", "validator:list-arg", "validator:wrapped-format"]>, z.ZodEnum<["off", "warn", "error"]>>;
|
|
118
|
+
rules: z.ZodRecord<z.ZodEnum<["lint:deep-css", "lint:xpath", "lint:obfuscated-class", "lint:pause-explicit", "lint:disambig-by-index", "lint:evaluate-discouraged", "lint:scenario-in-root", "lint:mustache-in-dsl", "lint:goto-concat", "lint:regex-invalid", "lint:echo-assert", "lint:api-call-file-body", "lint:lint-ok-missing-reason", "lint:flow-returns-value", "lint:legacy-layout", "lint:external-variable-undeclared", "lint:env-host-from-base", "lint:one-test-per-file", "lint:shadows-dsl-function", "validator:unknown-function", "validator:arity", "validator:arg-kind", "validator:step-coverage", "validator:step-shape", "validator:meta-shape", "validator:function-shape", "validator:for-shape", "validator:if-shape", "validator:var-shape", "validator:unsupported-statement", "validator:unsupported-expression", "validator:string-concat", "validator:semantic-loss", "validator:list-arg", "validator:wrapped-format"]>, z.ZodEnum<["off", "warn", "error"]>>;
|
|
107
119
|
}, "strict", z.ZodTypeAny, {
|
|
108
120
|
enabled: boolean;
|
|
109
|
-
rules: Partial<Record<"lint:deep-css" | "lint:xpath" | "lint:obfuscated-class" | "lint:pause-explicit" | "lint:disambig-by-index" | "lint:evaluate-discouraged" | "lint:scenario-in-root" | "lint:mustache-in-dsl" | "lint:goto-concat" | "lint:regex-invalid" | "lint:echo-assert" | "lint:api-call-file-body" | "lint:lint-ok-missing-reason" | "lint:flow-returns-value" | "lint:legacy-layout" | "lint:external-variable-undeclared" | "lint:env-host-from-base" | "lint:one-test-per-file" | "validator:unknown-function" | "validator:arity" | "validator:arg-kind" | "validator:step-coverage" | "validator:step-shape" | "validator:meta-shape" | "validator:function-shape" | "validator:for-shape" | "validator:if-shape" | "validator:var-shape" | "validator:unsupported-statement" | "validator:unsupported-expression" | "validator:string-concat" | "validator:semantic-loss" | "validator:list-arg" | "validator:wrapped-format", "off" | "warn" | "error">>;
|
|
121
|
+
rules: Partial<Record<"lint:deep-css" | "lint:xpath" | "lint:obfuscated-class" | "lint:pause-explicit" | "lint:disambig-by-index" | "lint:evaluate-discouraged" | "lint:scenario-in-root" | "lint:mustache-in-dsl" | "lint:goto-concat" | "lint:regex-invalid" | "lint:echo-assert" | "lint:api-call-file-body" | "lint:lint-ok-missing-reason" | "lint:flow-returns-value" | "lint:legacy-layout" | "lint:external-variable-undeclared" | "lint:env-host-from-base" | "lint:one-test-per-file" | "lint:shadows-dsl-function" | "validator:unknown-function" | "validator:arity" | "validator:arg-kind" | "validator:step-coverage" | "validator:step-shape" | "validator:meta-shape" | "validator:function-shape" | "validator:for-shape" | "validator:if-shape" | "validator:var-shape" | "validator:unsupported-statement" | "validator:unsupported-expression" | "validator:string-concat" | "validator:semantic-loss" | "validator:list-arg" | "validator:wrapped-format", "off" | "warn" | "error">>;
|
|
110
122
|
}, {
|
|
111
123
|
enabled: boolean;
|
|
112
|
-
rules: Partial<Record<"lint:deep-css" | "lint:xpath" | "lint:obfuscated-class" | "lint:pause-explicit" | "lint:disambig-by-index" | "lint:evaluate-discouraged" | "lint:scenario-in-root" | "lint:mustache-in-dsl" | "lint:goto-concat" | "lint:regex-invalid" | "lint:echo-assert" | "lint:api-call-file-body" | "lint:lint-ok-missing-reason" | "lint:flow-returns-value" | "lint:legacy-layout" | "lint:external-variable-undeclared" | "lint:env-host-from-base" | "lint:one-test-per-file" | "validator:unknown-function" | "validator:arity" | "validator:arg-kind" | "validator:step-coverage" | "validator:step-shape" | "validator:meta-shape" | "validator:function-shape" | "validator:for-shape" | "validator:if-shape" | "validator:var-shape" | "validator:unsupported-statement" | "validator:unsupported-expression" | "validator:string-concat" | "validator:semantic-loss" | "validator:list-arg" | "validator:wrapped-format", "off" | "warn" | "error">>;
|
|
124
|
+
rules: Partial<Record<"lint:deep-css" | "lint:xpath" | "lint:obfuscated-class" | "lint:pause-explicit" | "lint:disambig-by-index" | "lint:evaluate-discouraged" | "lint:scenario-in-root" | "lint:mustache-in-dsl" | "lint:goto-concat" | "lint:regex-invalid" | "lint:echo-assert" | "lint:api-call-file-body" | "lint:lint-ok-missing-reason" | "lint:flow-returns-value" | "lint:legacy-layout" | "lint:external-variable-undeclared" | "lint:env-host-from-base" | "lint:one-test-per-file" | "lint:shadows-dsl-function" | "validator:unknown-function" | "validator:arity" | "validator:arg-kind" | "validator:step-coverage" | "validator:step-shape" | "validator:meta-shape" | "validator:function-shape" | "validator:for-shape" | "validator:if-shape" | "validator:var-shape" | "validator:unsupported-statement" | "validator:unsupported-expression" | "validator:string-concat" | "validator:semantic-loss" | "validator:list-arg" | "validator:wrapped-format", "off" | "warn" | "error">>;
|
|
113
125
|
}>;
|
|
114
126
|
type LinterConfig = z.infer<typeof LinterConfigSchema>;
|
|
115
127
|
declare const QueueConfigSchema: z.ZodObject<{
|
|
@@ -279,16 +291,39 @@ declare const UnotestConfigSchema: z.ZodObject<{
|
|
|
279
291
|
}>>;
|
|
280
292
|
browsers: z.ZodArray<z.ZodEnum<["chromium", "firefox", "webkit"]>, "many">;
|
|
281
293
|
channel: z.ZodOptional<z.ZodUnion<[z.ZodEnum<["chrome", "msedge", "chrome-beta", "chromium"]>, z.ZodEffects<z.ZodLiteral<"bundled">, null, "bundled">, z.ZodNull]>>;
|
|
282
|
-
viewport: z.ZodObject<{
|
|
283
|
-
width: z.ZodNumber
|
|
284
|
-
height: z.ZodNumber
|
|
294
|
+
viewport: z.ZodOptional<z.ZodObject<{
|
|
295
|
+
width: z.ZodOptional<z.ZodNumber>;
|
|
296
|
+
height: z.ZodOptional<z.ZodNumber>;
|
|
297
|
+
/** CSS pixel → device pixel ratio: 2 makes a 390×844 page a 780×1688
|
|
298
|
+
* screenshot, the scale design mockups are usually drawn at. */
|
|
299
|
+
deviceScaleFactor: z.ZodOptional<z.ZodNumber>;
|
|
285
300
|
}, "strict", z.ZodTypeAny, {
|
|
286
|
-
width
|
|
287
|
-
height
|
|
301
|
+
width?: number | undefined;
|
|
302
|
+
height?: number | undefined;
|
|
303
|
+
deviceScaleFactor?: number | undefined;
|
|
288
304
|
}, {
|
|
289
|
-
width
|
|
290
|
-
height
|
|
291
|
-
|
|
305
|
+
width?: number | undefined;
|
|
306
|
+
height?: number | undefined;
|
|
307
|
+
deviceScaleFactor?: number | undefined;
|
|
308
|
+
}>>;
|
|
309
|
+
/** A device preset by its Playwright name (`"iPhone 14"`, `"Pixel 7"`):
|
|
310
|
+
* size, scale, touch, mobile mode and user agent at once. `viewport`
|
|
311
|
+
* next to it overrides the preset's size. The name is checked by the
|
|
312
|
+
* driver when a context opens — the list is Playwright's, and this
|
|
313
|
+
* schema is imported where Playwright is not installed. */
|
|
314
|
+
device: z.ZodOptional<z.ZodString>;
|
|
315
|
+
/** IANA time zone of every page (`"Europe/Madrid"`): `Date#toString`,
|
|
316
|
+
* `Intl` and `getTimezoneOffset` answer in it. Unset → the machine's.
|
|
317
|
+
* `UNOTEST_TIMEZONE` overrides it. */
|
|
318
|
+
timezone: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
319
|
+
/** BCP 47 locale of every page (`"es-ES"`): `navigator.language`, `Intl`
|
|
320
|
+
* defaults, Accept-Language. Unset → the browser's. `UNOTEST_LOCALE`
|
|
321
|
+
* overrides it. */
|
|
322
|
+
locale: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
323
|
+
/** Browser contexts one test may hold at once — the run's own plus
|
|
324
|
+
* every `newContext()`. The next one past it is refused: a guard
|
|
325
|
+
* against a loop that opens a browser per iteration. Default 4. */
|
|
326
|
+
maxContexts: z.ZodOptional<z.ZodNumber>;
|
|
292
327
|
/** How the browser is started: a profile and extra arguments
|
|
293
328
|
* (`config/launch.ts`). Unset → as before: a fresh browser, a fresh
|
|
294
329
|
* context per scenario. */
|
|
@@ -338,6 +373,11 @@ declare const UnotestConfigSchema: z.ZodObject<{
|
|
|
338
373
|
days: number;
|
|
339
374
|
}>;
|
|
340
375
|
storageDir: z.ZodString;
|
|
376
|
+
/** What page.html keeps. `redacted`: a script's content and a hidden
|
|
377
|
+
* field's value become a label with their length. `full`: both as the
|
|
378
|
+
* page had them — an app's bootstrap data and request-forgery tokens
|
|
379
|
+
* included. A secret field's value is hidden either way. */
|
|
380
|
+
pageHtml: z.ZodEnum<["redacted", "full"]>;
|
|
341
381
|
}, "strict", z.ZodTypeAny, {
|
|
342
382
|
tier1: true;
|
|
343
383
|
tier2: boolean;
|
|
@@ -350,6 +390,7 @@ declare const UnotestConfigSchema: z.ZodObject<{
|
|
|
350
390
|
days: number;
|
|
351
391
|
};
|
|
352
392
|
storageDir: string;
|
|
393
|
+
pageHtml: "redacted" | "full";
|
|
353
394
|
}, {
|
|
354
395
|
tier2: boolean;
|
|
355
396
|
tier3: {
|
|
@@ -361,6 +402,7 @@ declare const UnotestConfigSchema: z.ZodObject<{
|
|
|
361
402
|
days: number;
|
|
362
403
|
};
|
|
363
404
|
storageDir: string;
|
|
405
|
+
pageHtml: "redacted" | "full";
|
|
364
406
|
tier1?: true | undefined;
|
|
365
407
|
}>;
|
|
366
408
|
dialogPolicy: z.ZodEnum<["accept", "dismiss", "manual"]>;
|
|
@@ -384,6 +426,12 @@ declare const UnotestConfigSchema: z.ZodObject<{
|
|
|
384
426
|
* slow-but-correct navigation.
|
|
385
427
|
* Override via `UNOTEST_DEFAULT_NAVIGATION_TIMEOUT_MS`. */
|
|
386
428
|
defaultNavigationTimeoutMs: z.ZodNumber;
|
|
429
|
+
/** Wall-clock ceiling for a run's rollback pass — every `teardown(...)`
|
|
430
|
+
* block together. Separate from the run's own time budget on purpose: a
|
|
431
|
+
* test that failed BY that budget still has to give the stand back.
|
|
432
|
+
* Unset → 60000. A pass cut by it leaves the rest of the stand as the
|
|
433
|
+
* test set it, and the run result says which blocks did not finish. */
|
|
434
|
+
teardownTimeoutMs: z.ZodOptional<z.ZodNumber>;
|
|
387
435
|
/** Cross-environment declarations (`.env.<name>` overlays). */
|
|
388
436
|
env: z.ZodObject<{
|
|
389
437
|
/** Variable names whose base `.env` value is deliberately shared by
|
|
@@ -397,13 +445,13 @@ declare const UnotestConfigSchema: z.ZodObject<{
|
|
|
397
445
|
}>;
|
|
398
446
|
linter: z.ZodObject<{
|
|
399
447
|
enabled: z.ZodBoolean;
|
|
400
|
-
rules: z.ZodRecord<z.ZodEnum<["lint:deep-css", "lint:xpath", "lint:obfuscated-class", "lint:pause-explicit", "lint:disambig-by-index", "lint:evaluate-discouraged", "lint:scenario-in-root", "lint:mustache-in-dsl", "lint:goto-concat", "lint:regex-invalid", "lint:echo-assert", "lint:api-call-file-body", "lint:lint-ok-missing-reason", "lint:flow-returns-value", "lint:legacy-layout", "lint:external-variable-undeclared", "lint:env-host-from-base", "lint:one-test-per-file", "validator:unknown-function", "validator:arity", "validator:arg-kind", "validator:step-coverage", "validator:step-shape", "validator:meta-shape", "validator:function-shape", "validator:for-shape", "validator:if-shape", "validator:var-shape", "validator:unsupported-statement", "validator:unsupported-expression", "validator:string-concat", "validator:semantic-loss", "validator:list-arg", "validator:wrapped-format"]>, z.ZodEnum<["off", "warn", "error"]>>;
|
|
448
|
+
rules: z.ZodRecord<z.ZodEnum<["lint:deep-css", "lint:xpath", "lint:obfuscated-class", "lint:pause-explicit", "lint:disambig-by-index", "lint:evaluate-discouraged", "lint:scenario-in-root", "lint:mustache-in-dsl", "lint:goto-concat", "lint:regex-invalid", "lint:echo-assert", "lint:api-call-file-body", "lint:lint-ok-missing-reason", "lint:flow-returns-value", "lint:legacy-layout", "lint:external-variable-undeclared", "lint:env-host-from-base", "lint:one-test-per-file", "lint:shadows-dsl-function", "validator:unknown-function", "validator:arity", "validator:arg-kind", "validator:step-coverage", "validator:step-shape", "validator:meta-shape", "validator:function-shape", "validator:for-shape", "validator:if-shape", "validator:var-shape", "validator:unsupported-statement", "validator:unsupported-expression", "validator:string-concat", "validator:semantic-loss", "validator:list-arg", "validator:wrapped-format"]>, z.ZodEnum<["off", "warn", "error"]>>;
|
|
401
449
|
}, "strict", z.ZodTypeAny, {
|
|
402
450
|
enabled: boolean;
|
|
403
|
-
rules: Partial<Record<"lint:deep-css" | "lint:xpath" | "lint:obfuscated-class" | "lint:pause-explicit" | "lint:disambig-by-index" | "lint:evaluate-discouraged" | "lint:scenario-in-root" | "lint:mustache-in-dsl" | "lint:goto-concat" | "lint:regex-invalid" | "lint:echo-assert" | "lint:api-call-file-body" | "lint:lint-ok-missing-reason" | "lint:flow-returns-value" | "lint:legacy-layout" | "lint:external-variable-undeclared" | "lint:env-host-from-base" | "lint:one-test-per-file" | "validator:unknown-function" | "validator:arity" | "validator:arg-kind" | "validator:step-coverage" | "validator:step-shape" | "validator:meta-shape" | "validator:function-shape" | "validator:for-shape" | "validator:if-shape" | "validator:var-shape" | "validator:unsupported-statement" | "validator:unsupported-expression" | "validator:string-concat" | "validator:semantic-loss" | "validator:list-arg" | "validator:wrapped-format", "off" | "warn" | "error">>;
|
|
451
|
+
rules: Partial<Record<"lint:deep-css" | "lint:xpath" | "lint:obfuscated-class" | "lint:pause-explicit" | "lint:disambig-by-index" | "lint:evaluate-discouraged" | "lint:scenario-in-root" | "lint:mustache-in-dsl" | "lint:goto-concat" | "lint:regex-invalid" | "lint:echo-assert" | "lint:api-call-file-body" | "lint:lint-ok-missing-reason" | "lint:flow-returns-value" | "lint:legacy-layout" | "lint:external-variable-undeclared" | "lint:env-host-from-base" | "lint:one-test-per-file" | "lint:shadows-dsl-function" | "validator:unknown-function" | "validator:arity" | "validator:arg-kind" | "validator:step-coverage" | "validator:step-shape" | "validator:meta-shape" | "validator:function-shape" | "validator:for-shape" | "validator:if-shape" | "validator:var-shape" | "validator:unsupported-statement" | "validator:unsupported-expression" | "validator:string-concat" | "validator:semantic-loss" | "validator:list-arg" | "validator:wrapped-format", "off" | "warn" | "error">>;
|
|
404
452
|
}, {
|
|
405
453
|
enabled: boolean;
|
|
406
|
-
rules: Partial<Record<"lint:deep-css" | "lint:xpath" | "lint:obfuscated-class" | "lint:pause-explicit" | "lint:disambig-by-index" | "lint:evaluate-discouraged" | "lint:scenario-in-root" | "lint:mustache-in-dsl" | "lint:goto-concat" | "lint:regex-invalid" | "lint:echo-assert" | "lint:api-call-file-body" | "lint:lint-ok-missing-reason" | "lint:flow-returns-value" | "lint:legacy-layout" | "lint:external-variable-undeclared" | "lint:env-host-from-base" | "lint:one-test-per-file" | "validator:unknown-function" | "validator:arity" | "validator:arg-kind" | "validator:step-coverage" | "validator:step-shape" | "validator:meta-shape" | "validator:function-shape" | "validator:for-shape" | "validator:if-shape" | "validator:var-shape" | "validator:unsupported-statement" | "validator:unsupported-expression" | "validator:string-concat" | "validator:semantic-loss" | "validator:list-arg" | "validator:wrapped-format", "off" | "warn" | "error">>;
|
|
454
|
+
rules: Partial<Record<"lint:deep-css" | "lint:xpath" | "lint:obfuscated-class" | "lint:pause-explicit" | "lint:disambig-by-index" | "lint:evaluate-discouraged" | "lint:scenario-in-root" | "lint:mustache-in-dsl" | "lint:goto-concat" | "lint:regex-invalid" | "lint:echo-assert" | "lint:api-call-file-body" | "lint:lint-ok-missing-reason" | "lint:flow-returns-value" | "lint:legacy-layout" | "lint:external-variable-undeclared" | "lint:env-host-from-base" | "lint:one-test-per-file" | "lint:shadows-dsl-function" | "validator:unknown-function" | "validator:arity" | "validator:arg-kind" | "validator:step-coverage" | "validator:step-shape" | "validator:meta-shape" | "validator:function-shape" | "validator:for-shape" | "validator:if-shape" | "validator:var-shape" | "validator:unsupported-statement" | "validator:unsupported-expression" | "validator:string-concat" | "validator:semantic-loss" | "validator:list-arg" | "validator:wrapped-format", "off" | "warn" | "error">>;
|
|
407
455
|
}>;
|
|
408
456
|
mcp: z.ZodObject<{
|
|
409
457
|
transport: z.ZodLiteral<"stdio">;
|
|
@@ -499,10 +547,6 @@ declare const UnotestConfigSchema: z.ZodObject<{
|
|
|
499
547
|
defaultTimeoutMs: number;
|
|
500
548
|
defaultNavigationTimeoutMs: number;
|
|
501
549
|
browsers: ("chromium" | "firefox" | "webkit")[];
|
|
502
|
-
viewport: {
|
|
503
|
-
width: number;
|
|
504
|
-
height: number;
|
|
505
|
-
};
|
|
506
550
|
retry: {
|
|
507
551
|
count: number;
|
|
508
552
|
on: ("network" | "transient" | "crash")[];
|
|
@@ -519,6 +563,7 @@ declare const UnotestConfigSchema: z.ZodObject<{
|
|
|
519
563
|
days: number;
|
|
520
564
|
};
|
|
521
565
|
storageDir: string;
|
|
566
|
+
pageHtml: "redacted" | "full";
|
|
522
567
|
};
|
|
523
568
|
dialogPolicy: "accept" | "dismiss" | "manual";
|
|
524
569
|
testDir: string;
|
|
@@ -528,7 +573,7 @@ declare const UnotestConfigSchema: z.ZodObject<{
|
|
|
528
573
|
};
|
|
529
574
|
linter: {
|
|
530
575
|
enabled: boolean;
|
|
531
|
-
rules: Partial<Record<"lint:deep-css" | "lint:xpath" | "lint:obfuscated-class" | "lint:pause-explicit" | "lint:disambig-by-index" | "lint:evaluate-discouraged" | "lint:scenario-in-root" | "lint:mustache-in-dsl" | "lint:goto-concat" | "lint:regex-invalid" | "lint:echo-assert" | "lint:api-call-file-body" | "lint:lint-ok-missing-reason" | "lint:flow-returns-value" | "lint:legacy-layout" | "lint:external-variable-undeclared" | "lint:env-host-from-base" | "lint:one-test-per-file" | "validator:unknown-function" | "validator:arity" | "validator:arg-kind" | "validator:step-coverage" | "validator:step-shape" | "validator:meta-shape" | "validator:function-shape" | "validator:for-shape" | "validator:if-shape" | "validator:var-shape" | "validator:unsupported-statement" | "validator:unsupported-expression" | "validator:string-concat" | "validator:semantic-loss" | "validator:list-arg" | "validator:wrapped-format", "off" | "warn" | "error">>;
|
|
576
|
+
rules: Partial<Record<"lint:deep-css" | "lint:xpath" | "lint:obfuscated-class" | "lint:pause-explicit" | "lint:disambig-by-index" | "lint:evaluate-discouraged" | "lint:scenario-in-root" | "lint:mustache-in-dsl" | "lint:goto-concat" | "lint:regex-invalid" | "lint:echo-assert" | "lint:api-call-file-body" | "lint:lint-ok-missing-reason" | "lint:flow-returns-value" | "lint:legacy-layout" | "lint:external-variable-undeclared" | "lint:env-host-from-base" | "lint:one-test-per-file" | "lint:shadows-dsl-function" | "validator:unknown-function" | "validator:arity" | "validator:arg-kind" | "validator:step-coverage" | "validator:step-shape" | "validator:meta-shape" | "validator:function-shape" | "validator:for-shape" | "validator:if-shape" | "validator:var-shape" | "validator:unsupported-statement" | "validator:unsupported-expression" | "validator:string-concat" | "validator:semantic-loss" | "validator:list-arg" | "validator:wrapped-format", "off" | "warn" | "error">>;
|
|
532
577
|
};
|
|
533
578
|
mcp: {
|
|
534
579
|
transport: "stdio";
|
|
@@ -551,6 +596,8 @@ declare const UnotestConfigSchema: z.ZodObject<{
|
|
|
551
596
|
apiBaseUrl?: string | undefined;
|
|
552
597
|
uploadDir?: string | undefined;
|
|
553
598
|
};
|
|
599
|
+
locale?: string | undefined;
|
|
600
|
+
timezone?: string | undefined;
|
|
554
601
|
baseUrl?: string | undefined;
|
|
555
602
|
webServer?: {
|
|
556
603
|
url: string;
|
|
@@ -559,19 +606,23 @@ declare const UnotestConfigSchema: z.ZodObject<{
|
|
|
559
606
|
reuseExistingServer: boolean;
|
|
560
607
|
} | undefined;
|
|
561
608
|
channel?: "chrome" | "msedge" | "chrome-beta" | "chromium" | null | undefined;
|
|
609
|
+
viewport?: {
|
|
610
|
+
width?: number | undefined;
|
|
611
|
+
height?: number | undefined;
|
|
612
|
+
deviceScaleFactor?: number | undefined;
|
|
613
|
+
} | undefined;
|
|
614
|
+
device?: string | undefined;
|
|
615
|
+
maxContexts?: number | undefined;
|
|
562
616
|
launch?: {
|
|
563
617
|
args?: string[] | undefined;
|
|
564
618
|
userDataDir?: string | undefined;
|
|
565
619
|
} | undefined;
|
|
566
620
|
storageState?: string | undefined;
|
|
621
|
+
teardownTimeoutMs?: number | undefined;
|
|
567
622
|
}, {
|
|
568
623
|
defaultTimeoutMs: number;
|
|
569
624
|
defaultNavigationTimeoutMs: number;
|
|
570
625
|
browsers: ("chromium" | "firefox" | "webkit")[];
|
|
571
|
-
viewport: {
|
|
572
|
-
width: number;
|
|
573
|
-
height: number;
|
|
574
|
-
};
|
|
575
626
|
retry: {
|
|
576
627
|
count: number;
|
|
577
628
|
on: ("network" | "transient" | "crash")[];
|
|
@@ -587,6 +638,7 @@ declare const UnotestConfigSchema: z.ZodObject<{
|
|
|
587
638
|
days: number;
|
|
588
639
|
};
|
|
589
640
|
storageDir: string;
|
|
641
|
+
pageHtml: "redacted" | "full";
|
|
590
642
|
tier1?: true | undefined;
|
|
591
643
|
};
|
|
592
644
|
dialogPolicy: "accept" | "dismiss" | "manual";
|
|
@@ -597,7 +649,7 @@ declare const UnotestConfigSchema: z.ZodObject<{
|
|
|
597
649
|
};
|
|
598
650
|
linter: {
|
|
599
651
|
enabled: boolean;
|
|
600
|
-
rules: Partial<Record<"lint:deep-css" | "lint:xpath" | "lint:obfuscated-class" | "lint:pause-explicit" | "lint:disambig-by-index" | "lint:evaluate-discouraged" | "lint:scenario-in-root" | "lint:mustache-in-dsl" | "lint:goto-concat" | "lint:regex-invalid" | "lint:echo-assert" | "lint:api-call-file-body" | "lint:lint-ok-missing-reason" | "lint:flow-returns-value" | "lint:legacy-layout" | "lint:external-variable-undeclared" | "lint:env-host-from-base" | "lint:one-test-per-file" | "validator:unknown-function" | "validator:arity" | "validator:arg-kind" | "validator:step-coverage" | "validator:step-shape" | "validator:meta-shape" | "validator:function-shape" | "validator:for-shape" | "validator:if-shape" | "validator:var-shape" | "validator:unsupported-statement" | "validator:unsupported-expression" | "validator:string-concat" | "validator:semantic-loss" | "validator:list-arg" | "validator:wrapped-format", "off" | "warn" | "error">>;
|
|
652
|
+
rules: Partial<Record<"lint:deep-css" | "lint:xpath" | "lint:obfuscated-class" | "lint:pause-explicit" | "lint:disambig-by-index" | "lint:evaluate-discouraged" | "lint:scenario-in-root" | "lint:mustache-in-dsl" | "lint:goto-concat" | "lint:regex-invalid" | "lint:echo-assert" | "lint:api-call-file-body" | "lint:lint-ok-missing-reason" | "lint:flow-returns-value" | "lint:legacy-layout" | "lint:external-variable-undeclared" | "lint:env-host-from-base" | "lint:one-test-per-file" | "lint:shadows-dsl-function" | "validator:unknown-function" | "validator:arity" | "validator:arg-kind" | "validator:step-coverage" | "validator:step-shape" | "validator:meta-shape" | "validator:function-shape" | "validator:for-shape" | "validator:if-shape" | "validator:var-shape" | "validator:unsupported-statement" | "validator:unsupported-expression" | "validator:string-concat" | "validator:semantic-loss" | "validator:list-arg" | "validator:wrapped-format", "off" | "warn" | "error">>;
|
|
601
653
|
};
|
|
602
654
|
mcp: {
|
|
603
655
|
transport: "stdio";
|
|
@@ -614,6 +666,8 @@ declare const UnotestConfigSchema: z.ZodObject<{
|
|
|
614
666
|
apiBaseUrl?: string | undefined;
|
|
615
667
|
uploadDir?: string | undefined;
|
|
616
668
|
};
|
|
669
|
+
locale?: string | undefined;
|
|
670
|
+
timezone?: string | undefined;
|
|
617
671
|
baseUrl?: string | undefined;
|
|
618
672
|
webServer?: {
|
|
619
673
|
url: string;
|
|
@@ -622,11 +676,19 @@ declare const UnotestConfigSchema: z.ZodObject<{
|
|
|
622
676
|
reuseExistingServer?: boolean | undefined;
|
|
623
677
|
} | undefined;
|
|
624
678
|
channel?: "chrome" | "msedge" | "chrome-beta" | "chromium" | "bundled" | null | undefined;
|
|
679
|
+
viewport?: {
|
|
680
|
+
width?: number | undefined;
|
|
681
|
+
height?: number | undefined;
|
|
682
|
+
deviceScaleFactor?: number | undefined;
|
|
683
|
+
} | undefined;
|
|
684
|
+
device?: string | undefined;
|
|
685
|
+
maxContexts?: number | undefined;
|
|
625
686
|
launch?: {
|
|
626
687
|
args?: string[] | undefined;
|
|
627
688
|
userDataDir?: string | undefined;
|
|
628
689
|
} | undefined;
|
|
629
690
|
storageState?: string | undefined;
|
|
691
|
+
teardownTimeoutMs?: number | undefined;
|
|
630
692
|
schedules?: {
|
|
631
693
|
collection: string;
|
|
632
694
|
cron: string;
|