browser-debugger-cli 0.13.0 → 0.14.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 +100 -186
- package/README.md +4 -4
- package/dist/commands/console.js +5 -1
- package/dist/commands/dom/a11y.d.ts +1 -1
- package/dist/commands/dom/a11y.js +20 -20
- package/dist/commands/dom/eval.d.ts +2 -1
- package/dist/commands/dom/eval.js +21 -3
- package/dist/commands/dom/formInteraction.js +1 -1
- package/dist/commands/dom/get.js +25 -7
- package/dist/commands/dom/index.js +7 -2
- package/dist/commands/dom/query.d.ts +2 -1
- package/dist/commands/dom/query.js +5 -3
- package/dist/commands/dom/screenshot.js +1 -0
- package/dist/commands/helpJson.js +1 -1
- package/dist/commands/network/list.js +46 -3
- package/dist/commands/optionBehaviors.d.ts +25 -2
- package/dist/commands/optionBehaviors.js +55 -42
- package/dist/commands/peek.js +3 -0
- package/dist/commands/shared/CommandRunner.js +13 -13
- package/dist/commands/shared/daemonErrorHandler.js +2 -2
- package/dist/commands/shared/dataFetcher.d.ts +4 -2
- package/dist/commands/shared/dataFetcher.js +11 -3
- package/dist/commands/shared/handleValidationError.js +3 -3
- package/dist/commands/shared/optionTypes.d.ts +14 -3
- package/dist/commands/shared/startHelpers.js +3 -3
- package/dist/connection/chromeIdentity.d.ts +8 -2
- package/dist/connection/chromeIdentity.js +85 -13
- package/dist/constants.d.ts +29 -1
- package/dist/constants.js +35 -1
- package/dist/daemon/SessionController.js +2 -0
- package/dist/daemon/session/Session.d.ts +2 -1
- package/dist/daemon/session/Session.js +10 -2
- package/dist/daemon/session/TelemetryStore.d.ts +7 -0
- package/dist/daemon/session/TelemetryStore.js +6 -0
- package/dist/daemon/session/commandRegistry.js +23 -5
- package/dist/daemon/session/matchedStylesReset.d.ts +26 -0
- package/dist/daemon/session/matchedStylesReset.js +46 -0
- package/dist/daemon/session/plugins.js +1 -0
- package/dist/daemon/session/triggeredRequests.d.ts +0 -5
- package/dist/daemon/session/triggeredRequests.js +13 -7
- package/dist/daemon.js +742 -460
- package/dist/errors/messages.d.ts +8 -0
- package/dist/errors/messages.js +10 -0
- package/dist/index.js +710 -518
- package/dist/ipc/protocol/commands.d.ts +4 -0
- package/dist/ipc/protocol/inspectTypes.d.ts +5 -2
- package/dist/ipc/session/types.d.ts +5 -1
- package/dist/program.d.ts +14 -0
- package/dist/program.js +53 -0
- package/dist/runtime/dom/elementGeometry.d.ts +23 -0
- package/dist/runtime/dom/elementGeometry.js +17 -15
- package/dist/runtime/dom/elementInfo.d.ts +6 -4
- package/dist/runtime/dom/elementInfo.js +7 -4
- package/dist/runtime/dom/frameScopedConnection.d.ts +7 -0
- package/dist/runtime/dom/frameScopedConnection.js +2 -2
- package/dist/runtime/dom/inspect.d.ts +17 -3
- package/dist/runtime/dom/inspect.js +40 -26
- package/dist/runtime/dom/inspectModel.d.ts +3 -3
- package/dist/runtime/dom/inspectRules.d.ts +29 -3
- package/dist/runtime/dom/inspectRules.js +205 -11
- package/dist/runtime/dom/layout.d.ts +0 -2
- package/dist/runtime/dom/layout.js +1 -2
- package/dist/runtime/dom/reactEventHelpers.d.ts +4 -1
- package/dist/runtime/dom/reactEventHelpers.js +9 -2
- package/dist/runtime/dom/targetNode.d.ts +10 -6
- package/dist/runtime/dom/targetNode.js +15 -8
- package/dist/telemetry/a11y.d.ts +15 -1
- package/dist/telemetry/a11y.js +83 -0
- package/dist/telemetry/har/builder.js +1 -1
- package/dist/telemetry/network.d.ts +13 -16
- package/dist/telemetry/network.js +30 -52
- package/dist/telemetry/networkRetention.d.ts +83 -0
- package/dist/telemetry/networkRetention.js +117 -0
- package/dist/types.d.ts +26 -0
- package/dist/ui/OutputBuilder.d.ts +10 -0
- package/dist/ui/OutputBuilder.js +12 -0
- package/dist/ui/formatters/a11y.d.ts +5 -7
- package/dist/ui/formatters/a11y.js +7 -61
- package/dist/ui/formatters/console/chronological.js +4 -4
- package/dist/ui/formatters/console/follow.d.ts +4 -2
- package/dist/ui/formatters/console/follow.js +6 -3
- package/dist/ui/formatters/console/json.d.ts +3 -6
- package/dist/ui/formatters/console/json.js +9 -13
- package/dist/ui/formatters/console/shared.d.ts +17 -2
- package/dist/ui/formatters/console/shared.js +17 -0
- package/dist/ui/formatters/console/summarize.d.ts +2 -2
- package/dist/ui/formatters/console/summarize.js +22 -7
- package/dist/ui/formatters/console.d.ts +1 -1
- package/dist/ui/formatters/console.js +1 -5
- package/dist/ui/formatters/details.js +1 -1
- package/dist/ui/formatters/dom.d.ts +13 -4
- package/dist/ui/formatters/dom.js +25 -7
- package/dist/ui/formatters/layout.js +2 -1
- package/dist/ui/formatters/longValues.d.ts +14 -0
- package/dist/ui/formatters/longValues.js +23 -0
- package/dist/ui/formatters/networkList.d.ts +8 -2
- package/dist/ui/formatters/networkList.js +11 -2
- package/dist/ui/formatters/preview.d.ts +4 -1
- package/dist/ui/formatters/preview.js +55 -13
- package/dist/ui/formatters/status.js +7 -0
- package/dist/ui/formatters/triggeredRequests.js +2 -1
- package/dist/ui/messages/chrome.d.ts +20 -1
- package/dist/ui/messages/chrome.js +29 -3
- package/dist/ui/messages/commands.d.ts +29 -8
- package/dist/ui/messages/commands.js +36 -8
- package/dist/ui/messages/networkMessages.d.ts +24 -0
- package/dist/ui/messages/networkMessages.js +45 -0
- package/dist/utils/http.d.ts +9 -2
- package/dist/utils/http.js +4 -3
- package/dist/utils/strings.d.ts +19 -0
- package/dist/utils/strings.js +16 -0
- package/package.json +2 -2
|
@@ -1,12 +1,15 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { buildSuccessResponse } from '../OutputBuilder.js';
|
|
3
|
-
import { formatTimestamp } from './console/shared.js';
|
|
1
|
+
import { MAX_CONSOLE_TEXT_LENGTH, MIME_TYPE_RULES, RESOURCE_TYPE_ABBREVIATIONS, } from '../../constants.js';
|
|
2
|
+
import { buildSuccessResponse, stringifyEnvelope } from '../OutputBuilder.js';
|
|
3
|
+
import { capMessageText, formatTimestamp } from './console/shared.js';
|
|
4
|
+
import { capForDisplay } from './longValues.js';
|
|
4
5
|
import { failureReason, formatRequestStatus, getRequestState, } from './requestStatus.js';
|
|
5
6
|
import { OutputFormatter, truncateUrl, truncateText } from '../formatting.js';
|
|
6
|
-
import { withPageCrashedNote } from '../messages/commands.js';
|
|
7
|
-
import { PREVIEW_EMPTY_STATES, PREVIEW_HEADERS, compactTipsMessage, verboseCommandsMessage, } from '../messages/preview.js';
|
|
7
|
+
import { moreCharsNote, withPageCrashedNote } from '../messages/commands.js';
|
|
8
8
|
import { consoleDroppedNote } from '../messages/consoleMessages.js';
|
|
9
|
+
import { networkEvictedNote } from '../messages/networkMessages.js';
|
|
10
|
+
import { PREVIEW_EMPTY_STATES, PREVIEW_HEADERS, compactTipsMessage, verboseCommandsMessage, } from '../messages/preview.js';
|
|
9
11
|
import { sessionCommand } from '../messages/sessionCommand.js';
|
|
12
|
+
import { capLength } from '../../utils/strings.js';
|
|
10
13
|
/**
|
|
11
14
|
* Infer resource type from MIME type when CDP doesn't provide it.
|
|
12
15
|
*
|
|
@@ -55,7 +58,8 @@ export function formatPreview(output, options) {
|
|
|
55
58
|
return formatPreviewHumanReadable(output, options);
|
|
56
59
|
}
|
|
57
60
|
/**
|
|
58
|
-
* Build the JSON payload for a preview, honoring the section and `--last`
|
|
61
|
+
* Build the JSON payload for a preview, honoring the section and `--last`
|
|
62
|
+
* filters; console texts are cut with `truncatedFrom` unless `--full`.
|
|
59
63
|
*
|
|
60
64
|
* @param output - Preview output from the daemon
|
|
61
65
|
* @param options - Preview options (section filters, last N)
|
|
@@ -73,7 +77,10 @@ export function buildPreviewJsonData(output, options) {
|
|
|
73
77
|
...(output.totals && { totals: output.totals }),
|
|
74
78
|
...(output.pageCrashedAt !== undefined && { pageCrashedAt: output.pageCrashedAt }),
|
|
75
79
|
...(pick('network') && output.data.network && { network: last(output.data.network) }),
|
|
76
|
-
...(pick('console') &&
|
|
80
|
+
...(pick('console') &&
|
|
81
|
+
output.data.console && {
|
|
82
|
+
console: last(output.data.console)?.map((message) => capMessageText(message, options.full)),
|
|
83
|
+
}),
|
|
77
84
|
};
|
|
78
85
|
}
|
|
79
86
|
/**
|
|
@@ -81,12 +88,12 @@ export function buildPreviewJsonData(output, options) {
|
|
|
81
88
|
*
|
|
82
89
|
* @param output - Preview output
|
|
83
90
|
* @param options - Preview options
|
|
84
|
-
* @returns `{ version, success, data }` envelope,
|
|
85
|
-
* line in follow mode (one object per line, NDJSON)
|
|
91
|
+
* @returns `{ version, success, data }` envelope, indented on a terminal, and
|
|
92
|
+
* always on one line in follow mode (one object per line, NDJSON)
|
|
86
93
|
*/
|
|
87
94
|
function formatPreviewAsJson(output, options) {
|
|
88
95
|
const envelope = buildSuccessResponse(buildPreviewJsonData(output, options));
|
|
89
|
-
return options.follow ? JSON.stringify(envelope) :
|
|
96
|
+
return options.follow ? JSON.stringify(envelope) : stringifyEnvelope(envelope);
|
|
90
97
|
}
|
|
91
98
|
/**
|
|
92
99
|
* Format preview as human-readable output, after a warning when the page
|
|
@@ -98,6 +105,24 @@ function formatPreviewHumanReadable(output, options) {
|
|
|
98
105
|
: formatPreviewCompact(output, options);
|
|
99
106
|
return withPageCrashedNote(body, output.pageCrashedAt);
|
|
100
107
|
}
|
|
108
|
+
/**
|
|
109
|
+
* A console message text in the compact preview: cut like `console --list`
|
|
110
|
+
* cuts it and to its first two lines, or whole with `--full`. The pointer
|
|
111
|
+
* naming `--full` comes after the line cut, so it is always shown.
|
|
112
|
+
*
|
|
113
|
+
* @param text - Message text
|
|
114
|
+
* @param full - `--full`
|
|
115
|
+
* @returns Text to print
|
|
116
|
+
*/
|
|
117
|
+
function compactConsoleText(text, full) {
|
|
118
|
+
if (full)
|
|
119
|
+
return text;
|
|
120
|
+
const capped = capLength(text, MAX_CONSOLE_TEXT_LENGTH);
|
|
121
|
+
const shown = truncateText(capped.text, 2);
|
|
122
|
+
return capped.truncatedFrom === undefined
|
|
123
|
+
? shown
|
|
124
|
+
: `${shown}${moreCharsNote(capped.truncatedFrom - capped.text.length)}`;
|
|
125
|
+
}
|
|
101
126
|
/**
|
|
102
127
|
* Format preview in compact format (default)
|
|
103
128
|
* Token-efficient output optimized for AI agents
|
|
@@ -119,6 +144,9 @@ function formatPreviewCompact(output, options) {
|
|
|
119
144
|
const totalCount = output.totals?.network ?? output.data.network.length;
|
|
120
145
|
const limitHint = formatLimitHint(showingCount, totalCount);
|
|
121
146
|
fmt.text(`NETWORK (${showingCount}/${totalCount})${limitHint}:`);
|
|
147
|
+
const evictedNote = previewEvictedNote(output);
|
|
148
|
+
if (evictedNote)
|
|
149
|
+
fmt.text(` ${evictedNote}`);
|
|
122
150
|
if (requests.length === 0) {
|
|
123
151
|
if (options.filteredTypes &&
|
|
124
152
|
options.filteredTypes.length > 0 &&
|
|
@@ -159,8 +187,7 @@ function formatPreviewCompact(output, options) {
|
|
|
159
187
|
else {
|
|
160
188
|
const consoleLines = messages.map((msg) => {
|
|
161
189
|
const prefix = msg.type.toUpperCase().padEnd(5);
|
|
162
|
-
|
|
163
|
-
return `${prefix} ${text}`;
|
|
190
|
+
return `${prefix} ${compactConsoleText(msg.text, options.full)}`;
|
|
164
191
|
});
|
|
165
192
|
fmt.list(consoleLines, 2);
|
|
166
193
|
}
|
|
@@ -172,6 +199,18 @@ function formatPreviewCompact(output, options) {
|
|
|
172
199
|
}
|
|
173
200
|
return fmt.build();
|
|
174
201
|
}
|
|
202
|
+
/**
|
|
203
|
+
* Note that the session dropped requests or evicted bodies at its capture limits.
|
|
204
|
+
*
|
|
205
|
+
* @param output - Preview output with its totals
|
|
206
|
+
* @returns Note text, or undefined when nothing was let go
|
|
207
|
+
*/
|
|
208
|
+
function previewEvictedNote(output) {
|
|
209
|
+
return networkEvictedNote({
|
|
210
|
+
requestsDropped: output.totals?.networkDropped ?? 0,
|
|
211
|
+
bodiesEvicted: output.totals?.networkBodiesEvicted ?? 0,
|
|
212
|
+
});
|
|
213
|
+
}
|
|
175
214
|
/**
|
|
176
215
|
* Format preview in verbose format (opt-in with --verbose)
|
|
177
216
|
* Original human-friendly output with Unicode formatting
|
|
@@ -198,6 +237,9 @@ function formatPreviewVerbose(output, options) {
|
|
|
198
237
|
? `Network Requests (all ${requests.length})`
|
|
199
238
|
: `Network Requests (last ${requests.length} of ${output.totals?.network ?? output.data.network.length})`;
|
|
200
239
|
fmt.text(title).separator('━', 50);
|
|
240
|
+
const evictedNote = previewEvictedNote(output);
|
|
241
|
+
if (evictedNote)
|
|
242
|
+
fmt.text(evictedNote);
|
|
201
243
|
if (requests.length === 0) {
|
|
202
244
|
if (options.filteredTypes &&
|
|
203
245
|
options.filteredTypes.length > 0 &&
|
|
@@ -248,7 +290,7 @@ function formatPreviewVerbose(output, options) {
|
|
|
248
290
|
else {
|
|
249
291
|
messages.forEach((msg) => {
|
|
250
292
|
const icon = msg.type === 'error' ? 'ERR' : msg.type === 'warning' ? 'WARN' : 'INFO';
|
|
251
|
-
fmt.text(`${icon} [${msg.type}] ${msg.text}`);
|
|
293
|
+
fmt.text(`${icon} [${msg.type}] ${capForDisplay(msg.text, MAX_CONSOLE_TEXT_LENGTH, options.full)}`);
|
|
252
294
|
});
|
|
253
295
|
}
|
|
254
296
|
fmt.blank();
|
|
@@ -2,6 +2,7 @@ import { describeRunningChrome } from '../../session/chrome.js';
|
|
|
2
2
|
import { calculateDuration, formatTimeAgo } from '../../session/statusData.js';
|
|
3
3
|
import { OutputFormatter } from '../formatting.js';
|
|
4
4
|
import { colorSchemeLabel, sessionActiveLine } from '../messages/commands.js';
|
|
5
|
+
import { networkEvictedNote } from '../messages/networkMessages.js';
|
|
5
6
|
import { lastSessionEndText } from '../messages/session.js';
|
|
6
7
|
import { noActiveSessionMessage, sessionCommand } from '../messages/sessionCommand.js';
|
|
7
8
|
import { isProcessAlive } from '../../utils/process.js';
|
|
@@ -51,6 +52,12 @@ export function formatSessionStatus(metadata, pid, activity, pageState, verbose
|
|
|
51
52
|
if (activity.lastNetworkRequestAt) {
|
|
52
53
|
fmt.keyValue(' Last Request', formatTimeAgo(activity.lastNetworkRequestAt), 18);
|
|
53
54
|
}
|
|
55
|
+
const evictedNote = networkEvictedNote({
|
|
56
|
+
requestsDropped: activity.networkRequestsDropped ?? 0,
|
|
57
|
+
bodiesEvicted: activity.networkBodiesEvicted ?? 0,
|
|
58
|
+
});
|
|
59
|
+
if (evictedNote)
|
|
60
|
+
fmt.text(` ${evictedNote}`);
|
|
54
61
|
fmt.keyValue('Console Messages', `${activity.consoleMessagesCaptured} captured`, 18);
|
|
55
62
|
if (activity.lastConsoleMessageAt) {
|
|
56
63
|
fmt.keyValue(' Last Message', formatTimeAgo(activity.lastConsoleMessageAt), 18);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Human output of the network requests a DOM action triggered.
|
|
3
3
|
*/
|
|
4
|
+
import { MAX_TRIGGERED_REQUESTS } from '../../constants.js';
|
|
4
5
|
import { assetTypeNames, isNotableRequest } from '../../telemetry/requestKinds.js';
|
|
5
6
|
import { formatRequestStatus } from './requestStatus.js';
|
|
6
7
|
import { formatDuration, truncateUrl } from '../formatting.js';
|
|
@@ -57,7 +58,7 @@ export function formatTriggeredRequestLines(requests, omitted = 0) {
|
|
|
57
58
|
const lines = notable.slice(0, MAX_TRIGGERED_REQUESTS_SHOWN).map(formatTriggeredRequest);
|
|
58
59
|
const hidden = notable.length - lines.length + omitted;
|
|
59
60
|
if (hidden > 0)
|
|
60
|
-
lines.push(omitted > 0 ? moreRequestsNote(hidden) : moreMatchesNote(hidden));
|
|
61
|
+
lines.push(omitted > 0 ? moreRequestsNote(hidden) : moreMatchesNote(hidden, MAX_TRIGGERED_REQUESTS));
|
|
61
62
|
if (assets.length > 0)
|
|
62
63
|
lines.push(assetRequestsNote(assets.length, assetTypeNames(assets)));
|
|
63
64
|
return lines;
|
|
@@ -158,9 +158,28 @@ export declare function chromeBinaryOverrideIsDirectory(path: string, source: st
|
|
|
158
158
|
* Generate error when CDP port is already in use.
|
|
159
159
|
*
|
|
160
160
|
* @param port - Port number that is in use
|
|
161
|
+
* @param reason - What was found on the port, when known
|
|
161
162
|
* @returns Multi-line formatted error message with troubleshooting steps
|
|
162
163
|
*/
|
|
163
|
-
export declare function portInUseError(port: number): string;
|
|
164
|
+
export declare function portInUseError(port: number, reason?: string): string;
|
|
165
|
+
/**
|
|
166
|
+
* Why a launched Chrome is not the one answering on 127.0.0.1:<port>.
|
|
167
|
+
*
|
|
168
|
+
* @param answeredBy - What answers there: a different browser, another
|
|
169
|
+
* process, or nothing (the address is held but does not answer)
|
|
170
|
+
* @param chromeHost - Address the launched Chrome listens on
|
|
171
|
+
* @returns Reason for the PORT_IN_USE issue
|
|
172
|
+
*/
|
|
173
|
+
export declare function portTakenByReason(answeredBy: 'browser' | 'process' | 'nothing', chromeHost: string): string;
|
|
174
|
+
/**
|
|
175
|
+
* Why a launch failed when Chrome announced its port but did not answer on
|
|
176
|
+
* it in time (a slow start, not a port conflict).
|
|
177
|
+
*
|
|
178
|
+
* @param port - Port Chrome announced
|
|
179
|
+
* @param waitedMs - How long bdg waited
|
|
180
|
+
* @returns Reason for the CHROME_LAUNCH_FAILED issue
|
|
181
|
+
*/
|
|
182
|
+
export declare function chromeNotAnsweringReason(port: number, waitedMs: number): string;
|
|
164
183
|
/**
|
|
165
184
|
* Warning when bdg's Chrome preferences (password manager and leak check
|
|
166
185
|
* off, etc.) could not be written into the profile.
|
|
@@ -37,7 +37,7 @@ export function formatChromeIssue(issue) {
|
|
|
37
37
|
const ctx = issue.context ?? {};
|
|
38
38
|
switch (issue.code) {
|
|
39
39
|
case 'PORT_IN_USE':
|
|
40
|
-
return portInUseError(ctx['port']);
|
|
40
|
+
return portInUseError(ctx['port'], ctx['reason']);
|
|
41
41
|
case 'INVALID_PORT':
|
|
42
42
|
return invalidPortError(ctx['port']);
|
|
43
43
|
case 'USER_DATA_DIR_CREATE_FAILED':
|
|
@@ -274,10 +274,36 @@ export function chromeBinaryOverrideIsDirectory(path, source) {
|
|
|
274
274
|
* Generate error when CDP port is already in use.
|
|
275
275
|
*
|
|
276
276
|
* @param port - Port number that is in use
|
|
277
|
+
* @param reason - What was found on the port, when known
|
|
277
278
|
* @returns Multi-line formatted error message with troubleshooting steps
|
|
278
279
|
*/
|
|
279
|
-
export function portInUseError(port) {
|
|
280
|
-
return joinLines(`Port ${port} is already in use.\n`, 'Another program (or a Chrome left from a previous session) is listening on it.\n', 'Try:', ` - Use a different port: ${sessionCommand(`bdg <url> --port ${port + 1}`)}`, ` - If a bdg session holds it: find it with bdg sessions, then end that one: bdg stop --session <name> (bdg cleanup --force --session <name> if it is stuck)`, ` - See what uses the port: lsof -i :${port}`);
|
|
280
|
+
export function portInUseError(port, reason) {
|
|
281
|
+
return joinLines(reason ? `Port ${port} is already in use: ${reason}.\n` : `Port ${port} is already in use.\n`, 'Another program (or a Chrome left from a previous session) is listening on it.\n', 'Try:', ` - Use a different port: ${sessionCommand(`bdg <url> --port ${port + 1}`)}`, ` - If a bdg session holds it: find it with bdg sessions, then end that one: bdg stop --session <name> (bdg cleanup --force --session <name> if it is stuck)`, ` - See what uses the port: lsof -i :${port}`);
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Why a launched Chrome is not the one answering on 127.0.0.1:<port>.
|
|
285
|
+
*
|
|
286
|
+
* @param answeredBy - What answers there: a different browser, another
|
|
287
|
+
* process, or nothing (the address is held but does not answer)
|
|
288
|
+
* @param chromeHost - Address the launched Chrome listens on
|
|
289
|
+
* @returns Reason for the PORT_IN_USE issue
|
|
290
|
+
*/
|
|
291
|
+
export function portTakenByReason(answeredBy, chromeHost) {
|
|
292
|
+
if (answeredBy === 'nothing')
|
|
293
|
+
return `something holds 127.0.0.1 (Chrome fell back to ${chromeHost})`;
|
|
294
|
+
const other = answeredBy === 'browser' ? 'another browser' : 'another process';
|
|
295
|
+
return `${other} answers on 127.0.0.1 (Chrome listens on ${chromeHost})`;
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Why a launch failed when Chrome announced its port but did not answer on
|
|
299
|
+
* it in time (a slow start, not a port conflict).
|
|
300
|
+
*
|
|
301
|
+
* @param port - Port Chrome announced
|
|
302
|
+
* @param waitedMs - How long bdg waited
|
|
303
|
+
* @returns Reason for the CHROME_LAUNCH_FAILED issue
|
|
304
|
+
*/
|
|
305
|
+
export function chromeNotAnsweringReason(port, waitedMs) {
|
|
306
|
+
return `Chrome announced port ${port} but did not answer on 127.0.0.1 within ${(waitedMs / 1000).toFixed(1)}s (slow start)`;
|
|
281
307
|
}
|
|
282
308
|
/**
|
|
283
309
|
* Warning when bdg's Chrome preferences (password manager and leak check
|
|
@@ -134,14 +134,13 @@ export declare function valueMismatchWarning(mismatch: FillValueMismatch): strin
|
|
|
134
134
|
*/
|
|
135
135
|
export declare const CLICK_NOT_RECEIVED_WARNING = "The click may not have reached the element: the page saw no mouse press (the browser may be showing a dialog or bubble that captures input)";
|
|
136
136
|
/**
|
|
137
|
-
* Note under a shortened list
|
|
137
|
+
* Note under a shortened human list whose JSON output lists more, up to a cap.
|
|
138
138
|
*
|
|
139
|
-
* @param hidden -
|
|
140
|
-
* @param jsonLimit -
|
|
141
|
-
* @returns e.g. "... and
|
|
142
|
-
* "... and 8980 more (--json lists the first 100)"
|
|
139
|
+
* @param hidden - Items not listed
|
|
140
|
+
* @param jsonLimit - Most items the JSON output lists
|
|
141
|
+
* @returns e.g. "... and 8980 more (--json lists up to 100)"
|
|
143
142
|
*/
|
|
144
|
-
export declare function moreMatchesNote(hidden: number, jsonLimit
|
|
143
|
+
export declare function moreMatchesNote(hidden: number, jsonLimit: number): string;
|
|
145
144
|
/**
|
|
146
145
|
* Note under `dom query` matches cut by `--limit`.
|
|
147
146
|
*
|
|
@@ -158,6 +157,20 @@ export declare function queryMoreMatchesNote(omitted: number, indexed?: number):
|
|
|
158
157
|
* @returns e.g. `Visibility is checked for the first 100 matches only; bdg dom layout <index> checks any of them`
|
|
159
158
|
*/
|
|
160
159
|
export declare function queryViewportCheckedNote(checked: number): string;
|
|
160
|
+
/**
|
|
161
|
+
* First line under an accessibility tree cut by `--limit` or `--depth`.
|
|
162
|
+
*
|
|
163
|
+
* @param listed - Nodes listed
|
|
164
|
+
* @returns e.g. "Showing the first 50 nodes (text boxes and repeated text left out)"
|
|
165
|
+
*/
|
|
166
|
+
export declare function a11yTreeShownNote(listed: number): string;
|
|
167
|
+
/**
|
|
168
|
+
* How to see the rest of an accessibility tree cut by `--limit` or `--depth`.
|
|
169
|
+
*
|
|
170
|
+
* @param omitted - Nodes left out
|
|
171
|
+
* @returns e.g. "51391 more: --limit 0 lists all, --depth <n> limits the levels, or search with bdg dom a11y query \"role:<role>\""
|
|
172
|
+
*/
|
|
173
|
+
export declare function a11yTreeMoreNote(omitted: number): string;
|
|
161
174
|
/**
|
|
162
175
|
* Note under a list of a11y query matches cut by `--limit`.
|
|
163
176
|
*
|
|
@@ -362,10 +375,11 @@ export declare function coverText(cover: string, transparent: boolean | undefine
|
|
|
362
375
|
* Note when `bdg dom inspect` could not read the element's matched rules, so
|
|
363
376
|
* no hints, rules or why were computed.
|
|
364
377
|
*
|
|
365
|
-
* @param reason - `timeout` (very large stylesheets)
|
|
378
|
+
* @param reason - `timeout` (very large stylesheets), `failed` (Chrome reported
|
|
379
|
+
* an error) or `skipped` (hints not read: an earlier read on this page timed out)
|
|
366
380
|
* @returns Note
|
|
367
381
|
*/
|
|
368
|
-
export declare function inspectCascadeNote(reason: 'timeout' | 'failed'): string;
|
|
382
|
+
export declare function inspectCascadeNote(reason: 'timeout' | 'failed' | 'skipped'): string;
|
|
369
383
|
/**
|
|
370
384
|
* Note of `bdg dom inspect` when the selector named a pseudo-element: its
|
|
371
385
|
* element is inspected and the pseudo-element is on the `pseudo` line.
|
|
@@ -835,5 +849,12 @@ export declare const CSS_SEARCH_HELP_EXAMPLES = "\nExamples:\n bdg css search -
|
|
|
835
849
|
* @returns Note
|
|
836
850
|
*/
|
|
837
851
|
export declare function helpJsonDetailsNote(): string;
|
|
852
|
+
/**
|
|
853
|
+
* Pointer after a value cut for human output.
|
|
854
|
+
*
|
|
855
|
+
* @param count - Characters left out
|
|
856
|
+
* @returns e.g. `… 299800 more chars (use --full)`
|
|
857
|
+
*/
|
|
858
|
+
export declare function moreCharsNote(count: number): string;
|
|
838
859
|
export {};
|
|
839
860
|
//# sourceMappingURL=commands.d.ts.map
|
|
@@ -193,16 +193,14 @@ export function valueMismatchWarning(mismatch) {
|
|
|
193
193
|
*/
|
|
194
194
|
export const CLICK_NOT_RECEIVED_WARNING = 'The click may not have reached the element: the page saw no mouse press (the browser may be showing a dialog or bubble that captures input)';
|
|
195
195
|
/**
|
|
196
|
-
* Note under a shortened list
|
|
196
|
+
* Note under a shortened human list whose JSON output lists more, up to a cap.
|
|
197
197
|
*
|
|
198
|
-
* @param hidden -
|
|
199
|
-
* @param jsonLimit -
|
|
200
|
-
* @returns e.g. "... and
|
|
201
|
-
* "... and 8980 more (--json lists the first 100)"
|
|
198
|
+
* @param hidden - Items not listed
|
|
199
|
+
* @param jsonLimit - Most items the JSON output lists
|
|
200
|
+
* @returns e.g. "... and 8980 more (--json lists up to 100)"
|
|
202
201
|
*/
|
|
203
202
|
export function moreMatchesNote(hidden, jsonLimit) {
|
|
204
|
-
|
|
205
|
-
return `... and ${hidden} more (${where})`;
|
|
203
|
+
return `... and ${hidden} more (--json lists up to ${jsonLimit})`;
|
|
206
204
|
}
|
|
207
205
|
/**
|
|
208
206
|
* Note under `dom query` matches cut by `--limit`.
|
|
@@ -225,6 +223,24 @@ export function queryMoreMatchesNote(omitted, indexed) {
|
|
|
225
223
|
export function queryViewportCheckedNote(checked) {
|
|
226
224
|
return `Visibility is checked for the first ${checked} matches only; ${sessionCommand('bdg dom layout <index>')} checks any of them`;
|
|
227
225
|
}
|
|
226
|
+
/**
|
|
227
|
+
* First line under an accessibility tree cut by `--limit` or `--depth`.
|
|
228
|
+
*
|
|
229
|
+
* @param listed - Nodes listed
|
|
230
|
+
* @returns e.g. "Showing the first 50 nodes (text boxes and repeated text left out)"
|
|
231
|
+
*/
|
|
232
|
+
export function a11yTreeShownNote(listed) {
|
|
233
|
+
return `Showing the first ${listed} nodes (text boxes and repeated text left out)`;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* How to see the rest of an accessibility tree cut by `--limit` or `--depth`.
|
|
237
|
+
*
|
|
238
|
+
* @param omitted - Nodes left out
|
|
239
|
+
* @returns e.g. "51391 more: --limit 0 lists all, --depth <n> limits the levels, or search with bdg dom a11y query \"role:<role>\""
|
|
240
|
+
*/
|
|
241
|
+
export function a11yTreeMoreNote(omitted) {
|
|
242
|
+
return `${omitted} more: --limit 0 lists all, --depth <n> limits the levels, or search with bdg dom a11y query "role:<role>"`;
|
|
243
|
+
}
|
|
228
244
|
/**
|
|
229
245
|
* Note under a list of a11y query matches cut by `--limit`.
|
|
230
246
|
*
|
|
@@ -585,10 +601,13 @@ export function coverText(cover, transparent) {
|
|
|
585
601
|
* Note when `bdg dom inspect` could not read the element's matched rules, so
|
|
586
602
|
* no hints, rules or why were computed.
|
|
587
603
|
*
|
|
588
|
-
* @param reason - `timeout` (very large stylesheets)
|
|
604
|
+
* @param reason - `timeout` (very large stylesheets), `failed` (Chrome reported
|
|
605
|
+
* an error) or `skipped` (hints not read: an earlier read on this page timed out)
|
|
589
606
|
* @returns Note
|
|
590
607
|
*/
|
|
591
608
|
export function inspectCascadeNote(reason) {
|
|
609
|
+
if (reason === 'skipped')
|
|
610
|
+
return "hints skipped: this page's stylesheets are slow to read (--rules waits 5 s)";
|
|
592
611
|
return reason === 'timeout'
|
|
593
612
|
? "CSS rules not read: the page's stylesheets took too long (hints wait 1 s; --rules and --why 5 s)"
|
|
594
613
|
: 'CSS rules not read: Chrome could not report the rules matching this element';
|
|
@@ -1279,4 +1298,13 @@ Examples:
|
|
|
1279
1298
|
export function helpJsonDetailsNote() {
|
|
1280
1299
|
return 'Option behaviors, defaults, choices and examples: bdg <command> --help --json (e.g. bdg dom query --help --json). Everything at once: bdg --help --json --full';
|
|
1281
1300
|
}
|
|
1301
|
+
/**
|
|
1302
|
+
* Pointer after a value cut for human output.
|
|
1303
|
+
*
|
|
1304
|
+
* @param count - Characters left out
|
|
1305
|
+
* @returns e.g. `… 299800 more chars (use --full)`
|
|
1306
|
+
*/
|
|
1307
|
+
export function moreCharsNote(count) {
|
|
1308
|
+
return `… ${count} more chars (use --full)`;
|
|
1309
|
+
}
|
|
1282
1310
|
//# sourceMappingURL=commands.js.map
|
|
@@ -3,6 +3,30 @@
|
|
|
3
3
|
*
|
|
4
4
|
* User-facing messages for the network list command output and formatting.
|
|
5
5
|
*/
|
|
6
|
+
/**
|
|
7
|
+
* Why a stored response body was replaced by a placeholder (shown by
|
|
8
|
+
* `bdg details network <id>` as `bodyNotCaptured`).
|
|
9
|
+
*
|
|
10
|
+
* @param budgetBytes - Total body budget of the session
|
|
11
|
+
* @returns e.g. `evicted: total body budget (bdg keeps the newest 100 MB of response bodies)`
|
|
12
|
+
*/
|
|
13
|
+
export declare function bodyEvictedReason(budgetBytes: number): string;
|
|
14
|
+
/** What a session's network capture let go at its limits */
|
|
15
|
+
export interface NetworkEvictionCounts {
|
|
16
|
+
/** Oldest finished requests dropped at the request cap */
|
|
17
|
+
requestsDropped: number;
|
|
18
|
+
/** Oldest response bodies evicted at the body budget */
|
|
19
|
+
bodiesEvicted: number;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Note that the session dropped its oldest requests or evicted its oldest
|
|
23
|
+
* response bodies at its limits.
|
|
24
|
+
*
|
|
25
|
+
* @param counts - Requests dropped and bodies evicted
|
|
26
|
+
* @returns e.g. `⚠ 2000 older network requests were dropped: bdg keeps the newest 10000`;
|
|
27
|
+
* undefined when nothing was let go
|
|
28
|
+
*/
|
|
29
|
+
export declare function networkEvictedNote(counts: NetworkEvictionCounts): string | undefined;
|
|
6
30
|
/**
|
|
7
31
|
* Generate message for following network output.
|
|
8
32
|
*
|
|
@@ -3,6 +3,51 @@
|
|
|
3
3
|
*
|
|
4
4
|
* User-facing messages for the network list command output and formatting.
|
|
5
5
|
*/
|
|
6
|
+
import { MAX_NETWORK_REQUESTS, MAX_TOTAL_BODY_BYTES } from '../../constants.js';
|
|
7
|
+
import { pluralize } from '../formatting.js';
|
|
8
|
+
/**
|
|
9
|
+
* A byte budget in whole megabytes.
|
|
10
|
+
*
|
|
11
|
+
* @param bytes - Budget
|
|
12
|
+
* @returns e.g. `100 MB`
|
|
13
|
+
*/
|
|
14
|
+
function megabytes(bytes) {
|
|
15
|
+
return `${Math.round(bytes / (1024 * 1024))} MB`;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Why a stored response body was replaced by a placeholder (shown by
|
|
19
|
+
* `bdg details network <id>` as `bodyNotCaptured`).
|
|
20
|
+
*
|
|
21
|
+
* @param budgetBytes - Total body budget of the session
|
|
22
|
+
* @returns e.g. `evicted: total body budget (bdg keeps the newest 100 MB of response bodies)`
|
|
23
|
+
*/
|
|
24
|
+
export function bodyEvictedReason(budgetBytes) {
|
|
25
|
+
return `evicted: total body budget (bdg keeps the newest ${megabytes(budgetBytes)} of response bodies)`;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Note that the session dropped its oldest requests or evicted its oldest
|
|
29
|
+
* response bodies at its limits.
|
|
30
|
+
*
|
|
31
|
+
* @param counts - Requests dropped and bodies evicted
|
|
32
|
+
* @returns e.g. `⚠ 2000 older network requests were dropped: bdg keeps the newest 10000`;
|
|
33
|
+
* undefined when nothing was let go
|
|
34
|
+
*/
|
|
35
|
+
export function networkEvictedNote(counts) {
|
|
36
|
+
const { requestsDropped, bodiesEvicted } = counts;
|
|
37
|
+
const requests = pluralize(requestsDropped, 'older network request');
|
|
38
|
+
const bodies = pluralize(bodiesEvicted, 'older response body', 'older response bodies');
|
|
39
|
+
const budget = megabytes(MAX_TOTAL_BODY_BYTES);
|
|
40
|
+
if (requestsDropped > 0 && bodiesEvicted > 0) {
|
|
41
|
+
return `⚠ ${requests} dropped, ${bodies} evicted: bdg keeps the newest ${MAX_NETWORK_REQUESTS} requests and ${budget} of bodies`;
|
|
42
|
+
}
|
|
43
|
+
if (requestsDropped > 0) {
|
|
44
|
+
return `⚠ ${requests} ${requestsDropped === 1 ? 'was' : 'were'} dropped: bdg keeps the newest ${MAX_NETWORK_REQUESTS}`;
|
|
45
|
+
}
|
|
46
|
+
if (bodiesEvicted > 0) {
|
|
47
|
+
return `⚠ ${bodies} ${bodiesEvicted === 1 ? 'was' : 'were'} evicted: bdg keeps the newest ${budget} of bodies`;
|
|
48
|
+
}
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
6
51
|
/**
|
|
7
52
|
* Generate message for following network output.
|
|
8
53
|
*
|
package/dist/utils/http.d.ts
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import type { CDPTarget } from '../types.js';
|
|
2
2
|
import type { Logger } from '../ui/logging/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* Timeout for CDP HTTP requests in milliseconds.
|
|
5
|
+
*
|
|
6
|
+
* Chrome's HTTP API should respond quickly when running.
|
|
7
|
+
* A 5-second timeout helps detect when Chrome is not responding.
|
|
8
|
+
*/
|
|
9
|
+
export declare const CDP_HTTP_TIMEOUT_MS = 5000;
|
|
3
10
|
/**
|
|
4
11
|
* Options for CDP HTTP target fetch.
|
|
5
12
|
*/
|
|
@@ -47,10 +54,10 @@ export type DevToolsProbe = {
|
|
|
47
54
|
*
|
|
48
55
|
* @param port - Chrome debugging port
|
|
49
56
|
* @param logger - Optional logger for debug output
|
|
50
|
-
* @param options - Host and
|
|
57
|
+
* @param options - Host, HTTPS and request timeout
|
|
51
58
|
* @returns What answered
|
|
52
59
|
*/
|
|
53
|
-
export declare function probeDevToolsEndpoint(port: number, logger?: Logger, options?:
|
|
60
|
+
export declare function probeDevToolsEndpoint(port: number, logger?: Logger, options?: FetchCDPTargetsOptions): Promise<DevToolsProbe>;
|
|
54
61
|
/**
|
|
55
62
|
* The browser-level DevTools WebSocket URL of a Chrome (`/json/version`).
|
|
56
63
|
*
|
package/dist/utils/http.js
CHANGED
|
@@ -6,7 +6,7 @@ import { getErrorMessage } from './errors.js';
|
|
|
6
6
|
* Chrome's HTTP API should respond quickly when running.
|
|
7
7
|
* A 5-second timeout helps detect when Chrome is not responding.
|
|
8
8
|
*/
|
|
9
|
-
const CDP_HTTP_TIMEOUT_MS = 5000;
|
|
9
|
+
export const CDP_HTTP_TIMEOUT_MS = 5000;
|
|
10
10
|
/**
|
|
11
11
|
* Fetch CDP targets from Chrome's HTTP API.
|
|
12
12
|
*
|
|
@@ -67,14 +67,15 @@ export async function fetchCDPTargets(port = DEFAULT_CDP_PORT, logger, options)
|
|
|
67
67
|
*
|
|
68
68
|
* @param port - Chrome debugging port
|
|
69
69
|
* @param logger - Optional logger for debug output
|
|
70
|
-
* @param options - Host and
|
|
70
|
+
* @param options - Host, HTTPS and request timeout
|
|
71
71
|
* @returns What answered
|
|
72
72
|
*/
|
|
73
73
|
export async function probeDevToolsEndpoint(port, logger, options) {
|
|
74
74
|
const url = `${options?.secure ? 'https' : 'http'}://${options?.host ?? HTTP_LOCALHOST}:${port}/json/version`;
|
|
75
|
+
const timeoutMs = options?.timeoutMs ?? CDP_HTTP_TIMEOUT_MS;
|
|
75
76
|
let response;
|
|
76
77
|
try {
|
|
77
|
-
response = await fetch(url, { signal: AbortSignal.timeout(
|
|
78
|
+
response = await fetch(url, { signal: AbortSignal.timeout(timeoutMs) });
|
|
78
79
|
}
|
|
79
80
|
catch (error) {
|
|
80
81
|
logger?.debug(`Chrome version request failed: ${getErrorMessage(error)} (${url})`);
|
package/dist/utils/strings.d.ts
CHANGED
|
@@ -11,4 +11,23 @@
|
|
|
11
11
|
* @returns Truncated text with ellipsis if needed
|
|
12
12
|
*/
|
|
13
13
|
export declare function truncateByLength(text: string, maxLength?: number): string;
|
|
14
|
+
/**
|
|
15
|
+
* A text cut to a maximum length, with its original length when it was cut.
|
|
16
|
+
*/
|
|
17
|
+
export interface CappedText {
|
|
18
|
+
/** The text, or its first `maxLength` characters */
|
|
19
|
+
text: string;
|
|
20
|
+
/** Original length, set only when the text was cut */
|
|
21
|
+
truncatedFrom?: number;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Cut a text to its first `maxLength` characters (UTF-16 code units),
|
|
25
|
+
* keeping the original length (the `truncatedFrom` of JSON output). A
|
|
26
|
+
* surrogate pair (an emoji) is never split: the cut ends before it.
|
|
27
|
+
*
|
|
28
|
+
* @param text - Text to cut
|
|
29
|
+
* @param maxLength - Characters kept (at most)
|
|
30
|
+
* @returns The text, with `truncatedFrom` when it was cut
|
|
31
|
+
*/
|
|
32
|
+
export declare function capLength(text: string, maxLength: number): CappedText;
|
|
14
33
|
//# sourceMappingURL=strings.d.ts.map
|
package/dist/utils/strings.js
CHANGED
|
@@ -20,4 +20,20 @@ export function truncateByLength(text, maxLength = DEFAULT_MAX_LENGTH) {
|
|
|
20
20
|
}
|
|
21
21
|
return text.slice(0, maxLength - 1) + '…';
|
|
22
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Cut a text to its first `maxLength` characters (UTF-16 code units),
|
|
25
|
+
* keeping the original length (the `truncatedFrom` of JSON output). A
|
|
26
|
+
* surrogate pair (an emoji) is never split: the cut ends before it.
|
|
27
|
+
*
|
|
28
|
+
* @param text - Text to cut
|
|
29
|
+
* @param maxLength - Characters kept (at most)
|
|
30
|
+
* @returns The text, with `truncatedFrom` when it was cut
|
|
31
|
+
*/
|
|
32
|
+
export function capLength(text, maxLength) {
|
|
33
|
+
if (text.length <= maxLength)
|
|
34
|
+
return { text };
|
|
35
|
+
const lastKept = text.charCodeAt(maxLength - 1);
|
|
36
|
+
const end = lastKept >= 0xd800 && lastKept <= 0xdbff ? maxLength - 1 : maxLength;
|
|
37
|
+
return { text: text.slice(0, end), truncatedFrom: text.length };
|
|
38
|
+
}
|
|
23
39
|
//# sourceMappingURL=strings.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "browser-debugger-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "DevTools telemetry in your terminal. For humans and agents. Direct WebSocket to Chrome's debugging port.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"validate:ts-version": "node -e \"const ts = require('typescript'); const v = ts.version.split('.'); if (parseInt(v[0]) < 5 || (parseInt(v[0]) === 5 && parseInt(v[1]) < 6)) { console.error('Requires TypeScript 5.6+, found:', ts.version); process.exit(1); } else { console.log('✅ TypeScript version:', ts.version); }\"",
|
|
28
28
|
"check": "npm run format:check && npm run type-check && npm run lint",
|
|
29
29
|
"check:enhanced": "npm run format:check && npm run type-check && npm run lint && npm run validate:module-type && npm run validate:ts-version",
|
|
30
|
-
"test": "tsx --test 'src/**/__tests__/**/!(*.smoke).test.ts'",
|
|
30
|
+
"test": "tsx --test --test-timeout=120000 'src/**/__tests__/**/!(*.smoke).test.ts'",
|
|
31
31
|
"test:watch": "tsx --test --watch 'src/**/__tests__/**/!(*.smoke).test.ts'",
|
|
32
32
|
"test:coverage": "c8 npm test",
|
|
33
33
|
"test:smoke": "tsx --test --test-concurrency=1 'src/__tests__/smoke/*.smoke.test.ts'",
|