browser-debugger-cli 0.9.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -1
- package/dist/commands/dom/a11y.js +2 -1
- package/dist/commands/dom/formInteraction.js +56 -25
- package/dist/commands/dom/helpers/keyAttributes.d.ts +20 -0
- package/dist/commands/dom/helpers/keyAttributes.js +54 -0
- package/dist/commands/dom/helpers/query.d.ts +1 -1
- package/dist/commands/dom/helpers/query.js +66 -19
- package/dist/commands/dom/helpers/runElementCommand.js +4 -3
- package/dist/commands/dom/helpers/screenshot.js +85 -12
- package/dist/commands/dom/index.d.ts +1 -0
- package/dist/commands/dom/index.js +8 -3
- package/dist/commands/dom/inspect.d.ts +15 -0
- package/dist/commands/dom/inspect.js +82 -0
- package/dist/commands/dom/layout.js +2 -2
- package/dist/commands/dom/listeners.js +2 -2
- package/dist/commands/dom/semanticUtils.d.ts +14 -1
- package/dist/commands/dom/semanticUtils.js +44 -3
- package/dist/commands/network/list.js +13 -2
- package/dist/commands/optionBehaviors.js +48 -6
- package/dist/commands/page.d.ts +1 -1
- package/dist/commands/page.js +62 -3
- package/dist/commands/shared/commonOptions.d.ts +4 -0
- package/dist/commands/shared/commonOptions.js +9 -0
- package/dist/commands/shared/optionTypes.d.ts +21 -0
- package/dist/commands/shared/startHelpers.d.ts +66 -0
- package/dist/commands/shared/startHelpers.js +91 -10
- package/dist/commands/shared/validation.d.ts +11 -0
- package/dist/commands/shared/validation.js +16 -0
- package/dist/daemon/launcher.d.ts +8 -1
- package/dist/daemon/launcher.js +3 -1
- package/dist/daemon/session/Session.d.ts +7 -0
- package/dist/daemon/session/Session.js +23 -1
- package/dist/daemon/session/commandRegistry.d.ts +14 -1
- package/dist/daemon/session/commandRegistry.js +65 -9
- package/dist/daemon/session/interactions.d.ts +18 -5
- package/dist/daemon/session/interactions.js +22 -12
- package/dist/daemon.js +3565 -329
- package/dist/errors/messages.d.ts +69 -0
- package/dist/errors/messages.js +102 -1
- package/dist/index.js +2416 -1320
- package/dist/ipc/client.d.ts +9 -0
- package/dist/ipc/client.js +13 -0
- package/dist/ipc/protocol/commands.d.ts +56 -1
- package/dist/ipc/protocol/commands.js +2 -0
- package/dist/ipc/protocol/domTypes.d.ts +35 -2
- package/dist/ipc/protocol/inspectTypes.d.ts +388 -0
- package/dist/ipc/protocol/inspectTypes.js +10 -0
- package/dist/runtime/dom/actionEffects.d.ts +94 -15
- package/dist/runtime/dom/actionEffects.js +173 -27
- package/dist/runtime/dom/actionEffectsScripts.d.ts +52 -14
- package/dist/runtime/dom/actionEffectsScripts.js +224 -32
- package/dist/runtime/dom/elementInfo.d.ts +26 -0
- package/dist/runtime/dom/elementInfo.js +65 -0
- package/dist/runtime/dom/eventListeners.js +14 -4
- package/dist/runtime/dom/formFillHelpers/fill.d.ts +3 -4
- package/dist/runtime/dom/formFillHelpers/fill.js +77 -28
- package/dist/runtime/dom/frameSelection.d.ts +11 -0
- package/dist/runtime/dom/frameSelection.js +20 -1
- package/dist/runtime/dom/frames.d.ts +38 -5
- package/dist/runtime/dom/frames.js +136 -21
- package/dist/runtime/dom/inspect.d.ts +28 -0
- package/dist/runtime/dom/inspect.js +557 -0
- package/dist/runtime/dom/inspectAllStyles.d.ts +62 -0
- package/dist/runtime/dom/inspectAllStyles.js +385 -0
- package/dist/runtime/dom/inspectCascade.d.ts +94 -0
- package/dist/runtime/dom/inspectCascade.js +371 -0
- package/dist/runtime/dom/inspectCascadeModel.d.ts +39 -0
- package/dist/runtime/dom/inspectCascadeModel.js +232 -0
- package/dist/runtime/dom/inspectHints.d.ts +62 -0
- package/dist/runtime/dom/inspectHints.js +305 -0
- package/dist/runtime/dom/inspectLayoutModel.d.ts +87 -0
- package/dist/runtime/dom/inspectLayoutModel.js +346 -0
- package/dist/runtime/dom/inspectModel.d.ts +74 -0
- package/dist/runtime/dom/inspectModel.js +184 -0
- package/dist/runtime/dom/inspectPaintModel.d.ts +157 -0
- package/dist/runtime/dom/inspectPaintModel.js +461 -0
- package/dist/runtime/dom/inspectRules.d.ts +37 -0
- package/dist/runtime/dom/inspectRules.js +101 -0
- package/dist/runtime/dom/inspectScripts.d.ts +132 -0
- package/dist/runtime/dom/inspectScripts.js +263 -0
- package/dist/runtime/dom/inspectTree.d.ts +40 -0
- package/dist/runtime/dom/inspectTree.js +134 -0
- package/dist/runtime/dom/inspectVariables.d.ts +33 -0
- package/dist/runtime/dom/inspectVariables.js +94 -0
- package/dist/runtime/dom/inspectWhyModel.d.ts +20 -0
- package/dist/runtime/dom/inspectWhyModel.js +134 -0
- package/dist/runtime/dom/layout.d.ts +5 -1
- package/dist/runtime/dom/layout.js +10 -3
- package/dist/runtime/dom/listenerPageScripts.d.ts +11 -5
- package/dist/runtime/dom/listenerPageScripts.js +95 -9
- package/dist/runtime/dom/listenerSummary.d.ts +4 -0
- package/dist/runtime/dom/listenerSummary.js +26 -9
- package/dist/runtime/dom/reactEventHelpers.d.ts +5 -0
- package/dist/runtime/dom/reactEventHelpers.js +12 -4
- package/dist/runtime/page/emulation.d.ts +20 -0
- package/dist/runtime/page/emulation.js +37 -0
- package/dist/telemetry/a11y.d.ts +10 -0
- package/dist/telemetry/a11y.js +78 -1
- package/dist/telemetry/console.d.ts +1 -0
- package/dist/telemetry/console.js +100 -5
- package/dist/telemetry/network.js +3 -1
- package/dist/types.d.ts +32 -0
- package/dist/ui/formatters/details.d.ts +8 -0
- package/dist/ui/formatters/details.js +59 -3
- package/dist/ui/formatters/dom.d.ts +2 -1
- package/dist/ui/formatters/dom.js +25 -9
- package/dist/ui/formatters/inspect.d.ts +39 -0
- package/dist/ui/formatters/inspect.js +596 -0
- package/dist/ui/formatters/keyAttributes.d.ts +19 -0
- package/dist/ui/formatters/keyAttributes.js +84 -0
- package/dist/ui/formatters/layout.js +2 -2
- package/dist/ui/formatters/networkHeaders.d.ts +13 -0
- package/dist/ui/formatters/networkHeaders.js +23 -3
- package/dist/ui/formatters/networkList.d.ts +29 -1
- package/dist/ui/formatters/networkList.js +86 -20
- package/dist/ui/formatters/status.js +1 -1
- package/dist/ui/formatting.d.ts +9 -0
- package/dist/ui/formatting.js +6 -3
- package/dist/ui/messages/commands.d.ts +123 -7
- package/dist/ui/messages/commands.js +181 -10
- package/dist/ui/messages/networkMessages.d.ts +14 -0
- package/dist/ui/messages/networkMessages.js +18 -0
- package/dist/ui/messages/session.d.ts +14 -0
- package/dist/ui/messages/session.js +20 -0
- package/dist/utils/async.d.ts +9 -0
- package/dist/utils/async.js +17 -0
- package/dist/utils/color.d.ts +84 -0
- package/dist/utils/color.js +376 -0
- package/dist/utils/cssValues.d.ts +109 -0
- package/dist/utils/cssValues.js +236 -0
- package/dist/utils/selectorFilters.d.ts +12 -0
- package/dist/utils/selectorFilters.js +29 -0
- package/package.json +1 -1
|
@@ -11,6 +11,10 @@ import { MAX_EDGE_PX, PIXELS_PER_TOKEN, TALL_PAGE_THRESHOLD, } from './dom/scree
|
|
|
11
11
|
const 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';
|
|
12
12
|
/** What every DOM action reports about the page besides its requests */
|
|
13
13
|
const 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: "…" (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';
|
|
14
|
+
/** What click and pressKey report when the page was still changing as they returned */
|
|
15
|
+
const 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';
|
|
16
|
+
/** What hover and pressKey report about elements they showed */
|
|
17
|
+
const 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';
|
|
14
18
|
/** What `--no-wait` does to a DOM action's triggered requests */
|
|
15
19
|
const 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)';
|
|
16
20
|
/**
|
|
@@ -76,7 +80,7 @@ const OPTION_BEHAVIORS = {
|
|
|
76
80
|
'eval:--frame': {
|
|
77
81
|
default: "Evaluates in the page's main frame",
|
|
78
82
|
whenEnabled: "Evaluates in one iframe's main world (its own globals), including cross-origin (out-of-process) iframes; output gains a frame field (its URL)",
|
|
79
|
-
automaticBehavior: 'The value is matched as: a 0-based index (bdg dom frames order, main page not counted), else an exact name/id attribute, else a case-insensitive part of the name, id or URL. Several matches fail with 81 listing them; none fails with 83 listing all frames. Frames are looked up on every call (a reloaded iframe is found again).',
|
|
83
|
+
automaticBehavior: 'The value is matched as: a 0-based index (bdg dom frames order: document order of the <iframe> elements, nested ones depth-first, main page not counted), else an exact name/id attribute, else a case-insensitive part of the name, id or URL. Several matches fail with 81 listing them; none fails with 83 listing all frames. Frames are looked up on every call (a reloaded iframe is found again). An index that names another frame than in the last bdg dom frames listing (iframes added, removed or moved, or the page navigated) fails with 87 STALE_CACHE: re-run bdg dom frames or pick the frame by name.',
|
|
80
84
|
},
|
|
81
85
|
'console:-H': {
|
|
82
86
|
default: 'Shows messages from current page load only (most recent navigation)',
|
|
@@ -118,7 +122,7 @@ const OPTION_BEHAVIORS = {
|
|
|
118
122
|
'click:--no-wait': {
|
|
119
123
|
default: 'Waits for network stability after click (150ms idle, up to 2s)',
|
|
120
124
|
whenDisabled: NO_WAIT_TRIGGERED_REQUESTS,
|
|
121
|
-
automaticBehavior: `Network wait helps ensure AJAX requests triggered by click complete. ${TRIGGERED_REQUESTS_BEHAVIOR}. The click itself uses real mouse events in the visible part of the element (method "mouse"); if the element is covered or has no size it falls back to DOM events (method "dom", with a warning). Results the page shows later
|
|
125
|
+
automaticBehavior: `Network wait helps ensure AJAX requests triggered by click complete. ${TRIGGERED_REQUESTS_BEHAVIOR}. The click itself uses real mouse events in the visible part of the element (method "mouse"); if the element is covered or has no size it falls back to DOM events (method "dom", with a warning; --strict refuses instead). Results the page shows later (timers, spinners, slow renders) are not waited for but reported as pending work: use bdg dom wait <selector> --visible. ${ACTION_EFFECTS_BEHAVIOR}. ${STILL_CHANGING_BEHAVIOR}. A click with no DOM change, no request and no navigation (checked again 300 ms later, which adds 300 ms plus at most 250 ms for the read) is reported as ⚠ Element Clicked (no visible effect observed: no DOM change, requests or navigation within 300 ms) and effect: "none" in JSON (exit code stays 0); not claimed with --no-wait, for hover or right-click, after a copy or cut, or when the click hit a form control, label, media, iframe, popover button, a mailto:/tel:/javascript: or other non-http link, a link to another window or a custom element with a closed shadow root, or moved focus to an element that is not a button or link. Effects outside the DOM (CSS :hover/:focus-within styles, canvas, clipboard without a copy event) are not seen`,
|
|
122
126
|
tokenImpact: 'A click that navigates lists the whole page load in JSON triggeredRequests',
|
|
123
127
|
},
|
|
124
128
|
'click:--double': {
|
|
@@ -129,10 +133,19 @@ const OPTION_BEHAVIORS = {
|
|
|
129
133
|
default: 'Left click',
|
|
130
134
|
whenEnabled: 'Right-click: the page gets contextmenu (custom context menus open); cannot be combined with --double',
|
|
131
135
|
},
|
|
136
|
+
'click:--strict': {
|
|
137
|
+
default: 'A covered, hidden or zero-size element is clicked with DOM events (method "dom", with a warning, exit 0)',
|
|
138
|
+
whenEnabled: 'Refuses with exit 90 (RESOURCE_CONFLICT: the page state blocks the request) when a real mouse cannot reach the element, naming what covers it and suggesting bdg dom layout <selector>; also when the mouse press never reached the element (it is released, no further presses for --double)',
|
|
139
|
+
automaticBehavior: 'Applies to --double and --right too. Nothing is dispatched when the element is unreachable, so the page is unchanged',
|
|
140
|
+
},
|
|
132
141
|
'hover:--no-wait': {
|
|
133
142
|
default: 'Waits for network stability after moving the mouse (menus may load content)',
|
|
134
143
|
whenDisabled: NO_WAIT_TRIGGERED_REQUESTS,
|
|
135
|
-
automaticBehavior: `The mouse stays over the element afterwards, so hover menus stay open until the next mouse action. ${TRIGGERED_REQUESTS_BEHAVIOR}. ${ACTION_EFFECTS_BEHAVIOR}`,
|
|
144
|
+
automaticBehavior: `The mouse stays over the element afterwards, so hover menus stay open until the next mouse action. ${TRIGGERED_REQUESTS_BEHAVIOR}. ${ACTION_EFFECTS_BEHAVIOR}. ${SHOWN_BEHAVIOR}; for a hover also elements around it (its parent's subtree) and tooltips, menus, listboxes, dialogs and popovers anywhere that were hidden before, so captions shown by CSS :hover count (hidden elements noted by identity right before the mouse moves: up to 1500, within 8 ms). A hover never claims "no visible effect" and does not check whether the page was still changing`,
|
|
145
|
+
},
|
|
146
|
+
'hover:--strict': {
|
|
147
|
+
default: 'A covered, hidden or zero-size element gets synthetic mouseover/mouseenter events (method "dom", with a warning)',
|
|
148
|
+
whenEnabled: 'Refuses with exit 90 when a real mouse cannot reach the element, naming what covers it and suggesting bdg dom layout <selector>',
|
|
136
149
|
},
|
|
137
150
|
'navigate:--no-wait': {
|
|
138
151
|
default: 'Waits until the new page has loaded and the network and DOM are idle (up to 15 s)',
|
|
@@ -142,7 +155,7 @@ const OPTION_BEHAVIORS = {
|
|
|
142
155
|
'pressKey:--no-wait': {
|
|
143
156
|
default: 'Waits for network stability after key press (150ms idle, up to 2s)',
|
|
144
157
|
whenDisabled: NO_WAIT_TRIGGERED_REQUESTS,
|
|
145
|
-
automaticBehavior: `${TRIGGERED_REQUESTS_BEHAVIOR}. ${ACTION_EFFECTS_BEHAVIOR}`,
|
|
158
|
+
automaticBehavior: `${TRIGGERED_REQUESTS_BEHAVIOR}. ${ACTION_EFFECTS_BEHAVIOR}. ${SHOWN_BEHAVIOR}, such as the item Enter added to a list. ${STILL_CHANGING_BEHAVIOR}. A key press never claims "no visible effect"`,
|
|
146
159
|
},
|
|
147
160
|
'pressKey:--times': {
|
|
148
161
|
default: 'Presses key once',
|
|
@@ -187,7 +200,7 @@ const OPTION_BEHAVIORS = {
|
|
|
187
200
|
'listeners:--type': {
|
|
188
201
|
default: 'Lists listeners of every event type on the element, its ancestors (through open shadow roots), its document and window',
|
|
189
202
|
whenEnabled: 'Lists only these event types (comma-separated or repeated, case-sensitive like addEventListener); when none match, typeSuggestions names close types (Click, onclick → click)',
|
|
190
|
-
automaticBehavior:
|
|
203
|
+
automaticBehavior: 'Listeners are grouped by type, the element\'s own handlers first (types handled on the element before delegated ones; nearest first, no-ops last); JSON line/column numbers are 0-based (human output shows them 1-based like DevTools). React\'s on… props that run for the element (its own and its React parents\', through portals and from a nested root into the outer root; onFocus/onBlur as focusin/focusout; parents\' props for non-bubbling events left out) are listed with source and location (framework: "React", reactProp: "onClick"; at most 50 of the requested types, reactHandlersSkipped counts the rest). jQuery handlers are shown instead of jQuery\'s dispatcher (framework: "jQuery", delegateSelector for delegates the element matches; a dispatcher with no handler for the element is omitted; at most 50 are resolved, jqueryHandlersSkipped counts the rest). Preact\'s event proxy is replaced by the handler Preact runs (framework: "Preact"). Empty handlers (React\'s onclick placeholder) are marked noop and do not count as the element\'s own handler. The Debugger domain is not enabled',
|
|
191
204
|
tokenImpact: 'Framework roots are already collapsed; --type keeps the output short on pages with many listeners',
|
|
192
205
|
},
|
|
193
206
|
'listeners:--all': {
|
|
@@ -201,6 +214,35 @@ const OPTION_BEHAVIORS = {
|
|
|
201
214
|
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 (…)" 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 topmost element at the center of the largest visible box (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',
|
|
202
215
|
tokenImpact: 'About one line per element; a cheap alternative to screenshots for "where is it?"',
|
|
203
216
|
},
|
|
217
|
+
'inspect:--index': {
|
|
218
|
+
default: 'Inspects the first rendered match (the first when none is rendered) and notes how many matched; a numeric argument inspects that cached element (from dom query, dom form or dom a11y query)',
|
|
219
|
+
whenEnabled: 'Inspects the nth match (0-based); out of range exits 81',
|
|
220
|
+
automaticBehavior: 'Answers "what does it look like" without a screenshot, grouped like Figma Dev Mode: header (element, text, size and page position, [flex]/[grid], [not rendered]/[hidden]/[offscreen]/[covered by …], prefers-color-scheme), box (margin, padding, border widths, box-sizing, overflow, scroll size), layout (display, position, flex/grid container and item settings), parent (its display and layout, distances to its content edges, gaps to the neighbouring siblings), text (first font family → the font Chrome rendered, (webfont) or local; weight size/line-height; color; WCAG contrast against the composited background; only for elements with text), fill, border (sides, radius, outline), fx (shadow, transform, filter, opacity, blend), state (cursor, pointer-events, user-select, appearance), pseudo (::before/::after with content, ::placeholder) and a child tree (depth 2, 20 rows, identical siblings grouped). Values that change nothing (0, none, transparent, normal) are left out; colors are hex (lab/oklch from Tailwind converted), lengths px without the unit, rounded to 0.1. Secrets are never shown. JSON uses Figma-aligned names (rect, box, layout.sizing hug/fill/fixed, text, fills, strokes, radius, effects, children). Also by default: hints, the element\'s own declarations that have no effect (justify-content on a block, width on an inline element, top on a static one, var() of an unset custom property) with the reason, the fix and the rule\'s file:line',
|
|
221
|
+
tokenImpact: 'About 60–80 tokens for the styles and 30–70 more for the child tree (--tree 0 drops it), against about 1,500 for a screenshot or 3,000+ for raw computed styles',
|
|
222
|
+
},
|
|
223
|
+
'inspect:--all': {
|
|
224
|
+
default: 'Shows the curated groups (the properties that define the look)',
|
|
225
|
+
whenEnabled: 'Lists every computed property that differs from the default of the same element type, longhands collapsed into shorthands, noise (logical duplicates, currentColor echoes, custom properties) dropped',
|
|
226
|
+
tokenImpact: 'About 80 tokens instead of 60–80',
|
|
227
|
+
},
|
|
228
|
+
'inspect:--props': {
|
|
229
|
+
default: 'Shows the curated groups',
|
|
230
|
+
whenEnabled: 'Shows only the named properties (custom properties like --brand included, "(not set)" when no rule sets one; --* lists every custom property the element has, --bs-btn-* those with a prefix), each computed and normalized; an unknown name exits 81 with a suggestion',
|
|
231
|
+
},
|
|
232
|
+
'inspect:--rules': {
|
|
233
|
+
default: 'Shows the values, not where they come from',
|
|
234
|
+
whenEnabled: "Adds a rules group: for each shown property the page's CSS sets, the value as written (with the computed value when it uses var()), selector, file:line (column for minified files), @media/@container condition, cascade layer, how many ancestors up it is inherited from, and the rules it beats. Sides one declaration sets are one row; browser defaults are left out. With --props, only those properties",
|
|
235
|
+
automaticBehavior: 'The cascade is computed by bdg from CSS.getMatchedStylesForNode (origin, !important, style attribute, layers, specificity and order); reading it waits up to 5 s, then the output notes the cascade was not read',
|
|
236
|
+
tokenImpact: 'About 15–25 tokens per row, 5–20 rows',
|
|
237
|
+
},
|
|
238
|
+
'inspect:--why': {
|
|
239
|
+
default: 'Not shown',
|
|
240
|
+
whenEnabled: "Adds why <property> = computed value, then every declaration of it on the element, highest precedence first: ✓ the winner (or the inherited ancestor's), ✗ the ones it beats, browser defaults included. Each rule shows its selector specificity [ids,classes,types]. var() values are shown substituted (or invalid: --x not set), with where the winner's custom properties are set, followed up to :root. Logical names map to physical ones (margin-inline-start → margin-left); a shorthand (padding, border) gives one answer when one declaration sets all its sides, else one per side",
|
|
241
|
+
},
|
|
242
|
+
'inspect:--no-hints': {
|
|
243
|
+
default: "Hints at the element's own author declarations that have no effect (flex/grid properties without flex or grid, item properties without a flex or grid parent, offsets on static elements, sizes on inline ones, var() of an unset custom property, form controls in the browser's font), within a 1 s budget; hints none when nothing was found",
|
|
244
|
+
whenEnabled: 'Skips the hints and does not read the matched rules',
|
|
245
|
+
},
|
|
204
246
|
'scroll:--down': {
|
|
205
247
|
whenEnabled: 'Scrolls page down by specified pixel amount',
|
|
206
248
|
},
|
|
@@ -282,7 +324,7 @@ const OPTION_BEHAVIORS = {
|
|
|
282
324
|
'bdg:--viewport': {
|
|
283
325
|
default: 'A launched Chrome opens a 1920x1080 window (the viewport is smaller by the scrollbar, and in a visible window by the browser UI); an attached Chrome keeps its window',
|
|
284
326
|
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',
|
|
285
|
-
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×800 (
|
|
327
|
+
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×800 (emulated 1280x800)). Invalid sizes (not WxH, a side outside 1-10000) exit 81',
|
|
286
328
|
},
|
|
287
329
|
'bdg:--color-scheme': {
|
|
288
330
|
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',
|
package/dist/commands/page.d.ts
CHANGED
package/dist/commands/page.js
CHANGED
|
@@ -2,12 +2,15 @@
|
|
|
2
2
|
* `bdg page navigate|reload|back|forward` — move the session's page;
|
|
3
3
|
* `bdg page info` — where it is.
|
|
4
4
|
*/
|
|
5
|
+
import { Option } from 'commander';
|
|
5
6
|
import { noActiveSessionError, runCommand } from './shared/CommandRunner.js';
|
|
6
7
|
import { jsonOption } from './shared/commonOptions.js';
|
|
8
|
+
import { parseColorScheme, parseViewport } from './start.js';
|
|
9
|
+
import { CommandError } from '../errors/index.js';
|
|
7
10
|
import { javascriptNavigationError } from '../errors/messages.js';
|
|
8
|
-
import { getStatus, pageNavigate } from '../ipc/client.js';
|
|
11
|
+
import { getStatus, pageEmulate, pageNavigate } from '../ipc/client.js';
|
|
9
12
|
import { OutputFormatter } from '../ui/formatting.js';
|
|
10
|
-
import { PAGE_ACTION_DESCRIPTIONS, PAGE_ACTION_DONE, PAGE_INFO_DESCRIPTION, pageLoadingWarning, } from '../ui/messages/commands.js';
|
|
13
|
+
import { PAGE_ACTION_DESCRIPTIONS, PAGE_ACTION_DONE, PAGE_EMULATE_DESCRIPTION, PAGE_INFO_DESCRIPTION, pageEmulateNothingError, pageEmulationLines, pageLoadingWarning, } from '../ui/messages/commands.js';
|
|
11
14
|
import { EXIT_CODES } from '../utils/exitCodes.js';
|
|
12
15
|
import { validateUrl } from '../utils/url.js';
|
|
13
16
|
/**
|
|
@@ -114,6 +117,49 @@ async function showPageInfo(options) {
|
|
|
114
117
|
], 8)
|
|
115
118
|
.build());
|
|
116
119
|
}
|
|
120
|
+
/**
|
|
121
|
+
* The emulation request from the options.
|
|
122
|
+
*
|
|
123
|
+
* @param options - Command options
|
|
124
|
+
* @returns Request
|
|
125
|
+
* @throws CommandError (81) for nothing to change or an invalid value
|
|
126
|
+
*/
|
|
127
|
+
function emulationRequest(options) {
|
|
128
|
+
if (options.reset)
|
|
129
|
+
return { reset: true };
|
|
130
|
+
if (options.viewport === undefined && options.colorScheme === undefined) {
|
|
131
|
+
const err = pageEmulateNothingError();
|
|
132
|
+
throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.INVALID_ARGUMENTS);
|
|
133
|
+
}
|
|
134
|
+
return {
|
|
135
|
+
...(options.viewport !== undefined && { viewport: parseViewport(options.viewport) }),
|
|
136
|
+
...(options.colorScheme !== undefined && {
|
|
137
|
+
colorScheme: parseColorScheme(options.colorScheme),
|
|
138
|
+
}),
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* `bdg page emulate`: change the viewport or color scheme mid-session.
|
|
143
|
+
*
|
|
144
|
+
* @param options - Command options
|
|
145
|
+
*/
|
|
146
|
+
async function emulate(options) {
|
|
147
|
+
await runCommand(async () => {
|
|
148
|
+
const response = await pageEmulate(emulationRequest(options));
|
|
149
|
+
if (response.status === 'error' || !response.data) {
|
|
150
|
+
return {
|
|
151
|
+
success: false,
|
|
152
|
+
error: response.error ?? 'Failed to change the emulation',
|
|
153
|
+
exitCode: response.exitCode ?? EXIT_CODES.SOFTWARE_ERROR,
|
|
154
|
+
...(response.suggestion && { errorContext: { suggestion: response.suggestion } }),
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
return { success: true, data: response.data };
|
|
158
|
+
}, options, (result) => new OutputFormatter()
|
|
159
|
+
.text('✓ Page emulation changed')
|
|
160
|
+
.keyValueList(pageEmulationLines(result), 10)
|
|
161
|
+
.build());
|
|
162
|
+
}
|
|
117
163
|
/**
|
|
118
164
|
* Register the `page` command group.
|
|
119
165
|
*
|
|
@@ -122,7 +168,7 @@ async function showPageInfo(options) {
|
|
|
122
168
|
export function registerPageCommands(program) {
|
|
123
169
|
const page = program
|
|
124
170
|
.command('page')
|
|
125
|
-
.description('The session page: info (URL and title), navigate <url>, reload, back, forward');
|
|
171
|
+
.description('The session page: info (URL and title), navigate <url>, reload, back, forward, emulate (viewport, color scheme)');
|
|
126
172
|
page
|
|
127
173
|
.command('info')
|
|
128
174
|
.description(PAGE_INFO_DESCRIPTION)
|
|
@@ -139,6 +185,19 @@ export function registerPageCommands(program) {
|
|
|
139
185
|
.argument('<url>', 'URL to load')).action(async (url, options) => {
|
|
140
186
|
await runPageAction('navigate', options, url);
|
|
141
187
|
});
|
|
188
|
+
page
|
|
189
|
+
.command('emulate')
|
|
190
|
+
.description(PAGE_EMULATE_DESCRIPTION)
|
|
191
|
+
.option('--viewport <WxH>', 'Viewport size in CSS px, e.g. 900x700')
|
|
192
|
+
.option('--color-scheme <scheme>', 'Emulate prefers-color-scheme: light or dark')
|
|
193
|
+
.addOption(new Option('--reset', 'Back to the browser window size and the system setting').conflicts([
|
|
194
|
+
'viewport',
|
|
195
|
+
'colorScheme',
|
|
196
|
+
]))
|
|
197
|
+
.addOption(jsonOption())
|
|
198
|
+
.action(async (options) => {
|
|
199
|
+
await emulate(options);
|
|
200
|
+
});
|
|
142
201
|
for (const action of ['reload', 'back', 'forward']) {
|
|
143
202
|
withCommon(page.command(action).description(PAGE_ACTION_DESCRIPTIONS[action])).action(async (options) => {
|
|
144
203
|
await runPageAction(action, options);
|
|
@@ -30,4 +30,8 @@ export declare function showBothSectionsWhenBothRequested(options: {
|
|
|
30
30
|
network?: boolean;
|
|
31
31
|
console?: boolean;
|
|
32
32
|
}): void;
|
|
33
|
+
/** Help of a `<selectorOrIndex>` argument */
|
|
34
|
+
export declare const SELECTOR_OR_INDEX_ARGUMENT = "CSS selector (searches open shadow roots and same-origin iframes) or numeric index from query results (0-based)";
|
|
35
|
+
/** Help text after `dom query`'s options: what selectors search and what they cannot */
|
|
36
|
+
export declare const SELECTOR_SCOPE_HELP = "\nSelectors search the page, open shadow roots and same-origin iframes (nested ones\nincluded). They cannot reach into closed shadow roots or cross-origin iframes:\nuse bdg dom eval --frame <frame> for those (see bdg dom frames).";
|
|
33
37
|
//# sourceMappingURL=commonOptions.d.ts.map
|
|
@@ -34,4 +34,13 @@ export function showBothSectionsWhenBothRequested(options) {
|
|
|
34
34
|
options.console = false;
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
|
+
/** Where selectors search, for the help of commands that take one */
|
|
38
|
+
const SELECTOR_SCOPE = 'searches open shadow roots and same-origin iframes';
|
|
39
|
+
/** Help of a `<selectorOrIndex>` argument */
|
|
40
|
+
export const SELECTOR_OR_INDEX_ARGUMENT = `CSS selector (${SELECTOR_SCOPE}) or numeric index from query results (0-based)`;
|
|
41
|
+
/** Help text after `dom query`'s options: what selectors search and what they cannot */
|
|
42
|
+
export const SELECTOR_SCOPE_HELP = `
|
|
43
|
+
Selectors search the page, open shadow roots and same-origin iframes (nested ones
|
|
44
|
+
included). They cannot reach into closed shadow roots or cross-origin iframes:
|
|
45
|
+
use bdg dom eval --frame <frame> for those (see bdg dom frames).`;
|
|
37
46
|
//# sourceMappingURL=commonOptions.js.map
|
|
@@ -183,6 +183,8 @@ export interface ClickCommandOptions extends BaseOptions, IndexOptions {
|
|
|
183
183
|
double?: boolean;
|
|
184
184
|
/** Right-click */
|
|
185
185
|
right?: boolean;
|
|
186
|
+
/** Refuse instead of falling back to DOM events when the mouse can't reach the element */
|
|
187
|
+
strict?: boolean;
|
|
186
188
|
}
|
|
187
189
|
/**
|
|
188
190
|
* Options for submit command.
|
|
@@ -236,6 +238,25 @@ export interface ListenersCommandOptions extends BaseOptions, IndexOptions {
|
|
|
236
238
|
* Options for `dom layout`.
|
|
237
239
|
*/
|
|
238
240
|
export type LayoutCommandOptions = BaseOptions & IndexOptions;
|
|
241
|
+
/**
|
|
242
|
+
* Options for `dom inspect`.
|
|
243
|
+
*/
|
|
244
|
+
export interface InspectCommandOptions extends BaseOptions, IndexOptions {
|
|
245
|
+
/** Child tree depth (0 for none) */
|
|
246
|
+
tree?: number;
|
|
247
|
+
/** Child tree rows at most */
|
|
248
|
+
treeLimit?: number;
|
|
249
|
+
/** Every non-default property instead of the groups */
|
|
250
|
+
all?: boolean;
|
|
251
|
+
/** Only these properties */
|
|
252
|
+
props?: string[];
|
|
253
|
+
/** Which declaration sets each shown property */
|
|
254
|
+
rules?: boolean;
|
|
255
|
+
/** Every declaration of this property */
|
|
256
|
+
why?: string;
|
|
257
|
+
/** `--no-hints` sets false */
|
|
258
|
+
hints?: boolean;
|
|
259
|
+
}
|
|
239
260
|
/** Options for `bdg dom wait` */
|
|
240
261
|
export interface WaitCommandOptions extends BaseOptions {
|
|
241
262
|
/** Text a match must contain */
|
|
@@ -5,7 +5,48 @@
|
|
|
5
5
|
* Handles IPC communication with daemon to start browser sessions.
|
|
6
6
|
*/
|
|
7
7
|
import type { SessionStartOptions } from './optionTypes.js';
|
|
8
|
+
import { type SpawnedDaemon } from '../../daemon/launcher.js';
|
|
9
|
+
import { startSession as sendStartSessionRequest } from '../../ipc/client.js';
|
|
10
|
+
import { IPCErrorCode, type StartSessionResponseData } from '../../ipc/index.js';
|
|
8
11
|
import type { TelemetryType } from '../../types.js';
|
|
12
|
+
/**
|
|
13
|
+
* Outcome of a start attempt, printed by {@link reportStartOutcome}.
|
|
14
|
+
*/
|
|
15
|
+
export type StartOutcome = {
|
|
16
|
+
ok: true;
|
|
17
|
+
data: StartSessionResponseData;
|
|
18
|
+
} | {
|
|
19
|
+
ok: false;
|
|
20
|
+
/** Message for `--json` (no "Error:" prefix) */
|
|
21
|
+
error: string;
|
|
22
|
+
/** Full human-readable message */
|
|
23
|
+
human: string;
|
|
24
|
+
exitCode: number;
|
|
25
|
+
/** Daemon's error code (internal: not printed) */
|
|
26
|
+
errorCode?: IPCErrorCode | undefined;
|
|
27
|
+
/** Extra fields of the JSON error envelope */
|
|
28
|
+
details?: Record<string, unknown>;
|
|
29
|
+
/** The daemon went away mid-request (e.g. the previous session was ending) */
|
|
30
|
+
retryable?: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* The daemon reported the failure or dropped the connection, so it is
|
|
33
|
+
* exiting (a daemon this start spawned is then waited for)
|
|
34
|
+
*/
|
|
35
|
+
daemonExiting?: boolean;
|
|
36
|
+
/** The daemon this attempt spawned, when it is exiting */
|
|
37
|
+
spawned?: SpawnedDaemon;
|
|
38
|
+
};
|
|
39
|
+
/** How long a failed start waits for the daemons it spawned to exit */
|
|
40
|
+
export declare const SPAWNED_DAEMON_EXIT_WAIT_MS = 3000;
|
|
41
|
+
/** What a start uses to reach the daemon (replaced in tests) */
|
|
42
|
+
export interface StartDeps {
|
|
43
|
+
/** Spawns the daemon if none runs ({@link launchDaemon}) */
|
|
44
|
+
launch: () => Promise<SpawnedDaemon | undefined>;
|
|
45
|
+
/** Sends `start_session_request` */
|
|
46
|
+
send: typeof sendStartSessionRequest;
|
|
47
|
+
/** How long a failed start waits for the daemons it spawned to exit */
|
|
48
|
+
exitWaitMs: number;
|
|
49
|
+
}
|
|
9
50
|
/**
|
|
10
51
|
* Start a session via the daemon and report the result.
|
|
11
52
|
*
|
|
@@ -17,4 +58,29 @@ import type { TelemetryType } from '../../types.js';
|
|
|
17
58
|
* @param telemetry - Array of telemetry types to enable
|
|
18
59
|
*/
|
|
19
60
|
export declare function startSessionViaDaemon(url: string, options: SessionStartOptions, telemetry: TelemetryType[]): Promise<never>;
|
|
61
|
+
/**
|
|
62
|
+
* Start a session, retrying while the previous session shuts down. After a
|
|
63
|
+
* failure the daemon reported (or a dropped connection), it waits for every
|
|
64
|
+
* daemon the attempts spawned to exit ({@link afterSpawnedDaemonExit}).
|
|
65
|
+
*
|
|
66
|
+
* @param url - Target URL
|
|
67
|
+
* @param options - Session options
|
|
68
|
+
* @param telemetry - Telemetry types
|
|
69
|
+
* @param deps - How to reach the daemon (tests replace it)
|
|
70
|
+
* @returns Start outcome, ready to report
|
|
71
|
+
*/
|
|
72
|
+
export declare function attemptStart(url: string, options: SessionStartOptions, telemetry: TelemetryType[], deps?: StartDeps): Promise<StartOutcome>;
|
|
73
|
+
/**
|
|
74
|
+
* Let the daemons a failed start spawned finish exiting before the error is
|
|
75
|
+
* reported: a daemon removes its session files on the way out, and a command
|
|
76
|
+
* run right after (`bdg sessions`, another start) would otherwise still see
|
|
77
|
+
* the session as starting. The wait is bounded; a daemon still running after
|
|
78
|
+
* it is reported (`details.daemonStillRunning`, `daemonPid`, a suggestion).
|
|
79
|
+
*
|
|
80
|
+
* @param outcome - Start outcome
|
|
81
|
+
* @param spawned - Exiting daemons the attempts spawned
|
|
82
|
+
* @param waitMs - Milliseconds to wait at most for all of them
|
|
83
|
+
* @returns The failure (without internal fields), with a hint when a daemon did not exit in time
|
|
84
|
+
*/
|
|
85
|
+
export declare function afterSpawnedDaemonExit(outcome: StartOutcome, spawned: SpawnedDaemon[], waitMs: number): Promise<StartOutcome>;
|
|
20
86
|
//# sourceMappingURL=startHelpers.d.ts.map
|
|
@@ -17,14 +17,21 @@ import { getSessionName } from '../../session/paths.js';
|
|
|
17
17
|
import { OutputBuilder, buildSuccessResponse } from '../../ui/OutputBuilder.js';
|
|
18
18
|
import { escapeControlChars, joinLines } from '../../ui/formatting.js';
|
|
19
19
|
import { createLogger } from '../../ui/logging/index.js';
|
|
20
|
-
import { startNotices } from '../../ui/messages/session.js';
|
|
20
|
+
import { daemonStillExitingHint, daemonStillExitingSuggestion, startNotices, } from '../../ui/messages/session.js';
|
|
21
21
|
import { noActiveSessionMessage } from '../../ui/messages/sessionCommand.js';
|
|
22
|
-
import { delay } from '../../utils/async.js';
|
|
22
|
+
import { delay, waitUntil } from '../../utils/async.js';
|
|
23
23
|
import { getExitCodeForIPCError } from '../../utils/errorMapping.js';
|
|
24
24
|
import { getErrorMessage } from '../../utils/errors.js';
|
|
25
25
|
import { EXIT_CODES } from '../../utils/exitCodes.js';
|
|
26
26
|
import { filterDefined } from '../../utils/objects.js';
|
|
27
27
|
const log = createLogger('bdg');
|
|
28
|
+
/** How long a failed start waits for the daemons it spawned to exit */
|
|
29
|
+
export const SPAWNED_DAEMON_EXIT_WAIT_MS = 3000;
|
|
30
|
+
const DEFAULT_START_DEPS = {
|
|
31
|
+
launch: launchDaemon,
|
|
32
|
+
send: sendStartSessionRequest,
|
|
33
|
+
exitWaitMs: SPAWNED_DAEMON_EXIT_WAIT_MS,
|
|
34
|
+
};
|
|
28
35
|
/**
|
|
29
36
|
* Start a session via the daemon and report the result.
|
|
30
37
|
*
|
|
@@ -38,13 +45,69 @@ const log = createLogger('bdg');
|
|
|
38
45
|
export async function startSessionViaDaemon(url, options, telemetry) {
|
|
39
46
|
process.once('SIGINT', () => reportStartOutcome(interruptedOutcome('SIGINT'), options));
|
|
40
47
|
process.once('SIGTERM', () => reportStartOutcome(interruptedOutcome('SIGTERM'), options));
|
|
41
|
-
|
|
48
|
+
reportStartOutcome(await attemptStart(url, options, telemetry), options);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Start a session, retrying while the previous session shuts down. After a
|
|
52
|
+
* failure the daemon reported (or a dropped connection), it waits for every
|
|
53
|
+
* daemon the attempts spawned to exit ({@link afterSpawnedDaemonExit}).
|
|
54
|
+
*
|
|
55
|
+
* @param url - Target URL
|
|
56
|
+
* @param options - Session options
|
|
57
|
+
* @param telemetry - Telemetry types
|
|
58
|
+
* @param deps - How to reach the daemon (tests replace it)
|
|
59
|
+
* @returns Start outcome, ready to report
|
|
60
|
+
*/
|
|
61
|
+
export async function attemptStart(url, options, telemetry, deps = DEFAULT_START_DEPS) {
|
|
62
|
+
const spawned = [];
|
|
63
|
+
const attempt = async () => {
|
|
64
|
+
const outcome = await requestSession(url, options, telemetry, deps);
|
|
65
|
+
if (!outcome.ok && outcome.spawned)
|
|
66
|
+
spawned.push(outcome.spawned);
|
|
67
|
+
return outcome;
|
|
68
|
+
};
|
|
69
|
+
let outcome = await attempt();
|
|
42
70
|
const deadline = Date.now() + SHUTDOWN_WAIT_MS;
|
|
43
71
|
while (isShuttingDown(outcome) && Date.now() < deadline) {
|
|
44
72
|
await delay(SHUTDOWN_POLL_MS);
|
|
45
|
-
outcome = await
|
|
73
|
+
outcome = await attempt();
|
|
74
|
+
}
|
|
75
|
+
return afterSpawnedDaemonExit(outcome, spawned, deps.exitWaitMs);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Let the daemons a failed start spawned finish exiting before the error is
|
|
79
|
+
* reported: a daemon removes its session files on the way out, and a command
|
|
80
|
+
* run right after (`bdg sessions`, another start) would otherwise still see
|
|
81
|
+
* the session as starting. The wait is bounded; a daemon still running after
|
|
82
|
+
* it is reported (`details.daemonStillRunning`, `daemonPid`, a suggestion).
|
|
83
|
+
*
|
|
84
|
+
* @param outcome - Start outcome
|
|
85
|
+
* @param spawned - Exiting daemons the attempts spawned
|
|
86
|
+
* @param waitMs - Milliseconds to wait at most for all of them
|
|
87
|
+
* @returns The failure (without internal fields), with a hint when a daemon did not exit in time
|
|
88
|
+
*/
|
|
89
|
+
export async function afterSpawnedDaemonExit(outcome, spawned, waitMs) {
|
|
90
|
+
if (outcome.ok)
|
|
91
|
+
return outcome;
|
|
92
|
+
const { spawned: _spawned, daemonExiting: _exiting, ...failure } = outcome;
|
|
93
|
+
if (spawned.length === 0)
|
|
94
|
+
return failure;
|
|
95
|
+
if (await waitUntil(() => spawned.every((daemon) => daemon.hasExited()), waitMs)) {
|
|
96
|
+
return failure;
|
|
46
97
|
}
|
|
47
|
-
|
|
98
|
+
const running = spawned.filter((daemon) => !daemon.hasExited()).pop();
|
|
99
|
+
const suggestion = daemonStillExitingSuggestion();
|
|
100
|
+
const previous = failure.details?.['suggestion'];
|
|
101
|
+
return {
|
|
102
|
+
...failure,
|
|
103
|
+
human: joinLines(failure.human, `${daemonStillExitingHint(running?.pid, waitMs)}; ${suggestion}`),
|
|
104
|
+
details: {
|
|
105
|
+
...failure.details,
|
|
106
|
+
daemonStillRunning: true,
|
|
107
|
+
...(running?.pid !== undefined && { daemonPid: running.pid }),
|
|
108
|
+
suggestion: typeof previous === 'string' && previous ? `${previous}; ${suggestion}` : suggestion,
|
|
109
|
+
},
|
|
110
|
+
};
|
|
48
111
|
}
|
|
49
112
|
/**
|
|
50
113
|
* The outcome of a start interrupted with Ctrl-C (the daemon notices the
|
|
@@ -83,9 +146,10 @@ function isShuttingDown(outcome) {
|
|
|
83
146
|
* @param telemetry - Telemetry types
|
|
84
147
|
* @returns Start outcome
|
|
85
148
|
*/
|
|
86
|
-
async function requestSession(url, options, telemetry) {
|
|
149
|
+
async function requestSession(url, options, telemetry, deps) {
|
|
150
|
+
let spawned;
|
|
87
151
|
try {
|
|
88
|
-
await
|
|
152
|
+
spawned = await deps.launch();
|
|
89
153
|
}
|
|
90
154
|
catch (error) {
|
|
91
155
|
if (error instanceof SessionDirError) {
|
|
@@ -101,9 +165,24 @@ async function requestSession(url, options, telemetry) {
|
|
|
101
165
|
const exitCode = error instanceof DaemonError ? error.exitCode : EXIT_CODES.SOFTWARE_ERROR;
|
|
102
166
|
return { ok: false, error: message, human: genericError(message), exitCode };
|
|
103
167
|
}
|
|
168
|
+
const outcome = await sendStart(url, options, telemetry, deps.send);
|
|
169
|
+
return !outcome.ok && outcome.daemonExiting && spawned ? { ...outcome, spawned } : outcome;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Ask the running daemon to start a session.
|
|
173
|
+
*
|
|
174
|
+
* @param url - Target URL
|
|
175
|
+
* @param options - Session options
|
|
176
|
+
* @param telemetry - Telemetry types
|
|
177
|
+
* @param send - Sends the request
|
|
178
|
+
* @returns Start outcome; `daemonExiting` for a failure the daemon reported
|
|
179
|
+
* or a dropped connection, not for a timeout or an unexpected error (the
|
|
180
|
+
* daemon may still be starting the session then)
|
|
181
|
+
*/
|
|
182
|
+
async function sendStart(url, options, telemetry, send) {
|
|
104
183
|
try {
|
|
105
184
|
log.debug('Connecting to daemon...');
|
|
106
|
-
const response = await
|
|
185
|
+
const response = await send(url, filterDefined({
|
|
107
186
|
port: options.port,
|
|
108
187
|
timeout: options.timeout,
|
|
109
188
|
telemetry: telemetry.length > 0 ? telemetry : undefined,
|
|
@@ -116,8 +195,9 @@ async function requestSession(url, options, telemetry) {
|
|
|
116
195
|
viewport: options.viewport,
|
|
117
196
|
colorScheme: options.colorScheme,
|
|
118
197
|
}));
|
|
119
|
-
if (response.status === 'error')
|
|
120
|
-
return describeStartFailure(response, options);
|
|
198
|
+
if (response.status === 'error') {
|
|
199
|
+
return { ...describeStartFailure(response, options), daemonExiting: true };
|
|
200
|
+
}
|
|
121
201
|
if (!response.data) {
|
|
122
202
|
const message = 'Invalid response from daemon: missing data';
|
|
123
203
|
return {
|
|
@@ -147,6 +227,7 @@ async function requestSession(url, options, telemetry) {
|
|
|
147
227
|
human: daemonNotRunningError({ suggestStatus: true, suggestRetry: true }),
|
|
148
228
|
exitCode: EXIT_CODES.RESOURCE_NOT_FOUND,
|
|
149
229
|
retryable: true,
|
|
230
|
+
daemonExiting: true,
|
|
150
231
|
};
|
|
151
232
|
}
|
|
152
233
|
const message = getErrorMessage(error);
|
|
@@ -57,5 +57,16 @@ export declare function consoleLevelOption(value: string): ConsoleLevel;
|
|
|
57
57
|
* @throws InvalidArgumentError (exit 81) when no type is given
|
|
58
58
|
*/
|
|
59
59
|
export declare function eventTypesOption(value: string, previous?: string[]): string[];
|
|
60
|
+
/**
|
|
61
|
+
* Commander parser for `dom inspect --props`: comma-separated CSS property
|
|
62
|
+
* names, lowercased except custom properties (`--brand`), which are
|
|
63
|
+
* case-sensitive; repeated options add up.
|
|
64
|
+
*
|
|
65
|
+
* @param value - Raw option value, e.g. "padding-top,color"
|
|
66
|
+
* @param previous - Names from earlier `--props` options
|
|
67
|
+
* @returns Property names
|
|
68
|
+
* @throws InvalidArgumentError (exit 81) when no name is given
|
|
69
|
+
*/
|
|
70
|
+
export declare function cssPropertiesOption(value: string, previous?: string[]): string[];
|
|
60
71
|
export declare function resourceTypeRule(): ValidationRule<Protocol.Network.ResourceType[]>;
|
|
61
72
|
//# sourceMappingURL=validation.d.ts.map
|
|
@@ -158,6 +158,22 @@ export function eventTypesOption(value, previous = []) {
|
|
|
158
158
|
return [...new Set([...previous, ...types])];
|
|
159
159
|
throw new InvalidArgumentError('Give at least one event type, e.g. click or click,keydown.');
|
|
160
160
|
}
|
|
161
|
+
/**
|
|
162
|
+
* Commander parser for `dom inspect --props`: comma-separated CSS property
|
|
163
|
+
* names, lowercased except custom properties (`--brand`), which are
|
|
164
|
+
* case-sensitive; repeated options add up.
|
|
165
|
+
*
|
|
166
|
+
* @param value - Raw option value, e.g. "padding-top,color"
|
|
167
|
+
* @param previous - Names from earlier `--props` options
|
|
168
|
+
* @returns Property names
|
|
169
|
+
* @throws InvalidArgumentError (exit 81) when no name is given
|
|
170
|
+
*/
|
|
171
|
+
export function cssPropertiesOption(value, previous = []) {
|
|
172
|
+
const names = parseCommaSeparated(value).map((name) => name.startsWith('--') ? name : name.toLowerCase());
|
|
173
|
+
if (names.length > 0)
|
|
174
|
+
return [...new Set([...previous, ...names])];
|
|
175
|
+
throw new InvalidArgumentError('Give at least one CSS property, e.g. color or padding-top,color.');
|
|
176
|
+
}
|
|
161
177
|
function parseCommaSeparated(value) {
|
|
162
178
|
return value
|
|
163
179
|
.split(',')
|
|
@@ -6,13 +6,20 @@
|
|
|
6
6
|
* SocketServer), so concurrent launches are safe: a losing daemon exits and
|
|
7
7
|
* the CLI talks to the winner.
|
|
8
8
|
*/
|
|
9
|
+
/** A daemon this process spawned */
|
|
10
|
+
export interface SpawnedDaemon {
|
|
11
|
+
pid: number | undefined;
|
|
12
|
+
/** Whether it has exited (from the child process's exit event) */
|
|
13
|
+
hasExited: () => boolean;
|
|
14
|
+
}
|
|
9
15
|
/**
|
|
10
16
|
* Ensure a daemon is running, spawning one if needed.
|
|
11
17
|
*
|
|
18
|
+
* @returns The daemon it spawned, or undefined when one was already running
|
|
12
19
|
* @throws DaemonStartupError if the daemon script is missing or the daemon
|
|
13
20
|
* does not accept connections in time
|
|
14
21
|
*/
|
|
15
|
-
export declare function launchDaemon(): Promise<
|
|
22
|
+
export declare function launchDaemon(): Promise<SpawnedDaemon | undefined>;
|
|
16
23
|
/**
|
|
17
24
|
* Check that the session directory can hold the daemon's files before
|
|
18
25
|
* spawning it (otherwise the daemon dies and only its log says why).
|
package/dist/daemon/launcher.js
CHANGED
|
@@ -24,13 +24,14 @@ const DAEMON_READY_POLL_MS = 20;
|
|
|
24
24
|
/**
|
|
25
25
|
* Ensure a daemon is running, spawning one if needed.
|
|
26
26
|
*
|
|
27
|
+
* @returns The daemon it spawned, or undefined when one was already running
|
|
27
28
|
* @throws DaemonStartupError if the daemon script is missing or the daemon
|
|
28
29
|
* does not accept connections in time
|
|
29
30
|
*/
|
|
30
31
|
export async function launchDaemon() {
|
|
31
32
|
if (await isDaemonAlive()) {
|
|
32
33
|
log.debug('Daemon already running');
|
|
33
|
-
return;
|
|
34
|
+
return undefined;
|
|
34
35
|
}
|
|
35
36
|
if (!fs.existsSync(DAEMON_SCRIPT_PATH)) {
|
|
36
37
|
throw new DaemonStartupError(`Daemon script not found at ${DAEMON_SCRIPT_PATH}. Did you run 'npm run build'?`, 'DAEMON_SCRIPT_NOT_FOUND');
|
|
@@ -51,6 +52,7 @@ export async function launchDaemon() {
|
|
|
51
52
|
});
|
|
52
53
|
daemon.unref();
|
|
53
54
|
await waitForDaemonReady(() => exited);
|
|
55
|
+
return { pid: daemon.pid, hasExited: () => exited };
|
|
54
56
|
}
|
|
55
57
|
/**
|
|
56
58
|
* Check that the session directory can hold the daemon's files before
|
|
@@ -99,6 +99,13 @@ export declare class Session {
|
|
|
99
99
|
* @returns Session metadata
|
|
100
100
|
*/
|
|
101
101
|
metadata(): SessionStatusMetadata;
|
|
102
|
+
/**
|
|
103
|
+
* Record a page emulation changed mid-session, so screenshots restore it
|
|
104
|
+
* and `bdg status` reports it.
|
|
105
|
+
*
|
|
106
|
+
* @param emulation - Viewport and color scheme now emulated
|
|
107
|
+
*/
|
|
108
|
+
private setEmulation;
|
|
102
109
|
/**
|
|
103
110
|
* Whether stop() has been called.
|
|
104
111
|
*
|