browser-debugger-cli 0.13.0 → 0.15.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 +101 -187
- package/README.md +4 -4
- package/dist/commands/cdp.js +1 -0
- package/dist/commands/cleanup.js +3 -0
- 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 +3 -1
- package/dist/commands/dom/eval.js +8 -5
- package/dist/commands/dom/formInteraction.js +1 -1
- package/dist/commands/dom/get.js +25 -7
- package/dist/commands/dom/helpers/evalResult.d.ts +36 -0
- package/dist/commands/dom/helpers/evalResult.js +59 -0
- 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.d.ts +1 -1
- package/dist/commands/helpJson.js +4 -4
- package/dist/commands/helpTopic.js +10 -4
- package/dist/commands/network/har.js +18 -14
- package/dist/commands/network/list.js +46 -3
- package/dist/commands/optionBehaviors.d.ts +25 -2
- package/dist/commands/optionBehaviors.js +60 -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 +15 -3
- package/dist/commands/shared/outputFile.d.ts +2 -1
- package/dist/commands/shared/outputFile.js +7 -4
- package/dist/commands/shared/startHelpers.js +3 -3
- package/dist/commands/status.js +3 -1
- package/dist/commands/stop.js +2 -1
- package/dist/connection/chromeIdentity.d.ts +8 -2
- package/dist/connection/chromeIdentity.js +85 -13
- package/dist/connection/launcher/flagsBuilder.d.ts +46 -0
- package/dist/connection/launcher/flagsBuilder.js +107 -23
- package/dist/connection/launcher.d.ts +1 -1
- package/dist/connection/launcher.js +1 -2
- package/dist/constants.d.ts +31 -5
- package/dist/constants.js +37 -5
- package/dist/daemon/SessionController.js +2 -0
- package/dist/daemon/launcher.d.ts +17 -3
- package/dist/daemon/launcher.js +37 -7
- 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 +25 -7
- 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 +8742 -8315
- package/dist/errors/messages.d.ts +31 -0
- package/dist/errors/messages.js +96 -6
- package/dist/index.js +1129 -548
- package/dist/ipc/client.d.ts +6 -1
- package/dist/ipc/client.js +11 -2
- package/dist/ipc/protocol/commands.d.ts +8 -0
- package/dist/ipc/protocol/inspectTypes.d.ts +5 -2
- package/dist/ipc/session/types.d.ts +5 -1
- package/dist/ipc/transport/index.d.ts +6 -0
- package/dist/ipc/transport/index.js +16 -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 +13 -4
- package/dist/runtime/dom/elementInfo.js +15 -5
- package/dist/runtime/dom/evalHelpers.d.ts +24 -4
- package/dist/runtime/dom/evalHelpers.js +40 -12
- package/dist/runtime/dom/frameScopedConnection.d.ts +7 -0
- package/dist/runtime/dom/frameScopedConnection.js +2 -2
- package/dist/runtime/dom/frames.d.ts +2 -1
- package/dist/runtime/dom/frames.js +3 -1
- 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/runtime/page/emulation.js +6 -5
- package/dist/runtime/page/userAgent.d.ts +86 -2
- package/dist/runtime/page/userAgent.js +154 -33
- package/dist/session/paths.d.ts +38 -3
- package/dist/session/paths.js +154 -7
- package/dist/session/portClaims.d.ts +0 -8
- package/dist/session/portClaims.js +1 -22
- package/dist/session/sessionList.d.ts +5 -1
- package/dist/session/sessionList.js +5 -1
- package/dist/telemetry/a11y.d.ts +15 -1
- package/dist/telemetry/a11y.js +83 -0
- package/dist/telemetry/har/builder.d.ts +12 -1
- package/dist/telemetry/har/builder.js +11 -3
- package/dist/telemetry/har/sanitize.d.ts +24 -0
- package/dist/telemetry/har/sanitize.js +138 -0
- package/dist/telemetry/har/sanitizeBody.d.ts +38 -0
- package/dist/telemetry/har/sanitizeBody.js +168 -0
- 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/sessions.d.ts +3 -2
- package/dist/ui/formatters/sessions.js +10 -3
- package/dist/ui/formatters/status.js +7 -0
- package/dist/ui/formatters/triggeredRequests.js +2 -1
- package/dist/ui/messages/chrome.d.ts +34 -7
- package/dist/ui/messages/chrome.js +81 -15
- package/dist/ui/messages/commands.d.ts +29 -8
- package/dist/ui/messages/commands.js +36 -8
- package/dist/ui/messages/networkMessages.d.ts +50 -0
- package/dist/ui/messages/networkMessages.js +66 -0
- package/dist/ui/messages/session.d.ts +8 -0
- package/dist/ui/messages/session.js +10 -0
- package/dist/utils/atomicFile.d.ts +2 -1
- package/dist/utils/atomicFile.js +5 -2
- package/dist/utils/directories.d.ts +41 -0
- package/dist/utils/directories.js +48 -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
|
@@ -58,7 +58,7 @@ export function registerFormInteractionCommands(program) {
|
|
|
58
58
|
.command('fill')
|
|
59
59
|
.description('Fill a form field with a value (React-compatible, waits for stability)')
|
|
60
60
|
.argument('<selectorOrIndex>', SELECTOR_OR_INDEX_ARGUMENT)
|
|
61
|
-
.argument('<value>', 'Value to fill (file inputs: paths separated by commas, "" clears)')
|
|
61
|
+
.argument('<value>', 'Value to fill (file inputs: local file paths, separated by commas, that are uploaded to the page; "" clears)')
|
|
62
62
|
.option('--index <n>', 'Element index if selector matches multiple (0-based)', integerOption(0))
|
|
63
63
|
.option('--no-blur', 'Do not blur after filling (keeps focus on element)')
|
|
64
64
|
.option('--no-wait', 'Skip waiting for network stability after fill')
|
package/dist/commands/dom/get.js
CHANGED
|
@@ -9,12 +9,14 @@ import { DomElementResolver } from './DomElementResolver.js';
|
|
|
9
9
|
import { getDOMElements, getDomContext, selectMatch, } from './helpers/index.js';
|
|
10
10
|
import { formatSemanticNodeWithContext, resolveNodeWithFallback, } from './semanticUtils.js';
|
|
11
11
|
import { runCommand } from '../shared/CommandRunner.js';
|
|
12
|
+
import { MAX_VALUE_LENGTH } from '../../constants.js';
|
|
12
13
|
import { CommandError } from '../../errors/index.js';
|
|
13
14
|
import { conflictingOptionsError, indexWithIndexOptionError, nodeIdNotFoundError, optionRequiresError, } from '../../errors/messages.js';
|
|
14
15
|
import { resolveA11yNode } from '../../telemetry/a11y.js';
|
|
15
16
|
import { formatDomGet } from '../../ui/formatters/dom.js';
|
|
16
17
|
import { EXIT_CODES } from '../../utils/exitCodes.js';
|
|
17
18
|
import { filterDefined } from '../../utils/objects.js';
|
|
19
|
+
import { capLength } from '../../utils/strings.js';
|
|
18
20
|
/** What `bdg dom get` reads without a selector */
|
|
19
21
|
export const DOM_GET_DEFAULT_SELECTOR = 'body';
|
|
20
22
|
/**
|
|
@@ -58,7 +60,7 @@ async function semanticElement(backendNodeId, full) {
|
|
|
58
60
|
* Raw view: the element(s) with attributes and outer HTML.
|
|
59
61
|
*
|
|
60
62
|
* @param selectorOrIndex - CSS selector or cached index
|
|
61
|
-
* @param options - Command options (`--all`, `--index`)
|
|
63
|
+
* @param options - Command options (`--all`, `--index`, `--full`)
|
|
62
64
|
*/
|
|
63
65
|
async function handleRawGet(selectorOrIndex, options) {
|
|
64
66
|
await runCommand(async () => {
|
|
@@ -70,8 +72,27 @@ async function handleRawGet(selectorOrIndex, options) {
|
|
|
70
72
|
all: options.all,
|
|
71
73
|
nth: matchIndex(options),
|
|
72
74
|
});
|
|
73
|
-
return { success: true, data: await getDOMElements(getOptions) };
|
|
74
|
-
}, options, formatDomGet);
|
|
75
|
+
return { success: true, data: rawGetData(await getDOMElements(getOptions), options) };
|
|
76
|
+
}, options, (data) => formatDomGet(data, { full: options.full }));
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Raw elements for the output: in JSON each outer HTML is cut to
|
|
80
|
+
* {@link MAX_VALUE_LENGTH} characters with `truncatedFrom`, unless `--full`
|
|
81
|
+
* (human output is cut when formatted).
|
|
82
|
+
*
|
|
83
|
+
* @param result - Elements read
|
|
84
|
+
* @param options - `--json`, `--full`
|
|
85
|
+
* @returns Elements to output
|
|
86
|
+
*/
|
|
87
|
+
function rawGetData(result, options) {
|
|
88
|
+
if (!options.json || options.full)
|
|
89
|
+
return result;
|
|
90
|
+
return {
|
|
91
|
+
nodes: result.nodes.map((node) => {
|
|
92
|
+
const { text, truncatedFrom } = capLength(node.outerHTML ?? '', MAX_VALUE_LENGTH);
|
|
93
|
+
return truncatedFrom === undefined ? node : { ...node, outerHTML: text, truncatedFrom };
|
|
94
|
+
}),
|
|
95
|
+
};
|
|
75
96
|
}
|
|
76
97
|
/**
|
|
77
98
|
* Semantic view of the target.
|
|
@@ -105,9 +126,6 @@ function getOptionsConflict(selectorOrIndex, options) {
|
|
|
105
126
|
if (options.nodeId !== undefined && selectorOrIndex !== undefined) {
|
|
106
127
|
return conflictingOptionsError('--node-id', 'a selector or index');
|
|
107
128
|
}
|
|
108
|
-
if (options.full && (options.raw || options.nodeId !== undefined)) {
|
|
109
|
-
return conflictingOptionsError('--full', options.raw ? '--raw' : '--node-id');
|
|
110
|
-
}
|
|
111
129
|
if (options.index !== undefined && options.nth !== undefined) {
|
|
112
130
|
return conflictingOptionsError('--index', '--nth');
|
|
113
131
|
}
|
|
@@ -134,7 +152,7 @@ export async function handleDomGet(selectorOrIndex, options) {
|
|
|
134
152
|
}
|
|
135
153
|
const { nodeId } = options;
|
|
136
154
|
if (nodeId !== undefined) {
|
|
137
|
-
await runCommand(async () => ({ success: true, data: await getDOMElements({ nodeId }) }), options, formatDomGet);
|
|
155
|
+
await runCommand(async () => ({ success: true, data: rawGetData(await getDOMElements({ nodeId }), options) }), options, (data) => formatDomGet(data, { full: options.full }));
|
|
138
156
|
return;
|
|
139
157
|
}
|
|
140
158
|
const target = selectorOrIndex ?? DOM_GET_DEFAULT_SELECTOR;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bounds of a `dom eval --json` result (#478): one eval must not put
|
|
3
|
+
* megabytes into an agent's context.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* The `result` of `dom eval --json`, and what was left out of it. A string
|
|
7
|
+
* `result` with `truncatedFrom` while `type` is `object` is the start of
|
|
8
|
+
* the value's JSON text, not a string the script returned.
|
|
9
|
+
*/
|
|
10
|
+
export interface BoundedEvalResult {
|
|
11
|
+
/** The value, its first elements, or the start of its (JSON) text */
|
|
12
|
+
result: unknown;
|
|
13
|
+
/** Elements of an array result in the page, set only when it was bounded */
|
|
14
|
+
count?: number;
|
|
15
|
+
/** Elements left out of a listed array result */
|
|
16
|
+
omitted?: number;
|
|
17
|
+
/**
|
|
18
|
+
* Length of the string, or of the JSON text of the copied object or array
|
|
19
|
+
* (which holds at most 1000 entries per list or object), `result` was cut from
|
|
20
|
+
*/
|
|
21
|
+
truncatedFrom?: number;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Bound an eval result for JSON output: a string is cut to
|
|
25
|
+
* {@link MAX_VALUE_LENGTH} characters, an array keeps its first
|
|
26
|
+
* {@link EVAL_JSON_ARRAY_LIMIT} elements (with `count` and `omitted`), and
|
|
27
|
+
* an object or array whose JSON is still longer than the cap becomes the
|
|
28
|
+
* start of its JSON text (with `truncatedFrom`), so the output stays valid
|
|
29
|
+
* JSON. Small values are returned as they are.
|
|
30
|
+
*
|
|
31
|
+
* @param value - Evaluated value, as copied from the page
|
|
32
|
+
* @param length - Elements of an array result in the page (its copy may hold fewer)
|
|
33
|
+
* @returns `result`, with what was left out when bounded
|
|
34
|
+
*/
|
|
35
|
+
export declare function boundEvalResult(value: unknown, length?: number): BoundedEvalResult;
|
|
36
|
+
//# sourceMappingURL=evalResult.d.ts.map
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bounds of a `dom eval --json` result (#478): one eval must not put
|
|
3
|
+
* megabytes into an agent's context.
|
|
4
|
+
*/
|
|
5
|
+
import { EVAL_JSON_ARRAY_LIMIT, MAX_VALUE_LENGTH } from '../../../constants.js';
|
|
6
|
+
import { capLength } from '../../../utils/strings.js';
|
|
7
|
+
/**
|
|
8
|
+
* Bound an eval result for JSON output: a string is cut to
|
|
9
|
+
* {@link MAX_VALUE_LENGTH} characters, an array keeps its first
|
|
10
|
+
* {@link EVAL_JSON_ARRAY_LIMIT} elements (with `count` and `omitted`), and
|
|
11
|
+
* an object or array whose JSON is still longer than the cap becomes the
|
|
12
|
+
* start of its JSON text (with `truncatedFrom`), so the output stays valid
|
|
13
|
+
* JSON. Small values are returned as they are.
|
|
14
|
+
*
|
|
15
|
+
* @param value - Evaluated value, as copied from the page
|
|
16
|
+
* @param length - Elements of an array result in the page (its copy may hold fewer)
|
|
17
|
+
* @returns `result`, with what was left out when bounded
|
|
18
|
+
*/
|
|
19
|
+
export function boundEvalResult(value, length) {
|
|
20
|
+
if (typeof value === 'string') {
|
|
21
|
+
const { text, truncatedFrom } = capLength(value, MAX_VALUE_LENGTH);
|
|
22
|
+
return { result: text, ...(truncatedFrom !== undefined && { truncatedFrom }) };
|
|
23
|
+
}
|
|
24
|
+
if (Array.isArray(value))
|
|
25
|
+
return boundArray(value, length ?? value.length);
|
|
26
|
+
if (value === null || typeof value !== 'object')
|
|
27
|
+
return { result: value };
|
|
28
|
+
const json = JSON.stringify(value);
|
|
29
|
+
return json.length > MAX_VALUE_LENGTH ? jsonStart(json) : { result: value };
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Bound an array result: its first {@link EVAL_JSON_ARRAY_LIMIT} elements,
|
|
33
|
+
* or the start of its JSON text when even those are over the cap.
|
|
34
|
+
*
|
|
35
|
+
* @param value - Array result
|
|
36
|
+
* @param count - Elements of the array in the page
|
|
37
|
+
* @returns `result` with `count`, and `omitted` or `truncatedFrom`
|
|
38
|
+
*/
|
|
39
|
+
function boundArray(value, count) {
|
|
40
|
+
const listed = value.slice(0, EVAL_JSON_ARRAY_LIMIT);
|
|
41
|
+
const listedJson = JSON.stringify(listed);
|
|
42
|
+
if (listedJson.length > MAX_VALUE_LENGTH) {
|
|
43
|
+
const json = listed.length === value.length ? listedJson : JSON.stringify(value);
|
|
44
|
+
return { ...jsonStart(json), count };
|
|
45
|
+
}
|
|
46
|
+
return listed.length < count
|
|
47
|
+
? { result: listed, count, omitted: count - listed.length }
|
|
48
|
+
: { result: value };
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* The first {@link MAX_VALUE_LENGTH} characters of a JSON text.
|
|
52
|
+
*
|
|
53
|
+
* @param json - JSON text of an object or array over the cap
|
|
54
|
+
* @returns Its start, and the length of all of it
|
|
55
|
+
*/
|
|
56
|
+
function jsonStart(json) {
|
|
57
|
+
return { result: capLength(json, MAX_VALUE_LENGTH).text, truncatedFrom: json.length };
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=evalResult.js.map
|
|
@@ -31,6 +31,9 @@ import { handleDomScreenshot } from './screenshot.js';
|
|
|
31
31
|
import { registerWaitCommand } from './wait.js';
|
|
32
32
|
import { SELECTOR_OR_INDEX_ARGUMENT, SELECTOR_SCOPE_HELP, } from '../shared/commonOptions.js';
|
|
33
33
|
import { integerOption, screenshotFormatOption } from '../shared/validation.js';
|
|
34
|
+
import { MAX_VALUE_LENGTH, QUERY_JSON_LIST_LIMIT } from '../../constants.js';
|
|
35
|
+
/** Help of `--full` on `dom eval` and the `eval` shortcut */
|
|
36
|
+
const EVAL_FULL_HELP = `Print the whole value (default: the first ${MAX_VALUE_LENGTH} characters; a string result in JSON too)`;
|
|
34
37
|
/**
|
|
35
38
|
* Register DOM telemetry commands on the root Commander program.
|
|
36
39
|
*/
|
|
@@ -50,7 +53,7 @@ export function registerDomCommands(program) {
|
|
|
50
53
|
.command('query')
|
|
51
54
|
.description('Find elements by CSS selector')
|
|
52
55
|
.argument('<selector>', 'CSS selector (e.g., ".error", "#app", "button")')
|
|
53
|
-
.option('--limit <n>', `Matches to list (default: ${QUERY_LIST_LIMIT}, ${
|
|
56
|
+
.option('--limit <n>', `Matches to list (default: ${QUERY_LIST_LIMIT}, ${QUERY_JSON_LIST_LIMIT} with --json; 0 = all); the first ${QUERY_CACHE_LIMIT} (or more with a higher limit) are indexed`, integerOption(0))
|
|
54
57
|
.option('-j, --json', 'Output as JSON')
|
|
55
58
|
.addHelpText('after', SELECTOR_SCOPE_HELP)
|
|
56
59
|
.action(async (selector, options) => {
|
|
@@ -61,6 +64,7 @@ export function registerDomCommands(program) {
|
|
|
61
64
|
.description('Evaluate JavaScript expression in the page context')
|
|
62
65
|
.argument('<script>', 'JavaScript to execute (e.g., "document.title", "window.location.href")')
|
|
63
66
|
.option('--frame <frame>', 'Evaluate in an iframe, cross-origin ones included: index (from dom frames; 87 when stale), name/id attribute, or part of the name, id or URL')
|
|
67
|
+
.option('--full', EVAL_FULL_HELP)
|
|
64
68
|
.option('-j, --json', 'Output as JSON')
|
|
65
69
|
.action(async (script, options) => {
|
|
66
70
|
await handleDomEval(script, options);
|
|
@@ -70,6 +74,7 @@ export function registerDomCommands(program) {
|
|
|
70
74
|
.description('Shortcut for: bdg dom eval')
|
|
71
75
|
.argument('<script>', 'JavaScript to execute')
|
|
72
76
|
.option('--frame <frame>', 'Evaluate in an iframe (see dom frames)')
|
|
77
|
+
.option('--full', EVAL_FULL_HELP)
|
|
73
78
|
.option('-j, --json', 'Output as JSON')
|
|
74
79
|
.action(async (script, options) => {
|
|
75
80
|
await handleDomEval(script, options);
|
|
@@ -86,7 +91,7 @@ export function registerDomCommands(program) {
|
|
|
86
91
|
.description('Get semantic accessibility structure (default) or raw HTML (--raw)')
|
|
87
92
|
.argument('[selectorOrIndex]', `${SELECTOR_OR_INDEX_ARGUMENT} (e.g. ".error", "#app", 0); default: ${DOM_GET_DEFAULT_SELECTOR}`)
|
|
88
93
|
.option('--raw', 'Output raw HTML with all filtering options')
|
|
89
|
-
.option('--full',
|
|
94
|
+
.option('--full', `Show all of the element text (default: the first 500 characters); with --raw, all of the HTML (default: the first ${MAX_VALUE_LENGTH} characters)`)
|
|
90
95
|
.option('--all', 'Get all matches (only with --raw)')
|
|
91
96
|
.option('--index <n>', 'Element index if selector matches multiple (0-based)', integerOption(0))
|
|
92
97
|
.addOption(new Option('--nth <n>', 'Alias of --index').argParser(integerOption(0)).hideHelp())
|
|
@@ -10,7 +10,8 @@ export declare const QUERY_LIST_LIMIT = 50;
|
|
|
10
10
|
*
|
|
11
11
|
* Runs the selector, caches the described matches so later commands can
|
|
12
12
|
* reference elements by index, and lists the first `--limit` of them (50,
|
|
13
|
-
* or {@link
|
|
13
|
+
* or {@link QUERY_JSON_LIST_LIMIT} with `--json`; 0 = all) with the total
|
|
14
|
+
* count. The first {@link QUERY_CACHE_LIMIT} are indexed whatever is listed.
|
|
14
15
|
* No match exits 83, like `dom get` and `dom a11y`, and clears the cache so
|
|
15
16
|
* indices of an earlier query are not used by mistake.
|
|
16
17
|
*/
|
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
* `bdg dom query` — find elements by CSS selector and populate the query cache.
|
|
3
3
|
*/
|
|
4
4
|
import { noMatchesError, pageDocumentId, queryDOMElements } from './helpers/index.js';
|
|
5
|
-
import {
|
|
5
|
+
import { VIEWPORT_HINT_LIMIT } from './helpers/query.js';
|
|
6
6
|
import { runCommand } from '../shared/CommandRunner.js';
|
|
7
|
+
import { QUERY_JSON_LIST_LIMIT } from '../../constants.js';
|
|
7
8
|
import { QueryCacheManager } from '../../session/QueryCacheManager.js';
|
|
8
9
|
import { formatDomQuery } from '../../ui/formatters/dom.js';
|
|
9
10
|
import { EXIT_CODES } from '../../utils/exitCodes.js';
|
|
@@ -14,12 +15,13 @@ export const QUERY_LIST_LIMIT = 50;
|
|
|
14
15
|
*
|
|
15
16
|
* Runs the selector, caches the described matches so later commands can
|
|
16
17
|
* reference elements by index, and lists the first `--limit` of them (50,
|
|
17
|
-
* or {@link
|
|
18
|
+
* or {@link QUERY_JSON_LIST_LIMIT} with `--json`; 0 = all) with the total
|
|
19
|
+
* count. The first {@link QUERY_CACHE_LIMIT} are indexed whatever is listed.
|
|
18
20
|
* No match exits 83, like `dom get` and `dom a11y`, and clears the cache so
|
|
19
21
|
* indices of an earlier query are not used by mistake.
|
|
20
22
|
*/
|
|
21
23
|
export async function handleDomQuery(selector, options) {
|
|
22
|
-
const limit = options.limit ?? (options.json ?
|
|
24
|
+
const limit = options.limit ?? (options.json ? QUERY_JSON_LIST_LIMIT : QUERY_LIST_LIMIT);
|
|
23
25
|
await runCommand(async () => {
|
|
24
26
|
const document = await pageDocumentId();
|
|
25
27
|
const result = await queryDOMElements(selector, limit);
|
|
@@ -189,6 +189,7 @@ async function handleSequenceCapture(outputDir, options) {
|
|
|
189
189
|
/**
|
|
190
190
|
* End a capture sequence on an error (e.g. the element disappeared): print
|
|
191
191
|
* it (one JSON line with `--json`, like the frames) and exit with its code.
|
|
192
|
+
* It stays compact on a terminal too, since it ends the NDJSON frame stream.
|
|
192
193
|
*
|
|
193
194
|
* @param error - Capture error
|
|
194
195
|
* @param captured - Frames captured before it
|
|
@@ -93,7 +93,7 @@ export interface CompactCommand {
|
|
|
93
93
|
name: string;
|
|
94
94
|
/** Command aliases (only when it has some) */
|
|
95
95
|
aliases?: readonly string[];
|
|
96
|
-
/**
|
|
96
|
+
/** Summary if set, else the first line of the description */
|
|
97
97
|
description: string;
|
|
98
98
|
/** Arguments as in usage, e.g. "<selector> [index]" (only when it takes some) */
|
|
99
99
|
arguments?: string;
|
|
@@ -36,7 +36,7 @@ function convertOption(option, commandName) {
|
|
|
36
36
|
if (option.argChoices) {
|
|
37
37
|
metadata.choices = option.argChoices;
|
|
38
38
|
}
|
|
39
|
-
const behavior = getOptionBehavior(commandName, option
|
|
39
|
+
const behavior = getOptionBehavior(commandName, option);
|
|
40
40
|
if (behavior) {
|
|
41
41
|
metadata.behavior = behavior;
|
|
42
42
|
}
|
|
@@ -114,8 +114,8 @@ function argumentTerm(argument) {
|
|
|
114
114
|
return argument.required ? `<${name}>` : `[${name}]`;
|
|
115
115
|
}
|
|
116
116
|
/**
|
|
117
|
-
* Recursively converts a Commander Command to its compact summary:
|
|
118
|
-
* description line, arguments, and visible options with their descriptions.
|
|
117
|
+
* Recursively converts a Commander Command to its compact summary: summary
|
|
118
|
+
* (or first description line), arguments, and visible options with their descriptions.
|
|
119
119
|
* Empty fields are left out.
|
|
120
120
|
*
|
|
121
121
|
* @param command - Commander command instance
|
|
@@ -128,7 +128,7 @@ function convertCompactCommand(command) {
|
|
|
128
128
|
return {
|
|
129
129
|
name: command.name(),
|
|
130
130
|
...(aliases.length > 0 && { aliases }),
|
|
131
|
-
description: command.description().split('\n')[0] ?? '',
|
|
131
|
+
description: command.summary() || (command.description().split('\n')[0] ?? ''),
|
|
132
132
|
...(args && { arguments: args }),
|
|
133
133
|
...(options.length > 0 && {
|
|
134
134
|
options: Object.fromEntries(options.map((option) => [option.flags, option.description])),
|
|
@@ -3,11 +3,14 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { commandPath } from './helpJson.js';
|
|
5
5
|
import { CommandError } from '../errors/index.js';
|
|
6
|
-
import { missingSubcommandMessage, unknownHelpTopicError, usageHelpSuggestion, } from '../errors/messages.js';
|
|
6
|
+
import { didYouMeanSuggestion, missingSubcommandMessage, unknownHelpTopicError, usageHelpSuggestion, } from '../errors/messages.js';
|
|
7
7
|
import { EXIT_CODES } from '../utils/exitCodes.js';
|
|
8
8
|
import { findSimilar } from '../utils/suggestions.js';
|
|
9
|
-
/**
|
|
10
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Commander's typo hint on its own line: "(Did you mean query?)" or, for
|
|
11
|
+
* several equally close candidates, "(Did you mean one of form, frames?)"
|
|
12
|
+
*/
|
|
13
|
+
const COMMANDER_HINT = /\n?\(Did you mean (?:one of )?(.+)\?\)\s*$/;
|
|
11
14
|
/**
|
|
12
15
|
* The command path of `bdg help <path...>`: the words before the first option.
|
|
13
16
|
*
|
|
@@ -54,7 +57,10 @@ export function splitCommanderHint(text) {
|
|
|
54
57
|
const hint = COMMANDER_HINT.exec(message);
|
|
55
58
|
if (!hint)
|
|
56
59
|
return { message };
|
|
57
|
-
return {
|
|
60
|
+
return {
|
|
61
|
+
message: message.slice(0, hint.index).trim(),
|
|
62
|
+
suggestion: didYouMeanSuggestion((hint[1] ?? '').split(', ')),
|
|
63
|
+
};
|
|
58
64
|
}
|
|
59
65
|
/** The option named in Commander's "unknown option '--x'" message, without an `=value` */
|
|
60
66
|
const UNKNOWN_OPTION = /unknown option '([^'=]+)/;
|
|
@@ -15,6 +15,7 @@ import { getSessionFilePath } from '../../session/paths.js';
|
|
|
15
15
|
import { applyFilters, parseFilterString } from '../../telemetry/filterDsl.js';
|
|
16
16
|
import { buildHAR } from '../../telemetry/har/builder.js';
|
|
17
17
|
import { createLogger } from '../../ui/logging/index.js';
|
|
18
|
+
import { harExportedMessage } from '../../ui/messages/networkMessages.js';
|
|
18
19
|
import { getErrorMessage } from '../../utils/errors.js';
|
|
19
20
|
import { VERSION } from '../../utils/version.js';
|
|
20
21
|
import { getNetworkRequests, validateFilterOption } from './shared.js';
|
|
@@ -67,15 +68,20 @@ async function getChromeVersion() {
|
|
|
67
68
|
function formatHARExport(data) {
|
|
68
69
|
if ('log' in data)
|
|
69
70
|
return JSON.stringify(data, null, 2);
|
|
70
|
-
|
|
71
|
-
return `✓ Exported ${data.entries} requests${filterNote} to ${data.file}`;
|
|
71
|
+
return harExportedMessage(data);
|
|
72
72
|
}
|
|
73
|
+
/** HAR files may hold credentials (--include-sensitive): readable by their owner only */
|
|
74
|
+
const HAR_FILE_MODE = 0o600;
|
|
73
75
|
/** Output path meaning "write the HAR to stdout" */
|
|
74
76
|
const STDOUT_PATH = '-';
|
|
75
77
|
/**
|
|
76
78
|
* Option for filter DSL.
|
|
77
79
|
*/
|
|
78
80
|
const filterDslOption = new Option('--filter <dsl>', 'Filter requests using DevTools DSL (e.g., "status-code:>=400")');
|
|
81
|
+
/**
|
|
82
|
+
* Option to keep credentials in the export.
|
|
83
|
+
*/
|
|
84
|
+
const includeSensitiveOption = new Option('--include-sensitive', 'Keep credentials (auth/cookie/API key headers, cookie values, URL tokens, password and token body fields); redacted by default');
|
|
79
85
|
/**
|
|
80
86
|
* Register HAR export command.
|
|
81
87
|
*
|
|
@@ -88,6 +94,7 @@ export function registerHarCommand(networkCmd) {
|
|
|
88
94
|
.description('Export network data as HAR 1.2 format')
|
|
89
95
|
.addOption(jsonOption())
|
|
90
96
|
.addOption(filterDslOption)
|
|
97
|
+
.addOption(includeSensitiveOption)
|
|
91
98
|
.action(async (outputFile, options) => {
|
|
92
99
|
await runCommand(async () => {
|
|
93
100
|
let requests = await getNetworkRequests();
|
|
@@ -103,21 +110,18 @@ export function registerHarCommand(networkCmd) {
|
|
|
103
110
|
if (outputPath !== STDOUT_PATH)
|
|
104
111
|
assertFilePath(outputPath, '.har');
|
|
105
112
|
const chromeVersion = await getChromeVersion();
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
...(chromeVersion && { chromeVersion }),
|
|
109
|
-
});
|
|
113
|
+
const includeSensitive = options.includeSensitive === true;
|
|
114
|
+
const har = buildHAR(requests, { version: VERSION, ...(chromeVersion && { chromeVersion }) }, { includeSensitive });
|
|
110
115
|
if (outputPath === STDOUT_PATH)
|
|
111
116
|
return { success: true, data: har };
|
|
112
|
-
const file = await writeOutputFile(outputPath, JSON.stringify(har, null, 2), '.har');
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
filtered,
|
|
119
|
-
},
|
|
117
|
+
const file = await writeOutputFile(outputPath, JSON.stringify(har, null, 2), '.har', HAR_FILE_MODE);
|
|
118
|
+
const data = {
|
|
119
|
+
file,
|
|
120
|
+
entries: har.log.entries.length,
|
|
121
|
+
filtered,
|
|
122
|
+
sanitized: !includeSensitive,
|
|
120
123
|
};
|
|
124
|
+
return { success: true, data };
|
|
121
125
|
}, options, formatHARExport);
|
|
122
126
|
});
|
|
123
127
|
}
|
|
@@ -112,13 +112,51 @@ function buildFormatOptions(options, result, lastLimit) {
|
|
|
112
112
|
totalCount: result.totalCount,
|
|
113
113
|
filteredCount: result.filteredCount,
|
|
114
114
|
...(result.pageStart && { pageStart: result.pageStart }),
|
|
115
|
+
evictions: {
|
|
116
|
+
requestsDropped: result.dropped ?? 0,
|
|
117
|
+
bodiesEvicted: result.bodiesEvicted ?? 0,
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Watch the session's dropped/evicted counts across follow polls, so the
|
|
123
|
+
* stream notes them once per kind instead of on every poll.
|
|
124
|
+
*
|
|
125
|
+
* @returns Function giving the counts when requests or bodies were let go
|
|
126
|
+
* for the first time since the stream started, otherwise undefined
|
|
127
|
+
*/
|
|
128
|
+
function newEvictionKinds() {
|
|
129
|
+
let requestsNoted = false;
|
|
130
|
+
let bodiesNoted = false;
|
|
131
|
+
return (counts) => {
|
|
132
|
+
const newRequests = !requestsNoted && counts.requestsDropped > 0;
|
|
133
|
+
const newBodies = !bodiesNoted && counts.bodiesEvicted > 0;
|
|
134
|
+
requestsNoted ||= newRequests;
|
|
135
|
+
bodiesNoted ||= newBodies;
|
|
136
|
+
return newRequests || newBodies ? counts : undefined;
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* JSON fields of the dropped/evicted counts (the non-zero ones).
|
|
141
|
+
*
|
|
142
|
+
* @param counts - Counts to report, if any
|
|
143
|
+
* @returns `dropped` and `bodiesEvicted` when non-zero
|
|
144
|
+
*/
|
|
145
|
+
function evictionFields(counts) {
|
|
146
|
+
if (!counts)
|
|
147
|
+
return {};
|
|
148
|
+
return {
|
|
149
|
+
...(counts.requestsDropped > 0 && { dropped: counts.requestsDropped }),
|
|
150
|
+
...(counts.bodiesEvicted > 0 && { bodiesEvicted: counts.bodiesEvicted }),
|
|
115
151
|
};
|
|
116
152
|
}
|
|
117
153
|
/**
|
|
118
154
|
* Stream network requests: the last `lastN` finished ones at start, then
|
|
119
155
|
* each request once, when it has finished loading or failed (a request whose
|
|
120
156
|
* headers arrived but whose body is still loading waits), like `tail -f`,
|
|
121
|
-
* with a warning (JSON `pageCrashedAt`) once when the page crashes
|
|
157
|
+
* with a warning (JSON `pageCrashedAt`) once when the page crashes, and a
|
|
158
|
+
* note (JSON `dropped`, `bodiesEvicted`) the first time the session drops
|
|
159
|
+
* requests or evicts bodies at its limits.
|
|
122
160
|
*
|
|
123
161
|
* @param options - Command options
|
|
124
162
|
* @param resourceTypes - Validated resource types
|
|
@@ -127,6 +165,7 @@ function buildFormatOptions(options, result, lastLimit) {
|
|
|
127
165
|
async function runFollowMode(options, resourceTypes, lastN) {
|
|
128
166
|
const shown = new Set();
|
|
129
167
|
const newCrash = newPageCrashes();
|
|
168
|
+
const newEviction = newEvictionKinds();
|
|
130
169
|
let started = false;
|
|
131
170
|
const showNetwork = async () => {
|
|
132
171
|
const result = await fetchNetworkRequests(filtersNeedHeaders(options));
|
|
@@ -136,6 +175,7 @@ async function runFollowMode(options, resourceTypes, lastN) {
|
|
|
136
175
|
noteFollowConnected();
|
|
137
176
|
const { requests } = result.data;
|
|
138
177
|
const crashedAt = newCrash(result.data.pageCrashedAt);
|
|
178
|
+
const evictions = newEviction(result.data.evictions);
|
|
139
179
|
const finished = filterRequests(requests, options, resourceTypes).filter((request) => request.duration !== undefined && !shown.has(request.requestId));
|
|
140
180
|
const present = new Set(requests.map((request) => request.requestId));
|
|
141
181
|
for (const id of shown)
|
|
@@ -144,12 +184,13 @@ async function runFollowMode(options, resourceTypes, lastN) {
|
|
|
144
184
|
finished.forEach((request) => shown.add(request.requestId));
|
|
145
185
|
const fresh = started || lastN === 0 ? finished : finished.slice(-lastN);
|
|
146
186
|
if (options.json) {
|
|
147
|
-
if (!started || fresh.length > 0 || crashedAt !== undefined) {
|
|
187
|
+
if (!started || fresh.length > 0 || crashedAt !== undefined || evictions) {
|
|
148
188
|
const data = {
|
|
149
189
|
requests: fresh,
|
|
150
190
|
totalCount: requests.length,
|
|
151
191
|
filteredCount: fresh.length,
|
|
152
192
|
...(crashedAt !== undefined && { pageCrashedAt: crashedAt }),
|
|
193
|
+
...evictionFields(evictions),
|
|
153
194
|
};
|
|
154
195
|
console.log(JSON.stringify(buildSuccessResponse(data)));
|
|
155
196
|
}
|
|
@@ -160,6 +201,7 @@ async function runFollowMode(options, resourceTypes, lastN) {
|
|
|
160
201
|
header: !started,
|
|
161
202
|
verbose: options.verbose ?? false,
|
|
162
203
|
...(pageStart && { pageStart }),
|
|
204
|
+
...(evictions && { evictions }),
|
|
163
205
|
});
|
|
164
206
|
if (text)
|
|
165
207
|
console.log(text);
|
|
@@ -227,7 +269,7 @@ export function registerListCommand(networkCmd) {
|
|
|
227
269
|
}
|
|
228
270
|
return createErrorResult(result.error, result.exitCode, result.suggestion);
|
|
229
271
|
}
|
|
230
|
-
const { requests, pageCrashedAt } = result.data;
|
|
272
|
+
const { requests, pageCrashedAt, evictions } = result.data;
|
|
231
273
|
const filtered = filterRequests(requests, options, resourceTypes);
|
|
232
274
|
const pageStart = pageStartOf(requests);
|
|
233
275
|
return {
|
|
@@ -238,6 +280,7 @@ export function registerListCommand(networkCmd) {
|
|
|
238
280
|
filteredCount: filtered.length,
|
|
239
281
|
...(pageStart && { pageStart }),
|
|
240
282
|
...(pageCrashedAt !== undefined && { pageCrashedAt }),
|
|
283
|
+
...evictionFields(evictions),
|
|
241
284
|
},
|
|
242
285
|
};
|
|
243
286
|
}, options, (data) => withPageCrashedNote(formatNetworkList(data.requests, buildFormatOptions(options, data, lastN)), data.pageCrashedAt));
|
|
@@ -6,13 +6,36 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @see docs/principles/SELF_DOCUMENTING_SYSTEMS.md
|
|
8
8
|
*/
|
|
9
|
+
import type { Option } from 'commander';
|
|
9
10
|
import type { OptionBehavior } from './helpJson.js';
|
|
11
|
+
/**
|
|
12
|
+
* Registry key format: last command name, colon, long flag (the short flag
|
|
13
|
+
* when there is no long one), e.g. "screenshot:--no-resize", "bdg:--headless"
|
|
14
|
+
*/
|
|
15
|
+
type BehaviorKey = string;
|
|
16
|
+
/**
|
|
17
|
+
* Build behavior registry key from command and option: the command's own
|
|
18
|
+
* name (`bdg` for the root) and the option's long flag, or its short flag
|
|
19
|
+
* when it has no long one.
|
|
20
|
+
*
|
|
21
|
+
* @param commandName - Command name (e.g., "screenshot")
|
|
22
|
+
* @param option - Commander option
|
|
23
|
+
* @returns Registry key
|
|
24
|
+
*/
|
|
25
|
+
export declare function behaviorKey(commandName: string, option: Option): BehaviorKey;
|
|
26
|
+
/**
|
|
27
|
+
* Every key in the behavior registry.
|
|
28
|
+
*
|
|
29
|
+
* @returns Registry keys
|
|
30
|
+
*/
|
|
31
|
+
export declare function listBehaviorKeys(): BehaviorKey[];
|
|
10
32
|
/**
|
|
11
33
|
* Look up behavioral metadata for an option.
|
|
12
34
|
*
|
|
13
35
|
* @param commandName - Name of the command containing the option
|
|
14
|
-
* @param
|
|
36
|
+
* @param option - Commander option
|
|
15
37
|
* @returns Behavioral metadata if registered, undefined otherwise
|
|
16
38
|
*/
|
|
17
|
-
export declare function getOptionBehavior(commandName: string,
|
|
39
|
+
export declare function getOptionBehavior(commandName: string, option: Option): OptionBehavior | undefined;
|
|
40
|
+
export {};
|
|
18
41
|
//# sourceMappingURL=optionBehaviors.d.ts.map
|