browser-debugger-cli 0.8.0 → 0.9.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 +4 -1
- package/dist/cdp/schema.d.ts +4 -1
- package/dist/cdp/schema.js +48 -7
- package/dist/commands/cdp.js +3 -2
- package/dist/commands/cleanup.d.ts +11 -0
- package/dist/commands/cleanup.js +161 -57
- package/dist/commands/console.d.ts +20 -1
- package/dist/commands/console.js +57 -17
- package/dist/commands/details.js +3 -2
- package/dist/commands/dom/DomElementResolver.d.ts +10 -3
- package/dist/commands/dom/DomElementResolver.js +35 -17
- package/dist/commands/dom/a11y.d.ts +10 -0
- package/dist/commands/dom/a11y.js +27 -5
- package/dist/commands/dom/eval.d.ts +3 -1
- package/dist/commands/dom/eval.js +29 -4
- package/dist/commands/dom/form.js +16 -62
- package/dist/commands/dom/formInteraction.js +152 -113
- package/dist/commands/dom/formSummary.d.ts +49 -0
- package/dist/commands/dom/formSummary.js +180 -0
- package/dist/commands/dom/frames.d.ts +2 -1
- package/dist/commands/dom/frames.js +17 -2
- package/dist/commands/dom/get.d.ts +6 -5
- package/dist/commands/dom/get.js +92 -82
- package/dist/commands/dom/helpers/index.d.ts +1 -1
- package/dist/commands/dom/helpers/index.js +1 -1
- package/dist/commands/dom/helpers/query.d.ts +44 -17
- package/dist/commands/dom/helpers/query.js +244 -97
- package/dist/commands/dom/helpers/runElementCommand.d.ts +10 -2
- package/dist/commands/dom/helpers/runElementCommand.js +97 -30
- package/dist/commands/dom/helpers/screenshot.d.ts +4 -1
- package/dist/commands/dom/helpers/screenshot.js +164 -49
- package/dist/commands/dom/index.d.ts +3 -1
- package/dist/commands/dom/index.js +16 -6
- package/dist/commands/dom/layout.d.ts +14 -0
- package/dist/commands/dom/layout.js +54 -0
- package/dist/commands/dom/listeners.d.ts +5 -1
- package/dist/commands/dom/listeners.js +13 -3
- package/dist/commands/dom/query.js +2 -3
- package/dist/commands/dom/screenshot.d.ts +12 -2
- package/dist/commands/dom/screenshot.js +27 -3
- package/dist/commands/dom/semanticUtils.d.ts +6 -13
- package/dist/commands/dom/semanticUtils.js +15 -19
- package/dist/commands/dom/wait.d.ts +13 -0
- package/dist/commands/dom/wait.js +83 -0
- package/dist/commands/helpJson.js +2 -2
- package/dist/commands/network/list.js +4 -11
- package/dist/commands/optionBehaviors.js +112 -21
- package/dist/commands/page.d.ts +2 -1
- package/dist/commands/page.js +41 -5
- package/dist/commands/peek.js +4 -11
- package/dist/commands/sessions.d.ts +8 -0
- package/dist/commands/sessions.js +19 -0
- package/dist/commands/shared/CommandRunner.js +4 -4
- package/dist/commands/shared/dataFetcher.js +2 -2
- package/dist/commands/shared/followMode.d.ts +21 -1
- package/dist/commands/shared/followMode.js +29 -2
- package/dist/commands/shared/handleValidationError.d.ts +2 -2
- package/dist/commands/shared/handleValidationError.js +12 -3
- package/dist/commands/shared/optionTypes.d.ts +40 -5
- package/dist/commands/shared/startHelpers.js +12 -3
- package/dist/commands/shared/validation.d.ts +3 -2
- package/dist/commands/shared/validation.js +4 -3
- package/dist/commands/start.d.ts +63 -0
- package/dist/commands/start.js +115 -15
- package/dist/commands/status.js +29 -7
- package/dist/commands/stop.js +7 -6
- package/dist/commands/tail.js +4 -11
- package/dist/commands/types.d.ts +2 -0
- package/dist/commands.js +2 -0
- package/dist/connection/chromeIdentity.d.ts +65 -0
- package/dist/connection/chromeIdentity.js +143 -0
- package/dist/connection/launcher/profilePreferences.d.ts +47 -0
- package/dist/connection/launcher/profilePreferences.js +151 -0
- package/dist/connection/launcher.d.ts +21 -2
- package/dist/connection/launcher.js +42 -16
- package/dist/connection/portReservation.d.ts +14 -4
- package/dist/connection/portReservation.js +21 -6
- package/dist/connection/startupExit.d.ts +8 -0
- package/dist/connection/startupExit.js +15 -6
- package/dist/constants.d.ts +6 -2
- package/dist/constants.js +9 -2
- package/dist/daemon/SessionController.js +23 -7
- package/dist/daemon/errors.d.ts +1 -1
- package/dist/daemon/errors.js +1 -1
- package/dist/daemon/launcher.d.ts +2 -1
- package/dist/daemon/launcher.js +5 -6
- package/dist/daemon/server/SocketServer.js +1 -2
- package/dist/daemon/session/Session.d.ts +13 -0
- package/dist/daemon/session/Session.js +57 -8
- package/dist/daemon/session/chromeConnection.d.ts +9 -0
- package/dist/daemon/session/chromeConnection.js +45 -8
- package/dist/daemon/session/commandRegistry.js +52 -62
- package/dist/daemon/session/interactions.d.ts +35 -9
- package/dist/daemon/session/interactions.js +36 -9
- package/dist/daemon/session/triggeredRequests.d.ts +67 -0
- package/dist/daemon/session/triggeredRequests.js +157 -0
- package/dist/daemon/session/types.d.ts +5 -1
- package/dist/daemon.js +5393 -1600
- package/dist/errors/messages.d.ts +387 -24
- package/dist/errors/messages.js +761 -67
- package/dist/index.js +3976 -1558
- package/dist/ipc/client.d.ts +12 -1
- package/dist/ipc/client.js +22 -3
- package/dist/ipc/protocol/commands.d.ts +89 -4
- package/dist/ipc/protocol/commands.js +2 -0
- package/dist/ipc/protocol/domTypes.d.ts +258 -7
- package/dist/ipc/session/lifecycle.d.ts +8 -1
- package/dist/ipc/session/queries.d.ts +5 -1
- package/dist/ipc/session/types.d.ts +5 -0
- package/dist/ipc/transport/index.d.ts +2 -1
- package/dist/ipc/transport/index.js +2 -2
- package/dist/runtime/dom/actionEffects.d.ts +106 -0
- package/dist/runtime/dom/actionEffects.js +256 -0
- package/dist/runtime/dom/actionEffectsScripts.d.ts +52 -0
- package/dist/runtime/dom/actionEffectsScripts.js +234 -0
- package/dist/runtime/dom/elementGeometry.d.ts +170 -0
- package/dist/runtime/dom/elementGeometry.js +553 -0
- package/dist/runtime/dom/elementInfo.d.ts +77 -0
- package/dist/runtime/dom/elementInfo.js +191 -0
- package/dist/runtime/dom/evalHelpers.d.ts +51 -6
- package/dist/runtime/dom/evalHelpers.js +136 -26
- package/dist/runtime/dom/eventListeners.d.ts +2 -1
- package/dist/runtime/dom/eventListeners.js +174 -47
- package/dist/runtime/dom/formDiscovery.d.ts +1 -1
- package/dist/runtime/dom/formDiscovery.js +116 -16
- package/dist/runtime/dom/formFillHelpers/fill.d.ts +10 -0
- package/dist/runtime/dom/formFillHelpers/fill.js +125 -10
- package/dist/runtime/dom/formFillHelpers/index.d.ts +2 -2
- package/dist/runtime/dom/formFillHelpers/index.js +2 -2
- package/dist/runtime/dom/formFillHelpers/pressKey.js +14 -3
- package/dist/runtime/dom/formFillHelpers/scroll.d.ts +3 -0
- package/dist/runtime/dom/formFillHelpers/scroll.js +60 -18
- package/dist/runtime/dom/formFillHelpers/shared.d.ts +17 -0
- package/dist/runtime/dom/formFillHelpers/shared.js +25 -1
- package/dist/runtime/dom/formFillHelpers/stability.d.ts +20 -6
- package/dist/runtime/dom/formFillHelpers/stability.js +50 -19
- package/dist/runtime/dom/formSubmitHelpers.d.ts +3 -0
- package/dist/runtime/dom/formSubmitHelpers.js +89 -15
- package/dist/runtime/dom/frameLayout.d.ts +60 -0
- package/dist/runtime/dom/frameLayout.js +140 -0
- package/dist/runtime/dom/frameOrigin.d.ts +50 -0
- package/dist/runtime/dom/frameOrigin.js +62 -0
- package/dist/runtime/dom/frameScopedConnection.d.ts +92 -0
- package/dist/runtime/dom/frameScopedConnection.js +252 -0
- package/dist/runtime/dom/frameSelection.d.ts +1 -1
- package/dist/runtime/dom/frameSelection.js +2 -2
- package/dist/runtime/dom/frames.d.ts +25 -2
- package/dist/runtime/dom/frames.js +202 -63
- package/dist/runtime/dom/layout.d.ts +67 -0
- package/dist/runtime/dom/layout.js +333 -0
- package/dist/runtime/dom/listenerPageScripts.d.ts +66 -0
- package/dist/runtime/dom/listenerPageScripts.js +279 -0
- package/dist/runtime/dom/listenerSummary.d.ts +132 -11
- package/dist/runtime/dom/listenerSummary.js +344 -22
- package/dist/runtime/dom/pageActivity.d.ts +41 -0
- package/dist/runtime/dom/pageActivity.js +123 -0
- package/dist/runtime/dom/reactEventHelpers.d.ts +58 -2
- package/dist/runtime/dom/reactEventHelpers.js +212 -41
- package/dist/runtime/dom/targetNode.d.ts +80 -27
- package/dist/runtime/dom/targetNode.js +249 -33
- package/dist/runtime/dom/wait.d.ts +25 -0
- package/dist/runtime/dom/wait.js +199 -0
- package/dist/runtime/dom/waitCondition.d.ts +71 -0
- package/dist/runtime/dom/waitCondition.js +75 -0
- package/dist/runtime/page/emulation.d.ts +51 -0
- package/dist/runtime/page/emulation.js +80 -0
- package/dist/runtime/page/loadingState.d.ts +36 -0
- package/dist/runtime/page/loadingState.js +86 -0
- package/dist/runtime/page/navigation.d.ts +46 -2
- package/dist/runtime/page/navigation.js +69 -33
- package/dist/session/QueryCacheManager.d.ts +11 -1
- package/dist/session/QueryCacheManager.js +25 -3
- package/dist/session/chromeOwners.d.ts +34 -0
- package/dist/session/chromeOwners.js +51 -0
- package/dist/session/cleanup/staleSession.d.ts +11 -1
- package/dist/session/cleanup/staleSession.js +17 -6
- package/dist/session/cleanup/userCommands.js +2 -4
- package/dist/session/metadata.d.ts +5 -1
- package/dist/session/metadata.js +2 -1
- package/dist/session/paths.d.ts +77 -3
- package/dist/session/paths.js +111 -5
- package/dist/session/port.d.ts +31 -7
- package/dist/session/port.js +50 -43
- package/dist/session/portClaims.d.ts +66 -0
- package/dist/session/portClaims.js +284 -0
- package/dist/session/sessionList.d.ts +58 -0
- package/dist/session/sessionList.js +199 -0
- package/dist/session/sessionName.d.ts +46 -0
- package/dist/session/sessionName.js +97 -0
- package/dist/telemetry/a11y.d.ts +8 -3
- package/dist/telemetry/a11y.js +92 -28
- package/dist/telemetry/requestKinds.d.ts +32 -0
- package/dist/telemetry/requestKinds.js +61 -0
- package/dist/telemetry/requestState.d.ts +31 -0
- package/dist/telemetry/requestState.js +38 -0
- package/dist/types.d.ts +80 -3
- package/dist/ui/formatters/a11y.js +3 -0
- package/dist/ui/formatters/console/chronological.d.ts +8 -0
- package/dist/ui/formatters/console/chronological.js +17 -4
- package/dist/ui/formatters/console/json.js +3 -4
- package/dist/ui/formatters/console/shared.d.ts +12 -0
- package/dist/ui/formatters/console.d.ts +2 -2
- package/dist/ui/formatters/console.js +1 -1
- package/dist/ui/formatters/details.js +2 -1
- package/dist/ui/formatters/dom.d.ts +26 -14
- package/dist/ui/formatters/dom.js +65 -52
- package/dist/ui/formatters/form.js +29 -18
- package/dist/ui/formatters/layout.d.ts +31 -0
- package/dist/ui/formatters/layout.js +53 -0
- package/dist/ui/formatters/listeners.d.ts +3 -2
- package/dist/ui/formatters/listeners.js +73 -9
- package/dist/ui/formatters/networkHeaders.js +13 -0
- package/dist/ui/formatters/preview.js +2 -1
- package/dist/ui/formatters/requestStatus.d.ts +1 -17
- package/dist/ui/formatters/requestStatus.js +2 -30
- package/dist/ui/formatters/sessions.d.ts +12 -0
- package/dist/ui/formatters/sessions.js +40 -0
- package/dist/ui/formatters/status.d.ts +21 -2
- package/dist/ui/formatters/status.js +47 -10
- package/dist/ui/formatters/triggeredRequests.d.ts +36 -0
- package/dist/ui/formatters/triggeredRequests.js +65 -0
- package/dist/ui/formatting.d.ts +10 -0
- package/dist/ui/formatting.js +28 -36
- package/dist/ui/messages/chrome.d.ts +9 -0
- package/dist/ui/messages/chrome.js +17 -5
- package/dist/ui/messages/commands.d.ts +388 -14
- package/dist/ui/messages/commands.js +664 -21
- package/dist/ui/messages/consoleMessages.d.ts +10 -0
- package/dist/ui/messages/consoleMessages.js +17 -0
- package/dist/ui/messages/hints.js +2 -1
- package/dist/ui/messages/preview.js +5 -4
- package/dist/ui/messages/session.d.ts +16 -21
- package/dist/ui/messages/session.js +28 -26
- package/dist/ui/messages/sessionCommand.d.ts +43 -0
- package/dist/ui/messages/sessionCommand.js +52 -0
- package/dist/utils/async.d.ts +8 -0
- package/dist/utils/async.js +19 -0
- package/dist/utils/http.d.ts +22 -1
- package/dist/utils/http.js +28 -9
- package/dist/utils/selectorFilters.d.ts +36 -8
- package/dist/utils/selectorFilters.js +267 -53
- package/dist/utils/shellDetection.d.ts +8 -2
- package/dist/utils/shellDetection.js +120 -33
- package/dist/utils/suggestions.d.ts +26 -0
- package/dist/utils/suggestions.js +73 -0
- package/dist/utils/taskMappings.js +10 -0
- package/dist/utils/url.d.ts +12 -2
- package/dist/utils/url.js +69 -7
- package/package.json +1 -1
- package/dist/ui/formatters/sessionFormatters.d.ts +0 -58
- package/dist/ui/formatters/sessionFormatters.js +0 -121
package/dist/daemon/launcher.js
CHANGED
|
@@ -12,7 +12,7 @@ import { join } from 'path';
|
|
|
12
12
|
import { DaemonStartupError, SessionDirError } from './errors.js';
|
|
13
13
|
import { sessionDirIsFileError, sessionDirNotWritableError, socketPathTooLongError, } from '../errors/messages.js';
|
|
14
14
|
import { isDaemonAlive } from '../session/daemonSocket.js';
|
|
15
|
-
import { ensureSessionDir, getSessionDir, getSessionFilePath } from '../session/paths.js';
|
|
15
|
+
import { MAX_DAEMON_SOCKET_PATH_BYTES, ensureSessionDir, getSessionDir, getSessionFilePath, } from '../session/paths.js';
|
|
16
16
|
import { createLogger } from '../ui/logging/index.js';
|
|
17
17
|
import { delay } from '../utils/async.js';
|
|
18
18
|
import { getErrorMessage } from '../utils/errors.js';
|
|
@@ -52,13 +52,12 @@ export async function launchDaemon() {
|
|
|
52
52
|
daemon.unref();
|
|
53
53
|
await waitForDaemonReady(() => exited);
|
|
54
54
|
}
|
|
55
|
-
/** Longest Unix socket path (sun_path is 104 bytes on macOS, 108 on Linux, NUL included) */
|
|
56
|
-
const MAX_SOCKET_PATH = process.platform === 'darwin' ? 103 : 107;
|
|
57
55
|
/**
|
|
58
56
|
* Check that the session directory can hold the daemon's files before
|
|
59
57
|
* spawning it (otherwise the daemon dies and only its log says why).
|
|
60
58
|
*
|
|
61
|
-
* @throws SessionDirError (103) for a file
|
|
59
|
+
* @throws SessionDirError (103) for a file, (81) for a too-long path like a
|
|
60
|
+
* named session's, (82) when not writable
|
|
62
61
|
*/
|
|
63
62
|
export function assertUsableSessionDir() {
|
|
64
63
|
const dir = getSessionDir();
|
|
@@ -68,8 +67,8 @@ export function assertUsableSessionDir() {
|
|
|
68
67
|
if (fs.existsSync(dir) && !fs.statSync(dir).isDirectory())
|
|
69
68
|
fail(sessionDirIsFileError(dir));
|
|
70
69
|
const socketPath = getSessionFilePath('DAEMON_SOCKET');
|
|
71
|
-
if (Buffer.byteLength(socketPath) >
|
|
72
|
-
fail(socketPathTooLongError(socketPath,
|
|
70
|
+
if (Buffer.byteLength(socketPath) > MAX_DAEMON_SOCKET_PATH_BYTES) {
|
|
71
|
+
fail(socketPathTooLongError(socketPath, MAX_DAEMON_SOCKET_PATH_BYTES), EXIT_CODES.INVALID_ARGUMENTS);
|
|
73
72
|
}
|
|
74
73
|
try {
|
|
75
74
|
ensureSessionDir();
|
|
@@ -2,11 +2,10 @@ import { linkSync, lstatSync, unlinkSync } from 'fs';
|
|
|
2
2
|
import { createServer } from 'net';
|
|
3
3
|
import { DaemonError } from '../errors.js';
|
|
4
4
|
import { probeDaemonSocket } from '../../session/daemonSocket.js';
|
|
5
|
+
import { MAX_SOCKET_PATH_BYTES } from '../../session/paths.js';
|
|
5
6
|
import { createLogger } from '../../ui/logging/index.js';
|
|
6
7
|
import { getErrorMessage } from '../../utils/errors.js';
|
|
7
8
|
import { EXIT_CODES } from '../../utils/exitCodes.js';
|
|
8
|
-
/** Unix socket path limit (sun_path is 104 bytes on macOS incl. NUL; 108 on Linux). */
|
|
9
|
-
const MAX_SOCKET_PATH_BYTES = 103;
|
|
10
9
|
/** Error code thrown when another daemon already owns the socket path. */
|
|
11
10
|
export const DAEMON_ALREADY_RUNNING_CODE = 'DAEMON_ALREADY_RUNNING';
|
|
12
11
|
/**
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* separate worker process: the daemon process *is* the session.
|
|
7
7
|
*/
|
|
8
8
|
import type { CommandName, CommandSchemas } from '../../ipc/index.js';
|
|
9
|
+
import type { PageLoadingState } from '../../ipc/protocol/commands.js';
|
|
9
10
|
import type { SessionOptions } from '../../ipc/session/lifecycle.js';
|
|
10
11
|
import type { StatusResponseData } from '../../ipc/session/queries.js';
|
|
11
12
|
/** Why a session ended. */
|
|
@@ -18,6 +19,8 @@ export interface SessionInfo {
|
|
|
18
19
|
targetTitle?: string;
|
|
19
20
|
/** HTTP status of the page's main document, when known */
|
|
20
21
|
documentStatus?: number;
|
|
22
|
+
/** The page had not finished loading when the readiness wait ended */
|
|
23
|
+
loading?: PageLoadingState;
|
|
21
24
|
}
|
|
22
25
|
/** Session metadata as reported in status responses. */
|
|
23
26
|
export type SessionStatusMetadata = NonNullable<StatusResponseData['sessionMetadata']>;
|
|
@@ -50,6 +53,7 @@ export declare class Session {
|
|
|
50
53
|
private readonly launchAbort;
|
|
51
54
|
private started;
|
|
52
55
|
private documentRequestId;
|
|
56
|
+
private loading;
|
|
53
57
|
private constructor();
|
|
54
58
|
/**
|
|
55
59
|
* Validate the request and create a session that has not been launched yet.
|
|
@@ -115,6 +119,15 @@ export declare class Session {
|
|
|
115
119
|
* Acquire Chrome, CDP and collectors, checking for a concurrent stop after each step.
|
|
116
120
|
*/
|
|
117
121
|
private acquireResources;
|
|
122
|
+
/**
|
|
123
|
+
* Choose the port and launch Chrome on it.
|
|
124
|
+
*
|
|
125
|
+
* A Chrome left by this session's crashed daemon is reaped first, so the
|
|
126
|
+
* session gets its remembered port back. If another process took an
|
|
127
|
+
* automatically chosen port before Chrome could listen on it, another port
|
|
128
|
+
* is chosen (the taken one now answers, so it is skipped).
|
|
129
|
+
*/
|
|
130
|
+
private launchOwnChrome;
|
|
118
131
|
/**
|
|
119
132
|
* End the session after its page connection was lost. A launched Chrome
|
|
120
133
|
* still running a moment later means the tab was closed (e.g.
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* telemetry collectors and executes session commands directly. There is no
|
|
6
6
|
* separate worker process: the daemon process *is* the session.
|
|
7
7
|
*/
|
|
8
|
+
import { ChromeLaunchError } from '../../connection/errors.js';
|
|
8
9
|
import { TelemetryStore } from './TelemetryStore.js';
|
|
9
10
|
import { connectCDP, navigateToTarget } from './cdpSetup.js';
|
|
10
11
|
import { externalChromePort, findPageTarget, setupChromeConnection, } from './chromeConnection.js';
|
|
@@ -12,6 +13,8 @@ import { startTelemetryCollectors } from './collectors.js';
|
|
|
12
13
|
import { createCommandRegistry } from './commandRegistry.js';
|
|
13
14
|
import { teardownSession } from './teardown.js';
|
|
14
15
|
import { CommandError } from '../../errors/index.js';
|
|
16
|
+
import { applySessionEmulation } from '../../runtime/page/emulation.js';
|
|
17
|
+
import { readPageLoadingState } from '../../runtime/page/loadingState.js';
|
|
15
18
|
import { reapOrphanedChrome, removeSessionFiles } from '../../session/cleanup/staleSession.js';
|
|
16
19
|
import { writeSessionMetadata } from '../../session/metadata.js';
|
|
17
20
|
import { getSessionPort } from '../../session/port.js';
|
|
@@ -25,6 +28,17 @@ import { validateUrl } from '../../utils/url.js';
|
|
|
25
28
|
const log = createLogger('session');
|
|
26
29
|
/** How long Chrome gets to exit after the page connection drops, before the end is called a closed tab */
|
|
27
30
|
const CRASH_SETTLE_MS = 500;
|
|
31
|
+
/** Ports tried when automatically chosen ports turn out to be taken */
|
|
32
|
+
const PORT_ATTEMPTS = 3;
|
|
33
|
+
/**
|
|
34
|
+
* Whether a launch failed because another process holds the port.
|
|
35
|
+
*
|
|
36
|
+
* @param error - Launch error
|
|
37
|
+
* @returns True for a PORT_IN_USE launch error
|
|
38
|
+
*/
|
|
39
|
+
function isPortConflict(error) {
|
|
40
|
+
return error instanceof ChromeLaunchError && error.issue?.code === 'PORT_IN_USE';
|
|
41
|
+
}
|
|
28
42
|
/**
|
|
29
43
|
* A launch that failed because the session was stopped while starting.
|
|
30
44
|
*/
|
|
@@ -56,6 +70,7 @@ export class Session {
|
|
|
56
70
|
launchAbort = new AbortController();
|
|
57
71
|
started = false;
|
|
58
72
|
documentRequestId;
|
|
73
|
+
loading;
|
|
59
74
|
constructor(config, onEnded) {
|
|
60
75
|
this.config = config;
|
|
61
76
|
this.onEnded = onEnded;
|
|
@@ -134,6 +149,7 @@ export class Session {
|
|
|
134
149
|
targetUrl: target?.url ?? this.config.url,
|
|
135
150
|
...(target?.title && { targetTitle: target.title }),
|
|
136
151
|
...(documentStatus !== undefined && { documentStatus }),
|
|
152
|
+
...(this.loading && { loading: this.loading }),
|
|
137
153
|
};
|
|
138
154
|
}
|
|
139
155
|
/**
|
|
@@ -153,6 +169,8 @@ export class Session {
|
|
|
153
169
|
autoStopAt: this.autoStopAt,
|
|
154
170
|
targetId: target?.id,
|
|
155
171
|
webSocketDebuggerUrl: target?.webSocketDebuggerUrl,
|
|
172
|
+
viewport: this.config.viewport,
|
|
173
|
+
colorScheme: this.config.colorScheme,
|
|
156
174
|
}),
|
|
157
175
|
};
|
|
158
176
|
}
|
|
@@ -194,15 +212,14 @@ export class Session {
|
|
|
194
212
|
async acquireResources() {
|
|
195
213
|
this.store.resetSessionStart();
|
|
196
214
|
const { chromeWsUrl } = this.config;
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
await
|
|
215
|
+
if (chromeWsUrl) {
|
|
216
|
+
this.config = { ...this.config, port: externalChromePort(chromeWsUrl) };
|
|
217
|
+
this.throwIfStopping();
|
|
218
|
+
this.chrome = await setupChromeConnection(this.config, this.store, log, this.notify);
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
await this.launchOwnChrome();
|
|
204
222
|
}
|
|
205
|
-
this.chrome = await setupChromeConnection(this.config, this.store, log, this.notify);
|
|
206
223
|
this.throwIfStopping();
|
|
207
224
|
if (this.chrome) {
|
|
208
225
|
await findPageTarget(this.config, this.store, log);
|
|
@@ -213,10 +230,40 @@ export class Session {
|
|
|
213
230
|
signal: this.launchAbort.signal,
|
|
214
231
|
});
|
|
215
232
|
this.throwIfStopping();
|
|
233
|
+
await applySessionEmulation(this.cdp, this.config);
|
|
234
|
+
this.throwIfStopping();
|
|
216
235
|
this.cleanupFunctions = await startTelemetryCollectors(this.cdp, this.config, this.store, log);
|
|
217
236
|
this.throwIfStopping();
|
|
218
237
|
this.documentRequestId = await navigateToTarget(this.cdp, this.config, this.store, this.chrome, log);
|
|
219
238
|
this.throwIfStopping();
|
|
239
|
+
this.loading = await readPageLoadingState(this.cdp, this.store.pendingNetworkRequests.values());
|
|
240
|
+
this.throwIfStopping();
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Choose the port and launch Chrome on it.
|
|
244
|
+
*
|
|
245
|
+
* A Chrome left by this session's crashed daemon is reaped first, so the
|
|
246
|
+
* session gets its remembered port back. If another process took an
|
|
247
|
+
* automatically chosen port before Chrome could listen on it, another port
|
|
248
|
+
* is chosen (the taken one now answers, so it is skipped).
|
|
249
|
+
*/
|
|
250
|
+
async launchOwnChrome() {
|
|
251
|
+
await reapOrphanedChrome();
|
|
252
|
+
const explicitPort = this.config.port || undefined;
|
|
253
|
+
for (let attempt = 1;; attempt++) {
|
|
254
|
+
this.config = { ...this.config, port: await getSessionPort(explicitPort) };
|
|
255
|
+
this.throwIfStopping();
|
|
256
|
+
try {
|
|
257
|
+
this.chrome = await setupChromeConnection(this.config, this.store, log, this.notify);
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
catch (error) {
|
|
261
|
+
const retry = !explicitPort && attempt < PORT_ATTEMPTS && isPortConflict(error);
|
|
262
|
+
if (!retry)
|
|
263
|
+
throw error;
|
|
264
|
+
log.info(`Port ${this.config.port} was taken by another process; choosing another`);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
220
267
|
}
|
|
221
268
|
/**
|
|
222
269
|
* End the session after its page connection was lost. A launched Chrome
|
|
@@ -282,6 +329,8 @@ function buildConfig(url, port, options) {
|
|
|
282
329
|
maxBodySize: options.maxBodySize,
|
|
283
330
|
chromeWsUrl: options.chromeWsUrl,
|
|
284
331
|
chromeFlags: options.chromeFlags,
|
|
332
|
+
viewport: options.viewport,
|
|
333
|
+
colorScheme: options.colorScheme,
|
|
285
334
|
}),
|
|
286
335
|
};
|
|
287
336
|
}
|
|
@@ -22,6 +22,15 @@ export declare function setupChromeConnection(config: SessionConfig, telemetrySt
|
|
|
22
22
|
* @returns Port in the URL, or the protocol default
|
|
23
23
|
*/
|
|
24
24
|
export declare function externalChromePort(wsUrl: string): number;
|
|
25
|
+
/**
|
|
26
|
+
* Chrome flags of a launched Chrome: with `--viewport`, a window of that size
|
|
27
|
+
* first (so tabs the page opens get it too), then the user's own flags, which
|
|
28
|
+
* win over it.
|
|
29
|
+
*
|
|
30
|
+
* @param config - Session configuration
|
|
31
|
+
* @returns Flags, or undefined without any
|
|
32
|
+
*/
|
|
33
|
+
export declare function windowSizeFlags(config: SessionConfig): string[] | undefined;
|
|
25
34
|
/**
|
|
26
35
|
* Find the page target of a Chrome that bdg launched.
|
|
27
36
|
*
|
|
@@ -9,11 +9,12 @@ import { launchChrome } from '../../connection/launcher.js';
|
|
|
9
9
|
import { HTTP_LOCALHOST } from '../../constants.js';
|
|
10
10
|
import { ConfigError } from '../errors.js';
|
|
11
11
|
import { CommandError } from '../../errors/index.js';
|
|
12
|
-
import { externalBrowserIdMismatchError, externalChromeUnreachableError, externalPageNotFoundError, } from '../../errors/messages.js';
|
|
12
|
+
import { chromeInUseBySessionError, externalBrowserIdMismatchError, externalChromeUnreachableError, externalPageNotFoundError, notDevToolsEndpointError, } from '../../errors/messages.js';
|
|
13
13
|
import { writeChromePid } from '../../session/chrome.js';
|
|
14
|
+
import { findConflictingOwner } from '../../session/chromeOwners.js';
|
|
14
15
|
import { getSessionDir } from '../../session/paths.js';
|
|
15
16
|
import { EXIT_CODES } from '../../utils/exitCodes.js';
|
|
16
|
-
import { createPageTarget,
|
|
17
|
+
import { createPageTarget, fetchCDPTargets, probeDevToolsEndpoint } from '../../utils/http.js';
|
|
17
18
|
import { filterDefined } from '../../utils/objects.js';
|
|
18
19
|
/**
|
|
19
20
|
* Setup Chrome connection - either launch new instance or connect to existing.
|
|
@@ -71,23 +72,30 @@ async function resolveExternalTarget(wsUrl, config, log) {
|
|
|
71
72
|
const { hostname, pathname, protocol, port } = new URL(wsUrl);
|
|
72
73
|
const http = { host: hostname, secure: protocol === 'wss:' };
|
|
73
74
|
const endpoint = `${http.secure ? 'https' : 'http'}://${hostname}:${config.port}`;
|
|
74
|
-
const
|
|
75
|
-
if (
|
|
76
|
-
|
|
75
|
+
const probe = await probeDevToolsEndpoint(config.port, log, http);
|
|
76
|
+
if (probe.kind !== 'devtools') {
|
|
77
|
+
const err = probe.kind === 'not-devtools'
|
|
78
|
+
? notDevToolsEndpointError(endpoint)
|
|
79
|
+
: externalChromeUnreachableError(endpoint, http.secure);
|
|
80
|
+
fail(err, EXIT_CODES.CDP_CONNECTION_FAILURE);
|
|
81
|
+
}
|
|
82
|
+
const browserWsUrl = probe.wsUrl;
|
|
77
83
|
const targets = await fetchCDPTargets(config.port, log, http);
|
|
78
84
|
const targetId = pathname.split('/').pop() ?? '';
|
|
79
85
|
if (!pathname.startsWith(BROWSER_WS_PATH)) {
|
|
80
86
|
const known = targets.find((t) => t.id === targetId);
|
|
81
87
|
if (!known)
|
|
82
88
|
fail(externalPageNotFoundError(targetId, endpoint), EXIT_CODES.RESOURCE_NOT_FOUND);
|
|
89
|
+
await assertNotOwned(targets, known.id, endpoint);
|
|
83
90
|
return { ...known, webSocketDebuggerUrl: wsUrl };
|
|
84
91
|
}
|
|
85
92
|
if (new URL(browserWsUrl).pathname !== pathname) {
|
|
86
93
|
const actual = withEndpoint(browserWsUrl, protocol, hostname, port);
|
|
87
94
|
fail(externalBrowserIdMismatchError(endpoint, actual), EXIT_CODES.RESOURCE_NOT_FOUND);
|
|
88
95
|
}
|
|
89
|
-
const
|
|
90
|
-
|
|
96
|
+
const firstPage = targets.find((t) => t.type === 'page');
|
|
97
|
+
await assertNotOwned(targets, firstPage?.id ?? '', endpoint);
|
|
98
|
+
const page = firstPage ?? (await createPageTarget(hostname, config.port, log, http));
|
|
91
99
|
if (!page)
|
|
92
100
|
fail(externalChromeUnreachableError(endpoint, http.secure), EXIT_CODES.CDP_CONNECTION_FAILURE);
|
|
93
101
|
log.info(`Attaching to page ${page.url} of the external Chrome`);
|
|
@@ -96,6 +104,21 @@ async function resolveExternalTarget(wsUrl, config, log) {
|
|
|
96
104
|
webSocketDebuggerUrl: withEndpoint(page.webSocketDebuggerUrl, protocol, hostname, port),
|
|
97
105
|
};
|
|
98
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* Refuse to attach when another running bdg session launched this Chrome or
|
|
109
|
+
* drives the tab this session would use: taking it over would mix both
|
|
110
|
+
* sessions' telemetry and let one end the other.
|
|
111
|
+
*
|
|
112
|
+
* @param targets - Every target of the Chrome
|
|
113
|
+
* @param targetId - Tab this session would drive ('' for a tab still to be opened)
|
|
114
|
+
* @param endpoint - e.g. http://127.0.0.1:9222
|
|
115
|
+
* @throws CommandError (90) naming the other session
|
|
116
|
+
*/
|
|
117
|
+
async function assertNotOwned(targets, targetId, endpoint) {
|
|
118
|
+
const owner = await findConflictingOwner(targets.map((t) => t.id), targetId);
|
|
119
|
+
if (owner)
|
|
120
|
+
fail(chromeInUseBySessionError(endpoint, owner), EXIT_CODES.RESOURCE_CONFLICT);
|
|
121
|
+
}
|
|
99
122
|
/**
|
|
100
123
|
* A DevTools WebSocket URL from Chrome, reached the way the user's URL is
|
|
101
124
|
* (Chrome reports its own host, e.g. 127.0.0.1, even behind a proxy).
|
|
@@ -123,6 +146,20 @@ function withEndpoint(url, protocol, hostname, port) {
|
|
|
123
146
|
function fail(err, exitCode) {
|
|
124
147
|
throw new CommandError(err.message, { suggestion: err.suggestion }, exitCode);
|
|
125
148
|
}
|
|
149
|
+
/**
|
|
150
|
+
* Chrome flags of a launched Chrome: with `--viewport`, a window of that size
|
|
151
|
+
* first (so tabs the page opens get it too), then the user's own flags, which
|
|
152
|
+
* win over it.
|
|
153
|
+
*
|
|
154
|
+
* @param config - Session configuration
|
|
155
|
+
* @returns Flags, or undefined without any
|
|
156
|
+
*/
|
|
157
|
+
export function windowSizeFlags(config) {
|
|
158
|
+
if (!config.viewport)
|
|
159
|
+
return config.chromeFlags;
|
|
160
|
+
const { width, height } = config.viewport;
|
|
161
|
+
return [`--window-size=${width},${height}`, ...(config.chromeFlags ?? [])];
|
|
162
|
+
}
|
|
126
163
|
/**
|
|
127
164
|
* Launch a new Chrome instance and record its PID for crash cleanup.
|
|
128
165
|
*/
|
|
@@ -134,7 +171,7 @@ async function setupLaunchedChrome(config, log) {
|
|
|
134
171
|
...filterDefined({
|
|
135
172
|
userDataDir: config.userDataDir,
|
|
136
173
|
headless: config.headless,
|
|
137
|
-
chromeFlags: config
|
|
174
|
+
chromeFlags: windowSizeFlags(config),
|
|
138
175
|
}),
|
|
139
176
|
});
|
|
140
177
|
log.info(`Chrome launched (PID ${chrome.pid})`);
|
|
@@ -1,16 +1,24 @@
|
|
|
1
1
|
import { CDPConnectionError } from '../../connection/errors.js';
|
|
2
2
|
import { PatternDetector } from '../patternDetector.js';
|
|
3
3
|
import { createInteractionRunner } from './interactions.js';
|
|
4
|
+
import { withTriggeredRequestCount } from './triggeredRequests.js';
|
|
4
5
|
import { CommandError } from '../../errors/index.js';
|
|
6
|
+
import { formDiscoveryFailedError } from '../../errors/messages.js';
|
|
5
7
|
import { evaluateScript, withBusyPageRecovery } from '../../runtime/dom/evalHelpers.js';
|
|
6
8
|
import { inspectEventListeners } from '../../runtime/dom/eventListeners.js';
|
|
7
9
|
import { FORM_DISCOVERY_SCRIPT, isRawFormData } from '../../runtime/dom/formDiscovery.js';
|
|
8
|
-
import { fillElement, clickElement, pressKeyElement, scrollPage,
|
|
10
|
+
import { fillElement, clickElement, pressKeyElement, scrollPage, withActionStability, } from '../../runtime/dom/formFillHelpers/index.js';
|
|
11
|
+
import { exceptionSummary } from '../../runtime/dom/formFillHelpers/shared.js';
|
|
9
12
|
import { submitForm } from '../../runtime/dom/formSubmitHelpers.js';
|
|
10
13
|
import { evaluateInFrame, listFrames } from '../../runtime/dom/frames.js';
|
|
11
|
-
import {
|
|
14
|
+
import { inspectLayout } from '../../runtime/dom/layout.js';
|
|
15
|
+
import { onScriptTarget } from '../../runtime/dom/targetNode.js';
|
|
16
|
+
import { waitForCondition } from '../../runtime/dom/wait.js';
|
|
17
|
+
import { pageAppearance } from '../../runtime/page/emulation.js';
|
|
18
|
+
import { readDocumentReadyState } from '../../runtime/page/loadingState.js';
|
|
12
19
|
import { navigatePage } from '../../runtime/page/navigation.js';
|
|
13
20
|
import { skippedBodyReason } from '../../telemetry/network.js';
|
|
21
|
+
import { sessionCommand } from '../../ui/messages/sessionCommand.js';
|
|
14
22
|
import { EXIT_CODES } from '../../utils/exitCodes.js';
|
|
15
23
|
import { filterDefined } from '../../utils/objects.js';
|
|
16
24
|
import { VERSION } from '../../utils/version.js';
|
|
@@ -169,7 +177,7 @@ function findNetworkRequestOrThrow(store, id) {
|
|
|
169
177
|
store.pendingNetworkRequests.get(id)?.request ??
|
|
170
178
|
(webSocket && webSocketAsRequest(webSocket));
|
|
171
179
|
if (!request) {
|
|
172
|
-
throw new CommandError(`Network request not found: ${id}`, { suggestion:
|
|
180
|
+
throw new CommandError(`Network request not found: ${id}`, { suggestion: `List request ids with: ${sessionCommand('bdg network list')}` }, EXIT_CODES.RESOURCE_NOT_FOUND);
|
|
173
181
|
}
|
|
174
182
|
return request;
|
|
175
183
|
}
|
|
@@ -183,13 +191,13 @@ function findNetworkRequestOrThrow(store, id) {
|
|
|
183
191
|
*/
|
|
184
192
|
function findConsoleMessageOrThrow(messages, indexStr) {
|
|
185
193
|
if (!/^\d+$/.test(indexStr)) {
|
|
186
|
-
throw new CommandError(`Invalid console message index: ${indexStr}`, { suggestion:
|
|
194
|
+
throw new CommandError(`Invalid console message index: ${indexStr}`, { suggestion: `Use a 0-based index from: ${sessionCommand('bdg console --list')}` }, EXIT_CODES.INVALID_ARGUMENTS);
|
|
187
195
|
}
|
|
188
196
|
const index = parseInt(indexStr, 10);
|
|
189
197
|
if (index >= messages.length) {
|
|
190
198
|
throw new CommandError(messages.length === 0
|
|
191
199
|
? `Console message not found at index: ${indexStr} (no messages captured yet)`
|
|
192
|
-
: `Console message not found at index: ${indexStr} (available: 0-${messages.length - 1})`, { suggestion:
|
|
200
|
+
: `Console message not found at index: ${indexStr} (available: 0-${messages.length - 1})`, { suggestion: `List messages with: ${sessionCommand('bdg console --list')}` }, EXIT_CODES.RESOURCE_NOT_FOUND);
|
|
193
201
|
}
|
|
194
202
|
const message = messages[index];
|
|
195
203
|
if (!message) {
|
|
@@ -304,7 +312,7 @@ export function createCommandRegistry(store) {
|
|
|
304
312
|
}
|
|
305
313
|
return Promise.reject(new Error(`Unknown itemType: ${String(params.itemType)}. Expected 'network' or 'console'.`));
|
|
306
314
|
},
|
|
307
|
-
session_status: async (
|
|
315
|
+
session_status: async (cdp, _params) => {
|
|
308
316
|
const duration = Date.now() - store.sessionStartTime;
|
|
309
317
|
const lastNetworkRequest = store.networkRequests[store.networkRequests.length - 1];
|
|
310
318
|
const lastConsoleMessage = store.consoleMessages[store.consoleMessages.length - 1];
|
|
@@ -314,6 +322,7 @@ export function createCommandRegistry(store) {
|
|
|
314
322
|
target: {
|
|
315
323
|
url: store.targetInfo?.url ?? '',
|
|
316
324
|
title: store.targetInfo?.title ?? '',
|
|
325
|
+
...(await pageAppearance(cdp)),
|
|
317
326
|
},
|
|
318
327
|
activeTelemetry: store.activeTelemetry,
|
|
319
328
|
activity: filterDefined({
|
|
@@ -324,7 +333,7 @@ export function createCommandRegistry(store) {
|
|
|
324
333
|
}),
|
|
325
334
|
navigationId: store.getCurrentNavigationId?.() ?? 0,
|
|
326
335
|
};
|
|
327
|
-
return
|
|
336
|
+
return result;
|
|
328
337
|
},
|
|
329
338
|
session_har_data: async (_cdp, _params) => {
|
|
330
339
|
return Promise.resolve({
|
|
@@ -342,6 +351,10 @@ export function createCommandRegistry(store) {
|
|
|
342
351
|
return Promise.resolve({
|
|
343
352
|
url: targetRequest.url,
|
|
344
353
|
requestId: targetRequest.requestId,
|
|
354
|
+
method: targetRequest.method,
|
|
355
|
+
...(targetRequest.status !== undefined && { status: targetRequest.status }),
|
|
356
|
+
...(targetRequest.statusText && { statusText: targetRequest.statusText }),
|
|
357
|
+
...(targetRequest.errorText && { errorText: targetRequest.errorText }),
|
|
345
358
|
requestHeaders,
|
|
346
359
|
responseHeaders,
|
|
347
360
|
});
|
|
@@ -365,66 +378,40 @@ export function createCommandRegistry(store) {
|
|
|
365
378
|
? evaluateScript(cdp, params.script)
|
|
366
379
|
: evaluateInFrame(cdp, pageWebSocketUrl(store), params.script, params.frame),
|
|
367
380
|
dom_frames: async (cdp) => ({ frames: await listFrames(cdp, pageWebSocketUrl(store)) }),
|
|
368
|
-
dom_fill: async (cdp, params) => interact(cdp, async () => {
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
index: target.index,
|
|
372
|
-
blur: params.blur,
|
|
373
|
-
cwd: params.cwd,
|
|
374
|
-
});
|
|
375
|
-
const result = withUserSelector(await fillElement(cdp, target.selector, params.value, fillOptions), params.selector);
|
|
376
|
-
if (result.success && params.wait !== false)
|
|
377
|
-
await waitForActionStability(cdp);
|
|
378
|
-
return result;
|
|
381
|
+
dom_fill: async (cdp, params) => interact(cdp, async () => onScriptTarget(cdp, params, (target) => withActionStability(cdp, () => fillElement(target.cdp, target.selector, params.value, filterDefined({ index: target.index, blur: params.blur, cwd: params.cwd })), params.wait !== false))),
|
|
382
|
+
dom_click: async (cdp, params) => interact(cdp, async () => onScriptTarget(cdp, params, (target) => withActionStability(cdp, () => clickElement(target.cdp, target.selector, filterDefined({ index: target.index, action: params.action })), params.wait !== false)), {
|
|
383
|
+
detectNoEffect: params.wait !== false && params.action !== 'hover' && params.action !== 'right',
|
|
379
384
|
}),
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
const clickOptions = filterDefined({ index: target.index, action: params.action });
|
|
383
|
-
const result = withUserSelector(await clickElement(cdp, target.selector, clickOptions), params.selector);
|
|
384
|
-
if (result.success && params.wait !== false)
|
|
385
|
-
await waitForActionStability(cdp);
|
|
386
|
-
return result;
|
|
387
|
-
}),
|
|
388
|
-
dom_submit: async (cdp, params) => interact(cdp, async () => {
|
|
389
|
-
const target = await resolveScriptTarget(cdp, params);
|
|
390
|
-
const submitOptions = filterDefined({
|
|
385
|
+
dom_submit: async (cdp, params) => withTriggeredRequestCount(await interact(cdp, async () => onScriptTarget(cdp, params, (target) => submitForm(target.cdp, target.selector, {
|
|
386
|
+
...filterDefined({
|
|
391
387
|
index: target.index,
|
|
392
388
|
waitNavigation: params.waitNavigation,
|
|
393
389
|
waitNetwork: params.waitNetwork,
|
|
394
390
|
timeout: params.timeout,
|
|
395
|
-
})
|
|
396
|
-
|
|
397
|
-
}),
|
|
398
|
-
dom_press_key: async (cdp, params) => interact(cdp, async () => {
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
const scrollOptions = filterDefined({
|
|
413
|
-
index: target.index,
|
|
414
|
-
down: params.down,
|
|
415
|
-
up: params.up,
|
|
416
|
-
left: params.left,
|
|
417
|
-
right: params.right,
|
|
418
|
-
top: params.top,
|
|
419
|
-
bottom: params.bottom,
|
|
420
|
-
});
|
|
421
|
-
const result = withUserSelector(await scrollPage(cdp, target.selector || undefined, scrollOptions), params.selector);
|
|
422
|
-
if (result.success && params.wait !== false)
|
|
423
|
-
await waitForActionStability(cdp);
|
|
424
|
-
return result;
|
|
425
|
-
}),
|
|
391
|
+
}),
|
|
392
|
+
pendingRequests: () => store.pendingNetworkRequests.values(),
|
|
393
|
+
})), { detectNoEffect: params.waitNetwork !== 0 || params.waitNavigation === true })),
|
|
394
|
+
dom_press_key: async (cdp, params) => interact(cdp, async () => onScriptTarget(cdp, params, (target) => withActionStability(cdp, () => pressKeyElement(target.cdp, target.selector, params.key, filterDefined({
|
|
395
|
+
index: target.index,
|
|
396
|
+
times: params.times,
|
|
397
|
+
modifiers: params.modifiers,
|
|
398
|
+
})), params.wait !== false))),
|
|
399
|
+
dom_scroll: async (cdp, params) => interact(cdp, async () => onScriptTarget(cdp, params, (target) => withActionStability(cdp, () => scrollPage(target.cdp, target.selector || undefined, filterDefined({
|
|
400
|
+
index: target.index,
|
|
401
|
+
down: params.down,
|
|
402
|
+
up: params.up,
|
|
403
|
+
left: params.left,
|
|
404
|
+
right: params.right,
|
|
405
|
+
top: params.top,
|
|
406
|
+
bottom: params.bottom,
|
|
407
|
+
})), params.wait !== false))),
|
|
426
408
|
dom_listeners: async (cdp, params) => withBusyPageRecovery(cdp, inspectEventListeners(cdp, params)),
|
|
427
|
-
|
|
409
|
+
dom_layout: async (cdp, params) => withBusyPageRecovery(cdp, inspectLayout(cdp, params)),
|
|
410
|
+
dom_wait: async (cdp, params) => waitForCondition(cdp, params),
|
|
411
|
+
page_navigate: async (cdp, params) => interact(cdp, () => navigatePage(cdp, params.action, {
|
|
412
|
+
...filterDefined({ url: params.url, wait: params.wait }),
|
|
413
|
+
pendingRequests: () => store.pendingNetworkRequests.values(),
|
|
414
|
+
}), { reportRequests: false, reportEffects: false }),
|
|
428
415
|
dom_form_discover: async (cdp) => {
|
|
429
416
|
const response = await withBusyPageRecovery(cdp, cdp.send('Runtime.evaluate', {
|
|
430
417
|
expression: FORM_DISCOVERY_SCRIPT,
|
|
@@ -432,7 +419,10 @@ export function createCommandRegistry(store) {
|
|
|
432
419
|
}));
|
|
433
420
|
const cdpResponse = response;
|
|
434
421
|
if (cdpResponse.exceptionDetails) {
|
|
435
|
-
|
|
422
|
+
const readyState = await readDocumentReadyState(cdp);
|
|
423
|
+
const loading = readyState !== undefined && readyState !== 'complete';
|
|
424
|
+
const err = formDiscoveryFailedError(exceptionSummary(cdpResponse.exceptionDetails), readyState);
|
|
425
|
+
throw new CommandError(err.message, { suggestion: err.suggestion }, loading ? EXIT_CODES.RESOURCE_NOT_FOUND : EXIT_CODES.SOFTWARE_ERROR);
|
|
436
426
|
}
|
|
437
427
|
const rawData = cdpResponse.result?.value;
|
|
438
428
|
if (!isRawFormData(rawData)) {
|
|
@@ -1,14 +1,36 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Running page interactions (fill, click, submit, pressKey, scroll) one at a
|
|
3
|
-
* time, reporting the dialogs they caused.
|
|
3
|
+
* time, reporting the dialogs, network requests and page changes they caused.
|
|
4
4
|
*/
|
|
5
5
|
import type { TelemetryStore } from './TelemetryStore.js';
|
|
6
6
|
import type { CDPConnection } from '../../connection/cdp.js';
|
|
7
|
-
import type { DialogInfo } from '../../ipc/protocol/domTypes.js';
|
|
8
|
-
/**
|
|
9
|
-
|
|
7
|
+
import type { ActionEffects, DialogInfo, TriggeredRequest } from '../../ipc/protocol/domTypes.js';
|
|
8
|
+
/** What an interaction caused besides its own result */
|
|
9
|
+
interface InteractionReport extends ActionEffects {
|
|
10
10
|
dialogs?: DialogInfo[];
|
|
11
|
-
|
|
11
|
+
triggeredRequests?: TriggeredRequest[];
|
|
12
|
+
triggeredRequestsOmitted?: number;
|
|
13
|
+
}
|
|
14
|
+
/** How an interaction is reported */
|
|
15
|
+
export interface InteractionOptions {
|
|
16
|
+
/**
|
|
17
|
+
* List the network requests it triggered (default true; off for page
|
|
18
|
+
* navigation, whose requests are the whole page load)
|
|
19
|
+
*/
|
|
20
|
+
reportRequests?: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Report navigation and new messages (default true; off for page
|
|
23
|
+
* navigation, which reports its own page)
|
|
24
|
+
*/
|
|
25
|
+
reportEffects?: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Say when it had no visible effect (`effect: "none"`): only for actions
|
|
28
|
+
* whose effect shows in the DOM (click, submit) and that waited for it
|
|
29
|
+
*/
|
|
30
|
+
detectNoEffect?: boolean;
|
|
31
|
+
}
|
|
32
|
+
/** Runs one interaction after the previous one finished */
|
|
33
|
+
export type InteractionRunner = <T extends object>(cdp: CDPConnection, action: () => Promise<T>, options?: InteractionOptions) => Promise<T & InteractionReport>;
|
|
12
34
|
/**
|
|
13
35
|
* Create the runner for a session's interactions.
|
|
14
36
|
*
|
|
@@ -16,12 +38,16 @@ export type InteractionRunner = <T extends object>(cdp: CDPConnection, action: (
|
|
|
16
38
|
* commands would interleave keystrokes into each other's field, so they are
|
|
17
39
|
* queued. After each one, the bound target (`window.__bdgTarget`) is removed
|
|
18
40
|
* from the page (without waiting: during a pending navigation that takes until
|
|
19
|
-
* the new page commits), and the dialogs it opened
|
|
20
|
-
*
|
|
21
|
-
*
|
|
41
|
+
* the new page commits), and the dialogs it opened, what it changed on the
|
|
42
|
+
* page (see {@link watchActionEffects}) and the network requests it
|
|
43
|
+
* triggered (see {@link watchTriggeredRequests}) are added to its result.
|
|
44
|
+
* They are attributed by time: a dialog or request started by a page timer
|
|
45
|
+
* or a navigation started earlier is reported by whichever interaction is
|
|
46
|
+
* running then.
|
|
22
47
|
*
|
|
23
|
-
* @param store - Session store recording accepted dialogs
|
|
48
|
+
* @param store - Session store recording accepted dialogs and network requests
|
|
24
49
|
* @returns Interaction runner
|
|
25
50
|
*/
|
|
26
51
|
export declare function createInteractionRunner(store: TelemetryStore): InteractionRunner;
|
|
52
|
+
export {};
|
|
27
53
|
//# sourceMappingURL=interactions.d.ts.map
|