@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
package/dist/api.d.ts
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
export { test, describe, beforeEach, afterEach, beforeAll, afterAll, } from "./runner/registry.js";
|
|
2
|
+
export { request, type NewRequestContextOptions } from "./request/request.js";
|
|
3
|
+
export type { APIRequestContext, RequestOptions, } from "./request/api-request-context.js";
|
|
4
|
+
export type { APIResponse } from "./request/api-response.js";
|
|
5
|
+
export type { FilePayload } from "./request/request-body.js";
|
|
2
6
|
export { expect } from "./assert/expect.js";
|
|
3
7
|
export { defineConfig } from "./config/config.js";
|
|
8
|
+
export type { GlobalHookConfig, WebServerConfig } from "./types/config.js";
|
|
4
9
|
export { defineStep, type StepDefinition, type StepResult, type StepRunInput, } from "./flows/steps/index.js";
|
|
5
|
-
export type { AllHookFixtures, TestDetails, TestFixtures, TestOptions, TestOutputs, } from "./types/test.js";
|
|
10
|
+
export type { AllHookFixtures, TestAnnotation, TestAttachment, TestDetails, TestFixtures, TestInfo, TestOptions, TestOutputs, TestStepEntry, } from "./types/test.js";
|
|
11
|
+
export type { FixtureOptions } from "./types/extend.js";
|
|
12
|
+
export type { ColorScheme, StorageState, StorageStateCookie, RuntimeUseOptions, UseOptions, } from "./types/use.js";
|
|
6
13
|
export { generatedFile, type GeneratedFile, type FileInput, } from "./browser/generated-files.js";
|
package/dist/api.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { test, describe, beforeEach, afterEach, beforeAll, afterAll, } from "./runner/registry.js";
|
|
2
|
+
export { request } from "./request/request.js";
|
|
2
3
|
export { expect } from "./assert/expect.js";
|
|
3
4
|
export { defineConfig } from "./config/config.js";
|
|
4
5
|
export { defineStep, } from "./flows/steps/index.js";
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { BiDiConnector } from "../transport/bidi-connection.js";
|
|
2
2
|
import type { SameSite } from "../types/bidi-modules/network.js";
|
|
3
|
-
import { type
|
|
3
|
+
import { APIRequestContext, type RequestContextOptions } from "../request/api-request-context.js";
|
|
4
|
+
import type { RuntimeUseOptions } from "../types/use.js";
|
|
4
5
|
import type Page from "./page.js";
|
|
5
6
|
/** A cookie as `context.cookies()` returns it */
|
|
6
7
|
export interface Cookie {
|
|
@@ -54,6 +55,8 @@ export declare class BrowserContext {
|
|
|
54
55
|
readonly isDefault: boolean;
|
|
55
56
|
private opened;
|
|
56
57
|
private isClosed;
|
|
58
|
+
private requestContext;
|
|
59
|
+
private readonly requestDefaults;
|
|
57
60
|
constructor(host: ContextHost,
|
|
58
61
|
/** The BiDi user context id; "default" for the browser's own */
|
|
59
62
|
id: string, isDefault: boolean);
|
|
@@ -65,6 +68,14 @@ export declare class BrowserContext {
|
|
|
65
68
|
pages(): Page[];
|
|
66
69
|
/** @internal Called when a page is opened in this context */
|
|
67
70
|
register(page: Page): void;
|
|
71
|
+
/**
|
|
72
|
+
* Makes HTTP requests from the test that share this context's cookies: they send the cookies the pages hold,
|
|
73
|
+
* and the cookies the responses set are in the pages' `document.cookie` afterwards. Its `extraHTTPHeaders`
|
|
74
|
+
* and `ignoreHTTPSErrors` follow the run's `use` options.
|
|
75
|
+
*/
|
|
76
|
+
get request(): APIRequestContext;
|
|
77
|
+
/** @internal What `request` starts from: the run's `use.extraHTTPHeaders` and `acceptInsecureCerts` */
|
|
78
|
+
setRequestDefaults(defaults: RequestContextOptions): void;
|
|
68
79
|
/** Adds cookies to this context's storage. Every cookie is checked before any is stored */
|
|
69
80
|
addCookies(cookies: CookieInput[]): Promise<void>;
|
|
70
81
|
/** The cookies of this context, optionally only those matching `filter` */
|
|
@@ -72,15 +83,16 @@ export declare class BrowserContext {
|
|
|
72
83
|
/** Deletes all cookies of this context, or those matching `filter` */
|
|
73
84
|
clearCookies(filter?: CookieFilter): Promise<void>;
|
|
74
85
|
/**
|
|
75
|
-
*
|
|
76
|
-
* geolocation, offline, orientation, screen
|
|
86
|
+
* Changes the `use` options that can change while the browser runs for every page of this context, including
|
|
87
|
+
* pages opened later: locale, timezone, user agent, geolocation, offline, orientation, screen, hasTouch,
|
|
88
|
+
* javaScriptEnabled. A page's own `use()` wins over the context's.
|
|
77
89
|
* @example
|
|
78
|
-
* await context.
|
|
90
|
+
* await context.use({ locale: "nl-NL", timezone: "Europe/Amsterdam", offline: false });
|
|
79
91
|
*/
|
|
80
|
-
|
|
92
|
+
use(options: RuntimeUseOptions): Promise<void>;
|
|
81
93
|
/**
|
|
82
94
|
* Grants, denies or resets a browser permission ("geolocation", "notifications", "camera", …) for `origin`
|
|
83
|
-
* in this context.
|
|
95
|
+
* in this context. A geolocation override only answers once the permission is granted.
|
|
84
96
|
*/
|
|
85
97
|
setPermission(name: string, state: "granted" | "denied" | "prompt", origin: string): Promise<void>;
|
|
86
98
|
/** Removes the context; the browser closes its pages. The default context cannot be closed */
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { BiDiError } from "../transport/bidi-error.js";
|
|
2
|
-
import {
|
|
2
|
+
import { APIRequestContext, } from "../request/api-request-context.js";
|
|
3
|
+
import { ContextCookieStore } from "./context-cookie-store.js";
|
|
4
|
+
import { applyUse } from "./use-commands.js";
|
|
3
5
|
/**
|
|
4
6
|
* An isolated browsing session inside one browser: its own cookies, storage and cache. Pages opened in it
|
|
5
7
|
* share them; pages of other contexts see none of it.
|
|
@@ -13,6 +15,7 @@ export class BrowserContext {
|
|
|
13
15
|
this.isDefault = isDefault;
|
|
14
16
|
this.opened = [];
|
|
15
17
|
this.isClosed = false;
|
|
18
|
+
this.requestDefaults = {};
|
|
16
19
|
}
|
|
17
20
|
/** True once `close()` has run */
|
|
18
21
|
get closed() {
|
|
@@ -36,6 +39,19 @@ export class BrowserContext {
|
|
|
36
39
|
this.opened.push(page);
|
|
37
40
|
page.setContext(this);
|
|
38
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* Makes HTTP requests from the test that share this context's cookies: they send the cookies the pages hold,
|
|
44
|
+
* and the cookies the responses set are in the pages' `document.cookie` afterwards. Its `extraHTTPHeaders`
|
|
45
|
+
* and `ignoreHTTPSErrors` follow the run's `use` options.
|
|
46
|
+
*/
|
|
47
|
+
get request() {
|
|
48
|
+
this.assertOpen();
|
|
49
|
+
return (this.requestContext ??= new APIRequestContext(this.requestDefaults, new ContextCookieStore(this)));
|
|
50
|
+
}
|
|
51
|
+
/** @internal What `request` starts from: the run's `use.extraHTTPHeaders` and `acceptInsecureCerts` */
|
|
52
|
+
setRequestDefaults(defaults) {
|
|
53
|
+
Object.assign(this.requestDefaults, defaults);
|
|
54
|
+
}
|
|
39
55
|
/** Adds cookies to this context's storage. Every cookie is checked before any is stored */
|
|
40
56
|
async addCookies(cookies) {
|
|
41
57
|
this.assertOpen();
|
|
@@ -65,18 +81,19 @@ export class BrowserContext {
|
|
|
65
81
|
});
|
|
66
82
|
}
|
|
67
83
|
/**
|
|
68
|
-
*
|
|
69
|
-
* geolocation, offline, orientation, screen
|
|
84
|
+
* Changes the `use` options that can change while the browser runs for every page of this context, including
|
|
85
|
+
* pages opened later: locale, timezone, user agent, geolocation, offline, orientation, screen, hasTouch,
|
|
86
|
+
* javaScriptEnabled. A page's own `use()` wins over the context's.
|
|
70
87
|
* @example
|
|
71
|
-
* await context.
|
|
88
|
+
* await context.use({ locale: "nl-NL", timezone: "Europe/Amsterdam", offline: false });
|
|
72
89
|
*/
|
|
73
|
-
async
|
|
90
|
+
async use(options) {
|
|
74
91
|
this.assertOpen();
|
|
75
|
-
await
|
|
92
|
+
await applyUse(this.host.connector, { userContexts: [this.id] }, options);
|
|
76
93
|
}
|
|
77
94
|
/**
|
|
78
95
|
* Grants, denies or resets a browser permission ("geolocation", "notifications", "camera", …) for `origin`
|
|
79
|
-
* in this context.
|
|
96
|
+
* in this context. A geolocation override only answers once the permission is granted.
|
|
80
97
|
*/
|
|
81
98
|
async setPermission(name, state, origin) {
|
|
82
99
|
this.assertOpen();
|
|
@@ -104,6 +121,7 @@ export class BrowserContext {
|
|
|
104
121
|
throw err;
|
|
105
122
|
}
|
|
106
123
|
this.isClosed = true;
|
|
124
|
+
await this.requestContext?.dispose();
|
|
107
125
|
const pages = this.opened;
|
|
108
126
|
this.opened = [];
|
|
109
127
|
await Promise.all(pages.map((page) => page.detach()));
|
|
@@ -4,14 +4,16 @@ import Page from "./page.js";
|
|
|
4
4
|
import { type BrowserProcess } from "./chrome-launch.js";
|
|
5
5
|
import { BrowserContext } from "./browser-context.js";
|
|
6
6
|
import type { UserPromptHandler } from "../types/bidi-modules/session.js";
|
|
7
|
-
import {
|
|
7
|
+
import type { RuntimeUseOptions } from "../types/use.js";
|
|
8
|
+
import { UseApplier } from "./use-applier.js";
|
|
9
|
+
import type { GroupUse } from "../types/config.js";
|
|
8
10
|
import { ChromeDownloadFolders } from "./download-folders.js";
|
|
9
11
|
import { type BrowserLaunchOptions } from "./launch-flags.js";
|
|
10
12
|
import type { BiDiCommands } from "../types/bidi.js";
|
|
11
13
|
import type { CookieFilter, PartialCookie, PartitionDescriptor } from "../types/bidi-modules/storage.js";
|
|
12
14
|
export type { BrowserLaunchOptions } from "./launch-flags.js";
|
|
13
15
|
/** Options of `browser.newContext()` */
|
|
14
|
-
export interface ContextOptions extends
|
|
16
|
+
export interface ContextOptions extends RuntimeUseOptions {
|
|
15
17
|
acceptInsecureCerts?: boolean;
|
|
16
18
|
/** How this context's pages answer JavaScript dialogs; the session default is to leave them for the framework */
|
|
17
19
|
unhandledPromptBehavior?: UserPromptHandler;
|
|
@@ -27,6 +29,8 @@ export declare class Browser {
|
|
|
27
29
|
readonly downloadsDir: string | undefined;
|
|
28
30
|
private exitedPromise;
|
|
29
31
|
private downloadFolders;
|
|
32
|
+
/** The `use` options of the run, once `setUse` has been called: what every context and page gets */
|
|
33
|
+
private use;
|
|
30
34
|
constructor({ browserProc, biDiConnector, downloadsDir, downloadFolders, }: {
|
|
31
35
|
browserProc: BrowserProcess;
|
|
32
36
|
biDiConnector: BiDiConnector;
|
|
@@ -43,6 +47,11 @@ export declare class Browser {
|
|
|
43
47
|
}>;
|
|
44
48
|
/** Sets up a browser whose BiDi session is open: downloads folder, the first page and what it tracks */
|
|
45
49
|
private static attach;
|
|
50
|
+
/**
|
|
51
|
+
* @internal The runner calls this after launch: the `use` options now apply to the default context and its pages,
|
|
52
|
+
* and to every context and page created later. Options passed to `newContext` win over them.
|
|
53
|
+
*/
|
|
54
|
+
setUse(options: GroupUse, projectDir: string): Promise<UseApplier>;
|
|
46
55
|
/** Closes the browser via BiDi browser.close, then makes sure the process has exited */
|
|
47
56
|
close(): Promise<void>;
|
|
48
57
|
/** Alias of close() */
|
package/dist/browser/browser.js
CHANGED
|
@@ -5,7 +5,8 @@ import { projectBrowser } from "../browsers/resolve.js";
|
|
|
5
5
|
import Page from "./page.js";
|
|
6
6
|
import { launchChrome } from "./chrome-launch.js";
|
|
7
7
|
import { BrowserContext } from "./browser-context.js";
|
|
8
|
-
import {
|
|
8
|
+
import { validateRuntimeUse } from "./use-commands.js";
|
|
9
|
+
import { UseApplier } from "./use-applier.js";
|
|
9
10
|
import { prepareDownloadsDir, removeIfEmpty } from "./downloads-dir.js";
|
|
10
11
|
import { ChromeDownloadFolders } from "./download-folders.js";
|
|
11
12
|
import { launchFirefox } from "./launch-firefox.js";
|
|
@@ -46,7 +47,7 @@ export class Browser {
|
|
|
46
47
|
const resolved = await projectBrowser(browserName);
|
|
47
48
|
logger.debug("Found browser install location at: %s", resolved.executablePath);
|
|
48
49
|
ensureBrowserProfile(browserName, launchOptions.profileDir &&
|
|
49
|
-
path.join(launchOptions.profileDir, "user.js"));
|
|
50
|
+
path.join(launchOptions.profileDir, "user.js"), launchOptions.use);
|
|
50
51
|
const commandTimeout = await readCommandTimeout();
|
|
51
52
|
// Both launchers hand back an open BiDi session: Firefox after session.new, Chrome with the one
|
|
52
53
|
// its chromedriver created
|
|
@@ -58,6 +59,7 @@ export class Browser {
|
|
|
58
59
|
defaultLaunchOptions("chrome").profileDir,
|
|
59
60
|
headless: launchOptions.headless ?? defaultLaunchOptions("chrome").headless,
|
|
60
61
|
commandTimeout,
|
|
62
|
+
use: launchOptions.use,
|
|
61
63
|
signal,
|
|
62
64
|
})
|
|
63
65
|
: await launchFirefox({
|
|
@@ -122,6 +124,19 @@ export class Browser {
|
|
|
122
124
|
await page.startTracking();
|
|
123
125
|
return { browser, page };
|
|
124
126
|
}
|
|
127
|
+
/**
|
|
128
|
+
* @internal The runner calls this after launch: the `use` options now apply to the default context and its pages,
|
|
129
|
+
* and to every context and page created later. Options passed to `newContext` win over them.
|
|
130
|
+
*/
|
|
131
|
+
async setUse(options, projectDir) {
|
|
132
|
+
const name = this.biDiConnector.browserName ?? "firefox";
|
|
133
|
+
const applier = new UseApplier(this.biDiConnector, name, options, projectDir);
|
|
134
|
+
this.use = applier;
|
|
135
|
+
await applier.context(this.defaultContext);
|
|
136
|
+
for (const page of this.defaultContext.pages())
|
|
137
|
+
await applier.page(page);
|
|
138
|
+
return applier;
|
|
139
|
+
}
|
|
125
140
|
/** Closes the browser via BiDi browser.close, then makes sure the process has exited */
|
|
126
141
|
async close() {
|
|
127
142
|
await this.downloadFolders?.dispose();
|
|
@@ -163,6 +178,7 @@ export class Browser {
|
|
|
163
178
|
const page = new Page(this.biDiConnector, createBrowserContext.context);
|
|
164
179
|
this.contextFor(userContext).register(page);
|
|
165
180
|
await page.startTracking();
|
|
181
|
+
await this.use?.page(page);
|
|
166
182
|
return page;
|
|
167
183
|
}
|
|
168
184
|
/**
|
|
@@ -173,27 +189,34 @@ export class Browser {
|
|
|
173
189
|
* const page = await alice.newPage();
|
|
174
190
|
*/
|
|
175
191
|
async newContext(options = {}) {
|
|
176
|
-
const {
|
|
192
|
+
const { unhandledPromptBehavior, ...settings } = options;
|
|
193
|
+
const insecure = options.acceptInsecureCerts ?? this.use?.acceptInsecureCerts;
|
|
194
|
+
delete settings.acceptInsecureCerts;
|
|
177
195
|
// A wrong value must not leave a context behind, so it is refused before one is created
|
|
178
|
-
|
|
196
|
+
validateRuntimeUse(settings);
|
|
179
197
|
const { userContext } = await this.biDiConnector.send("browser.createUserContext", {
|
|
180
|
-
...(
|
|
198
|
+
...(insecure !== undefined && { acceptInsecureCerts: insecure }),
|
|
181
199
|
...(unhandledPromptBehavior && { unhandledPromptBehavior }),
|
|
182
200
|
});
|
|
183
201
|
const context = new BrowserContext(this.host, userContext, false);
|
|
184
202
|
this.userContexts.set(userContext, context);
|
|
185
|
-
if (Object.keys(
|
|
203
|
+
if (this.use || Object.keys(settings).length > 0) {
|
|
186
204
|
try {
|
|
187
|
-
await context
|
|
205
|
+
await this.use?.context(context);
|
|
206
|
+
if (Object.keys(settings).length > 0)
|
|
207
|
+
await context.use(settings);
|
|
188
208
|
}
|
|
189
209
|
catch (err) {
|
|
190
210
|
// Firefox keeps a context in its profile until it is removed
|
|
191
|
-
await context.close().catch((closeErr) => logger.debug("Could not remove a context whose
|
|
211
|
+
await context.close().catch((closeErr) => logger.debug("Could not remove a context whose settings failed", {
|
|
192
212
|
closeErr,
|
|
193
213
|
}));
|
|
194
214
|
throw err;
|
|
195
215
|
}
|
|
196
216
|
}
|
|
217
|
+
// The option of this context wins over the run's `use.acceptInsecureCerts`
|
|
218
|
+
if (insecure !== undefined)
|
|
219
|
+
context.setRequestDefaults({ ignoreHTTPSErrors: insecure });
|
|
197
220
|
return context;
|
|
198
221
|
}
|
|
199
222
|
/** The default context, then the contexts this browser created that are still open */
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { BiDiConnector } from "../transport/bidi-connection.js";
|
|
2
|
+
/**
|
|
3
|
+
* Sends a command the generated BiDi types do not list: `network.setExtraHeaders` (both browsers answer it) and
|
|
4
|
+
* the `goog:cdp.*` commands of Chrome
|
|
5
|
+
*/
|
|
6
|
+
export declare function sendRaw(connector: BiDiConnector, method: string, params: object): Promise<any>;
|
|
7
|
+
/**
|
|
8
|
+
* Runs a Chrome DevTools Protocol command on the page `context` through the BiDi connection (`goog:cdp`, Chrome
|
|
9
|
+
* only). Use it where BiDi has no command yet; CDP state belongs to one page, so it is set per page.
|
|
10
|
+
*/
|
|
11
|
+
export declare function cdpSend(connector: BiDiConnector, context: string, method: string, params: object): Promise<void>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/** The CDP session of each page, per connection: `goog:cdp.getSession` attaches on every call */
|
|
2
|
+
const sessions = new WeakMap();
|
|
3
|
+
/**
|
|
4
|
+
* Sends a command the generated BiDi types do not list: `network.setExtraHeaders` (both browsers answer it) and
|
|
5
|
+
* the `goog:cdp.*` commands of Chrome
|
|
6
|
+
*/
|
|
7
|
+
export function sendRaw(connector, method, params) {
|
|
8
|
+
return connector.send(method, params);
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Runs a Chrome DevTools Protocol command on the page `context` through the BiDi connection (`goog:cdp`, Chrome
|
|
12
|
+
* only). Use it where BiDi has no command yet; CDP state belongs to one page, so it is set per page.
|
|
13
|
+
*/
|
|
14
|
+
export async function cdpSend(connector, context, method, params) {
|
|
15
|
+
let known = sessions.get(connector);
|
|
16
|
+
if (!known)
|
|
17
|
+
sessions.set(connector, (known = new Map()));
|
|
18
|
+
let session = known.get(context);
|
|
19
|
+
if (session === undefined) {
|
|
20
|
+
const reply = await sendRaw(connector, "goog:cdp.getSession", { context });
|
|
21
|
+
known.set(context, (session = reply.session));
|
|
22
|
+
}
|
|
23
|
+
await sendRaw(connector, "goog:cdp.sendCommand", {
|
|
24
|
+
method,
|
|
25
|
+
params,
|
|
26
|
+
session,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import EventEmitter from "node:events";
|
|
3
3
|
import { BiDiConnector } from "../transport/bidi-connection.js";
|
|
4
|
+
import type { UseOptions } from "../types/use.js";
|
|
4
5
|
/** What `Browser` needs of the thing it launched: the browser process, or for Chrome the chromedriver in front of it */
|
|
5
6
|
export interface BrowserProcess {
|
|
6
7
|
readonly exitCode: number | null;
|
|
@@ -14,6 +15,8 @@ interface ChromeLaunch {
|
|
|
14
15
|
profileDir: string;
|
|
15
16
|
headless: boolean;
|
|
16
17
|
commandTimeout: number | undefined;
|
|
18
|
+
/** The `use` options that are fixed when the browser starts */
|
|
19
|
+
use?: UseOptions | undefined;
|
|
17
20
|
signal?: AbortSignal | undefined;
|
|
18
21
|
}
|
|
19
22
|
export declare const chromeFlags: ({ headless, profileDir, }: Pick<ChromeLaunch, "headless" | "profileDir">) => string[];
|
|
@@ -25,6 +28,7 @@ export declare const chromeCapabilities: (launch: ChromeLaunch) => {
|
|
|
25
28
|
binary: string;
|
|
26
29
|
args: string[];
|
|
27
30
|
};
|
|
31
|
+
acceptInsecureCerts?: boolean | undefined;
|
|
28
32
|
unhandledPromptBehavior: {
|
|
29
33
|
default: string;
|
|
30
34
|
};
|
|
@@ -5,7 +5,7 @@ import os from "node:os";
|
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import logger from "../logger/index.js";
|
|
7
7
|
import { BiDiConnector } from "../transport/bidi-connection.js";
|
|
8
|
-
import {
|
|
8
|
+
import { sessionCapabilities } from "./session-capabilities.js";
|
|
9
9
|
export const chromeFlags = ({ headless, profileDir, }) => [
|
|
10
10
|
"--no-sandbox",
|
|
11
11
|
"--no-first-run",
|
|
@@ -21,7 +21,7 @@ export const chromeCapabilities = (launch) => ({
|
|
|
21
21
|
alwaysMatch: {
|
|
22
22
|
browserName: "chrome",
|
|
23
23
|
webSocketUrl: true,
|
|
24
|
-
...
|
|
24
|
+
...sessionCapabilities(launch.use).alwaysMatch,
|
|
25
25
|
"goog:chromeOptions": {
|
|
26
26
|
binary: launch.executablePath,
|
|
27
27
|
args: chromeFlags(launch),
|
|
@@ -118,18 +118,26 @@ export async function launchChrome(launch) {
|
|
|
118
118
|
launch.signal?.removeEventListener("abort", onAbort);
|
|
119
119
|
}
|
|
120
120
|
}
|
|
121
|
+
/** How many of chromedriver's last lines the error carries when it exits before it is ready */
|
|
122
|
+
const OUTPUT_TAIL_LINES = 10;
|
|
121
123
|
function driverPort(driver) {
|
|
122
124
|
return new Promise((resolve, reject) => {
|
|
125
|
+
const tail = [];
|
|
123
126
|
const onData = (data) => {
|
|
124
127
|
logger.debug(`chromedriver: ${data}`);
|
|
125
128
|
const match = String(data).match(/started successfully on port (\d+)/);
|
|
126
129
|
if (match)
|
|
127
130
|
resolve(Number(match[1]));
|
|
131
|
+
for (const line of String(data).split("\n"))
|
|
132
|
+
if (line.trim() !== "")
|
|
133
|
+
tail.push(line);
|
|
134
|
+
tail.splice(0, Math.max(0, tail.length - OUTPUT_TAIL_LINES));
|
|
128
135
|
};
|
|
129
136
|
driver.stdout?.on("data", onData);
|
|
130
137
|
driver.stderr?.on("data", onData);
|
|
131
138
|
driver.once("error", reject);
|
|
132
|
-
driver.once("exit", (code) => reject(new Error(`chromedriver exited with code ${code} before it was ready`
|
|
139
|
+
driver.once("exit", (code, signal) => reject(new Error(`chromedriver exited with code ${code ?? signal} before it was ready` +
|
|
140
|
+
(tail.length > 0 ? `. Its last output:\n${tail.join("\n")}` : ""))));
|
|
133
141
|
});
|
|
134
142
|
}
|
|
135
143
|
/** chromedriver as the process of a Chrome launch: ending it ends Chrome, and so does deleting the session */
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type CookieStore } from "../request/cookie-jar.js";
|
|
2
|
+
import type { StorageStateCookie } from "../types/use.js";
|
|
3
|
+
import type { BrowserContext } from "./browser-context.js";
|
|
4
|
+
/**
|
|
5
|
+
* The cookies of a browser context as a request context's cookie store: requests send what the pages of the
|
|
6
|
+
* context hold, and the `Set-Cookie` headers of the responses go into the context, so both see one session.
|
|
7
|
+
*/
|
|
8
|
+
export declare class ContextCookieStore implements CookieStore {
|
|
9
|
+
private readonly context;
|
|
10
|
+
constructor(context: BrowserContext);
|
|
11
|
+
cookieHeader(url: URL): Promise<string | undefined>;
|
|
12
|
+
store(url: URL, setCookies: string[]): Promise<void>;
|
|
13
|
+
list(): Promise<StorageStateCookie[]>;
|
|
14
|
+
private add;
|
|
15
|
+
/** The site deleted the cookie: the browser keeps it under the host or, for a domain cookie, the dotted domain */
|
|
16
|
+
private remove;
|
|
17
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import logger from "../logger/index.js";
|
|
2
|
+
import { cookieHeaderOf, cookieMatches, isExpired, parseSetCookie, } from "../request/cookie-jar.js";
|
|
3
|
+
const CAPITAL = { strict: "Strict", lax: "Lax", none: "None" };
|
|
4
|
+
function stored(cookie) {
|
|
5
|
+
const sameSite = CAPITAL[cookie.sameSite];
|
|
6
|
+
return {
|
|
7
|
+
name: cookie.name,
|
|
8
|
+
value: cookie.value,
|
|
9
|
+
domain: cookie.domain.replace(/^\./, "").toLowerCase(),
|
|
10
|
+
hostOnly: !cookie.domain.startsWith("."),
|
|
11
|
+
path: cookie.path,
|
|
12
|
+
secure: cookie.secure,
|
|
13
|
+
httpOnly: cookie.httpOnly,
|
|
14
|
+
...(sameSite !== undefined && { sameSite }),
|
|
15
|
+
...(cookie.expiry !== undefined && { expires: cookie.expiry }),
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* The cookies of a browser context as a request context's cookie store: requests send what the pages of the
|
|
20
|
+
* context hold, and the `Set-Cookie` headers of the responses go into the context, so both see one session.
|
|
21
|
+
*/
|
|
22
|
+
export class ContextCookieStore {
|
|
23
|
+
constructor(context) {
|
|
24
|
+
this.context = context;
|
|
25
|
+
}
|
|
26
|
+
async cookieHeader(url) {
|
|
27
|
+
const now = Date.now();
|
|
28
|
+
const matching = (await this.context.cookies())
|
|
29
|
+
.map(stored)
|
|
30
|
+
.filter((cookie) => cookieMatches(cookie, url, now));
|
|
31
|
+
return cookieHeaderOf(matching);
|
|
32
|
+
}
|
|
33
|
+
async store(url, setCookies) {
|
|
34
|
+
for (const header of setCookies) {
|
|
35
|
+
const cookie = parseSetCookie(header, url);
|
|
36
|
+
if (!cookie)
|
|
37
|
+
continue;
|
|
38
|
+
try {
|
|
39
|
+
if (isExpired(cookie))
|
|
40
|
+
await this.remove(cookie);
|
|
41
|
+
else
|
|
42
|
+
await this.add(cookie);
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
// A browser refuses some cookies a server can send (Secure on a plain-http page, say): as a browser would
|
|
46
|
+
logger.debug("The browser did not take a Set-Cookie", {
|
|
47
|
+
name: cookie.name,
|
|
48
|
+
err,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
async list() {
|
|
54
|
+
return (await this.context.cookies()).map((cookie) => ({
|
|
55
|
+
name: cookie.name,
|
|
56
|
+
value: cookie.value,
|
|
57
|
+
domain: cookie.domain,
|
|
58
|
+
path: cookie.path,
|
|
59
|
+
expires: cookie.expiry ?? -1,
|
|
60
|
+
httpOnly: cookie.httpOnly,
|
|
61
|
+
secure: cookie.secure,
|
|
62
|
+
sameSite: CAPITAL[cookie.sameSite] ?? "Lax",
|
|
63
|
+
}));
|
|
64
|
+
}
|
|
65
|
+
add(cookie) {
|
|
66
|
+
return this.context.addCookies([
|
|
67
|
+
{
|
|
68
|
+
name: cookie.name,
|
|
69
|
+
value: cookie.value,
|
|
70
|
+
domain: cookie.hostOnly ? cookie.domain : `.${cookie.domain}`,
|
|
71
|
+
path: cookie.path,
|
|
72
|
+
secure: cookie.secure,
|
|
73
|
+
httpOnly: cookie.httpOnly,
|
|
74
|
+
...(cookie.sameSite !== undefined && {
|
|
75
|
+
sameSite: cookie.sameSite.toLowerCase(),
|
|
76
|
+
}),
|
|
77
|
+
...(cookie.expires !== undefined && { expiry: cookie.expires }),
|
|
78
|
+
},
|
|
79
|
+
]);
|
|
80
|
+
}
|
|
81
|
+
/** The site deleted the cookie: the browser keeps it under the host or, for a domain cookie, the dotted domain */
|
|
82
|
+
async remove(cookie) {
|
|
83
|
+
for (const domain of [cookie.domain, `.${cookie.domain}`])
|
|
84
|
+
await this.context.clearCookies({
|
|
85
|
+
name: cookie.name,
|
|
86
|
+
domain,
|
|
87
|
+
path: cookie.path,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
package/dist/browser/index.d.ts
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
export { Browser, type BrowserLaunchOptions, type ContextOptions, } from "./browser.js";
|
|
2
2
|
export { BrowserContext } from "./browser-context.js";
|
|
3
3
|
export { default as Page } from "./page.js";
|
|
4
|
+
export type { default as Keyboard } from "./keyboard.js";
|
|
5
|
+
export type { default as Mouse } from "./mouse.js";
|
|
4
6
|
export { DownloadTimeoutError } from "./download.js";
|
|
5
|
-
export {
|
|
7
|
+
export { UrlTimeoutError } from "./navigation-error.js";
|
|
8
|
+
export { ScriptError, ScriptSerializationError, type PageFunction, } from "../script/call-function.js";
|
|
9
|
+
export { ElementHandle } from "../script/element-handle.js";
|
|
10
|
+
export { RemoteObject } from "../script/serialize.js";
|
|
11
|
+
export type { BoundingBox } from "../locator/element-reads.js";
|
|
12
|
+
export { UseError, UseUnsupportedError } from "./use-commands.js";
|
|
6
13
|
export { findBrowser } from "./browser-finder.js";
|
package/dist/browser/index.js
CHANGED
|
@@ -2,5 +2,9 @@ export { Browser, } from "./browser.js";
|
|
|
2
2
|
export { BrowserContext } from "./browser-context.js";
|
|
3
3
|
export { default as Page } from "./page.js";
|
|
4
4
|
export { DownloadTimeoutError } from "./download.js";
|
|
5
|
-
export {
|
|
5
|
+
export { UrlTimeoutError } from "./navigation-error.js";
|
|
6
|
+
export { ScriptError, ScriptSerializationError, } from "../script/call-function.js";
|
|
7
|
+
export { ElementHandle } from "../script/element-handle.js";
|
|
8
|
+
export { RemoteObject } from "../script/serialize.js";
|
|
9
|
+
export { UseError, UseUnsupportedError } from "./use-commands.js";
|
|
6
10
|
export { findBrowser } from "./browser-finder.js";
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { BiDiConnector } from "../transport/bidi-connection.js";
|
|
2
|
+
/**
|
|
3
|
+
* The page's keyboard: real key input to whatever has focus. BiDi remembers which keys are down between calls,
|
|
4
|
+
* so `down("Shift")`, other input, then `up("Shift")` works, and a held `Shift` capitalizes `press("a")`.
|
|
5
|
+
*/
|
|
6
|
+
export default class Keyboard {
|
|
7
|
+
private connector;
|
|
8
|
+
private contextId;
|
|
9
|
+
private assertOpen;
|
|
10
|
+
constructor(connector: BiDiConnector, contextId: string, assertOpen: () => void);
|
|
11
|
+
/**
|
|
12
|
+
* Presses and releases a key, or a chord: modifiers (`Control`, `Shift`, `Alt`, `Meta`, `ControlOrMeta`) joined to
|
|
13
|
+
* the key by `+`. Named keys are `Enter`, `Tab`, `Escape`, `Backspace`, `Delete`, `Space`, `Insert`, the arrows, `Home`, `End`,
|
|
14
|
+
* `PageUp`, `PageDown` and `F1` to `F12`; any other key is a single character. `delay` waits (ms) between press and release.
|
|
15
|
+
* @example
|
|
16
|
+
* await page.keyboard.press("Control+a");
|
|
17
|
+
* await page.keyboard.press("Shift+Tab");
|
|
18
|
+
*/
|
|
19
|
+
press(key: string, options?: {
|
|
20
|
+
delay?: number;
|
|
21
|
+
}): Promise<void>;
|
|
22
|
+
/** Presses a key and keeps it down until `up`; a modifier stays in effect for the keys and clicks in between */
|
|
23
|
+
down(key: string): Promise<void>;
|
|
24
|
+
/** Releases a key pressed with `down`; a key that is not down is ignored */
|
|
25
|
+
up(key: string): Promise<void>;
|
|
26
|
+
/** Presses and releases one key per character of `text`, `delay` ms apart; a line break is Enter */
|
|
27
|
+
type(text: string, options?: {
|
|
28
|
+
delay?: number;
|
|
29
|
+
}): Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* Inserts `text` at the caret of the focused element in one step, as a paste does: `input` fires, `keydown` and `keyup`
|
|
32
|
+
* do not. Does nothing when no editable element has focus.
|
|
33
|
+
*/
|
|
34
|
+
insertText(text: string): Promise<void>;
|
|
35
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { callFunction } from "../script/call-function.js";
|
|
2
|
+
import { chordSteps, performKeys, textSteps } from "../locator/key-actions.js";
|
|
3
|
+
import { parseKeyChord } from "../locator/key-map.js";
|
|
4
|
+
/** Types into whatever has focus, replacing the selection: an input, a textarea or an editable element, also in a same-origin frame */
|
|
5
|
+
const INSERT_TEXT = `(text) => {
|
|
6
|
+
let doc = document;
|
|
7
|
+
let el = doc.activeElement;
|
|
8
|
+
while (el && el.tagName === "IFRAME" && el.contentDocument && el.contentDocument.activeElement) {
|
|
9
|
+
doc = el.contentDocument;
|
|
10
|
+
el = doc.activeElement;
|
|
11
|
+
}
|
|
12
|
+
const selection = doc.getSelection();
|
|
13
|
+
// A freshly focused editable element can have no caret yet (Firefox): put it at the end
|
|
14
|
+
if (el && el.isContentEditable && selection && !el.contains(selection.anchorNode)) {
|
|
15
|
+
const range = doc.createRange();
|
|
16
|
+
range.selectNodeContents(el);
|
|
17
|
+
range.collapse(false);
|
|
18
|
+
selection.removeAllRanges();
|
|
19
|
+
selection.addRange(range);
|
|
20
|
+
}
|
|
21
|
+
doc.execCommand("insertText", false, text);
|
|
22
|
+
}`;
|
|
23
|
+
/** The key `down` and `up` take: one key, no chord, since a chord is several keys */
|
|
24
|
+
function singleKey(key, method) {
|
|
25
|
+
const chord = parseKeyChord(key, method);
|
|
26
|
+
if (chord.modifiers.length > 0) {
|
|
27
|
+
throw new Error(`${method}(): "${key}" is a chord. Press its keys one at a time`);
|
|
28
|
+
}
|
|
29
|
+
return chord.key;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* The page's keyboard: real key input to whatever has focus. BiDi remembers which keys are down between calls,
|
|
33
|
+
* so `down("Shift")`, other input, then `up("Shift")` works, and a held `Shift` capitalizes `press("a")`.
|
|
34
|
+
*/
|
|
35
|
+
export default class Keyboard {
|
|
36
|
+
constructor(connector, contextId, assertOpen) {
|
|
37
|
+
this.connector = connector;
|
|
38
|
+
this.contextId = contextId;
|
|
39
|
+
this.assertOpen = assertOpen;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Presses and releases a key, or a chord: modifiers (`Control`, `Shift`, `Alt`, `Meta`, `ControlOrMeta`) joined to
|
|
43
|
+
* the key by `+`. Named keys are `Enter`, `Tab`, `Escape`, `Backspace`, `Delete`, `Space`, `Insert`, the arrows, `Home`, `End`,
|
|
44
|
+
* `PageUp`, `PageDown` and `F1` to `F12`; any other key is a single character. `delay` waits (ms) between press and release.
|
|
45
|
+
* @example
|
|
46
|
+
* await page.keyboard.press("Control+a");
|
|
47
|
+
* await page.keyboard.press("Shift+Tab");
|
|
48
|
+
*/
|
|
49
|
+
async press(key, options) {
|
|
50
|
+
this.assertOpen();
|
|
51
|
+
const chord = parseKeyChord(key, "keyboard.press");
|
|
52
|
+
await performKeys(this.connector, this.contextId, chordSteps(chord, options?.delay));
|
|
53
|
+
}
|
|
54
|
+
/** Presses a key and keeps it down until `up`; a modifier stays in effect for the keys and clicks in between */
|
|
55
|
+
async down(key) {
|
|
56
|
+
this.assertOpen();
|
|
57
|
+
await performKeys(this.connector, this.contextId, [
|
|
58
|
+
{ type: "keyDown", value: singleKey(key, "keyboard.down") },
|
|
59
|
+
]);
|
|
60
|
+
}
|
|
61
|
+
/** Releases a key pressed with `down`; a key that is not down is ignored */
|
|
62
|
+
async up(key) {
|
|
63
|
+
this.assertOpen();
|
|
64
|
+
await performKeys(this.connector, this.contextId, [
|
|
65
|
+
{ type: "keyUp", value: singleKey(key, "keyboard.up") },
|
|
66
|
+
]);
|
|
67
|
+
}
|
|
68
|
+
/** Presses and releases one key per character of `text`, `delay` ms apart; a line break is Enter */
|
|
69
|
+
async type(text, options) {
|
|
70
|
+
this.assertOpen();
|
|
71
|
+
await performKeys(this.connector, this.contextId, textSteps(text, options?.delay));
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Inserts `text` at the caret of the focused element in one step, as a paste does: `input` fires, `keydown` and `keyup`
|
|
75
|
+
* do not. Does nothing when no editable element has focus.
|
|
76
|
+
*/
|
|
77
|
+
async insertText(text) {
|
|
78
|
+
this.assertOpen();
|
|
79
|
+
await callFunction(this.connector, this.contextId, INSERT_TEXT, [text], {
|
|
80
|
+
awaitPromise: false,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|