@unotest/web 0.43.2 → 0.45.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unotest/web",
3
- "version": "0.43.2",
3
+ "version": "0.45.0",
4
4
  "description": "AI-native E2E testing for web applications. MCP server (run_test / step / resume / inspect_runtime / agent_fix) + CLI runner + JavaScript DSL scenarios on a sandboxed AST engine + semantic DOM snapshots.",
5
5
  "license": "MIT",
6
6
  "publishConfig": {
@@ -80,21 +80,22 @@
80
80
  "chokidar": "^4.0.3",
81
81
  "dotenv": "^16.4.5",
82
82
  "playwright": "1.62.1",
83
+ "pngjs": "7.0.0",
83
84
  "tsx": "^4.19.2",
84
85
  "yaml": "^2.6.0",
85
86
  "zod": "^3.23.8",
86
- "@unotest/core": "^0.43.2",
87
- "@unotest/dsl": "^0.43.2",
88
- "@unotest/grounder-client": "^0.43.2",
89
- "@unotest/protocol": "^0.43.2",
90
- "@unotest/viewer": "^0.43.2"
87
+ "@unotest/core": "^0.45.0",
88
+ "@unotest/dsl": "^0.45.0",
89
+ "@unotest/protocol": "^0.45.0",
90
+ "@unotest/viewer": "^0.45.0",
91
+ "@unotest/grounder-client": "^0.45.0"
91
92
  },
92
93
  "devDependencies": {
93
94
  "@types/node": "^22.10.0",
94
95
  "javascript-obfuscator": "^4.1.1",
95
96
  "tsup": "^8.5.1",
96
97
  "typescript": "^5.7.2",
97
- "@unotest/judge": "^0.43.2",
98
- "@unotest/grounder": "^0.43.2"
98
+ "@unotest/grounder": "^0.45.0",
99
+ "@unotest/judge": "^0.45.0"
99
100
  }
100
101
  }
@@ -231,6 +231,20 @@ declare function assertUrl(pattern: string): void;
231
231
  */
232
232
  declare function assertJudge(locator: Locator, rubric: string): void;
233
233
 
234
+ /**
235
+ * assertScreenshot(name | locator, name?, { maxDiffRatio?, threshold?, mask?, ignore?, fullPage? })
236
+ *
237
+ * The page (or an element) against the PNG baseline unotest/e2e/<scenario>.screenshots/<name>.png. A pixel differs when any channel differs by more than threshold (default 32); fails when the share of differing pixels is MORE than maxDiffRatio (default 0). mask: locators, ignore: rectangles in baseline pixels — both left out of the comparison. No baseline: fails and saves the snapshot as a candidate; accept with e2e --update-screenshots. On failure the run keeps expected / actual / diff.
238
+ */
239
+ declare function assertScreenshot(nameOrLocator: any, nameOrOptions?: any, options?: any): void;
240
+
241
+ /**
242
+ * assertEqual(actual, expected)
243
+ *
244
+ * Deep equality as JSON sees it: key order does not count, a missing key differs from a null one. FAILS naming the first differing path (`chat.labels[0].send`) with both values, plus a line diff of the two sides. The DSL form of toEqual.
245
+ */
246
+ declare function assertEqual(actual: any, expected: any): void;
247
+
234
248
  /**
235
249
  * assertTrue(condition, message?)
236
250
  *
@@ -295,11 +309,67 @@ declare function note(label: string, value: any): void;
295
309
  declare function evaluate(jsBody: string, ...args: unknown[]): any;
296
310
 
297
311
  /**
298
- * setPage(index)
312
+ * evaluateOnLocator(locator, js, ...args) → any
313
+ *
314
+ * Like evaluate, on the matched element: the body is called with the element, then the extra arguments by the same rule (`(el, x) => …`, two or more → `(el, [a, b]) => …`). Last resort — prefer typed getters.
315
+ */
316
+ declare function evaluateOnLocator(locator: Locator, jsBody: string, ...args: unknown[]): any;
317
+
318
+ /**
319
+ * setPage(index, options?)
320
+ *
321
+ * Switch the active tab/page by index (0-based) and bring it to front. {activate: false} switches only the tab the steps act on and leaves the visible tab as is.
322
+ */
323
+ declare function setPage(indexOrTitle: string | number, options?: any): void;
324
+
325
+ /**
326
+ * waitForPage(options?) → number
327
+ *
328
+ * Wait for a tab the scenario has not been handed yet (window.open, target=_blank, an extension), switch to it and return its index. Counts a tab that opened before the call. {timeoutMs} defaults to the navigation timeout.
329
+ */
330
+ declare function waitForPage(options?: any): number;
331
+
332
+ /**
333
+ * bringToFront(index?)
334
+ *
335
+ * Bring a tab to front — the visible one — without changing the tab the steps act on. No index: the active tab.
336
+ */
337
+ declare function bringToFront(index?: number): void;
338
+
339
+ /**
340
+ * newPage(url?) → number
341
+ *
342
+ * Open a new tab in the active browser context, make it active and return its index. With a url the tab opens it (relative to the base URL, as goto).
343
+ */
344
+ declare function newPage(url?: string): number;
345
+
346
+ /**
347
+ * closePage(index?)
299
348
  *
300
- * Switch the active tab/page by index (0-based).
349
+ * Close a tab; no index: the active one. Closing the active tab makes the tab that was active before it active again. The only open tab cannot be closed.
301
350
  */
