@unotest/web 0.28.0 → 0.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/skills/write-e2e-test/SKILL.md +41 -2
- package/.claude/skills/write-e2e-test-ground/SKILL.md +44 -2
- package/CHANGELOG.md +197 -0
- package/README.md +14 -0
- package/dist/config/schema.d.ts +12 -12
- package/dist/config/schema.js +1 -1
- package/dist/driver/index.d.ts +3 -3
- package/dist/driver/index.js +1 -1
- package/dist/dsl/index.d.ts +2 -3
- package/dist/dsl/index.js +1 -1
- package/dist/dsl/web-dsl-language-service.js +1 -1
- package/dist/inspection/page-inject.d.ts +1 -1
- package/dist/{interfaces-SzdtOj1F.d.ts → interfaces-BluheUKs.d.ts} +9 -2
- package/dist/mcp/server.js +1 -1
- package/dist/runner/cli.js +1 -1
- package/dist/runner/init/chrome-probe.js +1 -1
- package/dist/runner/init.js +1 -1
- package/dist/runner/install-chromium.js +1 -1
- package/dist/runner/prepare-fix.js +1 -1
- package/dist/runner/scaffold-workspace.js +1 -1
- package/dist/runner/serve-fixture.js +1 -1
- package/dist/runner/web-runner-adapter.js +1 -1
- package/guides/dsl-reference.md +12 -0
- package/package.json +8 -8
- package/types/unotest-dsl.d.ts +3 -3
|
@@ -87,6 +87,15 @@ APP_BASE_URL) — same idea as run_test's per-call `env`.
|
|
|
87
87
|
`explore_start` opens the browser itself. Do NOT call `new_context`
|
|
88
88
|
first — it's for RESETTING the browser (Phase 5), not for starting.
|
|
89
89
|
|
|
90
|
+
**Variables in steps.** In a value field (`fill` value, `press` key,
|
|
91
|
+
`select_option` value, `goto` url) the bare NAME of a `.env`/`.secrets`
|
|
92
|
+
variable is a reference: `value: "PASSWORD"` types the secret live and
|
|
93
|
+
the saved test says `fill(loc, PASSWORD)`. In a locator's text or an
|
|
94
|
+
assertion's text a bare NAME is the literal it looks like — to reference
|
|
95
|
+
a variable there write `{{NAME}}` (`text: "{{GREETING}}"`); the reply
|
|
96
|
+
warns when a literal equals a variable's name. Never type a secret's
|
|
97
|
+
VALUE: it is recorded as the name anyway, and the reply says so.
|
|
98
|
+
|
|
90
99
|
### Phase 2 — first recorded step: navigate
|
|
91
100
|
|
|
92
101
|
```
|
|
@@ -418,7 +427,23 @@ Assertions:
|
|
|
418
427
|
|
|
419
428
|
State reads:
|
|
420
429
|
- `getTitle()`, `getUrl()`, `getAttribute(loc, name)`, `getInnerText(loc)`,
|
|
421
|
-
`getInputValue(loc)
|
|
430
|
+
`getInputValue(loc)`, `count(loc)`; `textContains(haystack, needle)`
|
|
431
|
+
for a substring probe on a string you already hold
|
|
432
|
+
(`assertTrue(textContains(out.stdout, 'ready'))`).
|
|
433
|
+
- A `textarea` / `input` VALUE is not page text: `waitForText` and
|
|
434
|
+
`assertText` never see it. Check it with `assertValue(loc, expected)`
|
|
435
|
+
— it polls until the timeout (5 s by default, `{timeout}` to raise
|
|
436
|
+
it), so a value that fills in late is fine. For a substring of the
|
|
437
|
+
value there is no polling assert: read it in a loop —
|
|
438
|
+
`for (i = 0; i < 20; i = i + 1) { if (textContains(getInputValue(loc), 'x')) { break; } pause(200); // lint-ok: polling a textarea value }`
|
|
439
|
+
— then assert.
|
|
440
|
+
- Only the vocabulary above is expression language. `!` is rejected by
|
|
441
|
+
the parser (`unotest-web lint`: `parse error — Invalid token`);
|
|
442
|
+
`JSON.stringify` / `Date.now()` / `indexOf` and other JS globals and
|
|
443
|
+
string methods are rejected by the validator with the replacement
|
|
444
|
+
named (`use json(value)`, `use nowMs()`, `use textContains(haystack,
|
|
445
|
+
needle)`). Express the check as an assertion or a locator
|
|
446
|
+
(`assertHidden`, `assertCount`, `.filter({hasText})`, `textContains`).
|
|
422
447
|
|
|
423
448
|
Sandbox primitives:
|
|
424
449
|
- `shell("cmd", "arg", …, options?)` — `execFile` style, no shell interpretation. Non-zero exit fails the step; pass `{allowNonZero: true}` to inspect `res.code` yourself. Wall-clock budget `{timeoutMs}` (default 120s).
|
|
@@ -433,8 +458,17 @@ Sandbox primitives:
|
|
|
433
458
|
JSON — there is no key-name magic.
|
|
434
459
|
|
|
435
460
|
Escape hatch:
|
|
436
|
-
- `evaluate(\`js body\`,
|
|
461
|
+
- `evaluate(\`js body\`, arg?)` — raw backticks, no `${}`. Linter
|
|
437
462
|
warns `lint:evaluate-discouraged`. Use only when nothing above fits.
|
|
463
|
+
The body gets at most ONE argument — extra arguments reach it like
|
|
464
|
+
this: none → nothing, exactly one → the value itself, **with two or
|
|
465
|
+
more extra arguments the body receives ONE array** — destructure it:
|
|
466
|
+
`evaluate('function(n){ return n * 2 }', 21)` and
|
|
467
|
+
`evaluate('([a, b]) => a + b', a, b)`. The linter's warning says so
|
|
468
|
+
when it sees two or more.
|
|
469
|
+
- Native dialogs (`confirm()` / `alert()` / `prompt()`) are accepted by
|
|
470
|
+
the runner itself (`dialogPolicy: "accept"` by default) — a "Remove"
|
|
471
|
+
that asks first needs no special step.
|
|
438
472
|
|
|
439
473
|
Time helpers: `nowMs()`, `today()`, `daysFromNow(n)`. Marker helper: `randomWord(len)` — random lowercase letters, digit-free. Debug helper: `json(value)` — serialize any value for an assert message: `assertTrue(res.status == 202, json(res.body))`.
|
|
440
474
|
|
|
@@ -635,6 +669,11 @@ Every locator must resolve to exactly one element. When you see
|
|
|
635
669
|
multi-matches.** Element order is brittle. Use `.filter({hasText:
|
|
636
670
|
'…'})` or `.filter({has: someLocator})`. Linter flags index-based
|
|
637
671
|
picking as `lint:disambig-by-index`.
|
|
672
|
+
- **Don't assert "the first row of the list / audit".** On a shared
|
|
673
|
+
stand another run (or a person) writes rows between your steps. Give
|
|
674
|
+
your own data a unique marker (`randomWord()` in a name or note),
|
|
675
|
+
find your row by it (`.filter({hasText: marker})`), and remove what
|
|
676
|
+
you created at the end of the scenario.
|
|
638
677
|
- **Don't reach for `locator(...)` with `>` combinators, hashed class
|
|
639
678
|
names, or `xpath=…`.** Stop and ask the user whether the app should
|
|
640
679
|
expose a `data-testid` or accessible name.
|
|
@@ -112,6 +112,15 @@ APP_BASE_URL) — same idea as run_test's per-call `env`.
|
|
|
112
112
|
`explore_start` opens the browser itself. Do NOT call `new_context`
|
|
113
113
|
first — it's for RESETTING the browser (Phase 5), not for starting.
|
|
114
114
|
|
|
115
|
+
**Variables in steps.** In a value field (`fill` value, `press` key,
|
|
116
|
+
`select_option` value, `goto` url) the bare NAME of a `.env`/`.secrets`
|
|
117
|
+
variable is a reference: `value: "PASSWORD"` types the secret live and
|
|
118
|
+
the saved test says `fill(loc, PASSWORD)`. In a locator's text or an
|
|
119
|
+
assertion's text a bare NAME is the literal it looks like — to reference
|
|
120
|
+
a variable there write `{{NAME}}` (`text: "{{GREETING}}"`); the reply
|
|
121
|
+
warns when a literal equals a variable's name. Never type a secret's
|
|
122
|
+
VALUE: it is recorded as the name anyway, and the reply says so.
|
|
123
|
+
|
|
115
124
|
### Phase 2 — record the known steps as ONE batch
|
|
116
125
|
|
|
117
126
|
**Batch-first default.** When the task brief already spells out the
|
|
@@ -549,7 +558,23 @@ Assertions:
|
|
|
549
558
|
|
|
550
559
|
State reads:
|
|
551
560
|
- `getTitle()`, `getUrl()`, `getAttribute(loc, name)`, `getInnerText(loc)`,
|
|
552
|
-
`getInputValue(loc)
|
|
561
|
+
`getInputValue(loc)`, `count(loc)`; `textContains(haystack, needle)`
|
|
562
|
+
for a substring probe on a string you already hold
|
|
563
|
+
(`assertTrue(textContains(out.stdout, 'ready'))`).
|
|
564
|
+
- A `textarea` / `input` VALUE is not page text: `waitForText` and
|
|
565
|
+
`assertText` never see it. Check it with `assertValue(loc, expected)`
|
|
566
|
+
— it polls until the timeout (5 s by default, `{timeout}` to raise
|
|
567
|
+
it), so a value that fills in late is fine. For a substring of the
|
|
568
|
+
value there is no polling assert: read it in a loop —
|
|
569
|
+
`for (i = 0; i < 20; i = i + 1) { if (textContains(getInputValue(loc), 'x')) { break; } pause(200); // lint-ok: polling a textarea value }`
|
|
570
|
+
— then assert.
|
|
571
|
+
- Only the vocabulary above is expression language. `!` is rejected by
|
|
572
|
+
the parser (`unotest-web lint`: `parse error — Invalid token`);
|
|
573
|
+
`JSON.stringify` / `Date.now()` / `indexOf` and other JS globals and
|
|
574
|
+
string methods are rejected by the validator with the replacement
|
|
575
|
+
named (`use json(value)`, `use nowMs()`, `use textContains(haystack,
|
|
576
|
+
needle)`). Express the check as an assertion or a locator
|
|
577
|
+
(`assertHidden`, `assertCount`, `.filter({hasText})`, `textContains`).
|
|
553
578
|
|
|
554
579
|
Sandbox primitives:
|
|
555
580
|
- `shell("cmd", "arg", …, options?)` — `execFile` style, no shell interpretation. Non-zero exit fails the step; pass `{allowNonZero: true}` to inspect `res.code` yourself. Wall-clock budget `{timeoutMs}` (default 120s).
|
|
@@ -564,8 +589,17 @@ Sandbox primitives:
|
|
|
564
589
|
JSON — there is no key-name magic.
|
|
565
590
|
|
|
566
591
|
Escape hatch:
|
|
567
|
-
- `evaluate(\`js body\`,
|
|
592
|
+
- `evaluate(\`js body\`, arg?)` — raw backticks, no `${}`. Linter
|
|
568
593
|
warns `lint:evaluate-discouraged`. Use only when nothing above fits.
|
|
594
|
+
The body gets at most ONE argument — extra arguments reach it like
|
|
595
|
+
this: none → nothing, exactly one → the value itself, **with two or
|
|
596
|
+
more extra arguments the body receives ONE array** — destructure it:
|
|
597
|
+
`evaluate('function(n){ return n * 2 }', 21)` and
|
|
598
|
+
`evaluate('([a, b]) => a + b', a, b)`. The linter's warning says so
|
|
599
|
+
when it sees two or more.
|
|
600
|
+
- Native dialogs (`confirm()` / `alert()` / `prompt()`) are accepted by
|
|
601
|
+
the runner itself (`dialogPolicy: "accept"` by default) — a "Remove"
|
|
602
|
+
that asks first needs no special step.
|
|
569
603
|
|
|
570
604
|
Time helpers: `nowMs()`, `today()`, `daysFromNow(n)`. Marker helper: `randomWord(len)` — random lowercase letters, digit-free. Debug helper: `json(value)` — serialize any value for an assert message: `assertTrue(res.status == 202, json(res.body))`.
|
|
571
605
|
|
|
@@ -711,6 +745,9 @@ no recording.
|
|
|
711
745
|
- **`StaleRefError`** — a ref from an earlier lookup is no longer in
|
|
712
746
|
the DOM. Intent locators don't go stale (grounded per call) — retry
|
|
713
747
|
with the intent step, or re-ground via `ground_element`.
|
|
748
|
+
- **Grounder unavailable** — `explore_start` or the first intent step
|
|
749
|
+
says so; intent locators are off for the session. Use
|
|
750
|
+
`find_element({role, name, near?})` and its ref instead.
|
|
714
751
|
- **`RefResolveError`** — element has no stable identifier (testId /
|
|
715
752
|
role+name / aria-label / placeholder / alt / title / text / href /
|
|
716
753
|
stable id / name attribute). Best fix: ask the app team to add
|
|
@@ -769,6 +806,11 @@ Every locator must resolve to exactly one element. When you see
|
|
|
769
806
|
multi-matches.** Element order is brittle. Use `.filter({hasText:
|
|
770
807
|
'…'})` or `.filter({has: someLocator})`. Linter flags index-based
|
|
771
808
|
picking as `lint:disambig-by-index`.
|
|
809
|
+
- **Don't assert "the first row of the list / audit".** On a shared
|
|
810
|
+
stand another run (or a person) writes rows between your steps. Give
|
|
811
|
+
your own data a unique marker (`randomWord()` in a name or note),
|
|
812
|
+
find your row by it (`.filter({hasText: marker})`), and remove what
|
|
813
|
+
you created at the end of the scenario.
|
|
772
814
|
- **Don't reach for `locator(...)` with `>` combinators, hashed class
|
|
773
815
|
names, or `xpath=…`.** Stop and ask the user whether the app should
|
|
774
816
|
expose a `data-testid` or accessible name.
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,202 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.30.0] - 2026-09-03
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 23de03c: On a hosted viewer (a box), the project and environment you are looking at
|
|
8
|
+
are now named and switched in the viewer's own tree header, in place of
|
|
9
|
+
the checkout directory it used to show there (`current`, which told
|
|
10
|
+
nobody anything). Previously the only way to another environment was
|
|
11
|
+
logging out and using the picker page. The chevrons appear on hover, like
|
|
12
|
+
the refresh button beside them, and only when something fronts the viewer
|
|
13
|
+
and offers it a list; a plain local viewer keeps the folder name it
|
|
14
|
+
always showed. Because
|
|
15
|
+
each environment on a box is its own viewer process, picking one is a full
|
|
16
|
+
page load into that viewer, and the menu shows which test bundle each
|
|
17
|
+
environment is running. The status bar's `.env.<name>` overlay switcher is
|
|
18
|
+
now labelled "env overlay", so the two senses of "environment" no longer
|
|
19
|
+
share a name. `@unotest/viewer/session` carries the two URLs this needs
|
|
20
|
+
(`environmentsUrl`, `switchEnvUrl`) and the `ViewerEnvOption` type, both
|
|
21
|
+
optional — a proxy that does not offer them gets the old behaviour.
|
|
22
|
+
|
|
23
|
+
### Patch Changes
|
|
24
|
+
|
|
25
|
+
- A run started in the first moments after the viewer boots is now discovered: the run watcher finishes its initial scan of the day directory before reporting itself started, instead of filing such a run as history.
|
|
26
|
+
- 3f5cab7: `bundle push` and `env push` no longer send you to "the box's
|
|
27
|
+
admin page" for a project token — there is no such page; tokens are
|
|
28
|
+
issued per project by the box's operator, and the refusal hints and the
|
|
29
|
+
missing-token message now say so.
|
|
30
|
+
- 3f5cab7: The vocabulary's description of `evaluate` now says how extra
|
|
31
|
+
arguments reach the body — none → nothing, exactly one → the value
|
|
32
|
+
itself, two or more → one array — matching the runtime, the linter's
|
|
33
|
+
hint and the authoring skill; it used to read as if they were positional
|
|
34
|
+
(`evaluate(js, ...args)`), and so did the generated DSL typings.
|
|
35
|
+
- 3f5cab7: A scenario that answers a native `confirm()` — a "Remove" button that
|
|
36
|
+
asks first, say — no longer fails when `run_test` auto-attaches the MCP
|
|
37
|
+
session to the run's browser, or after `attach_debug_session`. The
|
|
38
|
+
attaching client now applies the same dialog policy the run does
|
|
39
|
+
(`dialogPolicy`, accept by default) to the page it watches; before, it
|
|
40
|
+
listened for no dialogs at all, and Playwright dismisses every dialog on
|
|
41
|
+
behalf of a client that has no listener — so the second pair of eyes was
|
|
42
|
+
answering "no" before the run's own accept could land. The same scenario
|
|
43
|
+
passed with an exploration session open only because that session
|
|
44
|
+
happened to keep the run's browser to itself. Under `manual` the attached
|
|
45
|
+
client leaves the dialog to whoever handles it instead of dismissing it.
|
|
46
|
+
- 9f2e244: On a box, an administrator can now see a secret's value from the guard's
|
|
47
|
+
values page — the eye reveals it in the row, the copy button puts it on
|
|
48
|
+
the clipboard without showing it — and every look is a `secret.revealed`
|
|
49
|
+
entry in the box's audit trail, naming who, which secret and whether it
|
|
50
|
+
was shown or copied. The page is one table now (target, variables,
|
|
51
|
+
secrets), edited in place, with a JSON view that changes the whole
|
|
52
|
+
environment at once; a secret left as `"••••"` there is kept as it is.
|
|
53
|
+
The token door `env push` / `env set` talk to is unchanged: secrets stay
|
|
54
|
+
write-only on the wire.
|
|
55
|
+
|
|
56
|
+
`@unotest/protocol` carries the contract: `boxEnvSecretRevealPath` /
|
|
57
|
+
`boxEnvSecretsRevealPath` with their response parsers, the session-door
|
|
58
|
+
batch `BoxEnvValuesReplaceRequest` (`null` for a secret means "keep") and
|
|
59
|
+
`parseBoxEnvValuesReplaceRequest`, and the refusal code `unknown-secret`
|
|
60
|
+
for a reveal or a keep that names a secret the environment does not
|
|
61
|
+
hold. `isEnvVarName` now refuses `__proto__`: it is spelled like a
|
|
62
|
+
variable, but a plain object cannot hold it and the value was silently
|
|
63
|
+
lost. The CLI explains an `unknown-secret` refusal.
|
|
64
|
+
|
|
65
|
+
- 3f5cab7: `evaluate(js, …args)` has always handed the body its extra arguments in
|
|
66
|
+
one of three shapes — none, the single value, or ONE array for two or
|
|
67
|
+
more — while the authoring skill described them as positional. The
|
|
68
|
+
skill now states the rule with both spellings
|
|
69
|
+
(`evaluate('function(n){…}', 21)` / `evaluate('([a, b]) => …', a, b)`),
|
|
70
|
+
and the `lint:evaluate-discouraged` warning adds the same hint when it
|
|
71
|
+
sees two or more extra arguments; the runtime is unchanged. The skill
|
|
72
|
+
also gains the getters it left out (`count`, `getInputValue`,
|
|
73
|
+
`textContains`), the note that `waitForText` never sees a textarea's
|
|
74
|
+
value, the parser's refusal of `!` / `indexOf` / string methods, the
|
|
75
|
+
shared-stand rule against "the first row" (mark your own data, filter
|
|
76
|
+
by it, clean up), that native dialogs are accepted by the runner, and
|
|
77
|
+
what to do when the grounder is unavailable.
|
|
78
|
+
- 3f5cab7: While recording with `explore_step` / `explore_steps`, the bare NAME of a
|
|
79
|
+
scenario variable is substituted only where the step types a value into
|
|
80
|
+
the page — `fill`'s value, `press`'s key, `select_option`'s value,
|
|
81
|
+
`goto`'s url. In a locator's text or an assertion's expected text a bare
|
|
82
|
+
name is now the literal it looks like: `assertText(el, "BOX_LAB_PASSWORD")`
|
|
83
|
+
expects those letters, where before the live step silently waited for
|
|
84
|
+
the secret's value and the saved test said something else. To reference
|
|
85
|
+
a variable in a matcher, write `{{NAME}}`; the reply warns when a literal
|
|
86
|
+
happens to equal a variable's name. And a secret's value typed into a
|
|
87
|
+
value field is recorded as the secret's name (the reply says which), so
|
|
88
|
+
the value never lands in the test file.
|
|
89
|
+
- 3f5cab7: `run_test` no longer reports `spawn_failed` about a runner that is still
|
|
90
|
+
starting. Two runs launched together on a busy box took longer than the
|
|
91
|
+
fixed 10-second wait to write their first `runtime.json`, and the second
|
|
92
|
+
was declared dead while it was booting a browser. The wait now lasts as
|
|
93
|
+
long as the runner child is alive (up to 60 seconds), ends early when the
|
|
94
|
+
child joins the run queue, and — when the child really dies — says so with
|
|
95
|
+
its exit code and the last lines of its stderr, which the runner now
|
|
96
|
+
writes to `spawn.stderr.log` in its run directory.
|
|
97
|
+
|
|
98
|
+
`explore_start` now reports whether the grounder is usable for the
|
|
99
|
+
session: `grounder: {available: true}`, or `{available: false, reason,
|
|
100
|
+
hint}` when grounding is off, the backend is unreachable, or a required
|
|
101
|
+
model is not pulled (`"embeddinggemma" is not available there — pull it:
|
|
102
|
+
\`ollama pull embeddinggemma\``). An intent step in such a session fails
|
|
103
|
+
with the same words, not with the backend's raw error; the probe is one
|
|
104
|
+
short request per session.
|
|
105
|
+
|
|
106
|
+
Secret masking no longer rewrites the runner's own identifiers in
|
|
107
|
+
`runtime.json`: with `LOGIN=admin`, the scenario path `guard-admin.js`,
|
|
108
|
+
the test function `test_guard_admin` and the run id kept their names in
|
|
109
|
+
`inspect_runtime` replies instead of turning into `guard-‹secret:LOGIN›.js`.
|
|
110
|
+
Masking by value is unchanged everywhere else; the run id, scenario
|
|
111
|
+
path, test function and call-stack names are an explicit exception.
|
|
112
|
+
|
|
113
|
+
- 3f5cab7: `save_exploration_as_test` (and `generate_dsl_from_exploration`) now
|
|
114
|
+
write scoped locators — a `getByRole` / `getByText` / `locator(css)` step
|
|
115
|
+
under a parent, such as the "Reveal" button inside one table row — as
|
|
116
|
+
the chain the DSL already runs:
|
|
117
|
+
`locator('tr[data-row="A"]').getByRole('button', {name: 'Reveal', exact: true})`.
|
|
118
|
+
Before, any recorded step whose locator went below its root was skipped
|
|
119
|
+
with `NO_DSL_PRIMITIVE`, and the save was refused, although the step
|
|
120
|
+
had executed and the hand-written form worked. Every locator is now
|
|
121
|
+
rendered in the same method-chain spelling — narrowing steps too
|
|
122
|
+
(`getByRole('row').filter({hasText: 'Alice'}).first()` instead of
|
|
123
|
+
`first(filter(getByRole('row'), {hasText: 'Alice'}))`); both spellings
|
|
124
|
+
remain valid input, the runtime is unchanged.
|
|
125
|
+
- Updated dependencies [23de03c]
|
|
126
|
+
- Updated dependencies [9f2e244]
|
|
127
|
+
- @unotest/viewer@0.30.0
|
|
128
|
+
- @unotest/protocol@0.30.0
|
|
129
|
+
- @unotest/core@0.30.0
|
|
130
|
+
- @unotest/dsl@0.30.0
|
|
131
|
+
- @unotest/grounder-client@0.30.0
|
|
132
|
+
|
|
133
|
+
## [0.29.0] - 2026-09-02
|
|
134
|
+
|
|
135
|
+
### Minor Changes
|
|
136
|
+
|
|
137
|
+
- ab296ff: New `env push`, `env set` and `env rm` commands send an environment's
|
|
138
|
+
values to a box — the target, the variables and the secrets a suite runs
|
|
139
|
+
with there — from the same files a local run reads (`unotest/.env`,
|
|
140
|
+
`.env.<env>`, `.secrets`, `.secrets.<env>`), or from files named with
|
|
141
|
+
`--env-file` / `--secrets-file`. `.env*` become the box environment's
|
|
142
|
+
variables, `.secrets*` its secrets and `APP_BASE_URL` its target;
|
|
143
|
+
`UNOTEST_*` and empty values stay home and are listed as skipped. A push
|
|
144
|
+
replaces the box's layers and reports what it removed; `env set` reads
|
|
145
|
+
one value from stdin, never from the command line. Values never print.
|
|
146
|
+
The commands use the project token of `bundle push`, minted on the box
|
|
147
|
+
with the new `--values` scope — a token without it is refused, so a CI
|
|
148
|
+
token that pushes suites cannot rewrite an environment's credentials.
|
|
149
|
+
`@unotest/protocol` carries the wire (`box-values.ts`) and `TARGET_ENV_VAR`.
|
|
150
|
+
The viewer's messages about box-held variables now point at these
|
|
151
|
+
commands instead of the box's shell.
|
|
152
|
+
- 64108b8: The Variables panel of a viewer on a box now lists what the box injects
|
|
153
|
+
into every run of the environment — the target URL, the operator's
|
|
154
|
+
variables and the names of the environment's secrets — as read-only rows
|
|
155
|
+
with a `box` badge. It used to read `unotest/.env*`, which never travel
|
|
156
|
+
in a bundle, and show an empty panel next to a full run history; a
|
|
157
|
+
secret's value is still never sent, only its name. The box passes the
|
|
158
|
+
names through `UNOTEST_BOX_VARIABLE_NAMES` / `UNOTEST_BOX_SECRET_NAMES`
|
|
159
|
+
(constants in `@unotest/protocol`); writes to such a key are refused
|
|
160
|
+
with 409 and point at `boxd.config.mjs` / `boxd.mjs secret set`.
|
|
161
|
+
- 64108b8: `UNOTEST_BROWSER` and `UNOTEST_BROWSER_CHANNEL` now exist as per-run
|
|
162
|
+
overrides of `browsers[0]` and `channel` in `unotest.config.*`.
|
|
163
|
+
`UNOTEST_BROWSER` was shown in the CI manual and the `.env.example`
|
|
164
|
+
template, but nothing read it — a browser matrix ran Chromium three
|
|
165
|
+
times. A value outside `chromium | firefox | webkit` (or `chrome |
|
|
166
|
+
msedge | chrome-beta | bundled`) is an error, not a silent default. The
|
|
167
|
+
config travels with the suite in a bundle, so a `channel: "chrome"`
|
|
168
|
+
written on a laptop used to ask a box for a Google Chrome it does not
|
|
169
|
+
have; a box now sets `UNOTEST_BROWSER_CHANNEL=bundled` for every run and
|
|
170
|
+
the run log names each override. Constants: `BROWSER_ENV`,
|
|
171
|
+
`BROWSER_CHANNEL_ENV`, `BUNDLED_BROWSER_CHANNEL` in `@unotest/protocol`.
|
|
172
|
+
|
|
173
|
+
### Patch Changes
|
|
174
|
+
|
|
175
|
+
- 64108b8: `init` without `--force` still leaves an existing `unotest/.env.example`
|
|
176
|
+
alone, but the skip now names the keys the current template declares
|
|
177
|
+
that the file does not — a project seeded on an older version never
|
|
178
|
+
learnt about `UNOTEST_STEP_SCREENSHOTS`, because nothing compared its
|
|
179
|
+
file with the template. The file is not touched.
|
|
180
|
+
`declaredEnvKeys()` in `@unotest/protocol` reads active and
|
|
181
|
+
commented-out keys alike. The DSL reference now documents
|
|
182
|
+
`UNOTEST_STEP_SCREENSHOTS` next to `screenshot()`: readers of that page
|
|
183
|
+
concluded the every-step mode did not exist.
|
|
184
|
+
- 64108b8: `shell()` tells a missing working directory apart from a missing
|
|
185
|
+
binary. Node reports both as `ENOENT`, and the old message sent people
|
|
186
|
+
looking for a tool that was there all along while the real cause was a
|
|
187
|
+
`sandbox.shellCwd` that exists on the laptop and not in CI or on a box.
|
|
188
|
+
The directory is checked before the spawn and the new
|
|
189
|
+
`ShellCwdMissingError` names it and where it came from.
|
|
190
|
+
- Updated dependencies [ab296ff]
|
|
191
|
+
- Updated dependencies [64108b8]
|
|
192
|
+
- Updated dependencies [64108b8]
|
|
193
|
+
- Updated dependencies [64108b8]
|
|
194
|
+
- @unotest/protocol@0.29.0
|
|
195
|
+
- @unotest/viewer@0.29.0
|
|
196
|
+
- @unotest/core@0.29.0
|
|
197
|
+
- @unotest/dsl@0.29.0
|
|
198
|
+
- @unotest/grounder-client@0.29.0
|
|
199
|
+
|
|
3
200
|
## [0.28.0] - 2026-09-01
|
|
4
201
|
|
|
5
202
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -172,6 +172,20 @@ back to GitHub. `--pr` makes a newer push withdraw the older runs of the
|
|
|
172
172
|
same pull request that are still waiting, so three pushes in five minutes
|
|
173
173
|
cost one suite. Recipe: `guides/manuals/ci-setup.md`.
|
|
174
174
|
|
|
175
|
+
The values a suite runs with on the box — `APP_BASE_URL`, its `.env`
|
|
176
|
+
settings, its secrets — are sent separately, from the same files a local
|
|
177
|
+
run reads:
|
|
178
|
+
|
|
179
|
+
```sh
|
|
180
|
+
npx @unotest/web env push dev # .env + .env.dev + .secrets + .secrets.dev
|
|
181
|
+
printf '%s' "$KEY" | npx @unotest/web env set dev API_KEY --secret
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
`.env*` become the environment's variables, `.secrets*` its secrets,
|
|
185
|
+
`APP_BASE_URL` its target; `UNOTEST_*` stay home. A value never prints.
|
|
186
|
+
The token must be minted with `--values` on the box. Admins also see and
|
|
187
|
+
change them on the box's admin page. Manual: `guides/manuals/box-values.md`.
|
|
188
|
+
|
|
175
189
|
## 5. Watch it run — the viewer
|
|
176
190
|
|
|
177
191
|
`npx @unotest/web viewer` opens a local UI (no cloud, no account):
|
package/dist/config/schema.d.ts
CHANGED
|
@@ -215,15 +215,15 @@ declare const WebServerConfigSchema: z.ZodObject<{
|
|
|
215
215
|
/** How long to wait for the URL to come up after spawning. */
|
|
216
216
|
timeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
217
217
|
}, "strip", z.ZodTypeAny, {
|
|
218
|
-
command: string;
|
|
219
218
|
url: string;
|
|
220
|
-
reuseExistingServer: boolean;
|
|
221
219
|
timeoutMs: number;
|
|
222
|
-
}, {
|
|
223
220
|
command: string;
|
|
221
|
+
reuseExistingServer: boolean;
|
|
222
|
+
}, {
|
|
224
223
|
url: string;
|
|
225
|
-
|
|
224
|
+
command: string;
|
|
226
225
|
timeoutMs?: number | undefined;
|
|
226
|
+
reuseExistingServer?: boolean | undefined;
|
|
227
227
|
}>;
|
|
228
228
|
type WebServerConfig = z.infer<typeof WebServerConfigSchema>;
|
|
229
229
|
declare const UnotestConfigSchema: z.ZodObject<{
|
|
@@ -249,15 +249,15 @@ declare const UnotestConfigSchema: z.ZodObject<{
|
|
|
249
249
|
/** How long to wait for the URL to come up after spawning. */
|
|
250
250
|
timeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
251
251
|
}, "strip", z.ZodTypeAny, {
|
|
252
|
-
command: string;
|
|
253
252
|
url: string;
|
|
254
|
-
reuseExistingServer: boolean;
|
|
255
253
|
timeoutMs: number;
|
|
256
|
-
}, {
|
|
257
254
|
command: string;
|
|
255
|
+
reuseExistingServer: boolean;
|
|
256
|
+
}, {
|
|
258
257
|
url: string;
|
|
259
|
-
|
|
258
|
+
command: string;
|
|
260
259
|
timeoutMs?: number | undefined;
|
|
260
|
+
reuseExistingServer?: boolean | undefined;
|
|
261
261
|
}>>;
|
|
262
262
|
browsers: z.ZodArray<z.ZodEnum<["chromium", "firefox", "webkit"]>, "many">;
|
|
263
263
|
channel: z.ZodOptional<z.ZodUnion<[z.ZodLiteral<"chrome">, z.ZodLiteral<"msedge">, z.ZodLiteral<"chrome-beta">, z.ZodNull]>>;
|
|
@@ -508,10 +508,10 @@ declare const UnotestConfigSchema: z.ZodObject<{
|
|
|
508
508
|
};
|
|
509
509
|
baseUrl?: string | undefined;
|
|
510
510
|
webServer?: {
|
|
511
|
-
command: string;
|
|
512
511
|
url: string;
|
|
513
|
-
reuseExistingServer: boolean;
|
|
514
512
|
timeoutMs: number;
|
|
513
|
+
command: string;
|
|
514
|
+
reuseExistingServer: boolean;
|
|
515
515
|
} | undefined;
|
|
516
516
|
channel?: "chrome" | "msedge" | "chrome-beta" | null | undefined;
|
|
517
517
|
storageState?: string | undefined;
|
|
@@ -564,10 +564,10 @@ declare const UnotestConfigSchema: z.ZodObject<{
|
|
|
564
564
|
};
|
|
565
565
|
baseUrl?: string | undefined;
|
|
566
566
|
webServer?: {
|
|
567
|
-
command: string;
|
|
568
567
|
url: string;
|
|
569
|
-
|
|
568
|
+
command: string;
|
|
570
569
|
timeoutMs?: number | undefined;
|
|
570
|
+
reuseExistingServer?: boolean | undefined;
|
|
571
571
|
} | undefined;
|
|
572
572
|
channel?: "chrome" | "msedge" | "chrome-beta" | null | undefined;
|
|
573
573
|
storageState?: string | undefined;
|
package/dist/config/schema.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var _0xb70be3=_0x1568;(function(_0x487679,_0xf30c69){var _0x564764={_0x39328b:0x102,_0x59616f:0xff,_0x6e10ff:0x119,_0x18b7c8:0xfc,_0x46e2f8:0x12a,_0x4f5fca:0x130},_0x580d82=_0x1568,_0x340ec3=_0x487679();while(!![]){try{var _0x164607=-parseInt(_0x580d82(_0x564764._0x39328b))/0x1*(parseInt(_0x580d82(0x11e))/0x2)+parseInt(_0x580d82(0xfb))/0x3*(parseInt(_0x580d82(_0x564764._0x59616f))/0x4)+-parseInt(_0x580d82(0x117))/0x5*(parseInt(_0x580d82(_0x564764._0x6e10ff))/0x6)+parseInt(_0x580d82(0x101))/0x7+parseInt(_0x580d82(_0x564764._0x18b7c8))/0x8*(parseInt(_0x580d82(_0x564764._0x46e2f8))/0x9)+parseInt(_0x580d82(0x10e))/0xa*(parseInt(_0x580d82(0xfe))/0xb)+-parseInt(_0x580d82(_0x564764._0x4f5fca))/0xc;if(_0x164607===_0xf30c69)break;else _0x340ec3['push'](_0x340ec3['shift']());}catch(_0x1b9f91){_0x340ec3['push'](_0x340ec3['shift']());}}}(_0x2f16,0x91f77));function _0x1568(_0x3c32df,_0x2877ec){_0x3c32df=_0x3c32df-0xf9;var _0x2f165e=_0x2f16();var _0x15683a=_0x2f165e[_0x3c32df];if(_0x1568['eflopM']===undefined){var _0x3aed1d=function(_0x643f24){var _0x48d278='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';var _0x34c2ef='',_0x1f31b5='';for(var _0x314cb3=0x0,_0x1d5fff,_0x4b0a9f,_0x5cc54a=0x0;_0x4b0a9f=_0x643f24['charAt'](_0x5cc54a++);~_0x4b0a9f&&(_0x1d5fff=_0x314cb3%0x4?_0x1d5fff*0x40+_0x4b0a9f:_0x4b0a9f,_0x314cb3++%0x4)?_0x34c2ef+=String['fromCharCode'](0xff&_0x1d5fff>>(-0x2*_0x314cb3&0x6)):0x0){_0x4b0a9f=_0x48d278['indexOf'](_0x4b0a9f);}for(var _0x54127f=0x0,_0x47953b=_0x34c2ef['length'];_0x54127f<_0x47953b;_0x54127f++){_0x1f31b5+='%'+('00'+_0x34c2ef['charCodeAt'](_0x54127f)['toString'](0x10))['slice'](-0x2);}return decodeURIComponent(_0x1f31b5);};_0x1568['fyZHBF']=_0x3aed1d,_0x1568['JbVAVC']={},_0x1568['eflopM']=!![];}var _0x4abcf7=_0x2f165e[0x0],_0x50138f=_0x3c32df+_0x4abcf7,_0x39a36a=_0x1568['JbVAVC'][_0x50138f];return!_0x39a36a?(_0x15683a=_0x1568['fyZHBF'](_0x15683a),_0x1568['JbVAVC'][_0x50138f]=_0x15683a):_0x15683a=_0x39a36a,_0x15683a;}import{z}from'zod';var BrowserSchema=z['enum']([_0xb70be3(0x12e),_0xb70be3(0x120),_0xb70be3(0x125)]),BrowserChannelSchema=z[_0xb70be3(0x132)]([z[_0xb70be3(0x113)](_0xb70be3(0x109)),z['literal'](_0xb70be3(0x129)),z[_0xb70be3(0x113)]('chrome-beta'),z[_0xb70be3(0xfd)]()])['optional'](),ViewportSchema=z[_0xb70be3(0x12d)]({'width':z[_0xb70be3(0x112)]()[_0xb70be3(0x121)]()['positive'](),'height':z[_0xb70be3(0x112)]()[_0xb70be3(0x121)]()[_0xb70be3(0x11d)]()}),RetryReasonSchema=z[_0xb70be3(0x105)]([_0xb70be3(0x11c),_0xb70be3(0x134),'crash']),RetryConfigSchema=z[_0xb70be3(0x12d)]({'count':z[_0xb70be3(0x112)]()['int']()[_0xb70be3(0x103)](0x0)[_0xb70be3(0x133)](0xa),'on':z[_0xb70be3(0x138)](RetryReasonSchema)}),FailureBundleConfigSchema=z['object']({'tier1':z[_0xb70be3(0x113)](!![])[_0xb70be3(0x128)](!![]),'tier2':z[_0xb70be3(0x124)](),'tier3':z[_0xb70be3(0x12d)]({'network':z[_0xb70be3(0x124)](),'video':z[_0xb70be3(0x124)]()}),'retention':z[_0xb70be3(0x12d)]({'runs':z[_0xb70be3(0x112)]()['int']()[_0xb70be3(0x11d)](),'days':z[_0xb70be3(0x112)]()[_0xb70be3(0x121)]()[_0xb70be3(0x11d)]()}),'storageDir':z[_0xb70be3(0x11f)]()[_0xb70be3(0x103)](0x1)}),DialogPolicySchema=z['enum']([_0xb70be3(0x12f),_0xb70be3(0xf9),_0xb70be3(0x126)]),LinterRuleIdSchema=z[_0xb70be3(0x105)]([_0xb70be3(0x108),_0xb70be3(0x10b),_0xb70be3(0x12c),'lint:pause-explicit',_0xb70be3(0x111),_0xb70be3(0x10f),_0xb70be3(0x10a),'lint:mustache-in-dsl',_0xb70be3(0x114),'lint:regex-invalid',_0xb70be3(0x136),_0xb70be3(0x123),_0xb70be3(0x10c),_0xb70be3(0x11a),_0xb70be3(0x135),'validator:arity',_0xb70be3(0xfa),'validator:step-coverage','validator:step-shape','validator:meta-shape','validator:function-shape',_0xb70be3(0x131),'validator:if-shape',_0xb70be3(0x12b),_0xb70be3(0x137),_0xb70be3(0x115),'validator:string-concat','validator:semantic-loss',_0xb70be3(0x127),_0xb70be3(0x107)]),LinterSeveritySchema=z['enum']([_0xb70be3(0x118),'warn',_0xb70be3(0x122)]),LinterConfigSchema=z[_0xb70be3(0x12d)]({'enabled':z[_0xb70be3(0x124)](),'rules':z[_0xb70be3(0x116)](LinterRuleIdSchema,LinterSeveritySchema)}),QueueConfigSchema=z[_0xb70be3(0x12d)]({'enabled':z[_0xb70be3(0x124)]()[_0xb70be3(0x128)](!![]),'concurrency':z[_0xb70be3(0x112)]()[_0xb70be3(0x121)]()[_0xb70be3(0x11d)]()[_0xb70be3(0x128)](0x1)}),ScheduleSchema=z['object']({'collection':z[_0xb70be3(0x11f)]()['min'](0x1),'cron':z[_0xb70be3(0x11f)]()['min'](0x1),'env':z['string']()[_0xb70be3(0x103)](0x1)['optional'](),'prepare':z['string']()[_0xb70be3(0x103)](0x1)[_0xb70be3(0x100)]()}),McpConfigSchema=z['object']({'transport':z[_0xb70be3(0x113)](_0xb70be3(0x104))}),SandboxConfigSchema=z['object']({'shellCwd':z[_0xb70be3(0x11f)]()['optional'](),'shellTimeoutMs':z[_0xb70be3(0x112)]()[_0xb70be3(0x121)]()['positive']()[_0xb70be3(0x100)](),'exportSecrets':z['array'](z[_0xb70be3(0x11f)]())[_0xb70be3(0x100)](),'database':z[_0xb70be3(0x11f)]()[_0xb70be3(0x100)](),'apiBaseUrl':z[_0xb70be3(0x11f)]()[_0xb70be3(0x11b)]()[_0xb70be3(0x100)](),'uploadDir':z[_0xb70be3(0x11f)]()[_0xb70be3(0x100)]()}),WebServerConfigSchema=z['object']({'command':z['string']()[_0xb70be3(0x103)](0x1),'url':z[_0xb70be3(0x11f)]()[_0xb70be3(0x11b)](),'reuseExistingServer':z[_0xb70be3(0x124)]()[_0xb70be3(0x128)](!![]),'timeoutMs':z[_0xb70be3(0x112)]()[_0xb70be3(0x121)]()['positive']()[_0xb70be3(0x128)](0xea60)}),UnotestConfigSchema=z[_0xb70be3(0x12d)]({'baseUrl':z[_0xb70be3(0x11f)]()[_0xb70be3(0x11b)]()['optional'](),'webServer':WebServerConfigSchema['optional'](),'browsers':z['array'](BrowserSchema)[_0xb70be3(0x103)](0x1),'channel':BrowserChannelSchema,'viewport':ViewportSchema,'retry':RetryConfigSchema,'failureBundle':FailureBundleConfigSchema,'dialogPolicy':DialogPolicySchema,'storageState':z[_0xb70be3(0x11f)]()['optional'](),'testDir':z[_0xb70be3(0x11f)]()[_0xb70be3(0x103)](0x1),'helpersDir':z[_0xb70be3(0x11f)]()[_0xb70be3(0x103)](0x1),'defaultTimeoutMs':z[_0xb70be3(0x112)]()[_0xb70be3(0x121)]()['positive'](),'defaultNavigationTimeoutMs':z[_0xb70be3(0x112)]()[_0xb70be3(0x121)]()[_0xb70be3(0x11d)](),'linter':LinterConfigSchema,'mcp':McpConfigSchema,'queue':QueueConfigSchema,'schedules':z['array'](ScheduleSchema)[_0xb70be3(0x128)]([]),'sandbox':SandboxConfigSchema}),DEFAULT_CONFIG={'browsers':[_0xb70be3(0x12e)],'viewport':{'width':0x500,'height':0x2d0},'retry':{'count':0x0,'on':['transient']},'failureBundle':{'tier1':!![],'tier2':!![],'tier3':{'network':![],'video':![]},'retention':{'runs':0x14,'days':0x7},'storageDir':'.unotest/failures'},'dialogPolicy':'accept','testDir':_0xb70be3(0x10d),'helpersDir':_0xb70be3(0x110),'defaultTimeoutMs':0xbb8,'defaultNavigationTimeoutMs':0x7530,'linter':{'enabled':!![],'rules':{'lint:deep-css':_0xb70be3(0x106),'lint:xpath':_0xb70be3(0x106),'lint:obfuscated-class':'warn','lint:pause-explicit':_0xb70be3(0x106),'lint:disambig-by-index':_0xb70be3(0x118),'lint:evaluate-discouraged':'warn','lint:scenario-in-root':_0xb70be3(0x122),'lint:mustache-in-dsl':_0xb70be3(0x122),'lint:goto-concat':'warn','lint:regex-invalid':'error','lint:echo-assert':_0xb70be3(0x106),'lint:lint-ok-missing-reason':'warn','lint:flow-returns-value':_0xb70be3(0x106),'validator:unknown-function':_0xb70be3(0x122)}},'mcp':{'transport':'stdio'},'queue':{'enabled':!![],'concurrency':0x1},'schedules':[],'sandbox':{}};export{BrowserChannelSchema,BrowserSchema,DEFAULT_CONFIG,DialogPolicySchema,FailureBundleConfigSchema,LinterConfigSchema,LinterRuleIdSchema,LinterSeveritySchema,McpConfigSchema,QueueConfigSchema,RetryConfigSchema,RetryReasonSchema,SandboxConfigSchema,ScheduleSchema,UnotestConfigSchema,ViewportSchema,WebServerConfigSchema};function _0x2f16(){var _0x16bf16=['DMfSAwrHDg9YoMfYzY1RAw5K','mtiXmJe4uwnZzKTe','mti5mJe1mMDPzMHIDa','BNvSBa','mtu3m2fHrvDMAW','nhLID01pDa','B3b0Aw9UywW','ndGWmtK3mKP2revkwG','nteWn1DLrgnVBa','BwLU','C3rKAw8','zw51Bq','D2fYBG','DMfSAwrHDg9YoNDYyxbWzwqTzM9YBwf0','BgLUDdPKzwvWlwnZCW','y2HYB21L','BgLUDdPZy2vUyxjPBY1PBI1YB290','BgLUDdP4Cgf0Aa','BgLUDdPSAw50lw9Rlw1PC3nPBMCTCMvHC29U','Dw5VDgvZDc9LmMu','nJm4mtbutxHAu2G','BgLUDdPLDMfSDwf0zs1KAxnJB3vYywDLza','Dw5VDgvZDc9LmMuVx2HLBhbLCNm','BgLUDdPKAxnHBwjPzY1IEs1PBMrLEa','BNvTyMvY','BgL0zxjHBa','BgLUDdPNB3rVlwnVBMnHDa','DMfSAwrHDg9YoNvUC3vWCg9YDgvKlwv4ChjLC3nPB24','CMvJB3jK','mZvty1zbD2K','B2zM','mZKZnJC4C09xsuvr','BgLUDdPMBg93lxjLDhvYBNmTDMfSDwu','DxjS','DhjHBNnPzw50','Cg9ZAxrPDMu','mtyYu25uqu1A','C3rYAw5N','zMLYzwzVEa','Aw50','zxjYB3i','BgLUDdPHCgKTy2fSBc1MAwXLlwjVzhK','yM9VBgvHBG','D2vIA2L0','BwfUDwfS','DMfSAwrHDg9YoMXPC3qTyxjN','zgvMyxvSDa','BxnLzgDL','nJnovMnIAuO','DMfSAwrHDg9YoNzHCI1ZAgfWzq','BgLUDdPVyMz1C2nHDgvKlwnSyxnZ','B2jQzwn0','y2HYB21PDw0','ywnJzxb0','mtu1odqXnZjqBunTrNi','DMfSAwrHDg9YoMzVCI1ZAgfWzq','Dw5PB24','Bwf4','BMv0D29YAW','DMfSAwrHDg9YoNvUA25VD24TzNvUy3rPB24','BgLUDdPLy2HVlwfZC2vYDa','DMfSAwrHDg9YoNvUC3vWCg9YDgvKlxn0yxrLBwvUDa','yxjYyxK','zgLZBwLZCW'];_0x2f16=function(){return _0x16bf16;};return _0x2f16();}
|
package/dist/driver/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { E as ElementHint, N as NetworkEntry, W as WebDriver, L as LaunchOptions, C as ContextOptions, D as DriverContext } from '../interfaces-
|
|
2
|
-
export {
|
|
1
|
+
import { E as ElementHint, N as NetworkEntry, W as WebDriver, L as LaunchOptions, C as ContextOptions, D as DriverContext, A as AttachOptions } from '../interfaces-BluheUKs.js';
|
|
2
|
+
export { a as ActionTimeout, b as CheckOptions, c as ClickOptions, d as CookieOptions, e as DriverPage, F as FillOptions, G as GotoOptions, H as HoverOptions, f as NavigationOptions, P as PressOptions, R as ReloadOptions, S as ScreenshotOptions, g as SelectOptions, V as Viewport, h as WaitOptions, i as WaitState } from '../interfaces-BluheUKs.js';
|
|
3
3
|
import '@unotest/protocol';
|
|
4
4
|
import '../config/schema.js';
|
|
5
5
|
import 'zod';
|
|
@@ -77,7 +77,7 @@ declare class FakeWebDriver implements WebDriver {
|
|
|
77
77
|
close(): Promise<void>;
|
|
78
78
|
wsEndpoint(): string | undefined;
|
|
79
79
|
connectShared(): Promise<void>;
|
|
80
|
-
attachExisting(): DriverContext;
|
|
80
|
+
attachExisting(_opts?: AttachOptions): DriverContext;
|
|
81
81
|
/** Test helper — full recorded call log. */
|
|
82
82
|
calls(): ReadonlyArray<RecordedCall>;
|
|
83
83
|
/** Test helper — calls matching a method name. */
|