@unotest/web 0.45.0 → 0.46.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/CHANGELOG.md CHANGED
@@ -1,5 +1,39 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.46.0] - 2026-10-02
4
+
5
+ ### Minor Changes
6
+
7
+ - 5a047cc: Assertions take a regex and can wait for the opposite. `assertText(loc, expected, {not})` accepts a `/regex/` as well as a string, and `{not: true}` passes once the text stops matching, for a spinner label that has to go away. New `assertTexts(loc, [...])` checks a whole list in order, with the element count, and `assertAttribute(loc, name, expected, {not})` waits for an attribute value such as `aria-expanded`. A string still compares whole by default, `{exact: false}` for a substring, so existing scenarios behave as before. The "From @playwright/test" page maps `toHaveText`, `not.toHaveText` and `toHaveAttribute`.
8
+ - b6b4558: New DSL function `assertSchema(value, schema)`: checks a value against a JSON Schema (draft 2020-12), such as one your app generates from its zod schemas with `z.toJSONSchema` and the scenario reads with `readJson`. When the value breaks the schema, the step fails naming every broken field by path with what is wrong there (`/tags/1: must be string`), not only the first one. A `$ref` inside the schema (`#/$defs/...`) is followed; a `$ref` to another file is refused, as is a schema that declares another draft or is itself invalid. `format` is not checked. `@unotest/web` now depends on `ajv`.
9
+ - d9bb5af: The page console in scenarios: `consoleEntries({level?, since?})` returns what the active page logged as `[{level, text, url}]`, `pageErrors({since?})` the errors it threw that nothing caught as `[{message, stack}]`, and `assertNoConsoleErrors({includePageErrors?, since?})` fails on either, quoting the first three. Pages are recorded from the moment they open, extension pages (`chrome-extension://`) included, so a scenario acts first and asks afterwards; `since` takes a time from `nowMs()`. `UNOTEST_CONSOLE` still decides alone whether a run writes `console.json` into its artifacts and failure bundles.
10
+ - 8241cfb: New DSL function `env(name)`: returns an environment value as a string, or `null` when the name is not set, so a test can decide on it (skip a live-only check, pick a target) instead of failing on a missing variable. It reads what a bare `UPPER_SNAKE` name reads: the environment the run started in, then `unotest/.env`. A secret, from `unotest/.secrets` or injected by a box, is refused: write its bare name where the value is typed, and it stays masked.
11
+ - 25702f0: A new browser profile for every test: `launch: { profile: 'per-test' }` starts each `test_*` in a new browser on a new, empty profile directory, so an extension loads again, its service worker starts from scratch and nothing in `chrome.storage`, `localStorage` or the tabs carries over; with one test per file, a suite of N files runs on N profiles. The directory is removed after a passing test and kept in the run folder after a failing one; the run log names it. Browser extensions and the service-worker functions (`waitForServiceWorker`, `evaluateInServiceWorker`, `extensionId`) now work on per-test profiles too, not only with `launch.userDataDir`, and `mockRoute` answers the worker's own requests in every test. It cannot be combined with `launch.userDataDir`, and it is refused with `storageState`, with more than one worker and on a box; `UNOTEST_USER_DATA_DIR` overrides it with one shared profile. An agent joined to a debug run follows it into each test's browser.
12
+ - 8241cfb: New DSL function `skip(reason, condition?)`: a test ends itself as skipped when it does not apply here, for example `skip('no live API configured', env('LIVE_API') == null)`. Without a condition it always skips; with a false one the test goes on. `skip` is a test guard: it stands in the `test_*` body next to the steps, like `step(...)` and `teardown(...)`, and the validator accepts it there. The run reports skipped with the reason, neither passed nor failed: the CLI prints `○ test: … skipped: <reason>` and counts `1 skipped`, a collection counts `skipped=N(test=N)`, and `run_test` answers `outcome: "skipped"` with `skipReason`. The viewer does not know skipped yet: it shows such a run as passed, without the reason. The exit code stays 0. Nothing after the call runs; teardown blocks registered before it still play. A skip after a failed soft step, or a skipped test whose teardown fails, is a failed test. A new lint rule, `lint:skip-outside-test` (error), reports `skip()` anywhere else: inside a step or a block, in a helper or a `flow_*`. Validator contracts gain an optional `testGuard` flag: a function marked with it may stand as a direct child of a `test_*` body. On the wire a skipped run is `completed` with a new optional `skipReason` field on `run:finished` and in the runtime state, so readers that predate it still see a finished run.
13
+ - 0fa0343: A test's time budget, set where you expect it. The config key `testTimeoutMs` (default 300000) is the deadline of one test: an action still running when it runs out is cut there, the test fails naming `testTimeoutMs`, and its teardown blocks still play. `30000` gives every test 30 s. The new DSL function `setTestTimeout(ms)`, the counterpart of Playwright's `test.setTimeout`, gives one test its own budget, longer or shorter, counted from the test's start; the error then names `setTestTimeout(ms)`. `UNOTEST_MAX_DURATION_MS` still overrides the config for a run and keeps its name in the error. A new lint rule, `lint:set-test-timeout-outside-test` (error), reports `setTestTimeout()` outside a `test_*` body. Running out of time fails the test; Stop in the viewer still aborts the run.
14
+ - 3f18c02: Actions (`click`, `doubleClick`, `fill`, `press`, `check`, `uncheck`, `hover`, `selectOption`, `dragAndDrop`), navigation (`goto`, `reload`, `goBack`, `goForward`, `waitForUrl`, `waitForNavigation`) waits (`waitFor`, `waitForText`, `waitForCount`) and assertions (`assertText`, `assertTexts`, `assertAttribute`, `assertVisible`, `assertHidden`, `assertValue`, `assertCount`, `assertUrl`, `assertNeverAppears`) now refuse an option they do not take, naming the ones they do: `click(btn, {timout: 5000})` used to wait the default 3 s without a word.
15
+
16
+ **Migration:** a misspelled option in these functions now fails naming the supported ones — fix the spelling the message points at. `noWaitAfter` on `click` and `fill` never had an effect; it is now refused, so remove it.
17
+
18
+ - fefe1e7: The file oracles now read only where `readFile` does. `waitForFile`, `assertNoFile`, `waitForJsonLine`, `readJsonLine`, `assertNoJsonLine`, `waitForFileCount` and `assertFileCount` take a path relative to `sandbox.shellCwd`, or an absolute one inside the project, the run's downloads or a directory listed in the new `sandbox.readRoots`. Anything else (`../` out of them, an absolute path elsewhere, a symlink that leads out) fails the step with a scenario error naming the path and the directories it left. The check repeats on every read of a wait, so a file that appears as a symlink out is refused when it appears.
19
+
20
+ `readFile` and `readFileInfo` are stricter too: a file that does not exist yet is judged by the nearest directory above it that does, so a path under a directory symlinked out of the project is refused instead of reported missing. They also read from `sandbox.readRoots`.
21
+
22
+ Migration: a scenario that waits on or reads a file by an absolute path outside the project, such as a background listener's log in `/tmp`, now fails with that error. List the directory in the config: `sandbox: { readRoots: ['/tmp/my-listener'] }`.
23
+
24
+ - bb538e7: `webServer` in the config takes one server or a list — a front and the fake backends it talks to, each with its own readiness `url`. A list starts all at once, the first scenario runs once every `url` answers, and the servers stop in reverse order; if one does not come up, the ones that did are stopped and the error names each that failed. Two entries on one `url` are refused. `app-server` prints one event per server. Type change: `UnotestConfig.webServer` is now always a list once the config is parsed (an object in the file still works).
25
+
26
+ ### Patch Changes
27
+
28
+ - cd4664d: Stopping a `webServer` the run started now waits until the server itself has exited, not only the shell that launched it. On Linux the shell exits at once on the stop signal while the server is still shutting down, so the next step could find its port still taken or its shutdown output unwritten.
29
+ - Updated dependencies [8241cfb]
30
+ - Updated dependencies [0fa0343]
31
+ - @unotest/dsl@0.46.0
32
+ - @unotest/protocol@0.46.0
33
+ - @unotest/viewer@0.46.0
34
+ - @unotest/core@0.46.0
35
+ - @unotest/grounder-client@0.46.0
36
+
3
37
  ## [0.45.0] - 2026-10-01
