@itmetsam/samurai-framework 0.21.5 → 0.22.1

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.
Files changed (174) hide show
  1. package/dist/api.d.ts +8 -3
  2. package/dist/api.js +1 -0
  3. package/dist/browser/browser-context.d.ts +18 -6
  4. package/dist/browser/browser-context.js +25 -7
  5. package/dist/browser/browser.d.ts +4 -4
  6. package/dist/browser/browser.js +11 -8
  7. package/dist/browser/chrome-launch.js +9 -1
  8. package/dist/browser/context-cookie-store.d.ts +17 -0
  9. package/dist/browser/context-cookie-store.js +90 -0
  10. package/dist/browser/index.d.ts +3 -1
  11. package/dist/browser/index.js +1 -1
  12. package/dist/browser/keyboard.d.ts +35 -0
  13. package/dist/browser/keyboard.js +83 -0
  14. package/dist/browser/mouse.d.ts +42 -0
  15. package/dist/browser/mouse.js +66 -0
  16. package/dist/browser/page-content.d.ts +4 -0
  17. package/dist/browser/page-content.js +12 -0
  18. package/dist/browser/page-events.d.ts +11 -0
  19. package/dist/browser/page-events.js +23 -0
  20. package/dist/browser/page-logging.d.ts +24 -0
  21. package/dist/browser/page-logging.js +43 -0
  22. package/dist/browser/page-navigation.d.ts +0 -2
  23. package/dist/browser/page-navigation.js +1 -6
  24. package/dist/browser/page-settings.d.ts +1 -1
  25. package/dist/browser/page-viewport.d.ts +21 -0
  26. package/dist/browser/page-viewport.js +33 -0
  27. package/dist/browser/page.d.ts +23 -16
  28. package/dist/browser/page.js +48 -71
  29. package/dist/browser/screenshot.d.ts +6 -0
  30. package/dist/browser/screenshot.js +14 -0
  31. package/dist/browser/storage-state.d.ts +0 -2
  32. package/dist/browser/storage-state.js +0 -20
  33. package/dist/browser/use-applier.d.ts +16 -5
  34. package/dist/browser/use-applier.js +44 -17
  35. package/dist/browser/use-commands.d.ts +36 -0
  36. package/dist/browser/use-commands.js +120 -0
  37. package/dist/cli/args.d.ts +8 -4
  38. package/dist/cli/args.js +8 -5
  39. package/dist/cli/console-reporter.js +5 -1
  40. package/dist/cli/main.js +1 -0
  41. package/dist/cli/usage.d.ts +1 -1
  42. package/dist/cli/usage.js +4 -1
  43. package/dist/config/base-url.d.ts +4 -0
  44. package/dist/config/base-url.js +6 -0
  45. package/dist/config/config.js +7 -0
  46. package/dist/config/fixture-use.d.ts +19 -0
  47. package/dist/config/fixture-use.js +44 -0
  48. package/dist/config/fully-parallel.d.ts +3 -0
  49. package/dist/config/fully-parallel.js +6 -0
  50. package/dist/config/group-use.d.ts +10 -0
  51. package/dist/config/group-use.js +51 -0
  52. package/dist/config/resolved-config.d.ts +2 -0
  53. package/dist/config/resolved-config.js +3 -0
  54. package/dist/config/storage-state-file.d.ts +3 -0
  55. package/dist/config/storage-state-file.js +20 -0
  56. package/dist/config/use-options.d.ts +3 -1
  57. package/dist/config/use-options.js +74 -14
  58. package/dist/config/web-server-options.d.ts +7 -0
  59. package/dist/config/web-server-options.js +111 -0
  60. package/dist/locator/check-actions.js +1 -1
  61. package/dist/locator/drag-actions.d.ts +17 -0
  62. package/dist/locator/drag-actions.js +80 -0
  63. package/dist/locator/element-actions.d.ts +10 -4
  64. package/dist/locator/element-actions.js +23 -16
  65. package/dist/locator/element-state.d.ts +5 -0
  66. package/dist/locator/element-state.js +2 -1
  67. package/dist/locator/event-actions.d.ts +10 -0
  68. package/dist/locator/event-actions.js +81 -0
  69. package/dist/locator/input-actions.d.ts +2 -11
  70. package/dist/locator/input-actions.js +3 -56
  71. package/dist/locator/key-actions.d.ts +11 -0
  72. package/dist/locator/key-actions.js +52 -0
  73. package/dist/locator/key-map.d.ts +16 -0
  74. package/dist/locator/key-map.js +76 -0
  75. package/dist/locator/locator.d.ts +35 -5
  76. package/dist/locator/locator.js +70 -16
  77. package/dist/locator/page-scripts.d.ts +2 -0
  78. package/dist/locator/page-scripts.js +7 -0
  79. package/dist/locator/pointer-actions.d.ts +44 -0
  80. package/dist/locator/pointer-actions.js +117 -0
  81. package/dist/locator/pointer-element-actions.d.ts +21 -0
  82. package/dist/locator/pointer-element-actions.js +42 -0
  83. package/dist/request/api-request-context.d.ts +65 -0
  84. package/dist/request/api-request-context.js +198 -0
  85. package/dist/request/api-response.d.ts +34 -0
  86. package/dist/request/api-response.js +57 -0
  87. package/dist/request/cookie-jar.d.ts +48 -0
  88. package/dist/request/cookie-jar.js +164 -0
  89. package/dist/request/http-transport.d.ts +31 -0
  90. package/dist/request/http-transport.js +113 -0
  91. package/dist/request/redirect.d.ts +10 -0
  92. package/dist/request/redirect.js +21 -0
  93. package/dist/request/request-body.d.ts +27 -0
  94. package/dist/request/request-body.js +71 -0
  95. package/dist/request/request.d.ts +16 -0
  96. package/dist/request/request.js +20 -0
  97. package/dist/runner/all-hooks.d.ts +7 -1
  98. package/dist/runner/all-hooks.js +17 -1
  99. package/dist/runner/fixture-defs.d.ts +2 -1
  100. package/dist/runner/fixture-defs.js +52 -18
  101. package/dist/runner/fixture-meta.d.ts +20 -0
  102. package/dist/runner/fixture-meta.js +109 -0
  103. package/dist/runner/fixture-options.d.ts +8 -0
  104. package/dist/runner/fixture-options.js +35 -0
  105. package/dist/runner/fixture-outputs.d.ts +11 -0
  106. package/dist/runner/fixture-outputs.js +28 -0
  107. package/dist/runner/fixture-registry.d.ts +35 -0
  108. package/dist/runner/fixture-registry.js +73 -0
  109. package/dist/runner/fixture-scope.d.ts +39 -9
  110. package/dist/runner/fixture-scope.js +139 -70
  111. package/dist/runner/fixture-start.d.ts +27 -0
  112. package/dist/runner/fixture-start.js +76 -0
  113. package/dist/runner/fixture-use.d.ts +23 -0
  114. package/dist/runner/fixture-use.js +64 -0
  115. package/dist/runner/fixture-worker.d.ts +26 -0
  116. package/dist/runner/fixture-worker.js +46 -0
  117. package/dist/runner/fixtures.d.ts +4 -1
  118. package/dist/runner/fixtures.js +3 -2
  119. package/dist/runner/global-hooks.d.ts +13 -0
  120. package/dist/runner/global-hooks.js +66 -0
  121. package/dist/runner/options.d.ts +13 -2
  122. package/dist/runner/output-tail.d.ts +14 -0
  123. package/dist/runner/output-tail.js +30 -0
  124. package/dist/runner/pool-schedule.d.ts +22 -0
  125. package/dist/runner/pool-schedule.js +55 -0
  126. package/dist/runner/pool-units.d.ts +34 -0
  127. package/dist/runner/pool-units.js +85 -0
  128. package/dist/runner/pooled-run.d.ts +7 -5
  129. package/dist/runner/pooled-run.js +19 -9
  130. package/dist/runner/prepare-run.js +5 -0
  131. package/dist/runner/registry.js +21 -7
  132. package/dist/runner/reporter.d.ts +13 -1
  133. package/dist/runner/reporter.js +13 -2
  134. package/dist/runner/request-fixture.d.ts +14 -0
  135. package/dist/runner/request-fixture.js +25 -0
  136. package/dist/runner/run-hooks.d.ts +16 -0
  137. package/dist/runner/run-hooks.js +52 -0
  138. package/dist/runner/run-test.d.ts +3 -0
  139. package/dist/runner/run-test.js +23 -3
  140. package/dist/runner/run.d.ts +8 -0
  141. package/dist/runner/run.js +10 -0
  142. package/dist/runner/select.d.ts +15 -4
  143. package/dist/runner/select.js +38 -12
  144. package/dist/runner/serial-run.d.ts +23 -0
  145. package/dist/runner/serial-run.js +88 -0
  146. package/dist/runner/serial.d.ts +34 -0
  147. package/dist/runner/serial.js +63 -0
  148. package/dist/runner/shard.d.ts +22 -0
  149. package/dist/runner/shard.js +73 -0
  150. package/dist/runner/suite.d.ts +3 -3
  151. package/dist/runner/suite.js +4 -3
  152. package/dist/runner/test-function.d.ts +15 -3
  153. package/dist/runner/test-info.js +2 -1
  154. package/dist/runner/test-runner.d.ts +1 -0
  155. package/dist/runner/test-runner.js +67 -18
  156. package/dist/runner/use-options.d.ts +18 -4
  157. package/dist/runner/use-options.js +33 -6
  158. package/dist/runner/wait-for-url.d.ts +20 -0
  159. package/dist/runner/wait-for-url.js +65 -0
  160. package/dist/runner/web-server.d.ts +16 -0
  161. package/dist/runner/web-server.js +165 -0
  162. package/dist/runner/worker-pool.d.ts +13 -5
  163. package/dist/runner/worker-pool.js +85 -51
  164. package/dist/runner/worker-protocol.d.ts +8 -0
  165. package/dist/runner/worker-session.js +9 -1
  166. package/dist/runner/worker.d.ts +2 -0
  167. package/dist/runner/worker.js +7 -1
  168. package/dist/types/config.d.ts +117 -21
  169. package/dist/types/extend.d.ts +54 -2
  170. package/dist/types/test.d.ts +42 -0
  171. package/dist/types/use.d.ts +59 -7
  172. package/package.json +1 -1
  173. package/dist/browser/emulation.d.ts +0 -61
  174. package/dist/browser/emulation.js +0 -180
