@itmetsam/samurai-framework 0.10.0 → 0.12.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/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
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import logger from "../logger/index.js";
|
|
3
|
+
import { BiDiConnector } from "../transport/bidi-connection.js";
|
|
4
|
+
import { browserLaunchEnv, browserLaunchFlag, defaultLaunchOptions, } from "./launch-flags.js";
|
|
5
|
+
import { abandonLaunch, startSession } from "./session.js";
|
|
6
|
+
// Firefox logs "WebDriver BiDi listening on ws://<host>:<port>" without the endpoint path
|
|
7
|
+
const FIREFOX_WS_PATH = "/session";
|
|
8
|
+
const FIREFOX_WS_REGEX = /ws:\/\/\d{1,3}(?:\.\d{1,3}){3}:\d+/g;
|
|
9
|
+
const PORT_IN_USE = /!!!\scould\snot\sstart\sserver\son\sport\s\d+/;
|
|
10
|
+
/**
|
|
11
|
+
* Starts Firefox, waits for it to announce its BiDi endpoint, connects and opens the BiDi session.
|
|
12
|
+
* Rejects with an Error when the process exits first or the handshake fails, with the abort reason when aborted,
|
|
13
|
+
* and with a message string when the debugging port is taken. A failed launch stops what it started.
|
|
14
|
+
*/
|
|
15
|
+
export function launchFirefox(launch) {
|
|
16
|
+
const { executablePath, label, launchOptions, commandTimeout, signal } = launch;
|
|
17
|
+
logger.verbose("Trying to spawn firefox process");
|
|
18
|
+
const proc = spawn(executablePath, browserLaunchFlag("firefox", launchOptions), { env: { ...process.env, ...browserLaunchEnv("firefox") } });
|
|
19
|
+
return new Promise((resolve, reject) => {
|
|
20
|
+
const onUnexpectedClose = (code) => reject(new Error(`Browser process exited unexpectedly with code ${code}`));
|
|
21
|
+
proc.on("close", onUnexpectedClose);
|
|
22
|
+
const onAbort = () => {
|
|
23
|
+
logger.verbose("Launch of firefox aborted, killing process");
|
|
24
|
+
proc.kill();
|
|
25
|
+
reject(signal?.reason);
|
|
26
|
+
};
|
|
27
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
28
|
+
const settled = () => {
|
|
29
|
+
proc.off("close", onUnexpectedClose);
|
|
30
|
+
signal?.removeEventListener("abort", onAbort);
|
|
31
|
+
};
|
|
32
|
+
proc.stdout.on("data", (data) => {
|
|
33
|
+
logger.debug(`firefox stdout: ${data}`);
|
|
34
|
+
if (PORT_IN_USE.test(String(data))) {
|
|
35
|
+
proc.kill();
|
|
36
|
+
reject(`PORT ${launchOptions.port ?? defaultLaunchOptions("firefox").port} is already in use!`);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
proc.stderr.on("data", async (data) => {
|
|
40
|
+
logger.debug(`firefox stderr: ${data}`);
|
|
41
|
+
const urlMatch = String(data).match(FIREFOX_WS_REGEX);
|
|
42
|
+
logger.debug("Browser announced its endpoint", { urlMatch });
|
|
43
|
+
if (!urlMatch || urlMatch.length === 0)
|
|
44
|
+
return;
|
|
45
|
+
// Killing the process mid-handshake rejects pending BiDi calls; surface that via reject
|
|
46
|
+
// instead of an unhandled rejection from this async listener
|
|
47
|
+
let connector;
|
|
48
|
+
try {
|
|
49
|
+
const url = urlMatch[0] + FIREFOX_WS_PATH;
|
|
50
|
+
logger.verbose("Websocket URL match: %s", url);
|
|
51
|
+
connector = await BiDiConnector.connect(url, { commandTimeout });
|
|
52
|
+
const status = await connector.send("session.status", {});
|
|
53
|
+
logger.verbose("Session status", status);
|
|
54
|
+
await startSession(connector, label, commandTimeout);
|
|
55
|
+
logger.verbose("Session started successfully");
|
|
56
|
+
settled();
|
|
57
|
+
resolve({ proc, connector });
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
settled();
|
|
61
|
+
abandonLaunch(proc, connector);
|
|
62
|
+
reject(err);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { SupportedBrowser } from "../types/browser.js";
|
|
2
|
+
export interface BrowserLaunchOptions {
|
|
3
|
+
/** Which port to use for the WebDeveloper tools */
|
|
4
|
+
port: number;
|
|
5
|
+
/** The browser profile folder: user.js goes in it for Firefox, Chrome's user data for Chrome */
|
|
6
|
+
profileDir: string;
|
|
7
|
+
/** Start the browser in headless mode or in UI mode */
|
|
8
|
+
headless: boolean;
|
|
9
|
+
}
|
|
10
|
+
/** The profile folder of a worker: worker 0 keeps the browser's own folder, worker N gets `<browser>-wN` beside it */
|
|
11
|
+
export declare const workerProfileDir: (browserName: SupportedBrowser, workerIndex: number) => string;
|
|
12
|
+
/** Read when a browser launches, since the profile folder depends on the active project */
|
|
13
|
+
export declare const defaultLaunchOptions: (browserName: SupportedBrowser) => BrowserLaunchOptions;
|
|
14
|
+
export declare const browserLaunchFlag: (browserName: SupportedBrowser, launchOptions: Partial<BrowserLaunchOptions>) => string[];
|
|
15
|
+
/** Env added to a browser's process */
|
|
16
|
+
export declare const browserLaunchEnv: (browserName: SupportedBrowser, appDataPath?: string) => Record<string, string>;
|
|
17
|
+
/** 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;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { browsersDir } from "../config/config.js";
|
|
4
|
+
import logger from "../logger/index.js";
|
|
5
|
+
const browserProfilePath = (browserName) => path.join(browsersDir(), "profiles", browserName, "user.js");
|
|
6
|
+
const baseBrowserLaunchFlags = {
|
|
7
|
+
// Chrome is started by its chromedriver: see chrome-launch.ts
|
|
8
|
+
chrome: [],
|
|
9
|
+
firefox: ["--no-sandbox", "--no-remote"],
|
|
10
|
+
};
|
|
11
|
+
/** The profile folder of a worker: worker 0 keeps the browser's own folder, worker N gets `<browser>-wN` beside it */
|
|
12
|
+
export const workerProfileDir = (browserName, workerIndex) => workerIndex === 0
|
|
13
|
+
? path.dirname(browserProfilePath(browserName))
|
|
14
|
+
: path.join(browsersDir(), "profiles", `${browserName}-w${workerIndex}`);
|
|
15
|
+
/** Read when a browser launches, since the profile folder depends on the active project */
|
|
16
|
+
export const defaultLaunchOptions = (browserName) => ({
|
|
17
|
+
port: 9222,
|
|
18
|
+
profileDir: path.dirname(browserProfilePath(browserName)),
|
|
19
|
+
headless: true,
|
|
20
|
+
});
|
|
21
|
+
export const browserLaunchFlag = (browserName, launchOptions) => {
|
|
22
|
+
const defLaunchOpts = defaultLaunchOptions(browserName);
|
|
23
|
+
const port = launchOptions.port || defLaunchOpts.port;
|
|
24
|
+
const profileDir = launchOptions.profileDir || defLaunchOpts.profileDir;
|
|
25
|
+
const headless = launchOptions.headless ?? defLaunchOpts.headless;
|
|
26
|
+
const baseLaunchFlags = [...baseBrowserLaunchFlags[browserName]];
|
|
27
|
+
if (headless) {
|
|
28
|
+
baseLaunchFlags.push("--headless");
|
|
29
|
+
}
|
|
30
|
+
baseLaunchFlags.push(`--remote-debugging-port=${port}`);
|
|
31
|
+
switch (browserName) {
|
|
32
|
+
case "chrome":
|
|
33
|
+
break;
|
|
34
|
+
case "firefox":
|
|
35
|
+
baseLaunchFlags.push("--profile", profileDir);
|
|
36
|
+
// The home page (about:home) is privileged: BiDi refuses commands such as setViewport on it
|
|
37
|
+
baseLaunchFlags.push("about:blank");
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
logger.verbose("Launch flags for browser %s: ", browserName, {
|
|
41
|
+
baseLaunchFlags,
|
|
42
|
+
});
|
|
43
|
+
return baseLaunchFlags;
|
|
44
|
+
};
|
|
45
|
+
// On macOS a spawned browser is attributed to the terminal, which is denied access to
|
|
46
|
+
// ~/Library/Application Support/Firefox (profiles.ini). Point Firefox at a local app data dir instead.
|
|
47
|
+
const browserAppDataPath = (browserName) => path.join(browsersDir(), "app-data", browserName);
|
|
48
|
+
/** Env added to a browser's process */
|
|
49
|
+
export const browserLaunchEnv = (browserName, appDataPath = browserAppDataPath(browserName)) => {
|
|
50
|
+
mkdirSync(appDataPath, { recursive: true });
|
|
51
|
+
logger.debug("Using app data dir for %s: %s", browserName, appDataPath);
|
|
52
|
+
switch (browserName) {
|
|
53
|
+
case "chrome":
|
|
54
|
+
return {};
|
|
55
|
+
case "firefox":
|
|
56
|
+
// The project profile may have been opened by a newer Firefox (the system one, before 0.6);
|
|
57
|
+
// without this Firefox refuses to start on it
|
|
58
|
+
return { MOZ_APP_DATA: appDataPath, MOZ_ALLOW_DOWNGRADE: "1" };
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
const browserProfiles = {
|
|
62
|
+
// Chrome needs no prefs file; its profile folder is created at launch
|
|
63
|
+
chrome: "",
|
|
64
|
+
firefox: `
|
|
65
|
+
user_pref("devtools.debugger.remote-enabled", true);
|
|
66
|
+
user_pref("devtools.debugger.prompt-connection", false);
|
|
67
|
+
user_pref("devtools.chrome.enabled", true);
|
|
68
|
+
// The home page (about:home) is privileged: BiDi refuses commands such as setViewport on it
|
|
69
|
+
user_pref("browser.startup.page", 0);
|
|
70
|
+
user_pref("browser.startup.homepage", "about:blank");
|
|
71
|
+
user_pref("browser.newtabpage.enabled", false);
|
|
72
|
+
`,
|
|
73
|
+
};
|
|
74
|
+
/** 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)) {
|
|
76
|
+
logger.verbose("Checking browser profile for: %s", browserName);
|
|
77
|
+
if (!profilePath) {
|
|
78
|
+
logger.verbose("No browser profile path declared, skipping...");
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (existsSync(profilePath)) {
|
|
82
|
+
if (readFileSync(profilePath, "utf8") === browserProfiles[browserName]) {
|
|
83
|
+
logger.verbose("Browser profile is up to date at: %s", profilePath);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
logger.verbose("Browser profile at %s is outdated, rewriting it", profilePath);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
logger.verbose("No browser profile created yet for %s", browserName);
|
|
90
|
+
}
|
|
91
|
+
createBrowserProfile(browserName, profilePath);
|
|
92
|
+
}
|
|
93
|
+
function createBrowserProfile(browserName, profilePath) {
|
|
94
|
+
logger.verbose("Creating browser profile for %s at %s", browserName, profilePath);
|
|
95
|
+
mkdirSync(path.dirname(profilePath), { recursive: true });
|
|
96
|
+
writeFileSync(profilePath, browserProfiles[browserName], {
|
|
97
|
+
encoding: "utf8",
|
|
98
|
+
});
|
|
99
|
+
logger.verbose("Browser profile created");
|
|
100
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Something started on first use and shared by everyone who asks: concurrent callers get the same promise, a failed
|
|
3
|
+
* start is forgotten so the next call tries again, and the finished value can be read without waiting (`peek`).
|
|
4
|
+
*/
|
|
5
|
+
export declare class Lazy<T> {
|
|
6
|
+
private promise;
|
|
7
|
+
private value;
|
|
8
|
+
/** Whether a start was made and not forgotten since (it may still be running or have failed and been kept) */
|
|
9
|
+
get started(): boolean;
|
|
10
|
+
/**
|
|
11
|
+
* The shared start, made by `factory` on the first call. `keepFailure` says which failures stay (every later call
|
|
12
|
+
* gets the same rejection) instead of being forgotten.
|
|
13
|
+
*/
|
|
14
|
+
get(factory: () => Promise<T>, keepFailure?: (err: unknown) => boolean): Promise<T>;
|
|
15
|
+
/** The value once the start finished, else undefined */
|
|
16
|
+
peek(): T | undefined;
|
|
17
|
+
/** Forgets the start; the next `get` starts again */
|
|
18
|
+
reset(): void;
|
|
19
|
+
/** Forgets the start and hands back its promise, for the caller to dispose; undefined when nothing was started */
|
|
20
|
+
take(): Promise<T> | undefined;
|
|
21
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Something started on first use and shared by everyone who asks: concurrent callers get the same promise, a failed
|
|
3
|
+
* start is forgotten so the next call tries again, and the finished value can be read without waiting (`peek`).
|
|
4
|
+
*/
|
|
5
|
+
export class Lazy {
|
|
6
|
+
/** Whether a start was made and not forgotten since (it may still be running or have failed and been kept) */
|
|
7
|
+
get started() {
|
|
8
|
+
return this.promise !== undefined;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* The shared start, made by `factory` on the first call. `keepFailure` says which failures stay (every later call
|
|
12
|
+
* gets the same rejection) instead of being forgotten.
|
|
13
|
+
*/
|
|
14
|
+
get(factory, keepFailure = () => false) {
|
|
15
|
+
if (this.promise)
|
|
16
|
+
return this.promise;
|
|
17
|
+
const started = (async () => factory())().then((value) => {
|
|
18
|
+
if (this.promise === started)
|
|
19
|
+
this.value = value;
|
|
20
|
+
return value;
|
|
21
|
+
}, (err) => {
|
|
22
|
+
if (this.promise === started && !keepFailure(err))
|
|
23
|
+
this.reset();
|
|
24
|
+
throw err;
|
|
25
|
+
});
|
|
26
|
+
this.promise = started;
|
|
27
|
+
return started;
|
|
28
|
+
}
|
|
29
|
+
/** The value once the start finished, else undefined */
|
|
30
|
+
peek() {
|
|
31
|
+
return this.value;
|
|
32
|
+
}
|
|
33
|
+
/** Forgets the start; the next `get` starts again */
|
|
34
|
+
reset() {
|
|
35
|
+
this.promise = undefined;
|
|
36
|
+
this.value = undefined;
|
|
37
|
+
}
|
|
38
|
+
/** Forgets the start and hands back its promise, for the caller to dispose; undefined when nothing was started */
|
|
39
|
+
take() {
|
|
40
|
+
const promise = this.promise;
|
|
41
|
+
this.reset();
|
|
42
|
+
return promise;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type EventEmitter from "node:events";
|
|
2
|
+
import type { FailedRequest, NetworkRequest } from "../network/network-tracker.js";
|
|
3
|
+
import type { Response } from "../network/response.js";
|
|
4
|
+
import type { ConsoleMessage, PageError } from "./page-logs.js";
|
|
5
|
+
import type { Dialog } from "./dialog.js";
|
|
6
|
+
import type { Download } from "./download.js";
|
|
7
|
+
import type { FileChooser } from "./file-chooser.js";
|
|
8
|
+
export interface NetworkEvents {
|
|
9
|
+
request: [NetworkRequest];
|
|
10
|
+
response: [Response];
|
|
11
|
+
requestfailed: [FailedRequest];
|
|
12
|
+
}
|
|
13
|
+
/** Everything `page.on` can listen for */
|
|
14
|
+
export interface PageEvents extends NetworkEvents {
|
|
15
|
+
download: [Download];
|
|
16
|
+
filechooser: [FileChooser];
|
|
17
|
+
dialog: [Dialog];
|
|
18
|
+
console: [ConsoleMessage];
|
|
19
|
+
pageerror: [PageError];
|
|
20
|
+
}
|
|
21
|
+
export type PageEventName = keyof PageEvents;
|
|
22
|
+
/** Events `page.waitForEvent` can wait for */
|
|
23
|
+
export type WaitableEventName = "download" | "filechooser" | "request" | "response";
|
|
24
|
+
/** What the `waitFor…` calls of a page take */
|
|
25
|
+
export interface WaitOptions {
|
|
26
|
+
/** Time (ms) to wait. @default config `navigation.timeout`, then 30000 */
|
|
27
|
+
timeout?: number;
|
|
28
|
+
/** Runs once the wait is set up; what it causes cannot be missed */
|
|
29
|
+
trigger?: () => Promise<unknown>;
|
|
30
|
+
}
|
|
31
|
+
export interface WaitForEventOptions<T> extends WaitOptions {
|
|
32
|
+
/** Only an event this accepts counts */
|
|
33
|
+
predicate?: (item: T) => boolean;
|
|
34
|
+
}
|
|
35
|
+
/** What has to be switched on in the browser before an event can arrive */
|
|
36
|
+
export type EventSource = "dialogs" | "downloads" | "filechoosers" | "logs" | "network" | "bodies";
|
|
37
|
+
/** The parts of the page that must be listening for `event` to happen (a `response` also needs the bodies) */
|
|
38
|
+
export declare function sourcesFor(event: PageEventName): EventSource[];
|
|
39
|
+
/**
|
|
40
|
+
* Resolves with the first `event` that `matches` accepts. Listens before `options.trigger` runs, so what the trigger
|
|
41
|
+
* causes cannot be missed; a trigger that throws rejects the wait with its error. `onTimeout` builds the error for a
|
|
42
|
+
* wait that ran out after `timeout` ms.
|
|
43
|
+
*/
|
|
44
|
+
export declare function waitForPageEvent<E extends PageEventName>(emitter: EventEmitter, event: E, matches: (item: PageEvents[E][0]) => boolean, options: Pick<WaitOptions, "trigger">, timeout: number, onTimeout: (timeout: number) => Error): Promise<PageEvents[E][0]>;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/** The parts of the page that must be listening for `event` to happen (a `response` also needs the bodies) */
|
|
2
|
+
export function sourcesFor(event) {
|
|
3
|
+
switch (event) {
|
|
4
|
+
case "dialog":
|
|
5
|
+
return ["dialogs"];
|
|
6
|
+
case "download":
|
|
7
|
+
return ["downloads"];
|
|
8
|
+
case "filechooser":
|
|
9
|
+
return ["filechoosers"];
|
|
10
|
+
case "console":
|
|
11
|
+
case "pageerror":
|
|
12
|
+
return ["logs"];
|
|
13
|
+
case "response":
|
|
14
|
+
return ["network", "bodies"];
|
|
15
|
+
default:
|
|
16
|
+
return ["network"];
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Resolves with the first `event` that `matches` accepts. Listens before `options.trigger` runs, so what the trigger
|
|
21
|
+
* causes cannot be missed; a trigger that throws rejects the wait with its error. `onTimeout` builds the error for a
|
|
22
|
+
* wait that ran out after `timeout` ms.
|
|
23
|
+
*/
|
|
24
|
+
export function waitForPageEvent(emitter, event, matches, options, timeout, onTimeout) {
|
|
25
|
+
return new Promise((resolve, reject) => {
|
|
26
|
+
const done = () => {
|
|
27
|
+
clearTimeout(timer);
|
|
28
|
+
emitter.off(event, listener);
|
|
29
|
+
};
|
|
30
|
+
const listener = (item) => {
|
|
31
|
+
try {
|
|
32
|
+
if (!matches(item))
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
done();
|
|
37
|
+
reject(err);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
done();
|
|
41
|
+
resolve(item);
|
|
42
|
+
};
|
|
43
|
+
const timer = setTimeout(() => {
|
|
44
|
+
done();
|
|
45
|
+
reject(onTimeout(timeout));
|
|
46
|
+
}, timeout);
|
|
47
|
+
emitter.on(event, listener);
|
|
48
|
+
const { trigger } = options;
|
|
49
|
+
if (trigger) {
|
|
50
|
+
Promise.resolve()
|
|
51
|
+
.then(trigger)
|
|
52
|
+
.catch((err) => {
|
|
53
|
+
done();
|
|
54
|
+
reject(err);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import EventEmitter from "node:events";
|
|
2
|
-
import
|
|
2
|
+
import { emitSafely } from "./emit.js";
|
|
3
3
|
import { fromRemoteValue } from "../script/serialize.js";
|
|
4
4
|
/** How many entries a page keeps before the oldest are dropped */
|
|
5
5
|
const DEFAULT_MAX_ENTRIES = 1000;
|
|
@@ -157,13 +157,6 @@ export class PageLogs {
|
|
|
157
157
|
}
|
|
158
158
|
/** A listener that throws must not stop the recording or the other listeners */
|
|
159
159
|
emit(event, ...args) {
|
|
160
|
-
|
|
161
|
-
try {
|
|
162
|
-
listener(...args);
|
|
163
|
-
}
|
|
164
|
-
catch (err) {
|
|
165
|
-
logger.error("A %s listener threw", event, { err });
|
|
166
|
-
}
|
|
167
|
-
}
|
|
160
|
+
emitSafely(this.events, event, args);
|
|
168
161
|
}
|
|
169
162
|
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { BiDiConnector } from "../transport/bidi-connection.js";
|
|
2
|
+
import type { DocumentWatcher } from "./document-watcher.js";
|
|
3
|
+
import type { PageConfig, ReadinessState } from "./page-settings.js";
|
|
4
|
+
export type LoadState = "load" | "domcontentloaded" | "networkidle";
|
|
5
|
+
export interface NavigateOptions {
|
|
6
|
+
/** When navigateTo resolves. @default config `navigation.waitUntil`, then "complete" */
|
|
7
|
+
wait?: ReadinessState;
|
|
8
|
+
/** Time (ms) the navigation may take. @default config `navigation.timeout`, then 30000 */
|
|
9
|
+
timeout?: number;
|
|
10
|
+
/** Scheme added to a URL without one. @default "https" */
|
|
11
|
+
protocol?: "http" | "https";
|
|
12
|
+
}
|
|
13
|
+
/** What a navigation answers with: its id (null when the browser gave none) and the URL it ended on */
|
|
14
|
+
export type NavigationResult = {
|
|
15
|
+
navigation: string | null;
|
|
16
|
+
url: string;
|
|
17
|
+
};
|
|
18
|
+
/** What `PageNavigation` takes from the page it belongs to */
|
|
19
|
+
export interface NavigationDeps {
|
|
20
|
+
config: PageConfig;
|
|
21
|
+
documents: DocumentWatcher;
|
|
22
|
+
/** Throws when the page is closed */
|
|
23
|
+
assertOpen(): void;
|
|
24
|
+
/** The URL of the page now, for the text of a failed navigation */
|
|
25
|
+
url(): Promise<string>;
|
|
26
|
+
/** Waits for a quiet network, for the "networkidle" load state */
|
|
27
|
+
waitForNetworkIdle(options: {
|
|
28
|
+
timeout: number;
|
|
29
|
+
}): Promise<void>;
|
|
30
|
+
}
|
|
31
|
+
/** Navigating one page: `goto`, `navigateTo`, `reload` and the wait for a load state */
|
|
32
|
+
export declare class PageNavigation {
|
|
33
|
+
private connector;
|
|
34
|
+
private contextId;
|
|
35
|
+
private deps;
|
|
36
|
+
/** The navigation started by the last navigateTo; failures of it fail a wait */
|
|
37
|
+
private lastNavigation;
|
|
38
|
+
/** Base for relative `goto` URLs; falls back to the active run's baseURL */
|
|
39
|
+
private baseURL;
|
|
40
|
+
constructor(connector: BiDiConnector, contextId: string, deps: NavigationDeps);
|
|
41
|
+
/** Sets the base that relative `goto` URLs resolve against; `undefined` falls back to the run's baseURL */
|
|
42
|
+
setBaseURL(url: string | undefined): void;
|
|
43
|
+
goto(url: string, options?: NavigateOptions): Promise<NavigationResult>;
|
|
44
|
+
navigateTo(url: string, options?: NavigateOptions | ReadinessState, protocol?: "http" | "https"): Promise<NavigationResult>;
|
|
45
|
+
reload(options?: {
|
|
46
|
+
wait?: ReadinessState;
|
|
47
|
+
timeout?: number;
|
|
48
|
+
ignoreCache?: boolean;
|
|
49
|
+
}): Promise<NavigationResult>;
|
|
50
|
+
waitForLoadState(state?: LoadState, options?: {
|
|
51
|
+
timeout?: number;
|
|
52
|
+
}): Promise<void>;
|
|
53
|
+
/**
|
|
54
|
+
* Runs a navigation command and settles what waiting for it means: failures become NavigationErrors, and a
|
|
55
|
+
* navigation that did not wait for the load is watched so `waitForLoadState` can tell it from the old page.
|
|
56
|
+
*/
|
|
57
|
+
private navigation;
|
|
58
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { peekRunSettings } from "../config/run-settings.js";
|
|
2
|
+
import { BiDiError } from "../transport/bidi-error.js";
|
|
3
|
+
import { UnsupportedOperationError } from "../transport/unsupported.js";
|
|
4
|
+
import { WaitTimeoutError } from "../wait/wait-until.js";
|
|
5
|
+
import { LoadStateTimeoutError, NavigationError } from "./navigation-error.js";
|
|
6
|
+
/** Navigating one page: `goto`, `navigateTo`, `reload` and the wait for a load state */
|
|
7
|
+
export class PageNavigation {
|
|
8
|
+
constructor(connector, contextId, deps) {
|
|
9
|
+
this.connector = connector;
|
|
10
|
+
this.contextId = contextId;
|
|
11
|
+
this.deps = deps;
|
|
12
|
+
}
|
|
13
|
+
/** Sets the base that relative `goto` URLs resolve against; `undefined` falls back to the run's baseURL */
|
|
14
|
+
setBaseURL(url) {
|
|
15
|
+
this.baseURL = url;
|
|
16
|
+
}
|
|
17
|
+
async goto(url, options) {
|
|
18
|
+
if (!/^\.{0,2}\//.test(url))
|
|
19
|
+
return this.navigateTo(url, options);
|
|
20
|
+
const base = this.baseURL ?? peekRunSettings()?.baseURL;
|
|
21
|
+
if (!base) {
|
|
22
|
+
throw new Error(`goto(): "${url}" needs a baseURL; set one in samurai.config.ts or the environment`);
|
|
23
|
+
}
|
|
24
|
+
return this.navigateTo(new URL(url, base).href, options);
|
|
25
|
+
}
|
|
26
|
+
async navigateTo(url, options, protocol) {
|
|
27
|
+
const given = typeof options === "string" ? { wait: options } : (options ?? {});
|
|
28
|
+
const wait = await this.deps.config.navigationWait(given.wait);
|
|
29
|
+
const timeout = await this.deps.config.navigationTimeout(given.timeout);
|
|
30
|
+
// A scheme is letters followed by ":" not directly followed by a digit, so "localhost:3000" is a host
|
|
31
|
+
const hasScheme = /^[a-z][a-z\d+.-]*:(?!\d)/i.test(url);
|
|
32
|
+
const parsedURL = hasScheme
|
|
33
|
+
? url
|
|
34
|
+
: `${given.protocol ?? protocol ?? "https"}://${url}`;
|
|
35
|
+
this.deps.assertOpen();
|
|
36
|
+
return this.navigation("navigateTo", async () => parsedURL, wait, timeout, async (sendOptions) => {
|
|
37
|
+
const result = await this.connector.send("browsingContext.navigate", { context: this.contextId, url: parsedURL, wait }, sendOptions);
|
|
38
|
+
return {
|
|
39
|
+
navigation: result.navigation ?? null,
|
|
40
|
+
url: result.url ?? parsedURL,
|
|
41
|
+
};
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
async reload(options = {}) {
|
|
45
|
+
this.deps.assertOpen();
|
|
46
|
+
const wait = await this.deps.config.navigationWait(options.wait);
|
|
47
|
+
const timeout = await this.deps.config.navigationTimeout(options.timeout);
|
|
48
|
+
return this.navigation("reload", () => this.deps.url().catch(() => "the page"), wait, timeout, async (sendOptions) => {
|
|
49
|
+
try {
|
|
50
|
+
const result = await this.connector.send("browsingContext.reload", {
|
|
51
|
+
context: this.contextId,
|
|
52
|
+
wait,
|
|
53
|
+
...(options.ignoreCache && { ignoreCache: true }),
|
|
54
|
+
}, sendOptions);
|
|
55
|
+
return { navigation: result.navigation ?? null, url: result.url };
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
if (options.ignoreCache &&
|
|
59
|
+
err instanceof BiDiError &&
|
|
60
|
+
err.code === "unsupported operation") {
|
|
61
|
+
throw new UnsupportedOperationError("reload(): ignoreCache is not supported by the browser");
|
|
62
|
+
}
|
|
63
|
+
throw err;
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
async waitForLoadState(state = "load", options = {}) {
|
|
68
|
+
this.deps.assertOpen();
|
|
69
|
+
const timeout = await this.deps.config.navigationTimeout(options.timeout);
|
|
70
|
+
try {
|
|
71
|
+
await this.deps.documents.waitForState(state === "domcontentloaded" ? "domcontentloaded" : "load", timeout, this.lastNavigation);
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
if (err instanceof WaitTimeoutError)
|
|
75
|
+
throw new LoadStateTimeoutError(state, timeout);
|
|
76
|
+
throw err;
|
|
77
|
+
}
|
|
78
|
+
if (state === "networkidle")
|
|
79
|
+
await this.deps.waitForNetworkIdle({ timeout });
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Runs a navigation command and settles what waiting for it means: failures become NavigationErrors, and a
|
|
83
|
+
* navigation that did not wait for the load is watched so `waitForLoadState` can tell it from the old page.
|
|
84
|
+
*/
|
|
85
|
+
async navigation(operation, describe, wait, timeout, send) {
|
|
86
|
+
// A navigation that does not wait for the load needs watching, or waitForLoadState cannot tell it from the old page
|
|
87
|
+
if (wait !== "complete")
|
|
88
|
+
await this.deps.documents.watch();
|
|
89
|
+
try {
|
|
90
|
+
const result = await send({ timeout });
|
|
91
|
+
this.lastNavigation = result.navigation ?? undefined;
|
|
92
|
+
if (result.navigation && wait !== "complete") {
|
|
93
|
+
// Tracked from now on: the document still showing is the old one until this navigation commits
|
|
94
|
+
const progress = this.deps.documents.progressOf(result.navigation);
|
|
95
|
+
if (wait === "interactive")
|
|
96
|
+
progress.domContentLoaded = true;
|
|
97
|
+
}
|
|
98
|
+
return result;
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
if (err instanceof BiDiError) {
|
|
102
|
+
const reason = err.code === "timeout" ? `timeout after ${timeout}ms` : err.message;
|
|
103
|
+
throw new NavigationError(await describe(), reason, err.code, operation);
|
|
104
|
+
}
|
|
105
|
+
throw err;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type EventEmitter from "node:events";
|
|
2
|
+
import { NetworkTracker } from "../network/network-tracker.js";
|
|
3
|
+
import type { NetworkRequest } from "../network/network-tracker.js";
|
|
4
|
+
import { type NetworkMatch } from "../network/network-wait.js";
|
|
5
|
+
import { Response } from "../network/response.js";
|
|
6
|
+
import { type RouteHandler } from "../network/router.js";
|
|
7
|
+
import type { UrlPattern } from "../network/url-match.js";
|
|
8
|
+
import type { BiDiConnector } from "../transport/bidi-connection.js";
|
|
9
|
+
import type { BrowsingContext } from "../types/bidi-modules/browsing-context.js";
|
|
10
|
+
import type { TreeSource } from "./dialog-handling.js";
|
|
11
|
+
import type { PageEventName, PageEvents, WaitOptions } from "./page-events.js";
|
|
12
|
+
import type { PageConfig } from "./page-settings.js";
|
|
13
|
+
/** What `PageNetwork` takes from the page around it */
|
|
14
|
+
export interface PageNetworkDeps {
|
|
15
|
+
config: PageConfig;
|
|
16
|
+
tree: TreeSource;
|
|
17
|
+
/** Where `request`, `response` and `requestfailed` are emitted */
|
|
18
|
+
emitter: EventEmitter;
|
|
19
|
+
/** Resolves with the first event of a kind that `matches` accepts (the page's `waitForPageEvent`) */
|
|
20
|
+
waitForEvent: <E extends PageEventName>(event: E, matches: (item: PageEvents[E][0]) => boolean, options: WaitOptions, onTimeout: (timeout: number) => Error) => Promise<PageEvents[E][0]>;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Everything a page does with its network: request tracking and idle waits, response bodies, waiting for a
|
|
24
|
+
* request or response, the HTTP cache switch and the routes (interception).
|
|
25
|
+
*/
|
|
26
|
+
export declare class PageNetwork {
|
|
27
|
+
private connector;
|
|
28
|
+
private contextId;
|
|
29
|
+
private deps;
|
|
30
|
+
private lazyTracker;
|
|
31
|
+
private lazyCollector;
|
|
32
|
+
private lazyRouter;
|
|
33
|
+
private responses;
|
|
34
|
+
constructor(connector: BiDiConnector, contextId: BrowsingContext, deps: PageNetworkDeps);
|
|
35
|
+
/** The network tracker, created on first use. Rejects with NetworkTrackingDisabledError when config turned tracking off */
|
|
36
|
+
tracker(): Promise<NetworkTracker>;
|
|
37
|
+
/** Starts following requests now; does nothing when config `network.track` is false */
|
|
38
|
+
startTracking(): Promise<void>;
|
|
39
|
+
/** Waits until no request has been in flight for `idleTime` ms (default config `network.idleTime`, then 500) */
|
|
40
|
+
waitForIdle(options?: {
|
|
41
|
+
idleTime?: number;
|
|
42
|
+
timeout?: number;
|
|
43
|
+
}): Promise<void>;
|
|
44
|
+
/** Starts keeping response bodies, which is what makes `response.body()` possible */
|
|
45
|
+
startBodyCollection(): Promise<void>;
|
|
46
|
+
private openCollector;
|
|
47
|
+
/** One Response per network response, whoever asks, so a body is read from the browser once */
|
|
48
|
+
private responseFor;
|
|
49
|
+
private loadBody;
|
|
50
|
+
/** Resolves with the next response whose URL matches (a pattern, see `route`) or that the function accepts */
|
|
51
|
+
waitForResponse(match: NetworkMatch<Response>, options?: WaitOptions): Promise<Response>;
|
|
52
|
+
/** Like `waitForResponse`, for requests */
|
|
53
|
+
waitForRequest(match: NetworkMatch<NetworkRequest>, options?: WaitOptions): Promise<NetworkRequest>;
|
|
54
|
+
private waitForNetworkEvent;
|
|
55
|
+
/** Turns the browser's HTTP cache off (`true`) or back on for this page: every request then goes to the server */
|
|
56
|
+
setCacheDisabled(disabled: boolean): Promise<void>;
|
|
57
|
+
private router;
|
|
58
|
+
/** Sends requests whose URL matches to `handler` (see `Page.route`) */
|
|
59
|
+
route(url: UrlPattern, handler: RouteHandler): Promise<void>;
|
|
60
|
+
/** Removes the routes for `url` (only the one with `handler`, when given) */
|
|
61
|
+
unroute(url: UrlPattern, handler?: RouteHandler): Promise<void>;
|
|
62
|
+
unrouteAll(): Promise<void>;
|
|
63
|
+
/** Errors thrown by route handlers so far */
|
|
64
|
+
routeErrors(): readonly Error[];
|
|
65
|
+
/** The first half of releasing the network: the tracker goes before the tree it reads */
|
|
66
|
+
disposeTracker(): Promise<void>;
|
|
67
|
+
/** The second half: the router, then the body collector */
|
|
68
|
+
disposeRest(): (Promise<void> | undefined)[];
|
|
69
|
+
}
|