@unotest/web 0.48.0 → 0.49.1

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.
@@ -313,6 +313,10 @@ Then verify:
313
313
  run_test { name: "<scenario-name>" }
314
314
  ```
315
315
 
316
+ In a file with several `test_*`, add the one to run:
317
+ `run_test { name: "<scenario-name>", test: "test_<name>" }` — without it
318
+ the reply is `status: "test_required"` and its `next` is that call.
319
+
316
320
  `run_test` BLOCKS until the run settles — do NOT poll
317
321
  `inspect_runtime` to wait for completion (it stays a diagnostic tool
318
322
  for paused runs). The response carries `next.outcome`. **The scenario
@@ -444,6 +444,10 @@ Then verify:
444
444
  run_test { name: "<scenario-name>" }
445
445
  ```
446
446
 
447
+ In a file with several `test_*`, add the one to run:
448
+ `run_test { name: "<scenario-name>", test: "test_<name>" }` — without it
449
+ the reply is `status: "test_required"` and its `next` is that call.
450
+
447
451
  `run_test` BLOCKS until the run settles — do NOT poll
448
452
  `inspect_runtime` to wait for completion (it stays a diagnostic tool
449
453
  for paused runs). The response carries `next.outcome`. **The scenario
package/CHANGELOG.md CHANGED
@@ -1,5 +1,186 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.49.1] - 2026-10-11
4
+
5
+ ### Patch Changes
6
+
7
+ - 4897ede: A run of one test on a box:
8
+
9
+ - A test the file does not have is refused when the run is ordered, with the file's tests listed, the same refusal the viewer and `e2e --test` give. Before, the box accepted the run, it exited without a word and the notification said "tests failed". The check needs a bundle pushed with this version, since `bundle push` now lists each scenario file's tests in the bundle. An older bundle is not checked.
10
+ - `box runs`, `box run` and notifications name such a run `file › test`, as `bundle push --follow` already did. This also applies to a file with a single test, whose runs used to show only the file. `box queue` names a waiting or running run of one test the same way. `box run --json` and `box queue --json` gain a `test` field on those runs. A run of a whole file of several tests is named as before, and a file's notification series is the same whether a run played one of its tests or all of them.
11
+
12
+ - 4897ede: Box runs of one test: the box accepts a test name only as a scenario function is spelled (letters, digits, `_`, `$`, up to 128 characters) and refuses anything else before it reaches the queue; the box and the run sidecar check it by the same rule, so a name with Cyrillic letters or `$` is played instead of lost. `box queue`, `box runs` and `box run` print run labels with control characters shown as escapes, so a name written by an older box cannot drive the terminal.
13
+ - de529ab: A `const` assigned again (`K = 2` or `K++`) now fails the run at that line with the same message `lint` gives, as JavaScript does. Before, `lint` refused it but `e2e` and `run_test` ran the assignment and passed. `let` reassigns as before, and a `const` in a loop body is declared again on every pass.
14
+ - 4897ede: Scenarios can declare variables with `let` and `const`, and pass a lambda to four array methods. `let x = …` and `const x = …` work like the bare assignment, in the function's scope, and `for (let i = 0; …)` declares the loop variable; assigning a const again is a lint error (`const-assign`). At the top of a file a `const` holding a literal is visible in every function, as helper functions are. `a.filter(x => …)`, `a.map((x, i) => …)`, `a.find(x => …)` and `a.some(x => …)` take an expression or a block with `return`; the parameters live in their own scope and the body runs element by element. A lambda anywhere else is refused (`lambda-position`) — functions are not values — and `filter` with an options object is still the locator step. The viewer shows scenarios written with the new syntax.
15
+ - 4897ede: `lint` refuses a statement at the top level of a scenario or helper file, and the DSL's refusals of everyday JavaScript say what to write instead.
16
+
17
+ - Only function definitions run at the top level of a file. A statement there (`BASE = 'x'`, `const BASE = 'x'`, a call) was dropped without a word: `lint` passed and the test failed with `Variable "BASE" is not defined`. It is now `validator:unsupported-statement`, an error at its line, with the way out: a function that returns the value.
18
+ - `let x = 1` and `const x = 1` get the same answer as `var` ("use direct assignment") instead of `unexpected WORD`.
19
+ - An arrow with parameters (`a.map(x => x * 2)`), an arrow or a function as a value, a spread, `for…of` / `for…in`, `let` in a `for` loop, `n += 1`, `o.a = 2`, `try` / `throw` / `switch`, `typeof` and `?` (ternary, `?.`, `??`) each name the DSL way, at the token where parsing stops.
20
+ - `a[0] = 2` says to build a new array with `push`. `.includes()` on an array, at run time, says what an array answers.
21
+ - `pop`, `shift`, `unshift`, `splice`, `sort`, `reverse`, `find`, `some`, `every`, `reduce`, `Object.keys`, `Math.max` / `min` / `floor` / `ceil` and `indexOf` on an array are answered with what to write instead.
22
+
23
+ **Migration:** a file with a statement at its top level now fails `lint`. That statement never ran; move it into `test_*` or into a function.
24
+
25
+ - 4897ede: Scenarios can now write into arrays and sort them. `arr[i] = v` replaces an element (nested indexes and object keys too); an index past the end fails the step instead of leaving holes, and `arr[arr.length] = v` appends. The keys `__proto__`, `constructor` and `prototype` cannot be assigned, and an index read on an object returns its own keys only, so a scenario cannot reach a shared prototype. `arr.sortBy(key?, {desc?})` sorts an array in place by an object field, or by the values themselves without a key; the sort is stable and the values must be all numbers or all strings. The linter's hint for `a.sort()` now points to `sortBy`, and its message for a non-list `assertTexts` argument names it `expected`, as the docs do.
26
+ - 4897ede: `&&` and `||` in scenarios now short-circuit as in JavaScript: the right side is not evaluated once the left one decides the result. A null guard such as `x != null && x.a == 1` no longer fails with "Property access on non-object value" when `x` is null, including inside `waitUntil`. The result stays a boolean.
27
+ - 4897ede: The expected value of `assertTexts`, `assertText` and other assertions can now be any expression of the right kind: a variable holding a list, an index read such as `labels[i]`, or a helper's result. Previously the linter accepted only an array literal for `assertTexts` and rejected `arr[i]` as a text. A value known to be the wrong kind is still refused with the argument's name; anything else is checked when the step runs.
28
+ - 4897ede: Strings answer `s.split(separator)`, which returns an array of strings as in JavaScript: `"a, b".split(", ")` gives `["a", "b"]`, the parts keep their spaces, and a separator that is not there gives the whole string as the one part. The linter's hint for `split` no longer points to a shell script.
29
+ - 4897ede: `@expect-fail` covers a test that fails, not a script that breaks: a marked test that errors (an unknown function, a typo) is reported as errored and turns the collection red, instead of counting as failed on purpose.
30
+ - 5319d32: A file of several tests whose only failure is marked `@expect-fail` now reads as failed on purpose on its own line too, under `bundle push --follow` and in the run index, instead of a red `failed` above the collection's green verdict.
31
+ - 4897ede: `// @expect-fail` now reaches the runner, not only the viewer. A marked test that fails is reported as failed on purpose in `e2e` and in a collection (`N of M failed on purpose`, `expected=N` in the summary), and every `test_*` of a file whose leading comments carry the marker is marked. A collection whose only failures are on purpose exits 0; `e2e` of such a test still exits 1. A marked test that passes now fails with a message saying the failure it expects is gone. The run index records `expectFail` on such runs.
32
+ - 4897ede: `lint` knows the functions a scenario defines, and checks a call wherever it stands.
33
+
34
+ - A function defined at the top level of a scenario file is a helper of that file: the run always called it, and `lint` now does not report it as `validator:unknown-function`. Helpers in `unotest/e2e/_helpers/` work as before.
35
+ - A call inside an options object or a helper's argument (`apiCall('POST', '/pages', {html: page_hmtl()})`) is checked like any other call, so a misspelt name there is an error at its line instead of a failure at run time.
36
+
37
+ **Migration:** a scenario with an unknown or misspelt function inside an object argument used to pass `lint` and now fails it. That call would have failed the run.
38
+
39
+ - 4897ede: A collection whose `prepare` failed on a box is no longer just `failed (exit 94)` under `bundle push --follow`: the line says the prepare failed and the suite did not run, and where its output is. The run itself is now written (a failed collection with no scenario), so `box run <id>` finds it and gives the same reason instead of "no such run".
40
+ - ab0fb14: `bundle push --follow` now reads tests marked `@expect-fail` the way a local run does: a marked test that fails prints `✓ <test> — failed on purpose (@expect-fail)`, and a marked test that passes prints `✗ <test> — passed, but it is marked @expect-fail`. Previously a control collection on a box streamed its expected failures as ordinary ones. The line that ends a failed scenario in `--follow` also uses the local run's words: `✗ login — failed` instead of `✗ login (failed)`. In a local run, a test of a multi-test file that failed on purpose now carries the same `(@expect-fail)` suffix as a single test. Run lines from a box carry a new optional `expectFail` field; older clients ignore it.
41
+ - 4897ede: `env push` / `env set`: a box that refuses a name of the run's identity (`UNOTEST_RUN_ID` and the rest) no longer gets the advice about `APP_BASE_URL`. The box's refusal now says which kind of name it reserved, under the same `reserved` code, so older CLIs still show the box's own words. A refusal code this CLI does not know is shown as the box worded it, with exit 1, instead of "not a box's answer".
42
+ - 4897ede: `lint` names a scenario the way `e2e` does.
43
+
44
+ - `lint values/string-escapes` checks `unotest/e2e/values/string-escapes.js`, as `e2e values/string-escapes` runs it. It used to fail with `ENOENT` and exit 1.
45
+ - A folder, which failed with `EISDIR`, checks every `.js` under it — as a path or as a feature folder name (`lint values`).
46
+ - A file path works as before; typed from inside the suite package (`cd unotest && lint e2e/values/numbers.js`) it means what it says there.
47
+ - A name that is not there exits 2 and prints where it was looked for, with the closest names, the same message `e2e` gives.
48
+
49
+ - 4897ede: A run paused on a failure (`e2e --debug`, an agent's `run_test`) with nobody attached is now aborted after 30 minutes without a client command, and frees the queue — the timeout existed but was restarted by the run reading its own state every few seconds, so it never ran out. Any debug command restarts it; reading the run's state does not. `UNOTEST_PAUSE_TTL_MS` sets the timeout, and the expiry line says how long it waited.
50
+ - 4897ede: The DSL reference shipped with the package now links `readJson` to the "App constants in scenarios" section on docs.unotest.com instead of naming a page the package does not contain.
51
+ - 4897ede: `skip(reason)` can be called anywhere in a test's body, including inside a step or an `if` block, so a test can skip itself on data an earlier step read. The rest of the test does not run, registered teardown blocks still play, and the run reports skipped with the reason. `lint:skip-outside-test` now reports only a `skip()` in a helper, a flow or at the top level of a file.
52
+ - 4897ede: The step a test skipped itself in now shows as skipped in the viewer, not as passed; the steps before it stay passed. The run journal marks that statement and its step block with an optional `skipped: true` next to `ok: true`, so readers that do not know the field see the same events as before.
53
+ - 4897ede: `getImageSize(loc)` reads an image's natural size without `evaluateOnLocator`.
54
+
55
+ - It returns `{width, height}` of the `<img>` file, or `null` when the image is broken or has no `src`. An image still loading is waited for up to the action timeout, so a good image is not read as broken.
56
+ - An element that is not an `<img>` fails with a `LocatorError` naming its tag; an inline `<svg>` is pointed to `boundingBox`. An image that never finishes loading fails with a `TimeoutError`; for a `loading="lazy"` one the message says to `scrollIntoView()` it first.
57
+ - It works inside iframes, cross-origin ones included.
58
+ - The `lint:evaluate-discouraged` hint now names `getImageSize` for images, and `count` / `assertCount` over `.filter({...})` for counting elements by text or by a child.
59
+ - The DSL reference shows how to count by a condition: `.filter({...})` for text or a child, a CSS `locator(...)` for the element's own class or attribute.
60
+
61
+ - 4897ede: Viewer tree:
62
+
63
+ - A collection file that does not parse no longer hides the others. The Collections section lists the valid collections and shows the broken file as its own row, with the parse error. Before, the whole section read "no collections match". Runs of the valid collections are not affected by a broken file next to them.
64
+ - The section footers show only keys that do something. `R` now runs the selected scenario headless, the same as "Run again", and is shown only where the viewer may run. `⌘K` focuses the filter in every section. Hints that had no handler are removed: arrows, `N` new collection, `O` open .zip, `⌃C` abort, `⌥` reveal secret.
65
+
66
+ - 4897ede: A run no longer hangs after a failed step on a page that stopped answering. After a failure the runner reads the page for the failure bundle (screenshot, HTML, semantic DOM, outline), and a page whose main thread is busy, such as a script in an endless loop, never answered those reads, in WebKit and Chromium alike. Each read now gets 15 seconds. The first that runs out ends the reading: the test stays red with its own error, the output adds `the page gave no answer within 15s after the failed step`, and the run's `failure/screenshot-error.txt` holds the same reason. Closing the browser context has the same limit.
67
+
68
+ Killing `unotest-web` outright (`kill -9`) now stops the run it started too, with its browser. Before, only SIGTERM, SIGINT and SIGHUP reached it, and the run went on with nobody left to stop it.
69
+
70
+ - Updated dependencies [4897ede]
71
+ - Updated dependencies [4897ede]
72
+ - Updated dependencies [de529ab]
73
+ - Updated dependencies [4897ede]
74
+ - Updated dependencies [4897ede]
75
+ - Updated dependencies [4897ede]
76
+ - Updated dependencies [4897ede]
77
+ - Updated dependencies [4897ede]
78
+ - Updated dependencies [4897ede]
79
+ - Updated dependencies [4897ede]
80
+ - Updated dependencies [4897ede]
81
+ - Updated dependencies [4897ede]
82
+ - Updated dependencies [ab0fb14]
83
+ - Updated dependencies [4897ede]
84
+ - Updated dependencies [4897ede]
85
+ - Updated dependencies [4897ede]
86
+ - @unotest/protocol@0.49.1
87
+ - @unotest/core@0.49.1
88
+ - @unotest/dsl@0.49.1
89
+ - @unotest/viewer@0.49.1
90
+ - @unotest/grounder-client@0.49.1
91
+
92
+ ## [0.49.0] - 2026-10-09
93
+
94
+ ### Minor Changes
95
+
96
+ - 3531ce6: A collection can set the browser window of its scenarios.
97
+
98
+ - `viewport:` in a collection's YAML: `null` for the real browser window, `{width, height}` for an emulated size. It replaces the project's `viewport` for every scenario the collection runs; the same scenario run on its own keeps the project's window. It works with a browser profile, where `setViewport(null)` is refused.
99
+ - A wrong value is refused with what to write instead. `viewport: null` over a project `device` is refused before the first scenario starts, as in the config.
100
+ - The `setViewport(null)` refusal on a browser profile now points at the collection field.
101
+ - The viewer's collection editor keeps a hand-written `viewport:` and `prepare:` when it saves a collection.
102
+
103
+ - 78e783a: A `generate` command keeps the scenarios' copy of application values in step with the source.
104
+
105
+ - `generate` in the config: a shell command that writes `unotest/generated/` from your application's source (constants, label tables, schemas). The scenarios read the files with `readJson()`. It runs from the project root before every local `e2e` and `collection` (once for the suite) before `bundle push`, and in `box create` before the box is rented, so an edit of the source reaches the next run and the bundle carries a fresh snapshot.
106
+ - A box never runs it: it has your suite, not your application, and the snapshot arrived in the bundle. `lint` does not run it.
107
+ - A failure refuses the run with exit 95 and names the command. A failure in `box create` creates no box. `--no-generate` on `e2e`, `collection`, `bundle push` and `box create` skips it for one run.
108
+ - `init` adds `unotest/generated/` to the project's `.gitignore`, and `bundle push` packs the folder although git ignores it.
109
+
110
+ - bf95375: A box keeps the names it sets on every run to itself.
111
+
112
+ - `env set` and the values page refuse a value named `UNOTEST_RUN_ID`, `UNOTEST_ENV`, `UNOTEST_BUNDLE_ID`, `UNOTEST_EDITOR_TYPES`, `UNOTEST_BOX_SECRET_NAMES`, `UNOTEST_QUEUE_LEASE`, `UNOTEST_RUN_TRIGGER` or `UNOTEST_RUN_ACTOR`, naming it. Before, such a value was taken and won over the box's own: `UNOTEST_BUNDLE_ID=""` made a box run look like a local one and let it run `generate`.
113
+ - A value already stored under one of these names stays listed on the box, and no run receives it. Remove it with `env rm`.
114
+ - Other `UNOTEST_*` names are set as before, `UNOTEST_BROWSER_CHANNEL` included.
115
+
116
+ - a262cb0: A box runs and reports each test of a file of several `test_*` on its own.
117
+
118
+ - Run again on one test in a box's viewer runs that test only. A box older than this runs the whole file.
119
+ - Run notifications (Slack, Telegram, webhook) and GitHub checks count tests, not files: `smoke failed — 1 of 42 tests`, and a failed test of a file of several is named `file › test`. The webhook fields keep their names: `scenariosTotal`, `scenariosFailed` and `failedScenarios` now count and name tests.
120
+ - `bundle push --run --follow` prints each test of such a file between the file's start and end, with its own failed steps, `log` and `note` lines, under `file › test`.
121
+ - A test that skipped itself prints in `--follow` as it does in a local run, `○ file › test — skipped: <reason>`, not as passed. A client older than this still prints it as passed.
122
+
123
+ - 970813c: A scenario file can hold several `test_*` functions, and each one is a test of its own: its own run, browser context, retry and verdict, with its own failure bundle, screenshots and console. Functions of the file are shared between its tests; state is not. `npx @unotest/web e2e <file>` plays them in order and exits 1 when any of them failed. A file with one test runs exactly as before. `e2e <file> --test <name>` plays one test, named as the function is or without its `test_` prefix. A name the file does not have is refused with exit 2 and the list of its tests.
124
+
125
+ `// @expect-fail` can mark one test: put it in the `//` comments directly above that test's `function` line, with no blank line between. A marker in the file's leading comments, set apart from the first function by a blank line, still marks every test of the file.
126
+
127
+ The MCP tool `run_test` takes `test` too. A debug session follows one test, so for a file that holds several, `run_test` without `test` starts nothing and answers with the tests and the call to make:
128
+
129
+ ```json
130
+ {
131
+ "status": "test_required",
132
+ "reason": "auth/login holds 3 tests; run_test plays one — name it in `test`.",
133
+ "tests": ["test_login_ok", "test_login_locked", "test_logout"],
134
+ "next": "run_test {name: \"auth/login\", test: \"test_login_ok\"}"
135
+ }
136
+ ```
137
+
138
+ An unknown `test` answers `status: "test_not_found"` with the same list.
139
+
140
+ A collection counts tests, not files. A file of three tests is three verdicts: its line in the `collection` output says `✗ <file> — 1 of 3 failed` and lists each test under it, and the summary counts passed, failed and skipped tests. A file that never started (bailed or busy) is still counted as one file.
141
+
142
+ The viewer shows each test on its own. A file of several tests is a node in the scenarios tree with one row per test, and the chevron opens it from the keyboard too (ArrowRight / ArrowLeft). The file's tab shows the steps of every test under its name. The file's dot and status sum its tests up: failed when one failed, skipped only when all of them skipped, failed on purpose only when every test that ran was expected to fail, otherwise passed. History shows each test's run under the file's run, and can be filtered to one test. A test's run tab shows its own steps, screenshots, console and failure, titled `file › test`, and Run again runs that test only. Home and a collection run's summary count tests, and the Home card says "tests". A link to a test's run opens that test, with its name and status in the tab.
143
+
144
+ `lint:one-test-per-file` no longer reports anything: several `test_*` in one file is now the way to write a suite of related tests.
145
+
146
+ A test that skips itself (`skip(reason)`) now shows as **skipped**, not as passed. The run tree, the history strip, the run tab (its progress bar included) and the overview draw it grey with its reason, the overview gets a "skipped" card when any test's last run skipped and no longer calls such a field "Everything is green", and a collection row counts skipped tests apart from passed ones. A run still in progress that ends in a skip turns skipped at once, without a reload. `box run` prints `(skipped)` with the reason, and its `--json` answer carries `skipReason`. Like a local `e2e`, it exits 0 on a skip.
147
+
148
+ **Migration:** `RunStatus` has a new value, `"skipped"`, and `RunMeta` and the scenario index entry carry the reason (`skipReason`, `lastSkipReason`). Code that switches over `RunStatus` exhaustively must handle the new value. Code that treated `"passed"` as "finished without failing" should accept `"skipped"` as well. The run index schema version moves to 3: the viewer discards an older index on start and rebuilds it from the runs on disk. Past skipped runs then show as skipped too.
149
+
150
+ **Migration:** the run journal has two new event kinds, `file-run:test-started` and `file-run:test-finished`, written by the run of a file with several tests. Code that switches over journal events exhaustively must handle them. Runs, manifests and `run:started` carry `test` (the function name). Its tests point at the file's run through `parentRunId`, as a collection's scenarios point at the collection's.
151
+
152
+ **Migration:** `lint:one-test-per-file` is deprecated and does nothing. A config that sets it (`"lint:one-test-per-file": "error"`) still loads; remove the entry when convenient. The rule id goes away in the next major version.
153
+
154
+ ### Patch Changes
155
+
156
+ - aac77aa: A failed step now brings its notes and the whole diff along.
157
+
158
+ - A failed statement carries the `note(label, value)` calls of its `step(...)` block: printed under the error in the `e2e` output as `note label: value`, and returned as `lastFailure.notes` (the `error` of a `run_test` reply). Secrets are masked as in the run journal. A nested step reports its own notes; a step that noted nothing reports an empty list.
159
+ - `assertEqual` shows every line of its diff. The `… N unchanged …` folding is gone from its failure message.
160
+
161
+ - 269cc10: `filter({...})` refuses a key it does not read.
162
+
163
+ - It reads `hasText`, `hasNotText`, `has` and `hasNot`. Any other key (`hasClass`, `visible`, a typo of `hasText`) used to be dropped without a word, so `count(getByRole('row').filter({visible: true}))` counted every row and the check passed.
164
+ - `lint` reports it as `lint:filter-unknown-option` (an error), and the run fails the step with the same message: the key, and the keys `filter` takes. `visible` is not supported yet.
165
+ - The MCP structured locator refuses such a `filter` step too, instead of stripping the key.
166
+
167
+ **Migration:** a scenario with an unknown or misspelled key in `filter` that used to pass now fails, at lint and at run time — on purpose: it was checking less than it said. Use one of the four keys, or a CSS `locator('tr.done')` for a class or an attribute of the element itself.
168
+
169
+ - e54db1a: Quieter linter on two rules.
170
+
171
+ - `lint:echo-assert` no longer fires when the text you fill is only part of the text you wait for. `fill(box, 'helper')` followed by `waitForText('ann/helper')` is not an echo: the page must show more than was typed. It still warns when the waited text is part of the filled value, equality included.
172
+ - `lint:evaluate-discouraged` gives one warning per file instead of one per call. The warning sits on the first call and says how many calls the file has. A call under `// lint-ok: <reason>` is left out of the count; with every call silenced, there is no warning. The argument hints (one value vs one array) name the lines they apply to.
173
+
174
+ - Updated dependencies [3531ce6]
175
+ - Updated dependencies [aac77aa]
176
+ - Updated dependencies [78e783a]
177
+ - Updated dependencies [970813c]
178
+ - @unotest/protocol@0.49.0
179
+ - @unotest/viewer@0.49.0
180
+ - @unotest/core@0.49.0
181
+ - @unotest/dsl@0.49.0
182
+ - @unotest/grounder-client@0.49.0
183
+
3
184
  ## [0.48.0] - 2026-10-08
4
185
 
5
186
  ### Minor Changes
@@ -1632,7 +1813,7 @@ picker on` fetches this project's own access and writes the three grounder
1632
1813
 
1633
1814
  That screen named one command:
1634
1815
 
1635
- npx @unotest/web bundle push --run --env ENVIRONMENT
1816
+ npx @unotest/web bundle push --run --env ENVIRONMENT
1636
1817
 
1637
1818
  In a project that has just been created, the command refuses with `no box
1638
1819
  address` before it does anything: the project holds neither the box's address
@@ -1641,8 +1822,8 @@ address` before it does anything: the project holds neither the box's address
1641
1822
 
1642
1823
  The screen now names both steps, in the order they have to happen:
1643
1824
 
1644
- npx @unotest/web box access larch
1645
- npx @unotest/web bundle push --run --env ENVIRONMENT
1825
+ npx @unotest/web box access larch
1826
+ npx @unotest/web bundle push --run --env ENVIRONMENT
1646
1827
 
1647
1828
  The box's own name is filled in when its licence carries one. `box access`
1648
1829
  with no name refuses, and its refusal offers to create a box — to somebody who
package/README.md CHANGED
@@ -352,7 +352,8 @@ npx @unotest/web box run checkout-mqf3pwr1 --env acme/test
352
352
  ```
353
353
 
354
354
  `box run` prints the failure, the results of soft steps and the judge's
355
- verdicts. When you want the artifacts too:
355
+ verdicts. A run whose test skipped itself reads `skipped`, with the reason,
356
+ and exits 0 like a local skip. When you want the artifacts too:
356
357
 
357
358
  ```sh
358
359
  npx @unotest/web box run checkout-mqf3pwr1 --env acme/test --download
@@ -52,6 +52,12 @@ const asksForHelp = args.some((a) => a === "--help" || a === "-h");
52
52
  // under src/. In published packages dist/ exists (built by prepublishOnly);
53
53
  // in a fresh clone we fall back to running TS sources via tsx so that
54
54
  // `unotest-web --help` works without an explicit `pnpm build` first.
55
+ /** The cli dispatcher's route. It stops when this wrapper is gone, through
56
+ * an IPC channel (see the spawn below); the other entries are not told. */
57
+ function cliRoute(forwardArgs) {
58
+ return { dist: "dist/runner/cli.js", src: "src/runner/cli.ts", forwardArgs, followsParent: true };
59
+ }
60
+
55
61
  function dispatch(sub) {
56
62
  // A question is not a command. Every `--help` goes to the cli dispatcher
57
63
  // with the arguments untouched, whatever the route for that subcommand
@@ -60,13 +66,13 @@ function dispatch(sub) {
60
66
  // drops the flag and runs the command — `viewer --help` started a viewer
61
67
  // and scaffolded `unotest/` into the current directory.
62
68
  if (asksForHelp) {
63
- return { dist: "dist/runner/cli.js", src: "src/runner/cli.ts", forwardArgs: args };
69
+ return cliRoute(args);
64
70
  }
65
71
  switch (sub) {
66
72
  case "mcp":
67
73
  return { dist: "dist/mcp/server.js", src: "src/mcp/server.ts", forwardArgs: [] };
68
74
  case "e2e":
69
- return { dist: "dist/runner/cli.js", src: "src/runner/cli.ts", forwardArgs: args };
75
+ return cliRoute(args);
70
76
  case "init":
71
77
  return {
72
78
  dist: "dist/runner/init.js",
@@ -88,9 +94,9 @@ function dispatch(sub) {
88
94
  case "lint":
89
95
  // Forwarded whole, like e2e: `--env <name>` and explicit file
90
96
  // arguments belong to the command, not to the dispatcher.
91
- return { dist: "dist/runner/cli.js", src: "src/runner/cli.ts", forwardArgs: args };
97
+ return cliRoute(args);
92
98
  case "viewer":
93
- return { dist: "dist/runner/cli.js", src: "src/runner/cli.ts", forwardArgs: ["viewer"] };
99
+ return cliRoute(["viewer"]);
94
100
  case "prepare-fix":
95
101
  return {
96
102
  dist: "dist/runner/prepare-fix.js",
@@ -100,16 +106,16 @@ function dispatch(sub) {
100
106
  case "--help":
101
107
  case "-h":
102
108
  case "--version":
103
- return { dist: "dist/runner/cli.js", src: "src/runner/cli.ts", forwardArgs: args };
109
+ return cliRoute(args);
104
110
  default:
105
111
  if (sub === undefined) {
106
112
  return { dist: "dist/mcp/server.js", src: "src/mcp/server.ts", forwardArgs: [] };
107
113
  }
108
- return { dist: "dist/runner/cli.js", src: "src/runner/cli.ts", forwardArgs: args };
114
+ return cliRoute(args);
109
115
  }
110
116
  }
111
117
 
112
- const { dist, src, forwardArgs } = dispatch(sub);
118
+ const { dist, src, forwardArgs, followsParent = false } = dispatch(sub);
113
119
  const distPath = resolve(root, dist);
114
120
 
115
121
  // Init is interactive (browser prompt) — running it via spawn through
@@ -138,9 +144,13 @@ if (sub === "init" && !asksForHelp && existsSync(distPath)) {
138
144
  // when dist/ is absent (fresh clone without `pnpm build`).
139
145
  let cmd = process.execPath;
140
146
  let cmdArgs;
147
+ // Only a cli started directly can hold the channel: under tsx it would be
148
+ // tsx's, not the cli's.
149
+ let linkParent = false;
141
150
  if (existsSync(distPath)) {
142
151
  // Published / built path — run compiled JS directly.
143
152
  cmdArgs = [distPath, ...forwardArgs];
153
+ linkParent = followsParent;
144
154
  } else {
145
155
  const srcPath = resolve(root, src);
146
156
  const haveSrc = existsSync(srcPath);
@@ -170,7 +180,13 @@ if (existsSync(distPath)) {
170
180
  cmdArgs = [tsxCliPath, entryForTsx, ...forwardArgs];
171
181
  }
172
182
 
173
- const child = spawn(cmd, cmdArgs, { stdio: "inherit" });
183
+ // The cli gets an IPC channel beside the inherited stdio — fd 3, so stdin
184
+ // and stdout stay the caller's. `kill -9` on this wrapper forwards nothing;
185
+ // the channel closing is how the cli learns its parent is gone, and it
186
+ // stops then as it would on the SIGTERM forwarded below (#378).
187
+ const child = spawn(cmd, cmdArgs, {
188
+ stdio: linkParent ? ["inherit", "inherit", "inherit", "ipc"] : "inherit",
189
+ });
174
190
 
175
191
  // Pass termination on to the child. This dispatcher is a thin wrapper: kill
176
192
  // IT and the real process — the viewer server, a running scenario — has to
@@ -35,6 +35,13 @@ declare const ViewportSchema: z.ZodObject<{
35
35
  deviceScaleFactor?: number | undefined;
36
36
  }>;
37
37
  type Viewport = z.infer<typeof ViewportSchema>;
38
+ /** Why `viewport` cannot stand next to `device`, or undefined when it can.
39
+ * A preset IS a size and a scale: next to the real window it would
40
+ * either be ignored or break it. One wording for the config and for a
41
+ * collection's `viewport: null` over a project that names a device. */
42
+ declare function viewportDeviceConflict(viewport: Viewport | null | undefined, device: string | undefined,
43
+ /** Whose `viewport: null` it is, as the message names it. */
44
+ subject?: string): string | undefined;
38
45
  declare const RetryReasonSchema: z.ZodEnum<["transient", "network", "crash"]>;
39
46
  type RetryReason = z.infer<typeof RetryReasonSchema>;
40
47
  declare const RetryConfigSchema: z.ZodObject<{
@@ -109,19 +116,19 @@ declare const FailureBundleConfigSchema: z.ZodObject<{
109
116
  type FailureBundleConfig = z.infer<typeof FailureBundleConfigSchema>;
110
117
  declare const DialogPolicySchema: z.ZodEnum<["accept", "dismiss", "manual"]>;
111
118
  type DialogPolicy = z.infer<typeof DialogPolicySchema>;
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", "lint:skip-outside-test", "lint:set-test-timeout-outside-test", "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"]>;
119
+ 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", "lint:skip-outside-test", "lint:set-test-timeout-outside-test", "lint:filter-unknown-option", "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"]>;
113
120
  type LinterRuleId = z.infer<typeof LinterRuleIdSchema>;
114
121
  declare const LinterSeveritySchema: z.ZodEnum<["off", "warn", "error"]>;
115
122
  type LinterSeverity = z.infer<typeof LinterSeveritySchema>;
116
123
  declare const LinterConfigSchema: z.ZodObject<{
117
124
  enabled: z.ZodBoolean;
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", "lint:skip-outside-test", "lint:set-test-timeout-outside-test", "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"]>>;
125
+ 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", "lint:skip-outside-test", "lint:set-test-timeout-outside-test", "lint:filter-unknown-option", "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"]>>;
119
126
  }, "strict", z.ZodTypeAny, {
120
127
  enabled: boolean;
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" | "lint:skip-outside-test" | "lint:set-test-timeout-outside-test" | "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">>;
128
+ 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" | "lint:skip-outside-test" | "lint:set-test-timeout-outside-test" | "lint:filter-unknown-option" | "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">>;
122
129
  }, {
123
130
  enabled: boolean;
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" | "lint:skip-outside-test" | "lint:set-test-timeout-outside-test" | "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">>;
131
+ 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" | "lint:skip-outside-test" | "lint:set-test-timeout-outside-test" | "lint:filter-unknown-option" | "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">>;
125
132
  }>;
126
133
  type LinterConfig = z.infer<typeof LinterConfigSchema>;
127
134
  declare const QueueConfigSchema: z.ZodObject<{
@@ -439,6 +446,16 @@ declare const UnotestConfigSchema: z.ZodEffects<z.ZodObject<{
439
446
  reuseExistingServer?: boolean | undefined;
440
447
  timeoutMs?: number | undefined;
441
448
  }[]>>;
449
+ /** A shell command that writes `unotest/generated/` from the
450
+ * application's own source (constants, label tables, schemas), so the
451
+ * scenarios read them with `readJson()` instead of a hand-made copy.
452
+ * Runs in the project root — the same base as `prepare` and
453
+ * `webServer.command` — before every local `e2e` / `collection` and
454
+ * before `bundle push`, which then carries the folder although it is
455
+ * git-ignored. Not on a box (the snapshot arrived in the bundle) and
456
+ * not in `lint` (linting runs no project commands). A failure refuses
457
+ * the run; `--no-generate` skips it for one run. */
458
+ generate: z.ZodOptional<z.ZodString>;
442
459
  browsers: z.ZodArray<z.ZodEnum<["chromium", "firefox", "webkit"]>, "many">;
443
460
  channel: z.ZodOptional<z.ZodUnion<[z.ZodEnum<["chrome", "msedge", "chrome-beta", "chromium"]>, z.ZodEffects<z.ZodLiteral<"bundled">, null, "bundled">, z.ZodNull]>>;
444
461
  viewport: z.ZodOptional<z.ZodNullable<z.ZodObject<{
@@ -603,13 +620,13 @@ declare const UnotestConfigSchema: z.ZodEffects<z.ZodObject<{
603
620
  }>;
604
621
  linter: z.ZodObject<{
605
622
  enabled: z.ZodBoolean;
606
- 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", "lint:skip-outside-test", "lint:set-test-timeout-outside-test", "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"]>>;
623
+ 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", "lint:skip-outside-test", "lint:set-test-timeout-outside-test", "lint:filter-unknown-option", "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"]>>;
607
624
  }, "strict", z.ZodTypeAny, {
608
625
  enabled: boolean;
609
- 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" | "lint:skip-outside-test" | "lint:set-test-timeout-outside-test" | "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">>;
626
+ 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" | "lint:skip-outside-test" | "lint:set-test-timeout-outside-test" | "lint:filter-unknown-option" | "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">>;
610
627
  }, {
611
628
  enabled: boolean;
612
- 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" | "lint:skip-outside-test" | "lint:set-test-timeout-outside-test" | "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">>;
629
+ 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" | "lint:skip-outside-test" | "lint:set-test-timeout-outside-test" | "lint:filter-unknown-option" | "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">>;
613
630
  }>;
614
631
  mcp: z.ZodObject<{
615
632
  transport: z.ZodLiteral<"stdio">;
@@ -738,7 +755,7 @@ declare const UnotestConfigSchema: z.ZodEffects<z.ZodObject<{
738
755
  defaultNavigationTimeoutMs: number;
739
756
  linter: {
740
757
  enabled: boolean;
741
- 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" | "lint:skip-outside-test" | "lint:set-test-timeout-outside-test" | "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">>;
758
+ 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" | "lint:skip-outside-test" | "lint:set-test-timeout-outside-test" | "lint:filter-unknown-option" | "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">>;
742
759
  };
743
760
  mcp: {
744
761
  transport: "stdio";
@@ -769,6 +786,7 @@ declare const UnotestConfigSchema: z.ZodEffects<z.ZodObject<{
769
786
  reuseExistingServer: boolean;
770
787
  timeoutMs: number;
771
788
  }[] | undefined;
789
+ generate?: string | undefined;
772
790
  channel?: "chromium" | "chrome" | "msedge" | "chrome-beta" | null | undefined;
773
791
  viewport?: {
774
792
  width?: number | undefined;
@@ -817,7 +835,7 @@ declare const UnotestConfigSchema: z.ZodEffects<z.ZodObject<{
817
835
  defaultNavigationTimeoutMs: number;
818
836
  linter: {
819
837
  enabled: boolean;
820
- 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" | "lint:skip-outside-test" | "lint:set-test-timeout-outside-test" | "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">>;
838
+ 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" | "lint:skip-outside-test" | "lint:set-test-timeout-outside-test" | "lint:filter-unknown-option" | "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">>;
821
839
  };
822
840
  mcp: {
823
841
  transport: "stdio";
@@ -847,6 +865,7 @@ declare const UnotestConfigSchema: z.ZodEffects<z.ZodObject<{
847
865
  reuseExistingServer?: boolean | undefined;
848
866
  timeoutMs?: number | undefined;
849
867
  }[] | undefined;
868
+ generate?: string | undefined;
850
869
  channel?: "chromium" | "chrome" | "msedge" | "chrome-beta" | "bundled" | null | undefined;
851
870
  viewport?: {
852
871
  width?: number | undefined;
@@ -901,7 +920,7 @@ declare const UnotestConfigSchema: z.ZodEffects<z.ZodObject<{
901
920
  defaultNavigationTimeoutMs: number;
902
921
  linter: {
903
922
  enabled: boolean;
904
- 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" | "lint:skip-outside-test" | "lint:set-test-timeout-outside-test" | "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">>;
923
+ 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" | "lint:skip-outside-test" | "lint:set-test-timeout-outside-test" | "lint:filter-unknown-option" | "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">>;
905
924
  };
906
925
  mcp: {
907
926
  transport: "stdio";
@@ -932,6 +951,7 @@ declare const UnotestConfigSchema: z.ZodEffects<z.ZodObject<{
932
951
  reuseExistingServer: boolean;
933
952
  timeoutMs: number;
934
953
  }[] | undefined;
954
+ generate?: string | undefined;
935
955
  channel?: "chromium" | "chrome" | "msedge" | "chrome-beta" | null | undefined;
936
956
  viewport?: {
937
957
  width?: number | undefined;
@@ -980,7 +1000,7 @@ declare const UnotestConfigSchema: z.ZodEffects<z.ZodObject<{
980
1000
  defaultNavigationTimeoutMs: number;
981
1001
  linter: {
982
1002
  enabled: boolean;
983
- 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" | "lint:skip-outside-test" | "lint:set-test-timeout-outside-test" | "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">>;
1003
+ 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" | "lint:skip-outside-test" | "lint:set-test-timeout-outside-test" | "lint:filter-unknown-option" | "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">>;
984
1004
  };
985
1005
  mcp: {
986
1006
  transport: "stdio";
@@ -1010,6 +1030,7 @@ declare const UnotestConfigSchema: z.ZodEffects<z.ZodObject<{
1010
1030
  reuseExistingServer?: boolean | undefined;
1011
1031
  timeoutMs?: number | undefined;
1012
1032
  }[] | undefined;
1033
+ generate?: string | undefined;
1013
1034
  channel?: "chromium" | "chrome" | "msedge" | "chrome-beta" | "bundled" | null | undefined;
1014
1035
  viewport?: {
1015
1036
  width?: number | undefined;
@@ -1039,4 +1060,4 @@ type UnotestConfig = z.infer<typeof UnotestConfigSchema>;
1039
1060
  type UnotestConfigInput = z.input<typeof UnotestConfigSchema>;
1040
1061
  declare const DEFAULT_CONFIG: UnotestConfig;
1041
1062
 
1042
- export { BROWSER_CHANNEL_WORDS, type Browser, type BrowserChannel, BrowserChannelSchema, BrowserSchema, DEFAULT_CONFIG, type DialogPolicy, DialogPolicySchema, type EnvConfig, EnvConfigSchema, type FailureBundleConfig, FailureBundleConfigSchema, type LinterConfig, LinterConfigSchema, type LinterRuleId, LinterRuleIdSchema, type LinterSeverity, LinterSeveritySchema, type McpConfig, McpConfigSchema, type QueueConfig, QueueConfigSchema, type RetryConfig, RetryConfigSchema, type RetryReason, RetryReasonSchema, SYSTEM_BROWSER_CHANNELS, type SandboxConfig, SandboxConfigSchema, type ScheduleConfig, ScheduleSchema, type UnotestConfig, type UnotestConfigInput, UnotestConfigSchema, type Viewport, ViewportSchema, type WebServerConfig, WebServerConfigSchema, WebServersConfigSchema };
1063
+ export { BROWSER_CHANNEL_WORDS, type Browser, type BrowserChannel, BrowserChannelSchema, BrowserSchema, DEFAULT_CONFIG, type DialogPolicy, DialogPolicySchema, type EnvConfig, EnvConfigSchema, type FailureBundleConfig, FailureBundleConfigSchema, type LinterConfig, LinterConfigSchema, type LinterRuleId, LinterRuleIdSchema, type LinterSeverity, LinterSeveritySchema, type McpConfig, McpConfigSchema, type QueueConfig, QueueConfigSchema, type RetryConfig, RetryConfigSchema, type RetryReason, RetryReasonSchema, SYSTEM_BROWSER_CHANNELS, type SandboxConfig, SandboxConfigSchema, type ScheduleConfig, ScheduleSchema, type UnotestConfig, type UnotestConfigInput, UnotestConfigSchema, type Viewport, ViewportSchema, type WebServerConfig, WebServerConfigSchema, WebServersConfigSchema, viewportDeviceConflict };