@unotest/web 0.41.0 → 0.43.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 +590 -3
- package/README.md +68 -6
- package/dist/config/schema.d.ts +111 -55
- package/dist/config/schema.js +1 -1
- package/dist/driver/index.d.ts +2 -2
- package/dist/driver/index.js +1 -1
- package/dist/dsl/index.d.ts +68 -4
- package/dist/dsl/index.js +1 -1
- package/dist/dsl/web-dsl-language-service.d.ts +1 -1
- package/dist/dsl/web-dsl-language-service.js +1 -1
- package/dist/inspection/page-inject.d.ts +1 -1
- package/dist/{interfaces-B34N4rfI.d.ts → interfaces-7nMNCr2h.d.ts} +6 -0
- package/dist/{linter-Yf4gPe5J.d.ts → linter-BWSAYBQy.d.ts} +24 -0
- package/dist/mcp/server.js +1 -1
- package/dist/runner/cli.d.ts +1 -1
- package/dist/runner/cli.js +1 -1
- package/dist/runner/init/chrome-probe.js +1 -1
- package/dist/runner/init.js +1 -1
- package/dist/runner/install-chromium.js +1 -1
- package/dist/runner/prepare-fix.js +1 -1
- package/dist/runner/scaffold-workspace.js +1 -1
- package/dist/runner/serve-fixture.js +1 -1
- package/dist/runner/web-runner-adapter.js +1 -1
- package/guides/agent-integration.md +109 -7
- package/guides/dsl-reference.md +19 -0
- package/package.json +8 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,592 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.43.0] - 2026-09-27
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 6fc8140: A run on an environment no longer inherits a host from the base `.env`
|
|
8
|
+
|
|
9
|
+
Environment files layer: `unotest/.env` first, then the `unotest/.env.<name>`
|
|
10
|
+
overlay an active environment selects, the later file winning. That is the
|
|
11
|
+
right rule for a password or a user name, and the wrong one for a host. A
|
|
12
|
+
project whose base file points `API_BASE_CHAT` at production, and whose
|
|
13
|
+
`dev` overlay does not mention it, ran `--env dev` and sent its API calls to
|
|
14
|
+
production. Nothing in the output said so: the reply reported the `dev`
|
|
15
|
+
environment, and the requests went elsewhere.
|
|
16
|
+
|
|
17
|
+
From this release a variable that becomes a HOST has to come from the active
|
|
18
|
+
environment's overlay, or from the process environment. Three variables count
|
|
19
|
+
as hosts: `APP_BASE_URL`, which every relative `goto` resolves against; the
|
|
20
|
+
name passed as `apiCall(..., {base: "NAME"})`; and a variable used as the URL
|
|
21
|
+
in `goto(NAME)`. When the value still comes from the base file, the run is
|
|
22
|
+
refused before the browser starts, and `lint`, `run_test` and `explore_start`
|
|
23
|
+
refuse identically — with a message that names the variable, both files, and
|
|
24
|
+
the two ways forward. Every other variable layers exactly as before.
|
|
25
|
+
|
|
26
|
+
A base value that really is meant for every environment is declared once, in
|
|
27
|
+
`unotest.config.{js,mjs,ts}`:
|
|
28
|
+
|
|
29
|
+
```js
|
|
30
|
+
export default { env: { shared: ["API_BASE_CHAT"] } };
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
A value the process environment supplies — a shell variable, or the values a
|
|
34
|
+
box injects into a run — is never affected by this rule: it is a value
|
|
35
|
+
somebody stated for this run, not one silently inherited from a file the
|
|
36
|
+
overlay was supposed to replace.
|
|
37
|
+
|
|
38
|
+
Migration: a suite that runs with `--env <name>` and keeps its host in the
|
|
39
|
+
base file will now refuse, naming the variable. Either add the variable to
|
|
40
|
+
the overlay, which is what most projects meant, or list its name under
|
|
41
|
+
`env: { shared: [...] }`. A suite that never uses `--env` is unaffected.
|
|
42
|
+
|
|
43
|
+
- 703185f: A run held at its failure has a screenshot, and the tools answer while it stands
|
|
44
|
+
|
|
45
|
+
A run paused on a failure left nothing on disk: no failure bundle, no
|
|
46
|
+
screenshot, and `get_failure_screenshot` replied "failure bundle not
|
|
47
|
+
found". The frame only appeared once the run was over — which is the one
|
|
48
|
+
moment it is least useful, because the browser has closed and the page
|
|
49
|
+
that broke is gone. Meanwhile the run was sitting right there, still
|
|
50
|
+
showing it.
|
|
51
|
+
|
|
52
|
+
The bundle is now written at the pause, from that page, so
|
|
53
|
+
`list_failures`, `get_failure_screenshot`, `get_failure_a11y` and the
|
|
54
|
+
rest answer on the first call. Each record carries `runState`: `paused`
|
|
55
|
+
while the run is still held — the browser shows the failure, and a
|
|
56
|
+
resumed run that fails again elsewhere re-takes the frame at the new
|
|
57
|
+
failure — then `finished` or `aborted` once the run is over. The frame
|
|
58
|
+
of a finished run stays the one taken at the failure: resuming continues
|
|
59
|
+
with the next statement, so the page moves on while the verdict does
|
|
60
|
+
not, and re-taking it at the end would show a page the failure never
|
|
61
|
+
happened on.
|
|
62
|
+
|
|
63
|
+
Also fixed: aborting a run while it was paused left its process alive
|
|
64
|
+
until the 30-minute inspection TTL expired. The run now ends when it is
|
|
65
|
+
aborted.
|
|
66
|
+
|
|
67
|
+
- 7006895: Spending and deleting wait for a person
|
|
68
|
+
|
|
69
|
+
A key of a customer account — an agent's key, a signed-in machine's, and a CI
|
|
70
|
+
key issued before this release — can no longer rent a box, delete one or
|
|
71
|
+
switch the Picker on by itself. The cloud records what was asked for and answers with a
|
|
72
|
+
request instead; somebody approves or declines it in the cabinet, on the
|
|
73
|
+
Approvals tab, and only a signed-in browser can — not the key that asked, and
|
|
74
|
+
not the link in the email about it. Nothing is created and nothing is charged
|
|
75
|
+
until that happens.
|
|
76
|
+
|
|
77
|
+
For the CLI this is a wait, not a failure. `box create`, `box destroy` and
|
|
78
|
+
`box picker on` print where to answer and how long the request lives, then
|
|
79
|
+
carry on with what the cloud finally did and print exactly what they would
|
|
80
|
+
have printed anyway. Ten minutes with no answer is exit code 75 — nothing was
|
|
81
|
+
created and nothing was charged, run the command again. A decline is exit code 2. `--yes` does not skip it: the flag says the terminal need not ask, not that
|
|
82
|
+
the account need not.
|
|
83
|
+
|
|
84
|
+
Only the CI key of an operator account, one that unotest runs itself, acts
|
|
85
|
+
with nobody watching. A pipeline that rents or deletes boxes with a
|
|
86
|
+
customer's key now waits for somebody to press Approve, or has to rent its
|
|
87
|
+
box ahead of time.
|
|
88
|
+
|
|
89
|
+
- 4582091: `box create` with a short balance points to the dashboard instead of starting a top-up
|
|
90
|
+
|
|
91
|
+
The cloud's API for agents has no top-up: credit is added by a person, by
|
|
92
|
+
card, under "Add credit" on the dashboard's home page. `box create` used to
|
|
93
|
+
start a payment from the terminal when the balance did not cover the first
|
|
94
|
+
hour, against an endpoint the service does not have, so on the real cloud it
|
|
95
|
+
failed with a 404.
|
|
96
|
+
|
|
97
|
+
Now it stops before renting anything and says what is short: the first hour's
|
|
98
|
+
price, the balance and the amount missing, and the dashboard page to add it
|
|
99
|
+
on. It exits 75 — nothing created, nothing charged — and is run again once the
|
|
100
|
+
credit is there. A key that cannot read the balance (the one `login` issues
|
|
101
|
+
cannot) gets the price and the page, and no invented shortfall. `--json`
|
|
102
|
+
prints `{ "needs": "top-up", "firstHourCents", "balanceCents",
|
|
103
|
+
"missingCents", "currency", "url" }`, with `null` for a figure the key may not
|
|
104
|
+
read.
|
|
105
|
+
|
|
106
|
+
The `cloud_box_create` MCP tool answers the same case as
|
|
107
|
+
`{ state: "top-up-needed" }` with the same figures, instead of an error.
|
|
108
|
+
|
|
109
|
+
Migration: the `--topup <eur>` flag of `box create` is removed — top-up moved
|
|
110
|
+
to the dashboard. A script that passes it is refused with the usage and exit
|
|
111
|
+
2; drop the flag, and add credit on the dashboard's home page before renting.
|
|
112
|
+
|
|
113
|
+
- dae29a2: The cloud account as commands and as MCP tools: `box list`, `box logs`, `approvals`, and nine `cloud_*` tools
|
|
114
|
+
|
|
115
|
+
An agent could read a box it had been pointed at and nothing more: which
|
|
116
|
+
boxes the account had, renting one and deleting one lived in a terminal, and
|
|
117
|
+
the list of boxes appeared only inside an error message. The account is now
|
|
118
|
+
reachable from both sides through one implementation, so a box reads the same
|
|
119
|
+
whether a person or an agent asked.
|
|
120
|
+
|
|
121
|
+
New commands:
|
|
122
|
+
|
|
123
|
+
- `box list` — every box on the account with its state, version and projects.
|
|
124
|
+
`--json` prints the same objects the `cloud_boxes_list` tool returns.
|
|
125
|
+
- `box logs [name] [--bootstrap|--updater]` — the end of a box's own log,
|
|
126
|
+
without logging in to it. A box sends it on its next five-minute check, so
|
|
127
|
+
the first call says it has been asked and when to run again, with exit 0.
|
|
128
|
+
- `approvals [id]` — what is waiting for a person on this account, or what
|
|
129
|
+
became of one request. Answering stays in the cabinet: a terminal holding
|
|
130
|
+
the key cannot approve its own request.
|
|
131
|
+
|
|
132
|
+
New MCP tools, outside the `core` toolset: `cloud_boxes_list`,
|
|
133
|
+
`cloud_box_status`, `cloud_box_create`, `cloud_box_destroy`,
|
|
134
|
+
`cloud_box_access`, `cloud_box_update`, `cloud_box_logs`, `cloud_balance`,
|
|
135
|
+
`cloud_approval_wait`. `cloud_box_access` writes the fresh pair into the
|
|
136
|
+
project the way `box access` does and answers with the names it wrote, never
|
|
137
|
+
the values. They answer states an agent can act on instead of
|
|
138
|
+
errors: `not-logged-in` with the command that fixes it, `approval-pending`
|
|
139
|
+
with the request to wait on, `insufficient-scope`, and `declined`,
|
|
140
|
+
`expired` or `still-waiting` from the wait.
|
|
141
|
+
|
|
142
|
+
While a rent or a delete waits for approval, the terminal now names it in the
|
|
143
|
+
words the cabinet and the email use — `Rent a box "acme", size s (€0.09 per
|
|
144
|
+
hour)` — and prints a line when it is approved, not only when it is declined.
|
|
145
|
+
The sentences live in `@unotest/protocol` (`approvalActionText`,
|
|
146
|
+
`approvalPrice`), shared by every surface that shows a request.
|
|
147
|
+
|
|
148
|
+
`unotest-web --help` and `box --help` list the new commands, say which
|
|
149
|
+
commands wait for approval, and give one table of exit codes. `box <command>
|
|
150
|
+
--help` now prints that command's own flags wherever `--help` sits on the
|
|
151
|
+
line, and `approvals` refuses an option it does not know with exit 2.
|
|
152
|
+
|
|
153
|
+
Compatibility: every call to the cloud now carries an `X-Unotest-Client`
|
|
154
|
+
header. The cloud refuses a call that would wait for approval when the header
|
|
155
|
+
is missing — an older CLI could not read the request it would be handed — with
|
|
156
|
+
`426 client-too-old` and a message to update. Update `@unotest/web` before
|
|
157
|
+
renting or deleting boxes with any key of a customer account, a CI key
|
|
158
|
+
included.
|
|
159
|
+
|
|
160
|
+
- ba32d73: A config key the package does not know is refused, with the key you probably meant
|
|
161
|
+
|
|
162
|
+
`chanel: "chrome"` used to be dropped in silence: the run went ahead on
|
|
163
|
+
the default channel, printed `1 passed` and exited 0, so the one setting
|
|
164
|
+
you changed was the one setting that did not apply. Every section of
|
|
165
|
+
`unotest/unotest.config.*` is now checked by name, before the browser
|
|
166
|
+
starts:
|
|
167
|
+
|
|
168
|
+
```
|
|
169
|
+
e2e: config validation failed:
|
|
170
|
+
unknown key "chanel" — did you mean "channel"?
|
|
171
|
+
retry: unknown key "cuont" — did you mean "count"?
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
A key nothing is close to gets the other answer, because it usually
|
|
175
|
+
comes from a config written for a newer release than the installed
|
|
176
|
+
package:
|
|
177
|
+
|
|
178
|
+
```
|
|
179
|
+
unknown key "telemetryEndpoint" — not a config key; check your
|
|
180
|
+
@unotest/web version if it comes from a newer release
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
The config `unotest-web init` writes no longer offers a `viewer` section:
|
|
184
|
+
the viewer's port has always come from `UNOTEST_VIEWER_PORT`, and the
|
|
185
|
+
section was a suggestion the schema never had. A config generated by an
|
|
186
|
+
earlier version needs that block removed.
|
|
187
|
+
|
|
188
|
+
**Migration.** A config that carries a key this version does not know
|
|
189
|
+
now fails instead of running: fix the spelling, remove the key, or
|
|
190
|
+
install the release the key belongs to. Nothing else about the config
|
|
191
|
+
changes — every known field keeps its name, its default and its merge
|
|
192
|
+
behaviour, and a partially written section is still merged with the
|
|
193
|
+
defaults key by key.
|
|
194
|
+
|
|
195
|
+
- fb12602: A step that takes minutes no longer disappears into the client's background
|
|
196
|
+
|
|
197
|
+
Waiting on a slow page — a chat answer that takes four minutes to come
|
|
198
|
+
back, a report that builds itself — used to be a call your agent never got
|
|
199
|
+
an answer to: MCP clients stop waiting at about two minutes and carry on,
|
|
200
|
+
and whatever the step replied after that was lost. The way round it was to
|
|
201
|
+
cut every wait to under a hundred seconds and repeat the step.
|
|
202
|
+
|
|
203
|
+
Now a step still running after 90 seconds answers `{ status: "accepted",
|
|
204
|
+
taskId }` and keeps going: same timeout, same recording into the session.
|
|
205
|
+
Collect it with the new `explore_step_result({ taskId })` — `running`,
|
|
206
|
+
`done` with the reply the step would have given, or `failed`. A batch of
|
|
207
|
+
steps goes to the background as one task, for one id. The `durationMs` of
|
|
208
|
+
a collected step is the whole step, from its start — there is no threshold
|
|
209
|
+
to add back by hand.
|
|
210
|
+
|
|
211
|
+
While a step is out there, the browser context says so instead of being
|
|
212
|
+
damaged: another step, `explore_stop` and `close_context` answer `busy`
|
|
213
|
+
and name the task to wait for, and `explore_state` lists what is pending.
|
|
214
|
+
|
|
215
|
+
- 82d5920: Signing in to a box with one environment opens its viewer straight away
|
|
216
|
+
|
|
217
|
+
A box configured with a single environment used to answer a sign-in with a
|
|
218
|
+
screen holding one option and a button. There was nothing to choose, so the
|
|
219
|
+
screen only stood between you and the viewer behind it.
|
|
220
|
+
|
|
221
|
+
Now that sign-in lands in the viewer. The **Environments** screen still
|
|
222
|
+
appears whenever it has something to say — more than one environment to pick
|
|
223
|
+
from, a box that has not been sent a test suite yet, or a licence in its
|
|
224
|
+
grace period, whose warning lives there.
|
|
225
|
+
|
|
226
|
+
So that skipping the screen does not hide what it carried, the viewer's
|
|
227
|
+
session menu now offers **box pages**: one link back to the box's own
|
|
228
|
+
pages — environments, read tokens, and administration for those who have
|
|
229
|
+
it. It appears when the box tells the viewer where they are, so a viewer
|
|
230
|
+
and a box on different versions simply do without it.
|
|
231
|
+
|
|
232
|
+
- 3aee366: take_wheel now points the session at the run it takes the wheel of
|
|
233
|
+
|
|
234
|
+
An agent debugging a paused run would call `take_wheel`, take a
|
|
235
|
+
screenshot, and get a picture of a completely different stand — whatever
|
|
236
|
+
page the session had been recording on before. Nothing was wrong with the
|
|
237
|
+
screenshot: the wheel only ever took the control token, while the browser
|
|
238
|
+
stayed where it was, and the reply said `ok` either way.
|
|
239
|
+
|
|
240
|
+
`take_wheel` now also attaches this session to the run's browser, so the
|
|
241
|
+
next screenshot, snapshot or step reads the page being debugged. Every
|
|
242
|
+
`explore_step` reply carries `context` — `{kind: "run", runtimeId}` or
|
|
243
|
+
`{kind: "own"}` — so the source of a frame is stated rather than guessed.
|
|
244
|
+
A session has exactly one browser: `take_wheel` and
|
|
245
|
+
`attach_debug_session` point it at a run, `explore_start` and
|
|
246
|
+
`new_context` at your own, and `explorationId` chooses recording and
|
|
247
|
+
where a file is filed, never which browser a step runs on.
|
|
248
|
+
`release_wheel` hands the token back and deliberately leaves the browser
|
|
249
|
+
on the run.
|
|
250
|
+
|
|
251
|
+
Behaviour change for anything parsing the reply: `take_wheel` no longer
|
|
252
|
+
always answers `ok`. When it cannot join the browser it answers
|
|
253
|
+
`recording_open` (a live recording would be dropped — nothing is changed,
|
|
254
|
+
not even the wheel), `no_browser` (the run has not published one yet) or
|
|
255
|
+
`no_page`, each naming what to do next.
|
|
256
|
+
|
|
257
|
+
- 9034092: Opening a box's viewer while a suite is being installed no longer looks like a failure
|
|
258
|
+
|
|
259
|
+
Sending a new suite to a box replaces that environment's viewer, and for the
|
|
260
|
+
second or two that takes, anyone holding the page open got a bare `502` — the
|
|
261
|
+
status that tells a browser nothing and tells a script the box is broken.
|
|
262
|
+
|
|
263
|
+
The box now says, while it is happening, that it is replacing that viewer. A
|
|
264
|
+
browser gets a short page that comes back by itself once the new viewer
|
|
265
|
+
answers; an agent reading the box with a token gets `503` with `Retry-After`
|
|
266
|
+
and `unavailable`, which is the box's existing word for "wait and ask again".
|
|
267
|
+
|
|
268
|
+
A viewer that stopped for any other reason answers the same way but promises
|
|
269
|
+
nothing, and the page does not reload itself: nobody is bringing it back, and
|
|
270
|
+
saying otherwise would be worse than saying nothing.
|
|
271
|
+
|
|
272
|
+
### Patch Changes
|
|
273
|
+
|
|
274
|
+
- b41b02e: A cloud that will not show the price list is quoted as such, not as free
|
|
275
|
+
|
|
276
|
+
`box create` and `box picker on` quote the hourly price inside the
|
|
277
|
+
question they ask. When the cloud refused the price list, the question was
|
|
278
|
+
asked with no price in it at all — and silence there reads as "this is
|
|
279
|
+
free", with no way for the reader to learn that a price exists and they
|
|
280
|
+
were not shown it.
|
|
281
|
+
|
|
282
|
+
A refusal for want of a permission now says so: `price not visible to this
|
|
283
|
+
key: it needs billing:read`. On a current cloud the list is under `read`,
|
|
284
|
+
which every key from `login` carries — so this is what an OLDER cloud says
|
|
285
|
+
to a newer client, which is exactly when a client must not go quiet about
|
|
286
|
+
money. Every other reason stays silent, because a cloud older still has no
|
|
287
|
+
price list at all and a command must not fail for want of a courtesy.
|
|
288
|
+
|
|
289
|
+
- 3e7e3fc: API keys carry permissions
|
|
290
|
+
|
|
291
|
+
A key used to be all or nothing: any live key could do everything the API
|
|
292
|
+
offers. Now every key carries a list of permissions — see the boxes, manage
|
|
293
|
+
them, read the balance — and every route states the one it needs. A key
|
|
294
|
+
without it gets `insufficient-scope`, which is a 403 and not a 401: the
|
|
295
|
+
credential is good, so signing in again would change nothing.
|
|
296
|
+
|
|
297
|
+
Where the list comes from depends on the key. In the cabinet you tick the
|
|
298
|
+
permissions when issuing a key for an agent. Signing a machine in asks for
|
|
299
|
+
enough to see your boxes and work with them, never your money, and the
|
|
300
|
+
approval page in the browser lets you narrow that before you approve — the
|
|
301
|
+
key then has exactly what was left ticked. `login` and `whoami` print what
|
|
302
|
+
the key may do.
|
|
303
|
+
|
|
304
|
+
Keys that already exist keep the permissions they had, with one exception:
|
|
305
|
+
spending money and deleting with nobody watching. Only the CI key of an
|
|
306
|
+
operator account, one that unotest runs itself, may do that. A CI key a
|
|
307
|
+
customer holds from before this release works like an agent's key: renting,
|
|
308
|
+
deleting and switching the Picker on wait for a person to approve.
|
|
309
|
+
|
|
310
|
+
One visible change for a key that cannot read the balance: `whoami` says so
|
|
311
|
+
on the balance line instead of failing, and signing in with such a key is
|
|
312
|
+
still recognised as being signed in.
|
|
313
|
+
|
|
314
|
+
- 8353232: A rental the account cannot pay is refused before anybody is asked to approve it
|
|
315
|
+
|
|
316
|
+
A key that may not spend by itself used to get an approval request for a box
|
|
317
|
+
even when the balance could not cover the first hour: a person was asked to
|
|
318
|
+
approve it, and the rental then failed at their yes. The money is now checked
|
|
319
|
+
first, and such a create is answered `402 insufficient-balance` with nothing
|
|
320
|
+
recorded and nobody written to. The check at approval stays — a balance can
|
|
321
|
+
move while a request waits — and a request that fails there says so in words:
|
|
322
|
+
the price of the hour and where to add credit.
|
|
323
|
+
|
|
324
|
+
Every `insufficient-balance` carries `topupUrl`, the dashboard page where a
|
|
325
|
+
person adds credit. A key that may read the account's money also gets
|
|
326
|
+
`firstHourCents`, `balanceCents`, `missingCents` and `currency`; any other
|
|
327
|
+
key gets no figures.
|
|
328
|
+
|
|
329
|
+
`box create` prints the cloud's own figures and link when the refusal carries
|
|
330
|
+
them, instead of counting them again.
|
|
331
|
+
|
|
332
|
+
- 2006bf9: A refused config says which field is wrong, and `channel` takes the same words everywhere
|
|
333
|
+
|
|
334
|
+
Any bad value in `unotest/unotest.config.mjs` used to come back as one
|
|
335
|
+
line — `config validation failed` — with no field, no value and no list of
|
|
336
|
+
what would have been accepted, though all three were known. Now the
|
|
337
|
+
refusal names them:
|
|
338
|
+
|
|
339
|
+
```
|
|
340
|
+
config validation failed:
|
|
341
|
+
channel: expected one of "chrome" / "msedge" / "chrome-beta" / "chromium" / "bundled" / null, got "bundeld"
|
|
342
|
+
defaultTimeoutMs: expected number, got string
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
And `channel: "bundled"` is accepted. `UNOTEST_BROWSER_CHANNEL` has always
|
|
346
|
+
taken that word — the `.env` template says the variable merely overrides
|
|
347
|
+
`channel` — while the config refused it. Both inputs now share one
|
|
348
|
+
vocabulary, and `bundled` means what it has always meant: Playwright's own
|
|
349
|
+
Chromium, the default.
|
|
350
|
+
|
|
351
|
+
- e7cdab7: An empty id is refused instead of quietly changing what a tool does
|
|
352
|
+
|
|
353
|
+
`explore_step` and `explore_steps` treated `explorationId: ""` as "run
|
|
354
|
+
this without recording": the step went through, the reply said it had
|
|
355
|
+
succeeded, and nothing reached the session log. The agent found out at
|
|
356
|
+
`explore_stop`, with an empty recording and the work already done. The
|
|
357
|
+
same held for `explore_record`, and for `runtimeId: ""` in the debugger
|
|
358
|
+
tools, where an empty value silently meant "the session you attached to".
|
|
359
|
+
|
|
360
|
+
An empty id is now an input error naming the field, exactly as the
|
|
361
|
+
neighbouring tools have always answered. Leaving the key out is still how
|
|
362
|
+
you ask for the ad-hoc mode — absence is the mode, emptiness is a typo.
|
|
363
|
+
|
|
364
|
+
- a3f89ee: The MCP server reports the version it actually is
|
|
365
|
+
|
|
366
|
+
`initialize` answered `serverInfo` with a hard-coded `0.1.0` whatever
|
|
367
|
+
version of the package was running, while `--version` printed the real
|
|
368
|
+
number. An editor showed the wrong version in its server list, and nobody
|
|
369
|
+
checking a session could tell a server started from npm apart from one
|
|
370
|
+
started out of a working copy.
|
|
371
|
+
|
|
372
|
+
Both surfaces now read the package's own manifest through one helper, so
|
|
373
|
+
they cannot drift apart again. A manifest that cannot be read is an error
|
|
374
|
+
naming the fault, not a silent `0.0.0` — an invented version reads as a
|
|
375
|
+
real answer in a protocol reply and in a support transcript.
|
|
376
|
+
|
|
377
|
+
- f1f3fb1: Existing boxes install the five-minute update check themselves, and report a finished update in the tick that applied it
|
|
378
|
+
|
|
379
|
+
A box rented before the five-minute check existed only had the hourly timer:
|
|
380
|
+
pressing **Update** in the cabinet could take up to an hour and a half to
|
|
381
|
+
reach it. The updater now installs that pair of systemd units itself when the
|
|
382
|
+
box does not have them — exactly those two, only when the file is absent, and
|
|
383
|
+
never replacing a unit an operator edited.
|
|
384
|
+
|
|
385
|
+
A finished update is also reported in the tick that applied it, instead of in
|
|
386
|
+
the next one, so the cabinet card and `box status` stop showing "updating" for
|
|
387
|
+
a box that is already done.
|
|
388
|
+
|
|
389
|
+
**Both start working with the update after this one.** The move onto this
|
|
390
|
+
version is still carried out by the updater a box runs today: it arrives on
|
|
391
|
+
the hourly timer — up to an hour and a half — and that first "applied" still
|
|
392
|
+
comes with the next hourly tick. From there on the new updater is in charge,
|
|
393
|
+
a pressed **Update** lands within minutes, and the cabinet shows it as done as
|
|
394
|
+
soon as it is.
|
|
395
|
+
|
|
396
|
+
- 74a1f9d: The viewer finishes writing its run index before it exits
|
|
397
|
+
|
|
398
|
+
On boot, and whenever a run finishes or the environment switches, the viewer
|
|
399
|
+
rebuilds the derived index it serves history from. Those writes are
|
|
400
|
+
deliberately not waited for while the server runs — a history months deep
|
|
401
|
+
must not hold the UI back — but they were not waited for on the way down
|
|
402
|
+
either. Stopping the viewer returned while files were still landing under the
|
|
403
|
+
run directory: a directory the caller believed had been released kept growing
|
|
404
|
+
for a fraction of a second, and anything that removed or archived it right
|
|
405
|
+
after the viewer stopped could fail or capture a half-written index.
|
|
406
|
+
|
|
407
|
+
Shutdown now waits for the index writes that are still in flight, so a
|
|
408
|
+
stopped viewer is a viewer that has finished writing. A rebuild that takes
|
|
409
|
+
longer makes the stop take longer, which is the honest cost of not leaving
|
|
410
|
+
half a file behind.
|
|
411
|
+
|
|
412
|
+
- Updated dependencies [6fc8140]
|
|
413
|
+
- Updated dependencies [4582091]
|
|
414
|
+
- Updated dependencies [dae29a2]
|
|
415
|
+
- Updated dependencies [82d5920]
|
|
416
|
+
- Updated dependencies [74a1f9d]
|
|
417
|
+
- @unotest/core@0.43.0
|
|
418
|
+
- @unotest/protocol@0.43.0
|
|
419
|
+
- @unotest/viewer@0.43.0
|
|
420
|
+
- @unotest/dsl@0.43.0
|
|
421
|
+
- @unotest/grounder-client@0.43.0
|
|
422
|
+
|
|
423
|
+
## [0.42.0] - 2026-09-24
|
|
424
|
+
|
|
425
|
+
### Minor Changes
|
|
426
|
+
|
|
427
|
+
- 8f319b2: `box status` now reports the box's version: what it runs, what its channel
|
|
428
|
+
offers and where it is between the two.
|
|
429
|
+
|
|
430
|
+
A box never updates itself. A new release is offered, not installed — the
|
|
431
|
+
box keeps running the version it is on until somebody presses **Update** in
|
|
432
|
+
the cabinet, or an agent calls `POST /api/v1/boxes/<name>/update` with the
|
|
433
|
+
version it was offered. `box status` prints the offer and says where the
|
|
434
|
+
decision is made; a cloud that reports no versions leaves the CLI silent
|
|
435
|
+
about them rather than guessing.
|
|
436
|
+
|
|
437
|
+
Migration: boxes that used to install a new release on their own stop doing so
|
|
438
|
+
with this release. To move a box to a newer version, press **Update** on its
|
|
439
|
+
card in the cabinet, or call the endpoint above.
|
|
440
|
+
|
|
441
|
+
- 0c56c6d: Start the browser with a profile and extra arguments — what testing a browser extension needs
|
|
442
|
+
|
|
443
|
+
A new `launch` block in `unotest.config`:
|
|
444
|
+
|
|
445
|
+
- `launch.args` — extra browser command-line arguments, e.g.
|
|
446
|
+
`--load-extension=./ext` (extensions load in Chromium only);
|
|
447
|
+
- `launch.userDataDir` — a browser profile directory. The browser then starts
|
|
448
|
+
persistent: cookies, storage and installed extensions live in the profile.
|
|
449
|
+
`UNOTEST_USER_DATA_DIR` overrides it for a machine.
|
|
450
|
+
|
|
451
|
+
On a profile there is **no isolation between scenarios**: the browser has one
|
|
452
|
+
context, and every scenario of a run gets that same context. Each such run says
|
|
453
|
+
so in its log.
|
|
454
|
+
|
|
455
|
+
Refused with a message instead of being ignored: a profile together with
|
|
456
|
+
`storageState`, a profile in a collection of more than one worker, `launch`
|
|
457
|
+
on a box (its runs take no launch settings from the suite), and an extension
|
|
458
|
+
(`--load-extension`) on a headless run of bundled Chromium, whose headless
|
|
459
|
+
shell ignores extensions without an error. For headless runs with an
|
|
460
|
+
extension — CI — set the new `channel: "chromium"` (Chromium's new headless
|
|
461
|
+
mode, which loads them), or `UNOTEST_BROWSER_CHANNEL=chromium`; locally, run
|
|
462
|
+
headed. Without `launch` nothing changes.
|
|
463
|
+
|
|
464
|
+
- 1f1dffb: `box envs` and `box run` show how a box installed the suite: how long it
|
|
465
|
+
took, and whether the packages came from the registry or the box's own
|
|
466
|
+
npm cache.
|
|
467
|
+
|
|
468
|
+
```
|
|
469
|
+
acme/test main@1a2b3c4
|
|
470
|
+
installed in 12.3s from this box's cache (252 of 252 from cache)
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
The source is measured by the box, not read out of npm's output: before
|
|
474
|
+
an install it compares what the lockfile names against what its cache
|
|
475
|
+
already holds. On a fast registry a cold install and a warm one take the
|
|
476
|
+
same number of seconds, so the duration alone never said which had
|
|
477
|
+
happened — the counts are printed with the word so it can be checked.
|
|
478
|
+
`box run <id>` prints the same line for the bundle that run came from,
|
|
479
|
+
while the environment still serves it.
|
|
480
|
+
|
|
481
|
+
A box older than this contract says nothing about installs, and the
|
|
482
|
+
output is exactly what it was.
|
|
483
|
+
|
|
484
|
+
- 7958c4a: Recording tools refuse a misspelled field instead of silently not recording
|
|
485
|
+
|
|
486
|
+
`explore_step` with `exploration_id` (instead of `explorationId`) used to run the
|
|
487
|
+
step ad hoc, answer success and record nothing — the empty session only showed
|
|
488
|
+
up at `explore_stop`. Every `explore_*` tool, `generate_dsl_from_exploration`
|
|
489
|
+
and `save_exploration_as_test` now refuse a field they do not declare, including
|
|
490
|
+
inside an `explore_steps` step, and the error names the field they accept
|
|
491
|
+
(`exploration_id (did you mean explorationId?)`).
|
|
492
|
+
|
|
493
|
+
Ad-hoc execution is unchanged: omit `explorationId`, and the step's reply says
|
|
494
|
+
`recorded: false`.
|
|
495
|
+
|
|
496
|
+
Migration: a client that sent extra, ignored fields to these tools now gets an
|
|
497
|
+
input error listing the accepted fields — drop the extra ones.
|
|
498
|
+
|
|
499
|
+
- 92b446f: `box status` says until when the balance keeps a box, not when its paid hour ends
|
|
500
|
+
|
|
501
|
+
`box status` used to print `paid until` — the end of the hour already bought.
|
|
502
|
+
With credit left that read as "switched off within the hour". It now prints
|
|
503
|
+
`funded until <date> UTC`: the moment the box is deleted if nobody tops up,
|
|
504
|
+
computed by the cloud from the balance and every box on the account, each at
|
|
505
|
+
its own rate. A failed box prints neither.
|
|
506
|
+
|
|
507
|
+
The cloud API adds `fundedUntil` to each billed box and to `GET /balance` (absent
|
|
508
|
+
when nothing is billed); `paidUntil` stays, and means only the end of the paid
|
|
509
|
+
hour. The cabinet's box card and balance show the same date, and the low-balance
|
|
510
|
+
letter names it.
|
|
511
|
+
|
|
512
|
+
Migration: `box status` no longer prints a `paid until` line. A script that read
|
|
513
|
+
it should take `fundedUntil` from `box status --json` for the deadline, or
|
|
514
|
+
`paidUntil` there if it meant the end of the paid hour.
|
|
515
|
+
|
|
516
|
+
- c99f849: The viewer on a box has no terminal panel instead of a dead one
|
|
517
|
+
|
|
518
|
+
A box's viewer could never start its docked terminal — the PTY addon is not in
|
|
519
|
+
the box image — yet it showed the panel and the button. A host that cannot give
|
|
520
|
+
the terminal a PTY now says so with `UNOTEST_VIEWER_TERMINAL=off`, and the viewer
|
|
521
|
+
then leaves the terminal out entirely: no panel, no button, no terminal socket,
|
|
522
|
+
for every session. A box sets it for its viewers.
|
|
523
|
+
|
|
524
|
+
Nothing changes locally: the terminal is on by default, and a machine missing
|
|
525
|
+
the addon still reports it when the panel starts.
|
|
526
|
+
|
|
527
|
+
### Patch Changes
|
|
528
|
+
|
|
529
|
+
- 784c10d: A pressed Update reaches the box within minutes, and a failed one is not retried on its own
|
|
530
|
+
|
|
531
|
+
A box now checks every five minutes whether it has been asked to move to
|
|
532
|
+
another version, instead of once an hour, so pressing **Update** in the cabinet
|
|
533
|
+
(or calling the update endpoint) takes effect within minutes.
|
|
534
|
+
|
|
535
|
+
The box also reports back what it runs and how its last update ended —
|
|
536
|
+
applied, rolled back, refused or failed, and why — which is what the cabinet
|
|
537
|
+
and `box status` show.
|
|
538
|
+
|
|
539
|
+
An update that did not succeed is not tried again by the box itself: rolling
|
|
540
|
+
back takes the box's stack down and up again, and repeating that unasked would
|
|
541
|
+
be an automatic update. It is tried again when **Update** is pressed again.
|
|
542
|
+
|
|
543
|
+
- e0b822a: A reinstall no longer empties the tree a viewer is serving
|
|
544
|
+
|
|
545
|
+
A bundle whose tree already has packages is reinstalled in a copy while the
|
|
546
|
+
live tree waits aside; the viewer keeps serving it. A failed reinstall leaves
|
|
547
|
+
the live `node_modules` untouched instead of leaving the viewer answering 404
|
|
548
|
+
until an install succeeds. A tree a daemon left aside when it died mid-install
|
|
549
|
+
is put back by the next housekeeping sweep.
|
|
550
|
+
|
|
551
|
+
- 96e4977: A box that names no environments is told so, instead of being sent a name
|
|
552
|
+
it cannot match.
|
|
553
|
+
|
|
554
|
+
`box runs`, `box queue`, `box run` and the rest used to pass the name on to
|
|
555
|
+
a box with an empty list, which answered 404 and advice about the form of a
|
|
556
|
+
name — spelling advice to somebody whose box has nothing to spell. They now
|
|
557
|
+
say what is true: the box names no environments yet, a run ordered a moment
|
|
558
|
+
ago may not be listed straight away, and a box that goes on naming none has
|
|
559
|
+
none configured. Agents get the same sentence through the `box_*` MCP tools.
|
|
560
|
+
|
|
561
|
+
The command exits 2, like the other refusals of an environment name (was 1).
|
|
562
|
+
|
|
563
|
+
Migration: a script that checked for exit code 1 on this refusal should check
|
|
564
|
+
for 2.
|
|
565
|
+
|
|
566
|
+
`bundle push --run` is unchanged: it uploads and lets the box decide, because
|
|
567
|
+
a box older than this contract names nothing until its first run. What
|
|
568
|
+
changed there is the hint after the box's own refusal, which no longer points
|
|
569
|
+
back at the list that was empty.
|
|
570
|
+
|
|
571
|
+
- d1ecff6: A box lists its environments before the first suite arrives
|
|
572
|
+
|
|
573
|
+
The route table names every configured environment; one without a bundle is
|
|
574
|
+
listed without an address. `/_guard/api/envs` answers with it at once
|
|
575
|
+
(`note: "no suite yet"`), so an environment name can be checked before the
|
|
576
|
+
first push, and opening it in a browser shows a page with the commands that
|
|
577
|
+
send a suite instead of a bare 503.
|
|
578
|
+
|
|
579
|
+
Migration: the environment list a read token gets (`box envs`, the `box_envs`
|
|
580
|
+
MCP tool) now also names environments that have no suite yet, with the note
|
|
581
|
+
`no suite yet`. Reading one of them answers `unavailable` until a suite is pushed.
|
|
582
|
+
|
|
583
|
+
- Updated dependencies [c99f849]
|
|
584
|
+
- @unotest/viewer@0.42.0
|
|
585
|
+
- @unotest/core@0.42.0
|
|
586
|
+
- @unotest/dsl@0.42.0
|
|
587
|
+
- @unotest/grounder-client@0.42.0
|
|
588
|
+
- @unotest/protocol@0.42.0
|
|
589
|
+
|
|
3
590
|
## [0.41.0] - 2026-09-21
|
|
4
591
|
|
|
5
592
|
### Minor Changes
|
|
@@ -651,7 +1238,7 @@ picker on` fetches this project's own access and writes the three grounder
|
|
|
651
1238
|
|
|
652
1239
|
That screen named one command:
|
|
653
1240
|
|
|
654
|
-
|
|
1241
|
+
npx @unotest/web bundle push --run --env ENVIRONMENT
|
|
655
1242
|
|
|
656
1243
|
In a project that has just been created, the command refuses with `no box
|
|
657
1244
|
address` before it does anything: the project holds neither the box's address
|
|
@@ -660,8 +1247,8 @@ address` before it does anything: the project holds neither the box's address
|
|
|
660
1247
|
|
|
661
1248
|
The screen now names both steps, in the order they have to happen:
|
|
662
1249
|
|
|
663
|
-
|
|
664
|
-
|
|
1250
|
+
npx @unotest/web box access larch
|
|
1251
|
+
npx @unotest/web bundle push --run --env ENVIRONMENT
|
|
665
1252
|
|
|
666
1253
|
The box's own name is filled in when its licence carries one. `box access`
|
|
667
1254
|
with no name refuses, and its refusal offers to create a box — to somebody who
|