4
38
 
5
39
  ### Minor Changes
@@ -1490,7 +1524,7 @@ picker on` fetches this project's own access and writes the three grounder
1490
1524
 
1491
1525
  That screen named one command:
1492
1526
 
1493
- npx @unotest/web bundle push --run --env ENVIRONMENT
1527
+ npx @unotest/web bundle push --run --env ENVIRONMENT
1494
1528
 
1495
1529
  In a project that has just been created, the command refuses with `no box
1496
1530
  address` before it does anything: the project holds neither the box's address
@@ -1499,8 +1533,8 @@ address` before it does anything: the project holds neither the box's address
1499
1533
 
1500
1534
  The screen now names both steps, in the order they have to happen:
1501
1535
 
1502
- npx @unotest/web box access larch
1503
- npx @unotest/web bundle push --run --env ENVIRONMENT
1536
+ npx @unotest/web box access larch
1537
+ npx @unotest/web bundle push --run --env ENVIRONMENT
1504
1538
 
1505
1539
  The box's own name is filled in when its licence carries one. `box access`
1506
1540
  with no name refuses, and its refusal offers to create a box — to somebody who
package/README.md CHANGED
@@ -38,6 +38,8 @@ Testing a browser extension? `launch` in `unotest.config` starts the browser
38
38
  with a profile (`userDataDir`) and extra arguments (`args`, e.g.
39
39
  `--load-extension=./ext`; Chromium only). On a profile every scenario shares