302
- declare function setPage(indexOrTitle: string | number): void;
351
+ declare function closePage(index?: number): void;
352
+
353
+ /**
354
+ * setViewport({width, height})
355
+ *
356
+ * Resize every tab of the browser, and the ones it opens later, without reloading. The scale (deviceScaleFactor) is set in the config, not here.
357
+ */
358
+ declare function setViewport(size: any): void;
359
+
360
+ /**
361
+ * newContext() → number
362
+ *
363
+ * Open another browser context — a separate client with its own cookies, storage and tabs — and make it active. Returns its number (the run's own is 0).
364
+ */
365
+ declare function newContext(): number;
366
+
367
+ /**
368
+ * setContext(index)
369
+ *
370
+ * Make browser context `index` active. Each keeps its own tab and frame, so switching back lands where it was.
371
+ */
372
+ declare function setContext(index: number): void;
303
373
 
304
374
  /**
305
375
  * enterFrame(locator)
@@ -483,6 +553,27 @@ declare function today(): string;
483
553
  */
484
554
  declare function daysFromNow(days: number): string;
485
555
 
556
+ /**
557
+ * textMatches(text, /re/) → false | string[]
558
+ *
559
+ * First match of a regex literal: false on a miss, else `[whole, group1, …]` (a group that took no part is null). The array is truthy, so it also reads as a test: `m = textMatches(url, /room=(\d+)/); id = m[1];`. For a plain substring use textContains.
560
+ */
561
+ declare function textMatches(text: string, pattern: any): any;
562
+
563
+ /**
564
+ * parseJson(text) → any
565
+ *
566
+ * Parse a JSON string (an api body, a data attribute, a request's postData) into a value you walk with `.` and `[]`. FAILS on invalid JSON, quoting the input; an empty string is not JSON.
567
+ */
568
+ declare function parseJson(text: string): any;
569
+
570
+ /**
571
+ * urlParam(url, key) → string | null
572
+ *
573
+ * A query parameter of a URL, decoded; null when the key is absent, '' when it is empty. A path without a host (`/room?id=7`) works too. The DSL form of `new URL(u).searchParams.get(key)`.
574
+ */
575
+ declare function urlParam(url: string, key: string): any;
576
+
486
577
  /**
487
578
  * shell(cmd, ...args, options?) → { stdout, stderr, code }
488
579
  *
@@ -525,6 +616,27 @@ declare function assertNoJsonLine(path: string, filter: any, options?: any): voi
525
616
  */
526
617
  declare function readJsonLine(path: string, filter: any): any;
527
618
 
619
+ /**
620
+ * readJson(path) → any
621
+ *
622
+ * A whole JSON file, read once and parsed: app constants a build script exported, fixture objects. Path is relative to config `shellCwd` and must stay inside it. FAILS at once when the path leaves the project, the file is missing or it is not valid JSON. Walk the result with `.` and `[]`.
623
+ */
624
+ declare function readJson(path: string): any;
625
+
626
+ /**
627
+ * readFile(path, options?) → string
628
+ *
629
+ * A whole file, read once: its text, or its bytes as base64 with {encoding: 'base64'}. Path is relative to config `shellCwd`, or absolute inside the project or to a file waitForDownload saved (its .path). FAILS at once when the path or a symlink leads out, the file is missing or the encoding is unknown.
630
+ */
631
+ declare function readFile(path: string, options?: any): string;
632
+
633
+ /**
634
+ * readFileInfo(path) → object
635
+ *
636
+ * What to assert about a file without reading it: {size} in bytes, plus png: {width, height} for a PNG (from its header). Same path rules and failures as readFile.
637
+ */
638
+ declare function readFileInfo(path: string): any;
639
+
528
640
  /**
529
641
  * waitForFileCount(path, pattern, count, options?) → number
530
642
  *
@@ -651,6 +763,13 @@ declare function getInnerText(locator: Locator): string;
651
763
  */
652
764
  declare function getInputValue(locator: Locator): string;
