@itmetsam/samurai-framework 0.10.0 → 0.12.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.
- package/README.md +3 -4
- package/dist/assert/assertion-error.d.ts +1 -1
- package/dist/assert/expect.d.ts +5 -50
- package/dist/assert/expect.js +5 -182
- package/dist/assert/locator-assertions.d.ts +22 -0
- package/dist/assert/locator-assertions.js +61 -0
- package/dist/assert/pending-assertions.d.ts +7 -0
- package/dist/assert/pending-assertions.js +40 -0
- package/dist/assert/text-match.d.ts +4 -0
- package/dist/assert/text-match.js +11 -0
- package/dist/assert/value-assertions.d.ts +23 -0
- package/dist/assert/value-assertions.js +82 -0
- package/dist/browser/browser-context.d.ts +0 -1
- package/dist/browser/browser-context.js +13 -5
- package/dist/browser/browser-finder.js +5 -9
- package/dist/browser/browser.d.ts +3 -31
- package/dist/browser/browser.js +36 -219
- package/dist/browser/chrome-launch.d.ts +2 -1
- package/dist/browser/dialog-handling.d.ts +32 -0
- package/dist/browser/dialog-handling.js +85 -0
- package/dist/browser/document-watcher.d.ts +44 -0
- package/dist/browser/document-watcher.js +160 -0
- package/dist/browser/download-folders.d.ts +0 -1
- package/dist/browser/download-folders.js +7 -15
- package/dist/browser/download.js +5 -10
- package/dist/browser/downloads-dir.js +2 -2
- package/dist/browser/emit.d.ts +7 -0
- package/dist/browser/emit.js +16 -0
- package/dist/browser/emulation.d.ts +1 -1
- package/dist/browser/emulation.js +54 -18
- package/dist/browser/file-chooser.js +4 -11
- package/dist/browser/history.d.ts +6 -0
- package/dist/browser/history.js +89 -0
- package/dist/browser/index.d.ts +2 -0
- package/dist/browser/index.js +2 -0
- package/dist/browser/init-script.d.ts +2 -0
- package/dist/browser/init-script.js +16 -0
- package/dist/browser/launch-firefox.d.ts +20 -0
- package/dist/browser/launch-firefox.js +66 -0
- package/dist/browser/launch-flags.d.ts +18 -0
- package/dist/browser/launch-flags.js +100 -0
- package/dist/browser/lazy.d.ts +21 -0
- package/dist/browser/lazy.js +44 -0
- package/dist/browser/page-events.d.ts +44 -0
- package/dist/browser/page-events.js +58 -0
- package/dist/browser/page-logs.js +2 -9
- package/dist/browser/page-navigation.d.ts +58 -0
- package/dist/browser/page-navigation.js +108 -0
- package/dist/browser/page-network.d.ts +69 -0
- package/dist/browser/page-network.js +163 -0
- package/dist/browser/page-settings.d.ts +35 -0
- package/dist/browser/page-settings.js +51 -0
- package/dist/browser/page-transfers.d.ts +36 -0
- package/dist/browser/page-transfers.js +50 -0
- package/dist/browser/page.d.ts +39 -154
- package/dist/browser/page.js +134 -758
- package/dist/browser/pdf.d.ts +1 -1
- package/dist/browser/session.d.ts +16 -0
- package/dist/browser/session.js +32 -0
- package/dist/browser/viewport.d.ts +19 -0
- package/dist/browser/viewport.js +72 -0
- package/dist/browser/warm-up.d.ts +1 -1
- package/dist/browser/warm-up.js +2 -1
- package/dist/browsers/index.d.ts +1 -1
- package/dist/browsers/index.js +1 -1
- package/dist/browsers/pins.d.ts +0 -2
- package/dist/browsers/pins.js +0 -2
- package/dist/browsers/store.d.ts +0 -2
- package/dist/browsers/store.js +1 -2
- package/dist/cli/args.d.ts +13 -6
- package/dist/cli/args.js +13 -10
- package/dist/cli/browsers.d.ts +1 -2
- package/dist/cli/browsers.js +2 -2
- package/dist/cli/console-reporter.js +3 -2
- package/dist/cli/flow-reporter.js +2 -1
- package/dist/cli/flow.d.ts +1 -2
- package/dist/cli/flow.js +25 -22
- package/dist/cli/init.js +2 -7
- package/dist/cli/io.d.ts +10 -0
- package/dist/cli/io.js +1 -0
- package/dist/cli/main.d.ts +3 -10
- package/dist/cli/main.js +10 -143
- package/dist/cli/paint.d.ts +2 -0
- package/dist/cli/paint.js +4 -0
- package/dist/cli/record.d.ts +4 -0
- package/dist/cli/record.js +93 -0
- package/dist/cli/usage.d.ts +1 -0
- package/dist/cli/usage.js +58 -0
- package/dist/cli/version.d.ts +2 -0
- package/dist/cli/version.js +7 -0
- package/dist/config/browsers.d.ts +11 -0
- package/dist/config/browsers.js +32 -0
- package/dist/config/config.js +4 -1
- package/dist/config/mask.d.ts +1 -1
- package/dist/config/mask.js +7 -2
- package/dist/config/run-settings.d.ts +1 -2
- package/dist/config/run-settings.js +1 -6
- package/dist/config/timeouts.d.ts +2 -0
- package/dist/config/timeouts.js +13 -0
- package/dist/config/workers.d.ts +14 -0
- package/dist/config/workers.js +29 -0
- package/dist/flows/run/branch-context.d.ts +14 -0
- package/dist/flows/run/branch-context.js +35 -0
- package/dist/flows/run/handlers/condition.d.ts +1 -1
- package/dist/flows/run/handlers/end.d.ts +1 -1
- package/dist/flows/run/handlers/group.d.ts +1 -1
- package/dist/flows/run/handlers/index.d.ts +2 -2
- package/dist/flows/run/handlers/parallel.d.ts +1 -1
- package/dist/flows/run/handlers/start.d.ts +1 -1
- package/dist/flows/run/handlers/support.d.ts +3 -27
- package/dist/flows/run/handlers/test.d.ts +1 -1
- package/dist/flows/run/handlers/types.d.ts +27 -0
- package/dist/flows/run/handlers/types.js +1 -0
- package/dist/flows/run/index.d.ts +1 -1
- package/dist/flows/run/prepare-flow.d.ts +14 -0
- package/dist/flows/run/prepare-flow.js +44 -0
- package/dist/flows/run/project.d.ts +33 -2
- package/dist/flows/run/project.js +139 -57
- package/dist/flows/run/run.d.ts +3 -4
- package/dist/flows/run/run.js +23 -193
- package/dist/flows/run/walker.d.ts +56 -0
- package/dist/flows/run/walker.js +225 -0
- package/dist/flows/steps/catalog.d.ts +1 -1
- package/dist/flows/steps/handler.d.ts +1 -1
- package/dist/flows/steps/set.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/locator/actionability.d.ts +33 -0
- package/dist/locator/actionability.js +104 -0
- package/dist/locator/chain-resolver.d.ts +20 -0
- package/dist/locator/chain-resolver.js +102 -0
- package/dist/locator/document-swap.d.ts +1 -0
- package/dist/locator/document-swap.js +11 -0
- package/dist/locator/element-state.d.ts +3 -1
- package/dist/locator/element-state.js +11 -3
- package/dist/locator/file-actions.d.ts +35 -0
- package/dist/locator/file-actions.js +55 -0
- package/dist/{script/helpers.js → locator/helper-realm.js} +2 -2
- package/dist/locator/input-actions.d.ts +18 -0
- package/dist/locator/input-actions.js +80 -0
- package/dist/locator/label-locator.d.ts +1 -1
- package/dist/locator/label-locator.js +5 -12
- package/dist/locator/locator.d.ts +14 -37
- package/dist/locator/locator.js +68 -424
- package/dist/locator/page-scripts.d.ts +16 -0
- package/dist/locator/page-scripts.js +54 -0
- package/dist/locator/page-search.d.ts +10 -0
- package/dist/locator/page-search.js +19 -0
- package/dist/locator/selector-errors.d.ts +0 -4
- package/dist/locator/selector-errors.js +0 -7
- package/dist/locator/selector.d.ts +14 -13
- package/dist/locator/selector.js +10 -16
- package/dist/locator/text-locator.d.ts +1 -1
- package/dist/locator/text-locator.js +5 -9
- package/dist/logger/index.d.ts +6 -0
- package/dist/logger/index.js +14 -7
- package/dist/network/bidi-wire.d.ts +10 -0
- package/dist/network/bidi-wire.js +38 -0
- package/dist/network/intercept.d.ts +30 -0
- package/dist/network/intercept.js +130 -0
- package/dist/network/network-tracker.d.ts +10 -47
- package/dist/network/network-tracker.js +34 -68
- package/dist/network/network-types.d.ts +33 -0
- package/dist/network/network-types.js +1 -0
- package/dist/network/network-wait.d.ts +7 -2
- package/dist/network/network-wait.js +14 -4
- package/dist/network/response.d.ts +1 -1
- package/dist/network/response.js +1 -1
- package/dist/network/route.d.ts +92 -0
- package/dist/network/route.js +142 -0
- package/dist/network/router.d.ts +6 -91
- package/dist/network/router.js +25 -258
- package/dist/recorder/expectation.d.ts +5 -0
- package/dist/recorder/expectation.js +20 -0
- package/dist/recorder/files-recorder.d.ts +30 -0
- package/dist/recorder/files-recorder.js +88 -0
- package/dist/recorder/normalise.d.ts +17 -6
- package/dist/recorder/normalise.js +96 -91
- package/dist/recorder/options.d.ts +56 -0
- package/dist/recorder/options.js +1 -0
- package/dist/recorder/page-messages.d.ts +35 -0
- package/dist/recorder/page-messages.js +26 -0
- package/dist/recorder/ranking.js +1 -0
- package/dist/recorder/realms.d.ts +6 -0
- package/dist/recorder/realms.js +13 -0
- package/dist/recorder/recorder.d.ts +10 -74
- package/dist/recorder/recorder.js +39 -199
- package/dist/recorder/session-browser.d.ts +22 -0
- package/dist/recorder/session-browser.js +21 -0
- package/dist/recorder/session-secrets.d.ts +8 -0
- package/dist/recorder/session-secrets.js +45 -0
- package/dist/recorder/session.d.ts +5 -14
- package/dist/recorder/session.js +22 -120
- package/dist/recorder/spec-source.d.ts +39 -0
- package/dist/recorder/spec-source.js +84 -0
- package/dist/recorder/toolbar/action-guard.d.ts +21 -0
- package/dist/recorder/toolbar/action-guard.js +43 -0
- package/dist/recorder/toolbar/actions/assert.d.ts +4 -0
- package/dist/recorder/toolbar/actions/assert.js +52 -0
- package/dist/recorder/toolbar/actions/insert.d.ts +6 -0
- package/dist/recorder/toolbar/actions/insert.js +93 -0
- package/dist/recorder/toolbar/actions/pick.d.ts +8 -0
- package/dist/recorder/toolbar/actions/pick.js +26 -0
- package/dist/recorder/toolbar/actions/reply.d.ts +7 -0
- package/dist/recorder/toolbar/actions/reply.js +10 -0
- package/dist/recorder/toolbar/controller-fixture.d.ts +47 -0
- package/dist/recorder/toolbar/controller-fixture.js +129 -0
- package/dist/recorder/toolbar/controller.d.ts +12 -78
- package/dist/recorder/toolbar/controller.js +35 -247
- package/dist/recorder/toolbar/inspect.d.ts +1 -3
- package/dist/recorder/toolbar/inspect.js +2 -2
- package/dist/recorder/toolbar/page-scope.d.ts +19 -0
- package/dist/recorder/toolbar/page-scope.js +43 -0
- package/dist/recorder/toolbar/protocol.d.ts +2 -1
- package/dist/recorder/toolbar/source/attach.d.ts +2 -0
- package/dist/recorder/toolbar/source/attach.js +41 -0
- package/dist/recorder/toolbar/source/dom.d.ts +2 -0
- package/dist/recorder/toolbar/source/dom.js +47 -0
- package/dist/recorder/toolbar/source/drag.d.ts +2 -0
- package/dist/recorder/toolbar/source/drag.js +32 -0
- package/dist/recorder/toolbar/source/events-page.d.ts +2 -0
- package/dist/recorder/toolbar/source/events-page.js +67 -0
- package/dist/recorder/toolbar/source/events-toolbar.d.ts +2 -0
- package/dist/recorder/toolbar/source/events-toolbar.js +35 -0
- package/dist/recorder/toolbar/source/fields.d.ts +2 -0
- package/dist/recorder/toolbar/source/fields.js +44 -0
- package/dist/recorder/toolbar/source/handlers.d.ts +2 -0
- package/dist/recorder/toolbar/source/handlers.js +134 -0
- package/dist/recorder/toolbar/source/names.d.ts +2 -0
- package/dist/recorder/toolbar/source/names.js +12 -0
- package/dist/recorder/toolbar/source/picking.d.ts +2 -0
- package/dist/recorder/toolbar/source/picking.js +72 -0
- package/dist/recorder/toolbar/source/placement.d.ts +2 -0
- package/dist/recorder/toolbar/source/placement.js +45 -0
- package/dist/recorder/toolbar/source/recorder-side.d.ts +2 -0
- package/dist/recorder/toolbar/source/recorder-side.js +54 -0
- package/dist/recorder/toolbar/source/setup.d.ts +2 -0
- package/dist/recorder/toolbar/source/setup.js +50 -0
- package/dist/recorder/toolbar/source/views-panels.d.ts +2 -0
- package/dist/recorder/toolbar/source/views-panels.js +150 -0
- package/dist/recorder/toolbar/source/views-rail.d.ts +2 -0
- package/dist/recorder/toolbar/source/views-rail.js +40 -0
- package/dist/recorder/toolbar/source/views-render.d.ts +2 -0
- package/dist/recorder/toolbar/source/views-render.js +48 -0
- package/dist/recorder/toolbar/source.js +30 -856
- package/dist/recorder/toolbar/types.d.ts +70 -0
- package/dist/recorder/toolbar/types.js +1 -0
- package/dist/recorder/toolbar-bridge.d.ts +43 -0
- package/dist/recorder/toolbar-bridge.js +106 -0
- package/dist/runner/browsers.d.ts +0 -12
- package/dist/runner/browsers.js +1 -33
- package/dist/runner/options.d.ts +31 -0
- package/dist/runner/options.js +1 -0
- package/dist/runner/pooled-run.d.ts +43 -0
- package/dist/runner/pooled-run.js +75 -0
- package/dist/runner/ports.d.ts +2 -0
- package/dist/runner/ports.js +2 -0
- package/dist/runner/registry.d.ts +1 -1
- package/dist/runner/registry.js +1 -1
- package/dist/runner/reporter.d.ts +31 -10
- package/dist/runner/reporter.js +39 -6
- package/dist/runner/run-test.d.ts +16 -0
- package/dist/runner/run-test.js +178 -0
- package/dist/runner/run.d.ts +5 -3
- package/dist/runner/run.js +28 -29
- package/dist/runner/select.d.ts +24 -0
- package/dist/runner/select.js +77 -0
- package/dist/runner/test-dir.d.ts +13 -0
- package/dist/runner/test-dir.js +27 -0
- package/dist/runner/test-runner.d.ts +25 -56
- package/dist/runner/test-runner.js +81 -270
- package/dist/runner/worker-pool.d.ts +67 -0
- package/dist/runner/worker-pool.js +275 -0
- package/dist/runner/worker-process.d.ts +6 -0
- package/dist/runner/worker-process.js +54 -0
- package/dist/runner/worker-protocol.d.ts +67 -0
- package/dist/runner/worker-protocol.js +1 -0
- package/dist/runner/worker-session.d.ts +7 -0
- package/dist/runner/worker-session.js +60 -0
- package/dist/runner/worker.d.ts +26 -0
- package/dist/runner/worker.js +121 -0
- package/dist/runner/workers-option.d.ts +15 -0
- package/dist/runner/workers-option.js +22 -0
- package/dist/script/call-function.d.ts +1 -9
- package/dist/script/call-function.js +2 -19
- package/dist/script/element-handle.d.ts +1 -2
- package/dist/script/element-handle.js +1 -2
- package/dist/script/serialize.d.ts +3 -12
- package/dist/script/serialize.js +53 -62
- package/dist/steps/parse/call.d.ts +3 -0
- package/dist/steps/parse/call.js +81 -0
- package/dist/steps/parse/expectation.d.ts +5 -0
- package/dist/steps/parse/expectation.js +71 -0
- package/dist/steps/parse/file-entry.d.ts +4 -0
- package/dist/steps/parse/file-entry.js +74 -0
- package/dist/steps/parse/literals.d.ts +4 -0
- package/dist/steps/parse/literals.js +28 -0
- package/dist/steps/parse/step.d.ts +4 -0
- package/dist/steps/parse/step.js +81 -0
- package/dist/steps/parse.js +2 -323
- package/dist/transport/bidi-connection.d.ts +4 -15
- package/dist/transport/bidi-connection.js +9 -63
- package/dist/transport/log-redaction.d.ts +13 -0
- package/dist/transport/log-redaction.js +51 -0
- package/dist/transport/subscriptions.js +18 -13
- package/dist/transport/unsupported.d.ts +7 -0
- package/dist/transport/unsupported.js +13 -0
- package/dist/types/bidi-modules/browser.d.ts +1 -12
- package/dist/types/bidi-modules/browsing-context.d.ts +8 -18
- package/dist/types/bidi-modules/emulation.d.ts +29 -21
- package/dist/types/bidi-modules/input.d.ts +1 -9
- package/dist/types/bidi-modules/network.d.ts +29 -24
- package/dist/types/bidi-modules/script.d.ts +0 -9
- package/dist/types/bidi-modules/session.d.ts +0 -8
- package/dist/types/bidi-modules/storage.d.ts +0 -5
- package/dist/types/bidi.d.ts +7 -37
- package/dist/types/config.d.ts +12 -0
- package/dist/types/test.d.ts +8 -3
- package/dist/wait/wait-until.d.ts +1 -5
- package/dist/wait/wait-until.js +7 -16
- package/package.json +3 -2
- package/dist/config/default.d.ts +0 -2
- package/dist/config/default.js +0 -3
- package/dist/types/bidi-modules/web-extensions.d.ts +0 -9
- package/dist/types/bidi-protocols/command.d.ts +0 -7
- package/dist/types/bidi-protocols/event.d.ts +0 -17
- /package/dist/{script/helpers.d.ts → locator/helper-realm.d.ts} +0 -0
|
@@ -1,28 +1,25 @@
|
|
|
1
|
-
import assert from "node:assert";
|
|
2
1
|
import { execSync } from "node:child_process";
|
|
3
2
|
import { existsSync } from "node:fs";
|
|
4
3
|
import logger from "../logger/index.js";
|
|
5
4
|
const supportedBrowsers = ["firefox"];
|
|
5
|
+
const isFindable = (name) => supportedBrowsers.includes(name);
|
|
6
6
|
const macBrowserPaths = {
|
|
7
|
-
chrome: "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
|
8
7
|
firefox: "/Applications/Firefox.app/Contents/MacOS/Firefox",
|
|
9
8
|
};
|
|
10
9
|
/** @deprecated Launches use the builds SAMURAI downloads (`samurai browsers install`); removed in a later major */
|
|
11
10
|
export function findBrowser(browserName) {
|
|
12
11
|
logger.verbose("Checking install location for %s", browserName);
|
|
13
|
-
if (!
|
|
12
|
+
if (!isFindable(browserName)) {
|
|
14
13
|
throw new Error("This browser is not supported. Supported browsers include: " +
|
|
15
14
|
supportedBrowsers.join(", "));
|
|
16
15
|
}
|
|
17
|
-
let browserLocation
|
|
18
|
-
const notFoundMsg = `Cannot find browser ${browserName} on this machine! Is it installed?`;
|
|
16
|
+
let browserLocation;
|
|
19
17
|
try {
|
|
20
18
|
browserLocation = tryFindBrowser(browserName);
|
|
21
19
|
}
|
|
22
20
|
catch (err) {
|
|
23
|
-
throw new Error(
|
|
21
|
+
throw Object.assign(new Error(`Cannot find browser ${browserName} on this machine! Is it installed?`), { cause: err });
|
|
24
22
|
}
|
|
25
|
-
assert(browserLocation, notFoundMsg);
|
|
26
23
|
logger.verbose("Browser found at: %s", browserLocation);
|
|
27
24
|
return browserLocation;
|
|
28
25
|
}
|
|
@@ -42,7 +39,7 @@ function tryFindBrowser(browserName) {
|
|
|
42
39
|
function findBrowserWindows(browserName) {
|
|
43
40
|
// Function to prevent search the entire file system
|
|
44
41
|
// We'll only try the most common paths
|
|
45
|
-
const possibleDirs = ["Program
|
|
42
|
+
const possibleDirs = ["Program Files", "Program Files (x86)"];
|
|
46
43
|
const where = (dirPath) => {
|
|
47
44
|
const command = `where /r ${dirPath} ${browserName}.exe`;
|
|
48
45
|
try {
|
|
@@ -82,7 +79,6 @@ function findBrowserMac(browserName) {
|
|
|
82
79
|
return checkPathExists(macBrowserPaths[browserName]);
|
|
83
80
|
}
|
|
84
81
|
const linuxBinaryNames = {
|
|
85
|
-
chrome: "google-chrome",
|
|
86
82
|
firefox: "firefox",
|
|
87
83
|
};
|
|
88
84
|
function findBrowserLinux(browserName) {
|
|
@@ -1,41 +1,15 @@
|
|
|
1
1
|
import type { SupportedBrowser } from "../types/browser.js";
|
|
2
|
-
import { BiDiConnector } from "../transport/bidi-connection.js";
|
|
2
|
+
import type { BiDiConnector } from "../transport/bidi-connection.js";
|
|
3
3
|
import Page from "./page.js";
|
|
4
|
-
import { SESSION_CAPABILITIES } from "./session-capabilities.js";
|
|
5
4
|
import { type BrowserProcess } from "./chrome-launch.js";
|
|
6
5
|
import { BrowserContext } from "./browser-context.js";
|
|
7
6
|
import type { UserPromptHandler } from "../types/bidi-modules/session.js";
|
|
8
7
|
import { type EmulationOptions } from "./emulation.js";
|
|
9
8
|
import { ChromeDownloadFolders } from "./download-folders.js";
|
|
9
|
+
import { type BrowserLaunchOptions } from "./launch-flags.js";
|
|
10
10
|
import type { BiDiCommands } from "../types/bidi.js";
|
|
11
11
|
import type { CookieFilter, PartialCookie, PartitionDescriptor } from "../types/bidi-modules/storage.js";
|
|
12
|
-
export {
|
|
13
|
-
/** session.new got no reply in time */
|
|
14
|
-
export declare class BrowserStartTimeoutError extends Error {
|
|
15
|
-
constructor(label: string, timeout: number);
|
|
16
|
-
}
|
|
17
|
-
/** Sends session.new with its own, longer timeout; `label` names the browser in the timeout error */
|
|
18
|
-
export declare function startSession(connector: Pick<BiDiConnector, "send">, label: string, commandTimeout: number | undefined): Promise<void>;
|
|
19
|
-
export declare const browserLaunchFlag: (browserName: SupportedBrowser, launchOptions: Partial<BrowserLaunchOptions>) => string[];
|
|
20
|
-
/**
|
|
21
|
-
* Stops what a failed launch started. A browser left running holds the profile (the next launch
|
|
22
|
-
* fails with "already open") and keeps the test process alive.
|
|
23
|
-
*/
|
|
24
|
-
export declare function abandonLaunch(browserProc: {
|
|
25
|
-
kill(): boolean;
|
|
26
|
-
}, biDiConnector: {
|
|
27
|
-
kill(): void;
|
|
28
|
-
} | undefined): void;
|
|
29
|
-
/** Env added to a browser's process */
|
|
30
|
-
export declare const browserLaunchEnv: (browserName: SupportedBrowser, appDataPath?: string) => Record<string, string>;
|
|
31
|
-
export interface BrowserLaunchOptions {
|
|
32
|
-
/** Which port to use for the WebDeveloper tools */
|
|
33
|
-
port: number;
|
|
34
|
-
/** The browser profile folder: user.js goes in it for Firefox, Chrome's user data for Chrome */
|
|
35
|
-
profileDir: string;
|
|
36
|
-
/** Start the browser in headless mode or in UI mode */
|
|
37
|
-
headless: boolean;
|
|
38
|
-
}
|
|
12
|
+
export type { BrowserLaunchOptions } from "./launch-flags.js";
|
|
39
13
|
/** Options of `browser.newContext()` */
|
|
40
14
|
export interface ContextOptions extends EmulationOptions {
|
|
41
15
|
acceptInsecureCerts?: boolean;
|
|
@@ -95,5 +69,3 @@ export declare class Browser {
|
|
|
95
69
|
partitionKey: import("../types/bidi-modules/storage.js").PartitionKey;
|
|
96
70
|
}>;
|
|
97
71
|
}
|
|
98
|
-
/** Writes the browser's user.js unless it already holds the current prefs; an outdated one is replaced */
|
|
99
|
-
export declare function ensureBrowserProfile(browserName: SupportedBrowser, profilePath?: string | undefined): void;
|
package/dist/browser/browser.js
CHANGED
|
@@ -1,119 +1,19 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
2
1
|
import { setTimeout as sleep } from "node:timers/promises";
|
|
3
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
4
|
-
import { projectBrowser } from "../browsers/resolve.js";
|
|
5
2
|
import path from "node:path";
|
|
6
|
-
import {
|
|
7
|
-
import { BiDiError } from "../transport/bidi-error.js";
|
|
3
|
+
import { projectBrowser } from "../browsers/resolve.js";
|
|
8
4
|
import Page from "./page.js";
|
|
9
|
-
import { SESSION_CAPABILITIES } from "./session-capabilities.js";
|
|
10
5
|
import { launchChrome } from "./chrome-launch.js";
|
|
11
6
|
import { BrowserContext } from "./browser-context.js";
|
|
12
7
|
import { validateEmulation } from "./emulation.js";
|
|
13
8
|
import { prepareDownloadsDir, removeIfEmpty } from "./downloads-dir.js";
|
|
14
9
|
import { ChromeDownloadFolders } from "./download-folders.js";
|
|
10
|
+
import { launchFirefox } from "./launch-firefox.js";
|
|
11
|
+
import { defaultLaunchOptions, ensureBrowserProfile, } from "./launch-flags.js";
|
|
12
|
+
import { abandonLaunch } from "./session.js";
|
|
15
13
|
import logger from "../logger/index.js";
|
|
16
|
-
import {
|
|
17
|
-
export { SESSION_CAPABILITIES };
|
|
18
|
-
/** session.new waits at least this long (ms): the first start after an install can take a while (macOS checks the new app) */
|
|
19
|
-
const SESSION_START_TIMEOUT = 120_000;
|
|
20
|
-
/** session.new got no reply in time */
|
|
21
|
-
export class BrowserStartTimeoutError extends Error {
|
|
22
|
-
constructor(label, timeout) {
|
|
23
|
-
super(`${label} did not finish starting within ${Math.round(timeout / 1000)}s. The first start after an install can be slow; run again.`);
|
|
24
|
-
this.name = "BrowserStartTimeoutError";
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
/** Sends session.new with its own, longer timeout; `label` names the browser in the timeout error */
|
|
28
|
-
export async function startSession(connector, label, commandTimeout) {
|
|
29
|
-
const timeout = Math.max(commandTimeout ?? DEFAULT_COMMAND_TIMEOUT, SESSION_START_TIMEOUT);
|
|
30
|
-
try {
|
|
31
|
-
await connector.send("session.new", { capabilities: SESSION_CAPABILITIES }, { timeout });
|
|
32
|
-
}
|
|
33
|
-
catch (err) {
|
|
34
|
-
if (err instanceof BiDiError && err.code === "timeout")
|
|
35
|
-
throw new BrowserStartTimeoutError(label, timeout);
|
|
36
|
-
throw err;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
14
|
+
import { projectDir, readConfig } from "../config/config.js";
|
|
39
15
|
/** Time (ms) to wait for Firefox to answer browser.close before killing the process */
|
|
40
16
|
const BROWSER_CLOSE_GRACE = 2000;
|
|
41
|
-
const browserProfilePath = (browserName) => path.join(browsersDir(), "profiles", browserName, "user.js");
|
|
42
|
-
const baseBrowserLaunchFlags = {
|
|
43
|
-
// Chrome is started by its chromedriver: see chrome-launch.ts
|
|
44
|
-
chrome: [],
|
|
45
|
-
firefox: ["--no-sandbox", "--no-remote"],
|
|
46
|
-
};
|
|
47
|
-
/** Read when a browser launches, since the profile folder depends on the active project */
|
|
48
|
-
const defaultLaunchOptions = (browserName) => ({
|
|
49
|
-
port: 9222,
|
|
50
|
-
profileDir: path.dirname(browserProfilePath(browserName)),
|
|
51
|
-
headless: true,
|
|
52
|
-
});
|
|
53
|
-
export const browserLaunchFlag = (browserName, launchOptions) => {
|
|
54
|
-
const defLaunchOpts = defaultLaunchOptions(browserName);
|
|
55
|
-
const port = launchOptions.port || defLaunchOpts.port;
|
|
56
|
-
const profileDir = launchOptions.profileDir || defLaunchOpts.profileDir;
|
|
57
|
-
const headless = launchOptions.headless ?? defLaunchOpts.headless;
|
|
58
|
-
const baseLaunchFlags = [...baseBrowserLaunchFlags[browserName]];
|
|
59
|
-
if (headless) {
|
|
60
|
-
baseLaunchFlags.push("--headless");
|
|
61
|
-
}
|
|
62
|
-
baseLaunchFlags.push(`--remote-debugging-port=${port}`);
|
|
63
|
-
switch (browserName) {
|
|
64
|
-
case "chrome":
|
|
65
|
-
break;
|
|
66
|
-
case "firefox":
|
|
67
|
-
baseLaunchFlags.push("--profile", profileDir);
|
|
68
|
-
// The home page (about:home) is privileged: BiDi refuses commands such as setViewport on it
|
|
69
|
-
baseLaunchFlags.push("about:blank");
|
|
70
|
-
break;
|
|
71
|
-
}
|
|
72
|
-
logger.verbose("Launch flags for browser %s: ", browserName, {
|
|
73
|
-
baseLaunchFlags,
|
|
74
|
-
});
|
|
75
|
-
return baseLaunchFlags;
|
|
76
|
-
};
|
|
77
|
-
/**
|
|
78
|
-
* Stops what a failed launch started. A browser left running holds the profile (the next launch
|
|
79
|
-
* fails with "already open") and keeps the test process alive.
|
|
80
|
-
*/
|
|
81
|
-
export function abandonLaunch(browserProc, biDiConnector) {
|
|
82
|
-
biDiConnector?.kill();
|
|
83
|
-
browserProc.kill();
|
|
84
|
-
}
|
|
85
|
-
// On macOS a spawned browser is attributed to the terminal, which is denied access to
|
|
86
|
-
// ~/Library/Application Support/Firefox (profiles.ini). Point Firefox at a local app data dir instead.
|
|
87
|
-
const browserAppDataPath = (browserName) => path.join(browsersDir(), "app-data", browserName);
|
|
88
|
-
/** Env added to a browser's process */
|
|
89
|
-
export const browserLaunchEnv = (browserName, appDataPath = browserAppDataPath(browserName)) => {
|
|
90
|
-
mkdirSync(appDataPath, { recursive: true });
|
|
91
|
-
logger.debug("Using app data dir for %s: %s", browserName, appDataPath);
|
|
92
|
-
switch (browserName) {
|
|
93
|
-
case "chrome":
|
|
94
|
-
return {};
|
|
95
|
-
case "firefox":
|
|
96
|
-
// The project profile may have been opened by a newer Firefox (the system one, before 0.6);
|
|
97
|
-
// without this Firefox refuses to start on it
|
|
98
|
-
return { MOZ_APP_DATA: appDataPath, MOZ_ALLOW_DOWNGRADE: "1" };
|
|
99
|
-
}
|
|
100
|
-
};
|
|
101
|
-
const browserProfiles = {
|
|
102
|
-
// Chrome needs no prefs file; its profile folder is created at launch
|
|
103
|
-
chrome: "",
|
|
104
|
-
firefox: `
|
|
105
|
-
user_pref("devtools.debugger.remote-enabled", true);
|
|
106
|
-
user_pref("devtools.debugger.prompt-connection", false);
|
|
107
|
-
user_pref("devtools.chrome.enabled", true);
|
|
108
|
-
// The home page (about:home) is privileged: BiDi refuses commands such as setViewport on it
|
|
109
|
-
user_pref("browser.startup.page", 0);
|
|
110
|
-
user_pref("browser.startup.homepage", "about:blank");
|
|
111
|
-
user_pref("browser.newtabpage.enabled", false);
|
|
112
|
-
`,
|
|
113
|
-
};
|
|
114
|
-
// Firefox logs "WebDriver BiDi listening on ws://<host>:<port>" without the endpoint path
|
|
115
|
-
const FIREFOX_WS_PATH = "/session";
|
|
116
|
-
const FIREFOX_WS_REGEX = /ws:\/\/(\d){1,3}.(\d){1,3}.(\d){1,3}.(\d){1,3}:(\d){4}/g;
|
|
117
17
|
export class Browser {
|
|
118
18
|
constructor({ browserProc, biDiConnector, downloadsDir, downloadFolders, }) {
|
|
119
19
|
this.userContexts = new Map();
|
|
@@ -143,86 +43,41 @@ export class Browser {
|
|
|
143
43
|
signal?.throwIfAborted();
|
|
144
44
|
logger.verbose("Trying to launch browser %s", browserName);
|
|
145
45
|
const resolved = await projectBrowser(browserName);
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
46
|
+
logger.debug("Found browser install location at: %s", resolved.executablePath);
|
|
47
|
+
ensureBrowserProfile(browserName, launchOptions.profileDir &&
|
|
48
|
+
path.join(launchOptions.profileDir, "user.js"));
|
|
49
|
+
const commandTimeout = await readCommandTimeout();
|
|
50
|
+
// Both launchers hand back an open BiDi session: Firefox after session.new, Chrome with the one
|
|
51
|
+
// its chromedriver created
|
|
52
|
+
const { proc, connector } = browserName === "chrome"
|
|
53
|
+
? await launchChrome({
|
|
54
|
+
executablePath: resolved.executablePath,
|
|
153
55
|
driverPath: resolved.driverPath,
|
|
154
|
-
profileDir: launchOptions.profileDir ??
|
|
56
|
+
profileDir: launchOptions.profileDir ??
|
|
57
|
+
defaultLaunchOptions("chrome").profileDir,
|
|
155
58
|
headless: launchOptions.headless ?? defaultLaunchOptions("chrome").headless,
|
|
156
59
|
commandTimeout,
|
|
157
60
|
signal,
|
|
61
|
+
})
|
|
62
|
+
: await launchFirefox({
|
|
63
|
+
executablePath: resolved.executablePath,
|
|
64
|
+
label: resolved.label,
|
|
65
|
+
launchOptions,
|
|
66
|
+
commandTimeout,
|
|
67
|
+
signal,
|
|
158
68
|
});
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
throw err;
|
|
166
|
-
}
|
|
69
|
+
try {
|
|
70
|
+
return await Browser.attach(proc, connector, browserName);
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
abandonLaunch(proc, connector);
|
|
74
|
+
throw err;
|
|
167
75
|
}
|
|
168
|
-
logger.verbose("Trying to spawn %s process", browserName);
|
|
169
|
-
const browserProc = spawn(browserLocation, browserLaunchFlag(browserName, launchOptions), { env: { ...process.env, ...browserLaunchEnv(browserName) } });
|
|
170
|
-
return new Promise((resolve, reject) => {
|
|
171
|
-
const onUnexpectedClose = (code) => {
|
|
172
|
-
reject(new Error(`Browser process exited unexpectedly with code ${code}`));
|
|
173
|
-
};
|
|
174
|
-
browserProc.on("close", onUnexpectedClose);
|
|
175
|
-
const onAbort = () => {
|
|
176
|
-
logger.verbose("Launch of %s aborted, killing process", browserName);
|
|
177
|
-
browserProc.kill();
|
|
178
|
-
reject(signal?.reason);
|
|
179
|
-
};
|
|
180
|
-
signal?.addEventListener("abort", onAbort, { once: true });
|
|
181
|
-
browserProc.stdout.on("data", (data) => {
|
|
182
|
-
logger.debug(`${browserName} stdout: ${data}`);
|
|
183
|
-
if (String(data).match(/!!!\scould\snot\sstart\sserver\son\sport\s(\d){4}/g)) {
|
|
184
|
-
browserProc.kill();
|
|
185
|
-
reject(`PORT ${launchOptions.port} is already in use!`);
|
|
186
|
-
}
|
|
187
|
-
});
|
|
188
|
-
browserProc.stderr.on("data", async (data) => {
|
|
189
|
-
logger.debug(`${browserName} stderr: ${data}`);
|
|
190
|
-
const urlMatch = String(data).match(FIREFOX_WS_REGEX);
|
|
191
|
-
logger.debug("Browser announced its endpoint", { urlMatch });
|
|
192
|
-
if (!Array.isArray(urlMatch) || urlMatch.length === 0) {
|
|
193
|
-
return;
|
|
194
|
-
}
|
|
195
|
-
// Killing the process mid-handshake rejects pending BiDi calls; surface that via reject
|
|
196
|
-
// instead of an unhandled rejection from this async listener
|
|
197
|
-
let biDiConnector;
|
|
198
|
-
try {
|
|
199
|
-
let url = urlMatch[0] + FIREFOX_WS_PATH;
|
|
200
|
-
logger.verbose("Websocket URL match: %s", url);
|
|
201
|
-
const commandTimeout = await readCommandTimeout();
|
|
202
|
-
biDiConnector = await BiDiConnector.connect(url, { commandTimeout });
|
|
203
|
-
logger.verbose("Starting new session");
|
|
204
|
-
const status = await biDiConnector.send("session.status", {});
|
|
205
|
-
logger.verbose("Session status", status);
|
|
206
|
-
await startSession(biDiConnector, label, commandTimeout);
|
|
207
|
-
logger.verbose("Session started successfully");
|
|
208
|
-
const launched = await Browser.attach(browserProc, biDiConnector, browserName);
|
|
209
|
-
browserProc.off("close", onUnexpectedClose);
|
|
210
|
-
signal?.removeEventListener("abort", onAbort);
|
|
211
|
-
resolve(launched);
|
|
212
|
-
}
|
|
213
|
-
catch (err) {
|
|
214
|
-
browserProc.off("close", onUnexpectedClose);
|
|
215
|
-
signal?.removeEventListener("abort", onAbort);
|
|
216
|
-
abandonLaunch(browserProc, biDiConnector);
|
|
217
|
-
reject(err);
|
|
218
|
-
}
|
|
219
|
-
});
|
|
220
|
-
});
|
|
221
76
|
}
|
|
222
77
|
/** Sets up a browser whose BiDi session is open: downloads folder, the first page and what it tracks */
|
|
223
78
|
static async attach(browserProc, biDiConnector, browserName) {
|
|
224
79
|
biDiConnector.browserName = browserName;
|
|
225
|
-
const downloadsDir = await prepareDownloadsDir(biDiConnector, path.resolve(projectDir(), (await
|
|
80
|
+
const downloadsDir = await prepareDownloadsDir(biDiConnector, path.resolve(projectDir(), (await readDownloadsDir()) ?? "result/downloads"));
|
|
226
81
|
// Chrome replaces a file that has the name of a download; this keeps every download
|
|
227
82
|
const downloadFolders = browserName === "chrome" && downloadsDir
|
|
228
83
|
? new ChromeDownloadFolders(biDiConnector, downloadsDir)
|
|
@@ -247,13 +102,7 @@ export class Browser {
|
|
|
247
102
|
else {
|
|
248
103
|
page = await browser.newPage({ type: "tab" });
|
|
249
104
|
}
|
|
250
|
-
await page.
|
|
251
|
-
// Requests made before tracking starts are invisible to it, and a dialog nobody watches would hang the page
|
|
252
|
-
await page.startNetworkTracking();
|
|
253
|
-
await page.startDialogHandling();
|
|
254
|
-
await page.startLogging();
|
|
255
|
-
await page.startDownloadTracking();
|
|
256
|
-
await page.startFileChooserTracking();
|
|
105
|
+
await page.startTracking();
|
|
257
106
|
return { browser, page };
|
|
258
107
|
}
|
|
259
108
|
/** Closes the browser via BiDi browser.close, then makes sure the process has exited */
|
|
@@ -296,12 +145,7 @@ export class Browser {
|
|
|
296
145
|
logger.debug("Created browsing context", { createBrowserContext });
|
|
297
146
|
const page = new Page(this.biDiConnector, createBrowserContext.context);
|
|
298
147
|
this.contextFor(userContext).register(page);
|
|
299
|
-
await page.
|
|
300
|
-
await page.startNetworkTracking();
|
|
301
|
-
await page.startDialogHandling();
|
|
302
|
-
await page.startLogging();
|
|
303
|
-
await page.startDownloadTracking();
|
|
304
|
-
await page.startFileChooserTracking();
|
|
148
|
+
await page.startTracking();
|
|
305
149
|
return page;
|
|
306
150
|
}
|
|
307
151
|
/**
|
|
@@ -363,13 +207,13 @@ export class Browser {
|
|
|
363
207
|
});
|
|
364
208
|
}
|
|
365
209
|
}
|
|
366
|
-
/**
|
|
367
|
-
async function
|
|
210
|
+
/** The configured downloads folder; a missing or unreadable config falls back to the default */
|
|
211
|
+
async function readDownloadsDir() {
|
|
368
212
|
try {
|
|
369
|
-
return await readConfig(
|
|
213
|
+
return await readConfig("downloadsDir");
|
|
370
214
|
}
|
|
371
215
|
catch (err) {
|
|
372
|
-
logger.debug("Could not read
|
|
216
|
+
logger.debug("Could not read downloadsDir from config, using the default", {
|
|
373
217
|
err,
|
|
374
218
|
});
|
|
375
219
|
return undefined;
|
|
@@ -385,30 +229,3 @@ async function readCommandTimeout() {
|
|
|
385
229
|
return undefined;
|
|
386
230
|
}
|
|
387
231
|
}
|
|
388
|
-
/** Writes the browser's user.js unless it already holds the current prefs; an outdated one is replaced */
|
|
389
|
-
export function ensureBrowserProfile(browserName, profilePath = browserProfilePath(browserName)) {
|
|
390
|
-
logger.verbose("Checking browser profile for: %s", browserName);
|
|
391
|
-
if (!profilePath) {
|
|
392
|
-
logger.verbose("No browser profile path declared, skipping...");
|
|
393
|
-
return;
|
|
394
|
-
}
|
|
395
|
-
if (existsSync(profilePath)) {
|
|
396
|
-
if (readFileSync(profilePath, "utf8") === browserProfiles[browserName]) {
|
|
397
|
-
logger.verbose("Browser profile is up to date at: %s", profilePath);
|
|
398
|
-
return;
|
|
399
|
-
}
|
|
400
|
-
logger.verbose("Browser profile at %s is outdated, rewriting it", profilePath);
|
|
401
|
-
}
|
|
402
|
-
else {
|
|
403
|
-
logger.verbose("No browser profile created yet for %s", browserName);
|
|
404
|
-
}
|
|
405
|
-
createBrowserProfile(browserName, profilePath);
|
|
406
|
-
}
|
|
407
|
-
function createBrowserProfile(browserName, profilePath) {
|
|
408
|
-
logger.verbose("Creating browser profile for %s at %s", browserName, profilePath);
|
|
409
|
-
mkdirSync(path.dirname(profilePath), { recursive: true });
|
|
410
|
-
writeFileSync(profilePath, browserProfiles[browserName], {
|
|
411
|
-
encoding: "utf8",
|
|
412
|
-
});
|
|
413
|
-
logger.verbose("Browser profile created");
|
|
414
|
-
}
|
|
@@ -8,7 +8,7 @@ export interface BrowserProcess {
|
|
|
8
8
|
once(event: "exit", listener: (...args: any[]) => void): unknown;
|
|
9
9
|
kill(signal?: NodeJS.Signals): boolean;
|
|
10
10
|
}
|
|
11
|
-
|
|
11
|
+
interface ChromeLaunch {
|
|
12
12
|
executablePath: string;
|
|
13
13
|
driverPath: string;
|
|
14
14
|
profileDir: string;
|
|
@@ -67,3 +67,4 @@ export declare class ChromeProcess extends EventEmitter implements BrowserProces
|
|
|
67
67
|
ended(): void;
|
|
68
68
|
kill(signal?: NodeJS.Signals): boolean;
|
|
69
69
|
}
|
|
70
|
+
export {};
|
|
@@ -0,0 +1,32 @@
|
|
|
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
|
+
/** The context tree of a page: created on first use, readable once it exists */
|
|
6
|
+
export interface TreeSource {
|
|
7
|
+
ensure(): Promise<ContextTree>;
|
|
8
|
+
peek(): ContextTree | undefined;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Watches the JavaScript dialogs of one page (frames included) and hands each to the `dialog` listeners of
|
|
12
|
+
* `emitter`; a dialog nobody answers gets the default answer, so nothing hangs.
|
|
13
|
+
*/
|
|
14
|
+
export declare class DialogHandling {
|
|
15
|
+
private connector;
|
|
16
|
+
private contextId;
|
|
17
|
+
private tree;
|
|
18
|
+
private emitter;
|
|
19
|
+
private lazySubscription;
|
|
20
|
+
private openDialogs;
|
|
21
|
+
constructor(connector: BiDiConnector, contextId: BrowsingContext, tree: TreeSource, emitter: EventEmitter);
|
|
22
|
+
/** Starts watching for dialogs; the browser does this when it opens a page, `page.on("dialog")` when needed */
|
|
23
|
+
start(): Promise<void>;
|
|
24
|
+
/** Stops listening, forgets the open dialogs and gives the subscription back */
|
|
25
|
+
dispose(): Promise<void>;
|
|
26
|
+
private stopListening;
|
|
27
|
+
private ownsContext;
|
|
28
|
+
private onPromptOpened;
|
|
29
|
+
private onPromptClosed;
|
|
30
|
+
/** Lets the listeners answer; a dialog still open afterwards gets the default answer, so nothing hangs */
|
|
31
|
+
private dispatchDialog;
|
|
32
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import logger from "../logger/index.js";
|
|
2
|
+
import { Dialog } from "./dialog.js";
|
|
3
|
+
import { Lazy } from "./lazy.js";
|
|
4
|
+
/**
|
|
5
|
+
* Watches the JavaScript dialogs of one page (frames included) and hands each to the `dialog` listeners of
|
|
6
|
+
* `emitter`; a dialog nobody answers gets the default answer, so nothing hangs.
|
|
7
|
+
*/
|
|
8
|
+
export class DialogHandling {
|
|
9
|
+
constructor(connector, contextId, tree, emitter) {
|
|
10
|
+
this.connector = connector;
|
|
11
|
+
this.contextId = contextId;
|
|
12
|
+
this.tree = tree;
|
|
13
|
+
this.emitter = emitter;
|
|
14
|
+
this.lazySubscription = new Lazy();
|
|
15
|
+
this.openDialogs = new Map();
|
|
16
|
+
this.onPromptOpened = (params) => {
|
|
17
|
+
if (!this.ownsContext(params.context))
|
|
18
|
+
return;
|
|
19
|
+
this.dispatchDialog(new Dialog(this.connector, this.contextId, params), params).catch((err) => logger.error("Handling a dialog failed", { err }));
|
|
20
|
+
};
|
|
21
|
+
this.onPromptClosed = (params) => {
|
|
22
|
+
if (!this.ownsContext(params.context))
|
|
23
|
+
return;
|
|
24
|
+
const dialog = this.openDialogs.get(params.context);
|
|
25
|
+
this.openDialogs.delete(params.context);
|
|
26
|
+
dialog?.markClosed({
|
|
27
|
+
accepted: params.accepted,
|
|
28
|
+
...(params.userText !== undefined && { userText: params.userText }),
|
|
29
|
+
});
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/** Starts watching for dialogs; the browser does this when it opens a page, `page.on("dialog")` when needed */
|
|
33
|
+
async start() {
|
|
34
|
+
await this.lazySubscription.get(async () => {
|
|
35
|
+
await this.tree.ensure();
|
|
36
|
+
this.connector.onEvent("browsingContext.userPromptOpened", this.onPromptOpened);
|
|
37
|
+
this.connector.onEvent("browsingContext.userPromptClosed", this.onPromptClosed);
|
|
38
|
+
try {
|
|
39
|
+
return await this.connector.subscribe([
|
|
40
|
+
"browsingContext.userPromptOpened",
|
|
41
|
+
"browsingContext.userPromptClosed",
|
|
42
|
+
]);
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
this.stopListening();
|
|
46
|
+
throw err;
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
/** Stops listening, forgets the open dialogs and gives the subscription back */
|
|
51
|
+
async dispose() {
|
|
52
|
+
const subscription = this.lazySubscription.take();
|
|
53
|
+
this.stopListening();
|
|
54
|
+
this.openDialogs.clear();
|
|
55
|
+
await (await subscription)?.unsubscribe();
|
|
56
|
+
}
|
|
57
|
+
stopListening() {
|
|
58
|
+
this.connector.offEvent("browsingContext.userPromptOpened", this.onPromptOpened);
|
|
59
|
+
this.connector.offEvent("browsingContext.userPromptClosed", this.onPromptClosed);
|
|
60
|
+
}
|
|
61
|
+
ownsContext(context) {
|
|
62
|
+
return (this.tree.peek()?.isWithin(context, this.contextId) ??
|
|
63
|
+
context === this.contextId);
|
|
64
|
+
}
|
|
65
|
+
/** Lets the listeners answer; a dialog still open afterwards gets the default answer, so nothing hangs */
|
|
66
|
+
async dispatchDialog(dialog, params) {
|
|
67
|
+
this.openDialogs.set(params.context, dialog);
|
|
68
|
+
// rawListeners: the wrappers that make a `once` listener remove itself when called
|
|
69
|
+
const listeners = this.emitter.rawListeners("dialog");
|
|
70
|
+
await Promise.all(listeners.map(async (listener) => {
|
|
71
|
+
try {
|
|
72
|
+
await listener(dialog);
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
logger.error("A dialog listener threw", { err });
|
|
76
|
+
}
|
|
77
|
+
}));
|
|
78
|
+
if (dialog.handled)
|
|
79
|
+
return;
|
|
80
|
+
// Staying on a page is not what a test that navigates wants: leave it, dismiss everything else
|
|
81
|
+
const accept = params.type === "beforeunload";
|
|
82
|
+
logger.warn('Dialog "%s" was %s automatically; add page.on("dialog") to handle it', params.message, accept ? "accepted" : "dismissed");
|
|
83
|
+
await (accept ? dialog.accept() : dialog.dismiss());
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { BiDiConnector } from "../transport/bidi-connection.js";
|
|
2
|
+
import type { Subscription } from "../transport/subscriptions.js";
|
|
3
|
+
import type { BrowsingContext } from "../types/bidi-modules/browsing-context.js";
|
|
4
|
+
/** What a wait for the document can reach */
|
|
5
|
+
export type DocumentState = "load" | "domcontentloaded";
|
|
6
|
+
/**
|
|
7
|
+
* Follows the document of one page: which navigations reached DOMContentLoaded and load, and waits for a state of
|
|
8
|
+
* the document that is showing (or of the navigation that is on its way).
|
|
9
|
+
*/
|
|
10
|
+
export declare class DocumentWatcher {
|
|
11
|
+
private connector;
|
|
12
|
+
private contextId;
|
|
13
|
+
/** How far navigations that did not wait for the load got, by navigation id (the latest few) */
|
|
14
|
+
private navigationProgress;
|
|
15
|
+
private lazyEvents;
|
|
16
|
+
constructor(connector: BiDiConnector, contextId: BrowsingContext);
|
|
17
|
+
/** The progress of `navigation`, created (and the oldest forgotten) when it is not known yet */
|
|
18
|
+
progressOf(navigation: string): {
|
|
19
|
+
domContentLoaded: boolean;
|
|
20
|
+
load: boolean;
|
|
21
|
+
};
|
|
22
|
+
private onDocumentEvent;
|
|
23
|
+
private onDomContentLoaded;
|
|
24
|
+
private onLoad;
|
|
25
|
+
/** Starts recording which navigations reached DOMContentLoaded and load; kept until dispose */
|
|
26
|
+
watch(): Promise<Subscription>;
|
|
27
|
+
/** Stops listening and gives the subscription back */
|
|
28
|
+
dispose(): Promise<void>;
|
|
29
|
+
private canBeAborted;
|
|
30
|
+
/** Subscribes to what a wait listens for; a browser that lacks the navigationAborted event (Firefox) is asked only once */
|
|
31
|
+
private subscribe;
|
|
32
|
+
/**
|
|
33
|
+
* The first of: the state is reached, the navigation `last` fails, the navigation `last` is aborted. Registered
|
|
34
|
+
* before the state is read, so an event in between is not lost.
|
|
35
|
+
*/
|
|
36
|
+
private eventsOf;
|
|
37
|
+
/** Whether the document is at `target` already: a navigation we saw fall short beats the document that is showing */
|
|
38
|
+
private readyStateReached;
|
|
39
|
+
/**
|
|
40
|
+
* Resolves when the document reached `target`; rejects with a NavigationError when the navigation `last` (the
|
|
41
|
+
* latest one navigateTo started) fails or is aborted, and with a WaitTimeoutError after `timeout` ms.
|
|
42
|
+
*/
|
|
43
|
+
waitForState(target: DocumentState, timeout: number, last: string | undefined): Promise<void>;
|
|
44
|
+
}
|