@@ -42,3 +42,14 @@ export declare function sourcesFor(event: PageEventName): EventSource[];
42
42
  * wait that ran out after `timeout` ms.
43
43
  */
44
44
  export declare function waitForPageEvent<E extends PageEventName>(emitter: EventEmitter, event: E, matches: (item: PageEvents[E][0]) => boolean, options: Pick<WaitOptions, "trigger">, timeout: number, onTimeout: (timeout: number) => Error): Promise<PageEvents[E][0]>;
45
+ /** The waits `waitForEvent` hands its event to; `download` and `filechooser` take the predicate too */
46
+ export interface EventWaiters {
47
+ request(accepts: (item: unknown) => boolean, options: WaitOptions): Promise<unknown>;
48
+ response(accepts: (item: unknown) => boolean, options: WaitOptions): Promise<unknown>;
49
+ download(options: WaitOptions, accepts: (item: unknown) => boolean): Promise<unknown>;
50
+ filechooser(options: WaitOptions, accepts: (item: unknown) => boolean): Promise<unknown>;
51
+ }
52
+ /** `page.waitForEvent`: picks the named wait for `event`, with the predicate given as a function or in the options */
53
+ export declare function waitForEventOn<E extends WaitableEventName>(waiters: EventWaiters, event: E, optionsOrPredicate: ((item: PageEvents[E][0]) => boolean) | WaitForEventOptions<PageEvents[E][0]>): Promise<PageEvents[E][0]>;
54
+ /** Starts the parts `event` needs; a failure is reported to `onError`, not thrown */
55
+ export declare function startSources(event: PageEventName, start: Record<EventSource, () => Promise<unknown>>, onError: (err: unknown) => void): void;
@@ -56,3 +56,26 @@ export function waitForPageEvent(emitter, event, matches, options, timeout, onTi
56
56
  }
