@unotest/protocol 0.18.0 → 0.20.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 +83 -0
- package/dist/index.d.ts +1124 -963
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -68,6 +68,18 @@ declare const STEPS_FILE = "steps.jsonl";
|
|
|
68
68
|
/** mtime-updated periodically by the runner; viewer treats run as
|
|
69
69
|
* `interrupted` after `> 30s` without mtime change (per design-v0). */
|
|
70
70
|
declare const HEARTBEAT_FILE = "heartbeat";
|
|
71
|
+
/** Per-day derived index: one JSON line per run, inside the day's shard
|
|
72
|
+
* directory (`<runsRoot>/YYYY/MM/DD/_day.jsonl`). Derived from the run
|
|
73
|
+
* manifests beside it — losing it costs a rebuild, never data. */
|
|
74
|
+
declare const DAY_INDEX_FILE = "_day.jsonl";
|
|
75
|
+
/** Directory of per-scenario derived indexes, at the runs root. Holds one
|
|
76
|
+
* JSONL per scenario plus `_latest.json`. Named with a leading underscore
|
|
77
|
+
* so it sorts away from `YYYY` shards and can never collide with one. */
|
|
78
|
+
declare const SCENARIOS_INDEX_DIR = "_scenarios";
|
|
79
|
+
/** One entry per scenario: its most recent run, status and failure
|
|
80
|
+
* streak. Feeds the activity-bar badge and the "failing now" section,
|
|
81
|
+
* neither of which may cost a history scan. */
|
|
82
|
+
declare const SCENARIOS_LATEST_FILE = "_latest.json";
|
|
71
83
|
/** Captured child-process stdout when run is spawned by viewer. */
|
|
72
84
|
declare const STDOUT_FILE = "stdout.log";
|
|
73
85
|
/** Captured child-process stderr when run is spawned by viewer. */
|
|
@@ -148,1043 +160,1192 @@ declare function makeRunId(name: string): string;
|
|
|
148
160
|
* random suffix appended by `makeRunId`. */
|
|
149
161
|
declare function sanitizeRunIdSegment(name: string): string;
|
|
150
162
|
|
|
151
|
-
/**
|
|
152
|
-
* `
|
|
153
|
-
*
|
|
154
|
-
declare
|
|
155
|
-
/**
|
|
156
|
-
*
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
*
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
163
|
+
/** Epoch millis encoded in a runId, or null when the id does not follow
|
|
164
|
+
* `makeRunId`'s shape. Callers must handle null rather than guess — an
|
|
165
|
+
* id we cannot place has no shard. */
|
|
166
|
+
declare function runIdStartedAt(runId: string): number | null;
|
|
167
|
+
/** `YYYY/MM/DD` (UTC) for a timestamp. POSIX separators — these are
|
|
168
|
+
* project-relative artifact paths, not OS paths. */
|
|
169
|
+
declare function shardPathForTimestamp(ms: number): string;
|
|
170
|
+
/** `YYYY/MM/DD` for a runId, or null when the id carries no timestamp. */
|
|
171
|
+
declare function shardPathForRunId(runId: string): string | null;
|
|
172
|
+
/** Project-relative directory of one run: `<runsRoot>/YYYY/MM/DD/<runId>`.
|
|
173
|
+
* Null for an id we cannot place — callers decide whether that is a skip
|
|
174
|
+
* or an error, but nobody may invent a location. */
|
|
175
|
+
declare function runDirFor(runsRoot: string, runId: string): string | null;
|
|
176
|
+
/** Thrown when a runId carries no usable timestamp and the caller cannot
|
|
177
|
+
* proceed without one. Writers hit this only if they were handed an id
|
|
178
|
+
* they did not make. */
|
|
179
|
+
declare class UnplaceableRunIdError extends Error {
|
|
180
|
+
constructor(runId: string);
|
|
181
|
+
}
|
|
182
|
+
/** `runDirFor` for callers that own the id and therefore know it parses.
|
|
183
|
+
* Throws instead of returning null so a writer can never quietly invent
|
|
184
|
+
* a location and split one run across two layouts. */
|
|
185
|
+
declare function requireRunDirFor(runsRoot: string, runId: string): string;
|
|
186
|
+
/** Project-relative runs root for a target: `unotest/.runs<suffix>`. */
|
|
187
|
+
declare function projectRunsRoot(suffix?: string): string;
|
|
188
|
+
/** Project-relative directory of one run, shard included. The single
|
|
189
|
+
* place that knows how a run id becomes a path — every writer and reader
|
|
190
|
+
* goes through here, so the layout cannot drift between them. */
|
|
191
|
+
declare function projectRunDirFor(runId: string, suffix?: string): string;
|
|
192
|
+
/** Day directory: `<runsRoot>/YYYY/MM/DD`. */
|
|
193
|
+
declare function shardDirFor(runsRoot: string, ms: number): string;
|
|
194
|
+
/** Walk day-shard paths backwards from `fromMs`, newest first. Used by
|
|
195
|
+
* listing (fill a page from the newest days) and by retention (drop days
|
|
196
|
+
* older than the window) — both want days in order, neither wants to
|
|
197
|
+
* readdir the whole tree. */
|
|
198
|
+
declare function shardDaysDescending(runsRoot: string, fromMs: number, count: number): Generator<string>;
|
|
179
199
|
|
|
180
|
-
/**
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
/** Spawn with debugger enabled (breakpoints from
|
|
189
|
-
* `unotest/.debugger.json` + inline override). */
|
|
190
|
-
debug?: boolean;
|
|
191
|
-
/** Inline breakpoint override as `"line:col"` strings. Only honored
|
|
192
|
-
* when `debug=true`. */
|
|
193
|
-
breakpoints?: string[];
|
|
200
|
+
/** Which kind of locator the in-page picker overlay should retarget to and
|
|
201
|
+
* what DSL snippet to emit on pick. `locator` → an interactive element →
|
|
202
|
+
* `click(<loc>)`; `text`/`visibility` → any named/interactive element →
|
|
203
|
+
* `assertText`/`assertVisible`. See docs/recoder/plan.md. */
|
|
204
|
+
type PickerMode = "locator" | "text" | "visibility";
|
|
205
|
+
interface DbgCommandStep {
|
|
206
|
+
id: string;
|
|
207
|
+
cmd: "step";
|
|
194
208
|
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
* dir, linked to this parent via `parentRunId` in their manifests. */
|
|
199
|
-
interface CollectionRunRequest {
|
|
200
|
-
kind: "collection";
|
|
201
|
-
/** Collection `name` — filename stem under `_collections/`. */
|
|
202
|
-
collection: string;
|
|
203
|
-
/** Forward to each child scenario. */
|
|
204
|
-
headed?: boolean;
|
|
205
|
-
/** Worker count for parallel scenario execution. Default `1`
|
|
206
|
-
* (serial). The adapter passes it to the CLI; the runner enforces. */
|
|
207
|
-
workers?: number;
|
|
208
|
-
/** Stop on first failure. Default `false` (continue-on-fail). */
|
|
209
|
-
bail?: boolean;
|
|
209
|
+
interface DbgCommandContinue {
|
|
210
|
+
id: string;
|
|
211
|
+
cmd: "continue";
|
|
210
212
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
interface AuthoringRunRequest {
|
|
215
|
-
kind: "authoring";
|
|
216
|
-
/** Scenario `name` — file stem relative to `unotest/e2e/`. A stub is
|
|
217
|
-
* created if the file doesn't exist yet. */
|
|
218
|
-
scenario: string;
|
|
219
|
-
/** Authoring is visible by default — the human must see the live browser. */
|
|
220
|
-
headed?: boolean;
|
|
213
|
+
interface DbgCommandPause {
|
|
214
|
+
id: string;
|
|
215
|
+
cmd: "pause";
|
|
221
216
|
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
declare const RUN_MANIFEST_FILE = "manifest.json";
|
|
229
|
-
interface RunManifest {
|
|
230
|
-
/** Format-version of this manifest. Bump only on breaking schema
|
|
231
|
-
* changes — additive optional fields don't require it. Starts at 1. */
|
|
232
|
-
schemaVersion: 1;
|
|
233
|
-
/** Identifies which RunRequest variant produced this run. */
|
|
234
|
-
kind: RunKind;
|
|
235
|
-
/** Original (un-sanitized) `ref` from the request — scenario `name`
|
|
236
|
-
* for `kind:"scenario"`, collection `name` for `kind:"collection"`.
|
|
237
|
-
* The `runId` directory uses a sanitized form; this carries the
|
|
238
|
-
* human-readable original. */
|
|
239
|
-
ref: string;
|
|
240
|
-
/** Run-id of the parent collection-run when this is a child scenario
|
|
241
|
-
* spawned inside a collection. Absent for top-level scenario-runs
|
|
242
|
-
* and for collection-runs themselves. */
|
|
243
|
-
parentRunId?: string;
|
|
244
|
-
/** Adapter `name` that produced this run (`"@unotest/web"`,
|
|
245
|
-
* `"@unotest/mobile"`). Lets the viewer pick rendering details for
|
|
246
|
-
* runner-specific bundle contents without runner branches in core
|
|
247
|
-
* logic. */
|
|
248
|
-
adapter: string;
|
|
249
|
-
/** Wallclock at runner start. Distinct from the first event's `t`
|
|
250
|
-
* (which may have leading delay for adapter setup). */
|
|
251
|
-
startedAt: number;
|
|
252
|
-
/** Opt-in artifact-capture flags as resolved from the runner's env at
|
|
253
|
-
* start (`UNOTEST_CONSOLE` / `UNOTEST_NETWORK`). Recorded here so the
|
|
254
|
-
* viewer can tell "capture was disabled" apart from "capture was on but
|
|
255
|
-
* the run aborted before writing console.json / network.json" — a
|
|
256
|
-
* distinction the mere presence of those files can't make. Optional: runs
|
|
257
|
-
* produced before this field existed simply omit it, and the viewer falls
|
|
258
|
-
* back to a file-presence heuristic. */
|
|
259
|
-
capture?: {
|
|
260
|
-
console: boolean;
|
|
261
|
-
network: boolean;
|
|
262
|
-
};
|
|
217
|
+
interface DbgCommandSetBp {
|
|
218
|
+
id: string;
|
|
219
|
+
cmd: "set-bp";
|
|
220
|
+
line: number;
|
|
221
|
+
col: number;
|
|
263
222
|
}
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
* e.g. `unotest/e2e/foo.test.js`. Also a key in `files`. */
|
|
270
|
-
entry: string;
|
|
271
|
-
/** Project-root-relative posix path → full source text. Includes the
|
|
272
|
-
* entry file and every helper file pulled in by the loader. */
|
|
273
|
-
files: Record<string, string>;
|
|
223
|
+
interface DbgCommandClearBp {
|
|
224
|
+
id: string;
|
|
225
|
+
cmd: "clear-bp";
|
|
226
|
+
line: number;
|
|
227
|
+
col: number;
|
|
274
228
|
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
* lifecycle; the adapter merely tells it WHAT to spawn. */
|
|
279
|
-
interface IRunnerAdapter {
|
|
280
|
-
/** Human-readable identity for logs and error messages
|
|
281
|
-
* (e.g. `"@unotest/web"`). NOT used for control flow — viewer
|
|
282
|
-
* must not branch on this. */
|
|
283
|
-
readonly name: string;
|
|
284
|
-
/** Per-target artifact suffix — the single axis that scopes EVERY
|
|
285
|
-
* per-target filesystem name (M-10): `""` for web (the first target
|
|
286
|
-
* keeps the bare names), `"-mobile"` for mobile, `"-unity"` for a
|
|
287
|
-
* future unity runner. Derived names (via `paths.ts` helpers):
|
|
288
|
-
* scenario tree `e2e<suffix>`, run artifacts `.runs<suffix>`, debug
|
|
289
|
-
* logs `.debug<suffix>`, breakpoints `.debugger<suffix>.json`, env
|
|
290
|
-
* overlays `.env<suffix>`. The viewer scopes discovery/runs/
|
|
291
|
-
* breakpoints by the active target's suffix so no folder name is
|
|
292
|
-
* ever hardcoded in viewer source (OCP: a new target just declares
|
|
293
|
-
* its suffix). */
|
|
294
|
-
readonly suffix: string;
|
|
295
|
-
/** Debug-toolbar capabilities the viewer gates its controls on (OCP — a
|
|
296
|
-
* new target just declares what it supports; viewer never branches on the
|
|
297
|
-
* runner name). See `DebugCapabilities`. */
|
|
298
|
-
readonly debug: DebugCapabilities;
|
|
299
|
-
/** Which `RunRequest.kind` values this adapter can handle. Viewer
|
|
300
|
-
* rejects unsupported kinds with `UnsupportedRunKindError` before
|
|
301
|
-
* building argv. */
|
|
302
|
-
readonly supportedKinds: ReadonlyArray<RunKind>;
|
|
303
|
-
/** Absolute path to the bin entry that should be spawned with the
|
|
304
|
-
* current Node binary: `spawn(process.execPath, [resolveBin(), ...buildArgs(req)])`.
|
|
305
|
-
* Throws if the runner package isn't installed reachably from the
|
|
306
|
-
* viewer's project root. */
|
|
307
|
-
resolveBin(): string;
|
|
308
|
-
/** Argv to pass after the bin path. Pure — depends only on `req`.
|
|
309
|
-
* Example for web: `["e2e", "smoke", "--debug", "--break", "10:4"]`. */
|
|
310
|
-
buildArgs(req: RunRequest): string[];
|
|
311
|
-
/** Environment overrides merged on top of `process.env` by the
|
|
312
|
-
* caller. Pure — no `process.env` reads here. Example for web:
|
|
313
|
-
* `{ UNOTEST_HEADED: "1" }`. Worker-isolation env
|
|
314
|
-
* (`UNOTEST_WORKER_INDEX/COUNT`) is NOT the adapter's
|
|
315
|
-
* responsibility — see `D-C4` in the collections plan. */
|
|
316
|
-
buildEnv(req: RunRequest): NodeJS.ProcessEnv;
|
|
317
|
-
/** Argv (after the bin path) that starts this runner's LONG-LIVED
|
|
318
|
-
* app-under-test holder (M-12/M-16) — the process that prepares the
|
|
319
|
-
* app (web: spawns the dev server; mobile: boots devices + installs
|
|
320
|
-
* the build), emits `AppServerEvent` JSON lines on stdout, and tears
|
|
321
|
-
* down what it owns when terminated. Absent → the runner has no
|
|
322
|
-
* app-launch capability and the viewer hides the feature for this
|
|
323
|
-
* target. Example: `["app-server"]`. */
|
|
324
|
-
appServerArgs?(): string[];
|
|
325
|
-
/** Path inputs the App panel renders for this target (M-17) — each
|
|
326
|
-
* spec becomes an input + file-picker button whose value is saved
|
|
327
|
-
* into the target's env file (`envKey`). The viewer stays
|
|
328
|
-
* platform-blind: extensions/labels are vocab the adapter owns
|
|
329
|
-
* (mobile iOS: `.app`; a future Android entry adds `.apk`). */
|
|
330
|
-
appServerPickers?(): readonly AppPathPickerSpec[];
|
|
331
|
-
/** Host-OS requirement for THIS runner. The viewer resolves it against
|
|
332
|
-
* the server's real `process.platform` and gates the switcher: an
|
|
333
|
-
* unsupported host shows `reason` and blocks activation (e.g. mobile is
|
|
334
|
-
* macOS-only — iOS Simulator is Apple-licensed). Absent → runs on any
|
|
335
|
-
* host (web). OCP: a new platform-bound runner just declares this; the
|
|
336
|
-
* viewer never hardcodes which target needs which OS. */
|
|
337
|
-
readonly host?: HostRequirement;
|
|
338
|
-
/** Structured environment preflight ("doctor") — what must be installed/
|
|
339
|
-
* configured for this runner to actually run here (mobile: Xcode +
|
|
340
|
-
* simulators; web: Node + a system browser). Pure-ish but MAY shell out
|
|
341
|
-
* to probe the host, hence async; the viewer invokes it on demand (a
|
|
342
|
-
* "Check" button), never on every switch. Absent → nothing to verify
|
|
343
|
-
* beyond the package being installed. The viewer renders the returned
|
|
344
|
-
* list verbatim (severity + message + fix `detail`) — it owns no runner
|
|
345
|
-
* vocab. */
|
|
346
|
-
doctor?(ctx: DoctorContext): Promise<DoctorCheck[]>;
|
|
229
|
+
interface DbgCommandAbort {
|
|
230
|
+
id: string;
|
|
231
|
+
cmd: "abort";
|
|
347
232
|
}
|
|
348
|
-
/**
|
|
349
|
-
*
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
*
|
|
353
|
-
*
|
|
354
|
-
*
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
/** Human-readable remediation when not `ok` — a command or concrete
|
|
362
|
-
* next step. Surfaced under the message in the UI. */
|
|
363
|
-
detail?: string;
|
|
233
|
+
/** Start (or, if already active, re-mode) the in-page picker on the live
|
|
234
|
+
* paused page. `execute=false` (default) is capture-only: clicks are
|
|
235
|
+
* swallowed, never run — picks just record a locator/assertion.
|
|
236
|
+
* `execute=true` turns the overlay into a Playwright-style recorder: real
|
|
237
|
+
* clicks / typing / selects RUN on the page and are recorded as DSL actions
|
|
238
|
+
* (`click`/`fill`/`press`/`selectOption`/`check`). In execute mode the
|
|
239
|
+
* `text`/`visibility` modes still record an assertion without running the
|
|
240
|
+
* click (swap mode to drop an assertion mid-flow). */
|
|
241
|
+
interface DbgCommandPickerStart {
|
|
242
|
+
id: string;
|
|
243
|
+
cmd: "picker:start";
|
|
244
|
+
mode: PickerMode;
|
|
245
|
+
execute?: boolean;
|
|
364
246
|
}
|
|
365
|
-
/**
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
/** Absolute path to the project root (the dir containing `unotest/`) —
|
|
370
|
-
* for project-aware checks (mobile reads `package.json` to hint RN/Expo). */
|
|
371
|
-
projectRoot: string;
|
|
247
|
+
/** Tear down the picker overlay. */
|
|
248
|
+
interface DbgCommandPickerStop {
|
|
249
|
+
id: string;
|
|
250
|
+
cmd: "picker:stop";
|
|
372
251
|
}
|
|
373
|
-
/**
|
|
374
|
-
*
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
reason: string;
|
|
382
|
-
}
|
|
383
|
-
/** What a runner's debug session supports — drives which debug-toolbar
|
|
384
|
-
* controls the viewer shows. A target that lacks a capability gets the
|
|
385
|
-
* control hidden (no dead buttons). */
|
|
386
|
-
interface DebugCapabilities {
|
|
387
|
-
/** In-page locator picker / recorder — click an element in the SHARED,
|
|
388
|
-
* inspectable page to insert its locator/assertion (web). Runners that
|
|
389
|
-
* drive an opaque device without a clickable DOM (mobile via WDA) set this
|
|
390
|
-
* false → the viewer hides the picker cluster (Capture/Execute +
|
|
391
|
-
* target/A/eye). NOTE: distinct from `attach` — the wheel + agent join
|
|
392
|
-
* apply to mobile too. */
|
|
393
|
-
picker: boolean;
|
|
394
|
-
/** Collaborative attach: a second client (the MCP agent) can join the live
|
|
395
|
-
* debug session and co-drive it, coordinated by the control wheel. True for
|
|
396
|
-
* web (shared browser) AND mobile (shared device via WDA). The viewer shows
|
|
397
|
-
* the wheel / "who's driving" badge when set. */
|
|
398
|
-
attach: boolean;
|
|
399
|
-
/** A failure-pause is resumable as a RETRY of the failed step (mobile D-17:
|
|
400
|
-
* the user fixes the device/app state, Continue re-executes the same
|
|
401
|
-
* statement). When false (web: resume past a failure means "skip & pretend
|
|
402
|
-
* it passed"), the viewer hides Continue/Step on a failure-pause so the
|
|
403
|
-
* only action is Stop. */
|
|
404
|
-
resumeFromFailure: boolean;
|
|
405
|
-
}
|
|
406
|
-
/** One path-input row in the viewer's App panel (M-17). */
|
|
407
|
-
interface AppPathPickerSpec {
|
|
408
|
-
/** Env var the picked path is saved to (e.g. `"APP_PATH"`). Routed to
|
|
409
|
-
* the target's env file by the viewer's variables API (M-11). */
|
|
410
|
-
envKey: string;
|
|
411
|
-
/** Selectable leaf extensions (lowercase, with dot). macOS `.app`
|
|
412
|
-
* bundles are directories — the file browser treats a matching dir
|
|
413
|
-
* as a selectable leaf. */
|
|
414
|
-
extensions: readonly string[];
|
|
415
|
-
/** Human label rendered above the input (e.g. `"iOS app bundle"`). */
|
|
416
|
-
label: string;
|
|
417
|
-
}
|
|
418
|
-
/** Thrown by the viewer when a `RunRequest.kind` isn't in the
|
|
419
|
-
* adapter's `supportedKinds`. The exception filter maps to HTTP 400
|
|
420
|
-
* with the kind name + adapter name in the message. */
|
|
421
|
-
declare class UnsupportedRunKindError extends Error {
|
|
422
|
-
constructor(kind: RunKind, adapterName: string);
|
|
252
|
+
/** On-demand: capture the current page outline (what the picker hit-tests
|
|
253
|
+
* against) and write it to `picker-snapshot.txt`. Lets the viewer's Snapshot
|
|
254
|
+
* tab show / refresh the snapshot without arming the picker. Cheap — runs
|
|
255
|
+
* only when the user opens/refreshes the tab (the outline is expensive on
|
|
256
|
+
* huge pages, so it is never captured on every pause). */
|
|
257
|
+
interface DbgCommandSnapshot {
|
|
258
|
+
id: string;
|
|
259
|
+
cmd: "snapshot";
|
|
423
260
|
}
|
|
424
|
-
/**
|
|
425
|
-
*
|
|
426
|
-
|
|
427
|
-
|
|
261
|
+
/** The DSL line a recorded action / pick renders to. Mirrors the in-page
|
|
262
|
+
* recorder's verbs. `locator` → bare locator; `assertText`/`assertVisible`
|
|
263
|
+
* → assertions; the rest → Playwright-style actions. */
|
|
264
|
+
type RecordedActionKind = "locator" | "assertText" | "assertVisible" | "click" | "fill" | "press" | "selectOption" | "check" | "uncheck";
|
|
265
|
+
/** Agent-driven, ref-safe record (collaborative authoring). The agent picks
|
|
266
|
+
* an element by `ref` (from a snapshot of the SHARED page) + an action; the
|
|
267
|
+
* runner resolves `ref→locator` (`RefResolver`) and emits a `locator:picked`
|
|
268
|
+
* event the viewer inserts — so the agent NEVER hand-writes a locator. Same
|
|
269
|
+
* resolution/insertion pipeline as the human's in-page picker. */
|
|
270
|
+
interface DbgCommandRecord {
|
|
271
|
+
id: string;
|
|
272
|
+
cmd: "record";
|
|
273
|
+
ref: string;
|
|
274
|
+
action: RecordedActionKind;
|
|
275
|
+
/** Typed/selected value for `fill` / `selectOption`. */
|
|
276
|
+
value?: string;
|
|
277
|
+
/** Key for `press` (e.g. "Enter", "Control+A"). */
|
|
278
|
+
key?: string;
|
|
279
|
+
/** Expected text for `assertText` (else the element's textContent is used). */
|
|
280
|
+
text?: string;
|
|
281
|
+
/** Where the viewer drops the line. Default `append` (agent records a flow). */
|
|
282
|
+
placement?: "cursor" | "append";
|
|
428
283
|
}
|
|
429
|
-
/**
|
|
430
|
-
* `
|
|
431
|
-
*
|
|
432
|
-
|
|
433
|
-
|
|
284
|
+
/** Collaborative control token (Slice 3). Sets who may act on the shared
|
|
285
|
+
* page: `agent` lets the agent record; `human` (default) blocks the agent.
|
|
286
|
+
* Written by the viewer (Take control button) or the agent (take_wheel). */
|
|
287
|
+
interface DbgCommandWheel {
|
|
288
|
+
id: string;
|
|
289
|
+
cmd: "wheel";
|
|
290
|
+
owner: "agent" | "human";
|
|
434
291
|
}
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
*
|
|
438
|
-
*
|
|
439
|
-
*
|
|
440
|
-
*
|
|
441
|
-
|
|
442
|
-
* function test_<...>() { ... }
|
|
443
|
-
*
|
|
444
|
-
* All three lines are required; a file without a complete header is
|
|
445
|
-
* rejected by the discovery service as parse-error (no legacy fallback). */
|
|
446
|
-
interface ScenarioMeta {
|
|
447
|
-
/** Slug from `// id-<slug>`. Stable test identity across rename. */
|
|
292
|
+
/** Evaluate a DSL locator STRING against the live (paused or running) page and
|
|
293
|
+
* report what it matches — count, the N a trailing .first()/.last()/.nth()
|
|
294
|
+
* collapses, and per-match identity — via a `probe:result` event. The runner
|
|
295
|
+
* parses `locator` with the SAME DSL parser the agent's `check_locator` uses,
|
|
296
|
+
* so agent and human probe identically. READ-ONLY (never acts on the page),
|
|
297
|
+
* so it is intentionally NOT wheel-gated: any client may probe at any time. */
|
|
298
|
+
interface DbgCommandProbe {
|
|
448
299
|
id: string;
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
* Examples: `smoke-welcome`, `auth/signin-flow`.
|
|
456
|
-
* Source-of-truth identity for CLI invocations and tab keys. */
|
|
457
|
-
name: string;
|
|
458
|
-
/** Project-relative path to the source file. */
|
|
459
|
-
path: string;
|
|
300
|
+
cmd: "probe";
|
|
301
|
+
/** Locator exactly as written in a test, e.g.
|
|
302
|
+
* getByRole('row').filter({hasText: 'Uma Quinn'}).first(). */
|
|
303
|
+
locator: string;
|
|
304
|
+
/** Cap on the number of match details returned (default 20). */
|
|
305
|
+
limit?: number;
|
|
460
306
|
}
|
|
307
|
+
type DbgCommand = DbgCommandStep | DbgCommandContinue | DbgCommandPause | DbgCommandSetBp | DbgCommandClearBp | DbgCommandAbort | DbgCommandPickerStart | DbgCommandPickerStop | DbgCommandSnapshot | DbgCommandRecord | DbgCommandWheel | DbgCommandProbe;
|
|
308
|
+
type DbgCommandKind = DbgCommand["cmd"];
|
|
461
309
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
/**
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
310
|
+
interface ProbeMatch {
|
|
311
|
+
/** 0-based position among the locator's matches. */
|
|
312
|
+
index: number;
|
|
313
|
+
role?: string;
|
|
314
|
+
name?: string;
|
|
315
|
+
/** Trimmed/clipped textContent of the element. */
|
|
316
|
+
text?: string;
|
|
317
|
+
testId?: string;
|
|
318
|
+
href?: string;
|
|
319
|
+
/** Currently visible? Non-waiting check (a match can exist but be off-screen). */
|
|
320
|
+
visible: boolean;
|
|
471
321
|
}
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
* writer (authoritative) and the client form (pre-flight) so both agree.
|
|
480
|
-
* Pure — safe to bundle in the browser.
|
|
481
|
-
*
|
|
482
|
-
* Rejects: empty, `.`/`..`, `.`-prefix (hidden), `_`-prefix (reserved for
|
|
483
|
-
* `_helpers`/`_collections`/templates), path separators + FS-unsafe
|
|
484
|
-
* punctuation + control chars. */
|
|
485
|
-
declare function validateSegmentName(name: string): string | null;
|
|
486
|
-
|
|
487
|
-
/** Session profile — the kind of process a tab spawns. Mirrored by the
|
|
488
|
-
* viewer's TerminalStore.TermKind. */
|
|
489
|
-
type TerminalKind = "local" | "claude" | "codex";
|
|
490
|
-
interface TerminalSpawnMessage {
|
|
491
|
-
type: "spawn";
|
|
492
|
-
id: string;
|
|
493
|
-
kind: TerminalKind;
|
|
494
|
-
cols: number;
|
|
495
|
-
rows: number;
|
|
322
|
+
interface ProbePinned {
|
|
323
|
+
/** The terminal index op that collapsed a multi-match to a single element. */
|
|
324
|
+
op: "first" | "last" | "nth";
|
|
325
|
+
/** How many elements the chain matched BEFORE the terminal op — i.e. how many
|
|
326
|
+
* the .first()/.last()/.nth() silently picked from. `ofCount > 1` flags an
|
|
327
|
+
* index op hiding ambiguity. */
|
|
328
|
+
ofCount: number;
|
|
496
329
|
}
|
|
497
|
-
interface
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
/**
|
|
501
|
-
|
|
330
|
+
interface ProbeResult {
|
|
331
|
+
/** Human-readable chain echo. NOT a selector. */
|
|
332
|
+
chain: string;
|
|
333
|
+
/** Elements the full chain matches. */
|
|
334
|
+
count: number;
|
|
335
|
+
/** Present only when the chain ends in first/last/nth. */
|
|
336
|
+
pinned?: ProbePinned;
|
|
337
|
+
/** Details of up to the requested limit of matches. */
|
|
338
|
+
matches: ProbeMatch[];
|
|
339
|
+
/** True count before truncation (equal to `count`). */
|
|
340
|
+
totalCount: number;
|
|
341
|
+
truncated: boolean;
|
|
342
|
+
/** Actionable hints: nothing matched, hidden ambiguity, truncation, stale ref. */
|
|
343
|
+
warnings: string[];
|
|
502
344
|
}
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
345
|
+
|
|
346
|
+
/** Terminal outcome reported by the runner via `run:finished`. */
|
|
347
|
+
type RunOutcome = "completed" | "failed" | "aborted" | "interrupted";
|
|
348
|
+
/** Scenario-level run-id. Format opaque to consumers; treat as string. */
|
|
349
|
+
type RunId = string;
|
|
350
|
+
/** Common fields on every event. */
|
|
351
|
+
interface RunArtifactBase {
|
|
352
|
+
/** Wallclock timestamp (Date.now()) at emit. */
|
|
353
|
+
t: number;
|
|
508
354
|
}
|
|
509
|
-
interface
|
|
510
|
-
|
|
511
|
-
|
|
355
|
+
interface RunStartedEvent extends RunArtifactBase {
|
|
356
|
+
kind: "run:started";
|
|
357
|
+
/** Same as the `<runId>` directory name; included for self-describing
|
|
358
|
+
* events when reassembled out-of-band. */
|
|
359
|
+
runId: RunId;
|
|
360
|
+
/** Scenario `name` (file stem relative to `e2e/`). */
|
|
361
|
+
scenario: string;
|
|
512
362
|
}
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
363
|
+
interface StepStartedEvent extends RunArtifactBase {
|
|
364
|
+
kind: "step:started";
|
|
365
|
+
/** Project-root-relative posix path of the file this step lives in
|
|
366
|
+
* (entry or a helper). Matches a key in the run's `sources.json`.
|
|
367
|
+
* The viewer slices the source text by `line` from the snapshot. */
|
|
368
|
+
file: string;
|
|
369
|
+
/** Line number of the DSL call in the source file (1-indexed). */
|
|
370
|
+
line: number;
|
|
371
|
+
/** Column number (1-indexed since 0.11 — the DSL lexer's token
|
|
372
|
+
* columns; earlier releases emitted 0-indexed columns for
|
|
373
|
+
* word-initial statements). */
|
|
374
|
+
col: number;
|
|
519
375
|
}
|
|
520
|
-
interface
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
376
|
+
interface StepOkEvent extends RunArtifactBase {
|
|
377
|
+
kind: "step:ok";
|
|
378
|
+
/** Wall-clock duration of the step that just finished. */
|
|
379
|
+
durationMs: number;
|
|
524
380
|
}
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
381
|
+
interface StepFailEvent extends RunArtifactBase {
|
|
382
|
+
kind: "step:fail";
|
|
383
|
+
/** Human-readable error message (single line preferred). */
|
|
384
|
+
error: string;
|
|
385
|
+
/** Error class name (e.g. `"AssertionError"`, `"LocatorError"`,
|
|
386
|
+
* `"TimeoutError"`). Lets the viewer's `ErrorCard` show a typed
|
|
387
|
+
* pill instead of a generic "Error" label. Optional because some
|
|
388
|
+
* thrown errors lack a meaningful class name. */
|
|
389
|
+
errorName?: string;
|
|
390
|
+
/** Project-relative path to a Playwright trace zip, if captured. */
|
|
391
|
+
trace?: string;
|
|
531
392
|
}
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
* UNOTEST_BUSY_SCENARIOS, UNOTEST_WORKER_*), session-log internals
|
|
545
|
-
* (UNOTEST_SESSION_LOG_*), and path bootstrap (UNOTEST_DIR,
|
|
546
|
-
* UNOTEST_PROJECT_ROOT, UNOTEST_CONFIG, UNOTEST_VIEWER_NO_OPEN). A user would
|
|
547
|
-
* never put those in `unotest/.env`. */
|
|
548
|
-
declare const RUNNER_CONFIG_KEYS: ReadonlySet<string>;
|
|
549
|
-
/** True when `name` is a key the runner consumes as its own configuration. */
|
|
550
|
-
declare function isRunnerConfigKey(name: string): boolean;
|
|
551
|
-
/** Group a variable for the viewer. Secrecy from the source file; runner-vs-app
|
|
552
|
-
* from the owned-key set. */
|
|
553
|
-
declare function categorizeVariable(v: {
|
|
554
|
-
name: string;
|
|
555
|
-
source: "env" | "secrets";
|
|
556
|
-
}): VariableCategory;
|
|
557
|
-
/** An external scenario variable is referenced by a bare UPPER_SNAKE
|
|
558
|
-
* identifier (`APP_BASE_URL`). App / secret names must match this to be
|
|
559
|
-
* resolvable from a scenario; runner keys are validated against the set
|
|
560
|
-
* above instead. Convention only — no value inspection. */
|
|
561
|
-
declare function isExternalVarName(name: string): boolean;
|
|
562
|
-
|
|
563
|
-
/** Parse `KEY=VALUE` env-file CONTENT into a plain record. Pure — the
|
|
564
|
-
* caller does the fs read. Blank lines and `#` comments are skipped;
|
|
565
|
-
* balanced surrounding single/double quotes are stripped; keys not
|
|
566
|
-
* matching `[A-Za-z_][A-Za-z0-9_]*` are ignored. Shared by the web and
|
|
567
|
-
* mobile runners' layered env loaders (M-11) so both parse `.env` files
|
|
568
|
-
* identically. */
|
|
569
|
-
declare function parseEnvContent(raw: string): Record<string, string>;
|
|
570
|
-
/** Quote on write only when the value would otherwise be ambiguous (spaces,
|
|
571
|
-
* `#`, quotes, `=`, backslash, leading backtick, or newlines). Plain tokens
|
|
572
|
-
* stay unquoted. Mirrors the viewer env-dotenv serializer. */
|
|
573
|
-
declare function serializeEnvValue(value: string): string;
|
|
574
|
-
/** Active (non-commented) assignment keys present in env-file `content`.
|
|
575
|
-
* Used to detect duplicates / cross-file shadowing before an add. */
|
|
576
|
-
declare function envKeys(content: string): Set<string>;
|
|
577
|
-
/**
|
|
578
|
-
* Insert `KEY=VALUE` into env-file `content`. With `sectionLabel`, the line
|
|
579
|
-
* goes into the first `# --- <label> --- ` divider section — after the last
|
|
580
|
-
* assignment there (or its description comments if it has no vars yet). The
|
|
581
|
-
* label is matched as a whole word against DIVIDER lines only, so prose
|
|
582
|
-
* comments are never mistaken for a section. No such section → it is created
|
|
583
|
-
* at end-of-file. With no label the line is appended. Does NOT dedup — reject
|
|
584
|
-
* duplicates before calling.
|
|
585
|
-
*/
|
|
586
|
-
declare function insertEnvVar(content: string, key: string, value: string, sectionLabel?: string): string;
|
|
587
|
-
/**
|
|
588
|
-
* Remove the assignment line for `key`, leaving every comment and other line
|
|
589
|
-
* untouched (we do not guess which comments "belong" to the key). Returns the
|
|
590
|
-
* new content and whether anything was removed.
|
|
591
|
-
*/
|
|
592
|
-
declare function removeEnvVar(content: string, key: string): {
|
|
593
|
-
content: string;
|
|
594
|
-
removed: boolean;
|
|
595
|
-
};
|
|
596
|
-
|
|
597
|
-
/** Which group the user is adding into. App/runner → `.env`, secret →
|
|
598
|
-
* `.secrets`. Mirrors VariableCategory as an INPUT (the user's choice). */
|
|
599
|
-
type VariableKind = VariableCategory;
|
|
600
|
-
interface AddVariablePlanInput {
|
|
601
|
-
name: string;
|
|
602
|
-
kind: VariableKind;
|
|
603
|
-
/** Current text of `unotest/.env` (`""` if absent). */
|
|
604
|
-
envContent: string;
|
|
605
|
-
/** Current text of `unotest/.secrets` (`""` if absent). */
|
|
606
|
-
secretsContent: string;
|
|
393
|
+
interface ScreenshotEvent extends RunArtifactBase {
|
|
394
|
+
kind: "screenshot";
|
|
395
|
+
/** Project-relative path to the PNG (under `unotest/.runs/<runId>/screenshot/`). */
|
|
396
|
+
path: string;
|
|
397
|
+
/** Step identity for captures bound to a specific step (per-step
|
|
398
|
+
* screenshots under `UNOTEST_STEP_SCREENSHOTS`). Same semantics as
|
|
399
|
+
* `StepStartedEvent` — the viewer joins on `line:col`. Absent for
|
|
400
|
+
* unbound captures (`screenshot()` DSL calls); consumers may fall
|
|
401
|
+
* back to attributing those to the step in flight at emit time. */
|
|
402
|
+
file?: string;
|
|
403
|
+
line?: number;
|
|
404
|
+
col?: number;
|
|
607
405
|
}
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
*
|
|
637
|
-
*
|
|
638
|
-
*
|
|
639
|
-
*
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
406
|
+
/** Emitted ONCE after a failing run when the runner has finished writing
|
|
407
|
+
* the D-16 failure bundle to disk. Viewer's inspector reads
|
|
408
|
+
* `bundlePath` to fetch artifacts (screenshot, semantic DOM, network,
|
|
409
|
+
* trace) via `/api/runs/<runId>/asset?path=...`. The bundle directory
|
|
410
|
+
* is keyed by its own id (`<scenario>__<isoDate>__<stepIdx>`) under
|
|
411
|
+
* `.unotest/failures/`, not under `unotest/.runs/`, because the
|
|
412
|
+
* bundle layout pre-dates per-run dirs and is still consumed by the
|
|
413
|
+
* agent-fix MCP tools. */
|
|
414
|
+
interface FailureBundleWrittenEvent extends RunArtifactBase {
|
|
415
|
+
kind: "failure-bundle:written";
|
|
416
|
+
/** Project-relative path to the bundle directory. */
|
|
417
|
+
bundlePath: string;
|
|
418
|
+
/** Project-relative path to the screenshot PNG inside the bundle,
|
|
419
|
+
* when tier-2 capture succeeded. */
|
|
420
|
+
screenshotPath?: string;
|
|
421
|
+
/** Page URL at the moment of failure (same value captured into the
|
|
422
|
+
* bundle's `snapshot.json`/`outline.txt`), for the viewer's ErrorCard. */
|
|
423
|
+
url?: string;
|
|
424
|
+
}
|
|
425
|
+
interface RunFinishedEvent extends RunArtifactBase {
|
|
426
|
+
kind: "run:finished";
|
|
427
|
+
outcome: RunOutcome;
|
|
428
|
+
}
|
|
429
|
+
/** Debugger pause. Reason discriminates:
|
|
430
|
+
* - `breakpoint` — executor halted before a statement whose `line:col` is
|
|
431
|
+
* in `RunOptions.breakpoints`.
|
|
432
|
+
* - `step` — `pauseRequested` flag was set by a `pause` debug command,
|
|
433
|
+
* or the run was started in step-mode and just completed a statement.
|
|
434
|
+
* - `failure` — executor caught an error and `pauseOnFailure` is true.
|
|
435
|
+
*
|
|
436
|
+
* When the runner is hosting a debug session it also writes a parallel
|
|
437
|
+
* `runtime.json` file with the same snapshot. The snapshot is included
|
|
438
|
+
* inline here for replay / single-frame consumers that don't want to do a
|
|
439
|
+
* separate fs read. */
|
|
440
|
+
interface PausedEvent extends RunArtifactBase {
|
|
441
|
+
kind: "paused";
|
|
442
|
+
reason: "breakpoint" | "step" | "failure";
|
|
443
|
+
/** Project-root-relative posix path of the file the pause point lives
|
|
444
|
+
* in (entry or a helper). Matches a key in the run's `sources.json`. */
|
|
445
|
+
file: string;
|
|
643
446
|
line: number;
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
447
|
+
col: number;
|
|
448
|
+
/** Inline snapshot of the runtime state at the pause point. `vars` is
|
|
449
|
+
* per-value JSON-stringified (LocatorValue → "<Locator role=... name=...>"
|
|
450
|
+
* human-readable string) and per-var truncated to 4KB to keep the JSONL
|
|
451
|
+
* line manageable. `callStack` is outermost→innermost user-function frames. */
|
|
452
|
+
snapshot?: {
|
|
453
|
+
vars: Record<string, string>;
|
|
454
|
+
callStack: {
|
|
455
|
+
fn: string;
|
|
456
|
+
file: string;
|
|
457
|
+
line: number;
|
|
458
|
+
}[];
|
|
459
|
+
};
|
|
656
460
|
}
|
|
657
|
-
/**
|
|
658
|
-
*
|
|
659
|
-
*
|
|
660
|
-
*
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
*
|
|
672
|
-
*
|
|
673
|
-
|
|
461
|
+
/** Emitted by the in-page locator picker / action recorder (debug-pause)
|
|
462
|
+
* when the user picks an element or performs a recorded action. The runner
|
|
463
|
+
* resolved the picked `ref` to a locator (`RefResolver`) and rendered the
|
|
464
|
+
* DSL snippet. The viewer inserts `snippet` per `placement`.
|
|
465
|
+
* See docs/recoder/plan.md. */
|
|
466
|
+
interface LocatorPickedEvent extends RunArtifactBase {
|
|
467
|
+
kind: "locator:picked";
|
|
468
|
+
mode: PickerMode;
|
|
469
|
+
/** Ready-to-insert DSL line, e.g. a bare locator
|
|
470
|
+
* `getByRole("button", {name: "Sign in"})` (locator mode), an assertion
|
|
471
|
+
* (`assertText(…)`), or a recorded action (`click(…)`, `fill(…)`). */
|
|
472
|
+
snippet: string;
|
|
473
|
+
/** Where the viewer drops the snippet:
|
|
474
|
+
* - `cursor` — at the editor's caret (deliberate picks; user-placed).
|
|
475
|
+
* - `append` — after the previous recorded line (execute-mode action
|
|
476
|
+
* recorder, preserving order). */
|
|
477
|
+
placement: "cursor" | "append";
|
|
478
|
+
/** `stable` (testId / role+name / label / …) or `fragile` (css/nth-only).
|
|
479
|
+
* Lets the viewer flag brittle picks. */
|
|
480
|
+
stability: "stable" | "fragile";
|
|
674
481
|
}
|
|
675
|
-
/**
|
|
676
|
-
*
|
|
677
|
-
*
|
|
678
|
-
interface
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
/**
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
482
|
+
/** Emitted when execution resumes after a `paused` event. The `by` field
|
|
483
|
+
* records which debug command unblocked it — purely informational; the
|
|
484
|
+
* next `step:started` event carries the actual line/col. */
|
|
485
|
+
interface ResumedEvent extends RunArtifactBase {
|
|
486
|
+
kind: "resumed";
|
|
487
|
+
by: "step" | "continue";
|
|
488
|
+
}
|
|
489
|
+
/** Emitted when the collaborative control token changes owner (Slice 3), so
|
|
490
|
+
* the viewer can live-update the "who's driving" badge over WS. */
|
|
491
|
+
interface WheelChangedEvent extends RunArtifactBase {
|
|
492
|
+
kind: "wheel:changed";
|
|
493
|
+
owner: "agent" | "human";
|
|
494
|
+
}
|
|
495
|
+
/** Emitted in response to a `probe` debug command: the result of evaluating a
|
|
496
|
+
* locator string against the live page. `id` echoes the command's id so the
|
|
497
|
+
* client that issued the probe can correlate the reply. READ-ONLY — producing
|
|
498
|
+
* it never changed the page. */
|
|
499
|
+
interface ProbeResultEvent extends RunArtifactBase {
|
|
500
|
+
kind: "probe:result";
|
|
501
|
+
/** Correlates with the issuing `DbgCommandProbe.id`. */
|
|
502
|
+
id: string;
|
|
503
|
+
result: ProbeResult;
|
|
504
|
+
}
|
|
505
|
+
/** A single MCP tool invocation by the agent, surfaced into the session's
|
|
506
|
+
* event stream so the human sees — in the viewer's System console — what the
|
|
507
|
+
* agent called and how it ended. Emitted ONLY while the MCP server is attached
|
|
508
|
+
* to this run (own-browser calls have no session to attach to). Paired:
|
|
509
|
+
* `tool:call` at invocation, `tool:result` at completion, correlated by
|
|
510
|
+
* `callId`. Read-only telemetry — emitting it never touches the page. */
|
|
511
|
+
interface ToolCallEvent extends RunArtifactBase {
|
|
512
|
+
kind: "tool:call";
|
|
513
|
+
/** Correlates with the matching `tool:result` (one MCP call). */
|
|
514
|
+
callId: string;
|
|
515
|
+
/** MCP tool name, e.g. "check_locator", "explore_step". */
|
|
516
|
+
tool: string;
|
|
517
|
+
/** Arguments as a secret-redacted, length-capped JSON string — NOT the raw
|
|
518
|
+
* object. Capped upstream so the line stays a single atomic append. */
|
|
519
|
+
args: string;
|
|
520
|
+
}
|
|
521
|
+
interface ToolResultEvent extends RunArtifactBase {
|
|
522
|
+
kind: "tool:result";
|
|
523
|
+
/** Correlates with the issuing `ToolCallEvent.callId`. */
|
|
524
|
+
callId: string;
|
|
525
|
+
tool: string;
|
|
526
|
+
ok: boolean;
|
|
527
|
+
durationMs: number;
|
|
528
|
+
/** Secret-redacted, length-capped preview of the result text. */
|
|
529
|
+
preview: string;
|
|
530
|
+
/** Present only when `ok` is false. */
|
|
531
|
+
error?: {
|
|
532
|
+
class: string;
|
|
533
|
+
message: string;
|
|
710
534
|
};
|
|
711
535
|
}
|
|
712
|
-
/**
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
fileKind?: "scenario" | "helper";
|
|
536
|
+
/** Discriminated union of every JSONL event the scenario runner emits. */
|
|
537
|
+
type RunArtifact = RunStartedEvent | StepStartedEvent | StepOkEvent | StepFailEvent | ScreenshotEvent | FailureBundleWrittenEvent | PausedEvent | ResumedEvent | LocatorPickedEvent | WheelChangedEvent | ProbeResultEvent | ToolCallEvent | ToolResultEvent | RunFinishedEvent;
|
|
538
|
+
interface CollectionRunStartedEvent extends RunArtifactBase {
|
|
539
|
+
kind: "collection-run:started";
|
|
540
|
+
runId: RunId;
|
|
541
|
+
/** Collection `name` (filename stem). */
|
|
542
|
+
collection: string;
|
|
543
|
+
/** Scenario names that will be executed, in order. */
|
|
544
|
+
scenarios: string[];
|
|
722
545
|
}
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
/** Static, serializable vocabulary for autocomplete and signature
|
|
729
|
-
* help. Stable for the lifetime of the process — safe to cache. */
|
|
730
|
-
getVocab(): DslVocabEntry[];
|
|
731
|
-
/** Full diagnostics for one source string. Pure with respect to the
|
|
732
|
-
* filesystem — all workspace knowledge arrives via `ctx`. */
|
|
733
|
-
validate(source: string, ctx?: DslValidateContext): DslDiagnostic[];
|
|
546
|
+
interface CollectionScenarioStartedEvent extends RunArtifactBase {
|
|
547
|
+
kind: "collection-run:scenario-started";
|
|
548
|
+
/** The scenario-run-id created when this scenario started executing. */
|
|
549
|
+
scenarioRunId: RunId;
|
|
550
|
+
scenario: string;
|
|
734
551
|
}
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
interface
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
/** Captured stdout stream, or null when not piped. */
|
|
745
|
-
readonly stdout: Readable | null;
|
|
746
|
-
/** Captured stderr stream, or null when not piped. */
|
|
747
|
-
readonly stderr: Readable | null;
|
|
748
|
-
/** Register the exit handler. Called once when the process exits. */
|
|
749
|
-
onExit(cb: (code: number | null, signal: NodeJS.Signals | null) => void): void;
|
|
750
|
-
/** Register the spawn-error handler (e.g. bin not found). */
|
|
751
|
-
onError(cb: (err: Error) => void): void;
|
|
752
|
-
/** Terminate the process. Default signal SIGTERM. */
|
|
753
|
-
kill(signal?: NodeJS.Signals): void;
|
|
552
|
+
interface CollectionScenarioFinishedEvent extends RunArtifactBase {
|
|
553
|
+
kind: "collection-run:scenario-finished";
|
|
554
|
+
scenarioRunId: RunId;
|
|
555
|
+
scenario: string;
|
|
556
|
+
outcome: RunOutcome;
|
|
557
|
+
}
|
|
558
|
+
interface CollectionRunFinishedEvent extends RunArtifactBase {
|
|
559
|
+
kind: "collection-run:finished";
|
|
560
|
+
outcome: RunOutcome;
|
|
754
561
|
}
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
/**
|
|
761
|
-
|
|
762
|
-
/**
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
562
|
+
type CollectionRunArtifact = CollectionRunStartedEvent | CollectionScenarioStartedEvent | CollectionScenarioFinishedEvent | CollectionRunFinishedEvent;
|
|
563
|
+
|
|
564
|
+
/** Run a single scenario by its `name` (file stem under `e2e/`). */
|
|
565
|
+
interface ScenarioRunRequest {
|
|
566
|
+
kind: "scenario";
|
|
567
|
+
/** Scenario `name` — file stem relative to `unotest/e2e/`. */
|
|
568
|
+
scenario: string;
|
|
569
|
+
/** Run with browser visible (web) / device window visible (mobile).
|
|
570
|
+
* Adapter maps this to the runner's preferred env / arg. */
|
|
571
|
+
headed?: boolean;
|
|
572
|
+
/** Spawn with debugger enabled (breakpoints from
|
|
573
|
+
* `unotest/.debugger.json` + inline override). */
|
|
574
|
+
debug?: boolean;
|
|
575
|
+
/** Inline breakpoint override as `"line:col"` strings. Only honored
|
|
576
|
+
* when `debug=true`. */
|
|
577
|
+
breakpoints?: string[];
|
|
768
578
|
}
|
|
769
|
-
/**
|
|
770
|
-
*
|
|
771
|
-
*
|
|
772
|
-
|
|
773
|
-
|
|
579
|
+
/** Run a collection — the runner's CLI orchestrates scenarios inside.
|
|
580
|
+
* Viewer doesn't see the per-scenario subprocesses; it sees one direct
|
|
581
|
+
* child plus the artifacts each scenario writes to its own `.runs/`
|
|
582
|
+
* dir, linked to this parent via `parentRunId` in their manifests. */
|
|
583
|
+
interface CollectionRunRequest {
|
|
584
|
+
kind: "collection";
|
|
585
|
+
/** Collection `name` — filename stem under `_collections/`. */
|
|
586
|
+
collection: string;
|
|
587
|
+
/** Forward to each child scenario. */
|
|
588
|
+
headed?: boolean;
|
|
589
|
+
/** Worker count for parallel scenario execution. Default `1`
|
|
590
|
+
* (serial). The adapter passes it to the CLI; the runner enforces. */
|
|
591
|
+
workers?: number;
|
|
592
|
+
/** Stop on first failure. Default `false` (continue-on-fail). */
|
|
593
|
+
bail?: boolean;
|
|
594
|
+
}
|
|
595
|
+
/** Open a live AUTHORING-HOLD session (collaborative model, U1/U3): a
|
|
596
|
+
* headed shared browser + draft stay open with NO running test; the human
|
|
597
|
+
* and agent co-author the draft. Runs `unotest-web author <scenario>`. */
|
|
598
|
+
interface AuthoringRunRequest {
|
|
599
|
+
kind: "authoring";
|
|
600
|
+
/** Scenario `name` — file stem relative to `unotest/e2e/`. A stub is
|
|
601
|
+
* created if the file doesn't exist yet. */
|
|
602
|
+
scenario: string;
|
|
603
|
+
/** Authoring is visible by default — the human must see the live browser. */
|
|
604
|
+
headed?: boolean;
|
|
774
605
|
}
|
|
606
|
+
/** Discriminated union of every run-shape the viewer can request. */
|
|
607
|
+
type RunRequest = ScenarioRunRequest | CollectionRunRequest | AuthoringRunRequest;
|
|
608
|
+
/** All `kind` literals — useful for switch-completeness checks and for
|
|
609
|
+
* adapter `supportedKinds` lists. */
|
|
610
|
+
type RunKind = RunRequest["kind"];
|
|
775
611
|
|
|
776
|
-
/**
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
/**
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
612
|
+
/** Lifecycle stage as inferred by the server when listing runs. */
|
|
613
|
+
type RunStatus = "running" | "passed" | "failed" | "aborted" | "interrupted";
|
|
614
|
+
interface RunMeta {
|
|
615
|
+
runId: RunId;
|
|
616
|
+
kind: RunKind;
|
|
617
|
+
/** Scenario `name` (file stem under `e2e/`) for `kind:"scenario"`,
|
|
618
|
+
* collection `name` (yaml stem) for `kind:"collection"`. */
|
|
619
|
+
ref: string;
|
|
620
|
+
/** Wallclock from the `run:started` event. */
|
|
621
|
+
startedAt: number;
|
|
622
|
+
/** Wallclock from the `run:finished` event, or null if still active. */
|
|
623
|
+
finishedAt: number | null;
|
|
624
|
+
/** Total wall-clock duration, or null if still running. */
|
|
625
|
+
durationMs: number | null;
|
|
626
|
+
/** Status derived from outcome / liveness — `running` while the run's
|
|
627
|
+
* heartbeat is fresh and no `run:finished` has landed. */
|
|
628
|
+
status: RunStatus;
|
|
629
|
+
/** Terminal outcome from `run:finished`, or null if still running. */
|
|
630
|
+
outcome: RunOutcome | null;
|
|
631
|
+
/** Run-id of the parent collection-run when this is a child scenario
|
|
632
|
+
* spawned inside one. Absent for top-level scenarios and for
|
|
633
|
+
* collection-runs themselves. Populated by the writer from the
|
|
634
|
+
* child's `manifest.json`. */
|
|
635
|
+
parentRunId?: string;
|
|
788
636
|
}
|
|
789
637
|
|
|
790
|
-
/**
|
|
791
|
-
*
|
|
792
|
-
*
|
|
793
|
-
|
|
794
|
-
|
|
638
|
+
/** Bumped when a line's shape changes incompatibly. A file whose header
|
|
639
|
+
* carries a different version is discarded and rebuilt rather than
|
|
640
|
+
* half-parsed. */
|
|
641
|
+
declare const RUN_INDEX_SCHEMA_VERSION = 2;
|
|
642
|
+
/** How many past outcomes a scenario's rolling window keeps. Twelve is
|
|
643
|
+
* enough to see a flake and small enough that the whole field of them
|
|
644
|
+
* fits one JSON file. Exported so the fold and everything that renders
|
|
645
|
+
* the window agree on one number. */
|
|
646
|
+
declare const SCENARIO_RECENT_WINDOW = 12;
|
|
647
|
+
/** First line of every index JSONL. */
|
|
648
|
+
interface RunIndexHeader {
|
|
649
|
+
v: number;
|
|
650
|
+
}
|
|
651
|
+
/** One run, as stored in a day index. Identical to what the API returns,
|
|
652
|
+
* deliberately: the index exists to avoid recomputing `RunMeta`, so
|
|
653
|
+
* storing anything narrower would just force a second read. */
|
|
654
|
+
type RunIndexEntry = RunMeta;
|
|
655
|
+
/** Rolling per-scenario state. `failStreak`, `failingSince` and `recent`
|
|
656
|
+
* are the only fields not derivable from a single run — they are folded
|
|
657
|
+
* forward run by run, and a rebuild replays the scenario's history to
|
|
658
|
+
* restore them. */
|
|
659
|
+
interface ScenarioLatestEntry {
|
|
660
|
+
lastRunId: string;
|
|
661
|
+
lastStatus: RunStatus;
|
|
662
|
+
lastAt: number;
|
|
663
|
+
/** Wall-clock of the last run, or null if it never finished. */
|
|
664
|
+
lastDurationMs: number | null;
|
|
665
|
+
/** Up to {@link SCENARIO_RECENT_WINDOW} past outcomes, OLDEST FIRST.
|
|
666
|
+
* Answers the one question a single last status cannot: does this
|
|
667
|
+
* test flake. Kept here rather than fetched per scenario because the
|
|
668
|
+
* overview needs it for every test at once, and a page request per
|
|
669
|
+
* test would be one round trip per mouse movement. */
|
|
670
|
+
recent: RunStatus[];
|
|
671
|
+
/** Consecutive failing runs ending at `lastRunId`. Zero when the last
|
|
672
|
+
* run passed. Distinguishes "just broke" from "broken for weeks", and
|
|
673
|
+
* a value of 1 after green runs is the signature of a flake. */
|
|
674
|
+
failStreak: number;
|
|
675
|
+
/** Start of the current failing streak, or null when not failing. */
|
|
676
|
+
failingSince: number | null;
|
|
677
|
+
}
|
|
678
|
+
interface ScenariosLatest {
|
|
679
|
+
v: number;
|
|
680
|
+
scenarios: Record<string, ScenarioLatestEntry>;
|
|
681
|
+
}
|
|
682
|
+
/** Status values that count as a failure for streak purposes. `aborted`
|
|
683
|
+
* is excluded: the user stopped it, the test did not fail. */
|
|
684
|
+
declare function isFailingStatus(status: RunStatus): boolean;
|
|
685
|
+
/** Fold one newer run into a scenario's rolling state. Pure so the live
|
|
686
|
+
* path and the rebuild path cannot disagree about what a streak is. */
|
|
687
|
+
declare function foldScenarioRun(prev: ScenarioLatestEntry | undefined, run: Pick<RunMeta, "runId" | "status" | "startedAt" | "durationMs">): ScenarioLatestEntry;
|
|
688
|
+
/** Filename for a scenario's index. `encodeURIComponent` because scenario
|
|
689
|
+
* refs carry `/` (`big-table/edit-dialog`) and must not become nested
|
|
690
|
+
* directories — and because it is reversible, so the ref can be read
|
|
691
|
+
* back off the filename without a side table. */
|
|
692
|
+
declare function scenarioIndexFileName(ref: string): string;
|
|
693
|
+
/** Inverse of `scenarioIndexFileName`. */
|
|
694
|
+
declare function scenarioRefFromFileName(fileName: string): string | null;
|
|
695
|
+
|
|
696
|
+
/** Blob root under the runs root. Leading underscore for the same reason
|
|
697
|
+
* as `_scenarios`: derived, internal, and can never be mistaken for a
|
|
698
|
+
* `YYYY` shard directory. */
|
|
699
|
+
declare const BLOBS_DIR = "_blobs";
|
|
700
|
+
/** Thrown rather than returning a path built from a malformed hash — a
|
|
701
|
+
* writer that guessed here would scatter blobs outside the fanout. */
|
|
702
|
+
declare class InvalidBlobHashError extends Error {
|
|
703
|
+
constructor(hash: string);
|
|
704
|
+
}
|
|
705
|
+
/** Project-relative path of one blob.
|
|
706
|
+
*
|
|
707
|
+
* `generation` exists because a hardlink count is bounded — ext4 caps an
|
|
708
|
+
* inode at 65000 links. A frame present in every run crosses that at
|
|
709
|
+
* 65k runs, well inside the six months we size for, and `link()` then
|
|
710
|
+
* fails with EMLINK. Rather than silently degrading to full copies from
|
|
711
|
+
* that point on, the store opens `<hash>~1`, `<hash>~2`, … Each
|
|
712
|
+
* generation is an ordinary blob: same bytes, own inode, own link
|
|
713
|
+
* budget, and garbage-collected by the same `st_nlink` rule. */
|
|
714
|
+
declare function blobPathFor(runsRoot: string, hash: string, ext: string, generation?: number): string;
|
|
715
|
+
/** The same path relative to the runs root, as POSIX segments. Callers
|
|
716
|
+
* holding an OS-absolute root join these themselves rather than pasting
|
|
717
|
+
* an absolute path into a POSIX string. */
|
|
718
|
+
declare function blobRelSegmentsFor(hash: string, ext: string, generation?: number): string[];
|
|
719
|
+
/** Convenience over `blobRelSegmentsFor` for POSIX contexts. */
|
|
720
|
+
declare function blobRelPathFor(hash: string, ext: string, generation?: number): string;
|
|
721
|
+
/** Blob root for a runs root — what the garbage collector walks. */
|
|
722
|
+
declare function blobsRootFor(runsRoot: string): string;
|
|
723
|
+
|
|
724
|
+
/** Filesystem-unsafe characters in a collection name — see
|
|
725
|
+
* `FS_UNSAFE_NAME_RE` for the block-list rationale. Kept as a named
|
|
726
|
+
* re-export for the published surface. */
|
|
727
|
+
declare const COLLECTION_NAME_FORBIDDEN_RE: RegExp;
|
|
728
|
+
/** Validate a collection name. Returns `null` on success, or a
|
|
729
|
+
* human-readable reason string on failure. Shared by the viewer-server
|
|
730
|
+
* writer (authoritative) and any client form (pre-flight) so both sides
|
|
731
|
+
* agree on what's acceptable. Pure function — safe to bundle in the
|
|
732
|
+
* browser. */
|
|
733
|
+
declare function validateCollectionName(name: string): string | null;
|
|
734
|
+
/** Parsed YAML collection manifest from `unotest/e2e/_collections/*.yaml`.
|
|
735
|
+
* Per design-v0/decisions.md the manifest stays minimal — `description`,
|
|
736
|
+
* `scenarios`, and (since workers-support) `workers`. No `env`/`retry`/
|
|
737
|
+
* `timeout` fields until a real use-case appears. */
|
|
738
|
+
interface CollectionMeta {
|
|
739
|
+
/** Filename stem (single source of truth — no `name:` field inside YAML). */
|
|
795
740
|
name: string;
|
|
796
|
-
/** Project-relative path to the
|
|
741
|
+
/** Project-relative path to the `.yaml` file. */
|
|
797
742
|
path: string;
|
|
743
|
+
/** Optional one-line description from the YAML's top-level `description:`. */
|
|
744
|
+
description: string | null;
|
|
745
|
+
/** Scenario names referenced in the YAML's `scenarios:` array. Names are
|
|
746
|
+
* scenario-meta `name` values (file stem relative to `e2e/`). Orphan
|
|
747
|
+
* references (scenario file missing) are NOT filtered out — the viewer
|
|
748
|
+
* surfaces them as red entries with «file not found» tooltip; running
|
|
749
|
+
* a collection skips orphans with a warning, not blocks. */
|
|
750
|
+
scenarios: string[];
|
|
751
|
+
/** Optional `workers:` from the YAML — how many scenarios of this
|
|
752
|
+
* collection may run concurrently. Integer ≥ 1; `null` when the field
|
|
753
|
+
* is absent (callers default to 1 = sequential). A CLI `--workers`
|
|
754
|
+
* flag overrides this value for one run. */
|
|
755
|
+
workers: number | null;
|
|
798
756
|
}
|
|
799
757
|
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
758
|
+
declare const RUN_MANIFEST_FILE = "manifest.json";
|
|
759
|
+
interface RunManifest {
|
|
760
|
+
/** Format-version of this manifest. Bump only on breaking schema
|
|
761
|
+
* changes — additive optional fields don't require it. Starts at 1. */
|
|
762
|
+
schemaVersion: 1;
|
|
763
|
+
/** Identifies which RunRequest variant produced this run. */
|
|
764
|
+
kind: RunKind;
|
|
765
|
+
/** Original (un-sanitized) `ref` from the request — scenario `name`
|
|
766
|
+
* for `kind:"scenario"`, collection `name` for `kind:"collection"`.
|
|
767
|
+
* The `runId` directory uses a sanitized form; this carries the
|
|
768
|
+
* human-readable original. */
|
|
769
|
+
ref: string;
|
|
770
|
+
/** Run-id of the parent collection-run when this is a child scenario
|
|
771
|
+
* spawned inside a collection. Absent for top-level scenario-runs
|
|
772
|
+
* and for collection-runs themselves. */
|
|
773
|
+
parentRunId?: string;
|
|
774
|
+
/** Adapter `name` that produced this run (`"@unotest/web"`,
|
|
775
|
+
* `"@unotest/mobile"`). Lets the viewer pick rendering details for
|
|
776
|
+
* runner-specific bundle contents without runner branches in core
|
|
777
|
+
* logic. */
|
|
778
|
+
adapter: string;
|
|
779
|
+
/** Wallclock at runner start. Distinct from the first event's `t`
|
|
780
|
+
* (which may have leading delay for adapter setup). */
|
|
781
|
+
startedAt: number;
|
|
782
|
+
/** Opt-in artifact-capture flags as resolved from the runner's env at
|
|
783
|
+
* start (`UNOTEST_CONSOLE` / `UNOTEST_NETWORK`). Recorded here so the
|
|
784
|
+
* viewer can tell "capture was disabled" apart from "capture was on but
|
|
785
|
+
* the run aborted before writing console.json / network.json" — a
|
|
786
|
+
* distinction the mere presence of those files can't make. Optional: runs
|
|
787
|
+
* produced before this field existed simply omit it, and the viewer falls
|
|
788
|
+
* back to a file-presence heuristic. */
|
|
789
|
+
capture?: {
|
|
790
|
+
console: boolean;
|
|
791
|
+
network: boolean;
|
|
792
|
+
};
|
|
812
793
|
}
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
794
|
+
|
|
795
|
+
/** Snapshot file holding entry + every helper source that was loaded. */
|
|
796
|
+
declare const RUN_SOURCES_FILE = "sources.json";
|
|
797
|
+
interface RunSources {
|
|
798
|
+
/** Project-root-relative posix path of the entry scenario file,
|
|
799
|
+
* e.g. `unotest/e2e/foo.test.js`. Also a key in `files`. */
|
|
800
|
+
entry: string;
|
|
801
|
+
/** Project-root-relative posix path → full source text. Includes the
|
|
802
|
+
* entry file and every helper file pulled in by the loader. */
|
|
803
|
+
files: Record<string, string>;
|
|
816
804
|
}
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
805
|
+
|
|
806
|
+
/** The argv/env builder for a specific runner CLI. Pure functions —
|
|
807
|
+
* no spawning, no fs access. The viewer owns the actual subprocess
|
|
808
|
+
* lifecycle; the adapter merely tells it WHAT to spawn. */
|
|
809
|
+
interface IRunnerAdapter {
|
|
810
|
+
/** Human-readable identity for logs and error messages
|
|
811
|
+
* (e.g. `"@unotest/web"`). NOT used for control flow — viewer
|
|
812
|
+
* must not branch on this. */
|
|
813
|
+
readonly name: string;
|
|
814
|
+
/** Per-target artifact suffix — the single axis that scopes EVERY
|
|
815
|
+
* per-target filesystem name (M-10): `""` for web (the first target
|
|
816
|
+
* keeps the bare names), `"-mobile"` for mobile, `"-unity"` for a
|
|
817
|
+
* future unity runner. Derived names (via `paths.ts` helpers):
|
|
818
|
+
* scenario tree `e2e<suffix>`, run artifacts `.runs<suffix>`, debug
|
|
819
|
+
* logs `.debug<suffix>`, breakpoints `.debugger<suffix>.json`, env
|
|
820
|
+
* overlays `.env<suffix>`. The viewer scopes discovery/runs/
|
|
821
|
+
* breakpoints by the active target's suffix so no folder name is
|
|
822
|
+
* ever hardcoded in viewer source (OCP: a new target just declares
|
|
823
|
+
* its suffix). */
|
|
824
|
+
readonly suffix: string;
|
|
825
|
+
/** Debug-toolbar capabilities the viewer gates its controls on (OCP — a
|
|
826
|
+
* new target just declares what it supports; viewer never branches on the
|
|
827
|
+
* runner name). See `DebugCapabilities`. */
|
|
828
|
+
readonly debug: DebugCapabilities;
|
|
829
|
+
/** Which `RunRequest.kind` values this adapter can handle. Viewer
|
|
830
|
+
* rejects unsupported kinds with `UnsupportedRunKindError` before
|
|
831
|
+
* building argv. */
|
|
832
|
+
readonly supportedKinds: ReadonlyArray<RunKind>;
|
|
833
|
+
/** Absolute path to the bin entry that should be spawned with the
|
|
834
|
+
* current Node binary: `spawn(process.execPath, [resolveBin(), ...buildArgs(req)])`.
|
|
835
|
+
* Throws if the runner package isn't installed reachably from the
|
|
836
|
+
* viewer's project root. */
|
|
837
|
+
resolveBin(): string;
|
|
838
|
+
/** Argv to pass after the bin path. Pure — depends only on `req`.
|
|
839
|
+
* Example for web: `["e2e", "smoke", "--debug", "--break", "10:4"]`. */
|
|
840
|
+
buildArgs(req: RunRequest): string[];
|
|
841
|
+
/** Environment overrides merged on top of `process.env` by the
|
|
842
|
+
* caller. Pure — no `process.env` reads here. Example for web:
|
|
843
|
+
* `{ UNOTEST_HEADED: "1" }`. Worker-isolation env
|
|
844
|
+
* (`UNOTEST_WORKER_INDEX/COUNT`) is NOT the adapter's
|
|
845
|
+
* responsibility — see `D-C4` in the collections plan. */
|
|
846
|
+
buildEnv(req: RunRequest): NodeJS.ProcessEnv;
|
|
847
|
+
/** Argv (after the bin path) that starts this runner's LONG-LIVED
|
|
848
|
+
* app-under-test holder (M-12/M-16) — the process that prepares the
|
|
849
|
+
* app (web: spawns the dev server; mobile: boots devices + installs
|
|
850
|
+
* the build), emits `AppServerEvent` JSON lines on stdout, and tears
|
|
851
|
+
* down what it owns when terminated. Absent → the runner has no
|
|
852
|
+
* app-launch capability and the viewer hides the feature for this
|
|
853
|
+
* target. Example: `["app-server"]`. */
|
|
854
|
+
appServerArgs?(): string[];
|
|
855
|
+
/** Path inputs the App panel renders for this target (M-17) — each
|
|
856
|
+
* spec becomes an input + file-picker button whose value is saved
|
|
857
|
+
* into the target's env file (`envKey`). The viewer stays
|
|
858
|
+
* platform-blind: extensions/labels are vocab the adapter owns
|
|
859
|
+
* (mobile iOS: `.app`; a future Android entry adds `.apk`). */
|
|
860
|
+
appServerPickers?(): readonly AppPathPickerSpec[];
|
|
861
|
+
/** Host-OS requirement for THIS runner. The viewer resolves it against
|
|
862
|
+
* the server's real `process.platform` and gates the switcher: an
|
|
863
|
+
* unsupported host shows `reason` and blocks activation (e.g. mobile is
|
|
864
|
+
* macOS-only — iOS Simulator is Apple-licensed). Absent → runs on any
|
|
865
|
+
* host (web). OCP: a new platform-bound runner just declares this; the
|
|
866
|
+
* viewer never hardcodes which target needs which OS. */
|
|
867
|
+
readonly host?: HostRequirement;
|
|
868
|
+
/** Structured environment preflight ("doctor") — what must be installed/
|
|
869
|
+
* configured for this runner to actually run here (mobile: Xcode +
|
|
870
|
+
* simulators; web: Node + a system browser). Pure-ish but MAY shell out
|
|
871
|
+
* to probe the host, hence async; the viewer invokes it on demand (a
|
|
872
|
+
* "Check" button), never on every switch. Absent → nothing to verify
|
|
873
|
+
* beyond the package being installed. The viewer renders the returned
|
|
874
|
+
* list verbatim (severity + message + fix `detail`) — it owns no runner
|
|
875
|
+
* vocab. */
|
|
876
|
+
doctor?(ctx: DoctorContext): Promise<DoctorCheck[]>;
|
|
822
877
|
}
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
878
|
+
/** Severity of a single {@link DoctorCheck}. `error` = the runner cannot
|
|
879
|
+
* run until fixed; `warning` = may work, review; `ok` = satisfied. */
|
|
880
|
+
type CheckSeverity = "ok" | "warning" | "error";
|
|
881
|
+
/** One environment-preflight result. The single shape both `@unotest/web`
|
|
882
|
+
* and `@unotest/mobile` already produce from their `runEnvironmentChecks`
|
|
883
|
+
* — lifted here so the viewer (and any future surface) consumes one type,
|
|
884
|
+
* not a per-runner copy. */
|
|
885
|
+
interface DoctorCheck {
|
|
886
|
+
/** Short check id, e.g. `"xcode-cli"`, `"Node.js version"`. */
|
|
887
|
+
name: string;
|
|
888
|
+
severity: CheckSeverity;
|
|
889
|
+
/** One-line outcome, e.g. `"Xcode CLI tools at /Applications/…"`. */
|
|
890
|
+
message: string;
|
|
891
|
+
/** Human-readable remediation when not `ok` — a command or concrete
|
|
892
|
+
* next step. Surfaced under the message in the UI. */
|
|
893
|
+
detail?: string;
|
|
828
894
|
}
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
895
|
+
/** What an adapter's {@link IRunnerAdapter.doctor} receives. Kept as an
|
|
896
|
+
* object so new context (e.g. a target's env file) can be added without
|
|
897
|
+
* touching every implementor's signature. */
|
|
898
|
+
interface DoctorContext {
|
|
899
|
+
/** Absolute path to the project root (the dir containing `unotest/`) —
|
|
900
|
+
* for project-aware checks (mobile reads `package.json` to hint RN/Expo). */
|
|
901
|
+
projectRoot: string;
|
|
832
902
|
}
|
|
833
|
-
/**
|
|
834
|
-
*
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
id: string;
|
|
843
|
-
cmd: "picker:start";
|
|
844
|
-
mode: PickerMode;
|
|
845
|
-
execute?: boolean;
|
|
903
|
+
/** A runner's host-OS requirement. The viewer compares `platforms` against
|
|
904
|
+
* `process.platform`; a mismatch shows `reason` and blocks the switch. */
|
|
905
|
+
interface HostRequirement {
|
|
906
|
+
/** `process.platform` values this runner can execute on, e.g. `["darwin"]`
|
|
907
|
+
* for iOS. */
|
|
908
|
+
platforms: ReadonlyArray<NodeJS.Platform>;
|
|
909
|
+
/** Shown when the current host isn't in `platforms` — one sentence the
|
|
910
|
+
* user reads in the switcher / setup panel. */
|
|
911
|
+
reason: string;
|
|
846
912
|
}
|
|
847
|
-
/**
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
913
|
+
/** What a runner's debug session supports — drives which debug-toolbar
|
|
914
|
+
* controls the viewer shows. A target that lacks a capability gets the
|
|
915
|
+
* control hidden (no dead buttons). */
|
|
916
|
+
interface DebugCapabilities {
|
|
917
|
+
/** In-page locator picker / recorder — click an element in the SHARED,
|
|
918
|
+
* inspectable page to insert its locator/assertion (web). Runners that
|
|
919
|
+
* drive an opaque device without a clickable DOM (mobile via WDA) set this
|
|
920
|
+
* false → the viewer hides the picker cluster (Capture/Execute +
|
|
921
|
+
* target/A/eye). NOTE: distinct from `attach` — the wheel + agent join
|
|
922
|
+
* apply to mobile too. */
|
|
923
|
+
picker: boolean;
|
|
924
|
+
/** Collaborative attach: a second client (the MCP agent) can join the live
|
|
925
|
+
* debug session and co-drive it, coordinated by the control wheel. True for
|
|
926
|
+
* web (shared browser) AND mobile (shared device via WDA). The viewer shows
|
|
927
|
+
* the wheel / "who's driving" badge when set. */
|
|
928
|
+
attach: boolean;
|
|
929
|
+
/** A failure-pause is resumable as a RETRY of the failed step (mobile D-17:
|
|
930
|
+
* the user fixes the device/app state, Continue re-executes the same
|
|
931
|
+
* statement). When false (web: resume past a failure means "skip & pretend
|
|
932
|
+
* it passed"), the viewer hides Continue/Step on a failure-pause so the
|
|
933
|
+
* only action is Stop. */
|
|
934
|
+
resumeFromFailure: boolean;
|
|
851
935
|
}
|
|
852
|
-
/**
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
936
|
+
/** One path-input row in the viewer's App panel (M-17). */
|
|
937
|
+
interface AppPathPickerSpec {
|
|
938
|
+
/** Env var the picked path is saved to (e.g. `"APP_PATH"`). Routed to
|
|
939
|
+
* the target's env file by the viewer's variables API (M-11). */
|
|
940
|
+
envKey: string;
|
|
941
|
+
/** Selectable leaf extensions (lowercase, with dot). macOS `.app`
|
|
942
|
+
* bundles are directories — the file browser treats a matching dir
|
|
943
|
+
* as a selectable leaf. */
|
|
944
|
+
extensions: readonly string[];
|
|
945
|
+
/** Human label rendered above the input (e.g. `"iOS app bundle"`). */
|
|
946
|
+
label: string;
|
|
860
947
|
}
|
|
861
|
-
/**
|
|
862
|
-
*
|
|
863
|
-
*
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
* an element by `ref` (from a snapshot of the SHARED page) + an action; the
|
|
867
|
-
* runner resolves `ref→locator` (`RefResolver`) and emits a `locator:picked`
|
|
868
|
-
* event the viewer inserts — so the agent NEVER hand-writes a locator. Same
|
|
869
|
-
* resolution/insertion pipeline as the human's in-page picker. */
|
|
870
|
-
interface DbgCommandRecord {
|
|
871
|
-
id: string;
|
|
872
|
-
cmd: "record";
|
|
873
|
-
ref: string;
|
|
874
|
-
action: RecordedActionKind;
|
|
875
|
-
/** Typed/selected value for `fill` / `selectOption`. */
|
|
876
|
-
value?: string;
|
|
877
|
-
/** Key for `press` (e.g. "Enter", "Control+A"). */
|
|
878
|
-
key?: string;
|
|
879
|
-
/** Expected text for `assertText` (else the element's textContent is used). */
|
|
880
|
-
text?: string;
|
|
881
|
-
/** Where the viewer drops the line. Default `append` (agent records a flow). */
|
|
882
|
-
placement?: "cursor" | "append";
|
|
948
|
+
/** Thrown by the viewer when a `RunRequest.kind` isn't in the
|
|
949
|
+
* adapter's `supportedKinds`. The exception filter maps to HTTP 400
|
|
950
|
+
* with the kind name + adapter name in the message. */
|
|
951
|
+
declare class UnsupportedRunKindError extends Error {
|
|
952
|
+
constructor(kind: RunKind, adapterName: string);
|
|
883
953
|
}
|
|
884
|
-
/**
|
|
885
|
-
*
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
id: string;
|
|
889
|
-
cmd: "wheel";
|
|
890
|
-
owner: "agent" | "human";
|
|
954
|
+
/** Thrown by the viewer when the configured runner package can't be
|
|
955
|
+
* located on disk. Message names the package + how to install. */
|
|
956
|
+
declare class RunnerAdapterNotFoundError extends Error {
|
|
957
|
+
constructor(packageName: string, hint: string);
|
|
891
958
|
}
|
|
892
|
-
/**
|
|
893
|
-
*
|
|
894
|
-
*
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
* so it is intentionally NOT wheel-gated: any client may probe at any time. */
|
|
898
|
-
interface DbgCommandProbe {
|
|
899
|
-
id: string;
|
|
900
|
-
cmd: "probe";
|
|
901
|
-
/** Locator exactly as written in a test, e.g.
|
|
902
|
-
* getByRole('row').filter({hasText: 'Uma Quinn'}).first(). */
|
|
903
|
-
locator: string;
|
|
904
|
-
/** Cap on the number of match details returned (default 20). */
|
|
905
|
-
limit?: number;
|
|
959
|
+
/** Thrown by the viewer when `unotest.config.*` doesn't declare a
|
|
960
|
+
* `runner:` field. Surfaces what packages exist so the user can
|
|
961
|
+
* pick. */
|
|
962
|
+
declare class RunnerNotConfiguredError extends Error {
|
|
963
|
+
constructor(message: string);
|
|
906
964
|
}
|
|
907
|
-
type DbgCommand = DbgCommandStep | DbgCommandContinue | DbgCommandPause | DbgCommandSetBp | DbgCommandClearBp | DbgCommandAbort | DbgCommandPickerStart | DbgCommandPickerStop | DbgCommandSnapshot | DbgCommandRecord | DbgCommandWheel | DbgCommandProbe;
|
|
908
|
-
type DbgCommandKind = DbgCommand["cmd"];
|
|
909
965
|
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
/**
|
|
943
|
-
|
|
966
|
+
/** A test that is SUPPOSED to fail: the failure itself is the assertion.
|
|
967
|
+
* Its last run being red is the healthy state — and its last run being
|
|
968
|
+
* green is the broken one, because the thing it was guarding stopped
|
|
969
|
+
* being broken without anyone noticing. */
|
|
970
|
+
declare const EXPECT_FAIL_ANNOTATION = "expect-fail";
|
|
971
|
+
interface ScenarioAnnotations {
|
|
972
|
+
expectFail: boolean;
|
|
973
|
+
}
|
|
974
|
+
/** True for a line that is an annotation rather than prose or code. Used
|
|
975
|
+
* by the header parser to step over annotations while keeping the
|
|
976
|
+
* header's "no loose comments" rule for everything else. */
|
|
977
|
+
declare function isAnnotationLine(line: string): boolean;
|
|
978
|
+
/** Read the markers out of a scenario's source. Scans the leading run of
|
|
979
|
+
* comments and stops at the first line of code — an `// @expect-fail`
|
|
980
|
+
* further down the file is a comment about a step, not about the test. */
|
|
981
|
+
declare function parseScenarioAnnotations(source: string): ScenarioAnnotations;
|
|
982
|
+
|
|
983
|
+
/** Parsed scenario metadata from the mandatory header lines
|
|
984
|
+
* (per design-v0/decisions.md, "Test source format"):
|
|
985
|
+
*
|
|
986
|
+
* // id-<kebab-slug>
|
|
987
|
+
* // <human title>
|
|
988
|
+
* // #<6-hex-color>
|
|
989
|
+
* function test_<...>() { ... }
|
|
990
|
+
*
|
|
991
|
+
* All three lines are required; a file without a complete header is
|
|
992
|
+
* rejected by the discovery service as parse-error (no legacy fallback). */
|
|
993
|
+
interface ScenarioMeta {
|
|
994
|
+
/** Slug from `// id-<slug>`. Stable test identity across rename. */
|
|
995
|
+
id: string;
|
|
996
|
+
/** Human-readable title from `// <text>`. */
|
|
997
|
+
title: string;
|
|
998
|
+
/** Color identifier from `// #RRGGBB`. Used only in tab headers as a
|
|
999
|
+
* thin colored strip — NOT a status signal. */
|
|
1000
|
+
color: string;
|
|
1001
|
+
/** File stem relative to `unotest/e2e/`, dirs as slashes.
|
|
1002
|
+
* Examples: `smoke-welcome`, `auth/signin-flow`.
|
|
1003
|
+
* Source-of-truth identity for CLI invocations and tab keys. */
|
|
1004
|
+
name: string;
|
|
1005
|
+
/** Project-relative path to the source file. */
|
|
1006
|
+
path: string;
|
|
1007
|
+
/** The file carries `// @expect-fail` — a red run is this test's
|
|
1008
|
+
* healthy state, and a green one is the alarm. See
|
|
1009
|
+
* {@link ./scenario-annotations.ts}. */
|
|
1010
|
+
expectFail: boolean;
|
|
944
1011
|
}
|
|
945
1012
|
|
|
946
|
-
/**
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
1013
|
+
/** Response of `GET /api/scenarios`. Folders are listed independently of
|
|
1014
|
+
* the scenarios so an EMPTY directory under `unotest/e2e/` still renders
|
|
1015
|
+
* in the tree (the folder list isn't inferred from scenario paths). */
|
|
1016
|
+
interface ScenariosPayload {
|
|
1017
|
+
scenarios: ScenarioMeta[];
|
|
1018
|
+
/** Directory paths relative to `unotest/e2e/`, posix-separated, any
|
|
1019
|
+
* depth (e.g. `auth`, `auth/signup`, `autopark`). Excludes reserved
|
|
1020
|
+
* subtrees (`_helpers`, `_collections`) and any `_`/`.`-prefixed dir. */
|
|
1021
|
+
folders: string[];
|
|
954
1022
|
}
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
1023
|
+
/** Filesystem-unsafe characters in a single path segment — see
|
|
1024
|
+
* `FS_UNSAFE_NAME_RE` (it includes both path separators, so a valid
|
|
1025
|
+
* segment is guaranteed to be ONE level). Kept as a named re-export for
|
|
1026
|
+
* the published surface. */
|
|
1027
|
+
declare const SEGMENT_NAME_FORBIDDEN_RE: RegExp;
|
|
1028
|
+
/** Validate a single scenario-file stem or folder name. Returns `null` on
|
|
1029
|
+
* success, else a human-readable reason. Shared by the viewer-server
|
|
1030
|
+
* writer (authoritative) and the client form (pre-flight) so both agree.
|
|
1031
|
+
* Pure — safe to bundle in the browser.
|
|
1032
|
+
*
|
|
1033
|
+
* Rejects: empty, `.`/`..`, `.`-prefix (hidden), `_`-prefix (reserved for
|
|
1034
|
+
* `_helpers`/`_collections`/templates), path separators + FS-unsafe
|
|
1035
|
+
* punctuation + control chars. */
|
|
1036
|
+
declare function validateSegmentName(name: string): string | null;
|
|
1037
|
+
|
|
1038
|
+
/** Session profile — the kind of process a tab spawns. Mirrored by the
|
|
1039
|
+
* viewer's TerminalStore.TermKind. */
|
|
1040
|
+
type TerminalKind = "local" | "claude" | "codex";
|
|
1041
|
+
interface TerminalSpawnMessage {
|
|
1042
|
+
type: "spawn";
|
|
1043
|
+
id: string;
|
|
1044
|
+
kind: TerminalKind;
|
|
1045
|
+
cols: number;
|
|
1046
|
+
rows: number;
|
|
962
1047
|
}
|
|
963
|
-
interface
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
file: string;
|
|
969
|
-
/** Line number of the DSL call in the source file (1-indexed). */
|
|
970
|
-
line: number;
|
|
971
|
-
/** Column number (1-indexed since 0.11 — the DSL lexer's token
|
|
972
|
-
* columns; earlier releases emitted 0-indexed columns for
|
|
973
|
-
* word-initial statements). */
|
|
974
|
-
col: number;
|
|
1048
|
+
interface TerminalDataMessage {
|
|
1049
|
+
type: "data";
|
|
1050
|
+
id: string;
|
|
1051
|
+
/** Raw bytes the user typed, forwarded to the PTY stdin. */
|
|
1052
|
+
data: string;
|
|
975
1053
|
}
|
|
976
|
-
interface
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
1054
|
+
interface TerminalResizeMessage {
|
|
1055
|
+
type: "resize";
|
|
1056
|
+
id: string;
|
|
1057
|
+
cols: number;
|
|
1058
|
+
rows: number;
|
|
980
1059
|
}
|
|
981
|
-
interface
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
error: string;
|
|
985
|
-
/** Error class name (e.g. `"AssertionError"`, `"LocatorError"`,
|
|
986
|
-
* `"TimeoutError"`). Lets the viewer's `ErrorCard` show a typed
|
|
987
|
-
* pill instead of a generic "Error" label. Optional because some
|
|
988
|
-
* thrown errors lack a meaningful class name. */
|
|
989
|
-
errorName?: string;
|
|
990
|
-
/** Project-relative path to a Playwright trace zip, if captured. */
|
|
991
|
-
trace?: string;
|
|
1060
|
+
interface TerminalCloseMessage {
|
|
1061
|
+
type: "close";
|
|
1062
|
+
id: string;
|
|
992
1063
|
}
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
/**
|
|
998
|
-
|
|
999
|
-
* `StepStartedEvent` — the viewer joins on `line:col`. Absent for
|
|
1000
|
-
* unbound captures (`screenshot()` DSL calls); consumers may fall
|
|
1001
|
-
* back to attributing those to the step in flight at emit time. */
|
|
1002
|
-
file?: string;
|
|
1003
|
-
line?: number;
|
|
1004
|
-
col?: number;
|
|
1064
|
+
type TerminalClientMessage = TerminalSpawnMessage | TerminalDataMessage | TerminalResizeMessage | TerminalCloseMessage;
|
|
1065
|
+
interface TerminalOutputMessage {
|
|
1066
|
+
type: "output";
|
|
1067
|
+
id: string;
|
|
1068
|
+
/** Raw PTY stdout/stderr bytes to write to the xterm instance. */
|
|
1069
|
+
data: string;
|
|
1005
1070
|
}
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
* is keyed by its own id (`<scenario>__<isoDate>__<stepIdx>`) under
|
|
1011
|
-
* `.unotest/failures/`, not under `unotest/.runs/`, because the
|
|
1012
|
-
* bundle layout pre-dates per-run dirs and is still consumed by the
|
|
1013
|
-
* agent-fix MCP tools. */
|
|
1014
|
-
interface FailureBundleWrittenEvent extends RunArtifactBase {
|
|
1015
|
-
kind: "failure-bundle:written";
|
|
1016
|
-
/** Project-relative path to the bundle directory. */
|
|
1017
|
-
bundlePath: string;
|
|
1018
|
-
/** Project-relative path to the screenshot PNG inside the bundle,
|
|
1019
|
-
* when tier-2 capture succeeded. */
|
|
1020
|
-
screenshotPath?: string;
|
|
1021
|
-
/** Page URL at the moment of failure (same value captured into the
|
|
1022
|
-
* bundle's `snapshot.json`/`outline.txt`), for the viewer's ErrorCard. */
|
|
1023
|
-
url?: string;
|
|
1071
|
+
interface TerminalExitMessage {
|
|
1072
|
+
type: "exit";
|
|
1073
|
+
id: string;
|
|
1074
|
+
exitCode: number | null;
|
|
1024
1075
|
}
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1076
|
+
/** Spawn failure (e.g. `claude` not on PATH) reported to the tab so it can
|
|
1077
|
+
* print a friendly line instead of silently dying. */
|
|
1078
|
+
interface TerminalErrorMessage {
|
|
1079
|
+
type: "error";
|
|
1080
|
+
id: string;
|
|
1081
|
+
message: string;
|
|
1028
1082
|
}
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
*
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1083
|
+
type TerminalServerMessage = TerminalOutputMessage | TerminalExitMessage | TerminalErrorMessage;
|
|
1084
|
+
/** The `ws` adapter routes inbound messages by an `event` envelope; both
|
|
1085
|
+
* directions use this single event name on `/ws/terminal`. */
|
|
1086
|
+
declare const TERMINAL_WS_EVENT = "terminal";
|
|
1087
|
+
|
|
1088
|
+
type VariableCategory = "secret" | "app" | "runner";
|
|
1089
|
+
/** User-facing runner-config knobs documented in `unotest/.env`. Single source
|
|
1090
|
+
* of truth for "is this name our config?" — drives the viewer's "Runner
|
|
1091
|
+
* config" group and the add-variable dropdown.
|
|
1035
1092
|
*
|
|
1036
|
-
*
|
|
1037
|
-
*
|
|
1038
|
-
*
|
|
1039
|
-
*
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
*
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
*
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1093
|
+
* NOT listed (so they never surface as user config): launcher-injected
|
|
1094
|
+
* run-control vars (UNOTEST_RUN_ID, UNOTEST_PARENT_RUN_ID,
|
|
1095
|
+
* UNOTEST_BUSY_SCENARIOS, UNOTEST_WORKER_*), session-log internals
|
|
1096
|
+
* (UNOTEST_SESSION_LOG_*), and path bootstrap (UNOTEST_DIR,
|
|
1097
|
+
* UNOTEST_PROJECT_ROOT, UNOTEST_CONFIG, UNOTEST_VIEWER_NO_OPEN). A user would
|
|
1098
|
+
* never put those in `unotest/.env`. */
|
|
1099
|
+
declare const RUNNER_CONFIG_KEYS: ReadonlySet<string>;
|
|
1100
|
+
/** True when `name` is a key the runner consumes as its own configuration. */
|
|
1101
|
+
declare function isRunnerConfigKey(name: string): boolean;
|
|
1102
|
+
/** Group a variable for the viewer. Secrecy from the source file; runner-vs-app
|
|
1103
|
+
* from the owned-key set. */
|
|
1104
|
+
declare function categorizeVariable(v: {
|
|
1105
|
+
name: string;
|
|
1106
|
+
source: "env" | "secrets";
|
|
1107
|
+
}): VariableCategory;
|
|
1108
|
+
/** An external scenario variable is referenced by a bare UPPER_SNAKE
|
|
1109
|
+
* identifier (`APP_BASE_URL`). App / secret names must match this to be
|
|
1110
|
+
* resolvable from a scenario; runner keys are validated against the set
|
|
1111
|
+
* above instead. Convention only — no value inspection. */
|
|
1112
|
+
declare function isExternalVarName(name: string): boolean;
|
|
1113
|
+
|
|
1114
|
+
/** Parse `KEY=VALUE` env-file CONTENT into a plain record. Pure — the
|
|
1115
|
+
* caller does the fs read. Blank lines and `#` comments are skipped;
|
|
1116
|
+
* balanced surrounding single/double quotes are stripped; keys not
|
|
1117
|
+
* matching `[A-Za-z_][A-Za-z0-9_]*` are ignored. Shared by the web and
|
|
1118
|
+
* mobile runners' layered env loaders (M-11) so both parse `.env` files
|
|
1119
|
+
* identically. */
|
|
1120
|
+
declare function parseEnvContent(raw: string): Record<string, string>;
|
|
1121
|
+
/** Quote on write only when the value would otherwise be ambiguous (spaces,
|
|
1122
|
+
* `#`, quotes, `=`, backslash, leading backtick, or newlines). Plain tokens
|
|
1123
|
+
* stay unquoted. Mirrors the viewer env-dotenv serializer. */
|
|
1124
|
+
declare function serializeEnvValue(value: string): string;
|
|
1125
|
+
/** Active (non-commented) assignment keys present in env-file `content`.
|
|
1126
|
+
* Used to detect duplicates / cross-file shadowing before an add. */
|
|
1127
|
+
declare function envKeys(content: string): Set<string>;
|
|
1128
|
+
/**
|
|
1129
|
+
* Insert `KEY=VALUE` into env-file `content`. With `sectionLabel`, the line
|
|
1130
|
+
* goes into the first `# --- <label> --- ` divider section — after the last
|
|
1131
|
+
* assignment there (or its description comments if it has no vars yet). The
|
|
1132
|
+
* label is matched as a whole word against DIVIDER lines only, so prose
|
|
1133
|
+
* comments are never mistaken for a section. No such section → it is created
|
|
1134
|
+
* at end-of-file. With no label the line is appended. Does NOT dedup — reject
|
|
1135
|
+
* duplicates before calling.
|
|
1136
|
+
*/
|
|
1137
|
+
declare function insertEnvVar(content: string, key: string, value: string, sectionLabel?: string): string;
|
|
1138
|
+
/**
|
|
1139
|
+
* Remove the assignment line for `key`, leaving every comment and other line
|
|
1140
|
+
* untouched (we do not guess which comments "belong" to the key). Returns the
|
|
1141
|
+
* new content and whether anything was removed.
|
|
1142
|
+
*/
|
|
1143
|
+
declare function removeEnvVar(content: string, key: string): {
|
|
1144
|
+
content: string;
|
|
1145
|
+
removed: boolean;
|
|
1146
|
+
};
|
|
1147
|
+
|
|
1148
|
+
/** Which group the user is adding into. App/runner → `.env`, secret →
|
|
1149
|
+
* `.secrets`. Mirrors VariableCategory as an INPUT (the user's choice). */
|
|
1150
|
+
type VariableKind = VariableCategory;
|
|
1151
|
+
interface AddVariablePlanInput {
|
|
1152
|
+
name: string;
|
|
1153
|
+
kind: VariableKind;
|
|
1154
|
+
/** Current text of `unotest/.env` (`""` if absent). */
|
|
1155
|
+
envContent: string;
|
|
1156
|
+
/** Current text of `unotest/.secrets` (`""` if absent). */
|
|
1157
|
+
secretsContent: string;
|
|
1081
1158
|
}
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1159
|
+
type VariablePlanError = {
|
|
1160
|
+
ok: false;
|
|
1161
|
+
code: "duplicate" | "invalid-name" | "unknown-runner-key";
|
|
1162
|
+
message: string;
|
|
1163
|
+
};
|
|
1164
|
+
type AddVariablePlan = {
|
|
1165
|
+
ok: true;
|
|
1166
|
+
target: "env" | "secrets";
|
|
1167
|
+
sectionLabel?: string;
|
|
1168
|
+
warnings: string[];
|
|
1169
|
+
} | VariablePlanError;
|
|
1170
|
+
/**
|
|
1171
|
+
* Decide where a new variable goes and what to warn about — or reject it.
|
|
1172
|
+
* Reject reasons: duplicate key in the target file; a non-UPPER_SNAKE app/
|
|
1173
|
+
* secret name (a scenario could never reference it); a runner key not in the
|
|
1174
|
+
* owned-config allowlist. Warnings (non-fatal): secret shadowing a same-named
|
|
1175
|
+
* `.env` var, or vice-versa.
|
|
1176
|
+
*/
|
|
1177
|
+
declare function planAddVariable(input: AddVariablePlanInput): AddVariablePlan;
|
|
1178
|
+
/**
|
|
1179
|
+
* Warning to show after deleting `name` from the file: if the shell
|
|
1180
|
+
* environment still defines it with a DIFFERENT value, it will keep
|
|
1181
|
+
* resolving from there (set-if-absent means ambient wins). Comparing values
|
|
1182
|
+
* avoids a false positive when `.env` was merely flattened into process.env.
|
|
1183
|
+
*/
|
|
1184
|
+
declare function deleteShadowWarning(name: string, fileValue: string | undefined, ambientValue: string | undefined): string[];
|
|
1185
|
+
|
|
1186
|
+
/** A single editor diagnostic. Superset of the `@unotest/dsl`
|
|
1187
|
+
* validator diagnostic: it unifies parse errors, the vocab validator,
|
|
1188
|
+
* and the platform linter behind one shape, tagged by `source`/`rule`
|
|
1189
|
+
* so consumers can theme or filter. Positions are 1-based; a parse
|
|
1190
|
+
* error with no recoverable position falls back to `1:1`. */
|
|
1191
|
+
interface DslDiagnostic {
|
|
1192
|
+
severity: "error" | "warning" | "info";
|
|
1193
|
+
/** 1-based line. */
|
|
1194
|
+
line: number;
|
|
1195
|
+
/** 1-based column. */
|
|
1196
|
+
column: number;
|
|
1197
|
+
/** 1-based end line, when the producer knows the span. */
|
|
1198
|
+
endLine?: number;
|
|
1199
|
+
/** 1-based end column, when the producer knows the span. */
|
|
1200
|
+
endColumn?: number;
|
|
1201
|
+
message: string;
|
|
1202
|
+
/** Stable rule id, e.g. `"validator:unknown-function"`, `"validator:dsl"`,
|
|
1203
|
+
* a linter rule id, or `"parse"`. */
|
|
1204
|
+
rule: string;
|
|
1205
|
+
/** Which stage produced it — lets the UI group / colour by origin. */
|
|
1206
|
+
source: "parse" | "validate" | "lint";
|
|
1088
1207
|
}
|
|
1089
|
-
/**
|
|
1090
|
-
*
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1208
|
+
/** Coarse grouping for a vocab entry — drives completion icons and
|
|
1209
|
+
* ordering. `keyword` covers DSL control-flow / block words (`step`,
|
|
1210
|
+
* `if`, `for`, …); `helper` covers project-defined `flow_*` / mock
|
|
1211
|
+
* helpers surfaced from the workspace. */
|
|
1212
|
+
type DslVocabCategory = "action" | "assertion" | "selector" | "chain" | "navigation" | "read" | "wait" | "state" | "storage" | "multi-context" | "sandbox" | "diagnostic" | "escape-hatch" | "keyword" | "helper";
|
|
1213
|
+
/** One declared argument of a vocab entry — feeds signature help and
|
|
1214
|
+
* the snippet placeholders in `insertText`. `kind` is the validator's
|
|
1215
|
+
* arg-kind string (`"locatorLike"`, `"stringLike"`, `"number"`,
|
|
1216
|
+
* `"any"`, …); the editor treats it as an opaque label. */
|
|
1217
|
+
interface DslVocabArg {
|
|
1218
|
+
label: string;
|
|
1219
|
+
kind: string;
|
|
1220
|
+
required: boolean;
|
|
1221
|
+
/** Literal members when `kind` is `"enum"` — the label alone loses
|
|
1222
|
+
* them, and consumers (typings generator, completion) need the
|
|
1223
|
+
* actual values. */
|
|
1224
|
+
enumValues?: readonly string[];
|
|
1094
1225
|
}
|
|
1095
|
-
/**
|
|
1096
|
-
*
|
|
1097
|
-
*
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1226
|
+
/** A single autocomplete-able item: a vocab function, a DSL keyword, or
|
|
1227
|
+
* a project helper. Serializable — crosses the REST boundary verbatim
|
|
1228
|
+
* and is cached by the browser (the vocab is static per platform). */
|
|
1229
|
+
interface DslVocabEntry {
|
|
1230
|
+
name: string;
|
|
1231
|
+
category: DslVocabCategory;
|
|
1232
|
+
args: DslVocabArg[];
|
|
1233
|
+
/** True when the signature accepts extra positional args after the
|
|
1234
|
+
* declared ones (`shell(cmd, ...args)`, `dbQuery(sql, ...params)`). */
|
|
1235
|
+
variadic?: boolean;
|
|
1236
|
+
/** Return-kind string (`"void"`, `"string"`, `"locator"`, …). */
|
|
1237
|
+
returns: string;
|
|
1238
|
+
/** Snippet body with `${n:label}` placeholders, e.g.
|
|
1239
|
+
* `click(${1:locator})`. */
|
|
1240
|
+
insertText: string;
|
|
1241
|
+
/** Human-readable signature for hover / signature-help, e.g.
|
|
1242
|
+
* `getByRole(role, { name?, exact? })`. Richer than the bare arg
|
|
1243
|
+
* list — includes option-object shape. */
|
|
1244
|
+
signature?: string;
|
|
1245
|
+
/** Prose description (markdown) for hover and the completion info
|
|
1246
|
+
* panel. */
|
|
1247
|
+
doc?: string;
|
|
1248
|
+
/** Locator capability from the function contract. `"chainableLocator"`
|
|
1249
|
+
* entries can be chained off a locator (offered after `.`); any value
|
|
1250
|
+
* other than `"none"` produces a locator (so it fits a `locator`
|
|
1251
|
+
* argument slot). Absent for keywords / helpers that have no contract. */
|
|
1252
|
+
locatorCapability?: "none" | "locator" | "chainableLocator";
|
|
1253
|
+
/** Where the entry is defined in the USER's project — set for project
|
|
1254
|
+
* helpers (`flow_*` in `_helpers/`), absent for platform functions,
|
|
1255
|
+
* which have no source on disk there. Feeds go-to-definition.
|
|
1256
|
+
* `path` is project-relative posix; `line`/`column` are 1-based. */
|
|
1257
|
+
source?: {
|
|
1258
|
+
path: string;
|
|
1259
|
+
line: number;
|
|
1260
|
+
column: number;
|
|
1261
|
+
};
|
|
1104
1262
|
}
|
|
1105
|
-
/**
|
|
1106
|
-
*
|
|
1107
|
-
*
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
/**
|
|
1114
|
-
|
|
1115
|
-
/** MCP tool name, e.g. "check_locator", "explore_step". */
|
|
1116
|
-
tool: string;
|
|
1117
|
-
/** Arguments as a secret-redacted, length-capped JSON string — NOT the raw
|
|
1118
|
-
* object. Capped upstream so the line stays a single atomic append. */
|
|
1119
|
-
args: string;
|
|
1263
|
+
/** Per-request file context for {@link IDslLanguageService.validate}.
|
|
1264
|
+
* The viewer owns workspace discovery, so it supplies the things the
|
|
1265
|
+
* platform validator can't know on its own. */
|
|
1266
|
+
interface DslValidateContext {
|
|
1267
|
+
/** Names of project helpers (`flow_*`, mocks) so their calls are not
|
|
1268
|
+
* flagged as unknown functions — mirrors how the lint runner
|
|
1269
|
+
* registers `unotest/e2e/_helpers/`. */
|
|
1270
|
+
helperNames?: readonly string[];
|
|
1271
|
+
/** Whether the source is a scenario or a helper definition file. */
|
|
1272
|
+
fileKind?: "scenario" | "helper";
|
|
1120
1273
|
}
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
class: string;
|
|
1133
|
-
message: string;
|
|
1134
|
-
};
|
|
1274
|
+
/** The DSL language service a platform exposes to the viewer. Two
|
|
1275
|
+
* concerns only: the static vocabulary (for completion / signature
|
|
1276
|
+
* help) and full source validation (parse + vocab validator + linter,
|
|
1277
|
+
* identical to the platform's own lint gate). */
|
|
1278
|
+
interface IDslLanguageService {
|
|
1279
|
+
/** Static, serializable vocabulary for autocomplete and signature
|
|
1280
|
+
* help. Stable for the lifetime of the process — safe to cache. */
|
|
1281
|
+
getVocab(): DslVocabEntry[];
|
|
1282
|
+
/** Full diagnostics for one source string. Pure with respect to the
|
|
1283
|
+
* filesystem — all workspace knowledge arrives via `ctx`. */
|
|
1284
|
+
validate(source: string, ctx?: DslValidateContext): DslDiagnostic[];
|
|
1135
1285
|
}
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
/**
|
|
1144
|
-
|
|
1286
|
+
|
|
1287
|
+
/** A spawned runner process. Structural superset of the bits of a Node
|
|
1288
|
+
* `ChildProcess` the viewer actually consumes — pid for the active-run
|
|
1289
|
+
* snapshot, stdout/stderr for log capture, exit/error for finalize,
|
|
1290
|
+
* kill for abort. Both `child_process` and Electron `utilityProcess`
|
|
1291
|
+
* satisfy this via a thin adapter. */
|
|
1292
|
+
interface ILaunchedProcess {
|
|
1293
|
+
/** OS process id, or -1 if the spawn failed to produce one. */
|
|
1294
|
+
readonly pid: number;
|
|
1295
|
+
/** Captured stdout stream, or null when not piped. */
|
|
1296
|
+
readonly stdout: Readable | null;
|
|
1297
|
+
/** Captured stderr stream, or null when not piped. */
|
|
1298
|
+
readonly stderr: Readable | null;
|
|
1299
|
+
/** Register the exit handler. Called once when the process exits. */
|
|
1300
|
+
onExit(cb: (code: number | null, signal: NodeJS.Signals | null) => void): void;
|
|
1301
|
+
/** Register the spawn-error handler (e.g. bin not found). */
|
|
1302
|
+
onError(cb: (err: Error) => void): void;
|
|
1303
|
+
/** Terminate the process. Default signal SIGTERM. */
|
|
1304
|
+
kill(signal?: NodeJS.Signals): void;
|
|
1145
1305
|
}
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1306
|
+
/** Everything the launcher needs to spawn one runner process. The caller
|
|
1307
|
+
* (RunnerService) has already merged the environment and resolved the bin
|
|
1308
|
+
* + argv from the `IRunnerAdapter`; the launcher only decides HOW to run
|
|
1309
|
+
* it. Pure data — no behaviour. */
|
|
1310
|
+
interface LaunchSpec {
|
|
1311
|
+
/** Absolute path to the JS bin entry — `IRunnerAdapter.resolveBin()`. */
|
|
1312
|
+
bin: string;
|
|
1313
|
+
/** Argv to pass after the bin — `IRunnerAdapter.buildArgs(req)`. */
|
|
1314
|
+
args: string[];
|
|
1315
|
+
/** Fully-merged environment for the child. */
|
|
1316
|
+
env: NodeJS.ProcessEnv;
|
|
1317
|
+
/** Working directory — the workspace project root. */
|
|
1318
|
+
cwd: string;
|
|
1151
1319
|
}
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1320
|
+
/** Spawns the runner JS under a Node-capable runtime. Injected into the
|
|
1321
|
+
* viewer's RunnerService so the spawn mechanism (system Node vs Electron
|
|
1322
|
+
* utilityProcess) is a deployment choice, not baked into the service. */
|
|
1323
|
+
interface IProcessLauncher {
|
|
1324
|
+
launch(spec: LaunchSpec): ILaunchedProcess;
|
|
1157
1325
|
}
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1326
|
+
|
|
1327
|
+
/** Contents of `unotest/.viewer.lock`. Written by viewer-server on
|
|
1328
|
+
* startup, removed on graceful shutdown. Stale detection: `process.kill(
|
|
1329
|
+
* pid, 0)` throws ESRCH if PID is dead → lockfile is stale, safe to
|
|
1330
|
+
* overwrite. */
|
|
1331
|
+
interface LockFile {
|
|
1332
|
+
/** PID of the viewer-server process. */
|
|
1333
|
+
pid: number;
|
|
1334
|
+
/** Full URL (with port) to reach the viewer. e.g. `http://localhost:5173`. */
|
|
1335
|
+
url: string;
|
|
1336
|
+
/** ISO 8601 timestamp of `pid` start. Used for tie-breaking and stale-age
|
|
1337
|
+
* heuristics if `kill(pid, 0)` is inconclusive. */
|
|
1338
|
+
startedAt: string;
|
|
1161
1339
|
}
|
|
1162
|
-
type CollectionRunArtifact = CollectionRunStartedEvent | CollectionScenarioStartedEvent | CollectionScenarioFinishedEvent | CollectionRunFinishedEvent;
|
|
1163
1340
|
|
|
1164
|
-
/**
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
/** Wallclock from the `run:started` event. */
|
|
1173
|
-
startedAt: number;
|
|
1174
|
-
/** Wallclock from the `run:finished` event, or null if still active. */
|
|
1175
|
-
finishedAt: number | null;
|
|
1176
|
-
/** Total wall-clock duration, or null if still running. */
|
|
1177
|
-
durationMs: number | null;
|
|
1178
|
-
/** Status derived from outcome / liveness — `running` while the run's
|
|
1179
|
-
* heartbeat is fresh and no `run:finished` has landed. */
|
|
1180
|
-
status: RunStatus;
|
|
1181
|
-
/** Terminal outcome from `run:finished`, or null if still running. */
|
|
1182
|
-
outcome: RunOutcome | null;
|
|
1183
|
-
/** Run-id of the parent collection-run when this is a child scenario
|
|
1184
|
-
* spawned inside one. Absent for top-level scenarios and for
|
|
1185
|
-
* collection-runs themselves. Populated by the writer from the
|
|
1186
|
-
* child's `manifest.json`. */
|
|
1187
|
-
parentRunId?: string;
|
|
1341
|
+
/** Helper file metadata. Helpers live in `unotest/e2e/_helpers/**` as
|
|
1342
|
+
* `snake_case` functions (`signin_as`, `seed_user`, …). The runner
|
|
1343
|
+
* auto-discovers them; no import system in scenarios. */
|
|
1344
|
+
interface HelperMeta {
|
|
1345
|
+
/** File stem relative to `_helpers/`, dirs as slashes. */
|
|
1346
|
+
name: string;
|
|
1347
|
+
/** Project-relative path to the source file. */
|
|
1348
|
+
path: string;
|
|
1188
1349
|
}
|
|
1189
1350
|
|
|
1190
1351
|
interface FsScenariosChangedMessage {
|
|
@@ -1392,4 +1553,4 @@ interface RuntimeStateFile {
|
|
|
1392
1553
|
updatedAt: number;
|
|
1393
1554
|
}
|
|
1394
1555
|
|
|
1395
|
-
export { type AddVariablePlan, type AddVariablePlanInput, type AppPathPickerSpec, type AppServerEvent, type AuthoringRunRequest, type BreakpointEntry, type BreakpointsFile, COLLECTIONS_SUBDIR, COLLECTION_NAME_FORBIDDEN_RE, COMMANDS_FILE, type CheckSeverity, type CollectionMeta, type CollectionRunArtifact, type CollectionRunFinishedEvent, type CollectionRunRequest, type CollectionRunStartedEvent, type CollectionRunTab, type CollectionScenarioFinishedEvent, type CollectionScenarioStartedEvent, type CollectionTab, type DbgCommand, type DbgCommandAbort, type DbgCommandClearBp, type DbgCommandContinue, type DbgCommandKind, type DbgCommandPause, type DbgCommandPickerStart, type DbgCommandPickerStop, type DbgCommandProbe, type DbgCommandRecord, type DbgCommandSetBp, type DbgCommandSnapshot, type DbgCommandStep, type DbgCommandWheel, type DebugCapabilities, type DoctorCheck, type DoctorContext, type DslDiagnostic, type DslValidateContext, type DslVocabArg, type DslVocabCategory, type DslVocabEntry, E2E_SUBDIR, ENV_FILE, type EnvFile, type EnvUpdate, type EnvVar, type ErrorMessage, type FailureBundleWrittenEvent, type FsBreakpointsChangedMessage, type FsCollectionRunStepMessage, type FsCollectionsChangedMessage, type FsHelpersChangedMessage, type FsRunFinishedMessage, type FsRunStartedMessage, type FsRunStepMessage, type FsScenariosChangedMessage, HEARTBEAT_FILE, HELPERS_SUBDIR, type HelperMeta, type HelperTab, type HostRequirement, type IDslLanguageService, type ILaunchedProcess, type IProcessLauncher, type IRunnerAdapter, LOCK_FILE, LOG_FILE, type LaunchSpec, type LocatorPickedEvent, type LockFile, type LogLineMessage, type Matcher, PICKER_SNAPSHOT_FILE, type PausedEvent, type PickerMode, type ProbeMatch, type ProbePinned, type ProbeResult, type ProbeResultEvent, RUNNER_CONFIG_KEYS, RUNS_SUBDIR, RUNTIME_FILE, RUN_MANIFEST_FILE, RUN_SOURCES_FILE, type RecordedActionKind, type RegexLiteral, type ResumedEvent, type RunArtifact, type RunFinishedEvent, type RunId, type RunKind, type RunManifest, type RunMeta, type RunOutcome, type RunRequest, type RunSources, type RunStartedEvent, type RunStatus, RunnerAdapterNotFoundError, RunnerNotConfiguredError, type RuntimeState, type RuntimeStateFile, SECRETS_FILE, SEGMENT_NAME_FORBIDDEN_RE, SNAPSHOTS_SUBDIR, STDERR_FILE, STDOUT_FILE, STEPS_FILE, type ScenarioMeta, type ScenarioRunRequest, type ScenarioRunTab, type ScenarioTab, type ScenariosPayload, type ScreenshotEvent, type SnapshotTab, type StepFailEvent, type StepOkEvent, type StepStartedEvent, TERMINAL_RUNTIME_STATES, TERMINAL_WS_EVENT, type TabKind, type TabKindName, type TerminalClientMessage, type TerminalCloseMessage, type TerminalDataMessage, type TerminalErrorMessage, type TerminalExitMessage, type TerminalKind, type TerminalOutputMessage, type TerminalResizeMessage, type TerminalServerMessage, type TerminalSpawnMessage, type ToolCallEvent, type ToolResultEvent, UNOTEST_DIR, UnsupportedRunKindError, type VariableCategory, type VariableKind, type VariablePlanError, type WheelChangedEvent, type WsMessage, type WsMessageKind, activeEnvName, categorizeVariable, debugDirFor, debuggerFileFor, deleteShadowWarning, envFileFor, envKeys, envLayerFilesFor, insertEnvVar, isExternalVarName, isRegexLiteral, isRunnerConfigKey, isTerminalRuntimeState, makeRunId, parseAppServerEvent, parseEnvContent, planAddVariable, regexLiteral, removeEnvVar, runsDirFor, sanitizeRunIdSegment, secretsFileFor, secretsLayerFilesFor, serializeEnvValue, testDirFor, validateCollectionName, validateSegmentName };
|
|
1556
|
+
export { type AddVariablePlan, type AddVariablePlanInput, type AppPathPickerSpec, type AppServerEvent, type AuthoringRunRequest, BLOBS_DIR, type BreakpointEntry, type BreakpointsFile, COLLECTIONS_SUBDIR, COLLECTION_NAME_FORBIDDEN_RE, COMMANDS_FILE, type CheckSeverity, type CollectionMeta, type CollectionRunArtifact, type CollectionRunFinishedEvent, type CollectionRunRequest, type CollectionRunStartedEvent, type CollectionRunTab, type CollectionScenarioFinishedEvent, type CollectionScenarioStartedEvent, type CollectionTab, DAY_INDEX_FILE, type DbgCommand, type DbgCommandAbort, type DbgCommandClearBp, type DbgCommandContinue, type DbgCommandKind, type DbgCommandPause, type DbgCommandPickerStart, type DbgCommandPickerStop, type DbgCommandProbe, type DbgCommandRecord, type DbgCommandSetBp, type DbgCommandSnapshot, type DbgCommandStep, type DbgCommandWheel, type DebugCapabilities, type DoctorCheck, type DoctorContext, type DslDiagnostic, type DslValidateContext, type DslVocabArg, type DslVocabCategory, type DslVocabEntry, E2E_SUBDIR, ENV_FILE, EXPECT_FAIL_ANNOTATION, type EnvFile, type EnvUpdate, type EnvVar, type ErrorMessage, type FailureBundleWrittenEvent, type FsBreakpointsChangedMessage, type FsCollectionRunStepMessage, type FsCollectionsChangedMessage, type FsHelpersChangedMessage, type FsRunFinishedMessage, type FsRunStartedMessage, type FsRunStepMessage, type FsScenariosChangedMessage, HEARTBEAT_FILE, HELPERS_SUBDIR, type HelperMeta, type HelperTab, type HostRequirement, type IDslLanguageService, type ILaunchedProcess, type IProcessLauncher, type IRunnerAdapter, InvalidBlobHashError, LOCK_FILE, LOG_FILE, type LaunchSpec, type LocatorPickedEvent, type LockFile, type LogLineMessage, type Matcher, PICKER_SNAPSHOT_FILE, type PausedEvent, type PickerMode, type ProbeMatch, type ProbePinned, type ProbeResult, type ProbeResultEvent, RUNNER_CONFIG_KEYS, RUNS_SUBDIR, RUNTIME_FILE, RUN_INDEX_SCHEMA_VERSION, RUN_MANIFEST_FILE, RUN_SOURCES_FILE, type RecordedActionKind, type RegexLiteral, type ResumedEvent, type RunArtifact, type RunFinishedEvent, type RunId, type RunIndexEntry, type RunIndexHeader, type RunKind, type RunManifest, type RunMeta, type RunOutcome, type RunRequest, type RunSources, type RunStartedEvent, type RunStatus, RunnerAdapterNotFoundError, RunnerNotConfiguredError, type RuntimeState, type RuntimeStateFile, SCENARIOS_INDEX_DIR, SCENARIOS_LATEST_FILE, SCENARIO_RECENT_WINDOW, SECRETS_FILE, SEGMENT_NAME_FORBIDDEN_RE, SNAPSHOTS_SUBDIR, STDERR_FILE, STDOUT_FILE, STEPS_FILE, type ScenarioAnnotations, type ScenarioLatestEntry, type ScenarioMeta, type ScenarioRunRequest, type ScenarioRunTab, type ScenarioTab, type ScenariosLatest, type ScenariosPayload, type ScreenshotEvent, type SnapshotTab, type StepFailEvent, type StepOkEvent, type StepStartedEvent, TERMINAL_RUNTIME_STATES, TERMINAL_WS_EVENT, type TabKind, type TabKindName, type TerminalClientMessage, type TerminalCloseMessage, type TerminalDataMessage, type TerminalErrorMessage, type TerminalExitMessage, type TerminalKind, type TerminalOutputMessage, type TerminalResizeMessage, type TerminalServerMessage, type TerminalSpawnMessage, type ToolCallEvent, type ToolResultEvent, UNOTEST_DIR, UnplaceableRunIdError, UnsupportedRunKindError, type VariableCategory, type VariableKind, type VariablePlanError, type WheelChangedEvent, type WsMessage, type WsMessageKind, activeEnvName, blobPathFor, blobRelPathFor, blobRelSegmentsFor, blobsRootFor, categorizeVariable, debugDirFor, debuggerFileFor, deleteShadowWarning, envFileFor, envKeys, envLayerFilesFor, foldScenarioRun, insertEnvVar, isAnnotationLine, isExternalVarName, isFailingStatus, isRegexLiteral, isRunnerConfigKey, isTerminalRuntimeState, makeRunId, parseAppServerEvent, parseEnvContent, parseScenarioAnnotations, planAddVariable, projectRunDirFor, projectRunsRoot, regexLiteral, removeEnvVar, requireRunDirFor, runDirFor, runIdStartedAt, runsDirFor, sanitizeRunIdSegment, scenarioIndexFileName, scenarioRefFromFileName, secretsFileFor, secretsLayerFilesFor, serializeEnvValue, shardDaysDescending, shardDirFor, shardPathForRunId, shardPathForTimestamp, testDirFor, validateCollectionName, validateSegmentName };
|