browser-debugger-cli 0.11.0 → 0.12.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 +142 -79
- package/dist/commands/css.d.ts +13 -0
- package/dist/commands/css.js +53 -0
- package/dist/commands/dom/audit.d.ts +14 -0
- package/dist/commands/dom/audit.js +87 -0
- package/dist/commands/dom/formInteraction.js +36 -6
- package/dist/commands/dom/helpers/keyAttributes.d.ts +3 -2
- package/dist/commands/dom/helpers/keyAttributes.js +6 -4
- package/dist/commands/dom/helpers/screenshot.d.ts +1 -0
- package/dist/commands/dom/helpers/screenshot.js +158 -38
- package/dist/commands/dom/index.js +4 -1
- package/dist/commands/dom/screenshot.js +10 -6
- package/dist/commands/dom/wait.js +5 -3
- package/dist/commands/helpJson.js +1 -1
- package/dist/commands/optionBehaviors.js +21 -6
- package/dist/commands/page.js +7 -4
- package/dist/commands/peek.d.ts +7 -0
- package/dist/commands/peek.js +65 -23
- package/dist/commands/shared/optionTypes.d.ts +5 -1
- package/dist/commands/start.d.ts +13 -0
- package/dist/commands/start.js +19 -2
- package/dist/commands/tail.d.ts +7 -1
- package/dist/commands/tail.js +13 -62
- package/dist/commands.js +2 -0
- package/dist/daemon/session/commandRegistry.js +7 -1
- package/dist/daemon/session/plugins.js +4 -52
- package/dist/daemon.js +7986 -6848
- package/dist/errors/messages.d.ts +34 -4
- package/dist/errors/messages.js +68 -5
- package/dist/index.js +610 -186
- package/dist/ipc/client.d.ts +4 -0
- package/dist/ipc/client.js +8 -0
- package/dist/ipc/protocol/auditTypes.d.ts +129 -0
- package/dist/ipc/protocol/auditTypes.js +6 -0
- package/dist/ipc/protocol/commands.d.ts +23 -0
- package/dist/ipc/protocol/commands.js +2 -0
- package/dist/ipc/protocol/domTypes.d.ts +4 -0
- package/dist/ipc/protocol/inspectTypes.d.ts +71 -8
- package/dist/runtime/css/search.d.ts +39 -0
- package/dist/runtime/css/search.js +122 -0
- package/dist/runtime/dom/actionEffects.d.ts +4 -1
- package/dist/runtime/dom/actionEffects.js +8 -4
- package/dist/runtime/dom/audit.d.ts +19 -0
- package/dist/runtime/dom/audit.js +36 -0
- package/dist/runtime/dom/auditModel.d.ts +45 -0
- package/dist/runtime/dom/auditModel.js +215 -0
- package/dist/runtime/dom/auditScripts.d.ts +107 -0
- package/dist/runtime/dom/auditScripts.js +112 -0
- package/dist/runtime/dom/elementGeometry.d.ts +8 -2
- package/dist/runtime/dom/elementGeometry.js +24 -8
- package/dist/runtime/dom/elementInfo.d.ts +3 -2
- package/dist/runtime/dom/elementInfo.js +8 -2
- package/dist/runtime/dom/formFillHelpers/fill.js +2 -2
- package/dist/runtime/dom/inspect.d.ts +7 -0
- package/dist/runtime/dom/inspect.js +88 -23
- package/dist/runtime/dom/inspectAllStyles.d.ts +16 -4
- package/dist/runtime/dom/inspectAllStyles.js +89 -7
- package/dist/runtime/dom/inspectCascade.d.ts +19 -2
- package/dist/runtime/dom/inspectCascade.js +214 -44
- package/dist/runtime/dom/inspectCascadeModel.d.ts +8 -0
- package/dist/runtime/dom/inspectCascadeModel.js +108 -34
- package/dist/runtime/dom/inspectHints.d.ts +26 -3
- package/dist/runtime/dom/inspectHints.js +125 -9
- package/dist/runtime/dom/inspectModel.d.ts +3 -0
- package/dist/runtime/dom/inspectModel.js +30 -7
- package/dist/runtime/dom/inspectPaintModel.d.ts +48 -22
- package/dist/runtime/dom/inspectPaintModel.js +180 -68
- package/dist/runtime/dom/inspectRules.d.ts +19 -0
- package/dist/runtime/dom/inspectRules.js +21 -5
- package/dist/runtime/dom/inspectScripts.d.ts +85 -12
- package/dist/runtime/dom/inspectScripts.js +314 -28
- package/dist/runtime/dom/inspectTree.js +10 -2
- package/dist/runtime/dom/inspectWhyModel.d.ts +2 -1
- package/dist/runtime/dom/inspectWhyModel.js +52 -10
- package/dist/runtime/dom/layout.js +31 -9
- package/dist/runtime/dom/reactEventHelpers.d.ts +7 -0
- package/dist/runtime/dom/reactEventHelpers.js +27 -9
- package/dist/runtime/page/emulation.d.ts +13 -4
- package/dist/runtime/page/emulation.js +69 -4
- package/dist/runtime/page/userAgent.d.ts +17 -0
- package/dist/runtime/page/userAgent.js +57 -0
- package/dist/types.d.ts +4 -0
- package/dist/ui/formatters/audit.d.ts +19 -0
- package/dist/ui/formatters/audit.js +106 -0
- package/dist/ui/formatters/dom.d.ts +1 -1
- package/dist/ui/formatters/dom.js +6 -3
- package/dist/ui/formatters/inspect.js +42 -15
- package/dist/ui/formatters/status.js +1 -1
- package/dist/ui/messages/commands.d.ts +44 -7
- package/dist/ui/messages/commands.js +83 -11
- package/dist/ui/messages/preview.d.ts +6 -0
- package/dist/ui/messages/preview.js +9 -1
- package/dist/utils/cssValues.js +36 -4
- package/dist/utils/decisionTrees.js +0 -5
- package/dist/utils/suggestions.d.ts +4 -2
- package/dist/utils/suggestions.js +7 -5
- package/dist/utils/taskMappings.js +1 -1
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -567,13 +567,13 @@ var require_common = __commonJS({
|
|
|
567
567
|
}
|
|
568
568
|
}
|
|
569
569
|
}
|
|
570
|
-
function matchesTemplate(
|
|
570
|
+
function matchesTemplate(search2, template) {
|
|
571
571
|
let searchIndex = 0;
|
|
572
572
|
let templateIndex = 0;
|
|
573
573
|
let starIndex = -1;
|
|
574
574
|
let matchIndex2 = 0;
|
|
575
|
-
while (searchIndex <
|
|
576
|
-
if (templateIndex < template.length && (template[templateIndex] ===
|
|
575
|
+
while (searchIndex < search2.length) {
|
|
576
|
+
if (templateIndex < template.length && (template[templateIndex] === search2[searchIndex] || template[templateIndex] === "*")) {
|
|
577
577
|
if (template[templateIndex] === "*") {
|
|
578
578
|
starIndex = templateIndex;
|
|
579
579
|
matchIndex2 = searchIndex;
|
|
@@ -2412,6 +2412,9 @@ function pageNavigationText(navigation) {
|
|
|
2412
2412
|
const status = navigation.status === void 0 ? "" : ` (${navigation.status})`;
|
|
2413
2413
|
return `navigated to ${navigation.url}${status}`;
|
|
2414
2414
|
}
|
|
2415
|
+
function moreMessagesText(count) {
|
|
2416
|
+
return `(+${count} more)`;
|
|
2417
|
+
}
|
|
2415
2418
|
function newMessageText(message) {
|
|
2416
2419
|
return `"${message.text}" (${message.element})`;
|
|
2417
2420
|
}
|
|
@@ -2508,8 +2511,11 @@ function coverText(cover, transparent) {
|
|
|
2508
2511
|
function inspectCascadeNote(reason) {
|
|
2509
2512
|
return reason === "timeout" ? "CSS rules not read: the page's stylesheets took too long (hints wait 1 s; --rules and --why 5 s)" : "CSS rules not read: Chrome could not report the rules matching this element";
|
|
2510
2513
|
}
|
|
2511
|
-
function
|
|
2512
|
-
return
|
|
2514
|
+
function inspectPseudoOfNote(pseudo) {
|
|
2515
|
+
return `Inspected the element of ${pseudo}: pseudo-elements cannot be selected; ${pseudo} is on the pseudo line (content, size, position, inset, colors)`;
|
|
2516
|
+
}
|
|
2517
|
+
function inspectDarkThemeBadge(emulated = false) {
|
|
2518
|
+
return emulated ? "[dark theme, emulated; bdg page emulate --color-scheme light for light]" : "[dark theme from system; --color-scheme light for light]";
|
|
2513
2519
|
}
|
|
2514
2520
|
function inspectVisibilityBadges(visibility) {
|
|
2515
2521
|
const reason = (text) => text ? `: ${text}` : "";
|
|
@@ -2636,8 +2642,16 @@ function emptyElementLine(children, count) {
|
|
|
2636
2642
|
const more = count > children.length ? `, \u2026 ${count - children.length} more` : "";
|
|
2637
2643
|
return `No text; holds ${pluralize(count, "element")}: ${children.join(", ")}${more} (see its HTML with --raw)`;
|
|
2638
2644
|
}
|
|
2639
|
-
function
|
|
2640
|
-
return `
|
|
2645
|
+
function screenshotScaledNote(originalWidth, originalHeight, width, height) {
|
|
2646
|
+
return `scaled from ${originalWidth}\xD7${originalHeight} to ${width}\xD7${height}; --no-resize for full size`;
|
|
2647
|
+
}
|
|
2648
|
+
function screenshotGrownNote(box, captured, padding = 0) {
|
|
2649
|
+
const painted = { width: captured.width - 2 * padding, height: captured.height - 2 * padding };
|
|
2650
|
+
const grew = painted.width > box.width + 0.5 || painted.height > box.height + 0.5;
|
|
2651
|
+
const ink = `grown from ${box.width}\xD7${box.height} to ${painted.width}\xD7${painted.height} to include what it paints outside its box (overflowing content, shadows, outline)`;
|
|
2652
|
+
const pad = `${padding}px of page around it (--padding)`;
|
|
2653
|
+
if (!padding) return ink;
|
|
2654
|
+
return grew ? `${ink}, plus ${pad}` : `with ${pad}: ${captured.width}\xD7${captured.height}`;
|
|
2641
2655
|
}
|
|
2642
2656
|
function queryNextSteps(index) {
|
|
2643
2657
|
return `Next: bdg dom get ${index} (text), bdg dom get ${index} --raw (HTML), bdg dom layout ${index} (position)`;
|
|
@@ -2689,14 +2703,15 @@ function startCommandHelpMessage() {
|
|
|
2689
2703
|
function pageEmulateNothingError() {
|
|
2690
2704
|
return {
|
|
2691
2705
|
message: "Nothing to emulate",
|
|
2692
|
-
suggestion: "Give --viewport <WxH>, --color-scheme light|dark, or --reset, e.g. bdg page emulate --viewport 900x700"
|
|
2706
|
+
suggestion: "Give --viewport <WxH>, --mobile, --color-scheme light|dark, or --reset, e.g. bdg page emulate --viewport 900x700"
|
|
2693
2707
|
};
|
|
2694
2708
|
}
|
|
2695
2709
|
function pageEmulationLines(result) {
|
|
2696
2710
|
const size = (v) => `${v.width}x${v.height}`;
|
|
2697
2711
|
const { emulated } = result;
|
|
2712
|
+
const phone = emulated.viewport?.mobile ? " (phone: mobile layout, touch, mobile user agent)" : "";
|
|
2698
2713
|
return [
|
|
2699
|
-
["Viewport", emulated.viewport ? size(emulated.viewport) : "the browser window"],
|
|
2714
|
+
["Viewport", emulated.viewport ? `${size(emulated.viewport)}${phone}` : "the browser window"],
|
|
2700
2715
|
...result.viewport ? [["Layout", `${size(result.viewport)} (without scrollbars)`]] : [],
|
|
2701
2716
|
[
|
|
2702
2717
|
"Scheme",
|
|
@@ -2710,7 +2725,7 @@ function inspectAnimatingBadge(animating) {
|
|
|
2710
2725
|
function inspectMidTransitionNote() {
|
|
2711
2726
|
return "(mid-transition: inspect again for the final value)";
|
|
2712
2727
|
}
|
|
2713
|
-
var REQUIRED_FIELD_EMPTY_REASON, NO_VISIBLE_EFFECT, LAYOUT_REASONS, SCROLL_LOCKED_PREFIX, VIEWPORT_POSITION_HINTS, INSPECT_OUTPUT_LEGEND, COLLAPSED_LISTENERS_HEADING, NO_LISTENERS_HINT, NOTE_TYPES_SHOWN, POINTER_ACTION_DONE, POINTER_ACTION_NOUN, PAGE_ACTION_DONE, PAGE_INFO_DESCRIPTION, PAGE_EMULATE_DESCRIPTION, PAGE_ACTION_DESCRIPTIONS, CLICK_RESULT_WAIT_HELP, WAIT_HELP_EXAMPLES, FRAME_URL_MAX_LENGTH;
|
|
2728
|
+
var REQUIRED_FIELD_EMPTY_REASON, NO_VISIBLE_EFFECT, HOVER_OFF_DONE, HOVER_USAGE, LAYOUT_REASONS, SCROLL_LOCKED_PREFIX, VIEWPORT_POSITION_HINTS, INSPECT_OUTPUT_LEGEND, COLLAPSED_LISTENERS_HEADING, NO_LISTENERS_HINT, NOTE_TYPES_SHOWN, POINTER_ACTION_DONE, POINTER_ACTION_NOUN, PAGE_ACTION_DONE, PAGE_INFO_DESCRIPTION, PAGE_EMULATE_DESCRIPTION, PAGE_ACTION_DESCRIPTIONS, CLICK_RESULT_WAIT_HELP, WAIT_HELP_EXAMPLES, FRAME_URL_MAX_LENGTH, AUDIT_HELP_EXAMPLES, CSS_SEARCH_HELP_EXAMPLES;
|
|
2714
2729
|
var init_commands = __esm({
|
|
2715
2730
|
"src/ui/messages/commands.ts"() {
|
|
2716
2731
|
"use strict";
|
|
@@ -2720,6 +2735,8 @@ var init_commands = __esm({
|
|
|
2720
2735
|
init_strings();
|
|
2721
2736
|
REQUIRED_FIELD_EMPTY_REASON = "Required field is empty";
|
|
2722
2737
|
NO_VISIBLE_EFFECT = "no visible effect observed: no DOM change, requests or navigation within 300 ms";
|
|
2738
|
+
HOVER_OFF_DONE = "\u2713 Mouse moved off the page (hover styles and menus that close on mouseleave are gone)";
|
|
2739
|
+
HOVER_USAGE = "bdg dom hover <selector|index>, or bdg dom hover --off to move the mouse away";
|
|
2723
2740
|
LAYOUT_REASONS = {
|
|
2724
2741
|
/** Hidden: an `<option>` of a closed `<select>` has no box */
|
|
2725
2742
|
option: "not rendered (an <option> is shown by its <select>)",
|
|
@@ -2748,8 +2765,14 @@ Output notation:
|
|
|
2748
2765
|
in-parent distances to the parent's content edges (l t r b); sib: gaps to the sibling on each side
|
|
2749
2766
|
scroll WxH the content (pseudo-elements too) is larger than the box
|
|
2750
2767
|
16/24 font size / line height; 'webfont loaded' = drawn with a downloaded font;
|
|
2751
|
-
(rendered "X") = drawn with another font than declared (a fallback)
|
|
2768
|
+
(rendered "X") = drawn with another font than declared (a fallback);
|
|
2769
|
+
(resolves to "X") = the font a generic family (sans-serif, system-ui) became
|
|
2752
2770
|
contrast 4.47 WCAG ratio, rounded down, against the background behind the text
|
|
2771
|
+
text in X the text is drawn by descendant X (the one with most of it): its font, color, contrast
|
|
2772
|
+
truncated the text is cut off (overflow clip, ellipsis or line clamp); a "\u2026" in the header is
|
|
2773
|
+
only bdg shortening the text
|
|
2774
|
+
.a.b(+3) the first two classes and how many more the element has
|
|
2775
|
+
sizing content-box padding and border add to the CSS size (shown only then; border-box is not)
|
|
2753
2776
|
(+N not rendered) children with display: none (or not in the layout)
|
|
2754
2777
|
hints declarations on this element that have no effect, why, the fix and where they are
|
|
2755
2778
|
('none': checked, nothing found)
|
|
@@ -2803,6 +2826,21 @@ Sessions follow the system color scheme; start with --color-scheme light|dark to
|
|
|
2803
2826
|
" bdg dom wait --load # the page finished loading"
|
|
2804
2827
|
);
|
|
2805
2828
|
FRAME_URL_MAX_LENGTH = 100;
|
|
2829
|
+
AUDIT_HELP_EXAMPLES = `
|
|
2830
|
+
Examples:
|
|
2831
|
+
bdg dom audit All checks
|
|
2832
|
+
bdg dom audit contrast --level AAA Text below WCAG AAA, weakest first
|
|
2833
|
+
bdg dom audit overflow What scrolls sideways, cut-off text, scaled images
|
|
2834
|
+
bdg dom audit layers animations Fixed/sticky elements and running animations
|
|
2835
|
+
|
|
2836
|
+
Follow up on a finding with bdg dom inspect <element> (e.g. --why color).`;
|
|
2837
|
+
CSS_SEARCH_HELP_EXAMPLES = `
|
|
2838
|
+
Examples:
|
|
2839
|
+
bdg css search -- --brand Where a custom property is set and used (-- before a text
|
|
2840
|
+
that starts with -; options go before it:
|
|
2841
|
+
bdg css search --limit 50 -- --brand)
|
|
2842
|
+
bdg css search "oklch(" Rules that use oklch colors
|
|
2843
|
+
bdg css search ".btn-primary" Rules of a class, in every stylesheet`;
|
|
2806
2844
|
}
|
|
2807
2845
|
});
|
|
2808
2846
|
|
|
@@ -3248,6 +3286,7 @@ function noNodesFoundError(selector, context = {}) {
|
|
|
3248
3286
|
joinLines(
|
|
3249
3287
|
context.similar,
|
|
3250
3288
|
context.shadowHost && shadowBoundaryLine(context.shadowHost),
|
|
3289
|
+
shadowPseudoLine(selector),
|
|
3251
3290
|
`${hidden}Verify the CSS selector is correct.`,
|
|
3252
3291
|
note ? note : void 0
|
|
3253
3292
|
),
|
|
@@ -3256,6 +3295,13 @@ function noNodesFoundError(selector, context = {}) {
|
|
|
3256
3295
|
)
|
|
3257
3296
|
};
|
|
3258
3297
|
}
|
|
3298
|
+
function shadowPseudoLine(selector) {
|
|
3299
|
+
const part = /::part\(\s*([^)\s]+)\s*\)/.exec(selector)?.[1];
|
|
3300
|
+
if (part) {
|
|
3301
|
+
return `::part() styles a shadow part and selects nothing; bdg searches open shadow roots, so select the part itself: [part~="${part}"]`;
|
|
3302
|
+
}
|
|
3303
|
+
return /::slotted\(/.test(selector) ? "::slotted() selects nothing; select the slotted element itself (it is in the light DOM)" : void 0;
|
|
3304
|
+
}
|
|
3259
3305
|
function shellQuote(value) {
|
|
3260
3306
|
return `'${value.split("'").join(`'\\''`)}'`;
|
|
3261
3307
|
}
|
|
@@ -3285,6 +3331,12 @@ function elementZeroDimensionsError() {
|
|
|
3285
3331
|
suggestion: "It may be collapsed or empty; capture a visible ancestor instead"
|
|
3286
3332
|
};
|
|
3287
3333
|
}
|
|
3334
|
+
function hoverOffWithTargetError() {
|
|
3335
|
+
return {
|
|
3336
|
+
message: "--off moves the mouse off the page; it takes no element",
|
|
3337
|
+
suggestion: "Use bdg dom hover --off alone, or bdg dom hover <selector> to hover an element"
|
|
3338
|
+
};
|
|
3339
|
+
}
|
|
3288
3340
|
function missingArgumentError(usage) {
|
|
3289
3341
|
return {
|
|
3290
3342
|
message: "Missing required argument or flag",
|
|
@@ -3370,6 +3422,15 @@ function skillWriteFailedError(path6, reason) {
|
|
|
3370
3422
|
suggestion: "Check the permissions of that directory, or install for the other agent only (--claude or --agents)"
|
|
3371
3423
|
};
|
|
3372
3424
|
}
|
|
3425
|
+
function intervalWithoutFollowError() {
|
|
3426
|
+
return {
|
|
3427
|
+
message: "--interval sets how often --follow refreshes",
|
|
3428
|
+
suggestion: "Add --follow, e.g. bdg peek --follow --interval 500"
|
|
3429
|
+
};
|
|
3430
|
+
}
|
|
3431
|
+
function unknownAuditCheckMessage(value, similar, checks) {
|
|
3432
|
+
return `Unknown check "${value}"${similar.length > 0 ? `; did you mean ${similar[0]}?` : ""} (checks: ${checks.join(", ")})`;
|
|
3433
|
+
}
|
|
3373
3434
|
var LAUNCHED_CHROME_DESCRIPTION, A11Y_NAME_QUERY_EXAMPLE, SCOPED_FILTER_EXAMPLES, PLAYWRIGHT_ONLY_SYNTAX, MALFORMED_SELECTOR_DETAILS, SESSION_DIR_SUGGESTION, SCROLL_PROBLEMS, VIA_LABEL_SUFFIX, NAME_QUERY_PLACEHOLDER, LABEL_WITHOUT_CONTROL;
|
|
3374
3435
|
var init_messages = __esm({
|
|
3375
3436
|
"src/errors/messages.ts"() {
|
|
@@ -3499,11 +3560,14 @@ function findSimilarNames(name, candidates) {
|
|
|
3499
3560
|
const maxDistance2 = Math.max(2, Math.floor(name.length / 5));
|
|
3500
3561
|
const minAffix = Math.max(4, Math.ceil(name.length / 3));
|
|
3501
3562
|
const typos = findMatches(name, others, maxDistance2, false).map((match) => match.value);
|
|
3502
|
-
const byAffix = (length) => others.map((candidate) => ({ candidate, length: length(candidate) })).filter((entry) => entry.length >=
|
|
3563
|
+
const byAffix = (length, min = minAffix) => others.map((candidate) => ({ candidate, length: length(candidate) })).filter((entry) => entry.length >= min).sort((a, b) => b.length - a.length).map((entry) => entry.candidate);
|
|
3503
3564
|
const ranked = [
|
|
3504
3565
|
...typos,
|
|
3505
3566
|
...byAffix((candidate) => commonSuffixLength(name, candidate)),
|
|
3506
|
-
...byAffix(
|
|
3567
|
+
...byAffix(
|
|
3568
|
+
(candidate) => commonPrefixLength(name, candidate),
|
|
3569
|
+
Math.max(minAffix, Math.ceil(name.length / 2))
|
|
3570
|
+
)
|
|
3507
3571
|
];
|
|
3508
3572
|
return [...new Set(ranked)].slice(0, MAX_SIMILAR_NAMES);
|
|
3509
3573
|
}
|
|
@@ -3974,6 +4038,12 @@ async function domListeners(params) {
|
|
|
3974
4038
|
async function domLayout(params) {
|
|
3975
4039
|
return sendCommand("dom_layout", params);
|
|
3976
4040
|
}
|
|
4041
|
+
async function domAudit(params) {
|
|
4042
|
+
return sendCommand("dom_audit", params);
|
|
4043
|
+
}
|
|
4044
|
+
async function cssSearch(params) {
|
|
4045
|
+
return sendCommand("css_search", params);
|
|
4046
|
+
}
|
|
3977
4047
|
async function domInspect(params) {
|
|
3978
4048
|
return sendCommand("dom_inspect", params);
|
|
3979
4049
|
}
|
|
@@ -4162,7 +4232,12 @@ var init_elementInfo = __esm({
|
|
|
4162
4232
|
doc = frame.ownerDocument;
|
|
4163
4233
|
}
|
|
4164
4234
|
const root = el.getRootNode();
|
|
4165
|
-
if (root.host)
|
|
4235
|
+
if (root.host) {
|
|
4236
|
+
const host = root.host;
|
|
4237
|
+
const label = describe(host) + (!host.id && host.classList.length ? '.' + host.classList[0] : '');
|
|
4238
|
+
const text = (host.textContent || '').replace(/\\s+/g, ' ').trim().slice(0, 30);
|
|
4239
|
+
parts.push('shadow root of <' + label + (text ? ' "' + text + '"' : '') + '>');
|
|
4240
|
+
}
|
|
4166
4241
|
return parts.join(' > ');
|
|
4167
4242
|
}`;
|
|
4168
4243
|
}
|
|
@@ -4171,8 +4246,9 @@ var init_elementInfo = __esm({
|
|
|
4171
4246
|
// src/commands/dom/helpers/keyAttributes.ts
|
|
4172
4247
|
function keyAttributes(tag, attributes, state = {}) {
|
|
4173
4248
|
const names = ATTRIBUTES_BY_TAG[tag];
|
|
4174
|
-
|
|
4175
|
-
|
|
4249
|
+
const part = attributes["part"];
|
|
4250
|
+
if (!names) return part ? { part } : void 0;
|
|
4251
|
+
const result = part ? { part } : {};
|
|
4176
4252
|
const type = state.type ?? attributes["type"];
|
|
4177
4253
|
if (type) result["type"] = type;
|
|
4178
4254
|
for (const name of names) {
|
|
@@ -4239,8 +4315,9 @@ function axisScroll(start, size, viewSize, range, aim) {
|
|
|
4239
4315
|
const scroll = Math.round(Math.min(range.forward, Math.max(-range.back, target))) || 0;
|
|
4240
4316
|
return visibleLength(start, size, viewSize, scroll) >= fully ? scroll : null;
|
|
4241
4317
|
}
|
|
4242
|
-
function classifyViewportPosition(
|
|
4243
|
-
if (
|
|
4318
|
+
function classifyViewportPosition(measured, viewport) {
|
|
4319
|
+
if (measured.hidden) return { inViewport: "hidden", hiddenReason: measured.hidden };
|
|
4320
|
+
const geometry = measured.content ? { ...measured, rect: measured.content } : measured;
|
|
4244
4321
|
const rect = edgesOf(geometry.rect);
|
|
4245
4322
|
const screen = edgesOf({ x: 0, y: 0, ...viewport });
|
|
4246
4323
|
const view = geometry.clip ? overlap(screen, edgesOf(geometry.clip)) : screen;
|
|
@@ -4485,7 +4562,20 @@ var init_elementGeometry = __esm({
|
|
|
4485
4562
|
}
|
|
4486
4563
|
return 'not rendered';
|
|
4487
4564
|
};
|
|
4488
|
-
const
|
|
4565
|
+
const shownChildren = (node) => {
|
|
4566
|
+
const s = styleOf(node);
|
|
4567
|
+
if (s.overflowX !== 'visible' || s.overflowY !== 'visible') return null;
|
|
4568
|
+
let union = null;
|
|
4569
|
+
for (const c of Array.from(node.children).slice(0, 50)) {
|
|
4570
|
+
const r = c.getBoundingClientRect();
|
|
4571
|
+
if (r.width <= 0 || r.height <= 0 || styleOf(c).visibility !== 'visible') continue;
|
|
4572
|
+
union = union
|
|
4573
|
+
? { left: Math.min(union.left, r.left), top: Math.min(union.top, r.top), right: Math.max(union.right, r.right), bottom: Math.max(union.bottom, r.bottom) }
|
|
4574
|
+
: { left: r.left, top: r.top, right: r.right, bottom: r.bottom };
|
|
4575
|
+
}
|
|
4576
|
+
return union;
|
|
4577
|
+
};
|
|
4578
|
+
const hiddenReason = (style, box, content) => {
|
|
4489
4579
|
if (style.display === 'none') return 'display: none';
|
|
4490
4580
|
if (style.display === 'contents') return 'display: contents (no box of its own)';
|
|
4491
4581
|
if (el.getClientRects().length === 0) {
|
|
@@ -4494,7 +4584,7 @@ var init_elementGeometry = __esm({
|
|
|
4494
4584
|
const skipped = skippedReason(el);
|
|
4495
4585
|
if (skipped) return skipped;
|
|
4496
4586
|
if (style.visibility !== 'visible') return 'visibility: ' + style.visibility;
|
|
4497
|
-
if (box.width === 0 || box.height === 0) return 'zero size';
|
|
4587
|
+
if ((box.width === 0 || box.height === 0) && !content) return 'zero size';
|
|
4498
4588
|
return null;
|
|
4499
4589
|
};
|
|
4500
4590
|
const isSticky = () => {
|
|
@@ -4517,7 +4607,8 @@ var init_elementGeometry = __esm({
|
|
|
4517
4607
|
let clipper = own.clipper;
|
|
4518
4608
|
let fixed = own.fixed;
|
|
4519
4609
|
let fixedBy = own.fixedBy;
|
|
4520
|
-
let
|
|
4610
|
+
let content = box.width === 0 || box.height === 0 ? shownChildren(el) : null;
|
|
4611
|
+
let hidden = hiddenReason(styleOf(el), box, content) || own.collapsed;
|
|
4521
4612
|
let x = 0;
|
|
4522
4613
|
let y = 0;
|
|
4523
4614
|
for (let view = el.ownerDocument.defaultView; view && view.frameElement; view = view.parent) {
|
|
@@ -4529,6 +4620,7 @@ var init_elementGeometry = __esm({
|
|
|
4529
4620
|
overlay = { right: !!unframed && unframed.right <= clip.right && overlay.right, bottom: !!unframed && unframed.bottom <= clip.bottom && overlay.bottom };
|
|
4530
4621
|
const offset = frameOffset(frame);
|
|
4531
4622
|
rect = shift(rect, offset.x, offset.y);
|
|
4623
|
+
content = shift(content, offset.x, offset.y);
|
|
4532
4624
|
const outer = ancestorClip(frame, rect, describe);
|
|
4533
4625
|
const inner = shift(clip, offset.x, offset.y);
|
|
4534
4626
|
clip = intersect(inner, outer.clip);
|
|
@@ -4547,7 +4639,7 @@ var init_elementGeometry = __esm({
|
|
|
4547
4639
|
const invisible = hidden ? null : invisibleReason(el, describe);
|
|
4548
4640
|
const lock = scrollLock(top, describe);
|
|
4549
4641
|
if (lock && fixedBy === top.document.body) fixed = false;
|
|
4550
|
-
return { rect: toBox(rect), clip: toBox(clip), clipOverlay: overlay, clipper: clipper, hidden: hidden, invisible: invisible, inert: isInert(), fixed: fixed, sticky: !fixed && isSticky(), pageScroll: scrollRange(top), scrollLock: lock, offset: { x: x, y: y } };
|
|
4642
|
+
return { rect: toBox(rect), content: toBox(content), clip: toBox(clip), clipOverlay: overlay, clipper: clipper, hidden: hidden, invisible: invisible, inert: isInert(), fixed: fixed, sticky: !fixed && isSticky(), pageScroll: scrollRange(top), scrollLock: lock, offset: { x: x, y: y } };
|
|
4551
4643
|
}`;
|
|
4552
4644
|
IN_VIEW_SLACK = 1;
|
|
4553
4645
|
}
|
|
@@ -5550,18 +5642,34 @@ var init_errors5 = __esm({
|
|
|
5550
5642
|
}
|
|
5551
5643
|
});
|
|
5552
5644
|
|
|
5645
|
+
// src/runtime/page/userAgent.ts
|
|
5646
|
+
var init_userAgent = __esm({
|
|
5647
|
+
"src/runtime/page/userAgent.ts"() {
|
|
5648
|
+
"use strict";
|
|
5649
|
+
init_errors();
|
|
5650
|
+
}
|
|
5651
|
+
});
|
|
5652
|
+
|
|
5553
5653
|
// src/runtime/page/emulation.ts
|
|
5554
5654
|
function viewportOverride(viewport, deviceScaleFactor = 0) {
|
|
5555
|
-
|
|
5655
|
+
const mobile = viewport.mobile === true;
|
|
5656
|
+
return {
|
|
5657
|
+
width: viewport.width,
|
|
5658
|
+
height: viewport.height,
|
|
5659
|
+
deviceScaleFactor: deviceScaleFactor || (mobile ? MOBILE_PIXEL_RATIO : 0),
|
|
5660
|
+
mobile
|
|
5661
|
+
};
|
|
5556
5662
|
}
|
|
5557
|
-
var log16;
|
|
5663
|
+
var log16, MOBILE_PIXEL_RATIO;
|
|
5558
5664
|
var init_emulation = __esm({
|
|
5559
5665
|
"src/runtime/page/emulation.ts"() {
|
|
5560
5666
|
"use strict";
|
|
5561
5667
|
init_elementGeometry();
|
|
5668
|
+
init_userAgent();
|
|
5562
5669
|
init_logging();
|
|
5563
5670
|
init_errors();
|
|
5564
5671
|
log16 = createLogger("session");
|
|
5672
|
+
MOBILE_PIXEL_RATIO = 3;
|
|
5565
5673
|
}
|
|
5566
5674
|
});
|
|
5567
5675
|
|
|
@@ -5738,13 +5846,18 @@ async function useUnitPixelRatio(devicePixelRatio, viewport) {
|
|
|
5738
5846
|
const sessionViewport = readSessionMetadata()?.viewport;
|
|
5739
5847
|
const size = sessionViewport ?? await windowSize(viewport);
|
|
5740
5848
|
await callCDP("Emulation.setDeviceMetricsOverride", viewportOverride(size, 1));
|
|
5741
|
-
return
|
|
5742
|
-
|
|
5743
|
-
|
|
5744
|
-
|
|
5745
|
-
|
|
5746
|
-
}
|
|
5747
|
-
|
|
5849
|
+
return restoreSessionMetrics;
|
|
5850
|
+
}
|
|
5851
|
+
async function restoreSessionMetrics() {
|
|
5852
|
+
const sessionViewport = readSessionMetadata()?.viewport;
|
|
5853
|
+
if (!sessionViewport) {
|
|
5854
|
+
await callCDP("Emulation.clearDeviceMetricsOverride", {});
|
|
5855
|
+
return;
|
|
5856
|
+
}
|
|
5857
|
+
await callCDP("Emulation.setDeviceMetricsOverride", viewportOverride(sessionViewport));
|
|
5858
|
+
if (sessionViewport.mobile) {
|
|
5859
|
+
await callCDP("Emulation.setTouchEmulationEnabled", { enabled: true, maxTouchPoints: 5 });
|
|
5860
|
+
}
|
|
5748
5861
|
}
|
|
5749
5862
|
async function getElementBounds(ref) {
|
|
5750
5863
|
const response = await callCDP("DOM.getBoxModel", ref);
|
|
@@ -5888,21 +6001,61 @@ async function visibleViewport() {
|
|
|
5888
6001
|
function insideView(area, view) {
|
|
5889
6002
|
return area.x >= 0 && area.y >= 0 && area.x + area.width <= view.width && area.y + area.height <= view.height;
|
|
5890
6003
|
}
|
|
5891
|
-
async function measureInView(ref) {
|
|
6004
|
+
async function measureInView(ref, padding) {
|
|
5892
6005
|
const view = await visibleViewport();
|
|
5893
6006
|
let box = await getElementBounds(ref);
|
|
5894
|
-
let bounds = await captureArea(ref, box);
|
|
6007
|
+
let bounds = await captureArea(ref, box, padding);
|
|
5895
6008
|
const fits = bounds.width <= view.width && bounds.height <= view.height;
|
|
5896
|
-
if (fits
|
|
5897
|
-
|
|
5898
|
-
|
|
5899
|
-
|
|
6009
|
+
if (!fits) {
|
|
6010
|
+
await keepLayoutWithoutScrollbars(view);
|
|
6011
|
+
box = await getElementBounds(ref);
|
|
6012
|
+
return { box, bounds: await captureArea(ref, box, padding), inView: false };
|
|
6013
|
+
}
|
|
6014
|
+
if (insideView(bounds, view)) return { box, bounds, inView: true };
|
|
6015
|
+
const scrolledFrom = await scrollPosition();
|
|
6016
|
+
const dx = bounds.x + bounds.width / 2 - view.width / 2;
|
|
6017
|
+
const dy = bounds.y + bounds.height / 2 - view.height / 2;
|
|
6018
|
+
await callCDP("Runtime.evaluate", { expression: `window.scrollBy(${dx}, ${dy})` });
|
|
6019
|
+
try {
|
|
5900
6020
|
box = await getElementBounds(ref);
|
|
5901
|
-
bounds = await captureArea(ref, box);
|
|
6021
|
+
bounds = await captureArea(ref, box, padding);
|
|
6022
|
+
} catch (error) {
|
|
6023
|
+
await restoreScrollPosition(scrolledFrom);
|
|
6024
|
+
throw error;
|
|
5902
6025
|
}
|
|
5903
|
-
return { box, bounds, inView: insideView(bounds, view) };
|
|
6026
|
+
return { box, bounds, inView: insideView(bounds, view), scrolledFrom };
|
|
6027
|
+
}
|
|
6028
|
+
async function keepLayoutWithoutScrollbars(view) {
|
|
6029
|
+
const phone = readSessionMetadata()?.viewport?.mobile;
|
|
6030
|
+
await callCDP("Emulation.setScrollbarsHidden", { hidden: true });
|
|
6031
|
+
await callCDP(
|
|
6032
|
+
"Emulation.setDeviceMetricsOverride",
|
|
6033
|
+
viewportOverride({ ...view, ...phone && { mobile: true } }, 1)
|
|
6034
|
+
);
|
|
6035
|
+
}
|
|
6036
|
+
async function restoreViewport() {
|
|
6037
|
+
await callCDP("Emulation.setScrollbarsHidden", { hidden: false });
|
|
6038
|
+
await restoreSessionMetrics();
|
|
5904
6039
|
}
|
|
5905
|
-
async function
|
|
6040
|
+
async function scrollPosition() {
|
|
6041
|
+
const response = await callCDP("Runtime.evaluate", {
|
|
6042
|
+
expression: "[window.scrollX, window.scrollY]",
|
|
6043
|
+
returnByValue: true
|
|
6044
|
+
});
|
|
6045
|
+
const value = response.data?.result?.result?.value;
|
|
6046
|
+
const [x, y] = Array.isArray(value) ? value : [];
|
|
6047
|
+
return { x: x ?? 0, y: y ?? 0 };
|
|
6048
|
+
}
|
|
6049
|
+
async function captureArea(ref, bounds, padding) {
|
|
6050
|
+
const area = await paintedArea(ref, bounds);
|
|
6051
|
+
return padding > 0 ? {
|
|
6052
|
+
x: area.x - padding,
|
|
6053
|
+
y: area.y - padding,
|
|
6054
|
+
width: area.width + 2 * padding,
|
|
6055
|
+
height: area.height + 2 * padding
|
|
6056
|
+
} : area;
|
|
6057
|
+
}
|
|
6058
|
+
async function paintedArea(ref, bounds) {
|
|
5906
6059
|
const objectGroup = `bdg-shot-${process.pid}`;
|
|
5907
6060
|
try {
|
|
5908
6061
|
const resolved = await callCDP("DOM.resolveNode", { ...ref, objectGroup });
|
|
@@ -5946,15 +6099,18 @@ async function captureElementScreenshot(outputPath, ref, options = {}) {
|
|
|
5946
6099
|
devicePixelRatio,
|
|
5947
6100
|
before?.visualViewport ?? { clientWidth: 800, clientHeight: 600 }
|
|
5948
6101
|
);
|
|
5949
|
-
|
|
5950
|
-
|
|
5951
|
-
|
|
6102
|
+
const restore = async (wide, scrolledFrom2) => {
|
|
6103
|
+
await (wide ? restoreViewport() : restoreMetrics());
|
|
6104
|
+
if (scrolledFrom2) await restoreScrollPosition(scrolledFrom2);
|
|
6105
|
+
};
|
|
6106
|
+
let measured;
|
|
5952
6107
|
try {
|
|
5953
|
-
|
|
6108
|
+
measured = await measureInView(ref, options.padding ?? 0);
|
|
5954
6109
|
} catch (error) {
|
|
5955
|
-
await
|
|
6110
|
+
await restoreViewport();
|
|
5956
6111
|
throw error;
|
|
5957
6112
|
}
|
|
6113
|
+
const { box, bounds, inView, scrolledFrom } = measured;
|
|
5958
6114
|
const originalWidth = bounds.width;
|
|
5959
6115
|
const originalHeight = bounds.height;
|
|
5960
6116
|
const resized = shouldResize(originalWidth, originalHeight, noResize);
|
|
@@ -5980,7 +6136,7 @@ async function captureElementScreenshot(outputPath, ref, options = {}) {
|
|
|
5980
6136
|
});
|
|
5981
6137
|
screenshotResult = screenshotResponse.data?.result;
|
|
5982
6138
|
} finally {
|
|
5983
|
-
await
|
|
6139
|
+
await restore(!inView, scrolledFrom);
|
|
5984
6140
|
}
|
|
5985
6141
|
if (!screenshotResult?.data) {
|
|
5986
6142
|
throw new CDPConnectionError("No screenshot data returned", new Error("Empty response"));
|
|
@@ -5997,7 +6153,8 @@ async function captureElementScreenshot(outputPath, ref, options = {}) {
|
|
|
5997
6153
|
finalTokens: calculateImageTokens(finalWidth, finalHeight),
|
|
5998
6154
|
element: {
|
|
5999
6155
|
bounds: roundBounds(onPage(box)),
|
|
6000
|
-
...bounds !== box && { captured: roundBounds(clip) }
|
|
6156
|
+
...bounds !== box && { captured: roundBounds(clip) },
|
|
6157
|
+
...options.padding && { padding: options.padding }
|
|
6001
6158
|
}
|
|
6002
6159
|
};
|
|
6003
6160
|
if (quality !== void 0) {
|
|
@@ -6063,8 +6220,37 @@ var init_screenshot = __esm({
|
|
|
6063
6220
|
if (!clips(style)) walk(child);
|
|
6064
6221
|
}
|
|
6065
6222
|
};
|
|
6066
|
-
|
|
6067
|
-
|
|
6223
|
+
const ownStyle = view.getComputedStyle(this);
|
|
6224
|
+
if (!clips(ownStyle)) {
|
|
6225
|
+
walk(this);
|
|
6226
|
+
const scaleX = this.offsetWidth ? own.width / this.offsetWidth : 1;
|
|
6227
|
+
const scaleY = this.offsetHeight ? own.height / this.offsetHeight : 1;
|
|
6228
|
+
const wider = Math.max(0, this.scrollWidth - this.clientWidth) * scaleX;
|
|
6229
|
+
const taller = Math.max(0, this.scrollHeight - this.clientHeight) * scaleY;
|
|
6230
|
+
if (ownStyle.direction === 'rtl') reach.left = Math.min(reach.left, own.left - wider);
|
|
6231
|
+
else reach.right = Math.max(reach.right, own.right + wider);
|
|
6232
|
+
reach.bottom = Math.max(reach.bottom, own.bottom + taller);
|
|
6233
|
+
}
|
|
6234
|
+
const ink = { left: 0, top: 0, right: 0, bottom: 0 };
|
|
6235
|
+
const grow = (side, amount) => { ink[side] = Math.max(ink[side], amount); };
|
|
6236
|
+
for (const layer of ownStyle.boxShadow === 'none' ? [] : ownStyle.boxShadow.split(/,(?![^(]*\\))/)) {
|
|
6237
|
+
if (/\\binset\\b/.test(layer)) continue;
|
|
6238
|
+
const [x = 0, y = 0, blur = 0, spread = 0] = (layer.replace(/(rgba?|hsla?|color|oklch|lab|lch)\\([^)]*\\)/g, '').match(/-?[\\d.]+px/g) || []).map(parseFloat);
|
|
6239
|
+
grow('left', blur + spread - x);
|
|
6240
|
+
grow('right', blur + spread + x);
|
|
6241
|
+
grow('top', blur + spread - y);
|
|
6242
|
+
grow('bottom', blur + spread + y);
|
|
6243
|
+
}
|
|
6244
|
+
if (ownStyle.outlineStyle !== 'none') {
|
|
6245
|
+
const outline = parseFloat(ownStyle.outlineWidth) + parseFloat(ownStyle.outlineOffset);
|
|
6246
|
+
['left', 'top', 'right', 'bottom'].forEach((side) => grow(side, outline));
|
|
6247
|
+
}
|
|
6248
|
+
return {
|
|
6249
|
+
left: Math.max(own.left - reach.left, ink.left),
|
|
6250
|
+
top: Math.max(own.top - reach.top, ink.top),
|
|
6251
|
+
right: Math.max(reach.right - own.right, ink.right),
|
|
6252
|
+
bottom: Math.max(reach.bottom - own.bottom, ink.bottom)
|
|
6253
|
+
};
|
|
6068
6254
|
}`;
|
|
6069
6255
|
OVERFLOW_SLACK = 1;
|
|
6070
6256
|
}
|
|
@@ -8052,18 +8238,18 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
8052
8238
|
*
|
|
8053
8239
|
* @private
|
|
8054
8240
|
*/
|
|
8055
|
-
_prepareUserArgs(argv,
|
|
8241
|
+
_prepareUserArgs(argv, parseOptions2) {
|
|
8056
8242
|
if (argv !== void 0 && !Array.isArray(argv)) {
|
|
8057
8243
|
throw new Error("first parameter to parse must be array or undefined");
|
|
8058
8244
|
}
|
|
8059
|
-
|
|
8060
|
-
if (argv === void 0 &&
|
|
8245
|
+
parseOptions2 = parseOptions2 || {};
|
|
8246
|
+
if (argv === void 0 && parseOptions2.from === void 0) {
|
|
8061
8247
|
if (process2.versions?.electron) {
|
|
8062
|
-
|
|
8248
|
+
parseOptions2.from = "electron";
|
|
8063
8249
|
}
|
|
8064
8250
|
const execArgv = process2.execArgv ?? [];
|
|
8065
8251
|
if (execArgv.includes("-e") || execArgv.includes("--eval") || execArgv.includes("-p") || execArgv.includes("--print")) {
|
|
8066
|
-
|
|
8252
|
+
parseOptions2.from = "eval";
|
|
8067
8253
|
}
|
|
8068
8254
|
}
|
|
8069
8255
|
if (argv === void 0) {
|
|
@@ -8071,7 +8257,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
8071
8257
|
}
|
|
8072
8258
|
this.rawArgs = argv.slice();
|
|
8073
8259
|
let userArgs;
|
|
8074
|
-
switch (
|
|
8260
|
+
switch (parseOptions2.from) {
|
|
8075
8261
|
case void 0:
|
|
8076
8262
|
case "node":
|
|
8077
8263
|
this._scriptPath = argv[1];
|
|
@@ -8093,7 +8279,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
8093
8279
|
break;
|
|
8094
8280
|
default:
|
|
8095
8281
|
throw new Error(
|
|
8096
|
-
`unexpected parse option { from: '${
|
|
8282
|
+
`unexpected parse option { from: '${parseOptions2.from}' }`
|
|
8097
8283
|
);
|
|
8098
8284
|
}
|
|
8099
8285
|
if (!this._name && this._scriptPath)
|
|
@@ -8123,9 +8309,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
8123
8309
|
* @param {string} [parseOptions.from] - where the args are from: 'node', 'user', 'electron'
|
|
8124
8310
|
* @return {Command} `this` command for chaining
|
|
8125
8311
|
*/
|
|
8126
|
-
parse(argv,
|
|
8312
|
+
parse(argv, parseOptions2) {
|
|
8127
8313
|
this._prepareForParse();
|
|
8128
|
-
const userArgs = this._prepareUserArgs(argv,
|
|
8314
|
+
const userArgs = this._prepareUserArgs(argv, parseOptions2);
|
|
8129
8315
|
this._parseCommand([], userArgs);
|
|
8130
8316
|
return this;
|
|
8131
8317
|
}
|
|
@@ -8149,9 +8335,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
8149
8335
|
* @param {string} parseOptions.from - where the args are from: 'node', 'user', 'electron'
|
|
8150
8336
|
* @return {Promise}
|
|
8151
8337
|
*/
|
|
8152
|
-
async parseAsync(argv,
|
|
8338
|
+
async parseAsync(argv, parseOptions2) {
|
|
8153
8339
|
this._prepareForParse();
|
|
8154
|
-
const userArgs = this._prepareUserArgs(argv,
|
|
8340
|
+
const userArgs = this._prepareUserArgs(argv, parseOptions2);
|
|
8155
8341
|
await this._parseCommand([], userArgs);
|
|
8156
8342
|
return this;
|
|
8157
8343
|
}
|
|
@@ -9468,15 +9654,19 @@ init_schema();
|
|
|
9468
9654
|
// src/commands/optionBehaviors.ts
|
|
9469
9655
|
init_screenshotResize();
|
|
9470
9656
|
var TRIGGERED_REQUESTS_BEHAVIOR = "Requests (and WebSocket connections) that start after the action begins are returned as triggeredRequests (method, url, status, durationMs; pending when still running at return, loading when the response arrived but its body is still streaming; with resourceType; human output lists documents, XHR/fetch and WebSockets first (up to 10) and counts static assets on one line; absent when network telemetry is off). Attribution is by time: requests a page timer or poller starts meanwhile are listed too, whether or not the action caused them";
|
|
9471
|
-
var ACTION_EFFECTS_BEHAVIOR = 'The result also says what changed on the page: a navigation (Page: navigated to <url> (status), or URL changed to <url> (same document); JSON navigation { url, sameDocument, status }), and messages that appeared or changed in alert/status/aria-live elements or flash/error/toast-like classes (New text: "\u2026" (element); JSON messages [{ text, element }], at most 3; after a navigation every message on the new page counts; texts of only digits and time units, such as clocks and counters, are left out, but other text that changes on its own, such as a rotating banner, can show up). Both are absent when nothing changed. Cost: one page script sent before the action without waiting for it and one read after it, a few ms; when the page does not answer (a pending navigation) bdg waits at most 200 ms for the snapshot and 250 ms per read, and the navigation is still reported from CDP events';
|
|
9657
|
+
var ACTION_EFFECTS_BEHAVIOR = 'The result also says what changed on the page: a navigation (Page: navigated to <url> (status), or URL changed to <url> (same document); JSON navigation { url, sameDocument, status }), and messages that appeared or changed in alert/status/aria-live elements or flash/error/toast-like classes (New text: "\u2026" (element); JSON messages [{ text, element }], at most 3, with "(+N more)" and moreMessages for the rest; after a navigation every message on the new page counts; texts of only digits and time units, such as clocks and counters, are left out, but other text that changes on its own, such as a rotating banner, can show up). Both are absent when nothing changed. Cost: one page script sent before the action without waiting for it and one read after it, a few ms; when the page does not answer (a pending navigation) bdg waits at most 200 ms for the snapshot and 250 ms per read, and the navigation is still reported from CDP events';
|
|
9472
9658
|
var STILL_CHANGING_BEHAVIOR = 'When the page was still changing as the action returned, the status line says (page still changing), a note below it says what was pending and suggests bdg dom wait <selector>, and JSON has settled: false with pending { requests (document, fetch/XHR and script requests still running), navigation (a new page still loading), loading (a loading indicator that appeared, e.g. "div#loading"), domChanging (DOM changes kept coming in bursts over a second look 250 ms later; a single render, ticking text and style animations do not count), busy (the page did not answer within 250 ms: a long script) }; absent when the page looked settled (exit code stays 0). A result a timer renders later, with no DOM change, request or loading indicator before it, is not detected. Cost: nothing extra, except 250 ms plus one read when the DOM looked busy. Not checked with --no-wait';
|
|
9473
9659
|
var SHOWN_BEHAVIOR = `Elements the action showed are listed (Shown: <element> "<text>"; JSON shown [{ text, element }], at most 3, outermost first): elements with visible text added inside the target's form, search box, dialog or combobox (else its grandparent, or its parent when that is the body), and popups and messages added anywhere (tooltip, menu, listbox, dialog, alert, status roles, popover, aria-live, message-like classes); widgets elsewhere on the page and re-rendered elements whose text was there before do not count`;
|
|
9474
9660
|
var NO_WAIT_TRIGGERED_REQUESTS = "Returns immediately without waiting for network; triggeredRequests lists only requests bdg saw start before returning (often none yet; check bdg network list later)";
|
|
9475
9661
|
var OPTION_BEHAVIORS = {
|
|
9476
9662
|
"screenshot:--selector": {
|
|
9477
9663
|
default: "Captures the page (full page unless --no-full-page)",
|
|
9478
|
-
whenEnabled: 'Captures one element; the selector (or a query index) can also be given as the second argument: bdg dom screenshot out.png "#sel".
|
|
9479
|
-
automaticBehavior: "The capture covers the border box plus
|
|
9664
|
+
whenEnabled: 'Captures one element; the selector (or a query index) can also be given as the second argument: bdg dom screenshot out.png "#sel". With --index it picks that match of the selector (--selector ".item" --index 2). A positional and an option naming different elements exits 81',
|
|
9665
|
+
automaticBehavior: "The capture covers the border box plus what overflows it: uncleared floats, positioned children, text past a tight line height, and the element's own box shadows and outline (a focus ring); not what an overflow: hidden ancestor cuts off, nor fixed descendants. JSON element.bounds is the border box and element.captured the larger area when it grew, which human output notes. An element smaller than the viewport is scrolled into view for the capture and the page scroll put back afterwards; a larger one is captured with the page laid out at its width without scrollbars, so it does not shift"
|
|
9666
|
+
},
|
|
9667
|
+
"screenshot:--padding": {
|
|
9668
|
+
default: "The element capture is its painted area (border box, overflowing content, shadows, outline)",
|
|
9669
|
+
whenEnabled: "Adds that many CSS px of the page around the element capture on every side (0-500); without an element it exits 81"
|
|
9480
9670
|
},
|
|
9481
9671
|
"screenshot:--no-resize": {
|
|
9482
9672
|
default: `Images auto-resized to max ${MAX_EDGE_PX}px longest edge for Claude Vision optimization (~1,600 tokens)`,
|
|
@@ -9658,10 +9848,16 @@ var OPTION_BEHAVIORS = {
|
|
|
9658
9848
|
whenEnabled: "Lists every listener of framework roots individually",
|
|
9659
9849
|
tokenImpact: "On React pages --all adds a row per event type and phase (about 140 rows, 60 KB of JSON)"
|
|
9660
9850
|
},
|
|
9851
|
+
"audit:--level": {
|
|
9852
|
+
default: "Text must reach WCAG AA: 4.5, or 3 for large text (24px, or 18.66px bold); every text-drawing element is checked, composited like dom inspect",
|
|
9853
|
+
whenEnabled: "--level AAA asks 7, or 4.5 for large text",
|
|
9854
|
+
automaticBehavior: "One walk over the rendered elements (open shadow roots included, at most 20000; capped says when it stopped). Findings are sorted weakest first; --limit (default 20) lists that many per check and the rest are counted. Overflow leaves out content inside horizontal scrollers and visually-hidden 1px text; identical findings are grouped (\xD7N)",
|
|
9855
|
+
tokenImpact: "About one line per finding; --limit bounds it"
|
|
9856
|
+
},
|
|
9661
9857
|
"layout:--index": {
|
|
9662
9858
|
default: "Reports every match of the selector (human output lists the first 20, JSON up to 100 plus an omitted count); a numeric argument reports that cached query element",
|
|
9663
9859
|
whenEnabled: "Reports only the nth match (0-based); out of range exits 81",
|
|
9664
|
-
automaticBehavior: 'Coordinates are CSS px: bounds relative to the top-level page (iframe offsets and page scroll included), viewport relative to the visible area. Iframes and overflow containers (scroll lists, overflow: hidden) clip what counts as visible (clippedBy names the one cutting it off). scrollBy brings the whole element into view and is limited to how far the page can scroll: for an element out of view it centres it (aligns its start when it is larger than the viewport; human output says "to centre it"), for a partly visible one it is the smallest scroll that shows all of it (the part cut off at the top or bottom; the start of one larger than the viewport; "partly visible (87%); scroll up 5px to see all of it"). Elements a page script moves on scroll (floating menus) may move again after it; fixed and sticky elements (page scroll does not move them, or only until they stick) and ones beyond that range get offScreenReason instead, which says "page scrolling is locked (\u2026)" when the page cannot scroll because body/html is position: fixed or overflow: hidden, so in-flow content is not called fixed; a visible dialog (dialog[open], [aria-modal=true], [role=dialog|alertdialog]) is named as the likely cause ("likely by dialog div#consent"). page.viewport is the layout viewport without scrollbars, as dom scroll reports it; page.colorScheme is the prefers-color-scheme media feature the page sees (not the theme it renders). Content in a closed <details> or under content-visibility: hidden is hidden. coveredBy is the
|
|
9860
|
+
automaticBehavior: 'Coordinates are CSS px: bounds relative to the top-level page (iframe offsets and page scroll included), viewport relative to the visible area. Iframes and overflow containers (scroll lists, overflow: hidden) clip what counts as visible (clippedBy names the one cutting it off). scrollBy brings the whole element into view and is limited to how far the page can scroll: for an element out of view it centres it (aligns its start when it is larger than the viewport; human output says "to centre it"), for a partly visible one it is the smallest scroll that shows all of it (the part cut off at the top or bottom; the start of one larger than the viewport; "partly visible (87%); scroll up 5px to see all of it"). Elements a page script moves on scroll (floating menus) may move again after it; fixed and sticky elements (page scroll does not move them, or only until they stick) and ones beyond that range get offScreenReason instead, which says "page scrolling is locked (\u2026)" when the page cannot scroll because body/html is position: fixed or overflow: hidden, so in-flow content is not called fixed; a visible dialog (dialog[open], [aria-modal=true], [role=dialog|alertdialog]) is named as the likely cause ("likely by dialog div#consent"). page.viewport is the layout viewport without scrollbars, as dom scroll reports it; page.colorScheme is the prefers-color-scheme media feature the page sees (not the theme it renders). Content in a closed <details> or under content-visibility: hidden is hidden. coveredBy is the first element painted above it at the center of the largest visible box that paints there (the background of a sticky header rather than the transparent logo on it; inside a shadow host, what its shadow root paints), else the topmost one with coverTransparent (none for pointer-events: none, nor for an element of the same click target: an overlay inside the link, button or label the element is in, a link to the same URL, or the textless absolutely positioned overlay link spanning the card that holds plain content); inert elements are flagged, not hidden',
|
|
9665
9861
|
tokenImpact: 'About one line per element; a cheap alternative to screenshots for "where is it?"'
|
|
9666
9862
|
},
|
|
9667
9863
|
"inspect:--index": {
|
|
@@ -9736,11 +9932,11 @@ var OPTION_BEHAVIORS = {
|
|
|
9736
9932
|
},
|
|
9737
9933
|
"peek:-f": {
|
|
9738
9934
|
default: "Shows snapshot of current data",
|
|
9739
|
-
whenEnabled: "Continuous monitoring - refreshes every second
|
|
9935
|
+
whenEnabled: "Continuous monitoring (like tail -f): refreshes every second, or every --interval ms (100-60000). Replaces the deprecated bdg tail"
|
|
9740
9936
|
},
|
|
9741
9937
|
"peek:--follow": {
|
|
9742
9938
|
default: "Shows snapshot of current data",
|
|
9743
|
-
whenEnabled: "Continuous monitoring - refreshes every second
|
|
9939
|
+
whenEnabled: "Continuous monitoring (like tail -f): refreshes every second, or every --interval ms (100-60000). Replaces the deprecated bdg tail"
|
|
9744
9940
|
},
|
|
9745
9941
|
"peek:-v": {
|
|
9746
9942
|
default: "Compact output (truncated URLs, no resource types)",
|
|
@@ -9776,6 +9972,11 @@ var OPTION_BEHAVIORS = {
|
|
|
9776
9972
|
whenEnabled: "The page gets exactly that viewport (CSS px, e.g. 1280x800) for the whole session, through navigations and reloads (Emulation.setDeviceMetricsOverride at the display pixel ratio); a launched Chrome also opens its window at that size, so tabs the page opens get it too",
|
|
9777
9973
|
automaticBehavior: "Works with --chrome-ws-url: the override belongs to the session, and Chrome drops it when the session ends, so the attached browser gets its own size back. bdg status shows the resulting layout viewport without the scrollbar (Viewport: 1265\xD7800 (emulated 1280x800)). Invalid sizes (not WxH, a side outside 1-10000) exit 81"
|
|
9778
9974
|
},
|
|
9975
|
+
"bdg:--mobile": {
|
|
9976
|
+
default: "A desktop viewport: classic scrollbars take ~15px of the width, no touch, a desktop user agent",
|
|
9977
|
+
whenEnabled: "Emulates a phone for the whole session: a mobile viewport (390x844 unless --viewport) at pixel ratio 3 with mobile layout (meta viewport, overlay scrollbars, so 100vw fits), touch (pointer: coarse, maxTouchPoints 5) and an Android Chrome user agent with mobile client hints; bdg page emulate --mobile turns it on mid-session, --viewport WxH without --mobile or --reset turns it off",
|
|
9978
|
+
automaticBehavior: 'Screenshots keep the mobile layout and are taken at pixel ratio 1 (CSS px = image px); bdg status shows "(emulated 390x844, phone)"'
|
|
9979
|
+
},
|
|
9779
9980
|
"bdg:--color-scheme": {
|
|
9780
9981
|
default: "The page sees the system setting for prefers-color-scheme (headless Chrome follows the OS, so a dark OS renders dark pages); bdg status and dom layout show which one",
|
|
9781
9982
|
whenEnabled: "Emulates prefers-color-scheme: light or dark for the whole session (Emulation.setEmulatedMedia); other values exit 81 with a suggestion",
|
|
@@ -10631,11 +10832,6 @@ var DECISION_TREES = {
|
|
|
10631
10832
|
yesCommand: "peek --follow",
|
|
10632
10833
|
noAction: "next"
|
|
10633
10834
|
},
|
|
10634
|
-
{
|
|
10635
|
-
question: "Need continuous monitoring (like tail -f)?",
|
|
10636
|
-
yesCommand: "tail",
|
|
10637
|
-
noAction: "next"
|
|
10638
|
-
},
|
|
10639
10835
|
{
|
|
10640
10836
|
question: "Need quick preview of recent data?",
|
|
10641
10837
|
yesCommand: "peek",
|
|
@@ -10784,7 +10980,7 @@ var TASK_MAPPINGS = {
|
|
|
10784
10980
|
cdpAlternative: "Multiple IPC queries to session state"
|
|
10785
10981
|
},
|
|
10786
10982
|
live_monitoring: {
|
|
10787
|
-
commands: ["peek --follow"
|
|
10983
|
+
commands: ["peek --follow"],
|
|
10788
10984
|
description: "Monitor data collection in real-time",
|
|
10789
10985
|
cdpAlternative: "CDP event subscriptions with custom handler"
|
|
10790
10986
|
},
|
|
@@ -10891,7 +11087,7 @@ function convertCommand(command) {
|
|
|
10891
11087
|
}
|
|
10892
11088
|
function generateRuntimeState() {
|
|
10893
11089
|
const sessionActive = readLiveDaemonPid() !== null;
|
|
10894
|
-
const availableCommands = sessionActive ? ["peek", "
|
|
11090
|
+
const availableCommands = sessionActive ? ["peek", "details", "dom", "network", "console", "cdp", "status", "sessions", "stop"] : ["bdg <url>", "sessions", "cleanup", "--help", "--version"];
|
|
10895
11091
|
return {
|
|
10896
11092
|
sessionActive,
|
|
10897
11093
|
daemonRunning: sessionActive,
|
|
@@ -11253,6 +11449,8 @@ var COMMANDS = {
|
|
|
11253
11449
|
dom_form_discover: defineCommand(),
|
|
11254
11450
|
dom_listeners: defineCommand(),
|
|
11255
11451
|
dom_layout: defineCommand(),
|
|
11452
|
+
dom_audit: defineCommand(),
|
|
11453
|
+
css_search: defineCommand(),
|
|
11256
11454
|
dom_inspect: defineCommand(),
|
|
11257
11455
|
dom_wait: defineCommand()
|
|
11258
11456
|
};
|
|
@@ -11864,7 +12062,7 @@ function verboseCommandsMessage() {
|
|
|
11864
12062
|
return [
|
|
11865
12063
|
"Commands:",
|
|
11866
12064
|
` Full preview: ${sessionCommand("bdg peek --last 50")}`,
|
|
11867
|
-
` Watch live: ${sessionCommand("bdg
|
|
12065
|
+
` Watch live: ${sessionCommand("bdg peek --follow")}`,
|
|
11868
12066
|
` End session: ${sessionCommand("bdg stop")}`
|
|
11869
12067
|
].join("\n");
|
|
11870
12068
|
}
|
|
@@ -11881,6 +12079,9 @@ function connectionLostRetryMessage(timestamp, retryLabel) {
|
|
|
11881
12079
|
function connectionLostStopHintMessage() {
|
|
11882
12080
|
return "Press Ctrl+C to stop";
|
|
11883
12081
|
}
|
|
12082
|
+
function tailDeprecatedNotice() {
|
|
12083
|
+
return 'Note: "bdg tail" is deprecated and will be removed; use "bdg peek --follow" (same options: --last, --network, --console, --interval, --verbose)';
|
|
12084
|
+
}
|
|
11884
12085
|
|
|
11885
12086
|
// src/commands/shared/daemonErrorHandler.ts
|
|
11886
12087
|
init_exitCodes();
|
|
@@ -13687,9 +13888,17 @@ function registerFormInteractionCommands(program2) {
|
|
|
13687
13888
|
const action = options.double ? "double" : options.right ? "right" : "click";
|
|
13688
13889
|
await runPointerCommand(selectorOrIndex, options, action);
|
|
13689
13890
|
});
|
|
13690
|
-
domCommand.command("hover").description(
|
|
13691
|
-
|
|
13692
|
-
|
|
13891
|
+
domCommand.command("hover").description(
|
|
13892
|
+
"Move the mouse over an element (shows hover menus and tooltips); --off moves it off the page"
|
|
13893
|
+
).argument("[selectorOrIndex]", SELECTOR_OR_INDEX_ARGUMENT).option("--index <n>", "Element index if selector matches multiple (0-based)", integerOption(0)).option("--off", "Move the mouse off the page instead (closes menus that open on hover)").option("--strict", STRICT_OPTION_HELP).option("--no-wait", "Skip waiting for network stability after hovering").addOption(jsonOption()).action(
|
|
13894
|
+
async (selectorOrIndex, options) => {
|
|
13895
|
+
if (options.off || selectorOrIndex === void 0) {
|
|
13896
|
+
await hoverOff(selectorOrIndex, options);
|
|
13897
|
+
return;
|
|
13898
|
+
}
|
|
13899
|
+
await runPointerCommand(selectorOrIndex, options, "hover");
|
|
13900
|
+
}
|
|
13901
|
+
);
|
|
13693
13902
|
domCommand.command("submit").description("Submit a form by clicking submit button and waiting for completion").argument("<selectorOrIndex>", SELECTOR_OR_INDEX_ARGUMENT).option("--index <n>", "Element index if selector matches multiple (0-based)", integerOption(0)).option("--wait-navigation", "Wait for page navigation after submit").option(
|
|
13694
13903
|
"--wait-network <ms>",
|
|
13695
13904
|
"Wait for network idle after submit (milliseconds)",
|
|
@@ -13807,6 +14016,25 @@ function scrollOptionsProblem(selector, options) {
|
|
|
13807
14016
|
if (!selector && vertical + horizontal === 0) return "no-target";
|
|
13808
14017
|
return null;
|
|
13809
14018
|
}
|
|
14019
|
+
async function hoverOff(selectorOrIndex, options) {
|
|
14020
|
+
await runCommand(
|
|
14021
|
+
async () => {
|
|
14022
|
+
if (!options.off || selectorOrIndex !== void 0 || options.index !== void 0) {
|
|
14023
|
+
const err = options.off ? hoverOffWithTargetError() : missingArgumentError(HOVER_USAGE);
|
|
14024
|
+
return {
|
|
14025
|
+
success: false,
|
|
14026
|
+
error: err.message,
|
|
14027
|
+
exitCode: EXIT_CODES.INVALID_ARGUMENTS,
|
|
14028
|
+
errorContext: { suggestion: err.suggestion }
|
|
14029
|
+
};
|
|
14030
|
+
}
|
|
14031
|
+
await callCDP("Input.dispatchMouseEvent", { type: "mouseMoved", x: -1, y: -1 });
|
|
14032
|
+
return { success: true, data: { pointer: "off" } };
|
|
14033
|
+
},
|
|
14034
|
+
options,
|
|
14035
|
+
() => HOVER_OFF_DONE
|
|
14036
|
+
);
|
|
14037
|
+
}
|
|
13810
14038
|
async function runPointerCommand(selectorOrIndex, options, action) {
|
|
13811
14039
|
await runCommand(
|
|
13812
14040
|
() => options.double && options.right ? Promise.resolve({
|
|
@@ -13849,7 +14077,15 @@ function formatActionOutput(done, details, result, options = {}) {
|
|
|
13849
14077
|
fmt.blank();
|
|
13850
14078
|
fmt.keyValueList(details, keyWidth);
|
|
13851
14079
|
if (result.navigation) fmt.keyValue("Page", pageNavigationText(result.navigation), keyWidth);
|
|
13852
|
-
listRows(
|
|
14080
|
+
listRows(
|
|
14081
|
+
fmt,
|
|
14082
|
+
"New text",
|
|
14083
|
+
[
|
|
14084
|
+
...(result.messages ?? []).map(newMessageText),
|
|
14085
|
+
...result.moreMessages ? [moreMessagesText(result.moreMessages)] : []
|
|
14086
|
+
],
|
|
14087
|
+
keyWidth
|
|
14088
|
+
);
|
|
13853
14089
|
listRows(fmt, "Shown", (result.shown ?? []).map(shownElementText), keyWidth);
|
|
13854
14090
|
const omitted = result.triggeredRequestsOmitted;
|
|
13855
14091
|
const requests = formatTriggeredRequestLines(result.triggeredRequests ?? [], omitted);
|
|
@@ -14784,19 +15020,19 @@ function registerA11yCommands(domCmd) {
|
|
|
14784
15020
|
const a11y = domCmd.command("a11y").description("Accessibility tree inspection and semantic queries").argument(
|
|
14785
15021
|
"[search]",
|
|
14786
15022
|
'Quick search: index (describe), CSS selector (#id, .class), pattern with ":" (query), or name search'
|
|
14787
|
-
).enablePositionalOptions().addOption(jsonOption()).action(async (
|
|
14788
|
-
if (!
|
|
15023
|
+
).enablePositionalOptions().addOption(jsonOption()).action(async (search2, options) => {
|
|
15024
|
+
if (!search2) {
|
|
14789
15025
|
return a11y.help({ error: true });
|
|
14790
15026
|
}
|
|
14791
|
-
const isNumericIndex = /^\d+$/.test(
|
|
14792
|
-
const isCssSelector = /^[#.[]/u.test(
|
|
14793
|
-
const isPatternQuery = /^(role|name|description|desc)\s*[:=]/i.test(
|
|
15027
|
+
const isNumericIndex = /^\d+$/.test(search2);
|
|
15028
|
+
const isCssSelector = /^[#.[]/u.test(search2);
|
|
15029
|
+
const isPatternQuery = /^(role|name|description|desc)\s*[:=]/i.test(search2);
|
|
14794
15030
|
if (isNumericIndex || isCssSelector) {
|
|
14795
|
-
await handleA11yDescribe(
|
|
15031
|
+
await handleA11yDescribe(search2, options);
|
|
14796
15032
|
} else if (isPatternQuery) {
|
|
14797
|
-
await handleA11yQuery(
|
|
15033
|
+
await handleA11yQuery(search2, options);
|
|
14798
15034
|
} else {
|
|
14799
|
-
await handleA11yQuery(`name:${quoteQueryValue(
|
|
15035
|
+
await handleA11yQuery(`name:${quoteQueryValue(search2)}`, options);
|
|
14800
15036
|
}
|
|
14801
15037
|
});
|
|
14802
15038
|
a11y.command("tree").description("Dump full accessibility tree (filters ignored nodes)").addOption(jsonOption()).action(async (options) => {
|
|
@@ -14900,7 +15136,10 @@ function formatDomScreenshot(data) {
|
|
|
14900
15136
|
output += " (viewport only - page too tall)";
|
|
14901
15137
|
}
|
|
14902
15138
|
if (data.element?.captured) {
|
|
14903
|
-
output += ` (${screenshotGrownNote(data.element.bounds, data.element.captured)})`;
|
|
15139
|
+
output += ` (${screenshotGrownNote(data.element.bounds, data.element.captured, data.element.padding)})`;
|
|
15140
|
+
}
|
|
15141
|
+
if (data.resized && data.originalWidth !== void 0 && data.originalHeight !== void 0) {
|
|
15142
|
+
output += ` (${screenshotScaledNote(data.originalWidth, data.originalHeight, data.width, data.height)})`;
|
|
14904
15143
|
}
|
|
14905
15144
|
return output;
|
|
14906
15145
|
}
|
|
@@ -15688,11 +15927,11 @@ function inspectHeader(data) {
|
|
|
15688
15927
|
data.element,
|
|
15689
15928
|
data.content !== void 0 && `"${data.content}"`,
|
|
15690
15929
|
data.placeholder !== void 0 && `placeholder "${data.placeholder}"`,
|
|
15691
|
-
data.rect && `${data.rect.w}x${data.rect.h} @${data.rect.x},${data.rect.y}`,
|
|
15930
|
+
data.rect && `${data.rect.w}x${data.rect.h}${data.rect.screen ? ` (${data.rect.screen.w}x${data.rect.screen.h} on screen)` : ""} @${data.rect.x},${data.rect.y}${data.rect.in ? " (fixed: viewport position)" : ""}`,
|
|
15692
15931
|
kind && `[${kind}]`,
|
|
15693
15932
|
...inspectVisibilityBadges(data.visibility),
|
|
15694
15933
|
data.context && `in ${data.context}`,
|
|
15695
|
-
data.theme === "dark" && inspectDarkThemeBadge(),
|
|
15934
|
+
data.theme === "dark" && inspectDarkThemeBadge(data.themeFrom === "emulation"),
|
|
15696
15935
|
data.animating && inspectAnimatingBadge(data.animating)
|
|
15697
15936
|
].filter(Boolean).join(" ");
|
|
15698
15937
|
}
|
|
@@ -15767,6 +16006,7 @@ function parentLine(layout) {
|
|
|
15767
16006
|
function fontParts(text) {
|
|
15768
16007
|
const loaded = [
|
|
15769
16008
|
text.rendered && `rendered "${text.rendered}"`,
|
|
16009
|
+
text.resolved && `resolves to "${text.resolved}"`,
|
|
15770
16010
|
text.webfont && "webfont loaded"
|
|
15771
16011
|
].filter(Boolean);
|
|
15772
16012
|
const family = text.family && [text.family, loaded.length > 0 && `(${loaded.join(", ")})`];
|
|
@@ -15787,14 +16027,16 @@ function fontParts(text) {
|
|
|
15787
16027
|
function contrastText(contrast) {
|
|
15788
16028
|
if (!contrast) return void 0;
|
|
15789
16029
|
return [
|
|
15790
|
-
`contrast ${contrast.ratio} ${contrast.level}`,
|
|
15791
|
-
|
|
16030
|
+
contrast.approximate ? `contrast \u2248${contrast.ratio} ${contrast.level}` : `contrast ${contrast.ratio} ${contrast.level}`,
|
|
16031
|
+
`on ${contrast.background}`,
|
|
15792
16032
|
contrast.overImage && "(over image)",
|
|
15793
|
-
contrast.opacity !== void 0 && `(faded: opacity ${contrast.opacity})
|
|
16033
|
+
contrast.opacity !== void 0 && `(faded: opacity ${contrast.opacity})`,
|
|
16034
|
+
contrast.approximate && `(approximate: ${contrast.approximate.join(", ")})`
|
|
15794
16035
|
].filter(Boolean).join(" ");
|
|
15795
16036
|
}
|
|
15796
16037
|
function textLine(text) {
|
|
15797
16038
|
return groupLine("text", [
|
|
16039
|
+
text.holder && `in ${text.holder}`,
|
|
15798
16040
|
...fontParts(text),
|
|
15799
16041
|
text.color && `color ${text.color}`,
|
|
15800
16042
|
contrastText(text.contrast),
|
|
@@ -15805,17 +16047,25 @@ function textLine(text) {
|
|
|
15805
16047
|
text.whiteSpace && `ws ${text.whiteSpace}`,
|
|
15806
16048
|
text.overflow && `text-overflow ${text.overflow}`,
|
|
15807
16049
|
text.clamp && `clamp ${text.clamp}`,
|
|
16050
|
+
text.truncated && "truncated",
|
|
16051
|
+
text.gradientFill && "filled by its background (background-clip: text; no contrast)",
|
|
15808
16052
|
text.shadow && `shadow ${text.shadow}`,
|
|
15809
16053
|
text.features && `features ${text.features}`
|
|
15810
16054
|
]);
|
|
15811
16055
|
}
|
|
15812
16056
|
function fillText(fill) {
|
|
15813
16057
|
if (fill.type === "solid") return `bg ${fill.color}`;
|
|
15814
|
-
|
|
15815
|
-
|
|
16058
|
+
return [
|
|
16059
|
+
`bg-image ${fill.value}`,
|
|
16060
|
+
fill.size && `size ${fill.size}`,
|
|
16061
|
+
fill.position && `at ${fill.position}`
|
|
16062
|
+
].filter(Boolean).join(" ");
|
|
15816
16063
|
}
|
|
15817
16064
|
function fillLine(data) {
|
|
16065
|
+
const paint = data.paint;
|
|
15818
16066
|
return groupLine("fill", [
|
|
16067
|
+
paint && `fill ${paint.fill}`,
|
|
16068
|
+
paint && `stroke ${paint.stroke}${paint.strokeWidth !== void 0 ? ` ${paint.strokeWidth}` : ""}`,
|
|
15819
16069
|
...(data.fills ?? []).map(fillText),
|
|
15820
16070
|
data.opacity !== void 0 && `opacity ${data.opacity}`,
|
|
15821
16071
|
data.blend && `blend ${data.blend}`
|
|
@@ -15865,6 +16115,7 @@ function pseudoText(pseudo) {
|
|
|
15865
16115
|
pseudo.content !== void 0 && `content ${pseudo.content}`,
|
|
15866
16116
|
pseudo.display,
|
|
15867
16117
|
pseudo.position,
|
|
16118
|
+
pseudo.inset && `inset ${pseudo.inset}`,
|
|
15868
16119
|
pseudo.size && `${pseudo.size.w}x${pseudo.size.h}`,
|
|
15869
16120
|
pseudo.color && `color ${pseudo.color}`,
|
|
15870
16121
|
pseudo.fontStyle,
|
|
@@ -15886,7 +16137,9 @@ function treeRow(node) {
|
|
|
15886
16137
|
return [
|
|
15887
16138
|
node.element,
|
|
15888
16139
|
node.count !== void 0 && `\xD7${node.count}`,
|
|
15889
|
-
`${node.w}x${node.h}`,
|
|
16140
|
+
node.contents ? "(contents)" : `${node.w}x${node.h}`,
|
|
16141
|
+
node.shadow && "(shadow root)",
|
|
16142
|
+
node.via && `via ${node.via}`,
|
|
15890
16143
|
node.layout && `[${node.layout}]`,
|
|
15891
16144
|
node.text && `"${node.text}"`,
|
|
15892
16145
|
node.childCount !== void 0 && `(${node.childCount})`
|
|
@@ -15908,7 +16161,7 @@ function ruleScope(entry) {
|
|
|
15908
16161
|
}
|
|
15909
16162
|
function cascadeBlock(data) {
|
|
15910
16163
|
const hints = (data.hints ?? []).map(
|
|
15911
|
-
(hint) => `${hint.property}: ${hint.value} ${hint.kind === "not-inherited" ? "is the browser's" : "has no effect"}: ${hint.reason} \u2192 ${hint.fix} \xB7 in ${hint.source}`
|
|
16164
|
+
(hint) => `${hint.property}: ${hint.value} ${hint.kind === "not-inherited" ? "is the browser's" : hint.only ? `has no effect on ${hint.only.join(", ")}` : "has no effect"}: ${hint.reason} \u2192 ${hint.fix} \xB7 in ${hint.source}`
|
|
15912
16165
|
);
|
|
15913
16166
|
return [
|
|
15914
16167
|
...labelledLines("hints", data.hints?.length === 0 ? ["none"] : hints),
|
|
@@ -15939,7 +16192,8 @@ function whyLines(why, data) {
|
|
|
15939
16192
|
truncateByLength(value, CASCADE_VALUE_WIDTH) + resolved,
|
|
15940
16193
|
entry.important ? " !important" : "",
|
|
15941
16194
|
` ${entry.source}${entry.specificity ? ` [${entry.specificity.join(",")}]` : ""}${ruleScope(entry)}`,
|
|
15942
|
-
entry.status === "inherited" ? " (inherited)" : ""
|
|
16195
|
+
entry.status === "inherited" ? " (inherited)" : "",
|
|
16196
|
+
entry.note ? ` (${entry.note})` : ""
|
|
15943
16197
|
].join("");
|
|
15944
16198
|
});
|
|
15945
16199
|
const variables = (why.variables ?? []).map(
|
|
@@ -15948,8 +16202,12 @@ function whyLines(why, data) {
|
|
|
15948
16202
|
return [
|
|
15949
16203
|
`${why.property} = ${why.computed}${midTransition(why.property, data.animating) ? ` ${inspectMidTransitionNote()}` : ""}`,
|
|
15950
16204
|
...entries.slice(0, 1),
|
|
16205
|
+
...why.chain[0]?.rule ? [` in ${truncateByLength(why.chain[0].rule, RULE_LINE_WIDTH)}`] : [],
|
|
15951
16206
|
...variables,
|
|
15952
16207
|
...entries.slice(1),
|
|
16208
|
+
...(why.inactive ?? []).map(
|
|
16209
|
+
(rule) => ` - ${truncateByLength(rule.value, CASCADE_VALUE_WIDTH)} ${rule.selector} ${rule.condition} (does not apply now)`
|
|
16210
|
+
),
|
|
15953
16211
|
...why.chain.length === 0 ? [" no author declaration: the default or inherited value"] : []
|
|
15954
16212
|
];
|
|
15955
16213
|
}
|
|
@@ -15961,6 +16219,7 @@ function midTransition(property, animating) {
|
|
|
15961
16219
|
function levelsUp(levels) {
|
|
15962
16220
|
return levels === 1 ? "the parent" : `${levels} levels up`;
|
|
15963
16221
|
}
|
|
16222
|
+
var RULE_LINE_WIDTH = 120;
|
|
15964
16223
|
var CASCADE_VALUE_WIDTH = 60;
|
|
15965
16224
|
function treeBlock(data) {
|
|
15966
16225
|
if (!data.children && !data.hiddenChildren) return [];
|
|
@@ -16005,8 +16264,16 @@ function groupLines(data) {
|
|
|
16005
16264
|
function formatInspect(data) {
|
|
16006
16265
|
const body = data.props ? propLines(data.props) : data.all ? allLines(data.all) : groupLines(data);
|
|
16007
16266
|
const note = data.picked ? multipleMatchesWarning(data.count, inspectedMatchAction(data.picked, data.index)) : void 0;
|
|
16008
|
-
|
|
16009
|
-
return joinLines(inspectHeader(data), ...
|
|
16267
|
+
const pseudoNote = data.pseudoOf && inspectPseudoOfNote(data.pseudoOf);
|
|
16268
|
+
if (data.why) return joinLines(inspectHeader(data), ...cascadeBlock(data), note, pseudoNote);
|
|
16269
|
+
return joinLines(
|
|
16270
|
+
inspectHeader(data),
|
|
16271
|
+
...body,
|
|
16272
|
+
...cascadeBlock(data),
|
|
16273
|
+
...treeBlock(data),
|
|
16274
|
+
note,
|
|
16275
|
+
pseudoNote
|
|
16276
|
+
);
|
|
16010
16277
|
}
|
|
16011
16278
|
|
|
16012
16279
|
// src/commands/dom/inspect.ts
|
|
@@ -16075,6 +16342,125 @@ async function inspectTarget(selectorOrIndex, options) {
|
|
|
16075
16342
|
});
|
|
16076
16343
|
}
|
|
16077
16344
|
|
|
16345
|
+
// src/commands/dom/audit.ts
|
|
16346
|
+
init_messages();
|
|
16347
|
+
init_client();
|
|
16348
|
+
|
|
16349
|
+
// src/ipc/protocol/auditTypes.ts
|
|
16350
|
+
var AUDIT_CHECKS = ["contrast", "overflow", "layers", "animations"];
|
|
16351
|
+
|
|
16352
|
+
// src/ui/formatters/audit.ts
|
|
16353
|
+
init_strings();
|
|
16354
|
+
var TEXT_WIDTH = 50;
|
|
16355
|
+
function formatAudit(data) {
|
|
16356
|
+
const sections = [
|
|
16357
|
+
data.contrast && contrastSection(data.contrast),
|
|
16358
|
+
data.overflow && overflowSection(data.overflow),
|
|
16359
|
+
data.layers && layersSection(data.layers),
|
|
16360
|
+
data.animations && animationsSection(data.animations)
|
|
16361
|
+
].filter((section2) => section2 !== void 0);
|
|
16362
|
+
const capped = data.capped ? [`(stopped after ${data.walked} elements; the page has more)`] : [];
|
|
16363
|
+
return [...sections.flatMap((lines) => [...lines, ""]), ...capped].join("\n").trimEnd();
|
|
16364
|
+
}
|
|
16365
|
+
function contrastSection(contrast) {
|
|
16366
|
+
const head = `Contrast (${contrast.level}): ${contrast.failing} of ${contrast.checked} text elements below`;
|
|
16367
|
+
const rows = contrast.items.map(
|
|
16368
|
+
(item) => ` ${item.ratio.toFixed(2).padStart(5)} ${item.color} on ${item.background} ${item.element} "${truncateByLength(item.text, TEXT_WIDTH)}" ${item.size}px${item.weight >= 700 ? " bold" : ""}${item.inView ? "" : " (out of view)"}${item.approximate ? ` (approximate: ${item.approximate.join(", ")})` : ""}`
|
|
16369
|
+
);
|
|
16370
|
+
const more = contrast.failing - contrast.items.length;
|
|
16371
|
+
return [head, ...rows, ...more > 0 ? [` (+${more} more; --limit to list them)`] : []];
|
|
16372
|
+
}
|
|
16373
|
+
function overflowSection(overflow) {
|
|
16374
|
+
const head = overflow.scrollsSideways ? `Overflow: the page is ${overflow.pageWidth} wide in a ${overflow.viewportWidth} viewport (it scrolls sideways)` : `Overflow: nothing makes the page scroll sideways (${overflow.viewportWidth} wide)`;
|
|
16375
|
+
return [
|
|
16376
|
+
head,
|
|
16377
|
+
...overflow.wide.map(
|
|
16378
|
+
(item) => ` past the right edge: ${item.element} (to ${item.right}, ${item.width} wide)`
|
|
16379
|
+
),
|
|
16380
|
+
...overflow.truncated.map(
|
|
16381
|
+
(item) => ` cut off (${item.kind}): ${item.element}${times(item.count)} "${truncateByLength(item.text, TEXT_WIDTH)}"`
|
|
16382
|
+
),
|
|
16383
|
+
...overflow.scrollers.map(
|
|
16384
|
+
(item) => ` scrolls sideways inside: ${item.element} (${item.scrollWidth} of content in ${item.width})`
|
|
16385
|
+
),
|
|
16386
|
+
...overflow.images.map(
|
|
16387
|
+
(image) => ` image ${[image.upscaled && `upscaled ${image.scale}x${overflow.pixelRatio > 1 ? ` at pixel ratio ${overflow.pixelRatio}` : ""}`, image.distorted && "distorted"].filter(Boolean).join(", ")}: ${image.element}${times(image.count)} ${image.natural.w}x${image.natural.h} pixels drawn at ${image.rendered.w}x${image.rendered.h} CSS px`
|
|
16388
|
+
)
|
|
16389
|
+
];
|
|
16390
|
+
}
|
|
16391
|
+
function times(count) {
|
|
16392
|
+
return count ? ` \xD7${count}` : "";
|
|
16393
|
+
}
|
|
16394
|
+
function layersSection(layers) {
|
|
16395
|
+
if (layers.length === 0) return ["Layers: no fixed or sticky elements"];
|
|
16396
|
+
return [
|
|
16397
|
+
`Layers: ${layers.length} fixed or sticky`,
|
|
16398
|
+
...layers.map(
|
|
16399
|
+
(layer) => ` ${layer.position} z ${layer.zIndex} ${layer.element} ${layer.rect.w}x${layer.rect.h} at ${layer.rect.x},${layer.rect.y} of the viewport${layer.inView ? "" : " (not in view now)"}`
|
|
16400
|
+
)
|
|
16401
|
+
];
|
|
16402
|
+
}
|
|
16403
|
+
function animationsSection(animations) {
|
|
16404
|
+
if (animations.length === 0) return ["Animations: none running"];
|
|
16405
|
+
return [
|
|
16406
|
+
`Animations: ${animations.length} running`,
|
|
16407
|
+
...animations.map(
|
|
16408
|
+
(animation) => ` ${animation.name} on ${animation.element}${times(animation.count)} (${animation.type}, ${typeof animation.duration === "number" ? `${animation.duration}ms` : animation.duration}, ${animation.iterations === "infinite" ? "infinite" : `${animation.iterations}x`}${animation.scrollDriven ? ", scroll-driven" : ""})`
|
|
16409
|
+
)
|
|
16410
|
+
];
|
|
16411
|
+
}
|
|
16412
|
+
function formatCssSearch(data) {
|
|
16413
|
+
if (data.total === 0) return `"${data.query}" is not in the page's ${data.sheets} stylesheets`;
|
|
16414
|
+
const more = data.total - data.matches.length;
|
|
16415
|
+
return [
|
|
16416
|
+
`"${data.query}": ${data.total} matches in ${data.sheets} stylesheets`,
|
|
16417
|
+
...data.matches.flatMap((match) => [` ${match.source}`, ` ${match.text}`]),
|
|
16418
|
+
...more > 0 ? [` (+${more} more; --limit to list them)`] : []
|
|
16419
|
+
].join("\n");
|
|
16420
|
+
}
|
|
16421
|
+
|
|
16422
|
+
// src/commands/dom/audit.ts
|
|
16423
|
+
init_commands();
|
|
16424
|
+
init_exitCodes();
|
|
16425
|
+
init_suggestions();
|
|
16426
|
+
function registerAuditCommand(dom) {
|
|
16427
|
+
dom.command("audit").description(
|
|
16428
|
+
"Page-wide checks: text below WCAG contrast, sideways scroll and cut-off text, scaled images, fixed/sticky layers, animations"
|
|
16429
|
+
).argument("[checks...]", `Checks to run: ${AUDIT_CHECKS.join(", ")} (default: all)`, checkList).option("--level <level>", "WCAG level text must reach: AA or AAA (default: AA)", levelOption).option("--limit <n>", "Findings listed per check (default: 20)", integerOption(1, 500)).addOption(jsonOption()).addHelpText("after", AUDIT_HELP_EXAMPLES).action(async (checks, options) => {
|
|
16430
|
+
await runCommand(() => audit(checks, options), options, formatAudit);
|
|
16431
|
+
});
|
|
16432
|
+
}
|
|
16433
|
+
function checkList(value, previous = []) {
|
|
16434
|
+
const check = AUDIT_CHECKS.find((name) => name === value.trim().toLowerCase());
|
|
16435
|
+
if (!check) {
|
|
16436
|
+
throw new InvalidArgumentError(
|
|
16437
|
+
unknownAuditCheckMessage(value, findSimilar(value, [...AUDIT_CHECKS]), AUDIT_CHECKS)
|
|
16438
|
+
);
|
|
16439
|
+
}
|
|
16440
|
+
return previous.includes(check) ? previous : [...previous, check];
|
|
16441
|
+
}
|
|
16442
|
+
function levelOption(value) {
|
|
16443
|
+
const level = value.trim().toUpperCase();
|
|
16444
|
+
if (level === "AA" || level === "AAA") return level;
|
|
16445
|
+
throw new InvalidArgumentError("Use AA or AAA");
|
|
16446
|
+
}
|
|
16447
|
+
async function audit(checks, options) {
|
|
16448
|
+
const response = await domAudit({
|
|
16449
|
+
checks: checks && checks.length > 0 ? checks : [...AUDIT_CHECKS],
|
|
16450
|
+
...options.level && { level: options.level },
|
|
16451
|
+
...options.limit !== void 0 && { limit: options.limit }
|
|
16452
|
+
});
|
|
16453
|
+
if (response.status === "error" || !response.data) {
|
|
16454
|
+
return {
|
|
16455
|
+
success: false,
|
|
16456
|
+
error: response.error ?? "Failed to audit the page",
|
|
16457
|
+
exitCode: response.exitCode ?? EXIT_CODES.CDP_CONNECTION_FAILURE,
|
|
16458
|
+
...response.suggestion && { errorContext: { suggestion: response.suggestion } }
|
|
16459
|
+
};
|
|
16460
|
+
}
|
|
16461
|
+
return { success: true, data: response.data };
|
|
16462
|
+
}
|
|
16463
|
+
|
|
16078
16464
|
// src/commands/dom/layout.ts
|
|
16079
16465
|
init_client();
|
|
16080
16466
|
|
|
@@ -16394,13 +16780,17 @@ function buildElementScreenshotOptions(options) {
|
|
|
16394
16780
|
return filterDefined({
|
|
16395
16781
|
format: options.format,
|
|
16396
16782
|
quality: options.quality,
|
|
16397
|
-
noResize: options.resize === false
|
|
16783
|
+
noResize: options.resize === false,
|
|
16784
|
+
padding: options.padding
|
|
16398
16785
|
});
|
|
16399
16786
|
}
|
|
16400
16787
|
function hasElementTarget(options) {
|
|
16401
16788
|
return options.selector !== void 0 || options.index !== void 0;
|
|
16402
16789
|
}
|
|
16403
16790
|
async function resolveElementNodeId(options) {
|
|
16791
|
+
if (options.selector !== void 0 && options.index !== void 0) {
|
|
16792
|
+
return { backendNodeId: await selectMatch(options.selector, options.index) };
|
|
16793
|
+
}
|
|
16404
16794
|
if (options.index !== void 0) {
|
|
16405
16795
|
const resolver = DomElementResolver.getInstance();
|
|
16406
16796
|
const node = await resolver.getNodeIdForIndex(options.index);
|
|
@@ -16535,8 +16925,8 @@ function reportSequenceError(error, captured, json) {
|
|
|
16535
16925
|
}
|
|
16536
16926
|
function assertScreenshotOptions(outputPath, options) {
|
|
16537
16927
|
let message;
|
|
16538
|
-
if (options.
|
|
16539
|
-
message =
|
|
16928
|
+
if (options.padding !== void 0 && !hasElementTarget(options)) {
|
|
16929
|
+
message = "--padding applies to element captures; name an element (selector or index)";
|
|
16540
16930
|
} else if (options.quality !== void 0 && !options.follow && resolveImageFormat(outputPath, options.format) === "png") {
|
|
16541
16931
|
message = "--quality applies to JPEG only; this screenshot is a PNG";
|
|
16542
16932
|
}
|
|
@@ -16586,7 +16976,7 @@ function registerWaitCommand(dom) {
|
|
|
16586
16976
|
"Wait until elements appear, become visible, contain a text or are gone (or the page loads)"
|
|
16587
16977
|
).argument(
|
|
16588
16978
|
"[selector]",
|
|
16589
|
-
"CSS selector (:has-text, :visible allowed; shadow DOM and same-origin iframes searched);
|
|
16979
|
+
"CSS selector (:has-text, :visible allowed; shadow DOM and same-origin iframes searched); without one, waits for the page to load (--load)"
|
|
16590
16980
|
).option("--text <text>", "A match must contain this text (case-insensitive)").option("--visible", "Only count visible matches").option("--gone", "Wait until no element matches (none visible, with --visible)").option("--load", "Also wait for the page to finish loading (document.readyState complete)").option(
|
|
16591
16981
|
"--timeout <ms>",
|
|
16592
16982
|
"Give up after this many milliseconds (exit 102)",
|
|
@@ -16597,7 +16987,8 @@ function registerWaitCommand(dom) {
|
|
|
16597
16987
|
});
|
|
16598
16988
|
}
|
|
16599
16989
|
async function waitFor(selector, options) {
|
|
16600
|
-
const
|
|
16990
|
+
const load = options.load === true || selector === void 0 && options.text === void 0 && !options.gone && !options.visible;
|
|
16991
|
+
const needsSelector = options.text !== void 0 || options.gone === true || !load;
|
|
16601
16992
|
if (selector === void 0 && needsSelector) {
|
|
16602
16993
|
const err = waitTargetRequiredError();
|
|
16603
16994
|
return {
|
|
@@ -16611,7 +17002,7 @@ async function waitFor(selector, options) {
|
|
|
16611
17002
|
...filterDefined({ selector, text: options.text }),
|
|
16612
17003
|
...options.gone && { gone: true },
|
|
16613
17004
|
...options.visible && { visible: true },
|
|
16614
|
-
...
|
|
17005
|
+
...load && { load: true },
|
|
16615
17006
|
timeout: options.timeout
|
|
16616
17007
|
});
|
|
16617
17008
|
if (response.status === "error" || !response.data) {
|
|
@@ -16635,6 +17026,7 @@ function registerDomCommands(program2) {
|
|
|
16635
17026
|
registerFormCommand(dom);
|
|
16636
17027
|
registerListenersCommand(dom);
|
|
16637
17028
|
registerLayoutCommand(dom);
|
|
17029
|
+
registerAuditCommand(dom);
|
|
16638
17030
|
registerInspectCommand(dom);
|
|
16639
17031
|
registerWaitCommand(dom);
|
|
16640
17032
|
dom.command("query").description("Find elements by CSS selector").argument("<selector>", 'CSS selector (e.g., ".error", "#app", "button")').option("-j, --json", "Output as JSON").addHelpText("after", SELECTOR_SCOPE_HELP).action(async (selector, options) => {
|
|
@@ -16669,8 +17061,12 @@ function registerDomCommands(program2) {
|
|
|
16669
17061
|
"Element to capture: CSS selector or index from a query (same as --selector / --index)"
|
|
16670
17062
|
).option("--selector <selector>", "CSS selector for element capture").option(
|
|
16671
17063
|
"--index <number>",
|
|
16672
|
-
"Cached element index (0-based) from previous query",
|
|
17064
|
+
"Cached element index (0-based) from a previous query; with --selector, which match",
|
|
16673
17065
|
integerOption(0)
|
|
17066
|
+
).option(
|
|
17067
|
+
"--padding <px>",
|
|
17068
|
+
"Element capture: extra space around it (shadows and focus rings are included anyway)",
|
|
17069
|
+
integerOption(0, 500)
|
|
16674
17070
|
).option(
|
|
16675
17071
|
"--format <format>",
|
|
16676
17072
|
"Image format: png or jpeg/jpg (default: from the file extension, else png)",
|
|
@@ -17527,7 +17923,7 @@ function appearanceLines(metadata, pageState) {
|
|
|
17527
17923
|
...viewport ? [
|
|
17528
17924
|
[
|
|
17529
17925
|
"Viewport",
|
|
17530
|
-
`${viewport.width}\xD7${viewport.height}${metadata.viewport ? ` (emulated ${metadata.viewport.width}x${metadata.viewport.height})` : ""}`
|
|
17926
|
+
`${viewport.width}\xD7${viewport.height}${metadata.viewport ? ` (emulated ${metadata.viewport.width}x${metadata.viewport.height}${metadata.viewport.mobile ? ", phone" : ""})` : ""}`
|
|
17531
17927
|
]
|
|
17532
17928
|
] : [],
|
|
17533
17929
|
...scheme ? [
|
|
@@ -18349,6 +18745,37 @@ function registerNetworkCommands(program2) {
|
|
|
18349
18745
|
registerDocumentCommand(networkCmd);
|
|
18350
18746
|
}
|
|
18351
18747
|
|
|
18748
|
+
// src/commands/css.ts
|
|
18749
|
+
init_client();
|
|
18750
|
+
init_commands();
|
|
18751
|
+
init_exitCodes();
|
|
18752
|
+
function registerCssCommands(program2) {
|
|
18753
|
+
const css = program2.command("css").description("The page's stylesheets: search <text>");
|
|
18754
|
+
css.command("search").description(
|
|
18755
|
+
"Find text in every stylesheet of the page (cross-origin ones too), with the rule and file:line"
|
|
18756
|
+
).argument(
|
|
18757
|
+
"<text>",
|
|
18758
|
+
"Text to find (case-insensitive), e.g. oklch( or .btn-primary; put -- before one that starts with - (-- --brand)"
|
|
18759
|
+
).option("--limit <n>", "Matches listed (default: 20)", integerOption(1, 500)).addOption(jsonOption()).addHelpText("after", CSS_SEARCH_HELP_EXAMPLES).action(async (text, options) => {
|
|
18760
|
+
await runCommand(() => search(text, options), options, formatCssSearch);
|
|
18761
|
+
});
|
|
18762
|
+
}
|
|
18763
|
+
async function search(text, options) {
|
|
18764
|
+
const response = await cssSearch({
|
|
18765
|
+
query: text,
|
|
18766
|
+
...options.limit !== void 0 && { limit: options.limit }
|
|
18767
|
+
});
|
|
18768
|
+
if (response.status === "error" || !response.data) {
|
|
18769
|
+
return {
|
|
18770
|
+
success: false,
|
|
18771
|
+
error: response.error ?? "Failed to search the stylesheets",
|
|
18772
|
+
exitCode: response.exitCode ?? EXIT_CODES.CDP_CONNECTION_FAILURE,
|
|
18773
|
+
...response.suggestion && { errorContext: { suggestion: response.suggestion } }
|
|
18774
|
+
};
|
|
18775
|
+
}
|
|
18776
|
+
return { success: true, data: response.data };
|
|
18777
|
+
}
|
|
18778
|
+
|
|
18352
18779
|
// src/commands/start.ts
|
|
18353
18780
|
import fs15 from "node:fs";
|
|
18354
18781
|
import os3 from "node:os";
|
|
@@ -18822,8 +19249,17 @@ function applyCollectorOptions(command) {
|
|
|
18822
19249
|
).option(
|
|
18823
19250
|
"--color-scheme <scheme>",
|
|
18824
19251
|
"Emulate prefers-color-scheme for the session: light or dark (default: the system setting)"
|
|
19252
|
+
).option(
|
|
19253
|
+
"--mobile",
|
|
19254
|
+
`Emulate a phone for the session: mobile viewport (${MOBILE_VIEWPORT.width}x${MOBILE_VIEWPORT.height} unless --viewport), touch, mobile user agent`
|
|
18825
19255
|
);
|
|
18826
19256
|
}
|
|
19257
|
+
var MOBILE_VIEWPORT = { width: 390, height: 844 };
|
|
19258
|
+
function requestedViewport(viewport, mobile) {
|
|
19259
|
+
const size = viewport !== void 0 ? parseViewport(viewport) : void 0;
|
|
19260
|
+
if (!mobile) return size;
|
|
19261
|
+
return { ...size ?? MOBILE_VIEWPORT, mobile: true };
|
|
19262
|
+
}
|
|
18827
19263
|
var MAX_VIEWPORT_SIDE = 1e4;
|
|
18828
19264
|
var COLOR_SCHEMES = ["light", "dark"];
|
|
18829
19265
|
function parseViewport(value) {
|
|
@@ -18871,7 +19307,7 @@ function buildSessionOptions(options) {
|
|
|
18871
19307
|
quiet: options.quiet ?? false,
|
|
18872
19308
|
json: options.json ?? false,
|
|
18873
19309
|
chromeFlags,
|
|
18874
|
-
viewport: options.viewport
|
|
19310
|
+
viewport: requestedViewport(options.viewport, options.mobile),
|
|
18875
19311
|
colorScheme: options.colorScheme !== void 0 ? parseColorScheme(options.colorScheme) : void 0
|
|
18876
19312
|
};
|
|
18877
19313
|
}
|
|
@@ -19123,7 +19559,7 @@ async function showPageInfo(options) {
|
|
|
19123
19559
|
}
|
|
19124
19560
|
function emulationRequest(options) {
|
|
19125
19561
|
if (options.reset) return { reset: true };
|
|
19126
|
-
if (options.viewport === void 0 && options.colorScheme === void 0) {
|
|
19562
|
+
if (options.viewport === void 0 && options.colorScheme === void 0 && !options.mobile) {
|
|
19127
19563
|
const err = pageEmulateNothingError();
|
|
19128
19564
|
throw new CommandError(
|
|
19129
19565
|
err.message,
|
|
@@ -19131,8 +19567,9 @@ function emulationRequest(options) {
|
|
|
19131
19567
|
EXIT_CODES.INVALID_ARGUMENTS
|
|
19132
19568
|
);
|
|
19133
19569
|
}
|
|
19570
|
+
const viewport = requestedViewport(options.viewport, options.mobile);
|
|
19134
19571
|
return {
|
|
19135
|
-
...
|
|
19572
|
+
...viewport && { viewport },
|
|
19136
19573
|
...options.colorScheme !== void 0 && {
|
|
19137
19574
|
colorScheme: parseColorScheme(options.colorScheme)
|
|
19138
19575
|
}
|
|
@@ -19169,10 +19606,17 @@ function registerPageCommands(program2) {
|
|
|
19169
19606
|
).action(async (url, options) => {
|
|
19170
19607
|
await runPageAction("navigate", options, url);
|
|
19171
19608
|
});
|
|
19172
|
-
page.command("emulate").description(PAGE_EMULATE_DESCRIPTION).option(
|
|
19609
|
+
page.command("emulate").description(PAGE_EMULATE_DESCRIPTION).option(
|
|
19610
|
+
"--viewport <WxH>",
|
|
19611
|
+
"Viewport size in CSS px, e.g. 900x700 (a desktop one unless --mobile)"
|
|
19612
|
+
).option("--color-scheme <scheme>", "Emulate prefers-color-scheme: light or dark").option(
|
|
19613
|
+
"--mobile",
|
|
19614
|
+
"Emulate a phone: mobile viewport (390x844 unless --viewport), touch, mobile user agent"
|
|
19615
|
+
).addOption(
|
|
19173
19616
|
new Option("--reset", "Back to the browser window size and the system setting").conflicts([
|
|
19174
19617
|
"viewport",
|
|
19175
|
-
"colorScheme"
|
|
19618
|
+
"colorScheme",
|
|
19619
|
+
"mobile"
|
|
19176
19620
|
])
|
|
19177
19621
|
).addOption(jsonOption()).action(async (options) => {
|
|
19178
19622
|
await emulate(options);
|
|
@@ -19187,6 +19631,9 @@ function registerPageCommands(program2) {
|
|
|
19187
19631
|
}
|
|
19188
19632
|
|
|
19189
19633
|
// src/commands/peek.ts
|
|
19634
|
+
init_errors3();
|
|
19635
|
+
init_messages();
|
|
19636
|
+
init_exitCodes();
|
|
19190
19637
|
function parseOptions(options) {
|
|
19191
19638
|
const lastN = positiveIntRule({
|
|
19192
19639
|
name: "--last",
|
|
@@ -19196,7 +19643,21 @@ function parseOptions(options) {
|
|
|
19196
19643
|
allowZeroForAll: true
|
|
19197
19644
|
}).validate(options.last);
|
|
19198
19645
|
const resourceTypes = resourceTypeRule().validate(options.type);
|
|
19199
|
-
|
|
19646
|
+
if (options.interval !== void 0 && !options.follow) {
|
|
19647
|
+
const err = intervalWithoutFollowError();
|
|
19648
|
+
throw new CommandError(
|
|
19649
|
+
err.message,
|
|
19650
|
+
{ suggestion: err.suggestion },
|
|
19651
|
+
EXIT_CODES.INVALID_ARGUMENTS
|
|
19652
|
+
);
|
|
19653
|
+
}
|
|
19654
|
+
const interval = positiveIntRule({
|
|
19655
|
+
name: "--interval",
|
|
19656
|
+
min: 100,
|
|
19657
|
+
max: 6e4,
|
|
19658
|
+
default: 1e3
|
|
19659
|
+
}).validate(options.interval);
|
|
19660
|
+
return { lastN, resourceTypes, interval };
|
|
19200
19661
|
}
|
|
19201
19662
|
function peekSection(options) {
|
|
19202
19663
|
if (options.network) return "network";
|
|
@@ -19230,11 +19691,12 @@ function createPreviewOptions(base, resourceTypes, unfilteredCount) {
|
|
|
19230
19691
|
}
|
|
19231
19692
|
return options;
|
|
19232
19693
|
}
|
|
19233
|
-
async function runFollowMode3(options,
|
|
19694
|
+
async function runFollowMode3(options, parsed, baseOptions) {
|
|
19695
|
+
const { lastN, resourceTypes, interval } = parsed;
|
|
19234
19696
|
const showPreview = async () => {
|
|
19235
19697
|
const result = await fetchAndFilterPreview(lastN, resourceTypes, peekSection(options));
|
|
19236
19698
|
if (!result.success) {
|
|
19237
|
-
return followFetchFailure(result, { json: options.json, retryIntervalMs:
|
|
19699
|
+
return followFetchFailure(result, { json: options.json, retryIntervalMs: interval });
|
|
19238
19700
|
}
|
|
19239
19701
|
noteFollowConnected();
|
|
19240
19702
|
if (!options.json) console.clear();
|
|
@@ -19249,11 +19711,34 @@ async function runFollowMode3(options, lastN, resourceTypes, baseOptions) {
|
|
|
19249
19711
|
await setupFollowMode(showPreview, {
|
|
19250
19712
|
startMessage: followingPreviewMessage,
|
|
19251
19713
|
stopMessage: stoppedFollowingPreviewMessage,
|
|
19252
|
-
intervalMs:
|
|
19714
|
+
intervalMs: interval
|
|
19253
19715
|
});
|
|
19254
19716
|
}
|
|
19717
|
+
function parsePeekOptions(options) {
|
|
19718
|
+
try {
|
|
19719
|
+
return parseOptions(options);
|
|
19720
|
+
} catch (error) {
|
|
19721
|
+
handleValidationError(error, options.json ?? false);
|
|
19722
|
+
}
|
|
19723
|
+
}
|
|
19724
|
+
function previewDisplayOptions(options, lastN) {
|
|
19725
|
+
return {
|
|
19726
|
+
json: options.json,
|
|
19727
|
+
network: options.network,
|
|
19728
|
+
console: options.console,
|
|
19729
|
+
last: lastN,
|
|
19730
|
+
verbose: options.verbose,
|
|
19731
|
+
follow: options.follow
|
|
19732
|
+
};
|
|
19733
|
+
}
|
|
19734
|
+
async function followPreview(options) {
|
|
19735
|
+
const following = { ...options, follow: true };
|
|
19736
|
+
showBothSectionsWhenBothRequested(following);
|
|
19737
|
+
const parsed = parsePeekOptions(following);
|
|
19738
|
+
await runFollowMode3(following, parsed, previewDisplayOptions(following, parsed.lastN));
|
|
19739
|
+
}
|
|
19255
19740
|
function registerPeekCommand(program2) {
|
|
19256
|
-
program2.command("peek").description("Preview collected data without stopping the session").addOption(jsonOption()).option("-v, --verbose", "Use verbose output with full URLs and formatting", false).option("-n, --network", "Show only network requests", false).option("-c, --console", "Show only console messages", false).option("-f, --follow", "Watch for updates (like tail -f)", false).option("--last <count>", "Show last N items, 0 for all", "10").option(
|
|
19741
|
+
program2.command("peek").description("Preview collected data without stopping the session").addOption(jsonOption()).option("-v, --verbose", "Use verbose output with full URLs and formatting", false).option("-n, --network", "Show only network requests", false).option("-c, --console", "Show only console messages", false).option("-f, --follow", "Watch for updates (like tail -f)", false).option("--interval <ms>", "Refresh interval of --follow in ms, 100-60000 (default: 1000)").option("--last <count>", "Show last N items, 0 for all", "10").option(
|
|
19257
19742
|
"--type <types>",
|
|
19258
19743
|
"Filter network requests by resource type (comma-separated: Document,XHR,Fetch,etc.)"
|
|
19259
19744
|
).action(async (options) => {
|
|
@@ -19263,25 +19748,11 @@ function registerPeekCommand(program2) {
|
|
|
19263
19748
|
'Note: "bdg peek --network" is deprecated. Use "bdg network list" for enhanced filtering.'
|
|
19264
19749
|
);
|
|
19265
19750
|
}
|
|
19266
|
-
|
|
19267
|
-
|
|
19268
|
-
|
|
19269
|
-
const parsed = parseOptions(options);
|
|
19270
|
-
lastN = parsed.lastN;
|
|
19271
|
-
resourceTypes = parsed.resourceTypes;
|
|
19272
|
-
} catch (error) {
|
|
19273
|
-
handleValidationError(error, options.json ?? false);
|
|
19274
|
-
}
|
|
19275
|
-
const baseOptions = {
|
|
19276
|
-
json: options.json,
|
|
19277
|
-
network: options.network,
|
|
19278
|
-
console: options.console,
|
|
19279
|
-
last: lastN,
|
|
19280
|
-
verbose: options.verbose,
|
|
19281
|
-
follow: options.follow
|
|
19282
|
-
};
|
|
19751
|
+
const parsed = parsePeekOptions(options);
|
|
19752
|
+
const { lastN, resourceTypes } = parsed;
|
|
19753
|
+
const baseOptions = previewDisplayOptions(options, lastN);
|
|
19283
19754
|
if (options.follow) {
|
|
19284
|
-
await runFollowMode3(options,
|
|
19755
|
+
await runFollowMode3(options, parsed, baseOptions);
|
|
19285
19756
|
return;
|
|
19286
19757
|
}
|
|
19287
19758
|
await runCommand(
|
|
@@ -19750,62 +20221,14 @@ function registerStopCommand(program2) {
|
|
|
19750
20221
|
}
|
|
19751
20222
|
|
|
19752
20223
|
// src/commands/tail.ts
|
|
19753
|
-
function parseOptions2(options) {
|
|
19754
|
-
const lastRule = positiveIntRule({
|
|
19755
|
-
name: "--last",
|
|
19756
|
-
min: 1,
|
|
19757
|
-
max: MAX_LAST_ITEMS,
|
|
19758
|
-
default: 10,
|
|
19759
|
-
allowZeroForAll: true
|
|
19760
|
-
});
|
|
19761
|
-
const intervalRule = positiveIntRule({ name: "--interval", min: 100, max: 6e4, default: 1e3 });
|
|
19762
|
-
return {
|
|
19763
|
-
lastN: lastRule.validate(options.last),
|
|
19764
|
-
interval: intervalRule.validate(options.interval)
|
|
19765
|
-
};
|
|
19766
|
-
}
|
|
19767
|
-
function createPreviewOptions2(options, lastN) {
|
|
19768
|
-
return {
|
|
19769
|
-
json: options.json,
|
|
19770
|
-
network: options.network,
|
|
19771
|
-
console: options.console,
|
|
19772
|
-
last: lastN,
|
|
19773
|
-
verbose: options.verbose,
|
|
19774
|
-
follow: true,
|
|
19775
|
-
viewedAt: /* @__PURE__ */ new Date()
|
|
19776
|
-
};
|
|
19777
|
-
}
|
|
19778
20224
|
function registerTailCommand(program2) {
|
|
19779
|
-
program2.command("tail").description("
|
|
20225
|
+
program2.command("tail").description('Deprecated: use "bdg peek --follow" (same options)').addOption(jsonOption()).option("-v, --verbose", "Use verbose output with full URLs and formatting", false).option("-n, --network", "Show only network requests", false).option("-c, --console", "Show only console messages", false).option(
|
|
19780
20226
|
"--last <count>",
|
|
19781
20227
|
"Show last N items (network requests + console messages), 0 for all",
|
|
19782
20228
|
"10"
|
|
19783
20229
|
).option("--interval <ms>", "Update interval in milliseconds", "1000").action(async (options) => {
|
|
19784
|
-
|
|
19785
|
-
|
|
19786
|
-
let interval;
|
|
19787
|
-
try {
|
|
19788
|
-
const parsed = parseOptions2(options);
|
|
19789
|
-
lastN = parsed.lastN;
|
|
19790
|
-
interval = parsed.interval;
|
|
19791
|
-
} catch (error) {
|
|
19792
|
-
handleValidationError(error, options.json ?? false);
|
|
19793
|
-
}
|
|
19794
|
-
const showPreview = async () => {
|
|
19795
|
-
const result = await fetchPreviewOutput({ lastN });
|
|
19796
|
-
if (!result.success) {
|
|
19797
|
-
return followFetchFailure(result, { json: options.json, retryIntervalMs: interval });
|
|
19798
|
-
}
|
|
19799
|
-
noteFollowConnected();
|
|
19800
|
-
if (!options.json) console.clear();
|
|
19801
|
-
console.log(formatPreview(result.data, createPreviewOptions2(options, lastN)));
|
|
19802
|
-
return void 0;
|
|
19803
|
-
};
|
|
19804
|
-
await setupFollowMode(showPreview, {
|
|
19805
|
-
startMessage: followingPreviewMessage,
|
|
19806
|
-
stopMessage: stoppedFollowingPreviewMessage,
|
|
19807
|
-
intervalMs: interval
|
|
19808
|
-
});
|
|
20230
|
+
console.error(tailDeprecatedNotice());
|
|
20231
|
+
await followPreview({ ...options, follow: true });
|
|
19809
20232
|
});
|
|
19810
20233
|
}
|
|
19811
20234
|
|
|
@@ -19829,6 +20252,7 @@ var commandRegistry = [
|
|
|
19829
20252
|
registerDomCommands,
|
|
19830
20253
|
registerFormInteractionCommands,
|
|
19831
20254
|
registerPageCommands,
|
|
20255
|
+
registerCssCommands,
|
|
19832
20256
|
addCommandGroup("CDP Commands:"),
|
|
19833
20257
|
registerCdpCommand,
|
|
19834
20258
|
addCommandGroup("Network Commands:"),
|