browser-debugger-cli 0.9.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -1
- package/dist/commands/dom/a11y.js +2 -1
- package/dist/commands/dom/formInteraction.js +56 -25
- package/dist/commands/dom/helpers/keyAttributes.d.ts +20 -0
- package/dist/commands/dom/helpers/keyAttributes.js +54 -0
- package/dist/commands/dom/helpers/query.d.ts +1 -1
- package/dist/commands/dom/helpers/query.js +66 -19
- package/dist/commands/dom/helpers/runElementCommand.js +4 -3
- package/dist/commands/dom/helpers/screenshot.js +85 -12
- package/dist/commands/dom/index.d.ts +1 -0
- package/dist/commands/dom/index.js +8 -3
- package/dist/commands/dom/inspect.d.ts +15 -0
- package/dist/commands/dom/inspect.js +82 -0
- package/dist/commands/dom/layout.js +2 -2
- package/dist/commands/dom/listeners.js +2 -2
- package/dist/commands/dom/semanticUtils.d.ts +14 -1
- package/dist/commands/dom/semanticUtils.js +44 -3
- package/dist/commands/network/list.js +13 -2
- package/dist/commands/optionBehaviors.js +48 -6
- package/dist/commands/page.d.ts +1 -1
- package/dist/commands/page.js +62 -3
- package/dist/commands/shared/commonOptions.d.ts +4 -0
- package/dist/commands/shared/commonOptions.js +9 -0
- package/dist/commands/shared/optionTypes.d.ts +21 -0
- package/dist/commands/shared/startHelpers.d.ts +66 -0
- package/dist/commands/shared/startHelpers.js +91 -10
- package/dist/commands/shared/validation.d.ts +11 -0
- package/dist/commands/shared/validation.js +16 -0
- package/dist/daemon/launcher.d.ts +8 -1
- package/dist/daemon/launcher.js +3 -1
- package/dist/daemon/session/Session.d.ts +7 -0
- package/dist/daemon/session/Session.js +23 -1
- package/dist/daemon/session/commandRegistry.d.ts +14 -1
- package/dist/daemon/session/commandRegistry.js +65 -9
- package/dist/daemon/session/interactions.d.ts +18 -5
- package/dist/daemon/session/interactions.js +22 -12
- package/dist/daemon.js +3565 -329
- package/dist/errors/messages.d.ts +69 -0
- package/dist/errors/messages.js +102 -1
- package/dist/index.js +2416 -1320
- package/dist/ipc/client.d.ts +9 -0
- package/dist/ipc/client.js +13 -0
- package/dist/ipc/protocol/commands.d.ts +56 -1
- package/dist/ipc/protocol/commands.js +2 -0
- package/dist/ipc/protocol/domTypes.d.ts +35 -2
- package/dist/ipc/protocol/inspectTypes.d.ts +388 -0
- package/dist/ipc/protocol/inspectTypes.js +10 -0
- package/dist/runtime/dom/actionEffects.d.ts +94 -15
- package/dist/runtime/dom/actionEffects.js +173 -27
- package/dist/runtime/dom/actionEffectsScripts.d.ts +52 -14
- package/dist/runtime/dom/actionEffectsScripts.js +224 -32
- package/dist/runtime/dom/elementInfo.d.ts +26 -0
- package/dist/runtime/dom/elementInfo.js +65 -0
- package/dist/runtime/dom/eventListeners.js +14 -4
- package/dist/runtime/dom/formFillHelpers/fill.d.ts +3 -4
- package/dist/runtime/dom/formFillHelpers/fill.js +77 -28
- package/dist/runtime/dom/frameSelection.d.ts +11 -0
- package/dist/runtime/dom/frameSelection.js +20 -1
- package/dist/runtime/dom/frames.d.ts +38 -5
- package/dist/runtime/dom/frames.js +136 -21
- package/dist/runtime/dom/inspect.d.ts +28 -0
- package/dist/runtime/dom/inspect.js +557 -0
- package/dist/runtime/dom/inspectAllStyles.d.ts +62 -0
- package/dist/runtime/dom/inspectAllStyles.js +385 -0
- package/dist/runtime/dom/inspectCascade.d.ts +94 -0
- package/dist/runtime/dom/inspectCascade.js +371 -0
- package/dist/runtime/dom/inspectCascadeModel.d.ts +39 -0
- package/dist/runtime/dom/inspectCascadeModel.js +232 -0
- package/dist/runtime/dom/inspectHints.d.ts +62 -0
- package/dist/runtime/dom/inspectHints.js +305 -0
- package/dist/runtime/dom/inspectLayoutModel.d.ts +87 -0
- package/dist/runtime/dom/inspectLayoutModel.js +346 -0
- package/dist/runtime/dom/inspectModel.d.ts +74 -0
- package/dist/runtime/dom/inspectModel.js +184 -0
- package/dist/runtime/dom/inspectPaintModel.d.ts +157 -0
- package/dist/runtime/dom/inspectPaintModel.js +461 -0
- package/dist/runtime/dom/inspectRules.d.ts +37 -0
- package/dist/runtime/dom/inspectRules.js +101 -0
- package/dist/runtime/dom/inspectScripts.d.ts +132 -0
- package/dist/runtime/dom/inspectScripts.js +263 -0
- package/dist/runtime/dom/inspectTree.d.ts +40 -0
- package/dist/runtime/dom/inspectTree.js +134 -0
- package/dist/runtime/dom/inspectVariables.d.ts +33 -0
- package/dist/runtime/dom/inspectVariables.js +94 -0
- package/dist/runtime/dom/inspectWhyModel.d.ts +20 -0
- package/dist/runtime/dom/inspectWhyModel.js +134 -0
- package/dist/runtime/dom/layout.d.ts +5 -1
- package/dist/runtime/dom/layout.js +10 -3
- package/dist/runtime/dom/listenerPageScripts.d.ts +11 -5
- package/dist/runtime/dom/listenerPageScripts.js +95 -9
- package/dist/runtime/dom/listenerSummary.d.ts +4 -0
- package/dist/runtime/dom/listenerSummary.js +26 -9
- package/dist/runtime/dom/reactEventHelpers.d.ts +5 -0
- package/dist/runtime/dom/reactEventHelpers.js +12 -4
- package/dist/runtime/page/emulation.d.ts +20 -0
- package/dist/runtime/page/emulation.js +37 -0
- package/dist/telemetry/a11y.d.ts +10 -0
- package/dist/telemetry/a11y.js +78 -1
- package/dist/telemetry/console.d.ts +1 -0
- package/dist/telemetry/console.js +100 -5
- package/dist/telemetry/network.js +3 -1
- package/dist/types.d.ts +32 -0
- package/dist/ui/formatters/details.d.ts +8 -0
- package/dist/ui/formatters/details.js +59 -3
- package/dist/ui/formatters/dom.d.ts +2 -1
- package/dist/ui/formatters/dom.js +25 -9
- package/dist/ui/formatters/inspect.d.ts +39 -0
- package/dist/ui/formatters/inspect.js +596 -0
- package/dist/ui/formatters/keyAttributes.d.ts +19 -0
- package/dist/ui/formatters/keyAttributes.js +84 -0
- package/dist/ui/formatters/layout.js +2 -2
- package/dist/ui/formatters/networkHeaders.d.ts +13 -0
- package/dist/ui/formatters/networkHeaders.js +23 -3
- package/dist/ui/formatters/networkList.d.ts +29 -1
- package/dist/ui/formatters/networkList.js +86 -20
- package/dist/ui/formatters/status.js +1 -1
- package/dist/ui/formatting.d.ts +9 -0
- package/dist/ui/formatting.js +6 -3
- package/dist/ui/messages/commands.d.ts +123 -7
- package/dist/ui/messages/commands.js +181 -10
- package/dist/ui/messages/networkMessages.d.ts +14 -0
- package/dist/ui/messages/networkMessages.js +18 -0
- package/dist/ui/messages/session.d.ts +14 -0
- package/dist/ui/messages/session.js +20 -0
- package/dist/utils/async.d.ts +9 -0
- package/dist/utils/async.js +17 -0
- package/dist/utils/color.d.ts +84 -0
- package/dist/utils/color.js +376 -0
- package/dist/utils/cssValues.d.ts +109 -0
- package/dist/utils/cssValues.js +236 -0
- package/dist/utils/selectorFilters.d.ts +12 -0
- package/dist/utils/selectorFilters.js +29 -0
- package/package.json +1 -1
|
@@ -138,11 +138,32 @@ async function restoreScrollPosition(position) {
|
|
|
138
138
|
returnByValue: true,
|
|
139
139
|
});
|
|
140
140
|
}
|
|
141
|
+
/**
|
|
142
|
+
* The window's size with its scrollbars (`innerWidth`/`innerHeight`): an
|
|
143
|
+
* override at this size keeps the page's layout, where the visible size
|
|
144
|
+
* (without scrollbars) would narrow it and move centered content.
|
|
145
|
+
*
|
|
146
|
+
* @param viewport - Visible viewport size, used when the page does not answer
|
|
147
|
+
* @returns Width and height in CSS px
|
|
148
|
+
*/
|
|
149
|
+
async function windowSize(viewport) {
|
|
150
|
+
const response = await callCDP('Runtime.evaluate', {
|
|
151
|
+
expression: '[window.innerWidth, window.innerHeight]',
|
|
152
|
+
returnByValue: true,
|
|
153
|
+
});
|
|
154
|
+
const value = response.data?.result?.result?.value;
|
|
155
|
+
const [width, height] = Array.isArray(value) ? value : [];
|
|
156
|
+
return {
|
|
157
|
+
width: Math.round(width ?? viewport.clientWidth),
|
|
158
|
+
height: Math.round(height ?? viewport.clientHeight),
|
|
159
|
+
};
|
|
160
|
+
}
|
|
141
161
|
/**
|
|
142
162
|
* Capture at a pixel ratio of 1 (CSS px = image px) on a high-DPI display:
|
|
143
|
-
* the viewport is overridden at
|
|
144
|
-
*
|
|
145
|
-
*
|
|
163
|
+
* the viewport is overridden at the window's size (the session's
|
|
164
|
+
* `--viewport`, else the window with its scrollbars, so the layout does not
|
|
165
|
+
* change) until the returned function puts back what was there before, the
|
|
166
|
+
* session's viewport or none.
|
|
146
167
|
*
|
|
147
168
|
* @param devicePixelRatio - Page's pixel ratio
|
|
148
169
|
* @param viewport - Visible viewport size
|
|
@@ -152,10 +173,7 @@ async function useUnitPixelRatio(devicePixelRatio, viewport) {
|
|
|
152
173
|
if (devicePixelRatio === 1)
|
|
153
174
|
return () => Promise.resolve();
|
|
154
175
|
const sessionViewport = readSessionMetadata()?.viewport;
|
|
155
|
-
const size = sessionViewport ??
|
|
156
|
-
width: Math.round(viewport.clientWidth),
|
|
157
|
-
height: Math.round(viewport.clientHeight),
|
|
158
|
-
};
|
|
176
|
+
const size = sessionViewport ?? (await windowSize(viewport));
|
|
159
177
|
await callCDP('Emulation.setDeviceMetricsOverride', viewportOverride(size, 1));
|
|
160
178
|
return async () => {
|
|
161
179
|
if (sessionViewport) {
|
|
@@ -350,6 +368,53 @@ const CONTENT_OVERFLOW_JS = `function () {
|
|
|
350
368
|
if (!clips(view.getComputedStyle(this))) walk(this);
|
|
351
369
|
return { left: own.left - reach.left, top: own.top - reach.top, right: reach.right - own.right, bottom: reach.bottom - own.bottom };
|
|
352
370
|
}`;
|
|
371
|
+
/**
|
|
372
|
+
* The visible viewport (without scrollbars) in CSS px.
|
|
373
|
+
*
|
|
374
|
+
* @returns Width and height
|
|
375
|
+
*/
|
|
376
|
+
async function visibleViewport() {
|
|
377
|
+
const metrics = (await callCDP('Page.getLayoutMetrics', {})).data?.result;
|
|
378
|
+
const view = metrics?.cssVisualViewport ?? metrics?.visualViewport;
|
|
379
|
+
return { width: view?.clientWidth ?? 0, height: view?.clientHeight ?? 0 };
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Whether an area (viewport coordinates) lies inside the viewport.
|
|
383
|
+
*
|
|
384
|
+
* @param area - Area
|
|
385
|
+
* @param view - Viewport size
|
|
386
|
+
* @returns True when fully inside
|
|
387
|
+
*/
|
|
388
|
+
function insideView(area, view) {
|
|
389
|
+
return (area.x >= 0 &&
|
|
390
|
+
area.y >= 0 &&
|
|
391
|
+
area.x + area.width <= view.width &&
|
|
392
|
+
area.y + area.height <= view.height);
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Measure the area to capture and, when it fits in the viewport but is not
|
|
396
|
+
* in view, scroll it to the middle first. A capture inside the viewport
|
|
397
|
+
* keeps the page as it is; one beyond it makes Chrome lay the page out
|
|
398
|
+
* without its scrollbar, which moves centered content by half the
|
|
399
|
+
* scrollbar's width, so it is used only for areas larger than the viewport.
|
|
400
|
+
*
|
|
401
|
+
* @param ref - Node reference
|
|
402
|
+
* @returns Border box, area to capture (viewport coordinates) and whether it is in view
|
|
403
|
+
*/
|
|
404
|
+
async function measureInView(ref) {
|
|
405
|
+
const view = await visibleViewport();
|
|
406
|
+
let box = await getElementBounds(ref);
|
|
407
|
+
let bounds = await captureArea(ref, box);
|
|
408
|
+
const fits = bounds.width <= view.width && bounds.height <= view.height;
|
|
409
|
+
if (fits && !insideView(bounds, view)) {
|
|
410
|
+
const dx = bounds.x + bounds.width / 2 - view.width / 2;
|
|
411
|
+
const dy = bounds.y + bounds.height / 2 - view.height / 2;
|
|
412
|
+
await callCDP('Runtime.evaluate', { expression: `window.scrollBy(${dx}, ${dy})` });
|
|
413
|
+
box = await getElementBounds(ref);
|
|
414
|
+
bounds = await captureArea(ref, box);
|
|
415
|
+
}
|
|
416
|
+
return { box, bounds, inView: insideView(bounds, view) };
|
|
417
|
+
}
|
|
353
418
|
/** Overflow (px) below which the capture keeps to the border box (subpixel rounding) */
|
|
354
419
|
const OVERFLOW_SLACK = 1;
|
|
355
420
|
/**
|
|
@@ -403,8 +468,6 @@ async function captureArea(ref, bounds) {
|
|
|
403
468
|
* (the reported bounds are page coordinates, like `dom layout`'s).
|
|
404
469
|
*/
|
|
405
470
|
export async function captureElementScreenshot(outputPath, ref, options = {}) {
|
|
406
|
-
const box = await getElementBounds(ref);
|
|
407
|
-
const bounds = await captureArea(ref, box);
|
|
408
471
|
const format = options.format ?? 'png';
|
|
409
472
|
const quality = format === 'jpeg' ? (options.quality ?? 90) : undefined;
|
|
410
473
|
const noResize = options.noResize ?? false;
|
|
@@ -413,6 +476,18 @@ export async function captureElementScreenshot(outputPath, ref, options = {}) {
|
|
|
413
476
|
returnByValue: true,
|
|
414
477
|
});
|
|
415
478
|
const devicePixelRatio = dprResponse.data?.result?.result?.value ?? 1;
|
|
479
|
+
const before = (await callCDP('Page.getLayoutMetrics', {})).data?.result;
|
|
480
|
+
const restoreMetrics = await useUnitPixelRatio(devicePixelRatio, before?.visualViewport ?? { clientWidth: 800, clientHeight: 600 });
|
|
481
|
+
let box;
|
|
482
|
+
let bounds;
|
|
483
|
+
let inView;
|
|
484
|
+
try {
|
|
485
|
+
({ box, bounds, inView } = await measureInView(ref));
|
|
486
|
+
}
|
|
487
|
+
catch (error) {
|
|
488
|
+
await restoreMetrics();
|
|
489
|
+
throw error;
|
|
490
|
+
}
|
|
416
491
|
const originalWidth = bounds.width;
|
|
417
492
|
const originalHeight = bounds.height;
|
|
418
493
|
const resized = shouldResize(originalWidth, originalHeight, noResize);
|
|
@@ -421,7 +496,6 @@ export async function captureElementScreenshot(outputPath, ref, options = {}) {
|
|
|
421
496
|
const finalHeight = Math.round(originalHeight * scale);
|
|
422
497
|
const metricsResponse = await callCDP('Page.getLayoutMetrics', {});
|
|
423
498
|
const metricsResult = metricsResponse.data?.result;
|
|
424
|
-
const viewport = metricsResult?.visualViewport ?? { clientWidth: 800, clientHeight: 600 };
|
|
425
499
|
const scroll = metricsResult?.cssLayoutViewport ?? { pageX: 0, pageY: 0 };
|
|
426
500
|
const onPage = (area) => ({
|
|
427
501
|
...area,
|
|
@@ -429,14 +503,13 @@ export async function captureElementScreenshot(outputPath, ref, options = {}) {
|
|
|
429
503
|
y: area.y + scroll.pageY,
|
|
430
504
|
});
|
|
431
505
|
const clip = onPage(bounds);
|
|
432
|
-
const restoreMetrics = await useUnitPixelRatio(devicePixelRatio, viewport);
|
|
433
506
|
let screenshotResult;
|
|
434
507
|
try {
|
|
435
508
|
const screenshotResponse = await callCDP('Page.captureScreenshot', {
|
|
436
509
|
format,
|
|
437
510
|
...(quality !== undefined && { quality }),
|
|
438
511
|
clip: { ...clip, scale },
|
|
439
|
-
captureBeyondViewport:
|
|
512
|
+
captureBeyondViewport: !inView,
|
|
440
513
|
});
|
|
441
514
|
screenshotResult = screenshotResponse.data?.result;
|
|
442
515
|
}
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* - `frames.ts` — list the page's iframes
|
|
10
10
|
* - `listeners.ts` — list event listeners that run for an element
|
|
11
11
|
* - `layout.ts` — positions, sizes and visibility of elements
|
|
12
|
+
* - `inspect.ts` — what one element looks like (styles, box, layout, child tree)
|
|
12
13
|
* - `wait.ts` — wait for elements to appear, show, contain a text or go away
|
|
13
14
|
*
|
|
14
15
|
* Form-related commands register via `form.ts` and `formInteraction.ts`.
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* - `frames.ts` — list the page's iframes
|
|
10
10
|
* - `listeners.ts` — list event listeners that run for an element
|
|
11
11
|
* - `layout.ts` — positions, sizes and visibility of elements
|
|
12
|
+
* - `inspect.ts` — what one element looks like (styles, box, layout, child tree)
|
|
12
13
|
* - `wait.ts` — wait for elements to appear, show, contain a text or go away
|
|
13
14
|
*
|
|
14
15
|
* Form-related commands register via `form.ts` and `formInteraction.ts`.
|
|
@@ -20,11 +21,13 @@ import { handleDomEval } from './eval.js';
|
|
|
20
21
|
import { registerFormCommand } from './form.js';
|
|
21
22
|
import { handleDomFrames } from './frames.js';
|
|
22
23
|
import { DOM_GET_DEFAULT_SELECTOR, handleDomGet } from './get.js';
|
|
24
|
+
import { registerInspectCommand } from './inspect.js';
|
|
23
25
|
import { registerLayoutCommand } from './layout.js';
|
|
24
26
|
import { registerListenersCommand } from './listeners.js';
|
|
25
27
|
import { handleDomQuery } from './query.js';
|
|
26
28
|
import { handleDomScreenshot } from './screenshot.js';
|
|
27
29
|
import { registerWaitCommand } from './wait.js';
|
|
30
|
+
import { SELECTOR_OR_INDEX_ARGUMENT, SELECTOR_SCOPE_HELP, } from '../shared/commonOptions.js';
|
|
28
31
|
import { integerOption, screenshotFormatOption } from '../shared/validation.js';
|
|
29
32
|
/**
|
|
30
33
|
* Register DOM telemetry commands on the root Commander program.
|
|
@@ -38,12 +41,14 @@ export function registerDomCommands(program) {
|
|
|
38
41
|
registerFormCommand(dom);
|
|
39
42
|
registerListenersCommand(dom);
|
|
40
43
|
registerLayoutCommand(dom);
|
|
44
|
+
registerInspectCommand(dom);
|
|
41
45
|
registerWaitCommand(dom);
|
|
42
46
|
dom
|
|
43
47
|
.command('query')
|
|
44
48
|
.description('Find elements by CSS selector')
|
|
45
49
|
.argument('<selector>', 'CSS selector (e.g., ".error", "#app", "button")')
|
|
46
50
|
.option('-j, --json', 'Output as JSON')
|
|
51
|
+
.addHelpText('after', SELECTOR_SCOPE_HELP)
|
|
47
52
|
.action(async (selector, options) => {
|
|
48
53
|
await handleDomQuery(selector, options);
|
|
49
54
|
});
|
|
@@ -51,7 +56,7 @@ export function registerDomCommands(program) {
|
|
|
51
56
|
.command('eval')
|
|
52
57
|
.description('Evaluate JavaScript expression in the page context')
|
|
53
58
|
.argument('<script>', 'JavaScript to execute (e.g., "document.title", "window.location.href")')
|
|
54
|
-
.option('--frame <frame>', 'Evaluate in an iframe, cross-origin ones included: index, name/id attribute, or part of the name, id or URL
|
|
59
|
+
.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')
|
|
55
60
|
.option('-j, --json', 'Output as JSON')
|
|
56
61
|
.action(async (script, options) => {
|
|
57
62
|
await handleDomEval(script, options);
|
|
@@ -67,7 +72,7 @@ export function registerDomCommands(program) {
|
|
|
67
72
|
});
|
|
68
73
|
dom
|
|
69
74
|
.command('frames')
|
|
70
|
-
.description("List the page's iframes (nested and cross-origin ones included) for eval --frame")
|
|
75
|
+
.description("List the page's iframes in document order (nested and cross-origin ones included) for eval --frame")
|
|
71
76
|
.option('-j, --json', 'Output as JSON')
|
|
72
77
|
.action(async (options) => {
|
|
73
78
|
await handleDomFrames(options);
|
|
@@ -75,7 +80,7 @@ export function registerDomCommands(program) {
|
|
|
75
80
|
dom
|
|
76
81
|
.command('get')
|
|
77
82
|
.description('Get semantic accessibility structure (default) or raw HTML (--raw)')
|
|
78
|
-
.argument('[selectorOrIndex]',
|
|
83
|
+
.argument('[selectorOrIndex]', `${SELECTOR_OR_INDEX_ARGUMENT} (e.g. ".error", "#app", 0); default: ${DOM_GET_DEFAULT_SELECTOR}`)
|
|
79
84
|
.option('--raw', 'Output raw HTML with all filtering options')
|
|
80
85
|
.option('--full', 'Show all of the element text (default: the first 500 characters)')
|
|
81
86
|
.option('--all', 'Get all matches (only with --raw)')
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `bdg dom inspect <selector|index>` - what one element looks like, without a
|
|
3
|
+
* screenshot: its box, layout (and its place in the parent), typography with
|
|
4
|
+
* the rendered font and contrast, fills, borders, effects, CSS state,
|
|
5
|
+
* pseudo-elements and a compact child tree, as grouped lines (`--json`:
|
|
6
|
+
* Figma-aligned fields).
|
|
7
|
+
*/
|
|
8
|
+
import { type Command } from 'commander';
|
|
9
|
+
/**
|
|
10
|
+
* Register `bdg dom inspect`.
|
|
11
|
+
*
|
|
12
|
+
* @param dom - The `dom` command group
|
|
13
|
+
*/
|
|
14
|
+
export declare function registerInspectCommand(dom: Command): void;
|
|
15
|
+
//# sourceMappingURL=inspect.d.ts.map
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `bdg dom inspect <selector|index>` - what one element looks like, without a
|
|
3
|
+
* screenshot: its box, layout (and its place in the parent), typography with
|
|
4
|
+
* the rendered font and contrast, fills, borders, effects, CSS state,
|
|
5
|
+
* pseudo-elements and a compact child tree, as grouped lines (`--json`:
|
|
6
|
+
* Figma-aligned fields).
|
|
7
|
+
*/
|
|
8
|
+
import { Option } from 'commander';
|
|
9
|
+
import { runElementCommand } from './helpers/runElementCommand.js';
|
|
10
|
+
import { runCommand } from '../shared/CommandRunner.js';
|
|
11
|
+
import { jsonOption, SELECTOR_OR_INDEX_ARGUMENT } from '../shared/commonOptions.js';
|
|
12
|
+
import { cssPropertiesOption, integerOption } from '../shared/validation.js';
|
|
13
|
+
import { domInspect } from '../../ipc/client.js';
|
|
14
|
+
import { DEFAULT_TREE_DEPTH, DEFAULT_TREE_LIMIT } from '../../runtime/dom/inspectTree.js';
|
|
15
|
+
import { formatInspect } from '../../ui/formatters/inspect.js';
|
|
16
|
+
import { INSPECT_OUTPUT_LEGEND } from '../../ui/messages/commands.js';
|
|
17
|
+
import { filterDefined } from '../../utils/objects.js';
|
|
18
|
+
/**
|
|
19
|
+
* Register `bdg dom inspect`.
|
|
20
|
+
*
|
|
21
|
+
* @param dom - The `dom` command group
|
|
22
|
+
*/
|
|
23
|
+
export function registerInspectCommand(dom) {
|
|
24
|
+
dom
|
|
25
|
+
.command('inspect')
|
|
26
|
+
.description('What one element looks like without a screenshot: box, layout, font (rendered, contrast), colors, borders, effects, pseudo-elements and child tree')
|
|
27
|
+
.argument('<selectorOrIndex>', SELECTOR_OR_INDEX_ARGUMENT)
|
|
28
|
+
.option('--index <n>', 'Which match to inspect (0-based; default: the first rendered one)', integerOption(0))
|
|
29
|
+
.option('--tree <depth>', `Child tree depth (default ${DEFAULT_TREE_DEPTH}; 0 for none)`, integerOption(0, 10))
|
|
30
|
+
.option('--tree-limit <n>', `Child tree rows at most (default ${DEFAULT_TREE_LIMIT})`, integerOption(1, 500))
|
|
31
|
+
.addOption(new Option('--all', 'Every computed property that is not its default, collapsed into shorthands, instead of the groups').conflicts('props'))
|
|
32
|
+
.addHelpText('after', INSPECT_OUTPUT_LEGEND)
|
|
33
|
+
.option('--props <names>', "Only these properties, computed and normalized (comma-separated, e.g. padding,color,--brand; '--*' or '--bs-btn-*' lists custom properties)", cssPropertiesOption)
|
|
34
|
+
.addOption(new Option('--rules', 'Also show which CSS rule sets each shown property (selector, file:line, what it overrides); with --props, those properties').conflicts('all'))
|
|
35
|
+
.addOption(new Option('--why <property>', 'Every declaration of one property: the one that applies and those it overrides (e.g. --why color)')
|
|
36
|
+
.conflicts('all')
|
|
37
|
+
.argParser(propertyOption))
|
|
38
|
+
.option('--no-hints', 'Skip the check for declarations that have no effect')
|
|
39
|
+
.addOption(jsonOption())
|
|
40
|
+
.action(async (selectorOrIndex, options) => {
|
|
41
|
+
await runCommand(() => inspectTarget(selectorOrIndex, options), options, formatInspect);
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Parse `--why`: a property name, lowercased (custom properties as given).
|
|
46
|
+
*
|
|
47
|
+
* @param value - Property name
|
|
48
|
+
* @returns Name
|
|
49
|
+
*/
|
|
50
|
+
function propertyOption(value) {
|
|
51
|
+
return value.startsWith('--') ? value.trim() : value.trim().toLowerCase();
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Resolve the target and ask the daemon to inspect it.
|
|
55
|
+
*
|
|
56
|
+
* @param selectorOrIndex - CSS selector or cached query index
|
|
57
|
+
* @param options - Command options
|
|
58
|
+
* @returns Command result
|
|
59
|
+
*/
|
|
60
|
+
async function inspectTarget(selectorOrIndex, options) {
|
|
61
|
+
return runElementCommand({
|
|
62
|
+
selectorOrIndex,
|
|
63
|
+
index: options.index,
|
|
64
|
+
buildRequest: (target) => ({
|
|
65
|
+
...target,
|
|
66
|
+
...filterDefined({
|
|
67
|
+
tree: options.tree,
|
|
68
|
+
treeLimit: options.treeLimit,
|
|
69
|
+
all: options.all,
|
|
70
|
+
props: options.props,
|
|
71
|
+
rules: options.rules,
|
|
72
|
+
why: options.why,
|
|
73
|
+
...(options.hints === false && { hints: false }),
|
|
74
|
+
}),
|
|
75
|
+
}),
|
|
76
|
+
call: domInspect,
|
|
77
|
+
command: 'inspect',
|
|
78
|
+
action: 'inspect the element',
|
|
79
|
+
failureSuggestion: 'Verify the selector matches an element: bdg dom query "<selector>"',
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=inspect.js.map
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import { DomElementResolver } from './DomElementResolver.js';
|
|
8
8
|
import { runElementCommand } from './helpers/runElementCommand.js';
|
|
9
9
|
import { runCommand } from '../shared/CommandRunner.js';
|
|
10
|
-
import { jsonOption } from '../shared/commonOptions.js';
|
|
10
|
+
import { jsonOption, SELECTOR_OR_INDEX_ARGUMENT } from '../shared/commonOptions.js';
|
|
11
11
|
import { integerOption } from '../shared/validation.js';
|
|
12
12
|
import { domLayout } from '../../ipc/client.js';
|
|
13
13
|
import { formatLayout } from '../../ui/formatters/layout.js';
|
|
@@ -20,7 +20,7 @@ export function registerLayoutCommand(dom) {
|
|
|
20
20
|
dom
|
|
21
21
|
.command('layout')
|
|
22
22
|
.description('Positions, sizes and visibility of elements (above/below the fold, hidden, covered) without a screenshot')
|
|
23
|
-
.argument('<selectorOrIndex>',
|
|
23
|
+
.argument('<selectorOrIndex>', SELECTOR_OR_INDEX_ARGUMENT)
|
|
24
24
|
.option('--index <n>', 'Only this match of the selector (0-based)', integerOption(0))
|
|
25
25
|
.addOption(jsonOption())
|
|
26
26
|
.action(async (selectorOrIndex, options) => {
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
import { DomElementResolver } from './DomElementResolver.js';
|
|
14
14
|
import { runElementCommand } from './helpers/runElementCommand.js';
|
|
15
15
|
import { runCommand } from '../shared/CommandRunner.js';
|
|
16
|
-
import { jsonOption } from '../shared/commonOptions.js';
|
|
16
|
+
import { jsonOption, SELECTOR_OR_INDEX_ARGUMENT } from '../shared/commonOptions.js';
|
|
17
17
|
import { eventTypesOption, integerOption } from '../shared/validation.js';
|
|
18
18
|
import { domListeners } from '../../ipc/client.js';
|
|
19
19
|
import { formatListeners } from '../../ui/formatters/listeners.js';
|
|
@@ -26,7 +26,7 @@ export function registerListenersCommand(dom) {
|
|
|
26
26
|
dom
|
|
27
27
|
.command('listeners')
|
|
28
28
|
.description('List event listeners that run for an element (incl. delegated ones on ancestors)')
|
|
29
|
-
.argument('<selectorOrIndex>',
|
|
29
|
+
.argument('<selectorOrIndex>', SELECTOR_OR_INDEX_ARGUMENT)
|
|
30
30
|
.option('--index <n>', 'Element index if selector matches multiple (0-based)', integerOption(0))
|
|
31
31
|
.option('--type <types>', 'Only these event types (comma-separated, e.g. click,keydown; repeatable)', eventTypesOption)
|
|
32
32
|
.option('--all', 'List every listener of framework roots (React) instead of one line per node')
|
|
@@ -17,7 +17,9 @@ export interface SemanticNodeWithContext {
|
|
|
17
17
|
/**
|
|
18
18
|
* Format a semantic node together with DOM context for human-readable output.
|
|
19
19
|
*
|
|
20
|
-
* The role line
|
|
20
|
+
* The role line names the element's key attributes (an image's file name, a
|
|
21
|
+
* link's href, a field's type and name), like `dom query` does, and is
|
|
22
|
+
* followed by up to 500 characters of the element's text
|
|
21
23
|
* (all of it with `dom get --full`) when it is longer than the one-line
|
|
22
24
|
* preview, or, for an element without text or name, what it holds.
|
|
23
25
|
*
|
|
@@ -34,4 +36,15 @@ export declare function formatSemanticNodeWithContext(data: SemanticNodeWithCont
|
|
|
34
36
|
* @param nodeId - CDP nodeId for synthesis
|
|
35
37
|
*/
|
|
36
38
|
export declare function resolveNodeWithFallback(a11yNode: A11yNode | null, domContext: DomContext | null, nodeId: number | undefined): A11yNode | null;
|
|
39
|
+
/**
|
|
40
|
+
* The accessibility node with its value masked when the element holds a
|
|
41
|
+
* secret (`domContext.sensitive`): Chrome reports a password field's value
|
|
42
|
+
* as one bullet per character, and a field switched to text by a "show
|
|
43
|
+
* password" button in clear.
|
|
44
|
+
*
|
|
45
|
+
* @param node - Accessibility node
|
|
46
|
+
* @param domContext - DOM context of the same element
|
|
47
|
+
* @returns The node, with {@link MASKED_VALUE} as its value for a secret
|
|
48
|
+
*/
|
|
49
|
+
export declare function withSecretMasked(node: A11yNode, domContext: DomContext | null): A11yNode;
|
|
37
50
|
//# sourceMappingURL=semanticUtils.d.ts.map
|
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
* their surrounding DOM context and to fall back gracefully when only one
|
|
6
6
|
* of the two data sources is available.
|
|
7
7
|
*/
|
|
8
|
+
import { MASKED_VALUE } from '../../runtime/dom/elementInfo.js';
|
|
8
9
|
import { synthesizeA11yNode } from '../../telemetry/roleInference.js';
|
|
10
|
+
import { keyAttributeItems } from '../../ui/formatters/keyAttributes.js';
|
|
9
11
|
import { joinLines } from '../../ui/formatting.js';
|
|
10
12
|
import { elementTextLine, emptyElementLine } from '../../ui/messages/commands.js';
|
|
11
13
|
function capitalize(str) {
|
|
@@ -35,6 +37,27 @@ function buildContextText(node, domContext) {
|
|
|
35
37
|
}
|
|
36
38
|
return '';
|
|
37
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* Key attributes of the element (src, href, a field's type and name, ...),
|
|
42
|
+
* leaving out what the role line already shows: values equal to the
|
|
43
|
+
* accessible name (an image's alt, a field's placeholder) and the value
|
|
44
|
+
* when the accessibility node has one.
|
|
45
|
+
*
|
|
46
|
+
* @param node - Accessibility node
|
|
47
|
+
* @param domContext - DOM context with the key attributes
|
|
48
|
+
* @returns ` src="…/logo.png"`-style text, or empty
|
|
49
|
+
*/
|
|
50
|
+
function buildKeyAttributesText(node, domContext) {
|
|
51
|
+
if (!domContext?.attributes)
|
|
52
|
+
return '';
|
|
53
|
+
const shown = Object.entries(domContext.attributes)
|
|
54
|
+
.filter(([, value]) => Boolean(node.name) && value === node.name)
|
|
55
|
+
.map(([name]) => name);
|
|
56
|
+
if (node.value !== undefined && node.value !== '')
|
|
57
|
+
shown.push('value');
|
|
58
|
+
const items = keyAttributeItems(domContext.tag, domContext.attributes, new Set(shown));
|
|
59
|
+
return items.length > 0 ? ` ${items.join(' ')}` : '';
|
|
60
|
+
}
|
|
38
61
|
function buildPropertiesText(node) {
|
|
39
62
|
const props = [];
|
|
40
63
|
if (node.value !== undefined && node.value !== '')
|
|
@@ -59,7 +82,9 @@ function buildPropertiesText(node) {
|
|
|
59
82
|
/**
|
|
60
83
|
* Format a semantic node together with DOM context for human-readable output.
|
|
61
84
|
*
|
|
62
|
-
* The role line
|
|
85
|
+
* The role line names the element's key attributes (an image's file name, a
|
|
86
|
+
* link's href, a field's type and name), like `dom query` does, and is
|
|
87
|
+
* followed by up to 500 characters of the element's text
|
|
63
88
|
* (all of it with `dom get --full`) when it is longer than the one-line
|
|
64
89
|
* preview, or, for an element without text or name, what it holds.
|
|
65
90
|
*
|
|
@@ -70,9 +95,10 @@ export function formatSemanticNodeWithContext(data) {
|
|
|
70
95
|
const { node, domContext } = data;
|
|
71
96
|
const roleText = buildRoleText(node);
|
|
72
97
|
const contextText = buildContextText(node, domContext);
|
|
98
|
+
const keysText = buildKeyAttributesText(node, domContext);
|
|
73
99
|
const propsText = buildPropertiesText(node);
|
|
74
100
|
const inferredText = node.inferred ? ' (inferred from DOM)' : '';
|
|
75
|
-
const line = `${roleText}${contextText}${propsText}${inferredText}`;
|
|
101
|
+
const line = `${roleText}${contextText}${keysText}${propsText}${inferredText}`;
|
|
76
102
|
if (domContext?.text)
|
|
77
103
|
return joinLines(line, elementTextLine(domContext.text));
|
|
78
104
|
if (domContext?.childCount !== undefined && !node.name) {
|
|
@@ -90,9 +116,24 @@ export function formatSemanticNodeWithContext(data) {
|
|
|
90
116
|
*/
|
|
91
117
|
export function resolveNodeWithFallback(a11yNode, domContext, nodeId) {
|
|
92
118
|
if (a11yNode)
|
|
93
|
-
return a11yNode;
|
|
119
|
+
return withSecretMasked(a11yNode, domContext);
|
|
94
120
|
if (domContext && nodeId)
|
|
95
121
|
return synthesizeA11yNode(domContext, nodeId);
|
|
96
122
|
return null;
|
|
97
123
|
}
|
|
124
|
+
/**
|
|
125
|
+
* The accessibility node with its value masked when the element holds a
|
|
126
|
+
* secret (`domContext.sensitive`): Chrome reports a password field's value
|
|
127
|
+
* as one bullet per character, and a field switched to text by a "show
|
|
128
|
+
* password" button in clear.
|
|
129
|
+
*
|
|
130
|
+
* @param node - Accessibility node
|
|
131
|
+
* @param domContext - DOM context of the same element
|
|
132
|
+
* @returns The node, with {@link MASKED_VALUE} as its value for a secret
|
|
133
|
+
*/
|
|
134
|
+
export function withSecretMasked(node, domContext) {
|
|
135
|
+
if (!domContext?.sensitive || !node.value)
|
|
136
|
+
return node;
|
|
137
|
+
return { ...node, value: MASKED_VALUE };
|
|
138
|
+
}
|
|
98
139
|
//# sourceMappingURL=semanticUtils.js.map
|
|
@@ -13,7 +13,7 @@ import { applyFilters, getFilterHelpText, validateFilterString } from '../../tel
|
|
|
13
13
|
import { resolvePreset, FILTER_PRESETS } from '../../telemetry/filterPresets.js';
|
|
14
14
|
import { filterByResourceType } from '../../telemetry/filters.js';
|
|
15
15
|
import { buildSuccessResponse } from '../../ui/OutputBuilder.js';
|
|
16
|
-
import { formatNetworkFollowRows, formatNetworkList, } from '../../ui/formatters/networkList.js';
|
|
16
|
+
import { formatNetworkFollowRows, formatNetworkList, pageStartOf, } from '../../ui/formatters/networkList.js';
|
|
17
17
|
import { followingNetworkMessage, stoppedFollowingNetworkMessage, } from '../../ui/messages/networkMessages.js';
|
|
18
18
|
import { EXIT_CODES } from '../../utils/exitCodes.js';
|
|
19
19
|
import { validateFilterOption } from './shared.js';
|
|
@@ -110,6 +110,7 @@ function buildFormatOptions(options, result, lastLimit) {
|
|
|
110
110
|
last: lastLimit,
|
|
111
111
|
totalCount: result.totalCount,
|
|
112
112
|
filteredCount: result.filteredCount,
|
|
113
|
+
...(result.pageStart && { pageStart: result.pageStart }),
|
|
113
114
|
};
|
|
114
115
|
}
|
|
115
116
|
/**
|
|
@@ -148,9 +149,11 @@ async function runFollowMode(options, resourceTypes, lastN) {
|
|
|
148
149
|
}
|
|
149
150
|
}
|
|
150
151
|
else {
|
|
152
|
+
const pageStart = pageStartOf(result.data);
|
|
151
153
|
const text = formatNetworkFollowRows(fresh, {
|
|
152
154
|
header: !started,
|
|
153
155
|
verbose: options.verbose ?? false,
|
|
156
|
+
...(pageStart && { pageStart }),
|
|
154
157
|
});
|
|
155
158
|
if (text)
|
|
156
159
|
console.log(text);
|
|
@@ -164,6 +167,12 @@ async function runFollowMode(options, resourceTypes, lastN) {
|
|
|
164
167
|
intervalMs: FOLLOW_INTERVAL,
|
|
165
168
|
});
|
|
166
169
|
}
|
|
170
|
+
/** What the less obvious columns of the list mean */
|
|
171
|
+
const COLUMNS_HELP = `Columns:
|
|
172
|
+
START When the request started, from the start of the current page (its document
|
|
173
|
+
request): +1.2s. Requests of earlier pages are negative. --json has the
|
|
174
|
+
absolute time (timestamp, epoch ms) and data.pageStart.
|
|
175
|
+
TIME How long it took (to its last byte or failure); - while pending`;
|
|
167
176
|
function formatPresetHelp() {
|
|
168
177
|
return Object.entries(FILTER_PRESETS)
|
|
169
178
|
.map(([name, preset]) => ` ${name.padEnd(12)} ${preset.description}`)
|
|
@@ -180,7 +189,7 @@ export function registerListCommand(networkCmd) {
|
|
|
180
189
|
.addOption(networkLastOption)
|
|
181
190
|
.addOption(new Option('-f, --follow', 'Stream network requests in real-time').default(false))
|
|
182
191
|
.addOption(new Option('-v, --verbose', 'Show full URLs and additional details').default(false))
|
|
183
|
-
.addHelpText('after', `\n${getFilterHelpText()}\n\nPresets:\n${formatPresetHelp()}`)
|
|
192
|
+
.addHelpText('after', `\n${COLUMNS_HELP}\n\n${getFilterHelpText()}\n\nPresets:\n${formatPresetHelp()}`)
|
|
184
193
|
.action(async (options) => {
|
|
185
194
|
let resourceTypes;
|
|
186
195
|
let lastN;
|
|
@@ -211,12 +220,14 @@ export function registerListCommand(networkCmd) {
|
|
|
211
220
|
return createErrorResult(result.error, result.exitCode, result.suggestion);
|
|
212
221
|
}
|
|
213
222
|
const filtered = filterRequests(result.data, options, resourceTypes);
|
|
223
|
+
const pageStart = pageStartOf(result.data);
|
|
214
224
|
return {
|
|
215
225
|
success: true,
|
|
216
226
|
data: {
|
|
217
227
|
requests: lastN === 0 ? filtered : filtered.slice(-lastN),
|
|
218
228
|
totalCount: result.data.length,
|
|
219
229
|
filteredCount: filtered.length,
|
|
230
|
+
...(pageStart && { pageStart }),
|
|
220
231
|
},
|
|
221
232
|
};
|
|
222
233
|
}, options, (data) => formatNetworkList(data.requests, buildFormatOptions(options, data, lastN)));
|