57
57
  });
58
58
  }
59
+ /** `page.waitForEvent`: picks the named wait for `event`, with the predicate given as a function or in the options */
60
+ export async function waitForEventOn(waiters, event, optionsOrPredicate) {
61
+ const { predicate, ...options } = typeof optionsOrPredicate === "function"
62
+ ? { predicate: optionsOrPredicate }
63
+ : optionsOrPredicate;
64
+ const accepts = (predicate ?? (() => true));
65
+ switch (event) {
66
+ case "request":
67
+ return waiters.request(accepts, options);
68
+ case "response":
69
+ return waiters.response(accepts, options);
70
+ case "download":
71
+ return waiters.download(options, accepts);
72
+ case "filechooser":
73
+ return waiters.filechooser(options, accepts);
74
+ default:
75
+ throw new Error(`waitForEvent(): "${String(event)}" is not supported; use page.on()`);
76
+ }
77
+ }
78
+ /** Starts the parts `event` needs; a failure is reported to `onError`, not thrown */
79
+ export function startSources(event, start, onError) {
80
+ Promise.all(sourcesFor(event).map((source) => start[source]())).catch(onError);
81
+ }
@@ -0,0 +1,24 @@
1
+ import type EventEmitter from "node:events";
2
+ import type { BiDiConnector } from "../transport/bidi-connection.js";
3
+ import type { BrowsingContext } from "../types/bidi-modules/browsing-context.js";
4
+ import type { ContextTree } from "./context-tree.js";
5
+ import { PageLogs, type LogRecord, type PageError } from "./page-logs.js";
6
+ /** A page's console and error collection: started on demand, read at once, and whether the runner may fail on its errors */
7
+ export declare class PageLogging {
8
+ private readonly connector;
9
+ private readonly contextId;
10
+ private readonly tree;
11
+ private readonly emitter;
12
+ private lazyLogs;
13
+ private errorsAllowed;
14
+ constructor(connector: BiDiConnector, contextId: BrowsingContext, tree: () => Promise<ContextTree>, emitter: EventEmitter);
15
+ start(): Promise<void>;
16
+ /** Forgets the collection and hands back its start, for the caller to dispose; undefined when it never started */
17
+ take(): Promise<PageLogs> | undefined;
18
+ entries(): readonly LogRecord[];
19
+ errors(): readonly PageError[];
20
+ get dropped(): number;
21
+ clear(): void;
22
+ allowErrors(): void;
23
+ get errorsAreAllowed(): boolean;
24
+ }
@@ -0,0 +1,43 @@
1
+ import { Lazy } from "./lazy.js";
2
+ import { PageLogs } from "./page-logs.js";
3
+ /** A page's console and error collection: started on demand, read at once, and whether the runner may fail on its errors */
4
+ export class PageLogging {
5
+ constructor(connector, contextId, tree, emitter) {
6
+ this.connector = connector;
7
+ this.contextId = contextId;
8
+ this.tree = tree;
9
+ this.emitter = emitter;
10
+ this.lazyLogs = new Lazy();
11
+ this.errorsAllowed = false;
12
+ }
13
+ async start() {
14
+ await this.lazyLogs.get(async () => {
15
+ const logs = await PageLogs.start(this.connector, await this.tree(), this.contextId);
16
+ logs.on("console", (message) => this.emitter.emit("console", message));
17
+ logs.on("pageerror", (error) => this.emitter.emit("pageerror", error));
18
+ return logs;
19
+ });
20
+ }
21
+ /** Forgets the collection and hands back its start, for the caller to dispose; undefined when it never started */
22
+ take() {
23
+ return this.lazyLogs.take();
24
+ }
25
+ entries() {
26
+ return this.lazyLogs.peek()?.entries ?? [];
27
+ }
28
+ errors() {
29
+ return this.lazyLogs.peek()?.errors ?? [];
30
+ }
31
+ get dropped() {
32
+ return this.lazyLogs.peek()?.dropped ?? 0;
33
+ }
34
+ clear() {
35
+ this.lazyLogs.peek()?.clear();
36
+ }
37
+ allowErrors() {
38
+ this.errorsAllowed = true;
39
+ }
40
+ get errorsAreAllowed() {
41
+ return this.errorsAllowed;
42
+ }
43
+ }
@@ -29,8 +29,6 @@ export interface NavigationDeps {
29
29
  timeout: number;
30
30
  }): Promise<void>;
31
31
  }
32
- /** Resolves a slash-led `url` against `base`; any other URL is returned as given */
33
- export declare function resolveRelativeURL(url: string, base: string): string;
34
32
  /** Navigating one page: `goto`, `navigateTo`, `reload` and the wait for a load state */
