browser-debugger-cli 0.15.0 → 0.16.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/.claude/skills/bdg/SKILL.md +2 -1
- package/dist/cdp/methodTarget.d.ts +92 -0
- package/dist/cdp/methodTarget.js +159 -0
- package/dist/cdp/protocol.d.ts +16 -1
- package/dist/cdp/protocol.js +21 -0
- package/dist/cdp/schema.d.ts +55 -1
- package/dist/cdp/schema.js +134 -25
- package/dist/cdp/types.d.ts +3 -1
- package/dist/commands/cdp.d.ts +38 -1
- package/dist/commands/cdp.js +200 -133
- package/dist/commands/cleanup.js +18 -4
- package/dist/commands/dom/formInteraction.js +8 -4
- package/dist/commands/dom/helpers/index.d.ts +4 -4
- package/dist/commands/dom/helpers/index.js +3 -3
- package/dist/commands/dom/helpers/query.d.ts +2 -2
- package/dist/commands/dom/helpers/query.js +2 -2
- package/dist/commands/dom/helpers/screenshot.d.ts +21 -26
- package/dist/commands/dom/helpers/screenshot.js +50 -668
- package/dist/commands/dom/screenshot.js +56 -36
- package/dist/commands/optionBehaviors.js +18 -8
- package/dist/commands/shared/CommandRunner.d.ts +5 -0
- package/dist/commands/shared/CommandRunner.js +18 -3
- package/dist/commands/shared/interrupt.d.ts +40 -0
- package/dist/commands/shared/interrupt.js +73 -0
- package/dist/commands/shared/optionTypes.d.ts +2 -0
- package/dist/commands/shared/startHelpers.d.ts +26 -3
- package/dist/commands/shared/startHelpers.js +145 -23
- package/dist/commands/types.d.ts +5 -0
- package/dist/connection/cdp.js +1 -16
- package/dist/connection/chromeIdentity.d.ts +24 -5
- package/dist/connection/chromeIdentity.js +53 -22
- package/dist/connection/launcher.d.ts +34 -1
- package/dist/connection/launcher.js +98 -10
- package/dist/connection/typed-cdp.d.ts +3 -2
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +1 -1
- package/dist/daemon/SessionController.d.ts +10 -5
- package/dist/daemon/SessionController.js +15 -8
- package/dist/daemon/ipcServer.js +1 -1
- package/dist/daemon/launcher.d.ts +5 -0
- package/dist/daemon/launcher.js +8 -1
- package/dist/daemon/session/Session.d.ts +5 -1
- package/dist/daemon/session/Session.js +9 -8
- package/dist/daemon/session/TelemetryStore.d.ts +5 -0
- package/dist/daemon/session/TelemetryStore.js +4 -0
- package/dist/daemon/session/captureGate.d.ts +59 -0
- package/dist/daemon/session/captureGate.js +96 -0
- package/dist/daemon/session/chromeConnection.d.ts +16 -1
- package/dist/daemon/session/chromeConnection.js +34 -4
- package/dist/daemon/session/collectors.d.ts +15 -0
- package/dist/daemon/session/collectors.js +39 -2
- package/dist/daemon/session/commandRegistry.d.ts +14 -1
- package/dist/daemon/session/commandRegistry.js +46 -11
- package/dist/daemon/session/downloads.d.ts +32 -0
- package/dist/daemon/session/downloads.js +96 -0
- package/dist/daemon/session/interactions.d.ts +3 -2
- package/dist/daemon/session/interactions.js +7 -2
- package/dist/daemon/session/plugins.js +6 -0
- package/dist/daemon.js +12843 -11482
- package/dist/errors/CommandError.d.ts +2 -0
- package/dist/errors/issues.d.ts +1 -1
- package/dist/errors/messages.d.ts +58 -0
- package/dist/errors/messages.js +112 -0
- package/dist/index.js +999 -1020
- package/dist/ipc/client.d.ts +14 -1
- package/dist/ipc/client.js +21 -4
- package/dist/ipc/protocol/commands.d.ts +32 -2
- package/dist/ipc/protocol/commands.js +1 -0
- package/dist/ipc/protocol/domTypes.d.ts +24 -1
- package/dist/ipc/session/queries.d.ts +3 -0
- package/dist/ipc/session/types.d.ts +5 -0
- package/dist/ipc/transport/IPCError.d.ts +9 -0
- package/dist/ipc/transport/IPCError.js +12 -0
- package/dist/ipc/transport/errors.d.ts +2 -1
- package/dist/ipc/transport/errors.js +4 -1
- package/dist/ipc/transport/index.d.ts +4 -2
- package/dist/ipc/transport/index.js +13 -3
- package/dist/runtime/dom/actionEffects.d.ts +48 -9
- package/dist/runtime/dom/actionEffects.js +269 -34
- package/dist/runtime/dom/actionEffectsScripts.d.ts +45 -0
- package/dist/runtime/dom/actionEffectsScripts.js +101 -2
- package/dist/runtime/dom/captureArea.d.ts +35 -0
- package/dist/runtime/dom/captureArea.js +203 -0
- package/dist/runtime/dom/elementInfo.d.ts +10 -8
- package/dist/runtime/dom/elementInfo.js +8 -6
- package/dist/runtime/dom/formDiscovery.d.ts +1 -1
- package/dist/runtime/page/bdgWorld.d.ts +9 -0
- package/dist/runtime/page/bdgWorld.js +11 -0
- package/dist/runtime/page/captureEmulation.d.ts +119 -0
- package/dist/runtime/page/captureEmulation.js +189 -0
- package/dist/runtime/page/captureScroll.d.ts +24 -0
- package/dist/runtime/page/captureScroll.js +124 -0
- package/dist/runtime/page/screenshot.d.ts +41 -0
- package/dist/runtime/page/screenshot.js +394 -0
- package/dist/session/paths.d.ts +14 -0
- package/dist/session/paths.js +25 -0
- package/dist/telemetry/downloads.d.ts +127 -0
- package/dist/telemetry/downloads.js +265 -0
- package/dist/telemetry/har/builder.js +22 -7
- package/dist/telemetry/har/sanitize.d.ts +7 -3
- package/dist/telemetry/har/sanitize.js +52 -6
- package/dist/telemetry/har/sanitizeBody.d.ts +47 -7
- package/dist/telemetry/har/sanitizeBody.js +429 -56
- package/dist/telemetry/har/types.d.ts +2 -0
- package/dist/telemetry/network.d.ts +4 -4
- package/dist/telemetry/network.js +38 -4
- package/dist/telemetry/networkRetention.d.ts +35 -14
- package/dist/telemetry/networkRetention.js +62 -26
- package/dist/types.d.ts +9 -14
- package/dist/ui/OutputBuilder.d.ts +3 -2
- package/dist/ui/OutputBuilder.js +4 -3
- package/dist/ui/formatters/cdp.d.ts +32 -9
- package/dist/ui/formatters/cdp.js +77 -6
- package/dist/ui/formatters/details.js +7 -15
- package/dist/ui/formatters/preview.d.ts +2 -0
- package/dist/ui/formatters/preview.js +7 -1
- package/dist/ui/formatters/status.js +6 -1
- package/dist/ui/formatting.d.ts +7 -0
- package/dist/ui/formatting.js +13 -0
- package/dist/ui/logging/logger.d.ts +1 -1
- package/dist/ui/messages/chrome.d.ts +13 -0
- package/dist/ui/messages/chrome.js +26 -0
- package/dist/ui/messages/commands.d.ts +71 -3
- package/dist/ui/messages/commands.js +98 -3
- package/dist/ui/messages/networkMessages.d.ts +24 -5
- package/dist/ui/messages/networkMessages.js +31 -8
- package/dist/utils/async.d.ts +3 -2
- package/dist/utils/async.js +16 -3
- package/dist/utils/http.d.ts +11 -4
- package/dist/utils/http.js +5 -3
- package/package.json +18 -4
- /package/dist/{commands/dom → runtime/page}/screenshotResize.d.ts +0 -0
- /package/dist/{commands/dom → runtime/page}/screenshotResize.js +0 -0
|
@@ -14,23 +14,47 @@ import { writeChromePid } from '../../session/chrome.js';
|
|
|
14
14
|
import { findConflictingOwner } from '../../session/chromeOwners.js';
|
|
15
15
|
import { getSessionDir } from '../../session/paths.js';
|
|
16
16
|
import { EXIT_CODES } from '../../utils/exitCodes.js';
|
|
17
|
-
import { createPageTarget, fetchCDPTargets, probeDevToolsEndpoint } from '../../utils/http.js';
|
|
17
|
+
import { createPageTarget, fetchBrowserWsUrl, fetchCDPTargets, probeDevToolsEndpoint, } from '../../utils/http.js';
|
|
18
18
|
import { filterDefined } from '../../utils/objects.js';
|
|
19
19
|
/**
|
|
20
20
|
* Setup Chrome connection - either launch new instance or connect to existing.
|
|
21
21
|
*
|
|
22
|
+
* @param config - Session configuration
|
|
23
|
+
* @param telemetryStore - Store receiving the target info
|
|
24
|
+
* @param log - Logger
|
|
25
|
+
* @param notify - Receives notices about an external Chrome
|
|
26
|
+
* @param signal - Ends a launch at once when aborted (the session was stopped)
|
|
22
27
|
* @returns Launched Chrome instance (null if connecting to external Chrome)
|
|
23
28
|
*/
|
|
24
|
-
export async function setupChromeConnection(config, telemetryStore, log, notify) {
|
|
29
|
+
export async function setupChromeConnection(config, telemetryStore, log, notify, signal) {
|
|
25
30
|
if (config.chromeWsUrl) {
|
|
26
31
|
return setupExternalChrome(config, telemetryStore, log, notify);
|
|
27
32
|
}
|
|
28
33
|
else {
|
|
29
|
-
return setupLaunchedChrome(config, log);
|
|
34
|
+
return setupLaunchedChrome(config, log, signal);
|
|
30
35
|
}
|
|
31
36
|
}
|
|
32
37
|
/** Path of browser-level DevTools WebSocket URLs (`/devtools/browser/<uuid>`) */
|
|
33
38
|
const BROWSER_WS_PATH = '/devtools/browser/';
|
|
39
|
+
/**
|
|
40
|
+
* Browser-level DevTools WebSocket URL of the session's Chrome: a launched
|
|
41
|
+
* Chrome's, or for `--chrome-ws-url` the URL itself when it is browser-level,
|
|
42
|
+
* else the one Chrome reports, reached the way the user's URL is.
|
|
43
|
+
*
|
|
44
|
+
* @param config - Session configuration (port resolved)
|
|
45
|
+
* @param log - Logger
|
|
46
|
+
* @returns The URL, or null when Chrome does not answer `/json/version`
|
|
47
|
+
*/
|
|
48
|
+
export async function browserWebSocketUrl(config, log) {
|
|
49
|
+
if (!config.chromeWsUrl)
|
|
50
|
+
return fetchBrowserWsUrl(config.port, log);
|
|
51
|
+
const { hostname, pathname, protocol, port } = new URL(config.chromeWsUrl);
|
|
52
|
+
if (pathname.startsWith(BROWSER_WS_PATH))
|
|
53
|
+
return config.chromeWsUrl;
|
|
54
|
+
const http = { host: hostname, secure: protocol === 'wss:' };
|
|
55
|
+
const reported = await fetchBrowserWsUrl(config.port, log, http);
|
|
56
|
+
return reported && withEndpoint(reported, protocol, hostname, port);
|
|
57
|
+
}
|
|
34
58
|
/**
|
|
35
59
|
* Debugging port of an external Chrome, taken from its WebSocket URL.
|
|
36
60
|
*
|
|
@@ -162,12 +186,18 @@ export function windowSizeFlags(config) {
|
|
|
162
186
|
}
|
|
163
187
|
/**
|
|
164
188
|
* Launch a new Chrome instance and record its PID for crash cleanup.
|
|
189
|
+
*
|
|
190
|
+
* @param config - Session configuration
|
|
191
|
+
* @param log - Logger
|
|
192
|
+
* @param signal - Ends the launch at once when aborted
|
|
193
|
+
* @returns Launched Chrome
|
|
165
194
|
*/
|
|
166
|
-
async function setupLaunchedChrome(config, log) {
|
|
195
|
+
async function setupLaunchedChrome(config, log, signal) {
|
|
167
196
|
const chrome = await launchChrome({
|
|
168
197
|
port: config.port,
|
|
169
198
|
logger: log,
|
|
170
199
|
sessionDir: getSessionDir(),
|
|
200
|
+
signal,
|
|
171
201
|
...filterDefined({
|
|
172
202
|
userDataDir: config.userDataDir,
|
|
173
203
|
headless: config.headless,
|
|
@@ -4,5 +4,20 @@ import type { SessionConfig } from './types.js';
|
|
|
4
4
|
import type { CDPConnection } from '../../connection/cdp.js';
|
|
5
5
|
import type { CleanupFunction } from '../../types.js';
|
|
6
6
|
import type { Logger } from '../../ui/logging/index.js';
|
|
7
|
+
/**
|
|
8
|
+
* Start the session's collectors (telemetry plugins) in order.
|
|
9
|
+
*
|
|
10
|
+
* When one fails to start, the ones already started are cleaned up (their
|
|
11
|
+
* failures logged) before the error is rethrown, so nothing they opened (a
|
|
12
|
+
* browser-level connection, event handlers) outlives the failed start.
|
|
13
|
+
*
|
|
14
|
+
* @param cdp - Page connection
|
|
15
|
+
* @param config - Session configuration
|
|
16
|
+
* @param store - Telemetry store
|
|
17
|
+
* @param logger - Logger
|
|
18
|
+
* @param plugins - Plugins to start (default: the registered ones)
|
|
19
|
+
* @returns Cleanup functions of the started collectors
|
|
20
|
+
* @throws The error of the collector that failed to start
|
|
21
|
+
*/
|
|
7
22
|
export declare function startTelemetryCollectors(cdp: CDPConnection, config: SessionConfig, store: TelemetryStore, logger: Logger, plugins?: TelemetryPlugin[]): Promise<CleanupFunction[]>;
|
|
8
23
|
//# sourceMappingURL=collectors.d.ts.map
|
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
import { sessionActivatingCollector, sessionCollectorsActivated } from '../messages.js';
|
|
2
|
+
import { getErrorMessage } from '../../utils/errors.js';
|
|
2
3
|
import { getRegisteredTelemetryPlugins, shouldActivatePlugin } from './plugins.js';
|
|
3
4
|
const DEFAULT_TELEMETRY = ['network', 'console', 'dom'];
|
|
5
|
+
/**
|
|
6
|
+
* Start the session's collectors (telemetry plugins) in order.
|
|
7
|
+
*
|
|
8
|
+
* When one fails to start, the ones already started are cleaned up (their
|
|
9
|
+
* failures logged) before the error is rethrown, so nothing they opened (a
|
|
10
|
+
* browser-level connection, event handlers) outlives the failed start.
|
|
11
|
+
*
|
|
12
|
+
* @param cdp - Page connection
|
|
13
|
+
* @param config - Session configuration
|
|
14
|
+
* @param store - Telemetry store
|
|
15
|
+
* @param logger - Logger
|
|
16
|
+
* @param plugins - Plugins to start (default: the registered ones)
|
|
17
|
+
* @returns Cleanup functions of the started collectors
|
|
18
|
+
* @throws The error of the collector that failed to start
|
|
19
|
+
*/
|
|
4
20
|
export async function startTelemetryCollectors(cdp, config, store, logger, plugins) {
|
|
5
21
|
const cleanupFunctions = [];
|
|
6
22
|
store.activeTelemetry = config.telemetry ?? DEFAULT_TELEMETRY;
|
|
@@ -10,10 +26,31 @@ export async function startTelemetryCollectors(cdp, config, store, logger, plugi
|
|
|
10
26
|
continue;
|
|
11
27
|
}
|
|
12
28
|
logger.debug(sessionActivatingCollector(plugin.name));
|
|
13
|
-
|
|
14
|
-
|
|
29
|
+
try {
|
|
30
|
+
cleanupFunctions.push(await plugin.start({ cdp, config, store, logger }));
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
await runCleanups(cleanupFunctions, logger);
|
|
34
|
+
throw error;
|
|
35
|
+
}
|
|
15
36
|
}
|
|
16
37
|
logger.debug(sessionCollectorsActivated(store.activeTelemetry));
|
|
17
38
|
return cleanupFunctions;
|
|
18
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* Run cleanup functions, logging (not throwing) their failures.
|
|
42
|
+
*
|
|
43
|
+
* @param cleanups - Cleanup functions
|
|
44
|
+
* @param logger - Logger
|
|
45
|
+
*/
|
|
46
|
+
async function runCleanups(cleanups, logger) {
|
|
47
|
+
for (const cleanup of cleanups) {
|
|
48
|
+
try {
|
|
49
|
+
await cleanup();
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
logger.debug(`Collector cleanup error: ${getErrorMessage(error)}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
19
56
|
//# sourceMappingURL=collectors.js.map
|
|
@@ -1,11 +1,24 @@
|
|
|
1
1
|
import type { TelemetryStore } from './TelemetryStore.js';
|
|
2
2
|
import type { CDPConnection } from '../../connection/cdp.js';
|
|
3
|
+
import { CommandError } from '../../errors/index.js';
|
|
3
4
|
import type { CommandName, CommandSchemas } from '../../ipc/index.js';
|
|
4
5
|
import { type SessionEmulation } from '../../runtime/page/emulation.js';
|
|
5
|
-
|
|
6
|
+
/** A command's handler; `abandoned` aborts when its client disconnects */
|
|
7
|
+
type Handler<K extends CommandName> = (cdp: CDPConnection, params: CommandSchemas[K]['requestSchema'], abandoned?: AbortSignal) => Promise<CommandSchemas[K]['responseSchema']>;
|
|
6
8
|
export type CommandRegistry = {
|
|
7
9
|
[K in CommandName]: Handler<K>;
|
|
8
10
|
};
|
|
11
|
+
/**
|
|
12
|
+
* A `bdg cdp` failure that is the caller's: a method this Chrome doesn't
|
|
13
|
+
* implement (83), wrong parameters (81), or an id of a node, target or frame
|
|
14
|
+
* that does not exist (83). Other failures (internal errors, a detached
|
|
15
|
+
* page) stay software errors.
|
|
16
|
+
*
|
|
17
|
+
* @param method - CDP method
|
|
18
|
+
* @param error - What the call threw
|
|
19
|
+
* @returns The error to report, or undefined to keep the original
|
|
20
|
+
*/
|
|
21
|
+
export declare function callerError(method: string, error: unknown): CommandError | undefined;
|
|
9
22
|
/** The session's page emulation, which `page emulate` reads and changes */
|
|
10
23
|
export interface EmulationState {
|
|
11
24
|
get: () => SessionEmulation;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { missingMethodCause } from '../../cdp/methodTarget.js';
|
|
1
2
|
import { CDPConnectionError, CDPProtocolError } from '../../connection/errors.js';
|
|
2
3
|
import { PatternDetector } from '../patternDetector.js';
|
|
3
4
|
import { createInteractionRunner } from './interactions.js';
|
|
4
5
|
import { withTriggeredRequestCount } from './triggeredRequests.js';
|
|
5
6
|
import { CommandError } from '../../errors/index.js';
|
|
6
|
-
import { cdpCallError, formDiscoveryFailedError } from '../../errors/messages.js';
|
|
7
|
+
import { cdpCallError, cdpMethodNotImplementedError, formDiscoveryFailedError, } from '../../errors/messages.js';
|
|
7
8
|
import { searchStyleSheets } from '../../runtime/css/search.js';
|
|
8
9
|
import { auditPage } from '../../runtime/dom/audit.js';
|
|
9
10
|
import { evaluateScript, withBusyPageRecovery } from '../../runtime/dom/evalHelpers.js';
|
|
@@ -21,6 +22,8 @@ import { evaluateInBdgWorld, sendForBdgScript } from '../../runtime/page/bdgWorl
|
|
|
21
22
|
import { emulatePage, pageAppearance } from '../../runtime/page/emulation.js';
|
|
22
23
|
import { readDocumentReadyState } from '../../runtime/page/loadingState.js';
|
|
23
24
|
import { navigatePage } from '../../runtime/page/navigation.js';
|
|
25
|
+
import { takeScreenshot } from '../../runtime/page/screenshot.js';
|
|
26
|
+
import { toDownloadInfo } from '../../telemetry/downloads.js';
|
|
24
27
|
import { skippedBodyReason } from '../../telemetry/networkRetention.js';
|
|
25
28
|
import { consoleMessageDroppedError } from '../../ui/messages/consoleMessages.js';
|
|
26
29
|
import { sessionCommand } from '../../ui/messages/sessionCommand.js';
|
|
@@ -140,6 +143,15 @@ function webSocketAsRequest(connection) {
|
|
|
140
143
|
webSocket: { frames: connection.frames, ...(closedTime !== undefined && { closedTime }) },
|
|
141
144
|
};
|
|
142
145
|
}
|
|
146
|
+
/**
|
|
147
|
+
* The session's downloads for `status` and `peek`, left out when none began.
|
|
148
|
+
*
|
|
149
|
+
* @param store - Telemetry store
|
|
150
|
+
* @returns `downloads`, oldest first, as they stand now
|
|
151
|
+
*/
|
|
152
|
+
function sessionDownloads(store) {
|
|
153
|
+
return store.downloads.length > 0 ? { downloads: store.downloads.map(toDownloadInfo) } : {};
|
|
154
|
+
}
|
|
143
155
|
/**
|
|
144
156
|
* Status activity counts of what the network capture let go at its limits,
|
|
145
157
|
* left out when nothing was.
|
|
@@ -169,18 +181,28 @@ function allNetworkRequests(store) {
|
|
|
169
181
|
].sort((a, b) => a.timestamp - b.timestamp);
|
|
170
182
|
}
|
|
171
183
|
/**
|
|
172
|
-
* Present
|
|
173
|
-
* instead of
|
|
184
|
+
* Present bodies bdg did not fetch or keep as their reasons
|
|
185
|
+
* (`requestBodyNotCaptured`, `bodyNotCaptured`) instead of placeholder
|
|
186
|
+
* strings in `requestBody` and `responseBody`.
|
|
174
187
|
*
|
|
175
188
|
* @param request - Captured request
|
|
176
189
|
* @returns The request as `details` reports it
|
|
177
190
|
*/
|
|
178
191
|
function withBodyNotCaptured(request) {
|
|
179
|
-
const
|
|
180
|
-
|
|
192
|
+
const requestReason = skippedBodyReason(request.requestBody);
|
|
193
|
+
const responseReason = skippedBodyReason(request.responseBody);
|
|
194
|
+
if (requestReason === undefined && responseReason === undefined)
|
|
181
195
|
return request;
|
|
182
|
-
const
|
|
183
|
-
|
|
196
|
+
const result = { ...request };
|
|
197
|
+
if (requestReason !== undefined) {
|
|
198
|
+
delete result.requestBody;
|
|
199
|
+
result.requestBodyNotCaptured = requestReason;
|
|
200
|
+
}
|
|
201
|
+
if (responseReason !== undefined) {
|
|
202
|
+
delete result.responseBody;
|
|
203
|
+
result.bodyNotCaptured = responseReason;
|
|
204
|
+
}
|
|
205
|
+
return result;
|
|
184
206
|
}
|
|
185
207
|
/**
|
|
186
208
|
* Find a network request by ID: finished, in flight, or a WebSocket.
|
|
@@ -287,18 +309,25 @@ function pageWebSocketUrl(store) {
|
|
|
287
309
|
}
|
|
288
310
|
/** Chrome's "server error" code, used for failures on the page's state (a missing node, a bad id…) */
|
|
289
311
|
const CDP_SERVER_ERROR = -32000;
|
|
312
|
+
/** JSON-RPC "method not found": Chrome has no such method (for the target) */
|
|
313
|
+
const CDP_METHOD_NOT_FOUND = -32601;
|
|
290
314
|
/**
|
|
291
|
-
* A `bdg cdp` failure that is the caller's:
|
|
292
|
-
* id of a node, target or frame
|
|
293
|
-
* (internal errors, a detached
|
|
315
|
+
* A `bdg cdp` failure that is the caller's: a method this Chrome doesn't
|
|
316
|
+
* implement (83), wrong parameters (81), or an id of a node, target or frame
|
|
317
|
+
* that does not exist (83). Other failures (internal errors, a detached
|
|
318
|
+
* page) stay software errors.
|
|
294
319
|
*
|
|
295
320
|
* @param method - CDP method
|
|
296
321
|
* @param error - What the call threw
|
|
297
322
|
* @returns The error to report, or undefined to keep the original
|
|
298
323
|
*/
|
|
299
|
-
function callerError(method, error) {
|
|
324
|
+
export function callerError(method, error) {
|
|
300
325
|
if (!(error instanceof CDPProtocolError))
|
|
301
326
|
return undefined;
|
|
327
|
+
if (error.code === CDP_METHOD_NOT_FOUND) {
|
|
328
|
+
const missing = cdpMethodNotImplementedError(method, error.message, missingMethodCause(method));
|
|
329
|
+
return new CommandError(missing.message, { suggestion: missing.suggestion }, EXIT_CODES.RESOURCE_NOT_FOUND);
|
|
330
|
+
}
|
|
302
331
|
const err = cdpCallError(method, error.message);
|
|
303
332
|
if (error.isRequestError()) {
|
|
304
333
|
return new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.INVALID_ARGUMENTS);
|
|
@@ -364,6 +393,7 @@ export function createCommandRegistry(store, emulation) {
|
|
|
364
393
|
...(dropped > 0 && { droppedConsole: dropped }),
|
|
365
394
|
...(requestsDropped > 0 && { droppedNetwork: requestsDropped }),
|
|
366
395
|
...(bodiesEvicted > 0 && { evictedNetworkBodies: bodiesEvicted }),
|
|
396
|
+
...sessionDownloads(store),
|
|
367
397
|
hasMoreNetwork: networkBounds.start > 0,
|
|
368
398
|
hasMoreConsole: consoleBounds.start > 0,
|
|
369
399
|
});
|
|
@@ -400,6 +430,10 @@ export function createCommandRegistry(store, emulation) {
|
|
|
400
430
|
...(lastNetworkRequest && { lastNetworkRequestAt: lastNetworkRequest.timestamp }),
|
|
401
431
|
...(lastConsoleMessage && { lastConsoleMessageAt: lastConsoleMessage.timestamp }),
|
|
402
432
|
...networkEvictionActivity(store),
|
|
433
|
+
...sessionDownloads(store),
|
|
434
|
+
...(store.downloadsWarning !== undefined && {
|
|
435
|
+
downloadsWarning: store.downloadsWarning,
|
|
436
|
+
}),
|
|
403
437
|
},
|
|
404
438
|
navigationId: store.getCurrentNavigationId?.() ?? 0,
|
|
405
439
|
};
|
|
@@ -499,6 +533,7 @@ export function createCommandRegistry(store, emulation) {
|
|
|
499
533
|
dom_inspect: async (cdp, params) => withBusyPageRecovery(cdp, inspectElement(cdp, params).then((result) => result.theme === 'dark' && emulation.get().colorScheme === 'dark'
|
|
500
534
|
? { ...result, themeFrom: 'emulation' }
|
|
501
535
|
: result)),
|
|
536
|
+
dom_screenshot: async (cdp, params, abandoned) => takeScreenshot(cdp, params, () => emulation.get().viewport, { abandoned }),
|
|
502
537
|
dom_wait: async (cdp, params) => waitForCondition(cdp, params),
|
|
503
538
|
page_navigate: async (cdp, params) => interact(cdp, () => navigatePage(cdp, params.action, {
|
|
504
539
|
...filterDefined({ url: params.url, wait: params.wait }),
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Setting up a session's download tracking: where downloads go, and the
|
|
3
|
+
* connection their events arrive on.
|
|
4
|
+
*/
|
|
5
|
+
import type { TelemetryPluginContext } from './plugins.js';
|
|
6
|
+
import type { SessionConfig } from './types.js';
|
|
7
|
+
import { CDPConnection } from '../../connection/cdp.js';
|
|
8
|
+
import type { CleanupFunction } from '../../types.js';
|
|
9
|
+
import type { Logger } from '../../ui/logging/index.js';
|
|
10
|
+
/**
|
|
11
|
+
* Opens a browser-level connection to the session's Chrome, or null when
|
|
12
|
+
* there is none; `onLost` is called if it closes while the session runs.
|
|
13
|
+
*/
|
|
14
|
+
export type BrowserConnector = (config: SessionConfig, logger: Logger, onLost: () => void) => Promise<CDPConnection | null>;
|
|
15
|
+
/**
|
|
16
|
+
* Track the session's downloads (see {@link DownloadTracker}).
|
|
17
|
+
*
|
|
18
|
+
* For a Chrome bdg launched, on a browser-level connection, so downloads of
|
|
19
|
+
* tabs the page opens are named and recorded too; on the page's connection
|
|
20
|
+
* when there is none, or once it is lost (Chrome drops a connection's
|
|
21
|
+
* download behavior with it, so it is applied again there). An attached
|
|
22
|
+
* Chrome (`--chrome-ws-url`) is followed on the page's connection only: a
|
|
23
|
+
* browser-level one would count the user's other tabs' downloads as the
|
|
24
|
+
* session's, and a Chrome that allows one connection (toggle mode) would
|
|
25
|
+
* ask again for each.
|
|
26
|
+
*
|
|
27
|
+
* @param context - Plugin context
|
|
28
|
+
* @param connectBrowser - Opens the browser-level connection
|
|
29
|
+
* @returns Cleanup: stops tracking and closes the browser-level connection
|
|
30
|
+
*/
|
|
31
|
+
export declare function startSessionDownloads(context: TelemetryPluginContext, connectBrowser?: BrowserConnector): Promise<CleanupFunction>;
|
|
32
|
+
//# sourceMappingURL=downloads.d.ts.map
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Setting up a session's download tracking: where downloads go, and the
|
|
3
|
+
* connection their events arrive on.
|
|
4
|
+
*/
|
|
5
|
+
import { CDPConnection } from '../../connection/cdp.js';
|
|
6
|
+
import { browserWebSocketUrl } from './chromeConnection.js';
|
|
7
|
+
import { ensureSessionDownloadsDir } from '../../session/paths.js';
|
|
8
|
+
import { DownloadTracker } from '../../telemetry/downloads.js';
|
|
9
|
+
import { downloadsDirUnavailableReason } from '../../ui/messages/commands.js';
|
|
10
|
+
import { getErrorMessage } from '../../utils/errors.js';
|
|
11
|
+
/** Connection attempts for the browser-level connection (Chrome is already running) */
|
|
12
|
+
const BROWSER_CONNECT_ATTEMPTS = 2;
|
|
13
|
+
/**
|
|
14
|
+
* Track the session's downloads (see {@link DownloadTracker}).
|
|
15
|
+
*
|
|
16
|
+
* For a Chrome bdg launched, on a browser-level connection, so downloads of
|
|
17
|
+
* tabs the page opens are named and recorded too; on the page's connection
|
|
18
|
+
* when there is none, or once it is lost (Chrome drops a connection's
|
|
19
|
+
* download behavior with it, so it is applied again there). An attached
|
|
20
|
+
* Chrome (`--chrome-ws-url`) is followed on the page's connection only: a
|
|
21
|
+
* browser-level one would count the user's other tabs' downloads as the
|
|
22
|
+
* session's, and a Chrome that allows one connection (toggle mode) would
|
|
23
|
+
* ask again for each.
|
|
24
|
+
*
|
|
25
|
+
* @param context - Plugin context
|
|
26
|
+
* @param connectBrowser - Opens the browser-level connection
|
|
27
|
+
* @returns Cleanup: stops tracking and closes the browser-level connection
|
|
28
|
+
*/
|
|
29
|
+
export async function startSessionDownloads(context, connectBrowser = openBrowserConnection) {
|
|
30
|
+
const { cdp, config, store, logger } = context;
|
|
31
|
+
const tracker = new DownloadTracker(store, downloadDestination(config, logger));
|
|
32
|
+
const browser = config.chromeWsUrl
|
|
33
|
+
? null
|
|
34
|
+
: await connectBrowser(config, logger, () => void followOnPage(tracker, cdp, logger));
|
|
35
|
+
await tracker.attach(browser ?? cdp);
|
|
36
|
+
return () => {
|
|
37
|
+
tracker.stop();
|
|
38
|
+
browser?.close();
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Follow downloads on the page's connection after the browser-level one was lost.
|
|
43
|
+
*
|
|
44
|
+
* @param tracker - Session download tracker
|
|
45
|
+
* @param cdp - Page connection
|
|
46
|
+
* @param logger - Logger
|
|
47
|
+
*/
|
|
48
|
+
async function followOnPage(tracker, cdp, logger) {
|
|
49
|
+
logger.info('Browser connection for downloads lost; download behavior set again on the page');
|
|
50
|
+
await tracker.attach(cdp);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Open a browser-level connection to the session's Chrome.
|
|
54
|
+
*
|
|
55
|
+
* @param config - Session configuration
|
|
56
|
+
* @param logger - Logger
|
|
57
|
+
* @param onLost - Called if the connection closes while the session runs
|
|
58
|
+
* @returns Open connection, or null when there is none to open
|
|
59
|
+
*/
|
|
60
|
+
async function openBrowserConnection(config, logger, onLost) {
|
|
61
|
+
const url = await browserWebSocketUrl(config, logger);
|
|
62
|
+
if (!url)
|
|
63
|
+
return null;
|
|
64
|
+
const browser = new CDPConnection(logger);
|
|
65
|
+
try {
|
|
66
|
+
await browser.connect(url, { maxRetries: BROWSER_CONNECT_ATTEMPTS, onDisconnect: onLost });
|
|
67
|
+
return browser;
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
logger.debug(`No browser-level connection for downloads: ${getErrorMessage(error)}`);
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Where the session's downloads go: the session's `downloads/` for a Chrome
|
|
76
|
+
* bdg launched (refused when it cannot be created, rather than saved to the
|
|
77
|
+
* browser's default `~/Downloads`), the browser's own place for an attached
|
|
78
|
+
* Chrome.
|
|
79
|
+
*
|
|
80
|
+
* @param config - Session configuration
|
|
81
|
+
* @param logger - Logger
|
|
82
|
+
* @returns Destination
|
|
83
|
+
*/
|
|
84
|
+
function downloadDestination(config, logger) {
|
|
85
|
+
if (config.chromeWsUrl)
|
|
86
|
+
return { kind: 'browser' };
|
|
87
|
+
try {
|
|
88
|
+
return { kind: 'directory', dir: ensureSessionDownloadsDir() };
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
const reason = downloadsDirUnavailableReason(getErrorMessage(error));
|
|
92
|
+
logger.info(reason);
|
|
93
|
+
return { kind: 'refused', reason };
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=downloads.js.map
|
|
@@ -52,8 +52,9 @@ export type InteractionRunner = <T extends object>(cdp: CDPConnection, action: (
|
|
|
52
52
|
* from the page (without waiting: during a pending navigation that takes until
|
|
53
53
|
* the new page commits), and the dialogs it opened, what it changed on the
|
|
54
54
|
* page (see {@link watchActionEffects}; a console message logged meanwhile
|
|
55
|
-
* counts as an effect)
|
|
56
|
-
* triggered (see {@link watchTriggeredRequests})
|
|
55
|
+
* counts as an effect), the network requests it
|
|
56
|
+
* triggered (see {@link watchTriggeredRequests}) and the downloads that began
|
|
57
|
+
* meanwhile (as they stand when it returns) are added to its result,
|
|
57
58
|
* and, when asked, what the page was still working on (see
|
|
58
59
|
* {@link pendingChanges}). They are attributed by time: a dialog or request
|
|
59
60
|
* started by a page timer or a navigation started earlier is reported by
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { pendingChanges, watchActionEffects } from '../../runtime/dom/actionEffects.js';
|
|
7
7
|
import { UNBIND_TARGET_SCRIPT } from '../../runtime/dom/targetNode.js';
|
|
8
|
+
import { toDownloadInfo } from '../../telemetry/downloads.js';
|
|
8
9
|
import { createLogger } from '../../ui/logging/index.js';
|
|
9
10
|
import { getErrorMessage } from '../../utils/errors.js';
|
|
10
11
|
import { watchTriggeredRequests } from './triggeredRequests.js';
|
|
@@ -36,8 +37,9 @@ function consoleMessagesLogged(store) {
|
|
|
36
37
|
* from the page (without waiting: during a pending navigation that takes until
|
|
37
38
|
* the new page commits), and the dialogs it opened, what it changed on the
|
|
38
39
|
* page (see {@link watchActionEffects}; a console message logged meanwhile
|
|
39
|
-
* counts as an effect)
|
|
40
|
-
* triggered (see {@link watchTriggeredRequests})
|
|
40
|
+
* counts as an effect), the network requests it
|
|
41
|
+
* triggered (see {@link watchTriggeredRequests}) and the downloads that began
|
|
42
|
+
* meanwhile (as they stand when it returns) are added to its result,
|
|
41
43
|
* and, when asked, what the page was still working on (see
|
|
42
44
|
* {@link pendingChanges}). They are attributed by time: a dialog or request
|
|
43
45
|
* started by a page timer or a navigation started earlier is reported by
|
|
@@ -51,6 +53,7 @@ export function createInteractionRunner(store) {
|
|
|
51
53
|
return (cdp, action, options = {}) => {
|
|
52
54
|
const run = queue.then(async () => {
|
|
53
55
|
const firstDialog = store.dialogs.length;
|
|
56
|
+
const firstDownload = store.downloads.length;
|
|
54
57
|
const firstConsoleMessage = consoleMessagesLogged(store);
|
|
55
58
|
const collectRequests = options.reportRequests === false ? undefined : watchTriggeredRequests(store);
|
|
56
59
|
const effects = options.reportEffects === false ? undefined : watchActionEffects(cdp);
|
|
@@ -68,6 +71,7 @@ export function createInteractionRunner(store) {
|
|
|
68
71
|
});
|
|
69
72
|
const { work, ...changes } = collected ?? {};
|
|
70
73
|
const requests = collectRequests?.();
|
|
74
|
+
const downloads = store.downloads.slice(firstDownload).map(toDownloadInfo);
|
|
71
75
|
const pending = options.detectUnsettled && work
|
|
72
76
|
? pendingChanges(work, requests?.triggeredRequests)
|
|
73
77
|
: undefined;
|
|
@@ -76,6 +80,7 @@ export function createInteractionRunner(store) {
|
|
|
76
80
|
...(dialogs.length > 0 && { dialogs }),
|
|
77
81
|
...changes,
|
|
78
82
|
...(pending && { settled: false, pending }),
|
|
83
|
+
...(downloads.length > 0 && { downloads }),
|
|
79
84
|
...requests,
|
|
80
85
|
};
|
|
81
86
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { startSessionDownloads } from './downloads.js';
|
|
1
2
|
import { hideHeadlessUserAgent } from '../../runtime/page/userAgent.js';
|
|
2
3
|
import { startConsoleCollection } from '../../telemetry/console.js';
|
|
3
4
|
import { startDialogHandling } from '../../telemetry/dialogs.js';
|
|
@@ -94,6 +95,11 @@ export function createDefaultTelemetryPlugins() {
|
|
|
94
95
|
return startDialogHandling(cdp, (dialog) => store.recordDialog(dialog));
|
|
95
96
|
},
|
|
96
97
|
},
|
|
98
|
+
{
|
|
99
|
+
name: 'downloads',
|
|
100
|
+
runAlways: true,
|
|
101
|
+
start: startSessionDownloads,
|
|
102
|
+
},
|
|
97
103
|
{
|
|
98
104
|
name: 'page-identity',
|
|
99
105
|
runAlways: true,
|