653
765
 
766
+ /**
767
+ * boundingBox(locator) → {x, y, width, height} | null
768
+ *
769
+ * Where the element lies in the viewport, CSS pixels; null when it is not rendered (display:none, detached). Does not scroll.
770
+ */
771
+ declare function boundingBox(locator: Locator): any;
772
+
654
773
  /**
655
774
  * getTitle() → string
656
775
  *
@@ -728,6 +847,83 @@ declare function waitForCount(locator: Locator, count: number, options?: any): v
728
847
  */
729
848
  declare function pause(ms: number): void;
730
849
 
850
+ /**
851
+ * waitForDownload(options?) → { path, name }
852
+ *
853
+ * The first file the page downloaded that the scenario has not taken yet, saved with the run: `path` is absolute (read it with waitForFile), `name` is the name the site suggested. Downloads are recorded from the start, so click first, then call this. options: {timeoutMs} (default: the navigation timeout), {name} (substring or regex of the file name).
854
+ */
855
+ declare function waitForDownload(options?: any): any;
856
+
857
+ /**
858
+ * clockFreeze(iso)
859
+ *
860
+ * Stop time on the page at an ISO instant: `Date` reads it and no timer fires until clockTick / clockRun. Any instant, past or future.
861
+ */
862
+ declare function clockFreeze(iso: string): void;
863
+
864
+ /**
865
+ * clockTick(ms)
866
+ *
867
+ * Move frozen time forward by ms, firing every timer due in it.
868
+ */
869
+ declare function clockTick(ms: number): void;
870
+
871
+ /**
872
+ * clockRun()
873
+ *
874
+ * Let time on the page run again from where it stands.
875
+ */
876
+ declare function clockRun(): void;
877
+
878
+ /**
879
+ * mockRoute(glob, response)
880
+ *
881
+ * From now on, requests matching `glob` (a Playwright URL glob: `**\/api/items*`) get a fixed answer: `{status?, body?, headers?, contentType?}` (an object body goes as JSON), `{abort: true, record?}` or `{continue: true}` (to the network). Where several masks match, the one mocked last answers; mocking a mask again replaces its answer. A response to `**\/*` is refused; the site's own page is never aborted. Mocks last until the scenario ends.
882
+ */
883
+ declare function mockRoute(glob: string, response: any): void;
884
+
885
+ /**
886
+ * unmockRoute(glob?)
887
+ *
888
+ * Take off the mock at `glob`, or every mock when called without one.
889
+ */
890
+ declare function unmockRoute(glob?: string): void;
891
+
892
+ /**
893
+ * waitForRequest(glob, { timeoutMs? }) → { url, method, headers, postData, postDataJson }
894
+ *
895
+ * Wait for a request matching `glob` and return it; `postDataJson` is the body read as JSON (null when it is not). On a mocked mask it also finds a request sent before the call, each one returned once; on any other mask only requests from the call on. Default timeout 20 s.
896
+ */
897
+ declare function waitForRequest(glob: string, options?: any): any;
898
+
899
+ /**
900
+ * assertNoRequest(glob, { since? })
901
+ *
902
+ * FAIL if the recording abort at `glob` — mockRoute(glob, {abort: true, record: true}) — stopped any request; with `since` (epoch ms, from nowMs()) only from then on. A mask without a recording abort is an error, not a pass.
903
+ */
904
+ declare function assertNoRequest(glob: string, options?: any): void;
905
+
906
+ /**
907
+ * waitForServiceWorker({ timeoutMs?, url? }) → string
908
+ *
909
+ * Wait for a browser extension's service worker and return its URL (`chrome-extension://<id>/sw.js`); a running one comes back at once. `url` — substring or /regex/ when several extensions run. Extensions load only with `launch.userDataDir`: without it the call fails at once.
910
+ */
911
+ declare function waitForServiceWorker(options?: any): string;
912
+
913
+ /**
914
+ * extensionId() → string
915
+ *
916
+ * The id of the extension whose service worker runs — for `goto('chrome-extension://' + extensionId() + '/popup.html')`.
917
+ */
918
+ declare function extensionId(): string;
919
+
920
+ /**
921
+ * evaluateInServiceWorker(js, ...args) → any
922
+ *
923
+ * Like evaluate, in the extension's service worker — the way to `chrome.storage`, `chrome.tabs`, `chrome.runtime`. Same argument and JSON-return rules; waits for the worker if none runs. Each call finds the worker anew: MV3 stops an idle one.
924
+ */
925
+ declare function evaluateInServiceWorker(jsBody: string, ...args: unknown[]): any;
926
+
731
927
  /** step(label, [options,] body)
732
928
  *
733
929
  * Groups statements under a labelled block. Every direct child of a