40
40
  one browser context — no isolation between them, and the run log says so.
41
+ `profile: 'per-test'` instead gives every test a new browser on a new,
42
+ empty profile: a suite of N files runs on N profiles.
41
43
 
42
44
  ## 2. Open the viewer
43
45
 
@@ -109,19 +109,19 @@ declare const FailureBundleConfigSchema: z.ZodObject<{
109
109
  type FailureBundleConfig = z.infer<typeof FailureBundleConfigSchema>;
110
110
  declare const DialogPolicySchema: z.ZodEnum<["accept", "dismiss", "manual"]>;
111
111
  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", "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", "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"]>;
113
113
  type LinterRuleId = z.infer<typeof LinterRuleIdSchema>;
114
114
  declare const LinterSeveritySchema: z.ZodEnum<["off", "warn", "error"]>;
115
115
  type LinterSeverity = z.infer<typeof LinterSeveritySchema>;
116
116
  declare const LinterConfigSchema: z.ZodObject<{
117
117
  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", "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", "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"]>>;
119
119
  }, "strict", z.ZodTypeAny, {
120
120
  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" | "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" | "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">>;
122
122
  }, {
123
123
  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" | "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" | "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">>;
125
125
  }>;
126
126
  type LinterConfig = z.infer<typeof LinterConfigSchema>;
127
127
  declare const QueueConfigSchema: z.ZodObject<{
@@ -201,6 +201,11 @@ declare const SandboxConfigSchema: z.ZodObject<{
201
201
  * against it and may not escape it. Defaults to the project root
202
202
  * (the runner's cwd). */
203
203
  uploadDir: z.ZodOptional<z.ZodString>;
204
+ /** More directories a scenario may read files from, beside the project
205
+ * and the run's downloads: `readFile`, `waitForFile` and the other file
206
+ * oracles. For a file a background process writes outside the project.
207
+ * Relative entries resolve against `shellCwd`. */
208
+ readRoots: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
204
209
  }, "strict", z.ZodTypeAny, {
205
210
  shellCwd?: string | undefined;
206
211
  shellTimeoutMs?: number | undefined;
@@ -208,6 +213,7 @@ declare const SandboxConfigSchema: z.ZodObject<{
208
213
  database?: string | undefined;
209
214
  apiBaseUrl?: string | undefined;
210
215
  uploadDir?: string | undefined;
216
+ readRoots?: string[] | undefined;
211
217
  }, {
212
218
  shellCwd?: string | undefined;
213
219
  shellTimeoutMs?: number | undefined;
@@ -215,6 +221,7 @@ declare const SandboxConfigSchema: z.ZodObject<{
215
221
  database?: string | undefined;
216
222
  apiBaseUrl?: string | undefined;
217
223
  uploadDir?: string | undefined;
224
+ readRoots?: string[] | undefined;
218
225
  }>;
219
226
  type SandboxConfig = z.infer<typeof SandboxConfigSchema>;
220
227
  declare const WebServerConfigSchema: z.ZodObject<{
@@ -245,6 +252,93 @@ declare const WebServerConfigSchema: z.ZodObject<{
245
252
  timeoutMs?: number | undefined;
246
253
  }>;
247
254
  type WebServerConfig = z.infer<typeof WebServerConfigSchema>;
255
+ /** `webServer`: one server, or several (a front and its fake backends),
256
+ * each with its own readiness URL. Always a list once parsed, so no
257
+ * consumer asks which form it got. Two entries on one URL would probe
258
+ * the same server twice and start neither for sure — refused. */
259
+ declare const WebServersConfigSchema: z.ZodEffects<z.ZodEffects<z.ZodUnion<[z.ZodObject<{
260
+ /** Shell command that starts the app under test (`npm run dev`,
261
+ * `python manage.py runserver`, …). Runs in the user's shell from the
262
+ * project root. */
263
+ command: z.ZodString;
264
+ /** URL probed for readiness — any HTTP response counts as up. Usually
265
+ * equals `baseUrl`; declared explicitly because a dev server may
266
+ * answer on a different path/port than the tests navigate to. */
267
+ url: z.ZodString;
268
+ /** When the URL is already up, reuse that server instead of spawning a
269
+ * second one (default true — covers a dev server the user or the
270
+ * viewer already started). `false` makes an occupied URL an error,
271
+ * Playwright-style for CI strictness. */
272
+ reuseExistingServer: z.ZodDefault<z.ZodBoolean>;
273
+ /** How long to wait for the URL to come up after spawning. */
274
+ timeoutMs: z.ZodDefault<z.ZodNumber>;
275
+ }, "strict", z.ZodTypeAny, {
276
+ command: string;
277
+ url: string;
278
+ reuseExistingServer: boolean;
279
+ timeoutMs: number;
280
+ }, {
281
+ command: string;
282
+ url: string;
283
+ reuseExistingServer?: boolean | undefined;
284
+ timeoutMs?: number | undefined;
285
+ }>, z.ZodArray<z.ZodObject<{
286
+ /** Shell command that starts the app under test (`npm run dev`,
287
+ * `python manage.py runserver`, …). Runs in the user's shell from the
288
+ * project root. */
289
+ command: z.ZodString;
290
+ /** URL probed for readiness — any HTTP response counts as up. Usually
291
+ * equals `baseUrl`; declared explicitly because a dev server may
292
+ * answer on a different path/port than the tests navigate to. */
293
+ url: z.ZodString;
294
+ /** When the URL is already up, reuse that server instead of spawning a
295
+ * second one (default true — covers a dev server the user or the
296
+ * viewer already started). `false` makes an occupied URL an error,
297
+ * Playwright-style for CI strictness. */
298
+ reuseExistingServer: z.ZodDefault<z.ZodBoolean>;
299
+ /** How long to wait for the URL to come up after spawning. */
300
+ timeoutMs: z.ZodDefault<z.ZodNumber>;
301
+ }, "strict", z.ZodTypeAny, {
302
+ command: string;
303
+ url: string;
304
+ reuseExistingServer: boolean;
305
+ timeoutMs: number;
306
+ }, {
307
+ command: string;
308
+ url: string;
309
+ reuseExistingServer?: boolean | undefined;
310
+ timeoutMs?: number | undefined;
311
+ }>, "many">]>, {
312
+ command: string;
313
+ url: string;
314
+ reuseExistingServer: boolean;
315
+ timeoutMs: number;
316
+ }[], {
317
+ command: string;
318
+ url: string;
319
+ reuseExistingServer?: boolean | undefined;
320
+ timeoutMs?: number | undefined;
321
+ } | {
322
+ command: string;
323
+ url: string;
324
+ reuseExistingServer?: boolean | undefined;
325
+ timeoutMs?: number | undefined;
326
+ }[]>, {
327
+ command: string;
328
+ url: string;
329
+ reuseExistingServer: boolean;
330
+ timeoutMs: number;
331
+ }[], {
332
+ command: string;
333
+ url: string;
334
+ reuseExistingServer?: boolean | undefined;
335
+ timeoutMs?: number | undefined;
336
+ } | {
337
+ command: string;
338
+ url: string;
339
+ reuseExistingServer?: boolean | undefined;
340
+ timeoutMs?: number | undefined;
341
+ }[]>;
248
342
  declare const EnvConfigSchema: z.ZodObject<{
249
343
  /** Variable names whose base `.env` value is deliberately shared by
250
344
  * every environment. Only names that become a HOST are checked, so
@@ -262,7 +356,7 @@ declare const UnotestConfigSchema: z.ZodEffects<z.ZodObject<{
262
356
  * is expected to be running already (current behaviour). When set, the
263
357
  * CLI ensures it per run; `npx @unotest/web app-server` / the viewer
264
358
  * hold a long-lived instance and runs then reuse it. */
265
- webServer: z.ZodOptional<z.ZodObject<{
359
+ webServer: z.ZodOptional<z.ZodEffects<z.ZodEffects<z.ZodUnion<[z.ZodObject<{
266
360
  /** Shell command that starts the app under test (`npm run dev`,
267
361
  * `python manage.py runserver`, …). Runs in the user's shell from the
268
362
  * project root. */
@@ -288,7 +382,63 @@ declare const UnotestConfigSchema: z.ZodEffects<z.ZodObject<{
288
382
  url: string;
289
383
  reuseExistingServer?: boolean | undefined;
290
384
  timeoutMs?: number | undefined;
291
- }>>;
385
+ }>, z.ZodArray<z.ZodObject<{
386
+ /** Shell command that starts the app under test (`npm run dev`,
387
+ * `python manage.py runserver`, …). Runs in the user's shell from the
388
+ * project root. */
389
+ command: z.ZodString;
390
+ /** URL probed for readiness — any HTTP response counts as up. Usually
391
+ * equals `baseUrl`; declared explicitly because a dev server may
392
+ * answer on a different path/port than the tests navigate to. */
393
+ url: z.ZodString;
394
+ /** When the URL is already up, reuse that server instead of spawning a
395
+ * second one (default true — covers a dev server the user or the
396
+ * viewer already started). `false` makes an occupied URL an error,
397
+ * Playwright-style for CI strictness. */
398
+ reuseExistingServer: z.ZodDefault<z.ZodBoolean>;
399
+ /** How long to wait for the URL to come up after spawning. */
400
+ timeoutMs: z.ZodDefault<z.ZodNumber>;
401
+ }, "strict", z.ZodTypeAny, {
402
+ command: string;
403
+ url: string;
404
+ reuseExistingServer: boolean;
405
+ timeoutMs: number;
406
+ }, {
407
+ command: string;
408
+ url: string;
409
+ reuseExistingServer?: boolean | undefined;
410
+ timeoutMs?: number | undefined;
411
+ }>, "many">]>, {
412
+ command: string;
413
+ url: string;
414
+ reuseExistingServer: boolean;
415
+ timeoutMs: number;
416
+ }[], {
417
+ command: string;
418
+ url: string;
419
+ reuseExistingServer?: boolean | undefined;
420
+ timeoutMs?: number | undefined;
421
+ } | {
422
+ command: string;
423
+ url: string;
424
+ reuseExistingServer?: boolean | undefined;
425
+ timeoutMs?: number | undefined;
426
+ }[]>, {
427
+ command: string;
428
+ url: string;
429
+ reuseExistingServer: boolean;
430
+ timeoutMs: number;
431
+ }[], {
432
+ command: string;
433
+ url: string;
434
+ reuseExistingServer?: boolean | undefined;
435
+ timeoutMs?: number | undefined;
436
+ } | {
437
+ command: string;
438
+ url: string;
439
+ reuseExistingServer?: boolean | undefined;
440
+ timeoutMs?: number | undefined;
441
+ }[]>>;
292
442
  browsers: z.ZodArray<z.ZodEnum<["chromium", "firefox", "webkit"]>, "many">;
293
443
  channel: z.ZodOptional<z.ZodUnion<[z.ZodEnum<["chrome", "msedge", "chrome-beta", "chromium"]>, z.ZodEffects<z.ZodLiteral<"bundled">, null, "bundled">, z.ZodNull]>>;
294
444
  viewport: z.ZodOptional<z.ZodNullable<z.ZodObject<{
@@ -329,12 +479,15 @@ declare const UnotestConfigSchema: z.ZodEffects<z.ZodObject<{
329
479
  * context per scenario. */
330
480
  launch: z.ZodOptional<z.ZodObject<{
331
481
  userDataDir: z.ZodOptional<z.ZodString>;
482
+ profile: z.ZodOptional<z.ZodLiteral<"per-test">>;
332
483
  args: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
333
484
  }, "strict", z.ZodTypeAny, {
334
485
  userDataDir?: string | undefined;
486
+ profile?: "per-test" | undefined;
335
487
  args?: string[] | undefined;
336
488
  }, {
337
489
  userDataDir?: string | undefined;
490
+ profile?: "per-test" | undefined;
338
491
  args?: string[] | undefined;
339
492
  }>>;
340
493
  retry: z.ZodObject<{
@@ -432,6 +585,11 @@ declare const UnotestConfigSchema: z.ZodEffects<z.ZodObject<{
432
585
  * Unset → 60000. A pass cut by it leaves the rest of the stand as the
433
586
  * test set it, and the run result says which blocks did not finish. */
434
587
  teardownTimeoutMs: z.ZodOptional<z.ZodNumber>;
588
+ /** Wall-clock budget of one test, a deadline: an action still running
589
+ * when it runs out is cut there and the test fails, its teardown blocks
590
+ * still play. Unset → 300000. `UNOTEST_MAX_DURATION_MS` overrides it for
591
+ * a run; `setTestTimeout(ms)` inside a test overrides both for that test. */
592
+ testTimeoutMs: z.ZodOptional<z.ZodNumber>;
435
593
  /** Cross-environment declarations (`.env.<name>` overlays). */
436
594
  env: z.ZodObject<{
437
595
  /** Variable names whose base `.env` value is deliberately shared by
@@ -445,13 +603,13 @@ declare const UnotestConfigSchema: z.ZodEffects<z.ZodObject<{
445
603
  }>;
446
604
  linter: z.ZodObject<{
447
605
  enabled: z.ZodBoolean;
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"]>>;
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"]>>;
449
607
  }, "strict", z.ZodTypeAny, {
450
608
  enabled: boolean;
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">>;
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">>;
452
610
  }, {
453
611
  enabled: boolean;
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">>;
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">>;
455
613
  }>;
456
614
  mcp: z.ZodObject<{
457
615
  transport: z.ZodLiteral<"stdio">;
@@ -528,6 +686,11 @@ declare const UnotestConfigSchema: z.ZodEffects<z.ZodObject<{
528
686
  * against it and may not escape it. Defaults to the project root
529
687
  * (the runner's cwd). */
530
688
  uploadDir: z.ZodOptional<z.ZodString>;
689
+ /** More directories a scenario may read files from, beside the project
690
+ * and the run's downloads: `readFile`, `waitForFile` and the other file
691
+ * oracles. For a file a background process writes outside the project.
692
+ * Relative entries resolve against `shellCwd`. */
693
+ readRoots: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
531
694
  }, "strict", z.ZodTypeAny, {
532
695
  shellCwd?: string | undefined;
533
696
  shellTimeoutMs?: number | undefined;
@@ -535,6 +698,7 @@ declare const UnotestConfigSchema: z.ZodEffects<z.ZodObject<{
535
698
  database?: string | undefined;
536
699
  apiBaseUrl?: string | undefined;
537
700
  uploadDir?: string | undefined;
701
+ readRoots?: string[] | undefined;
538
702
  }, {
539
703
  shellCwd?: string | undefined;
540
704
  shellTimeoutMs?: number | undefined;
@@ -542,6 +706,7 @@ declare const UnotestConfigSchema: z.ZodEffects<z.ZodObject<{
542
706
  database?: string | undefined;
543
707
  apiBaseUrl?: string | undefined;
544
708
  uploadDir?: string | undefined;
709
+ readRoots?: string[] | undefined;
545
710
  }>;
546
711
  }, "strict", z.ZodTypeAny, {
547
712
  env: {
@@ -573,7 +738,7 @@ declare const UnotestConfigSchema: z.ZodEffects<z.ZodObject<{
573
738
  defaultNavigationTimeoutMs: number;
574
739
  linter: {
575
740
  enabled: boolean;
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">>;
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">>;
577
742
  };
578
743
  mcp: {
579
744
  transport: "stdio";
@@ -595,6 +760,7 @@ declare const UnotestConfigSchema: z.ZodEffects<z.ZodObject<{
595
760
  database?: string | undefined;
596
761
  apiBaseUrl?: string | undefined;
597
762
  uploadDir?: string | undefined;
763
+ readRoots?: string[] | undefined;
598
764
  };
599
765
  baseUrl?: string | undefined;
600
766
  webServer?: {
@@ -602,7 +768,7 @@ declare const UnotestConfigSchema: z.ZodEffects<z.ZodObject<{
602
768
  url: string;
603
769
  reuseExistingServer: boolean;
604
770
  timeoutMs: number;
605
- } | undefined;
771
+ }[] | undefined;
606
772
  channel?: "chromium" | "chrome" | "msedge" | "chrome-beta" | null | undefined;
607
773
  viewport?: {
608
774
  width?: number | undefined;
@@ -615,10 +781,12 @@ declare const UnotestConfigSchema: z.ZodEffects<z.ZodObject<{
615
781
  maxContexts?: number | undefined;
616
782
  launch?: {
617
783
  userDataDir?: string | undefined;
784
+ profile?: "per-test" | undefined;
618
785
  args?: string[] | undefined;
619
786
  } | undefined;
620
787
  storageState?: string | undefined;
621
788
  teardownTimeoutMs?: number | undefined;
789
+ testTimeoutMs?: number | undefined;
622
790
  }, {
623
791
  env: {
624
792
  shared?: string[] | undefined;
@@ -649,7 +817,7 @@ declare const UnotestConfigSchema: z.ZodEffects<z.ZodObject<{
649
817
  defaultNavigationTimeoutMs: number;
650
818
  linter: {
651
819
  enabled: boolean;
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">>;
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">>;
653
821
  };
654
822
  mcp: {
655
823
  transport: "stdio";
@@ -665,6 +833,7 @@ declare const UnotestConfigSchema: z.ZodEffects<z.ZodObject<{
665
833
  database?: string | undefined;
666
834
  apiBaseUrl?: string | undefined;
667
835
  uploadDir?: string | undefined;
836
+ readRoots?: string[] | undefined;
668
837
  };
669
838
  baseUrl?: string | undefined;
670
839
  webServer?: {
@@ -672,7 +841,12 @@ declare const UnotestConfigSchema: z.ZodEffects<z.ZodObject<{
672
841
  url: string;
673
842
  reuseExistingServer?: boolean | undefined;
674
843
  timeoutMs?: number | undefined;
675
- } | undefined;
844
+ } | {
845
+ command: string;
846
+ url: string;
847
+ reuseExistingServer?: boolean | undefined;
848
+ timeoutMs?: number | undefined;
849
+ }[] | undefined;
676
850
  channel?: "chromium" | "chrome" | "msedge" | "chrome-beta" | "bundled" | null | undefined;
677
851
  viewport?: {
678
852
  width?: number | undefined;
@@ -685,10 +859,12 @@ declare const UnotestConfigSchema: z.ZodEffects<z.ZodObject<{
685
859
  maxContexts?: number | undefined;
686
860
  launch?: {
687
861
  userDataDir?: string | undefined;
862
+ profile?: "per-test" | undefined;
688
863
  args?: string[] | undefined;
689
864
  } | undefined;
690
865
  storageState?: string | undefined;
691
866
  teardownTimeoutMs?: number | undefined;
867
+ testTimeoutMs?: number | undefined;
692
868
  schedules?: {
693
869
  collection: string;
694
870
  cron: string;
@@ -725,7 +901,7 @@ declare const UnotestConfigSchema: z.ZodEffects<z.ZodObject<{
725
901
  defaultNavigationTimeoutMs: number;
726
902
  linter: {
727
903
  enabled: boolean;
728
- 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">>;
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">>;
729
905
  };
730
906
  mcp: {
731
907
  transport: "stdio";
@@ -747,6 +923,7 @@ declare const UnotestConfigSchema: z.ZodEffects<z.ZodObject<{
747
923
  database?: string | undefined;
748
924
  apiBaseUrl?: string | undefined;
749
925
  uploadDir?: string | undefined;
926
+ readRoots?: string[] | undefined;
750
927
  };
751
928
  baseUrl?: string | undefined;
752
929
  webServer?: {
@@ -754,7 +931,7 @@ declare const UnotestConfigSchema: z.ZodEffects<z.ZodObject<{
754
931
  url: string;
755
932
  reuseExistingServer: boolean;
756
933
  timeoutMs: number;
757
- } | undefined;
934
+ }[] | undefined;
758
935
  channel?: "chromium" | "chrome" | "msedge" | "chrome-beta" | null | undefined;
759
936
  viewport?: {
760
937
  width?: number | undefined;
@@ -767,10 +944,12 @@ declare const UnotestConfigSchema: z.ZodEffects<z.ZodObject<{
767
944
  maxContexts?: number | undefined;
768
945
  launch?: {
769
946
  userDataDir?: string | undefined;
947
+ profile?: "per-test" | undefined;
770
948
  args?: string[] | undefined;
771
949
  } | undefined;
772
950
  storageState?: string | undefined;
773
951
  teardownTimeoutMs?: number | undefined;
952
+ testTimeoutMs?: number | undefined;
774
953
  }, {
775
954
  env: {
776
955
  shared?: string[] | undefined;
@@ -801,7 +980,7 @@ declare const UnotestConfigSchema: z.ZodEffects<z.ZodObject<{
801
980
  defaultNavigationTimeoutMs: number;
802
981
  linter: {
803
982
  enabled: boolean;
804
- 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">>;
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">>;
805
984
  };
806
985
  mcp: {
807
986
  transport: "stdio";
@@ -817,6 +996,7 @@ declare const UnotestConfigSchema: z.ZodEffects<z.ZodObject<{
817
996
  database?: string | undefined;
818
997
  apiBaseUrl?: string | undefined;
819
998
  uploadDir?: string | undefined;
999
+ readRoots?: string[] | undefined;
820
1000
  };
821
1001
  baseUrl?: string | undefined;
822
1002
  webServer?: {
@@ -824,7 +1004,12 @@ declare const UnotestConfigSchema: z.ZodEffects<z.ZodObject<{
824
1004
  url: string;
825
1005
  reuseExistingServer?: boolean | undefined;
826
1006
  timeoutMs?: number | undefined;
827
- } | undefined;
1007
+ } | {
1008
+ command: string;
1009
+ url: string;
1010
+ reuseExistingServer?: boolean | undefined;
1011
+ timeoutMs?: number | undefined;
1012
+ }[] | undefined;
828
1013
  channel?: "chromium" | "chrome" | "msedge" | "chrome-beta" | "bundled" | null | undefined;
829
1014
  viewport?: {
830
1015
  width?: number | undefined;
@@ -837,10 +1022,12 @@ declare const UnotestConfigSchema: z.ZodEffects<z.ZodObject<{
837
1022
  maxContexts?: number | undefined;
838
1023
  launch?: {
839
1024
  userDataDir?: string | undefined;
1025
+ profile?: "per-test" | undefined;
840
1026
  args?: string[] | undefined;
841
1027
  } | undefined;
842
1028
  storageState?: string | undefined;
843
1029
  teardownTimeoutMs?: number | undefined;
1030
+ testTimeoutMs?: number | undefined;
844
1031
  schedules?: {
845
1032
  collection: string;
846
1033
  cron: string;
@@ -852,4 +1039,4 @@ type UnotestConfig = z.infer<typeof UnotestConfigSchema>;
852
1039
  type UnotestConfigInput = z.input<typeof UnotestConfigSchema>;
853
1040
  declare const DEFAULT_CONFIG: UnotestConfig;
854
1041
 
855
- 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 };
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 };