35
33
  export declare class PageNavigation {
36
34
  private connector;
@@ -1,4 +1,5 @@
1
1
  import logger from "../logger/index.js";
2
+ import { RELATIVE_URL, resolveRelativeURL } from "../config/base-url.js";
2
3
  import { peekRunSettings } from "../config/run-settings.js";
3
4
  import { BiDiError } from "../transport/bidi-error.js";
4
5
  import { UnsupportedOperationError } from "../transport/unsupported.js";
@@ -6,12 +7,6 @@ import { matchUrl } from "../network/url-match.js";
6
7
  import { waitUntil, WaitTimeoutError } from "../wait/wait-until.js";
7
8
  import { LoadStateTimeoutError, NavigationError, UrlTimeoutError, } from "./navigation-error.js";
8
9
  import { writeContent } from "./page-content.js";
9
- /** A URL starting with "/", "./" or "../" */
10
- const RELATIVE_URL = /^\.{0,2}\//;
11
- /** Resolves a slash-led `url` against `base`; any other URL is returned as given */
12
- export function resolveRelativeURL(url, base) {
13
- return RELATIVE_URL.test(url) ? new URL(url, base).href : url;
14
- }
15
10
  /** Navigating one page: `goto`, `navigateTo`, `reload` and the wait for a load state */
16
11
  export class PageNavigation {
17
12
  constructor(connector, contextId, deps) {
@@ -19,7 +19,7 @@ export declare class PageConfig {
19
19
  timeout?: number;
20
20
  waitUntil?: "none" | "interactive" | "complete";
21
21
  }>;
22
- use(): Promise<import("../api.js").UseOptions & {
22
+ use(): Promise<import("../api.js").UseOptions & import("../api.js").FixtureUse & {
23
23
  viewport?: {
24
24
  width: number;
25
25
  height: number;
@@ -0,0 +1,21 @@
1
+ import type { BiDiConnector } from "../transport/bidi-connection.js";
2
+ import type { BrowsingContext } from "../types/bidi-modules/browsing-context.js";
3
+ import type { PageConfig } from "./page-settings.js";
4
+ import { type Viewport } from "./viewport.js";
5
+ /** A page's viewport: the size it was last given and whether Chrome lays it out like a phone */
6
+ export declare class PageViewport {
7
+ private readonly connector;
8
+ private readonly contextId;
9
+ private readonly config;
10
+ private known;
11
+ /** Chrome's phone layout is on (`setMobileLayout`): viewport changes keep it */
12
+ private mobile;
13
+ constructor(connector: BiDiConnector, contextId: BrowsingContext, config: PageConfig);
14
+ set(viewport: Viewport | null): Promise<void>;
15
+ setMobileLayout(): Promise<void>;
16
+ size(): {
17
+ width: number;
18
+ height: number;
19
+ } | null;
20
+ applyDefault(): Promise<void>;
21
+ }
@@ -0,0 +1,33 @@
1
+ import { DEFAULT_VIEWPORT } from "../config/defaults.js";
2
+ import { applyViewport } from "./viewport.js";
3
+ /** A page's viewport: the size it was last given and whether Chrome lays it out like a phone */
4
+ export class PageViewport {
5
+ constructor(connector, contextId, config) {
6
+ this.connector = connector;
7
+ this.contextId = contextId;
8
+ this.config = config;
9
+ this.known = null;
10
+ /** Chrome's phone layout is on (`setMobileLayout`): viewport changes keep it */
11
+ this.mobile = false;
12
+ }
13
+ async set(viewport) {
14
+ await applyViewport(this.connector, this.contextId, viewport, this.mobile);
15
+ this.known = viewport && {
16
+ width: viewport.width,
17
+ height: viewport.height,
18
+ };
19
+ }
20
+ async setMobileLayout() {
21
+ this.mobile = true;
22
+ await this.set(this.known);
23
+ }
24
+ size() {
25
+ return this.known && { ...this.known };
26
+ }
27
+ async applyDefault() {
28
+ const { viewport } = await this.config.use();
29
+ if (viewport === null)
30
+ return;
31
+ await this.set(viewport ?? DEFAULT_VIEWPORT);
32
+ }
33
+ }
@@ -1,3 +1,4 @@
1
+ import type { APIRequestContext } from "../request/api-request-context.js";
1
2
  import Locator from "../locator/locator.js";
2
3
  import type { FilterOptions } from "../locator/filter-step.js";
3
4
  import type { RoleOptions } from "../locator/role-locator.js";
@@ -7,25 +8,27 @@ import type { Recorder, RecorderOptions } from "../recorder/recorder.js";
7
8
  import { ContextTree } from "./context-tree.js";
8
9
  import type { BrowserContext } from "./browser-context.js";
9
10
  import { type PdfOptions } from "./pdf.js";
10
- import { type EmulationOptions } from "./emulation.js";
11
+ import type { RuntimeUseOptions } from "../types/use.js";
11
12
  import type { Download } from "./download.js";
12
13
  import type { FileChooser } from "./file-chooser.js";
13
14
  import { type ScreenshotOptions } from "./screenshot.js";
14
- import { Evidence } from "./evidence.js";
15
- import { type LogRecord, type PageError } from "./page-logs.js";
15
+ import type { Evidence } from "./evidence.js";
16
+ import type { LogRecord, PageError } from "./page-logs.js";
16
17
  import type { RouteHandler } from "../network/router.js";
17
18
  import { Response } from "../network/response.js";
18
19
  import type { NetworkMatch } from "../network/network-wait.js";
19
20
  import type { UrlPattern } from "../network/url-match.js";
20
21
  import type { LoadState, NavigateOptions, NavigationResult } from "./page-navigation.js";
22
+ import Keyboard from "./keyboard.js";
23
+ import Mouse from "./mouse.js";
21
24
  import { type PageEventName, type PageEvents, type WaitableEventName, type WaitForEventOptions, type WaitOptions } from "./page-events.js";
22
25
  export type { PageEventName, WaitableEventName, WaitForEventOptions, WaitOptions, };
23
26
  import { type PageSettings, type ReadinessState } from "./page-settings.js";
24
27
  export type { PageSettings, ReadinessState };
25
- import { type Viewport } from "./viewport.js";
28
+ import type { Viewport } from "./viewport.js";
26
29
  import type { PreloadHandle } from "../script/preload.js";
27
30
  import type { BiDiConnector } from "../transport/bidi-connection.js";
28
- import { type PageFunction } from "../script/call-function.js";
31
+ import type { PageFunction } from "../script/call-function.js";
29
32
  import type { BrowsingContext } from "../types/bidi-modules/browsing-context.js";
30
33
  export type { LoadState, NavigateOptions, NavigationResult };
31
34
  export default class Page {
@@ -36,20 +39,21 @@ export default class Page {
36
39
  private initScripts;
37
40
  private pageConfig;
38
41
  private locators;
42
+ /** Real key input to whatever has focus: `press` (also chords such as `Control+a`), `down`, `up`, `type`, `insertText` */
43
+ readonly keyboard: Keyboard;
44
+ /** Real pointer input at viewport coordinates: `move`, `down`, `up`, `click`, `dblclick`, `wheel` */
45
+ readonly mouse: Mouse;
39
46
  private pageEvents;
40
47
  private documents;
41
48
  private nav;
42
49
  private net;
43
50
  private dialogs;
44
- private lazyLogs;
45
- private errorsAllowed;
51
+ private logging;
46
52
  private isClosed;
47
53
  private browserContext;
48
54
  private transfers;
49
55
  private evidence;
50
- private knownViewport;
51
- /** Chrome's phone layout is on (`emulateMobile`): viewport changes keep it */
52
- private mobile;
56
+ private viewport;
53
57
  /** `settings` override what `samurai.config.ts` says; without them the config is read when needed */
54
58
  constructor(biDiConnector: BiDiConnector, contextId: string, settings?: PageSettings);
55
59
  /** The BiDi browsing context id of this page's top-level context */
@@ -181,7 +185,7 @@ export default class Page {
181
185
  */
182
186
  setViewport(viewport: Viewport | null): Promise<void>;
183
187
  /** @internal Chrome: lays this page out like a phone from now on (`use.isMobile`), including the size it has now */
184
- emulateMobile(): Promise<void>;
188
+ setMobileLayout(): Promise<void>;
185
189
  /** Playwright's name for `setViewport` */
186
190
  setViewportSize(size: Viewport): Promise<void>;
187
191
  /**
@@ -213,20 +217,23 @@ export default class Page {
213
217
  pdf(options?: PdfOptions): Promise<Buffer>;
214
218
  /** The browser context (cookies, storage) this page belongs to */
215
219
  context(): BrowserContext;
220
+ /** Makes HTTP requests from the test with this page's cookies; the same as `page.context().request` */
221
+ get request(): APIRequestContext;
216
222
  /** @internal Called when the page is opened in a context */
217
223
  setContext(context: BrowserContext): void;
218
224
  /** @internal Marks the page closed after its tab is gone (its context was removed) and releases what it registered */
219
225
  detach(): Promise<void>;
220
226
  /**
221
- * Pretends things about this page only: locale, timezone, user agent, geolocation, offline, orientation, screen
222
- * size. It wins over what the page's context says; `null` puts the real (or the context's) value back.
227
+ * Changes the `use` options that can change while the browser runs for this page only: locale, timezone, user
228
+ * agent, geolocation, offline, orientation, screen, hasTouch, javaScriptEnabled. It wins over what the page's
229
+ * context says; `null` puts the real (or the context's) value back. Same options as `test.use()`, applied now.
223
230
  * @example
224
- * await page.emulate({ locale: "nl-NL", offline: true });
231
+ * await page.use({ locale: "nl-NL", offline: true });
225
232
  */
226
- emulate(options: EmulationOptions): Promise<void>;
233
+ use(options: RuntimeUseOptions): Promise<void>;
227
234
  /**
228
235
  * Grants a browser permission ("geolocation", "notifications", "camera", …) to the origin this page is on,
229
- * or to `options.origin`. Geolocation emulation only answers once this is granted.
236
+ * or to `options.origin`. A geolocation override only answers once this is granted.
230
237
  */
231
238
  grantPermission(name: string, options?: {
232
239
  origin?: string;
@@ -1,16 +1,12 @@
1
1
  import Locator from "../locator/locator.js";
2
2
  import { attributeSelector, cssSelector, labelSelector, roleSelector, testIdSelector, textSelector, } from "../locator/selector.js";
3
3
  import EventEmitter from "node:events";
4
- import path from "node:path";
5
- import { projectDir } from "../config/config.js";
6
- import { DEFAULT_VIEWPORT } from "../config/defaults.js";
7
4
  import logger from "../logger/index.js";
8
5
  import { ContextTree } from "./context-tree.js";
9
6
  import { printPdf } from "./pdf.js";
10
- import { applyEmulation } from "./emulation.js";
11
- import { takeScreenshot, writeOutput, } from "./screenshot.js";
12
- import { Evidence } from "./evidence.js";
13
- import { PageLogs } from "./page-logs.js";
7
+ import { applyUse } from "./use-commands.js";
8
+ import { screenshotWithEvidence, } from "./screenshot.js";
9
+ import { PageLogging } from "./page-logging.js";
14
10
  import { Lazy } from "./lazy.js";
15
11
  import { Response } from "../network/response.js";
16
12
  import { traverseHistory } from "./history.js";
@@ -22,13 +18,14 @@ import { PageTransfers } from "./page-transfers.js";
22
18
  import { InitScripts } from "./init-script.js";
23
19
  import { grantPermission } from "./page-permissions.js";
24
20
  import { PageLocators } from "./page-locators.js";
25
- import { sourcesFor, waitForPageEvent, } from "./page-events.js";
21
+ import Keyboard from "./keyboard.js";
22
+ import Mouse from "./mouse.js";
23
+ import { startSources, waitForEventOn, waitForPageEvent, } from "./page-events.js";
26
24
  import { PageConfig, } from "./page-settings.js";
27
- import { applyViewport } from "./viewport.js";
25
+ import { PageViewport } from "./page-viewport.js";
28
26
  import { HelperRealm } from "../locator/helper-realm.js";
29
27
  import { BiDiError } from "../transport/bidi-error.js";
30
- import { callFunction } from "../script/call-function.js";
31
- import { readContent } from "./page-content.js";
28
+ import { readContent, readTitle, readUrl } from "./page-content.js";
32
29
  import { evaluateInPage } from "./page-evaluate.js";
33
30
  export default class Page {
34
31
  /** `settings` override what `samurai.config.ts` says; without them the config is read when needed */
@@ -36,18 +33,17 @@ export default class Page {
36
33
  this.lazyTree = new Lazy();
37
34
  this.initScripts = new InitScripts();
38
35
  this.pageEvents = new EventEmitter();
39
- this.lazyLogs = new Lazy();
40
- this.errorsAllowed = false;
41
36
  this.isClosed = false;
42
- this.knownViewport = null;
43
- /** Chrome's phone layout is on (`emulateMobile`): viewport changes keep it */
44
- this.mobile = false;
45
37
  this.biDiConnector = biDiConnector;
46
38
  this.id = contextId;
47
39
  this.pageConfig = new PageConfig(settings);
48
40
  this.helperRealm = new HelperRealm(biDiConnector, contextId);
49
41
  this.locators = new PageLocators(biDiConnector, contextId, this.helperRealm, this.pageConfig, () => this.assertOpen());
42
+ this.keyboard = new Keyboard(biDiConnector, contextId, () => this.assertOpen());
43
+ this.mouse = new Mouse(biDiConnector, contextId, () => this.assertOpen());
50
44
  this.documents = new DocumentWatcher(biDiConnector, contextId);
45
+ this.viewport = new PageViewport(biDiConnector, contextId, this.pageConfig);
46
+ this.logging = new PageLogging(biDiConnector, contextId, () => this.tree(), this.pageEvents);
51
47
  this.net = new PageNetwork(biDiConnector, contextId, {
52
48
  config: this.pageConfig,
53
49
  tree: { ensure: () => this.tree(), peek: () => this.lazyTree.peek() },
@@ -162,7 +158,7 @@ export default class Page {
162
158
  this.net.disposeTracker(),
163
159
  ]);
164
160
  const tree = this.lazyTree.take();
165
- const pageLogs = this.lazyLogs.take();
161
+ const pageLogs = this.logging.take();
166
162
  results.push(...(await Promise.allSettled([
167
163
  tree?.then((resolved) => resolved.dispose()),
168
164
  this.documents.dispose(),
@@ -224,7 +220,7 @@ export default class Page {
224
220
  /** The URL of the page now */
225
221
  async url() {
226
222
  this.assertOpen();
227
- return callFunction(this.biDiConnector, this.id, "() => location.href", [], { awaitPromise: false });
223
+ return readUrl(this.biDiConnector, this.id);
228
224
  }
229
225
  /**
230
226
  * Waits until the URL matches `url` (a string equals it, or with `*` / `**` is a glob; a RegExp is tested on it;
@@ -253,7 +249,7 @@ export default class Page {
253
249
  /** The title of the document now */
254
250
  async title() {
255
251
  this.assertOpen();
256
- return callFunction(this.biDiConnector, this.id, "() => document.title", [], { awaitPromise: false });
252
+ return readTitle(this.biDiConnector, this.id);
257
253
  }
258
254
  /**
259
255
  * Runs `fn` in the page, with `arg` as its only argument, and returns the result (a promise is awaited). `fn` is a function,
@@ -276,16 +272,11 @@ export default class Page {
276
272
  */
277
273
  async setViewport(viewport) {
278
274
  this.assertOpen();
279
- await applyViewport(this.biDiConnector, this.id, viewport, this.mobile);
280
- this.knownViewport = viewport && {
281
- width: viewport.width,
282
- height: viewport.height,
283
- };
275
+ await this.viewport.set(viewport);
284
276
  }
285
277
  /** @internal Chrome: lays this page out like a phone from now on (`use.isMobile`), including the size it has now */
286
- async emulateMobile() {
287
- this.mobile = true;
288
- await this.setViewport(this.knownViewport);
278
+ async setMobileLayout() {
279
+ await this.viewport.setMobileLayout();
289
280
  }
290
281
  /** Playwright's name for `setViewport` */
291
282
  setViewportSize(size) {
@@ -296,14 +287,11 @@ export default class Page {
296
287
  * browser opened it), read at once; null before one is known, after `setViewport(null)` and when config turns the viewport off.
297
288
  */
298
289
  viewportSize() {
299
- return this.knownViewport && { ...this.knownViewport };
290
+ return this.viewport.size();
300
291
  }
301
292
  /** Gives the page the size from config `use.viewport` (1280x720 by default); the browser does this when it opens a page */
302
293
  async applyDefaultViewport() {
303
- const { viewport } = await this.pageConfig.use();
304
- if (viewport === null)
305
- return;
306
- await this.setViewport(viewport ?? DEFAULT_VIEWPORT);
294
+ await this.viewport.applyDefault();
307
295
  }
308
296
  /**
309
297
  * What the browser sets up on every page it opens: the default size, then everything that has to be listening
@@ -324,12 +312,7 @@ export default class Page {
324
312
  */
325
313
  async screenshot(options = {}) {
326
314
  this.assertOpen();
327
- const image = await takeScreenshot(this.biDiConnector, this.id, options);
328
- if (options.name !== undefined) {
329
- const evidence = this.evidence ?? new Evidence(path.join(projectDir(), "result"));
330
- await writeOutput(evidence.fileFor(options.name, options.type).file, image);
331
- }
332
- return image;
315
+ return screenshotWithEvidence(this.biDiConnector, this.id, options, this.evidence);
333
316
  }
334
317
  /** @internal The runner gives each test its own evidence folder */
335
318
  setEvidence(evidence) {
@@ -352,6 +335,10 @@ export default class Page {
352
335
  throw new Error("this page has no browser context: open it with browser.newPage() or context.newPage()");
353
336
  return this.browserContext;
354
337
  }
338
+ /** Makes HTTP requests from the test with this page's cookies; the same as `page.context().request` */
339
+ get request() {
340
+ return this.context().request;
341
+ }
355
342
  /** @internal Called when the page is opened in a context */
356
343
  setContext(context) {
357
344
  this.browserContext = context;
@@ -362,18 +349,19 @@ export default class Page {
362
349
  await this.dispose().catch((err) => logger.debug("Releasing a detached page failed", { err }));
363
350
  }
364
351
  /**
365
- * Pretends things about this page only: locale, timezone, user agent, geolocation, offline, orientation, screen
366
- * size. It wins over what the page's context says; `null` puts the real (or the context's) value back.
352
+ * Changes the `use` options that can change while the browser runs for this page only: locale, timezone, user
353
+ * agent, geolocation, offline, orientation, screen, hasTouch, javaScriptEnabled. It wins over what the page's
354
+ * context says; `null` puts the real (or the context's) value back. Same options as `test.use()`, applied now.
367
355
  * @example
368
- * await page.emulate({ locale: "nl-NL", offline: true });
356
+ * await page.use({ locale: "nl-NL", offline: true });
369
357
  */
370
- async emulate(options) {
358
+ async use(options) {
371
359
  this.assertOpen();
372
- await applyEmulation(this.biDiConnector, { contexts: [this.id] }, options);
360
+ await applyUse(this.biDiConnector, { contexts: [this.id] }, options);
373
361
  }
374
362
  /**
375
363
  * Grants a browser permission ("geolocation", "notifications", "camera", …) to the origin this page is on,
376
- * or to `options.origin`. Geolocation emulation only answers once this is granted.
364
+ * or to `options.origin`. A geolocation override only answers once this is granted.
377
365
  */
378
366
  async grantPermission(name, options = {}) {
379
367
  this.assertOpen();
@@ -465,7 +453,7 @@ export default class Page {
465
453
  network: () => this.startNetworkTracking(),
466
454
  bodies: () => this.startBodyCollection(),
467
455
  };
468
- Promise.all(sourcesFor(event).map((source) => start[source]())).catch((err) => logger.error("Could not start listening for %s", event, { err }));
456
+ startSources(event, start, (err) => logger.error("Could not start listening for %s", event, { err }));
469
457
  }
470
458
  /** Starts following downloads; the browser does this when it opens a page, `page.on("download")` and `waitForDownload` when needed */
471
459
  startDownloadTracking() {
@@ -506,24 +494,18 @@ export default class Page {
506
494
  * await chooser.setFiles("./myfile.pdf");
507
495
  */
508
496
  async waitForEvent(event, optionsOrPredicate = {}) {
509
- const { predicate, ...options } = typeof optionsOrPredicate === "function"
510
- ? { predicate: optionsOrPredicate }
511
- : optionsOrPredicate;
512
- const accepts = (predicate ?? (() => true));
513
- switch (event) {
514
- case "request":
515
- return this.waitForRequest(accepts, options);
516
- case "response":
517
- return this.waitForResponse(accepts, options);
518
- case "download":
497
+ return waitForEventOn({
498
+ request: (accepts, options) => this.waitForRequest(accepts, options),
499
+ response: (accepts, options) => this.waitForResponse(accepts, options),
500
+ download: (options, accepts) => {
519
501
  this.assertOpen();
520
502
  return this.transfers.waitForDownload(options, accepts);
521
- case "filechooser":
503
+ },
504
+ filechooser: (options, accepts) => {
522
505
  this.assertOpen();
523
506
  return this.transfers.waitForFileChooser(options, accepts);
524
- default:
525
- throw new Error(`waitForEvent(): "${String(event)}" is not supported; use page.on()`);
526
- }
507
+ },
508
+ }, event, optionsOrPredicate);
527
509
  }
528
510
  /**
529
511
  * Starts keeping response bodies, which is what makes `response.body()` possible: the browser only keeps a body
@@ -588,27 +570,22 @@ export default class Page {
588
570
  * early output is kept; `page.on("console" | "pageerror")` starts it when needed.
589
571
  */
590
572
  async startLogging() {
591
- await this.lazyLogs.get(async () => {
592
- const logs = await PageLogs.start(this.biDiConnector, await this.tree(), this.id);
593
- logs.on("console", (message) => this.pageEvents.emit("console", message));
594
- logs.on("pageerror", (error) => this.pageEvents.emit("pageerror", error));
595
- return logs;
596
- });
573
+ await this.logging.start();
597
574
  }
598
575
  /** The newest console and error entries of this page (empty until logging started), oldest first */
599
576
  getLogs() {
600
- return this.lazyLogs.peek()?.entries ?? [];
577
+ return this.logging.entries();
601
578
  }
602
579
  /** Uncaught exceptions of the page since logging started or `clearLogs()` */
603
580
  pageErrors() {
604
- return this.lazyLogs.peek()?.errors ?? [];
581
+ return this.logging.errors();
605
582
  }
606
583
  /** How many entries fell out of the page's log buffer */
607
584
  get logsDropped() {
608
- return this.lazyLogs.peek()?.dropped ?? 0;
585
+ return this.logging.dropped;
609
586
  }
610
587
  clearLogs() {
611
- this.lazyLogs.peek()?.clear();
588
+ this.logging.clear();
612
589
  }
613
590
  /** Resolves once everything the page logged before the call has arrived: events and replies share one ordered connection */
614
591
  async syncLogs() {
@@ -616,10 +593,10 @@ export default class Page {
616
593
  }
617
594
  /** Tells the runner not to fail this test on uncaught page exceptions (config `logs.failOnPageError`) */
618
595
  allowPageErrors() {
619
- this.errorsAllowed = true;
596
+ this.logging.allowErrors();
620
597
  }
621
598
  get pageErrorsAllowed() {
622
- return this.errorsAllowed;
599
+ return this.logging.errorsAreAllowed;
623
600
  }
624
601
  /** Starts watching for dialogs; the browser does this when it opens a page, `page.on("dialog")` when needed */
625
602
  startDialogHandling() {
@@ -1,4 +1,5 @@
1
1
  import type { BiDiConnector } from "../transport/bidi-connection.js";
2
+ import { Evidence } from "./evidence.js";
2
3
  export interface ScreenshotOptions {
3
4
  /** Also write the image here (folders are created) */
4
5
  path?: string;
@@ -29,3 +30,8 @@ export declare function takeScreenshot(connector: BiDiConnector, context: string
29
30
  }): Promise<Buffer>;
30
31
  /** Writes `data` to `file`, creating the folders; nothing happens without a path */
31
32
  export declare function writeOutput(file: string | undefined, data: Buffer): Promise<void>;
33
+ /**
34
+ * `page.screenshot`: `takeScreenshot`, then, with `options.name`, a copy in the evidence folder (the test's own,
35
+ * or `result/` of the project when the page has none)
36
+ */
37
+ export declare function screenshotWithEvidence(connector: BiDiConnector, context: string, options: ScreenshotOptions, evidence: Evidence | undefined): Promise<Buffer>;
@@ -2,6 +2,8 @@ import { mkdir, writeFile } from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import { callFunction } from "../script/call-function.js";
4
4
  import { ElementHandle } from "../script/element-handle.js";
5
+ import { projectDir } from "../config/config.js";
6
+ import { Evidence } from "./evidence.js";
5
7
  const MIME = { png: "image/png", jpeg: "image/jpeg" };
6
8
  /** The `format` of a capture command; validates the type/quality combination */
7
9
  function imageFormat({ type, quality, }) {
@@ -57,3 +59,15 @@ export async function writeOutput(file, data) {
57
59
  await mkdir(path.dirname(file), { recursive: true });
58
60
  await writeFile(file, data);
59
61
  }
62
+ /**
63
+ * `page.screenshot`: `takeScreenshot`, then, with `options.name`, a copy in the evidence folder (the test's own,
64
+ * or `result/` of the project when the page has none)
65
+ */
66
+ export async function screenshotWithEvidence(connector, context, options, evidence) {
67
+ const image = await takeScreenshot(connector, context, options);
68
+ if (options.name !== undefined) {
69
+ const folder = evidence ?? new Evidence(path.join(projectDir(), "result"));
70
+ await writeOutput(folder.fileFor(options.name, options.type).file, image);
71
+ }
72
+ return image;
73
+ }
@@ -1,6 +1,4 @@
1
1
  import type { StorageState } from "../types/use.js";
2
2
  import type { CookieInput } from "./browser-context.js";
3
- /** The state itself, or the JSON file (relative to `baseDir`) that holds it. Throws `use(): what is wrong`. */
4
- export declare function loadStorageState(source: string | StorageState, baseDir: string): StorageState;
5
3
  /** The state's cookies as `context.addCookies()` takes them; `expires` of -1 (or none) is a session cookie */
6
4
  export declare function cookiesOf(state: StorageState): CookieInput[];
@@ -1,23 +1,3 @@
1
- import { readFileSync } from "node:fs";
2
- import path from "node:path";
3
- import { checkStorageStateObject } from "../config/use-options.js";
4
- /** The state itself, or the JSON file (relative to `baseDir`) that holds it. Throws `use(): what is wrong`. */
5
- export function loadStorageState(source, baseDir) {
6
- if (typeof source !== "string")
7
- return source;
8
- const file = path.resolve(baseDir, source);
9
- let state;
10
- try {
11
- state = JSON.parse(readFileSync(file, "utf8"));
12
- }
13
- catch (err) {
14
- throw new Error(`use(): storageState file ${JSON.stringify(source)} could not be read: ${err instanceof Error ? err.message : String(err)}`);
15
- }
16
- if (typeof state !== "object" || state === null || Array.isArray(state))
17
- throw new Error(`use(): storageState file ${JSON.stringify(source)} must hold an object`);
18
- checkStorageStateObject(state);
19
- return state;
20
- }
21
1
  const SAME_SITE = { Strict: "strict", Lax: "lax", None: "none" };
22
2
  /** The state's cookies as `context.addCookies()` takes them; `expires` of -1 (or none) is a session cookie */
23
3
  export function cookiesOf(state) {