@unotest/web 0.44.0 → 0.46.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +73 -3
- package/README.md +2 -0
- package/dist/config/schema.d.ts +385 -46
- package/dist/config/schema.js +1 -1
- package/dist/driver/index.d.ts +12 -2
- package/dist/driver/index.js +1 -1
- package/dist/dsl/index.d.ts +37 -2
- package/dist/dsl/index.js +1 -1
- package/dist/dsl/web-dsl-language-service.js +1 -1
- package/dist/inspection/page-inject.d.ts +1 -1
- package/dist/{interfaces-DEmGI704.d.ts → interfaces-CYoL4Taq.d.ts} +189 -6
- package/dist/mcp/server.js +1 -1
- package/dist/runner/cli.js +1 -1
- package/dist/runner/init/chrome-probe.js +1 -1
- package/dist/runner/init.js +1 -1
- package/dist/runner/install-chromium.js +1 -1
- package/dist/runner/prepare-fix.js +1 -1
- package/dist/runner/scaffold-workspace.js +1 -1
- package/dist/runner/serve-fixture.js +1 -1
- package/dist/runner/web-runner-adapter.js +1 -1
- package/guides/dsl-reference.md +653 -5
- package/package.json +9 -8
- package/types/unotest-dsl.d.ts +218 -8
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { InjectedDomHelpers } from '@unotest/grounder-client/dom-walker';
|
|
2
2
|
import { PickerMode } from '@unotest/protocol';
|
|
3
|
-
import { E as ElementHint } from '../interfaces-
|
|
3
|
+
import { E as ElementHint } from '../interfaces-CYoL4Taq.js';
|
|
4
4
|
import '../config/schema.js';
|
|
5
5
|
import 'zod';
|
|
6
6
|
|
|
@@ -88,6 +88,10 @@ interface LaunchOptions {
|
|
|
88
88
|
* persistent context, which every `newContext()` then returns
|
|
89
89
|
* (`config.launch.userDataDir`). */
|
|
90
90
|
userDataDir?: string;
|
|
91
|
+
/** A new browser on a new profile directory for every context
|
|
92
|
+
* (`config.launch.profile`); each `newContext()` then needs
|
|
93
|
+
* `ContextOptions.profileDir`. */
|
|
94
|
+
profile?: "per-test";
|
|
91
95
|
/** Extra browser command-line arguments (`config.launch.args`). */
|
|
92
96
|
args?: string[];
|
|
93
97
|
}
|
|
@@ -107,7 +111,8 @@ interface ContextOptions {
|
|
|
107
111
|
* name it does not know. Applied before `viewport`, which overrides the
|
|
108
112
|
* preset's size and scale field by field. */
|
|
109
113
|
device?: string;
|
|
110
|
-
|
|
114
|
+
/** `null` — the real browser window, no size emulation. */
|
|
115
|
+
viewport?: Viewport | null;
|
|
111
116
|
/** Put a fake clock on the context before its first page loads, so that
|
|
112
117
|
* every timer the pages set is under `DriverContext.clock()`. Off by
|
|
113
118
|
* default: time on the page is the real one. */
|
|
@@ -138,6 +143,10 @@ interface ContextOptions {
|
|
|
138
143
|
* `UNOTEST_DEFAULT_NAVIGATION_TIMEOUT_MS` env overrides.
|
|
139
144
|
*/
|
|
140
145
|
defaultNavigationTimeoutMs?: number;
|
|
146
|
+
/** The directory a `profile: "per-test"` browser starts on. The caller
|
|
147
|
+
* makes it and decides whether it stays; given to any other launch it
|
|
148
|
+
* is refused. */
|
|
149
|
+
profileDir?: string;
|
|
141
150
|
}
|
|
142
151
|
interface ActionTimeout {
|
|
143
152
|
/** Override default action timeout (D-7 actionability wait). Milliseconds. */
|
|
@@ -323,6 +332,11 @@ interface WebDriver {
|
|
|
323
332
|
wsEndpoint(): string | undefined;
|
|
324
333
|
connectShared(wsEndpoint: string, browser: LaunchOptions["browser"]): Promise<void>;
|
|
325
334
|
attachExisting(opts?: AttachOptions): Promise<DriverContext>;
|
|
335
|
+
/** True while the browser `connectShared` joined is still there. A run
|
|
336
|
+
* on `launch.profile: "per-test"` closes it with each test and starts
|
|
337
|
+
* the next one at the same endpoint, so a joined client asks this
|
|
338
|
+
* before it follows the run. False when nothing is joined. */
|
|
339
|
+
sharedBrowserConnected(): boolean;
|
|
326
340
|
}
|
|
327
341
|
/** One captured network request/response, recorded only under
|
|
328
342
|
* `UNOTEST_NETWORK`. `status`/`ok` come from the response; `failureText`
|
|
@@ -340,6 +354,23 @@ interface NetworkEntry {
|
|
|
340
354
|
/** Total request→response-end time (ms), from Playwright request timing. */
|
|
341
355
|
durationMs?: number;
|
|
342
356
|
timestamp: string;
|
|
357
|
+
/** The request never reached the network: a `mockRoute` answered or
|
|
358
|
+
* aborted it. Absent for requests a mock let through. */
|
|
359
|
+
mocked?: true;
|
|
360
|
+
}
|
|
361
|
+
/** A file a page of the context downloaded. The bytes stay with the
|
|
362
|
+
* browser until `saveAs` copies them out. */
|
|
363
|
+
interface DriverDownload {
|
|
364
|
+
/** The name the site suggested (`Content-Disposition`, `download`). */
|
|
365
|
+
readonly name: string;
|
|
366
|
+
/** Copy the file to `path` (absolute); resolves once it is written. */
|
|
367
|
+
saveAs(path: string): Promise<void>;
|
|
368
|
+
}
|
|
369
|
+
/** Which downloads `waitForDownload` accepts, by suggested name. `describe`
|
|
370
|
+
* is an infinitive for the timeout message ("contain \"report\""). */
|
|
371
|
+
interface DownloadNameFilter {
|
|
372
|
+
matches(name: string): boolean;
|
|
373
|
+
describe(): string;
|
|
343
374
|
}
|
|
344
375
|
/** A fake clock shared by every page of a context. Time runs by itself
|
|
345
376
|
* until `freeze`, and again after `run`. */
|
|
@@ -356,6 +387,68 @@ interface DriverClock {
|
|
|
356
387
|
* painted frame would wait for nothing. */
|
|
357
388
|
frozen(): boolean;
|
|
358
389
|
}
|
|
390
|
+
/** What a mocked route does with a request it catches. */
|
|
391
|
+
type RouteResponse = {
|
|
392
|
+
kind: "fulfill";
|
|
393
|
+
status: number;
|
|
394
|
+
body: string;
|
|
395
|
+
contentType?: string;
|
|
396
|
+
headers: Readonly<Record<string, string>>;
|
|
397
|
+
}
|
|
398
|
+
/** `record` keeps every aborted request, for `blocked` to report. */
|
|
399
|
+
| {
|
|
400
|
+
kind: "abort";
|
|
401
|
+
record: boolean;
|
|
402
|
+
} | {
|
|
403
|
+
kind: "continue";
|
|
404
|
+
};
|
|
405
|
+
/** A request as the page sent it. */
|
|
406
|
+
interface CapturedRequest {
|
|
407
|
+
url: string;
|
|
408
|
+
method: string;
|
|
409
|
+
headers: Readonly<Record<string, string>>;
|
|
410
|
+
/** The body as sent, or null when there is none. */
|
|
411
|
+
postData: string | null;
|
|
412
|
+
/** The body parsed as JSON, or null when it is absent or not JSON. */
|
|
413
|
+
postDataJson: unknown;
|
|
414
|
+
}
|
|
415
|
+
/** A service worker of the context — an MV3 extension's background. MV3
|
|
416
|
+
* puts an idle worker to sleep, so a handle is good for the call at hand:
|
|
417
|
+
* ask the context for the worker again next time. */
|
|
418
|
+
interface DriverWorker {
|
|
419
|
+
url(): string;
|
|
420
|
+
/** Like `DriverPage.evaluate`, in the worker: a function source is
|
|
421
|
+
* CALLED with `arg`. */
|
|
422
|
+
evaluate<T = unknown>(js: string, arg?: unknown): Promise<T>;
|
|
423
|
+
}
|
|
424
|
+
/** A request a recording abort stopped, with when it was stopped. */
|
|
425
|
+
interface BlockedRequest {
|
|
426
|
+
url: string;
|
|
427
|
+
method: string;
|
|
428
|
+
/** Epoch milliseconds. */
|
|
429
|
+
at: number;
|
|
430
|
+
}
|
|
431
|
+
/** The context's route mocks. Masks use Playwright's URL glob syntax;
|
|
432
|
+
* where several match one request, the one mocked last answers it, and
|
|
433
|
+
* mocking a mask again replaces its answer and makes it the latest.
|
|
434
|
+
* Mocks last as long as the context. */
|
|
435
|
+
interface DriverNetwork {
|
|
436
|
+
mock(glob: string, response: RouteResponse): Promise<void>;
|
|
437
|
+
/** Take off the mock at `glob`, or every mock when it is omitted.
|
|
438
|
+
* What a recording abort kept stays readable through `blocked`. */
|
|
439
|
+
unmock(glob?: string): Promise<void>;
|
|
440
|
+
/** The next request matching `glob` not yet returned by an earlier
|
|
441
|
+
* wait. A mocked mask keeps its requests from the moment it was
|
|
442
|
+
* mocked, so one sent before the wait is still found; any other mask
|
|
443
|
+
* sees requests from the call on. Throws TimeoutError after `timeoutMs`. */
|
|
444
|
+
waitForRequest(glob: string, opts: {
|
|
445
|
+
timeoutMs: number;
|
|
446
|
+
}): Promise<CapturedRequest>;
|
|
447
|
+
/** The requests stopped by the recording abort at `glob`. Throws when
|
|
448
|
+
* `glob` was never mocked with a recording abort: an empty answer
|
|
449
|
+
* would then mean "nothing was watched", not "nothing left". */
|
|
450
|
+
blocked(glob: string): ReadonlyArray<BlockedRequest>;
|
|
451
|
+
}
|
|
359
452
|
interface DriverContext {
|
|
360
453
|
/** Currently-open tabs/windows in this context. Index 0 = first opened. */
|
|
361
454
|
pages(): ReadonlyArray<DriverPage>;
|
|
@@ -363,8 +456,26 @@ interface DriverContext {
|
|
|
363
456
|
activePage(): DriverPage;
|
|
364
457
|
/** Switch the active page index (D-4 setPage). 0 = first opened, etc.
|
|
365
458
|
* Implementations MUST also reset the frame stack — a frame scope
|
|
366
|
-
* established on tab A is not valid on tab B.
|
|
367
|
-
|
|
459
|
+
* established on tab A is not valid on tab B. `activate: false` moves
|
|
460
|
+
* only the runner's target and leaves the browser's front tab as is
|
|
461
|
+
* (default `true`: the tab is brought to front too). */
|
|
462
|
+
setActivePage(index: number, opts?: {
|
|
463
|
+
activate?: boolean;
|
|
464
|
+
}): Promise<void>;
|
|
465
|
+
/** Bring a tab to front — the browser's visible tab, not the runner's
|
|
466
|
+
* target. Absent index → the active page. Throws `DriverError` on an
|
|
467
|
+
* index no open tab has. */
|
|
468
|
+
bringToFront(index?: number): Promise<void>;
|
|
469
|
+
/** Open a blank tab in this context and make it the active page (brought
|
|
470
|
+
* to front, frame stack reset). It counts as handed: `waitForNewPage`
|
|
471
|
+
* never returns it. Resolves with its index. */
|
|
472
|
+
newPage(): Promise<number>;
|
|
473
|
+
/** Close a tab. Absent index → the active page. Closing the active page
|
|
474
|
+
* makes the page that was active before it active again (the one at the
|
|
475
|
+
* index below when that one is gone); closing another tab keeps the
|
|
476
|
+
* active page. Throws `DriverError` on an index no open tab has, and on
|
|
477
|
+
* the only open tab — nothing would be left to drive. */
|
|
478
|
+
closePage(index?: number): Promise<void>;
|
|
368
479
|
/**
|
|
369
480
|
* Subscribe to new-page events. Listener fires when a tab/window opens
|
|
370
481
|
* in this context (`target="_blank"`, `window.open`, etc.). Returns an
|
|
@@ -379,6 +490,17 @@ interface DriverContext {
|
|
|
379
490
|
index: number;
|
|
380
491
|
url: string;
|
|
381
492
|
}) => void): () => void;
|
|
493
|
+
/**
|
|
494
|
+
* Resolve with the index of the first open tab the scenario has not been
|
|
495
|
+
* handed yet, waiting up to `timeoutMs` (default: the navigation budget)
|
|
496
|
+
* for one to open. "Handed" = open when the context started, switched to
|
|
497
|
+
* by `setActivePage`, or returned here before — so a tab opened by a click
|
|
498
|
+
* a step earlier still counts, and no tab is returned twice. Throws
|
|
499
|
+
* `TimeoutError` naming how many tabs are open. Does not switch to it.
|
|
500
|
+
*/
|
|
501
|
+
waitForNewPage(opts?: {
|
|
502
|
+
timeoutMs?: number;
|
|
503
|
+
}): Promise<number>;
|
|
382
504
|
/**
|
|
383
505
|
* Enter a frame scope (D-4 stateful pair). All subsequent locators
|
|
384
506
|
* resolve inside this frame until `exitFrame()` is called. Stacks (calling
|
|
@@ -407,6 +529,16 @@ interface DriverContext {
|
|
|
407
529
|
* page's own timers stay real) — otherwise every call throws
|
|
408
530
|
* ClockNotInstalledError. */
|
|
409
531
|
clock(): DriverClock;
|
|
532
|
+
/** Route mocks of this context: every page, tab and frame of it. */
|
|
533
|
+
network(): DriverNetwork;
|
|
534
|
+
/** A running service worker whose URL matches `url` (substring or
|
|
535
|
+
* regex; any when omitted), or the first to start within `timeoutMs`
|
|
536
|
+
* (default: the context's navigation timeout). Throws at once on a
|
|
537
|
+
* context without a browser profile — extensions never load there. */
|
|
538
|
+
waitForServiceWorker(opts?: {
|
|
539
|
+
timeoutMs?: number;
|
|
540
|
+
url?: Matcher;
|
|
541
|
+
}): Promise<DriverWorker>;
|
|
410
542
|
/** Typed storage shortcuts (D-6). */
|
|
411
543
|
setLocalStorage(key: string, value: string): Promise<void>;
|
|
412
544
|
getLocalStorage(key: string): Promise<string | null>;
|
|
@@ -426,10 +558,16 @@ interface DriverContext {
|
|
|
426
558
|
* runner can pull the tail when a test fails.
|
|
427
559
|
*/
|
|
428
560
|
consoleEntries(): ReadonlyArray<{
|
|
429
|
-
level:
|
|
561
|
+
level: ConsoleLevel;
|
|
430
562
|
text: string;
|
|
431
563
|
timestamp: string;
|
|
432
564
|
}>;
|
|
565
|
+
/** The console lines and uncaught errors of the ACTIVE page, oldest
|
|
566
|
+
* first. Recorded always, from the moment the page opened — unlike
|
|
567
|
+
* `consoleEntries()`, which only `UNOTEST_CONSOLE` fills — so a
|
|
568
|
+
* scenario that clicks and then asks finds what the click logged.
|
|
569
|
+
* Extension pages (`chrome-extension://`) are pages like any other. */
|
|
570
|
+
pageConsole(): PageConsole;
|
|
433
571
|
/**
|
|
434
572
|
* Snapshot of network request/response entries captured since context
|
|
435
573
|
* creation, oldest first. Populated only when `UNOTEST_NETWORK` is on (else
|
|
@@ -437,6 +575,18 @@ interface DriverContext {
|
|
|
437
575
|
* `network.json` for the viewer's Network tab.
|
|
438
576
|
*/
|
|
439
577
|
networkEntries(): ReadonlyArray<NetworkEntry>;
|
|
578
|
+
/**
|
|
579
|
+
* The first download of this context the scenario has not taken yet,
|
|
580
|
+
* waiting up to `timeoutMs` (default: the navigation budget) for one.
|
|
581
|
+
* Downloads are recorded from the moment the context opens, on every
|
|
582
|
+
* page, so one a click started before this call is not lost. `name`
|
|
583
|
+
* narrows to downloads whose suggested file name it accepts. Each
|
|
584
|
+
* download is handed out once. Throws `TimeoutError` when none comes.
|
|
585
|
+
*/
|
|
586
|
+
waitForDownload(opts?: {
|
|
587
|
+
timeoutMs?: number;
|
|
588
|
+
name?: DownloadNameFilter;
|
|
589
|
+
}): Promise<DriverDownload>;
|
|
440
590
|
/** Close all pages in this context. Idempotent. */
|
|
441
591
|
close(): Promise<void>;
|
|
442
592
|
}
|
|
@@ -477,6 +627,9 @@ interface DriverPage {
|
|
|
477
627
|
* `value` is the submitted payload ("on" by default), never its state. */
|
|
478
628
|
checkedState(loc: LocatorValue): Promise<boolean | null>;
|
|
479
629
|
isVisible(loc: LocatorValue): Promise<boolean>;
|
|
630
|
+
/** Where the matched element lies in the viewport, CSS pixels; `null`
|
|
631
|
+
* when it is not rendered (display:none, detached). No scrolling. */
|
|
632
|
+
boundingBox(loc: LocatorValue): Promise<BoundingBox | null>;
|
|
480
633
|
/** Disabled state of the matched element: native `:disabled` (covers
|
|
481
634
|
* fieldset inheritance) OR `aria-disabled="true"`. Boolean attributes
|
|
482
635
|
* are invisible through `getAttribute` (`""` present vs `null` absent
|
|
@@ -510,7 +663,9 @@ interface DriverPage {
|
|
|
510
663
|
* CALLED with `arg` — that is the documented multi-arg escape hatch
|
|
511
664
|
* (`evaluate('function([a, b]) { … }', 1, 2)`). */
|
|
512
665
|
evaluate<T = unknown>(js: string, arg?: unknown): Promise<T>;
|
|
513
|
-
|
|
666
|
+
/** `evaluate` on the matched element: a function body is called with
|
|
667
|
+
* the element and `arg`. */
|
|
668
|
+
evaluateOnLocator<T = unknown>(loc: LocatorValue, js: string, arg?: unknown): Promise<T>;
|
|
514
669
|
screenshot(opts?: ScreenshotOptions): Promise<Buffer>;
|
|
515
670
|
/** Capture ONE element (first match, scrolled into view) instead of the
|
|
516
671
|
* viewport — the evidence shot of a card / row / dialog. Same encoding
|
|
@@ -538,6 +693,13 @@ interface DriverPage {
|
|
|
538
693
|
* optional — an element might have only a role+name, or only a testId,
|
|
539
694
|
* or nothing stable at all (in which case the resolver throws).
|
|
540
695
|
*/
|
|
696
|
+
/** An element's box in the viewport, CSS pixels — Playwright's shape. */
|
|
697
|
+
interface BoundingBox {
|
|
698
|
+
x: number;
|
|
699
|
+
y: number;
|
|
700
|
+
width: number;
|
|
701
|
+
height: number;
|
|
702
|
+
}
|
|
541
703
|
/** How `inspectElement` reads the element's `readable` text. By default
|
|
542
704
|
* whitespace folded and clipped to 200 — a hint. `whole` keeps the text as
|
|
543
705
|
* it is, up to `limit` characters, for a refusal that quotes it (#256). */
|
|
@@ -644,5 +806,26 @@ interface AncestorHint {
|
|
|
644
806
|
/** A single stable, semantic class token (not a hash / utility class). */
|
|
645
807
|
className?: string;
|
|
646
808
|
}
|
|
809
|
+
type ConsoleLevel = "log" | "info" | "warn" | "error" | "debug";
|
|
810
|
+
/** One console line of a page: `url` is the page's at the time, `at`
|
|
811
|
+
* epoch ms — the scale `nowMs()` gives a scenario. */
|
|
812
|
+
interface PageConsoleEntry {
|
|
813
|
+
level: ConsoleLevel;
|
|
814
|
+
text: string;
|
|
815
|
+
url: string;
|
|
816
|
+
at: number;
|
|
817
|
+
}
|
|
818
|
+
/** An error the page threw and nothing caught (`pageerror`). `stack` is
|
|
819
|
+
* empty when the browser gave none. */
|
|
820
|
+
interface PageErrorEntry {
|
|
821
|
+
message: string;
|
|
822
|
+
stack: string;
|
|
823
|
+
url: string;
|
|
824
|
+
at: number;
|
|
825
|
+
}
|
|
826
|
+
interface PageConsole {
|
|
827
|
+
entries: readonly PageConsoleEntry[];
|
|
828
|
+
errors: readonly PageErrorEntry[];
|
|
829
|
+
}
|
|
647
830
|
|
|
648
|
-
export { type AttachOptions as A, type CaptureFrame as C, type DriverContext as D, type ElementHint as E, type FillOptions as F, type GotoOptions as G, type HoverOptions as H, type LocatorValue as L, type NetworkEntry as N, type
|
|
831
|
+
export { type AttachOptions as A, type BoundingBox as B, type CaptureFrame as C, type DriverContext as D, type ElementHint as E, type FillOptions as F, type GotoOptions as G, type HoverOptions as H, type LocatorValue as L, type NetworkEntry as N, type PageConsole as P, type ReloadOptions as R, type ScreenshotOptions as S, type Viewport as V, type WebDriver as W, type CaptureRect as a, type LaunchOptions as b, type ContextOptions as c, type ActionTimeout as d, type CheckOptions as e, type ClickOptions as f, type CookieOptions as g, type DriverPage as h, type NavigationOptions as i, type PressOptions as j, type SelectOptions as k, type ViewportSize as l, type WaitOptions as m, type WaitState as n, type DriverDownload as o, type LocatorStep as p, appendStep as q, describeLocator as r, isLocatorValue as s, rootedAt as t };
|