browser-debugger-cli 0.11.0 → 0.13.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 +4 -4
- package/README.md +143 -79
- package/dist/commands/cdp.d.ts +22 -1
- package/dist/commands/cdp.js +100 -43
- package/dist/commands/console.d.ts +12 -0
- package/dist/commands/console.js +62 -12
- package/dist/commands/css.d.ts +13 -0
- package/dist/commands/css.js +53 -0
- package/dist/commands/dom/DomElementResolver.d.ts +3 -1
- package/dist/commands/dom/DomElementResolver.js +10 -3
- package/dist/commands/dom/a11y.js +3 -2
- package/dist/commands/dom/audit.d.ts +14 -0
- package/dist/commands/dom/audit.js +87 -0
- package/dist/commands/dom/eval.d.ts +3 -2
- package/dist/commands/dom/eval.js +11 -5
- package/dist/commands/dom/form.js +10 -9
- package/dist/commands/dom/formInteraction.js +42 -11
- package/dist/commands/dom/get.js +8 -8
- package/dist/commands/dom/helpers/index.d.ts +1 -1
- package/dist/commands/dom/helpers/index.js +1 -1
- package/dist/commands/dom/helpers/keyAttributes.d.ts +3 -2
- package/dist/commands/dom/helpers/keyAttributes.js +6 -4
- package/dist/commands/dom/helpers/query.d.ts +27 -3
- package/dist/commands/dom/helpers/query.js +152 -64
- package/dist/commands/dom/helpers/screenshot.d.ts +1 -0
- package/dist/commands/dom/helpers/screenshot.js +169 -49
- package/dist/commands/dom/index.js +7 -2
- package/dist/commands/dom/query.d.ts +19 -2
- package/dist/commands/dom/query.js +37 -6
- package/dist/commands/dom/screenshot.js +12 -7
- package/dist/commands/dom/semanticUtils.d.ts +3 -2
- package/dist/commands/dom/semanticUtils.js +40 -9
- package/dist/commands/dom/wait.js +5 -3
- package/dist/commands/helpJson.d.ts +82 -19
- package/dist/commands/helpJson.js +112 -41
- package/dist/commands/helpTopic.d.ts +16 -1
- package/dist/commands/helpTopic.js +59 -1
- package/dist/commands/installSkill.d.ts +15 -5
- package/dist/commands/installSkill.js +86 -16
- package/dist/commands/network/list.js +22 -12
- package/dist/commands/optionBehaviors.js +53 -16
- package/dist/commands/page.js +7 -4
- package/dist/commands/peek.d.ts +7 -0
- package/dist/commands/peek.js +65 -23
- package/dist/commands/shared/daemonErrorHandler.d.ts +5 -2
- package/dist/commands/shared/daemonErrorHandler.js +20 -9
- package/dist/commands/shared/dataFetcher.d.ts +12 -4
- package/dist/commands/shared/dataFetcher.js +12 -4
- package/dist/commands/shared/followMode.d.ts +9 -1
- package/dist/commands/shared/followMode.js +22 -4
- package/dist/commands/shared/optionTypes.d.ts +9 -2
- package/dist/commands/shared/outputFile.js +6 -1
- package/dist/commands/start.d.ts +20 -5
- package/dist/commands/start.js +84 -23
- package/dist/commands/stop.d.ts +11 -0
- package/dist/commands/stop.js +24 -1
- package/dist/commands/tail.d.ts +7 -1
- package/dist/commands/tail.js +13 -62
- package/dist/commands.js +2 -0
- package/dist/connection/cdp.d.ts +7 -0
- package/dist/connection/cdp.js +9 -0
- package/dist/connection/launcher.js +3 -2
- package/dist/daemon/SessionController.js +6 -1
- package/dist/daemon/launcher.d.ts +3 -2
- package/dist/daemon/launcher.js +47 -3
- package/dist/daemon/session/Session.d.ts +4 -1
- package/dist/daemon/session/Session.js +33 -2
- package/dist/daemon/session/TelemetryStore.d.ts +8 -1
- package/dist/daemon/session/TelemetryStore.js +13 -1
- package/dist/daemon/session/commandRegistry.js +36 -14
- package/dist/daemon/session/interactions.d.ts +2 -1
- package/dist/daemon/session/interactions.js +13 -1
- package/dist/daemon/session/plugins.js +19 -53
- package/dist/daemon/session/teardown.js +1 -1
- package/dist/daemon.js +9234 -7222
- package/dist/errors/messages.d.ts +88 -15
- package/dist/errors/messages.js +177 -27
- package/dist/index.js +19322 -13961
- package/dist/ipc/client.d.ts +22 -2
- package/dist/ipc/client.js +34 -5
- package/dist/ipc/protocol/auditTypes.d.ts +135 -0
- package/dist/ipc/protocol/auditTypes.js +6 -0
- package/dist/ipc/protocol/commands.d.ts +35 -0
- package/dist/ipc/protocol/commands.js +2 -0
- package/dist/ipc/protocol/domTypes.d.ts +16 -0
- package/dist/ipc/protocol/inspectTypes.d.ts +73 -8
- package/dist/ipc/session/types.d.ts +2 -0
- package/dist/runtime/css/search.d.ts +39 -0
- package/dist/runtime/css/search.js +122 -0
- package/dist/runtime/dom/actionEffects.d.ts +9 -2
- package/dist/runtime/dom/actionEffects.js +30 -14
- package/dist/runtime/dom/audit.d.ts +19 -0
- package/dist/runtime/dom/audit.js +37 -0
- package/dist/runtime/dom/auditModel.d.ts +45 -0
- package/dist/runtime/dom/auditModel.js +220 -0
- package/dist/runtime/dom/auditScripts.d.ts +113 -0
- package/dist/runtime/dom/auditScripts.js +148 -0
- package/dist/runtime/dom/elementGeometry.d.ts +16 -3
- package/dist/runtime/dom/elementGeometry.js +49 -10
- package/dist/runtime/dom/elementInfo.d.ts +74 -17
- package/dist/runtime/dom/elementInfo.js +187 -34
- package/dist/runtime/dom/evalHelpers.d.ts +12 -2
- package/dist/runtime/dom/evalHelpers.js +67 -7
- package/dist/runtime/dom/formDiscovery.d.ts +6 -2
- package/dist/runtime/dom/formDiscovery.js +20 -3
- package/dist/runtime/dom/formFillHelpers/fill.js +8 -12
- package/dist/runtime/dom/formFillHelpers/pressKey.js +2 -2
- package/dist/runtime/dom/formFillHelpers/shared.d.ts +16 -10
- package/dist/runtime/dom/formFillHelpers/shared.js +19 -52
- package/dist/runtime/dom/formSubmitHelpers.js +4 -3
- package/dist/runtime/dom/frameLayout.js +1 -0
- package/dist/runtime/dom/inspect.d.ts +7 -0
- package/dist/runtime/dom/inspect.js +92 -28
- package/dist/runtime/dom/inspectAllStyles.d.ts +16 -4
- package/dist/runtime/dom/inspectAllStyles.js +90 -7
- package/dist/runtime/dom/inspectCascade.d.ts +19 -2
- package/dist/runtime/dom/inspectCascade.js +214 -44
- package/dist/runtime/dom/inspectCascadeModel.d.ts +8 -0
- package/dist/runtime/dom/inspectCascadeModel.js +108 -34
- package/dist/runtime/dom/inspectHints.d.ts +26 -3
- package/dist/runtime/dom/inspectHints.js +125 -9
- package/dist/runtime/dom/inspectModel.d.ts +5 -1
- package/dist/runtime/dom/inspectModel.js +37 -10
- package/dist/runtime/dom/inspectPaintModel.d.ts +50 -22
- package/dist/runtime/dom/inspectPaintModel.js +182 -68
- package/dist/runtime/dom/inspectRules.d.ts +19 -0
- package/dist/runtime/dom/inspectRules.js +21 -5
- package/dist/runtime/dom/inspectScripts.d.ts +112 -12
- package/dist/runtime/dom/inspectScripts.js +357 -32
- package/dist/runtime/dom/inspectTree.js +10 -2
- package/dist/runtime/dom/inspectWhyModel.d.ts +2 -1
- package/dist/runtime/dom/inspectWhyModel.js +52 -10
- package/dist/runtime/dom/layout.js +40 -16
- package/dist/runtime/dom/reactEventHelpers.d.ts +21 -4
- package/dist/runtime/dom/reactEventHelpers.js +90 -36
- package/dist/runtime/dom/targetNode.d.ts +18 -5
- package/dist/runtime/dom/targetNode.js +268 -8
- package/dist/runtime/dom/wait.js +2 -1
- package/dist/runtime/page/bdgWorld.d.ts +57 -0
- package/dist/runtime/page/bdgWorld.js +180 -0
- package/dist/runtime/page/emulation.d.ts +13 -4
- package/dist/runtime/page/emulation.js +69 -4
- package/dist/runtime/page/replacedBuiltins.d.ts +28 -0
- package/dist/runtime/page/replacedBuiltins.js +136 -0
- package/dist/runtime/page/userAgent.d.ts +17 -0
- package/dist/runtime/page/userAgent.js +57 -0
- package/dist/session/QueryCacheManager.d.ts +4 -1
- package/dist/session/QueryCacheManager.js +5 -2
- package/dist/session/chrome.d.ts +4 -1
- package/dist/session/chrome.js +7 -1
- package/dist/session/cleanup/staleSession.d.ts +21 -4
- package/dist/session/cleanup/staleSession.js +79 -9
- package/dist/session/cleanup/userCommands.d.ts +4 -1
- package/dist/session/cleanup/userCommands.js +10 -5
- package/dist/session/daemonSocket.d.ts +10 -0
- package/dist/session/daemonSocket.js +22 -0
- package/dist/session/lastSession.d.ts +6 -3
- package/dist/session/lastSession.js +11 -5
- package/dist/session/paths.d.ts +3 -1
- package/dist/session/paths.js +5 -5
- package/dist/session/portClaims.js +4 -3
- package/dist/session/sessionList.d.ts +13 -5
- package/dist/session/sessionList.js +31 -7
- package/dist/telemetry/a11y.js +2 -2
- package/dist/telemetry/console.d.ts +2 -1
- package/dist/telemetry/console.js +30 -21
- package/dist/telemetry/pageCrash.d.ts +26 -0
- package/dist/telemetry/pageCrash.js +53 -0
- package/dist/types.d.ts +20 -0
- package/dist/ui/formatters/audit.d.ts +19 -0
- package/dist/ui/formatters/audit.js +115 -0
- package/dist/ui/formatters/cdp.d.ts +138 -0
- package/dist/ui/formatters/cdp.js +131 -0
- package/dist/ui/formatters/console/chronological.js +3 -1
- package/dist/ui/formatters/console/follow.d.ts +2 -1
- package/dist/ui/formatters/console/follow.js +2 -2
- package/dist/ui/formatters/console/json.d.ts +2 -2
- package/dist/ui/formatters/console/json.js +11 -5
- package/dist/ui/formatters/console/shared.d.ts +30 -0
- package/dist/ui/formatters/console/shared.js +16 -0
- package/dist/ui/formatters/console/summarize.d.ts +9 -2
- package/dist/ui/formatters/console/summarize.js +40 -9
- package/dist/ui/formatters/console.d.ts +2 -1
- package/dist/ui/formatters/console.js +7 -5
- package/dist/ui/formatters/details.js +3 -1
- package/dist/ui/formatters/dom.d.ts +2 -2
- package/dist/ui/formatters/dom.js +10 -8
- package/dist/ui/formatters/helpFormatters.js +1 -1
- package/dist/ui/formatters/inspect.js +50 -17
- package/dist/ui/formatters/installSkill.d.ts +9 -1
- package/dist/ui/formatters/installSkill.js +32 -6
- package/dist/ui/formatters/layout.js +2 -1
- package/dist/ui/formatters/networkList.d.ts +1 -1
- package/dist/ui/formatters/networkList.js +1 -2
- package/dist/ui/formatters/preview.d.ts +2 -0
- package/dist/ui/formatters/preview.js +17 -7
- package/dist/ui/formatters/sessions.d.ts +2 -2
- package/dist/ui/formatters/sessions.js +9 -2
- package/dist/ui/formatters/status.js +1 -1
- package/dist/ui/logging/logger.d.ts +1 -1
- package/dist/ui/messages/commands.d.ts +168 -11
- package/dist/ui/messages/commands.js +245 -18
- package/dist/ui/messages/consoleMessages.d.ts +24 -0
- package/dist/ui/messages/consoleMessages.js +32 -0
- package/dist/ui/messages/preview.d.ts +12 -0
- package/dist/ui/messages/preview.js +18 -2
- package/dist/ui/messages/session.d.ts +13 -2
- package/dist/ui/messages/session.js +22 -3
- package/dist/utils/cssValues.js +36 -4
- package/dist/utils/decisionTrees.js +0 -5
- package/dist/utils/directories.d.ts +34 -0
- package/dist/utils/directories.js +88 -0
- package/dist/utils/display.d.ts +16 -0
- package/dist/utils/display.js +42 -0
- package/dist/utils/exitCodes.d.ts +1 -0
- package/dist/utils/exitCodes.js +6 -0
- package/dist/utils/process.d.ts +12 -0
- package/dist/utils/process.js +25 -0
- package/dist/utils/suggestions.d.ts +4 -2
- package/dist/utils/suggestions.js +7 -5
- package/dist/utils/taskMappings.js +1 -1
- package/package.json +3 -2
package/dist/commands/stop.d.ts
CHANGED
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
import type { Command } from 'commander';
|
|
2
|
+
/**
|
|
3
|
+
* Wait until the stopped session's daemon has exited: it removes the
|
|
4
|
+
* session's files on the way out, and a command run right after (`bdg
|
|
5
|
+
* sessions`, a new start) would otherwise still see the session. The wait is
|
|
6
|
+
* bounded, as after a failed start.
|
|
7
|
+
*
|
|
8
|
+
* @param pid - Daemon PID read before the stop, or null when unknown
|
|
9
|
+
* @param waitMs - Milliseconds to wait at most
|
|
10
|
+
* @returns Warning when the daemon still runs after the wait
|
|
11
|
+
*/
|
|
12
|
+
export declare function waitForDaemonExit(pid: number | null, waitMs?: number): Promise<string | undefined>;
|
|
2
13
|
/**
|
|
3
14
|
* Register stop command
|
|
4
15
|
*
|
package/dist/commands/stop.js
CHANGED
|
@@ -3,13 +3,33 @@ import { jsonOption } from './shared/commonOptions.js';
|
|
|
3
3
|
import { stopSession } from '../ipc/client.js';
|
|
4
4
|
import { IPCErrorCode } from '../ipc/index.js';
|
|
5
5
|
import { IPCTimeoutError } from '../ipc/transport/index.js';
|
|
6
|
+
import { readLiveDaemonPid } from '../session/cleanup/staleSession.js';
|
|
6
7
|
import { joinLines } from '../ui/formatting.js';
|
|
7
8
|
import { chromeClosedMessage, orphanedDaemonsCleanedMessage, warningMessage, } from '../ui/messages/commands.js';
|
|
8
|
-
import { sessionStopped, STOP_MESSAGES, stopFailedError } from '../ui/messages/session.js';
|
|
9
|
+
import { daemonStillExitingHint, daemonStillExitingSuggestion, sessionStopped, STOP_MESSAGES, stopFailedError, } from '../ui/messages/session.js';
|
|
9
10
|
import { noActiveSessionMessage, sessionCommand, startSessionSuggestion, } from '../ui/messages/sessionCommand.js';
|
|
11
|
+
import { waitUntil } from '../utils/async.js';
|
|
10
12
|
import { getExitCodeForIPCError, isDaemonNotRunningError } from '../utils/errorMapping.js';
|
|
11
13
|
import { getErrorMessage } from '../utils/errors.js';
|
|
12
14
|
import { EXIT_CODES } from '../utils/exitCodes.js';
|
|
15
|
+
import { isProcessAlive } from '../utils/process.js';
|
|
16
|
+
/** How long `bdg stop` waits for the session's daemon to exit */
|
|
17
|
+
const DAEMON_EXIT_WAIT_MS = 3000;
|
|
18
|
+
/**
|
|
19
|
+
* Wait until the stopped session's daemon has exited: it removes the
|
|
20
|
+
* session's files on the way out, and a command run right after (`bdg
|
|
21
|
+
* sessions`, a new start) would otherwise still see the session. The wait is
|
|
22
|
+
* bounded, as after a failed start.
|
|
23
|
+
*
|
|
24
|
+
* @param pid - Daemon PID read before the stop, or null when unknown
|
|
25
|
+
* @param waitMs - Milliseconds to wait at most
|
|
26
|
+
* @returns Warning when the daemon still runs after the wait
|
|
27
|
+
*/
|
|
28
|
+
export async function waitForDaemonExit(pid, waitMs = DAEMON_EXIT_WAIT_MS) {
|
|
29
|
+
if (pid === null || (await waitUntil(() => !isProcessAlive(pid), waitMs)))
|
|
30
|
+
return undefined;
|
|
31
|
+
return `${daemonStillExitingHint(pid, waitMs)}; ${daemonStillExitingSuggestion()}`;
|
|
32
|
+
}
|
|
13
33
|
/**
|
|
14
34
|
* Format stop result for human-readable output.
|
|
15
35
|
*
|
|
@@ -36,8 +56,10 @@ export function registerStopCommand(program) {
|
|
|
36
56
|
.action(async (options) => {
|
|
37
57
|
await runCommand(async () => {
|
|
38
58
|
try {
|
|
59
|
+
const daemonPid = readLiveDaemonPid();
|
|
39
60
|
const response = await stopSession();
|
|
40
61
|
if (response.status === 'ok') {
|
|
62
|
+
const warning = await waitForDaemonExit(daemonPid);
|
|
41
63
|
return {
|
|
42
64
|
success: true,
|
|
43
65
|
data: {
|
|
@@ -48,6 +70,7 @@ export function registerStopCommand(program) {
|
|
|
48
70
|
},
|
|
49
71
|
orphanedDaemonsCount: 0,
|
|
50
72
|
message: response.message ?? STOP_MESSAGES.SUCCESS,
|
|
73
|
+
...(warning && { warnings: [warning] }),
|
|
51
74
|
},
|
|
52
75
|
};
|
|
53
76
|
}
|
package/dist/commands/tail.d.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* `bdg tail`: deprecated alias of `bdg peek --follow` (#115). It keeps its
|
|
3
|
+
* options and says to use `peek --follow` instead.
|
|
3
4
|
*/
|
|
4
5
|
import type { Command } from 'commander';
|
|
6
|
+
/**
|
|
7
|
+
* Register the deprecated `tail` command.
|
|
8
|
+
*
|
|
9
|
+
* @param program - Root command
|
|
10
|
+
*/
|
|
5
11
|
export declare function registerTailCommand(program: Command): void;
|
|
6
12
|
//# sourceMappingURL=tail.d.ts.map
|
package/dist/commands/tail.js
CHANGED
|
@@ -1,43 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* `bdg tail`: deprecated alias of `bdg peek --follow` (#115). It keeps its
|
|
3
|
+
* options and says to use `peek --follow` instead.
|
|
4
|
+
*/
|
|
5
|
+
import { followPreview } from './peek.js';
|
|
6
|
+
import { jsonOption } from './shared/commonOptions.js';
|
|
7
|
+
import { tailDeprecatedNotice } from '../ui/messages/preview.js';
|
|
8
|
+
/**
|
|
9
|
+
* Register the deprecated `tail` command.
|
|
10
|
+
*
|
|
11
|
+
* @param program - Root command
|
|
3
12
|
*/
|
|
4
|
-
import { jsonOption, showBothSectionsWhenBothRequested } from './shared/commonOptions.js';
|
|
5
|
-
import { noteFollowConnected } from './shared/daemonErrorHandler.js';
|
|
6
|
-
import { fetchPreviewOutput } from './shared/dataFetcher.js';
|
|
7
|
-
import { followFetchFailure, setupFollowMode, } from './shared/followMode.js';
|
|
8
|
-
import { handleValidationError } from './shared/handleValidationError.js';
|
|
9
|
-
import { MAX_LAST_ITEMS, positiveIntRule } from './shared/validation.js';
|
|
10
|
-
import { formatPreview } from '../ui/formatters/preview.js';
|
|
11
|
-
import { followingPreviewMessage, stoppedFollowingPreviewMessage } from '../ui/messages/preview.js';
|
|
12
|
-
function parseOptions(options) {
|
|
13
|
-
const lastRule = positiveIntRule({
|
|
14
|
-
name: '--last',
|
|
15
|
-
min: 1,
|
|
16
|
-
max: MAX_LAST_ITEMS,
|
|
17
|
-
default: 10,
|
|
18
|
-
allowZeroForAll: true,
|
|
19
|
-
});
|
|
20
|
-
const intervalRule = positiveIntRule({ name: '--interval', min: 100, max: 60000, default: 1000 });
|
|
21
|
-
return {
|
|
22
|
-
lastN: lastRule.validate(options.last),
|
|
23
|
-
interval: intervalRule.validate(options.interval),
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
function createPreviewOptions(options, lastN) {
|
|
27
|
-
return {
|
|
28
|
-
json: options.json,
|
|
29
|
-
network: options.network,
|
|
30
|
-
console: options.console,
|
|
31
|
-
last: lastN,
|
|
32
|
-
verbose: options.verbose,
|
|
33
|
-
follow: true,
|
|
34
|
-
viewedAt: new Date(),
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
13
|
export function registerTailCommand(program) {
|
|
38
14
|
program
|
|
39
15
|
.command('tail')
|
|
40
|
-
.description('
|
|
16
|
+
.description('Deprecated: use "bdg peek --follow" (same options)')
|
|
41
17
|
.addOption(jsonOption())
|
|
42
18
|
.option('-v, --verbose', 'Use verbose output with full URLs and formatting', false)
|
|
43
19
|
.option('-n, --network', 'Show only network requests', false)
|
|
@@ -45,33 +21,8 @@ export function registerTailCommand(program) {
|
|
|
45
21
|
.option('--last <count>', 'Show last N items (network requests + console messages), 0 for all', '10')
|
|
46
22
|
.option('--interval <ms>', 'Update interval in milliseconds', '1000')
|
|
47
23
|
.action(async (options) => {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
let interval;
|
|
51
|
-
try {
|
|
52
|
-
const parsed = parseOptions(options);
|
|
53
|
-
lastN = parsed.lastN;
|
|
54
|
-
interval = parsed.interval;
|
|
55
|
-
}
|
|
56
|
-
catch (error) {
|
|
57
|
-
handleValidationError(error, options.json ?? false);
|
|
58
|
-
}
|
|
59
|
-
const showPreview = async () => {
|
|
60
|
-
const result = await fetchPreviewOutput({ lastN });
|
|
61
|
-
if (!result.success) {
|
|
62
|
-
return followFetchFailure(result, { json: options.json, retryIntervalMs: interval });
|
|
63
|
-
}
|
|
64
|
-
noteFollowConnected();
|
|
65
|
-
if (!options.json)
|
|
66
|
-
console.clear();
|
|
67
|
-
console.log(formatPreview(result.data, createPreviewOptions(options, lastN)));
|
|
68
|
-
return undefined;
|
|
69
|
-
};
|
|
70
|
-
await setupFollowMode(showPreview, {
|
|
71
|
-
startMessage: followingPreviewMessage,
|
|
72
|
-
stopMessage: stoppedFollowingPreviewMessage,
|
|
73
|
-
intervalMs: interval,
|
|
74
|
-
});
|
|
24
|
+
console.error(tailDeprecatedNotice());
|
|
25
|
+
await followPreview({ ...options, follow: true });
|
|
75
26
|
});
|
|
76
27
|
}
|
|
77
28
|
//# sourceMappingURL=tail.js.map
|
package/dist/commands.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { registerCdpCommand } from './commands/cdp.js';
|
|
2
2
|
import { registerCleanupCommand } from './commands/cleanup.js';
|
|
3
3
|
import { registerConsoleCommand } from './commands/console.js';
|
|
4
|
+
import { registerCssCommands } from './commands/css.js';
|
|
4
5
|
import { registerDetailsCommand } from './commands/details.js';
|
|
5
6
|
import { registerFormInteractionCommands } from './commands/dom/formInteraction.js';
|
|
6
7
|
import { registerDomCommands } from './commands/dom/index.js';
|
|
@@ -39,6 +40,7 @@ export const commandRegistry = [
|
|
|
39
40
|
registerDomCommands,
|
|
40
41
|
registerFormInteractionCommands,
|
|
41
42
|
registerPageCommands,
|
|
43
|
+
registerCssCommands,
|
|
42
44
|
addCommandGroup('CDP Commands:'),
|
|
43
45
|
registerCdpCommand,
|
|
44
46
|
addCommandGroup('Network Commands:'),
|
package/dist/connection/cdp.d.ts
CHANGED
|
@@ -181,6 +181,13 @@ export declare class CDPConnection implements CDPEventSource {
|
|
|
181
181
|
* @returns Calculated delay in milliseconds (base delay * 2^attempt, capped at maxDelay)
|
|
182
182
|
*/
|
|
183
183
|
private calculateBackoffDelay;
|
|
184
|
+
/**
|
|
185
|
+
* Fail every command still waiting for an answer, e.g. after the page's
|
|
186
|
+
* renderer crashed: commands it was running are never answered.
|
|
187
|
+
*
|
|
188
|
+
* @param error - Error the waiting commands fail with
|
|
189
|
+
*/
|
|
190
|
+
rejectPending(error: Error): void;
|
|
184
191
|
/**
|
|
185
192
|
* Clear all pending command promises with the given error.
|
|
186
193
|
*
|
package/dist/connection/cdp.js
CHANGED
|
@@ -390,6 +390,15 @@ export class CDPConnection {
|
|
|
390
390
|
calculateBackoffDelay(attempt, maxDelay) {
|
|
391
391
|
return Math.min(this.config.baseRetryDelay * Math.pow(2, attempt), maxDelay);
|
|
392
392
|
}
|
|
393
|
+
/**
|
|
394
|
+
* Fail every command still waiting for an answer, e.g. after the page's
|
|
395
|
+
* renderer crashed: commands it was running are never answered.
|
|
396
|
+
*
|
|
397
|
+
* @param error - Error the waiting commands fail with
|
|
398
|
+
*/
|
|
399
|
+
rejectPending(error) {
|
|
400
|
+
this.clearPendingMessages(error);
|
|
401
|
+
}
|
|
393
402
|
/**
|
|
394
403
|
* Clear all pending command promises with the given error.
|
|
395
404
|
*
|
|
@@ -3,6 +3,7 @@ import * as os from 'os';
|
|
|
3
3
|
import * as path from 'path';
|
|
4
4
|
import * as chromeLauncher from 'chrome-launcher';
|
|
5
5
|
import { BDG_CHROME_PREFS, DEFAULT_CDP_PORT, CHROME_PROFILE_DIR, DEFAULT_CHROME_LOG_LEVEL, DEFAULT_CHROME_HANDLE_SIGINT, } from '../constants.js';
|
|
6
|
+
import { makeDirectory } from '../utils/directories.js';
|
|
6
7
|
import { getErrorMessage } from '../utils/errors.js';
|
|
7
8
|
import { filterDefined } from '../utils/objects.js';
|
|
8
9
|
import { isProcessAlive } from '../utils/process.js';
|
|
@@ -62,7 +63,7 @@ export async function launchChrome(options = {}) {
|
|
|
62
63
|
const userDataDir = options.userDataDir ?? getPersistentUserDataDir(options.baseDir ?? options.sessionDir);
|
|
63
64
|
if (!fs.existsSync(userDataDir)) {
|
|
64
65
|
try {
|
|
65
|
-
|
|
66
|
+
makeDirectory(userDataDir);
|
|
66
67
|
}
|
|
67
68
|
catch (error) {
|
|
68
69
|
throw new ChromeLaunchError(`Failed to create user data directory`, {
|
|
@@ -160,7 +161,7 @@ function getPersistentUserDataDir(baseDir) {
|
|
|
160
161
|
const userDataDir = path.join(dir, CHROME_PROFILE_DIR);
|
|
161
162
|
if (!fs.existsSync(userDataDir)) {
|
|
162
163
|
try {
|
|
163
|
-
|
|
164
|
+
makeDirectory(userDataDir);
|
|
164
165
|
}
|
|
165
166
|
catch (error) {
|
|
166
167
|
throw new ChromeLaunchError(`Failed to create user data directory`, {
|
|
@@ -241,8 +241,13 @@ export class SessionController {
|
|
|
241
241
|
duration: data.duration,
|
|
242
242
|
target: data.target,
|
|
243
243
|
data: { network: data.network, console: data.console },
|
|
244
|
-
totals: {
|
|
244
|
+
totals: {
|
|
245
|
+
network: data.totalNetwork,
|
|
246
|
+
console: data.totalConsole,
|
|
247
|
+
...(data.droppedConsole && { consoleDropped: data.droppedConsole }),
|
|
248
|
+
},
|
|
245
249
|
currentNavigationId: data.currentNavigationId,
|
|
250
|
+
...(data.pageCrashedAt !== undefined && { pageCrashedAt: data.pageCrashedAt }),
|
|
246
251
|
partial: true,
|
|
247
252
|
},
|
|
248
253
|
},
|
|
@@ -24,8 +24,9 @@ export declare function launchDaemon(): Promise<SpawnedDaemon | undefined>;
|
|
|
24
24
|
* Check that the session directory can hold the daemon's files before
|
|
25
25
|
* spawning it (otherwise the daemon dies and only its log says why).
|
|
26
26
|
*
|
|
27
|
-
* @throws SessionDirError (103) for a file
|
|
28
|
-
*
|
|
27
|
+
* @throws SessionDirError (103) for a file or a path that cannot hold a
|
|
28
|
+
* directory (a pseudo-filesystem like `/proc`), (81) for a too-long path
|
|
29
|
+
* like a named session's, (82) when not writable
|
|
29
30
|
*/
|
|
30
31
|
export declare function assertUsableSessionDir(): void;
|
|
31
32
|
//# sourceMappingURL=launcher.d.ts.map
|
package/dist/daemon/launcher.js
CHANGED
|
@@ -11,13 +11,16 @@ import fs from 'fs';
|
|
|
11
11
|
import { join } from 'path';
|
|
12
12
|
import { DaemonStartupError, SessionDirError } from './errors.js';
|
|
13
13
|
import { sessionDirIsFileError, sessionDirNotWritableError, socketPathTooLongError, } from '../errors/messages.js';
|
|
14
|
-
import {
|
|
14
|
+
import { killSessionChromes, readLiveDaemonPid } from '../session/cleanup/staleSession.js';
|
|
15
|
+
import { isDaemonAlive, isDaemonSocketGone } from '../session/daemonSocket.js';
|
|
15
16
|
import { MAX_DAEMON_SOCKET_PATH_BYTES, ensureSessionDir, getSessionDir, getSessionFilePath, } from '../session/paths.js';
|
|
16
17
|
import { createLogger } from '../ui/logging/index.js';
|
|
17
18
|
import { delay } from '../utils/async.js';
|
|
19
|
+
import { directoryProblem } from '../utils/directories.js';
|
|
18
20
|
import { getErrorMessage } from '../utils/errors.js';
|
|
19
21
|
import { EXIT_CODES } from '../utils/exitCodes.js';
|
|
20
22
|
import { DAEMON_SCRIPT_PATH } from '../utils/packageRoot.js';
|
|
23
|
+
import { isProcessAlive } from '../utils/process.js';
|
|
21
24
|
const log = createLogger('launcher');
|
|
22
25
|
const DAEMON_READY_TIMEOUT_MS = 5000;
|
|
23
26
|
const DAEMON_READY_POLL_MS = 20;
|
|
@@ -37,6 +40,7 @@ export async function launchDaemon() {
|
|
|
37
40
|
throw new DaemonStartupError(`Daemon script not found at ${DAEMON_SCRIPT_PATH}. Did you run 'npm run build'?`, 'DAEMON_SCRIPT_NOT_FOUND');
|
|
38
41
|
}
|
|
39
42
|
assertUsableSessionDir();
|
|
43
|
+
await stopUnreachableDaemon();
|
|
40
44
|
const logPath = join(getSessionDir(), 'daemon.log');
|
|
41
45
|
rotateLog(logPath);
|
|
42
46
|
const logFd = fs.openSync(logPath, 'a');
|
|
@@ -54,12 +58,48 @@ export async function launchDaemon() {
|
|
|
54
58
|
await waitForDaemonReady(() => exited);
|
|
55
59
|
return { pid: daemon.pid, hasExited: () => exited };
|
|
56
60
|
}
|
|
61
|
+
/** How long a daemon that lost its socket gets to end its session before it is killed */
|
|
62
|
+
const UNREACHABLE_DAEMON_EXIT_MS = 8000;
|
|
63
|
+
/**
|
|
64
|
+
* Stop a daemon of this session directory that still runs but can no longer
|
|
65
|
+
* be reached (its socket was removed, or refuses connections; one that is
|
|
66
|
+
* only slow to answer is left alone), before a new one starts: two daemons
|
|
67
|
+
* would launch two Chromes and remove each other's files. It is asked to end
|
|
68
|
+
* its session (closing its Chrome), and killed with what it launched if it
|
|
69
|
+
* does not exit in time.
|
|
70
|
+
*/
|
|
71
|
+
async function stopUnreachableDaemon() {
|
|
72
|
+
const pid = readLiveDaemonPid();
|
|
73
|
+
if (pid === null || !(await isDaemonSocketGone()))
|
|
74
|
+
return;
|
|
75
|
+
log.info(`Stopping daemon ${pid}, which can no longer be reached (its socket is gone)`);
|
|
76
|
+
try {
|
|
77
|
+
process.kill(pid, 'SIGTERM');
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
log.debug(`Daemon ${pid} not signalled: ${getErrorMessage(error)}`);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const deadline = Date.now() + UNREACHABLE_DAEMON_EXIT_MS;
|
|
84
|
+
while (isProcessAlive(pid) && Date.now() < deadline)
|
|
85
|
+
await delay(100);
|
|
86
|
+
if (!isProcessAlive(pid))
|
|
87
|
+
return;
|
|
88
|
+
try {
|
|
89
|
+
process.kill(pid, 'SIGKILL');
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
log.debug(`Daemon ${pid} not killed: ${getErrorMessage(error)}`);
|
|
93
|
+
}
|
|
94
|
+
killSessionChromes();
|
|
95
|
+
}
|
|
57
96
|
/**
|
|
58
97
|
* Check that the session directory can hold the daemon's files before
|
|
59
98
|
* spawning it (otherwise the daemon dies and only its log says why).
|
|
60
99
|
*
|
|
61
|
-
* @throws SessionDirError (103) for a file
|
|
62
|
-
*
|
|
100
|
+
* @throws SessionDirError (103) for a file or a path that cannot hold a
|
|
101
|
+
* directory (a pseudo-filesystem like `/proc`), (81) for a too-long path
|
|
102
|
+
* like a named session's, (82) when not writable
|
|
63
103
|
*/
|
|
64
104
|
export function assertUsableSessionDir() {
|
|
65
105
|
const dir = getSessionDir();
|
|
@@ -72,6 +112,10 @@ export function assertUsableSessionDir() {
|
|
|
72
112
|
if (Buffer.byteLength(socketPath) > MAX_DAEMON_SOCKET_PATH_BYTES) {
|
|
73
113
|
fail(socketPathTooLongError(socketPath, MAX_DAEMON_SOCKET_PATH_BYTES), EXIT_CODES.INVALID_ARGUMENTS);
|
|
74
114
|
}
|
|
115
|
+
const problem = directoryProblem(dir);
|
|
116
|
+
if (problem) {
|
|
117
|
+
fail(sessionDirNotWritableError(dir, problem.reason), problem.denied ? EXIT_CODES.PERMISSION_DENIED : EXIT_CODES.SESSION_FILE_ERROR);
|
|
118
|
+
}
|
|
75
119
|
try {
|
|
76
120
|
ensureSessionDir();
|
|
77
121
|
fs.accessSync(dir, fs.constants.W_OK);
|
|
@@ -80,7 +80,10 @@ export declare class Session {
|
|
|
80
80
|
*/
|
|
81
81
|
launch(): Promise<void>;
|
|
82
82
|
/**
|
|
83
|
-
* Execute a registered command against this session.
|
|
83
|
+
* Execute a registered command against this session. After a renderer
|
|
84
|
+
* crash only {@link RUN_ON_CRASHED_PAGE} commands run (`bdg cdp` only for
|
|
85
|
+
* {@link CRASH_SAFE_CDP} methods); others fail with exit 107 instead of
|
|
86
|
+
* waiting for a page that cannot answer.
|
|
84
87
|
*
|
|
85
88
|
* @param name - Command name
|
|
86
89
|
* @param params - Command parameters
|
|
@@ -18,6 +18,7 @@ import { readPageLoadingState } from '../../runtime/page/loadingState.js';
|
|
|
18
18
|
import { reapOrphanedChrome, removeSessionFiles } from '../../session/cleanup/staleSession.js';
|
|
19
19
|
import { writeSessionMetadata } from '../../session/metadata.js';
|
|
20
20
|
import { getSessionPort } from '../../session/port.js';
|
|
21
|
+
import { pageCrashedCommandError } from '../../telemetry/pageCrash.js';
|
|
21
22
|
import { createLogger } from '../../ui/logging/index.js';
|
|
22
23
|
import { formatChromeNotice } from '../../ui/messages/chrome.js';
|
|
23
24
|
import { delay } from '../../utils/async.js';
|
|
@@ -31,6 +32,26 @@ const log = createLogger('session');
|
|
|
31
32
|
const CRASH_SETTLE_MS = 500;
|
|
32
33
|
/** Ports tried when automatically chosen ports turn out to be taken */
|
|
33
34
|
const PORT_ATTEMPTS = 3;
|
|
35
|
+
/**
|
|
36
|
+
* Commands that still work after the page's renderer crashed: they read what
|
|
37
|
+
* the session collected, load the page again (`page reload`/`navigate`) or
|
|
38
|
+
* send raw CDP. Every other command needs the page and fails at once.
|
|
39
|
+
*/
|
|
40
|
+
const RUN_ON_CRASHED_PAGE = new Set([
|
|
41
|
+
'session_peek',
|
|
42
|
+
'session_details',
|
|
43
|
+
'session_status',
|
|
44
|
+
'session_har_data',
|
|
45
|
+
'session_network_headers',
|
|
46
|
+
'page_navigate',
|
|
47
|
+
'cdp_call',
|
|
48
|
+
]);
|
|
49
|
+
/**
|
|
50
|
+
* CDP methods `bdg cdp` may still send after a renderer crash: loading the
|
|
51
|
+
* page again, and domains the browser process answers. Others wait for the
|
|
52
|
+
* renderer, which never answers.
|
|
53
|
+
*/
|
|
54
|
+
const CRASH_SAFE_CDP = /^(Page\.(navigate|reload|getNavigationHistory|navigateToHistoryEntry)|Target\.|Browser\.|Inspector\.|Network\.(?!getResponseBody|getRequestPostData)|Storage\.|SystemInfo\.)/;
|
|
34
55
|
/**
|
|
35
56
|
* Whether a launch failed because another process holds the port.
|
|
36
57
|
*
|
|
@@ -127,7 +148,10 @@ export class Session {
|
|
|
127
148
|
}
|
|
128
149
|
}
|
|
129
150
|
/**
|
|
130
|
-
* Execute a registered command against this session.
|
|
151
|
+
* Execute a registered command against this session. After a renderer
|
|
152
|
+
* crash only {@link RUN_ON_CRASHED_PAGE} commands run (`bdg cdp` only for
|
|
153
|
+
* {@link CRASH_SAFE_CDP} methods); others fail with exit 107 instead of
|
|
154
|
+
* waiting for a page that cannot answer.
|
|
131
155
|
*
|
|
132
156
|
* @param name - Command name
|
|
133
157
|
* @param params - Command parameters
|
|
@@ -137,6 +161,13 @@ export class Session {
|
|
|
137
161
|
if (!this.cdp || !this.started || this.stopping) {
|
|
138
162
|
return Promise.reject(new Error('No active session'));
|
|
139
163
|
}
|
|
164
|
+
const crashedAt = this.store.pageCrashedAt;
|
|
165
|
+
const cdpMethod = name === 'cdp_call' ? params.method : undefined;
|
|
166
|
+
const needsPage = !RUN_ON_CRASHED_PAGE.has(name) ||
|
|
167
|
+
(cdpMethod !== undefined && !CRASH_SAFE_CDP.test(cdpMethod));
|
|
168
|
+
if (crashedAt !== undefined && needsPage) {
|
|
169
|
+
return Promise.reject(pageCrashedCommandError(crashedAt));
|
|
170
|
+
}
|
|
140
171
|
return this.registry[name](this.cdp, params);
|
|
141
172
|
}
|
|
142
173
|
/**
|
|
@@ -223,7 +254,7 @@ export class Session {
|
|
|
223
254
|
await this.releaseResources();
|
|
224
255
|
if (!this.started)
|
|
225
256
|
return;
|
|
226
|
-
removeSessionFiles();
|
|
257
|
+
removeSessionFiles(process.pid);
|
|
227
258
|
this.onEnded(reason);
|
|
228
259
|
})();
|
|
229
260
|
return this.stopping;
|
|
@@ -7,11 +7,18 @@ export declare class TelemetryStore {
|
|
|
7
7
|
/** Requests still in flight, keyed by CDP requestId */
|
|
8
8
|
readonly pendingNetworkRequests: Map<string, PendingRequest>;
|
|
9
9
|
readonly consoleMessages: ConsoleMessage[];
|
|
10
|
+
/**
|
|
11
|
+
* Console messages dropped at the limit, oldest first: the index of
|
|
12
|
+
* `consoleMessages[i]` in the session is `consoleDropped + i`
|
|
13
|
+
*/
|
|
14
|
+
consoleDropped: number;
|
|
10
15
|
readonly navigationEvents: NavigationEvent[];
|
|
11
16
|
readonly websocketConnections: WebSocketConnection[];
|
|
12
17
|
/** JavaScript dialogs accepted during the session */
|
|
13
18
|
readonly dialogs: DialogInfo[];
|
|
14
19
|
activeTelemetry: TelemetryType[];
|
|
20
|
+
/** When the page's renderer crashed (epoch ms); undefined while the page is alive */
|
|
21
|
+
pageCrashedAt: number | undefined;
|
|
15
22
|
getCurrentNavigationId: (() => number) | null;
|
|
16
23
|
sessionStartTime: number;
|
|
17
24
|
targetInfo: CDPTarget | null;
|
|
@@ -21,7 +28,7 @@ export declare class TelemetryStore {
|
|
|
21
28
|
/**
|
|
22
29
|
* Record an accepted dialog, and show it among the console messages when
|
|
23
30
|
* console telemetry is collected (it is otherwise invisible: bdg accepts it
|
|
24
|
-
* before anyone could see it).
|
|
31
|
+
* before anyone could see it), dropping the oldest message at the limit.
|
|
25
32
|
*
|
|
26
33
|
* @param dialog - Dialog type and text
|
|
27
34
|
*/
|
|
@@ -1,14 +1,22 @@
|
|
|
1
|
+
import { MAX_CONSOLE_MESSAGES } from '../../constants.js';
|
|
1
2
|
import { dialogConsoleText } from '../../ui/messages/commands.js';
|
|
2
3
|
export class TelemetryStore {
|
|
3
4
|
networkRequests = [];
|
|
4
5
|
/** Requests still in flight, keyed by CDP requestId */
|
|
5
6
|
pendingNetworkRequests = new Map();
|
|
6
7
|
consoleMessages = [];
|
|
8
|
+
/**
|
|
9
|
+
* Console messages dropped at the limit, oldest first: the index of
|
|
10
|
+
* `consoleMessages[i]` in the session is `consoleDropped + i`
|
|
11
|
+
*/
|
|
12
|
+
consoleDropped = 0;
|
|
7
13
|
navigationEvents = [];
|
|
8
14
|
websocketConnections = [];
|
|
9
15
|
/** JavaScript dialogs accepted during the session */
|
|
10
16
|
dialogs = [];
|
|
11
17
|
activeTelemetry = [];
|
|
18
|
+
/** When the page's renderer crashed (epoch ms); undefined while the page is alive */
|
|
19
|
+
pageCrashedAt;
|
|
12
20
|
getCurrentNavigationId = null;
|
|
13
21
|
sessionStartTime = Date.now();
|
|
14
22
|
targetInfo = null;
|
|
@@ -24,7 +32,7 @@ export class TelemetryStore {
|
|
|
24
32
|
/**
|
|
25
33
|
* Record an accepted dialog, and show it among the console messages when
|
|
26
34
|
* console telemetry is collected (it is otherwise invisible: bdg accepts it
|
|
27
|
-
* before anyone could see it).
|
|
35
|
+
* before anyone could see it), dropping the oldest message at the limit.
|
|
28
36
|
*
|
|
29
37
|
* @param dialog - Dialog type and text
|
|
30
38
|
*/
|
|
@@ -32,6 +40,10 @@ export class TelemetryStore {
|
|
|
32
40
|
this.dialogs.push(dialog);
|
|
33
41
|
if (!this.activeTelemetry.includes('console'))
|
|
34
42
|
return;
|
|
43
|
+
if (this.consoleMessages.length >= MAX_CONSOLE_MESSAGES) {
|
|
44
|
+
this.consoleMessages.shift();
|
|
45
|
+
this.consoleDropped++;
|
|
46
|
+
}
|
|
35
47
|
this.consoleMessages.push({
|
|
36
48
|
type: 'info',
|
|
37
49
|
text: dialogConsoleText(dialog),
|