@itmetsam/samurai-framework 0.21.4 → 0.22.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/dist/api.d.ts +8 -1
- package/dist/api.js +1 -0
- package/dist/browser/browser-context.d.ts +18 -6
- package/dist/browser/browser-context.js +25 -7
- package/dist/browser/browser.d.ts +11 -2
- package/dist/browser/browser.js +31 -8
- package/dist/browser/cdp.d.ts +11 -0
- package/dist/browser/cdp.js +28 -0
- package/dist/browser/chrome-launch.d.ts +4 -0
- package/dist/browser/chrome-launch.js +11 -3
- package/dist/browser/context-cookie-store.d.ts +17 -0
- package/dist/browser/context-cookie-store.js +90 -0
- package/dist/browser/index.d.ts +8 -1
- package/dist/browser/index.js +5 -1
- package/dist/browser/keyboard.d.ts +35 -0
- package/dist/browser/keyboard.js +83 -0
- package/dist/browser/launch-firefox.js +1 -1
- package/dist/browser/launch-flags.d.ts +4 -1
- package/dist/browser/launch-flags.js +18 -5
- package/dist/browser/mouse.d.ts +42 -0
- package/dist/browser/mouse.js +66 -0
- package/dist/browser/page-navigation.d.ts +0 -2
- package/dist/browser/page-navigation.js +1 -6
- package/dist/browser/page-settings.d.ts +1 -1
- package/dist/browser/page.d.ts +20 -6
- package/dist/browser/page.js +24 -8
- package/dist/browser/session-capabilities.d.ts +10 -0
- package/dist/browser/session-capabilities.js +11 -0
- package/dist/browser/session.d.ts +2 -1
- package/dist/browser/session.js +3 -3
- package/dist/browser/storage-state.d.ts +4 -0
- package/dist/browser/storage-state.js +17 -0
- package/dist/browser/use-applier.d.ts +38 -0
- package/dist/browser/use-applier.js +121 -0
- package/dist/browser/use-commands.d.ts +36 -0
- package/dist/browser/use-commands.js +120 -0
- package/dist/browser/viewport.d.ts +3 -2
- package/dist/browser/viewport.js +15 -2
- package/dist/cli/args.d.ts +8 -4
- package/dist/cli/args.js +8 -5
- package/dist/cli/console-reporter.js +5 -1
- package/dist/cli/main.js +1 -0
- package/dist/cli/usage.d.ts +1 -1
- package/dist/cli/usage.js +4 -1
- package/dist/config/base-url.d.ts +4 -0
- package/dist/config/base-url.js +6 -0
- package/dist/config/config.js +8 -0
- package/dist/config/fully-parallel.d.ts +3 -0
- package/dist/config/fully-parallel.js +6 -0
- package/dist/config/group-use.d.ts +8 -0
- package/dist/config/group-use.js +41 -0
- package/dist/config/resolved-config.d.ts +2 -0
- package/dist/config/resolved-config.js +3 -0
- package/dist/config/storage-state-file.d.ts +3 -0
- package/dist/config/storage-state-file.js +20 -0
- package/dist/config/use-options.d.ts +18 -0
- package/dist/config/use-options.js +184 -0
- package/dist/config/web-server-options.d.ts +7 -0
- package/dist/config/web-server-options.js +111 -0
- package/dist/locator/check-actions.js +1 -1
- package/dist/locator/drag-actions.d.ts +17 -0
- package/dist/locator/drag-actions.js +80 -0
- package/dist/locator/element-actions.d.ts +10 -4
- package/dist/locator/element-actions.js +23 -16
- package/dist/locator/element-state.d.ts +5 -0
- package/dist/locator/element-state.js +2 -1
- package/dist/locator/event-actions.d.ts +10 -0
- package/dist/locator/event-actions.js +81 -0
- package/dist/locator/input-actions.d.ts +2 -11
- package/dist/locator/input-actions.js +3 -56
- package/dist/locator/key-actions.d.ts +11 -0
- package/dist/locator/key-actions.js +52 -0
- package/dist/locator/key-map.d.ts +16 -0
- package/dist/locator/key-map.js +76 -0
- package/dist/locator/locator.d.ts +35 -5
- package/dist/locator/locator.js +70 -16
- package/dist/locator/page-scripts.d.ts +2 -0
- package/dist/locator/page-scripts.js +7 -0
- package/dist/locator/pointer-actions.d.ts +44 -0
- package/dist/locator/pointer-actions.js +117 -0
- package/dist/locator/pointer-element-actions.d.ts +21 -0
- package/dist/locator/pointer-element-actions.js +42 -0
- package/dist/logger/warn-once.d.ts +4 -0
- package/dist/logger/warn-once.js +13 -0
- package/dist/request/api-request-context.d.ts +65 -0
- package/dist/request/api-request-context.js +198 -0
- package/dist/request/api-response.d.ts +34 -0
- package/dist/request/api-response.js +57 -0
- package/dist/request/cookie-jar.d.ts +48 -0
- package/dist/request/cookie-jar.js +164 -0
- package/dist/request/http-transport.d.ts +31 -0
- package/dist/request/http-transport.js +113 -0
- package/dist/request/redirect.d.ts +10 -0
- package/dist/request/redirect.js +21 -0
- package/dist/request/request-body.d.ts +27 -0
- package/dist/request/request-body.js +71 -0
- package/dist/request/request.d.ts +16 -0
- package/dist/request/request.js +20 -0
- package/dist/runner/all-hooks.d.ts +7 -1
- package/dist/runner/all-hooks.js +17 -1
- package/dist/runner/attachments.d.ts +16 -0
- package/dist/runner/attachments.js +60 -0
- package/dist/runner/fixture-args.d.ts +15 -0
- package/dist/runner/fixture-args.js +95 -0
- package/dist/runner/fixture-defs.d.ts +22 -0
- package/dist/runner/fixture-defs.js +104 -0
- package/dist/runner/fixture-scope.d.ts +33 -0
- package/dist/runner/fixture-scope.js +156 -0
- package/dist/runner/fixtures.d.ts +37 -0
- package/dist/runner/fixtures.js +40 -0
- package/dist/runner/global-hooks.d.ts +13 -0
- package/dist/runner/global-hooks.js +66 -0
- package/dist/runner/hooks.d.ts +6 -1
- package/dist/runner/hooks.js +15 -3
- package/dist/runner/options.d.ts +13 -2
- package/dist/runner/output-tail.d.ts +14 -0
- package/dist/runner/output-tail.js +30 -0
- package/dist/runner/pool-schedule.d.ts +22 -0
- package/dist/runner/pool-schedule.js +55 -0
- package/dist/runner/pool-units.d.ts +34 -0
- package/dist/runner/pool-units.js +85 -0
- package/dist/runner/pooled-run.d.ts +7 -5
- package/dist/runner/pooled-run.js +19 -9
- package/dist/runner/registry.d.ts +3 -39
- package/dist/runner/registry.js +99 -21
- package/dist/runner/reporter.d.ts +17 -1
- package/dist/runner/reporter.js +15 -2
- package/dist/runner/request-fixture.d.ts +14 -0
- package/dist/runner/request-fixture.js +25 -0
- package/dist/runner/run-hooks.d.ts +16 -0
- package/dist/runner/run-hooks.js +52 -0
- package/dist/runner/run-test.d.ts +1 -3
- package/dist/runner/run-test.js +98 -35
- package/dist/runner/run.d.ts +1 -0
- package/dist/runner/run.js +1 -0
- package/dist/runner/select.d.ts +15 -4
- package/dist/runner/select.js +38 -12
- package/dist/runner/serial-run.d.ts +23 -0
- package/dist/runner/serial-run.js +88 -0
- package/dist/runner/serial.d.ts +34 -0
- package/dist/runner/serial.js +63 -0
- package/dist/runner/shard.d.ts +22 -0
- package/dist/runner/shard.js +73 -0
- package/dist/runner/steps.d.ts +13 -0
- package/dist/runner/steps.js +67 -0
- package/dist/runner/suite.d.ts +8 -3
- package/dist/runner/suite.js +12 -3
- package/dist/runner/test-annotations.d.ts +16 -0
- package/dist/runner/test-annotations.js +47 -0
- package/dist/runner/test-function.d.ts +115 -0
- package/dist/runner/test-function.js +1 -0
- package/dist/runner/test-info.d.ts +104 -0
- package/dist/runner/test-info.js +170 -0
- package/dist/runner/test-runner.d.ts +1 -0
- package/dist/runner/test-runner.js +64 -19
- package/dist/runner/test-timeout.d.ts +22 -0
- package/dist/runner/test-timeout.js +45 -0
- package/dist/runner/use-options.d.ts +7 -0
- package/dist/runner/use-options.js +25 -0
- package/dist/runner/wait-for-url.d.ts +20 -0
- package/dist/runner/wait-for-url.js +65 -0
- package/dist/runner/web-server.d.ts +16 -0
- package/dist/runner/web-server.js +165 -0
- package/dist/runner/worker-pool.d.ts +13 -5
- package/dist/runner/worker-pool.js +85 -51
- package/dist/runner/worker-protocol.d.ts +8 -0
- package/dist/runner/worker-session.js +5 -2
- package/dist/runner/worker.d.ts +2 -0
- package/dist/runner/worker.js +7 -1
- package/dist/script/element-handle.d.ts +1 -1
- package/dist/script/element-handle.js +1 -1
- package/dist/types/config.d.ts +86 -2
- package/dist/types/extend.d.ts +30 -0
- package/dist/types/test.d.ts +138 -0
- package/dist/types/use.d.ts +85 -0
- package/package.json +1 -1
- package/dist/browser/emulation.d.ts +0 -61
- package/dist/browser/emulation.js +0 -180
|
@@ -51,7 +51,7 @@ export function launchFirefox(launch) {
|
|
|
51
51
|
connector = await BiDiConnector.connect(url, { commandTimeout });
|
|
52
52
|
const status = await connector.send("session.status", {});
|
|
53
53
|
logger.verbose("Session status", status);
|
|
54
|
-
await startSession(connector, label, commandTimeout);
|
|
54
|
+
await startSession(connector, label, commandTimeout, launchOptions.use);
|
|
55
55
|
logger.verbose("Session started successfully");
|
|
56
56
|
settled();
|
|
57
57
|
resolve({ proc, connector });
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { SupportedBrowser } from "../types/browser.js";
|
|
2
|
+
import type { UseOptions } from "../types/use.js";
|
|
2
3
|
export interface BrowserLaunchOptions {
|
|
3
4
|
/** Which port to use for the WebDeveloper tools */
|
|
4
5
|
port: number;
|
|
@@ -6,6 +7,8 @@ export interface BrowserLaunchOptions {
|
|
|
6
7
|
profileDir: string;
|
|
7
8
|
/** Start the browser in headless mode or in UI mode */
|
|
8
9
|
headless: boolean;
|
|
10
|
+
/** The `use` options that can only be set when the browser starts (certificates, and Firefox's JavaScript and colour scheme) */
|
|
11
|
+
use?: UseOptions;
|
|
9
12
|
}
|
|
10
13
|
/** The profile folder of a worker: worker 0 keeps the browser's own folder, worker N gets `<browser>-wN` beside it */
|
|
11
14
|
export declare const workerProfileDir: (browserName: SupportedBrowser, workerIndex: number) => string;
|
|
@@ -15,4 +18,4 @@ export declare const browserLaunchFlag: (browserName: SupportedBrowser, launchOp
|
|
|
15
18
|
/** Env added to a browser's process */
|
|
16
19
|
export declare const browserLaunchEnv: (browserName: SupportedBrowser, appDataPath?: string) => Record<string, string>;
|
|
17
20
|
/** Writes the browser's user.js unless it already holds the current prefs; an outdated one is replaced */
|
|
18
|
-
export declare function ensureBrowserProfile(browserName: SupportedBrowser, profilePath?: string | undefined): void;
|
|
21
|
+
export declare function ensureBrowserProfile(browserName: SupportedBrowser, profilePath?: string | undefined, use?: UseOptions): void;
|
|
@@ -71,15 +71,28 @@ const browserProfiles = {
|
|
|
71
71
|
user_pref("browser.newtabpage.enabled", false);
|
|
72
72
|
`,
|
|
73
73
|
};
|
|
74
|
+
/**
|
|
75
|
+
* The prefs a Firefox run chooses. Always written, with the browser's own values when nothing is chosen: Firefox
|
|
76
|
+
* keeps a pref a user.js set in the profile, so a run without `use` would otherwise inherit the one before it.
|
|
77
|
+
*/
|
|
78
|
+
function usePrefs(use) {
|
|
79
|
+
const scheme = { light: 1, dark: 0, "no-preference": 2 }[use?.colorScheme ?? "no-preference"];
|
|
80
|
+
return `
|
|
81
|
+
user_pref("javascript.enabled", ${use?.javaScriptEnabled !== false});
|
|
82
|
+
user_pref("layout.css.prefers-color-scheme.content-override", ${scheme});
|
|
83
|
+
`;
|
|
84
|
+
}
|
|
85
|
+
const profileText = (browserName, use) => browserProfiles[browserName] +
|
|
86
|
+
(browserName === "firefox" ? usePrefs(use) : "");
|
|
74
87
|
/** Writes the browser's user.js unless it already holds the current prefs; an outdated one is replaced */
|
|
75
|
-
export function ensureBrowserProfile(browserName, profilePath = browserProfilePath(browserName)) {
|
|
88
|
+
export function ensureBrowserProfile(browserName, profilePath = browserProfilePath(browserName), use) {
|
|
76
89
|
logger.verbose("Checking browser profile for: %s", browserName);
|
|
77
90
|
if (!profilePath) {
|
|
78
91
|
logger.verbose("No browser profile path declared, skipping...");
|
|
79
92
|
return;
|
|
80
93
|
}
|
|
81
94
|
if (existsSync(profilePath)) {
|
|
82
|
-
if (readFileSync(profilePath, "utf8") ===
|
|
95
|
+
if (readFileSync(profilePath, "utf8") === profileText(browserName, use)) {
|
|
83
96
|
logger.verbose("Browser profile is up to date at: %s", profilePath);
|
|
84
97
|
return;
|
|
85
98
|
}
|
|
@@ -88,12 +101,12 @@ export function ensureBrowserProfile(browserName, profilePath = browserProfilePa
|
|
|
88
101
|
else {
|
|
89
102
|
logger.verbose("No browser profile created yet for %s", browserName);
|
|
90
103
|
}
|
|
91
|
-
createBrowserProfile(browserName, profilePath);
|
|
104
|
+
createBrowserProfile(browserName, profilePath, use);
|
|
92
105
|
}
|
|
93
|
-
function createBrowserProfile(browserName, profilePath) {
|
|
106
|
+
function createBrowserProfile(browserName, profilePath, use) {
|
|
94
107
|
logger.verbose("Creating browser profile for %s at %s", browserName, profilePath);
|
|
95
108
|
mkdirSync(path.dirname(profilePath), { recursive: true });
|
|
96
|
-
writeFileSync(profilePath,
|
|
109
|
+
writeFileSync(profilePath, profileText(browserName, use), {
|
|
97
110
|
encoding: "utf8",
|
|
98
111
|
});
|
|
99
112
|
logger.verbose("Browser profile created");
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { BiDiConnector } from "../transport/bidi-connection.js";
|
|
2
|
+
import { type MouseButton } from "../locator/pointer-actions.js";
|
|
3
|
+
/**
|
|
4
|
+
* The page's mouse: real pointer input at viewport coordinates (CSS pixels from the top left of the page's
|
|
5
|
+
* visible area). The pointer starts at 0, 0; `move` with `steps` runs from where the mouse last was through this object.
|
|
6
|
+
*/
|
|
7
|
+
export default class Mouse {
|
|
8
|
+
private connector;
|
|
9
|
+
private contextId;
|
|
10
|
+
private assertOpen;
|
|
11
|
+
private position;
|
|
12
|
+
constructor(connector: BiDiConnector, contextId: string, assertOpen: () => void);
|
|
13
|
+
/** Moves the pointer to `x`, `y` in a straight line of `steps` moves (default 1) */
|
|
14
|
+
move(x: number, y: number, options?: {
|
|
15
|
+
steps?: number;
|
|
16
|
+
}): Promise<void>;
|
|
17
|
+
/** Presses a button (default left) where the pointer is, and keeps it down until `up` */
|
|
18
|
+
down(options?: {
|
|
19
|
+
button?: MouseButton;
|
|
20
|
+
}): Promise<void>;
|
|
21
|
+
/** Releases a button pressed with `down` */
|
|
22
|
+
up(options?: {
|
|
23
|
+
button?: MouseButton;
|
|
24
|
+
}): Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* Moves to `x`, `y` and clicks. `clickCount` presses in a row (2 is a double click), `delay` ms between each press and release.
|
|
27
|
+
* @example
|
|
28
|
+
* await page.mouse.click(120, 80, { button: "right" });
|
|
29
|
+
*/
|
|
30
|
+
click(x: number, y: number, options?: {
|
|
31
|
+
button?: MouseButton;
|
|
32
|
+
clickCount?: number;
|
|
33
|
+
delay?: number;
|
|
34
|
+
}): Promise<void>;
|
|
35
|
+
/** `click` with two presses */
|
|
36
|
+
dblclick(x: number, y: number, options?: {
|
|
37
|
+
button?: MouseButton;
|
|
38
|
+
delay?: number;
|
|
39
|
+
}): Promise<void>;
|
|
40
|
+
/** Turns the wheel where the pointer is by `deltaX` and `deltaY` pixels (positive is right and down) */
|
|
41
|
+
wheel(deltaX: number, deltaY: number): Promise<void>;
|
|
42
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { performPointer, wheelScroll, buttonCode, checkClick, clickSteps, moveSteps, } from "../locator/pointer-actions.js";
|
|
2
|
+
/** Throws `mouse.method(): ...` unless every value is a finite number */
|
|
3
|
+
function numbers(method, what, ...values) {
|
|
4
|
+
if (!values.every(Number.isFinite)) {
|
|
5
|
+
throw new Error(`mouse.${method}(): ${what} must be numbers`);
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* The page's mouse: real pointer input at viewport coordinates (CSS pixels from the top left of the page's
|
|
10
|
+
* visible area). The pointer starts at 0, 0; `move` with `steps` runs from where the mouse last was through this object.
|
|
11
|
+
*/
|
|
12
|
+
export default class Mouse {
|
|
13
|
+
constructor(connector, contextId, assertOpen) {
|
|
14
|
+
this.connector = connector;
|
|
15
|
+
this.contextId = contextId;
|
|
16
|
+
this.assertOpen = assertOpen;
|
|
17
|
+
this.position = { x: 0, y: 0 };
|
|
18
|
+
}
|
|
19
|
+
/** Moves the pointer to `x`, `y` in a straight line of `steps` moves (default 1) */
|
|
20
|
+
async move(x, y, options) {
|
|
21
|
+
this.assertOpen();
|
|
22
|
+
numbers("move", "x and y", x, y);
|
|
23
|
+
const to = { x, y };
|
|
24
|
+
await performPointer(this.connector, this.contextId, moveSteps(this.position, to, options?.steps));
|
|
25
|
+
this.position = to;
|
|
26
|
+
}
|
|
27
|
+
/** Presses a button (default left) where the pointer is, and keeps it down until `up` */
|
|
28
|
+
async down(options) {
|
|
29
|
+
this.assertOpen();
|
|
30
|
+
await performPointer(this.connector, this.contextId, [
|
|
31
|
+
{
|
|
32
|
+
type: "pointerDown",
|
|
33
|
+
button: buttonCode(options?.button, "mouse.down"),
|
|
34
|
+
},
|
|
35
|
+
]);
|
|
36
|
+
}
|
|
37
|
+
/** Releases a button pressed with `down` */
|
|
38
|
+
async up(options) {
|
|
39
|
+
this.assertOpen();
|
|
40
|
+
await performPointer(this.connector, this.contextId, [
|
|
41
|
+
{ type: "pointerUp", button: buttonCode(options?.button, "mouse.up") },
|
|
42
|
+
]);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Moves to `x`, `y` and clicks. `clickCount` presses in a row (2 is a double click), `delay` ms between each press and release.
|
|
46
|
+
* @example
|
|
47
|
+
* await page.mouse.click(120, 80, { button: "right" });
|
|
48
|
+
*/
|
|
49
|
+
async click(x, y, options) {
|
|
50
|
+
this.assertOpen();
|
|
51
|
+
numbers("click", "x and y", x, y);
|
|
52
|
+
checkClick(options ?? {}, "mouse.click");
|
|
53
|
+
await performPointer(this.connector, this.contextId, clickSteps({ x, y }, options, "mouse.click"));
|
|
54
|
+
this.position = { x, y };
|
|
55
|
+
}
|
|
56
|
+
/** `click` with two presses */
|
|
57
|
+
async dblclick(x, y, options) {
|
|
58
|
+
await this.click(x, y, { ...options, clickCount: 2 });
|
|
59
|
+
}
|
|
60
|
+
/** Turns the wheel where the pointer is by `deltaX` and `deltaY` pixels (positive is right and down) */
|
|
61
|
+
async wheel(deltaX, deltaY) {
|
|
62
|
+
this.assertOpen();
|
|
63
|
+
numbers("wheel", "deltaX and deltaY", deltaX, deltaY);
|
|
64
|
+
await wheelScroll(this.connector, this.contextId, this.position, deltaX, deltaY);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -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) {
|
package/dist/browser/page.d.ts
CHANGED
|
@@ -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,7 +8,7 @@ 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 {
|
|
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";
|
|
@@ -18,6 +19,8 @@ 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";
|
|
@@ -36,6 +39,10 @@ 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;
|
|
@@ -48,6 +55,8 @@ export default class Page {
|
|
|
48
55
|
private transfers;
|
|
49
56
|
private evidence;
|
|
50
57
|
private knownViewport;
|
|
58
|
+
/** Chrome's phone layout is on (`setMobileLayout`): viewport changes keep it */
|
|
59
|
+
private mobile;
|
|
51
60
|
/** `settings` override what `samurai.config.ts` says; without them the config is read when needed */
|
|
52
61
|
constructor(biDiConnector: BiDiConnector, contextId: string, settings?: PageSettings);
|
|
53
62
|
/** The BiDi browsing context id of this page's top-level context */
|
|
@@ -178,6 +187,8 @@ export default class Page {
|
|
|
178
187
|
* await page.setViewport({ width: 390, height: 844, devicePixelRatio: 3 });
|
|
179
188
|
*/
|
|
180
189
|
setViewport(viewport: Viewport | null): Promise<void>;
|
|
190
|
+
/** @internal Chrome: lays this page out like a phone from now on (`use.isMobile`), including the size it has now */
|
|
191
|
+
setMobileLayout(): Promise<void>;
|
|
181
192
|
/** Playwright's name for `setViewport` */
|
|
182
193
|
setViewportSize(size: Viewport): Promise<void>;
|
|
183
194
|
/**
|
|
@@ -209,20 +220,23 @@ export default class Page {
|
|
|
209
220
|
pdf(options?: PdfOptions): Promise<Buffer>;
|
|
210
221
|
/** The browser context (cookies, storage) this page belongs to */
|
|
211
222
|
context(): BrowserContext;
|
|
223
|
+
/** Makes HTTP requests from the test with this page's cookies; the same as `page.context().request` */
|
|
224
|
+
get request(): APIRequestContext;
|
|
212
225
|
/** @internal Called when the page is opened in a context */
|
|
213
226
|
setContext(context: BrowserContext): void;
|
|
214
227
|
/** @internal Marks the page closed after its tab is gone (its context was removed) and releases what it registered */
|
|
215
228
|
detach(): Promise<void>;
|
|
216
229
|
/**
|
|
217
|
-
*
|
|
218
|
-
*
|
|
230
|
+
* Changes the `use` options that can change while the browser runs for this page only: locale, timezone, user
|
|
231
|
+
* agent, geolocation, offline, orientation, screen, hasTouch, javaScriptEnabled. It wins over what the page's
|
|
232
|
+
* context says; `null` puts the real (or the context's) value back. Same options as `test.use()`, applied now.
|
|
219
233
|
* @example
|
|
220
|
-
* await page.
|
|
234
|
+
* await page.use({ locale: "nl-NL", offline: true });
|
|
221
235
|
*/
|
|
222
|
-
|
|
236
|
+
use(options: RuntimeUseOptions): Promise<void>;
|
|
223
237
|
/**
|
|
224
238
|
* Grants a browser permission ("geolocation", "notifications", "camera", …) to the origin this page is on,
|
|
225
|
-
* or to `options.origin`.
|
|
239
|
+
* or to `options.origin`. A geolocation override only answers once this is granted.
|
|
226
240
|
*/
|
|
227
241
|
grantPermission(name: string, options?: {
|
|
228
242
|
origin?: string;
|
package/dist/browser/page.js
CHANGED
|
@@ -7,7 +7,7 @@ import { DEFAULT_VIEWPORT } from "../config/defaults.js";
|
|
|
7
7
|
import logger from "../logger/index.js";
|
|
8
8
|
import { ContextTree } from "./context-tree.js";
|
|
9
9
|
import { printPdf } from "./pdf.js";
|
|
10
|
-
import {
|
|
10
|
+
import { applyUse } from "./use-commands.js";
|
|
11
11
|
import { takeScreenshot, writeOutput, } from "./screenshot.js";
|
|
12
12
|
import { Evidence } from "./evidence.js";
|
|
13
13
|
import { PageLogs } from "./page-logs.js";
|
|
@@ -22,6 +22,8 @@ import { PageTransfers } from "./page-transfers.js";
|
|
|
22
22
|
import { InitScripts } from "./init-script.js";
|
|
23
23
|
import { grantPermission } from "./page-permissions.js";
|
|
24
24
|
import { PageLocators } from "./page-locators.js";
|
|
25
|
+
import Keyboard from "./keyboard.js";
|
|
26
|
+
import Mouse from "./mouse.js";
|
|
25
27
|
import { sourcesFor, waitForPageEvent, } from "./page-events.js";
|
|
26
28
|
import { PageConfig, } from "./page-settings.js";
|
|
27
29
|
import { applyViewport } from "./viewport.js";
|
|
@@ -40,11 +42,15 @@ export default class Page {
|
|
|
40
42
|
this.errorsAllowed = false;
|
|
41
43
|
this.isClosed = false;
|
|
42
44
|
this.knownViewport = null;
|
|
45
|
+
/** Chrome's phone layout is on (`setMobileLayout`): viewport changes keep it */
|
|
46
|
+
this.mobile = false;
|
|
43
47
|
this.biDiConnector = biDiConnector;
|
|
44
48
|
this.id = contextId;
|
|
45
49
|
this.pageConfig = new PageConfig(settings);
|
|
46
50
|
this.helperRealm = new HelperRealm(biDiConnector, contextId);
|
|
47
51
|
this.locators = new PageLocators(biDiConnector, contextId, this.helperRealm, this.pageConfig, () => this.assertOpen());
|
|
52
|
+
this.keyboard = new Keyboard(biDiConnector, contextId, () => this.assertOpen());
|
|
53
|
+
this.mouse = new Mouse(biDiConnector, contextId, () => this.assertOpen());
|
|
48
54
|
this.documents = new DocumentWatcher(biDiConnector, contextId);
|
|
49
55
|
this.net = new PageNetwork(biDiConnector, contextId, {
|
|
50
56
|
config: this.pageConfig,
|
|
@@ -274,12 +280,17 @@ export default class Page {
|
|
|
274
280
|
*/
|
|
275
281
|
async setViewport(viewport) {
|
|
276
282
|
this.assertOpen();
|
|
277
|
-
await applyViewport(this.biDiConnector, this.id, viewport);
|
|
283
|
+
await applyViewport(this.biDiConnector, this.id, viewport, this.mobile);
|
|
278
284
|
this.knownViewport = viewport && {
|
|
279
285
|
width: viewport.width,
|
|
280
286
|
height: viewport.height,
|
|
281
287
|
};
|
|
282
288
|
}
|
|
289
|
+
/** @internal Chrome: lays this page out like a phone from now on (`use.isMobile`), including the size it has now */
|
|
290
|
+
async setMobileLayout() {
|
|
291
|
+
this.mobile = true;
|
|
292
|
+
await this.setViewport(this.knownViewport);
|
|
293
|
+
}
|
|
283
294
|
/** Playwright's name for `setViewport` */
|
|
284
295
|
setViewportSize(size) {
|
|
285
296
|
return this.setViewport(size);
|
|
@@ -345,6 +356,10 @@ export default class Page {
|
|
|
345
356
|
throw new Error("this page has no browser context: open it with browser.newPage() or context.newPage()");
|
|
346
357
|
return this.browserContext;
|
|
347
358
|
}
|
|
359
|
+
/** Makes HTTP requests from the test with this page's cookies; the same as `page.context().request` */
|
|
360
|
+
get request() {
|
|
361
|
+
return this.context().request;
|
|
362
|
+
}
|
|
348
363
|
/** @internal Called when the page is opened in a context */
|
|
349
364
|
setContext(context) {
|
|
350
365
|
this.browserContext = context;
|
|
@@ -355,18 +370,19 @@ export default class Page {
|
|
|
355
370
|
await this.dispose().catch((err) => logger.debug("Releasing a detached page failed", { err }));
|
|
356
371
|
}
|
|
357
372
|
/**
|
|
358
|
-
*
|
|
359
|
-
*
|
|
373
|
+
* Changes the `use` options that can change while the browser runs for this page only: locale, timezone, user
|
|
374
|
+
* agent, geolocation, offline, orientation, screen, hasTouch, javaScriptEnabled. It wins over what the page's
|
|
375
|
+
* context says; `null` puts the real (or the context's) value back. Same options as `test.use()`, applied now.
|
|
360
376
|
* @example
|
|
361
|
-
* await page.
|
|
377
|
+
* await page.use({ locale: "nl-NL", offline: true });
|
|
362
378
|
*/
|
|
363
|
-
async
|
|
379
|
+
async use(options) {
|
|
364
380
|
this.assertOpen();
|
|
365
|
-
await
|
|
381
|
+
await applyUse(this.biDiConnector, { contexts: [this.id] }, options);
|
|
366
382
|
}
|
|
367
383
|
/**
|
|
368
384
|
* Grants a browser permission ("geolocation", "notifications", "camera", …) to the origin this page is on,
|
|
369
|
-
* or to `options.origin`.
|
|
385
|
+
* or to `options.origin`. A geolocation override only answers once this is granted.
|
|
370
386
|
*/
|
|
371
387
|
async grantPermission(name, options = {}) {
|
|
372
388
|
this.assertOpen();
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { UseOptions } from "../types/use.js";
|
|
1
2
|
/**
|
|
2
3
|
* Sent with session.new (Firefox) or as capabilities of the chromedriver session (Chrome). With "ignore" the browser
|
|
3
4
|
* leaves a JavaScript dialog open and reports it (`browsingContext.userPromptOpened`), so the framework decides how
|
|
@@ -10,3 +11,12 @@ export declare const SESSION_CAPABILITIES: {
|
|
|
10
11
|
};
|
|
11
12
|
};
|
|
12
13
|
};
|
|
14
|
+
/** The session capabilities for a launch: the ones above, plus the `use` option that has to be set when the browser starts */
|
|
15
|
+
export declare function sessionCapabilities(use?: Pick<UseOptions, "acceptInsecureCerts">): {
|
|
16
|
+
alwaysMatch: {
|
|
17
|
+
acceptInsecureCerts?: boolean | undefined;
|
|
18
|
+
unhandledPromptBehavior: {
|
|
19
|
+
default: string;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
};
|
|
@@ -6,3 +6,14 @@
|
|
|
6
6
|
export const SESSION_CAPABILITIES = {
|
|
7
7
|
alwaysMatch: { unhandledPromptBehavior: { default: "ignore" } },
|
|
8
8
|
};
|
|
9
|
+
/** The session capabilities for a launch: the ones above, plus the `use` option that has to be set when the browser starts */
|
|
10
|
+
export function sessionCapabilities(use) {
|
|
11
|
+
return {
|
|
12
|
+
alwaysMatch: {
|
|
13
|
+
...SESSION_CAPABILITIES.alwaysMatch,
|
|
14
|
+
...(use?.acceptInsecureCerts !== undefined && {
|
|
15
|
+
acceptInsecureCerts: use.acceptInsecureCerts,
|
|
16
|
+
}),
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import type { BiDiConnector } from "../transport/bidi-connection.js";
|
|
2
|
+
import type { UseOptions } from "../types/use.js";
|
|
2
3
|
/** session.new got no reply in time */
|
|
3
4
|
export declare class BrowserStartTimeoutError extends Error {
|
|
4
5
|
constructor(label: string, timeout: number);
|
|
5
6
|
}
|
|
6
7
|
/** Sends session.new with its own, longer timeout; `label` names the browser in the timeout error */
|
|
7
|
-
export declare function startSession(connector: Pick<BiDiConnector, "send">, label: string, commandTimeout: number | undefined): Promise<void>;
|
|
8
|
+
export declare function startSession(connector: Pick<BiDiConnector, "send">, label: string, commandTimeout: number | undefined, use?: UseOptions): Promise<void>;
|
|
8
9
|
/**
|
|
9
10
|
* Stops what a failed launch started. A browser left running holds the profile (the next launch
|
|
10
11
|
* fails with "already open") and keeps the test process alive.
|
package/dist/browser/session.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { DEFAULT_COMMAND_TIMEOUT } from "../config/defaults.js";
|
|
2
2
|
import { BiDiError } from "../transport/bidi-error.js";
|
|
3
|
-
import {
|
|
3
|
+
import { sessionCapabilities } from "./session-capabilities.js";
|
|
4
4
|
/** session.new waits at least this long (ms): the first start after an install can take a while (macOS checks the new app) */
|
|
5
5
|
const SESSION_START_TIMEOUT = 120_000;
|
|
6
6
|
/** session.new got no reply in time */
|
|
@@ -11,10 +11,10 @@ export class BrowserStartTimeoutError extends Error {
|
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
13
|
/** Sends session.new with its own, longer timeout; `label` names the browser in the timeout error */
|
|
14
|
-
export async function startSession(connector, label, commandTimeout) {
|
|
14
|
+
export async function startSession(connector, label, commandTimeout, use) {
|
|
15
15
|
const timeout = Math.max(commandTimeout ?? DEFAULT_COMMAND_TIMEOUT, SESSION_START_TIMEOUT);
|
|
16
16
|
try {
|
|
17
|
-
await connector.send("session.new", { capabilities:
|
|
17
|
+
await connector.send("session.new", { capabilities: sessionCapabilities(use) }, { timeout });
|
|
18
18
|
}
|
|
19
19
|
catch (err) {
|
|
20
20
|
if (err instanceof BiDiError && err.code === "timeout")
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { StorageState } from "../types/use.js";
|
|
2
|
+
import type { CookieInput } from "./browser-context.js";
|
|
3
|
+
/** The state's cookies as `context.addCookies()` takes them; `expires` of -1 (or none) is a session cookie */
|
|
4
|
+
export declare function cookiesOf(state: StorageState): CookieInput[];
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const SAME_SITE = { Strict: "strict", Lax: "lax", None: "none" };
|
|
2
|
+
/** The state's cookies as `context.addCookies()` takes them; `expires` of -1 (or none) is a session cookie */
|
|
3
|
+
export function cookiesOf(state) {
|
|
4
|
+
return (state.cookies ?? []).map((cookie) => ({
|
|
5
|
+
name: cookie.name,
|
|
6
|
+
value: cookie.value,
|
|
7
|
+
domain: cookie.domain,
|
|
8
|
+
...(cookie.path !== undefined && { path: cookie.path }),
|
|
9
|
+
...(cookie.expires !== undefined &&
|
|
10
|
+
cookie.expires > 0 && { expiry: cookie.expires }),
|
|
11
|
+
...(cookie.httpOnly !== undefined && { httpOnly: cookie.httpOnly }),
|
|
12
|
+
...(cookie.secure !== undefined && { secure: cookie.secure }),
|
|
13
|
+
...(cookie.sameSite !== undefined && {
|
|
14
|
+
sameSite: SAME_SITE[cookie.sameSite],
|
|
15
|
+
}),
|
|
16
|
+
}));
|
|
17
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { SupportedBrowser } from "../types/browser.js";
|
|
2
|
+
import type { BiDiConnector } from "../transport/bidi-connection.js";
|
|
3
|
+
import type { GroupUse } from "../types/config.js";
|
|
4
|
+
import type { BrowserContext } from "./browser-context.js";
|
|
5
|
+
import type Page from "./page.js";
|
|
6
|
+
/** The `use` options a browser applies to its contexts and pages, by what each browser can do (see the wiki's support matrix) */
|
|
7
|
+
export declare class UseApplier {
|
|
8
|
+
private readonly connector;
|
|
9
|
+
private readonly options;
|
|
10
|
+
private readonly state;
|
|
11
|
+
private readonly chrome;
|
|
12
|
+
constructor(connector: BiDiConnector, browserName: SupportedBrowser, options: GroupUse, projectDir: string);
|
|
13
|
+
/** What new contexts accept unless they are told otherwise */
|
|
14
|
+
get acceptInsecureCerts(): boolean | undefined;
|
|
15
|
+
/** Whether pages are laid out like a phone: Chrome only */
|
|
16
|
+
get isMobile(): boolean;
|
|
17
|
+
/** Whether pages have touch input: Chrome only */
|
|
18
|
+
get hasTouch(): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Sets up a context: the run-time options (`page.use()`'s) through BiDi, the extra headers, then the storage state.
|
|
21
|
+
* An option the browser does not support is skipped with a one-time warning (`page.use()` throws instead).
|
|
22
|
+
* Firefox gets JavaScript and the colour scheme when it starts (a profile setting).
|
|
23
|
+
*/
|
|
24
|
+
context(context: BrowserContext): Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* Sets up a page of a context this applier set up: Chrome's phone layout and colour scheme (CDP, so per page), and
|
|
27
|
+
* the viewport a group asked for (after the phone layout, so the size keeps it)
|
|
28
|
+
*/
|
|
29
|
+
page(page: Page): Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* The declared options that go through BiDi. Left out: those that are not set (or `null`), the ones that change
|
|
32
|
+
* nothing (`offline: false`), Chrome-only touch on Firefox (warned once in the constructor) and JavaScript on
|
|
33
|
+
* Firefox (a profile setting).
|
|
34
|
+
*/
|
|
35
|
+
private startUp;
|
|
36
|
+
private chromePage;
|
|
37
|
+
private restore;
|
|
38
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { warnOnce } from "../logger/warn-once.js";
|
|
2
|
+
import { cdpSend, sendRaw } from "./cdp.js";
|
|
3
|
+
import { applyUse, RUNTIME_OPTIONS } from "./use-commands.js";
|
|
4
|
+
import { cookiesOf } from "./storage-state.js";
|
|
5
|
+
import { loadStorageState } from "../config/storage-state-file.js";
|
|
6
|
+
/** The `use` options a browser applies to its contexts and pages, by what each browser can do (see the wiki's support matrix) */
|
|
7
|
+
export class UseApplier {
|
|
8
|
+
constructor(connector, browserName, options, projectDir) {
|
|
9
|
+
this.connector = connector;
|
|
10
|
+
this.options = options;
|
|
11
|
+
this.chrome = browserName === "chrome";
|
|
12
|
+
// Read now: a wrong path fails the test before it starts
|
|
13
|
+
if (options.storageState !== undefined)
|
|
14
|
+
this.state = loadStorageState(options.storageState, projectDir);
|
|
15
|
+
if (!this.chrome) {
|
|
16
|
+
if (options.isMobile)
|
|
17
|
+
warnOnce("use.isMobile", "use.isMobile only works on Chrome: it is ignored on Firefox");
|
|
18
|
+
if (options.hasTouch)
|
|
19
|
+
warnOnce("use.hasTouch", "use.hasTouch only works on Chrome: it is ignored on Firefox");
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/** What new contexts accept unless they are told otherwise */
|
|
23
|
+
get acceptInsecureCerts() {
|
|
24
|
+
return this.options.acceptInsecureCerts;
|
|
25
|
+
}
|
|
26
|
+
/** Whether pages are laid out like a phone: Chrome only */
|
|
27
|
+
get isMobile() {
|
|
28
|
+
return this.chrome && this.options.isMobile === true;
|
|
29
|
+
}
|
|
30
|
+
/** Whether pages have touch input: Chrome only */
|
|
31
|
+
get hasTouch() {
|
|
32
|
+
return this.chrome && this.options.hasTouch === true;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Sets up a context: the run-time options (`page.use()`'s) through BiDi, the extra headers, then the storage state.
|
|
36
|
+
* An option the browser does not support is skipped with a one-time warning (`page.use()` throws instead).
|
|
37
|
+
* Firefox gets JavaScript and the colour scheme when it starts (a profile setting).
|
|
38
|
+
*/
|
|
39
|
+
async context(context) {
|
|
40
|
+
const { options } = this;
|
|
41
|
+
await applyUse(this.connector, { userContexts: [context.id] }, this.startUp(), (option) => warnOnce(`use.${option}`, `use.${option} is not supported by this browser: it is ignored`));
|
|
42
|
+
context.setRequestDefaults({
|
|
43
|
+
extraHTTPHeaders: options.extraHTTPHeaders,
|
|
44
|
+
ignoreHTTPSErrors: options.acceptInsecureCerts,
|
|
45
|
+
});
|
|
46
|
+
if (options.extraHTTPHeaders)
|
|
47
|
+
await sendRaw(this.connector, "network.setExtraHeaders", {
|
|
48
|
+
headers: Object.entries(options.extraHTTPHeaders).map(([name, value]) => ({ name, value: { type: "string", value } })),
|
|
49
|
+
userContexts: [context.id],
|
|
50
|
+
});
|
|
51
|
+
if (this.state)
|
|
52
|
+
await this.restore(context, this.state);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Sets up a page of a context this applier set up: Chrome's phone layout and colour scheme (CDP, so per page), and
|
|
56
|
+
* the viewport a group asked for (after the phone layout, so the size keeps it)
|
|
57
|
+
*/
|
|
58
|
+
async page(page) {
|
|
59
|
+
if (this.chrome)
|
|
60
|
+
await this.chromePage(page);
|
|
61
|
+
if (this.options.viewport !== undefined)
|
|
62
|
+
await page.setViewport(this.options.viewport);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* The declared options that go through BiDi. Left out: those that are not set (or `null`), the ones that change
|
|
66
|
+
* nothing (`offline: false`), Chrome-only touch on Firefox (warned once in the constructor) and JavaScript on
|
|
67
|
+
* Firefox (a profile setting).
|
|
68
|
+
*/
|
|
69
|
+
startUp() {
|
|
70
|
+
const { options } = this;
|
|
71
|
+
const wanted = {};
|
|
72
|
+
for (const name of RUNTIME_OPTIONS) {
|
|
73
|
+
const value = options[name];
|
|
74
|
+
if (value === undefined || value === null)
|
|
75
|
+
continue;
|
|
76
|
+
if (name === "offline" && value === false)
|
|
77
|
+
continue;
|
|
78
|
+
if (name === "hasTouch" && !this.hasTouch)
|
|
79
|
+
continue;
|
|
80
|
+
if (name === "javaScriptEnabled" && !(this.chrome && value === false))
|
|
81
|
+
continue;
|
|
82
|
+
wanted[name] = value;
|
|
83
|
+
}
|
|
84
|
+
return wanted;
|
|
85
|
+
}
|
|
86
|
+
async chromePage(page) {
|
|
87
|
+
if (this.isMobile)
|
|
88
|
+
await page.setMobileLayout();
|
|
89
|
+
if (this.options.colorScheme !== undefined)
|
|
90
|
+
await cdpSend(this.connector, page.contextId, "Emulation.setEmulatedMedia", {
|
|
91
|
+
features: [
|
|
92
|
+
{ name: "prefers-color-scheme", value: this.options.colorScheme },
|
|
93
|
+
],
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
async restore(context, state) {
|
|
97
|
+
const cookies = cookiesOf(state);
|
|
98
|
+
if (cookies.length > 0)
|
|
99
|
+
await context.addCookies(cookies);
|
|
100
|
+
for (const { origin, localStorage } of state.origins ?? []) {
|
|
101
|
+
if (localStorage.length === 0)
|
|
102
|
+
continue;
|
|
103
|
+
await writeLocalStorage(context, origin, localStorage);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Local storage belongs to an origin, so the browser has to be on it: a scratch tab opens the origin (the request is
|
|
109
|
+
* answered here, it never reaches the network), writes the entries and closes again.
|
|
110
|
+
*/
|
|
111
|
+
async function writeLocalStorage(context, origin, entries) {
|
|
112
|
+
const tab = await context.newPage();
|
|
113
|
+
try {
|
|
114
|
+
await tab.route(`${origin}/**`, (route) => route.fulfill({ contentType: "text/html", body: "<!doctype html>" }));
|
|
115
|
+
await tab.navigateTo(`${origin}/`);
|
|
116
|
+
await tab.evaluate((items) => items.forEach(({ name, value }) => localStorage.setItem(name, value)), entries);
|
|
117
|
+
}
|
|
118
|
+
finally {
|
|
119
|
+
await tab.close();
|
|
120
|
+
}
|
|
121
|
+
}
|