browser-debugger-cli 0.11.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/skills/bdg/SKILL.md +4 -4
- package/README.md +143 -79
- package/dist/commands/cdp.d.ts +22 -1
- package/dist/commands/cdp.js +100 -43
- package/dist/commands/console.d.ts +12 -0
- package/dist/commands/console.js +62 -12
- package/dist/commands/css.d.ts +13 -0
- package/dist/commands/css.js +53 -0
- package/dist/commands/dom/DomElementResolver.d.ts +3 -1
- package/dist/commands/dom/DomElementResolver.js +10 -3
- package/dist/commands/dom/a11y.js +3 -2
- package/dist/commands/dom/audit.d.ts +14 -0
- package/dist/commands/dom/audit.js +87 -0
- package/dist/commands/dom/eval.d.ts +3 -2
- package/dist/commands/dom/eval.js +11 -5
- package/dist/commands/dom/form.js +10 -9
- package/dist/commands/dom/formInteraction.js +42 -11
- package/dist/commands/dom/get.js +8 -8
- package/dist/commands/dom/helpers/index.d.ts +1 -1
- package/dist/commands/dom/helpers/index.js +1 -1
- package/dist/commands/dom/helpers/keyAttributes.d.ts +3 -2
- package/dist/commands/dom/helpers/keyAttributes.js +6 -4
- package/dist/commands/dom/helpers/query.d.ts +27 -3
- package/dist/commands/dom/helpers/query.js +152 -64
- package/dist/commands/dom/helpers/screenshot.d.ts +1 -0
- package/dist/commands/dom/helpers/screenshot.js +169 -49
- package/dist/commands/dom/index.js +7 -2
- package/dist/commands/dom/query.d.ts +19 -2
- package/dist/commands/dom/query.js +37 -6
- package/dist/commands/dom/screenshot.js +12 -7
- package/dist/commands/dom/semanticUtils.d.ts +3 -2
- package/dist/commands/dom/semanticUtils.js +40 -9
- package/dist/commands/dom/wait.js +5 -3
- package/dist/commands/helpJson.d.ts +82 -19
- package/dist/commands/helpJson.js +112 -41
- package/dist/commands/helpTopic.d.ts +16 -1
- package/dist/commands/helpTopic.js +59 -1
- package/dist/commands/installSkill.d.ts +15 -5
- package/dist/commands/installSkill.js +86 -16
- package/dist/commands/network/list.js +22 -12
- package/dist/commands/optionBehaviors.js +53 -16
- 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/daemonErrorHandler.d.ts +5 -2
- package/dist/commands/shared/daemonErrorHandler.js +20 -9
- package/dist/commands/shared/dataFetcher.d.ts +12 -4
- package/dist/commands/shared/dataFetcher.js +12 -4
- package/dist/commands/shared/followMode.d.ts +9 -1
- package/dist/commands/shared/followMode.js +22 -4
- package/dist/commands/shared/optionTypes.d.ts +9 -2
- package/dist/commands/shared/outputFile.js +6 -1
- package/dist/commands/start.d.ts +20 -5
- package/dist/commands/start.js +84 -23
- package/dist/commands/stop.d.ts +11 -0
- package/dist/commands/stop.js +24 -1
- package/dist/commands/tail.d.ts +7 -1
- package/dist/commands/tail.js +13 -62
- package/dist/commands.js +2 -0
- package/dist/connection/cdp.d.ts +7 -0
- package/dist/connection/cdp.js +9 -0
- package/dist/connection/launcher.js +3 -2
- package/dist/daemon/SessionController.js +6 -1
- package/dist/daemon/launcher.d.ts +3 -2
- package/dist/daemon/launcher.js +47 -3
- package/dist/daemon/session/Session.d.ts +4 -1
- package/dist/daemon/session/Session.js +33 -2
- package/dist/daemon/session/TelemetryStore.d.ts +8 -1
- package/dist/daemon/session/TelemetryStore.js +13 -1
- package/dist/daemon/session/commandRegistry.js +36 -14
- package/dist/daemon/session/interactions.d.ts +2 -1
- package/dist/daemon/session/interactions.js +13 -1
- package/dist/daemon/session/plugins.js +19 -53
- package/dist/daemon/session/teardown.js +1 -1
- package/dist/daemon.js +9234 -7222
- package/dist/errors/messages.d.ts +88 -15
- package/dist/errors/messages.js +177 -27
- package/dist/index.js +19322 -13961
- package/dist/ipc/client.d.ts +22 -2
- package/dist/ipc/client.js +34 -5
- package/dist/ipc/protocol/auditTypes.d.ts +135 -0
- package/dist/ipc/protocol/auditTypes.js +6 -0
- package/dist/ipc/protocol/commands.d.ts +35 -0
- package/dist/ipc/protocol/commands.js +2 -0
- package/dist/ipc/protocol/domTypes.d.ts +16 -0
- package/dist/ipc/protocol/inspectTypes.d.ts +73 -8
- package/dist/ipc/session/types.d.ts +2 -0
- package/dist/runtime/css/search.d.ts +39 -0
- package/dist/runtime/css/search.js +122 -0
- package/dist/runtime/dom/actionEffects.d.ts +9 -2
- package/dist/runtime/dom/actionEffects.js +30 -14
- package/dist/runtime/dom/audit.d.ts +19 -0
- package/dist/runtime/dom/audit.js +37 -0
- package/dist/runtime/dom/auditModel.d.ts +45 -0
- package/dist/runtime/dom/auditModel.js +220 -0
- package/dist/runtime/dom/auditScripts.d.ts +113 -0
- package/dist/runtime/dom/auditScripts.js +148 -0
- package/dist/runtime/dom/elementGeometry.d.ts +16 -3
- package/dist/runtime/dom/elementGeometry.js +49 -10
- package/dist/runtime/dom/elementInfo.d.ts +74 -17
- package/dist/runtime/dom/elementInfo.js +187 -34
- package/dist/runtime/dom/evalHelpers.d.ts +12 -2
- package/dist/runtime/dom/evalHelpers.js +67 -7
- package/dist/runtime/dom/formDiscovery.d.ts +6 -2
- package/dist/runtime/dom/formDiscovery.js +20 -3
- package/dist/runtime/dom/formFillHelpers/fill.js +8 -12
- package/dist/runtime/dom/formFillHelpers/pressKey.js +2 -2
- package/dist/runtime/dom/formFillHelpers/shared.d.ts +16 -10
- package/dist/runtime/dom/formFillHelpers/shared.js +19 -52
- package/dist/runtime/dom/formSubmitHelpers.js +4 -3
- package/dist/runtime/dom/frameLayout.js +1 -0
- package/dist/runtime/dom/inspect.d.ts +7 -0
- package/dist/runtime/dom/inspect.js +92 -28
- package/dist/runtime/dom/inspectAllStyles.d.ts +16 -4
- package/dist/runtime/dom/inspectAllStyles.js +90 -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 +5 -1
- package/dist/runtime/dom/inspectModel.js +37 -10
- package/dist/runtime/dom/inspectPaintModel.d.ts +50 -22
- package/dist/runtime/dom/inspectPaintModel.js +182 -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 +112 -12
- package/dist/runtime/dom/inspectScripts.js +357 -32
- 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 +40 -16
- package/dist/runtime/dom/reactEventHelpers.d.ts +21 -4
- package/dist/runtime/dom/reactEventHelpers.js +90 -36
- package/dist/runtime/dom/targetNode.d.ts +18 -5
- package/dist/runtime/dom/targetNode.js +268 -8
- package/dist/runtime/dom/wait.js +2 -1
- package/dist/runtime/page/bdgWorld.d.ts +57 -0
- package/dist/runtime/page/bdgWorld.js +180 -0
- package/dist/runtime/page/emulation.d.ts +13 -4
- package/dist/runtime/page/emulation.js +69 -4
- package/dist/runtime/page/replacedBuiltins.d.ts +28 -0
- package/dist/runtime/page/replacedBuiltins.js +136 -0
- package/dist/runtime/page/userAgent.d.ts +17 -0
- package/dist/runtime/page/userAgent.js +57 -0
- package/dist/session/QueryCacheManager.d.ts +4 -1
- package/dist/session/QueryCacheManager.js +5 -2
- package/dist/session/chrome.d.ts +4 -1
- package/dist/session/chrome.js +7 -1
- package/dist/session/cleanup/staleSession.d.ts +21 -4
- package/dist/session/cleanup/staleSession.js +79 -9
- package/dist/session/cleanup/userCommands.d.ts +4 -1
- package/dist/session/cleanup/userCommands.js +10 -5
- package/dist/session/daemonSocket.d.ts +10 -0
- package/dist/session/daemonSocket.js +22 -0
- package/dist/session/lastSession.d.ts +6 -3
- package/dist/session/lastSession.js +11 -5
- package/dist/session/paths.d.ts +3 -1
- package/dist/session/paths.js +5 -5
- package/dist/session/portClaims.js +4 -3
- package/dist/session/sessionList.d.ts +13 -5
- package/dist/session/sessionList.js +31 -7
- package/dist/telemetry/a11y.js +2 -2
- package/dist/telemetry/console.d.ts +2 -1
- package/dist/telemetry/console.js +30 -21
- package/dist/telemetry/pageCrash.d.ts +26 -0
- package/dist/telemetry/pageCrash.js +53 -0
- package/dist/types.d.ts +20 -0
- package/dist/ui/formatters/audit.d.ts +19 -0
- package/dist/ui/formatters/audit.js +115 -0
- package/dist/ui/formatters/cdp.d.ts +138 -0
- package/dist/ui/formatters/cdp.js +131 -0
- package/dist/ui/formatters/console/chronological.js +3 -1
- package/dist/ui/formatters/console/follow.d.ts +2 -1
- package/dist/ui/formatters/console/follow.js +2 -2
- package/dist/ui/formatters/console/json.d.ts +2 -2
- package/dist/ui/formatters/console/json.js +11 -5
- package/dist/ui/formatters/console/shared.d.ts +30 -0
- package/dist/ui/formatters/console/shared.js +16 -0
- package/dist/ui/formatters/console/summarize.d.ts +9 -2
- package/dist/ui/formatters/console/summarize.js +40 -9
- package/dist/ui/formatters/console.d.ts +2 -1
- package/dist/ui/formatters/console.js +7 -5
- package/dist/ui/formatters/details.js +3 -1
- package/dist/ui/formatters/dom.d.ts +2 -2
- package/dist/ui/formatters/dom.js +10 -8
- package/dist/ui/formatters/helpFormatters.js +1 -1
- package/dist/ui/formatters/inspect.js +50 -17
- package/dist/ui/formatters/installSkill.d.ts +9 -1
- package/dist/ui/formatters/installSkill.js +32 -6
- package/dist/ui/formatters/layout.js +2 -1
- package/dist/ui/formatters/networkList.d.ts +1 -1
- package/dist/ui/formatters/networkList.js +1 -2
- package/dist/ui/formatters/preview.d.ts +2 -0
- package/dist/ui/formatters/preview.js +17 -7
- package/dist/ui/formatters/sessions.d.ts +2 -2
- package/dist/ui/formatters/sessions.js +9 -2
- package/dist/ui/formatters/status.js +1 -1
- package/dist/ui/logging/logger.d.ts +1 -1
- package/dist/ui/messages/commands.d.ts +168 -11
- package/dist/ui/messages/commands.js +245 -18
- package/dist/ui/messages/consoleMessages.d.ts +24 -0
- package/dist/ui/messages/consoleMessages.js +32 -0
- package/dist/ui/messages/preview.d.ts +12 -0
- package/dist/ui/messages/preview.js +18 -2
- package/dist/ui/messages/session.d.ts +13 -2
- package/dist/ui/messages/session.js +22 -3
- package/dist/utils/cssValues.js +36 -4
- package/dist/utils/decisionTrees.js +0 -5
- package/dist/utils/directories.d.ts +34 -0
- package/dist/utils/directories.js +88 -0
- package/dist/utils/display.d.ts +16 -0
- package/dist/utils/display.js +42 -0
- package/dist/utils/exitCodes.d.ts +1 -0
- package/dist/utils/exitCodes.js +6 -0
- package/dist/utils/process.d.ts +12 -0
- package/dist/utils/process.js +25 -0
- 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/commands/dom/get.js
CHANGED
|
@@ -10,7 +10,7 @@ import { getDOMElements, getDomContext, selectMatch, } from './helpers/index.js'
|
|
|
10
10
|
import { formatSemanticNodeWithContext, resolveNodeWithFallback, } from './semanticUtils.js';
|
|
11
11
|
import { runCommand } from '../shared/CommandRunner.js';
|
|
12
12
|
import { CommandError } from '../../errors/index.js';
|
|
13
|
-
import {
|
|
13
|
+
import { conflictingOptionsError, indexWithIndexOptionError, nodeIdNotFoundError, optionRequiresError, } from '../../errors/messages.js';
|
|
14
14
|
import { resolveA11yNode } from '../../telemetry/a11y.js';
|
|
15
15
|
import { formatDomGet } from '../../ui/formatters/dom.js';
|
|
16
16
|
import { EXIT_CODES } from '../../utils/exitCodes.js';
|
|
@@ -99,23 +99,23 @@ function matchIndex(options) {
|
|
|
99
99
|
*
|
|
100
100
|
* @param selectorOrIndex - Selector or index argument
|
|
101
101
|
* @param options - Command options
|
|
102
|
-
* @returns What conflicts, or null
|
|
102
|
+
* @returns What conflicts and how to fix it, or null
|
|
103
103
|
*/
|
|
104
104
|
function getOptionsConflict(selectorOrIndex, options) {
|
|
105
105
|
if (options.nodeId !== undefined && selectorOrIndex !== undefined) {
|
|
106
|
-
return
|
|
106
|
+
return conflictingOptionsError('--node-id', 'a selector or index');
|
|
107
107
|
}
|
|
108
108
|
if (options.full && (options.raw || options.nodeId !== undefined)) {
|
|
109
|
-
return
|
|
109
|
+
return conflictingOptionsError('--full', options.raw ? '--raw' : '--node-id');
|
|
110
110
|
}
|
|
111
111
|
if (options.index !== undefined && options.nth !== undefined) {
|
|
112
|
-
return
|
|
112
|
+
return conflictingOptionsError('--index', '--nth');
|
|
113
113
|
}
|
|
114
114
|
if (options.all && matchIndex(options) !== undefined) {
|
|
115
|
-
return
|
|
115
|
+
return conflictingOptionsError('--all', '--index');
|
|
116
116
|
}
|
|
117
117
|
if (options.all && !options.raw)
|
|
118
|
-
return
|
|
118
|
+
return optionRequiresError('--all', '--raw');
|
|
119
119
|
return null;
|
|
120
120
|
}
|
|
121
121
|
/**
|
|
@@ -130,7 +130,7 @@ function getOptionsConflict(selectorOrIndex, options) {
|
|
|
130
130
|
export async function handleDomGet(selectorOrIndex, options) {
|
|
131
131
|
const conflict = getOptionsConflict(selectorOrIndex, options);
|
|
132
132
|
if (conflict) {
|
|
133
|
-
throw new CommandError(conflict, {}, EXIT_CODES.INVALID_ARGUMENTS);
|
|
133
|
+
throw new CommandError(conflict.message, { suggestion: conflict.suggestion }, EXIT_CODES.INVALID_ARGUMENTS);
|
|
134
134
|
}
|
|
135
135
|
const { nodeId } = options;
|
|
136
136
|
if (nodeId !== undefined) {
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* - `query.ts` — selector → backend node ids (shadow roots, same-origin iframes), DOM.describeNode
|
|
6
6
|
* - `screenshot.ts` — page / element capture, element bounds, scroll helpers
|
|
7
7
|
*/
|
|
8
|
-
export { documentReadyState, noMatchesError, queryDOMElements, getDomContext, getDOMElements, resolveSelector, resolveBackendNodeIds, selectMatch, assertNodeAttached, } from './query.js';
|
|
8
|
+
export { documentReadyState, noMatchesError, queryDOMElements, getDomContext, getDOMElements, resolveSelector, resolveBackendNodeIds, selectMatch, assertNodeAttached, pageDocumentId, } from './query.js';
|
|
9
9
|
export { capturePageScreenshot, captureElementScreenshot, getElementBounds, } from './screenshot.js';
|
|
10
10
|
export type { DomQueryResult, DomGetResult, ScreenshotResult, DomGetOptions, ScreenshotOptions, DomContext, ElementBounds, } from '../../../types.js';
|
|
11
11
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -5,6 +5,6 @@
|
|
|
5
5
|
* - `query.ts` — selector → backend node ids (shadow roots, same-origin iframes), DOM.describeNode
|
|
6
6
|
* - `screenshot.ts` — page / element capture, element bounds, scroll helpers
|
|
7
7
|
*/
|
|
8
|
-
export { documentReadyState, noMatchesError, queryDOMElements, getDomContext, getDOMElements, resolveSelector, resolveBackendNodeIds, selectMatch, assertNodeAttached, } from './query.js';
|
|
8
|
+
export { documentReadyState, noMatchesError, queryDOMElements, getDomContext, getDOMElements, resolveSelector, resolveBackendNodeIds, selectMatch, assertNodeAttached, pageDocumentId, } from './query.js';
|
|
9
9
|
export { capturePageScreenshot, captureElementScreenshot, getElementBounds, } from './screenshot.js';
|
|
10
10
|
//# sourceMappingURL=index.js.map
|
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import type { ElementState, KeyAttributes } from '../../../types.js';
|
|
7
7
|
/**
|
|
8
|
-
* The key attributes of an element:
|
|
9
|
-
*
|
|
8
|
+
* The key attributes of an element: its shadow `part` name (how a page's CSS
|
|
9
|
+
* reaches it with `::part()`), the identifying attributes of its type that
|
|
10
|
+
* are set (empty ones left out) and the live state of a form control
|
|
10
11
|
* (type, current value, checked, selected options). Values arrive masked
|
|
11
12
|
* from the page (`ELEMENT_STATE_JS`); a hidden input's value is never read,
|
|
12
13
|
* and a sensitive field's value is masked here again in case it was not.
|
|
@@ -16,8 +16,9 @@ const ATTRIBUTES_BY_TAG = {
|
|
|
16
16
|
form: ['action', 'method'],
|
|
17
17
|
};
|
|
18
18
|
/**
|
|
19
|
-
* The key attributes of an element:
|
|
20
|
-
*
|
|
19
|
+
* The key attributes of an element: its shadow `part` name (how a page's CSS
|
|
20
|
+
* reaches it with `::part()`), the identifying attributes of its type that
|
|
21
|
+
* are set (empty ones left out) and the live state of a form control
|
|
21
22
|
* (type, current value, checked, selected options). Values arrive masked
|
|
22
23
|
* from the page (`ELEMENT_STATE_JS`); a hidden input's value is never read,
|
|
23
24
|
* and a sensitive field's value is masked here again in case it was not.
|
|
@@ -29,9 +30,10 @@ const ATTRIBUTES_BY_TAG = {
|
|
|
29
30
|
*/
|
|
30
31
|
export function keyAttributes(tag, attributes, state = {}) {
|
|
31
32
|
const names = ATTRIBUTES_BY_TAG[tag];
|
|
33
|
+
const part = attributes['part'];
|
|
32
34
|
if (!names)
|
|
33
|
-
return undefined;
|
|
34
|
-
const result = {};
|
|
35
|
+
return part ? { part } : undefined;
|
|
36
|
+
const result = part ? { part } : {};
|
|
35
37
|
const type = state.type ?? attributes['type'];
|
|
36
38
|
if (type)
|
|
37
39
|
result['type'] = type;
|
|
@@ -38,13 +38,26 @@ export declare function noMatchContext(selector: string): Promise<NoMatchContext
|
|
|
38
38
|
* @returns The state, or undefined when the page did not answer
|
|
39
39
|
*/
|
|
40
40
|
export declare function documentReadyState(): Promise<string | undefined>;
|
|
41
|
+
/** Matches whose viewport position `dom query` reports (measuring is not free) */
|
|
42
|
+
export declare const VIEWPORT_HINT_LIMIT = 100;
|
|
41
43
|
/**
|
|
42
|
-
*
|
|
44
|
+
* Matches `dom query` describes and caches for use by index at least, beyond
|
|
45
|
+
* those it lists: describing costs a round trip per element, so a page with
|
|
46
|
+
* 50000 matches is not described whole unless `--limit 0` asks for it.
|
|
47
|
+
*/
|
|
48
|
+
export declare const QUERY_CACHE_LIMIT = 1000;
|
|
49
|
+
/**
|
|
50
|
+
* Query elements by selector for `bdg dom query`: all matches are counted,
|
|
51
|
+
* and the first `limit` (at least {@link QUERY_CACHE_LIMIT}, so indices past
|
|
52
|
+
* the listed ones still work) are described in one page-side pass plus one
|
|
53
|
+
* `DOM.describeNode` each for their backend node id.
|
|
43
54
|
*
|
|
44
55
|
* @param selector - CSS selector
|
|
45
|
-
* @
|
|
56
|
+
* @param limit - Matches the caller lists (0 = all, so all are described)
|
|
57
|
+
* @returns The match count, and the described matches with backend node
|
|
58
|
+
* ids, tags, key attributes, classes and text previews
|
|
46
59
|
*/
|
|
47
|
-
export declare function queryDOMElements(selector: string): Promise<DomQueryResult>;
|
|
60
|
+
export declare function queryDOMElements(selector: string, limit?: number): Promise<DomQueryResult>;
|
|
48
61
|
/**
|
|
49
62
|
* Get DOM context (tag, classes, key attributes, text preview) for a node: a one-line
|
|
50
63
|
* preview, and up to {@link ELEMENT_TEXT_LENGTH} characters of text when it
|
|
@@ -102,4 +115,15 @@ export declare function resolveBackendNodeIds(selectors: string[]): Promise<(num
|
|
|
102
115
|
* @throws CommandError (87) when the element is gone
|
|
103
116
|
*/
|
|
104
117
|
export declare function assertNodeAttached(backendNodeId: number, source?: IndexSource): Promise<void>;
|
|
118
|
+
/**
|
|
119
|
+
* Identity of the page's current document: its time origin, which every
|
|
120
|
+
* document load gets anew. Cached backend node ids belong to one document; a
|
|
121
|
+
* page loaded in a new renderer process reuses the same ids for other
|
|
122
|
+
* elements, so an index of an older document must not be used.
|
|
123
|
+
*
|
|
124
|
+
* @returns The identity, or undefined when the page cannot be asked
|
|
125
|
+
* @throws CommandError when the page is busy (102) or crashed (107), as the
|
|
126
|
+
* command itself would
|
|
127
|
+
*/
|
|
128
|
+
export declare function pageDocumentId(): Promise<string | undefined>;
|
|
105
129
|
//# sourceMappingURL=query.d.ts.map
|
|
@@ -12,9 +12,9 @@
|
|
|
12
12
|
import { keyAttributes } from './keyAttributes.js';
|
|
13
13
|
import { CommandError } from '../../../errors/index.js';
|
|
14
14
|
import { noNodesFoundError, indexOutOfRangeError, eitherArgumentRequiredError, invalidSelectorError, nodeIdNotFoundError, operationFailedError, similarSelectorsLine, staleNodeError, } from '../../../errors/messages.js';
|
|
15
|
-
import { callCDP } from '../../../ipc/client.js';
|
|
15
|
+
import { callBdgScript, callCDP } from '../../../ipc/client.js';
|
|
16
16
|
import { ELEMENT_GEOMETRY_JS, VIEWPORT_SIZE_JS, classifyViewportPosition, } from '../../../runtime/dom/elementGeometry.js';
|
|
17
|
-
import { ELEMENT_CONTEXT_JS, ELEMENT_STATE_JS, ELEMENT_TEXT_JS, ELEMENT_TEXT_LENGTH, textPreview, } from '../../../runtime/dom/elementInfo.js';
|
|
17
|
+
import { ELEMENT_CONTEXT_JS, ELEMENT_STATE_JS, ELEMENT_TEXT_JS, ELEMENT_TEXT_LENGTH, textPreview, isLabelClass, } from '../../../runtime/dom/elementInfo.js';
|
|
18
18
|
import { DEEP_QUERY_JS, UNSEARCHED_CONTENT_JS, pageNamesJS, selectorArgsJS, } from '../../../runtime/dom/targetNode.js';
|
|
19
19
|
import { createLogger } from '../../../ui/logging/index.js';
|
|
20
20
|
import { sessionCommand } from '../../../ui/messages/sessionCommand.js';
|
|
@@ -92,7 +92,7 @@ async function selectAll(selector) {
|
|
|
92
92
|
async function withSelection(selector, use) {
|
|
93
93
|
await callCDP('DOM.enable', {});
|
|
94
94
|
const objectGroup = `bdg-query-${process.pid}-${++queryCount}`;
|
|
95
|
-
const evaluated = await
|
|
95
|
+
const evaluated = await callBdgScript('Runtime.evaluate', {
|
|
96
96
|
expression: `(${DEEP_QUERY_JS})(${selectorArgsJS(selector)})`,
|
|
97
97
|
objectGroup,
|
|
98
98
|
});
|
|
@@ -163,7 +163,7 @@ export async function noMatchContext(selector) {
|
|
|
163
163
|
const compounds = leadingCompounds(selector);
|
|
164
164
|
const shadowHost = compounds.length > 0 ? shadowHostJS(compounds) : '-1';
|
|
165
165
|
try {
|
|
166
|
-
const evaluated = await
|
|
166
|
+
const evaluated = await callBdgScript('Runtime.evaluate', {
|
|
167
167
|
expression: `({ hidden: ${hidden}, readyState: document.readyState, unsearched: ${UNSEARCHED_CONTENT_JS}, names: ${names}, shadowHost: ${shadowHost} })`,
|
|
168
168
|
returnByValue: true,
|
|
169
169
|
});
|
|
@@ -199,7 +199,7 @@ export async function noMatchContext(selector) {
|
|
|
199
199
|
*/
|
|
200
200
|
export async function documentReadyState() {
|
|
201
201
|
try {
|
|
202
|
-
const evaluated = await
|
|
202
|
+
const evaluated = await callBdgScript('Runtime.evaluate', {
|
|
203
203
|
expression: 'document.readyState',
|
|
204
204
|
returnByValue: true,
|
|
205
205
|
});
|
|
@@ -212,46 +212,63 @@ export async function documentReadyState() {
|
|
|
212
212
|
}
|
|
213
213
|
}
|
|
214
214
|
/** Matches whose viewport position `dom query` reports (measuring is not free) */
|
|
215
|
-
const VIEWPORT_HINT_LIMIT = 100;
|
|
215
|
+
export const VIEWPORT_HINT_LIMIT = 100;
|
|
216
216
|
/**
|
|
217
|
-
*
|
|
218
|
-
*
|
|
219
|
-
*
|
|
220
|
-
* relative to the viewport, plus the viewport size. An element that cannot be
|
|
221
|
-
* read gets empty details instead of failing the whole query.
|
|
217
|
+
* Matches `dom query` describes and caches for use by index at least, beyond
|
|
218
|
+
* those it lists: describing costs a round trip per element, so a page with
|
|
219
|
+
* 50000 matches is not described whole unless `--limit 0` asks for it.
|
|
222
220
|
*/
|
|
223
|
-
const
|
|
221
|
+
export const QUERY_CACHE_LIMIT = 1000;
|
|
222
|
+
/**
|
|
223
|
+
* For the first `count` elements of a page-side array: the tag and
|
|
224
|
+
* attributes, where it lives (an iframe and/or a shadow root), its text, its
|
|
225
|
+
* form control state ({@link ELEMENT_STATE_JS}) and, for the first
|
|
226
|
+
* {@link VIEWPORT_HINT_LIMIT}, its position relative to the viewport; plus
|
|
227
|
+
* the viewport size and how many elements the array has. An element that
|
|
228
|
+
* cannot be read gets empty details instead of failing the whole query.
|
|
229
|
+
*/
|
|
230
|
+
const ELEMENT_DETAILS_FUNCTION = `function (count) {
|
|
224
231
|
const contextOf = ${ELEMENT_CONTEXT_JS};
|
|
225
232
|
const textOf = ${ELEMENT_TEXT_JS};
|
|
226
233
|
const geometryOf = ${ELEMENT_GEOMETRY_JS};
|
|
227
234
|
const stateOf = ${ELEMENT_STATE_JS};
|
|
228
235
|
const read = (el, index) => {
|
|
229
236
|
try {
|
|
230
|
-
|
|
237
|
+
const attributes = [];
|
|
238
|
+
for (const attribute of Array.from(el.attributes || [])) attributes.push(attribute.name, attribute.value);
|
|
239
|
+
return { tag: el.nodeName, attributes: attributes, context: contextOf(el), text: textOf(el), state: stateOf(el), geometry: index < ${VIEWPORT_HINT_LIMIT} ? geometryOf(el) : null };
|
|
231
240
|
} catch (e) {
|
|
232
241
|
return {};
|
|
233
242
|
}
|
|
234
243
|
};
|
|
235
|
-
return { viewport: (${VIEWPORT_SIZE_JS})(window), elements: Array.from(this, read) };
|
|
244
|
+
return { total: this.length, viewport: (${VIEWPORT_SIZE_JS})(window), elements: Array.from(Array.prototype.slice.call(this, 0, count), read) };
|
|
236
245
|
}`;
|
|
246
|
+
/** Page-side: the first `count` elements of the array, as a new array */
|
|
247
|
+
const SLICE_FUNCTION = `function (count) { return Array.prototype.slice.call(this, 0, count); }`;
|
|
237
248
|
/**
|
|
238
|
-
*
|
|
239
|
-
* lives (empty for the main document),
|
|
249
|
+
* The first `count` elements of a page-side array with their backend node
|
|
250
|
+
* ids, tag and attributes, where each lives (empty for the main document),
|
|
251
|
+
* its text and its viewport position, and how many elements the array has.
|
|
240
252
|
*
|
|
241
253
|
* @param arrayObjectId - Remote object id of the array
|
|
242
|
-
* @
|
|
254
|
+
* @param count - Elements to describe (0 = all)
|
|
255
|
+
* @returns Elements in array order (ones that cannot be described are left
|
|
256
|
+
* out) and the array's length
|
|
243
257
|
* @throws CommandError (91) when the page could not describe the matches
|
|
244
258
|
*/
|
|
245
|
-
async function elementsWithDetails(arrayObjectId) {
|
|
246
|
-
const
|
|
247
|
-
const
|
|
248
|
-
|
|
259
|
+
async function elementsWithDetails(arrayObjectId, count) {
|
|
260
|
+
const limit = count === 0 ? Number.MAX_SAFE_INTEGER : count;
|
|
261
|
+
const { total = 0, viewport, elements = [] } = await readPageDetails(arrayObjectId, limit);
|
|
262
|
+
const ids = await elementBackendNodeIds(total > limit ? await sliceArray(arrayObjectId, limit) : arrayObjectId);
|
|
263
|
+
const described = ids.flatMap((backendNodeId, index) => {
|
|
249
264
|
if (backendNodeId === undefined)
|
|
250
265
|
return [];
|
|
251
266
|
const details = elements[index];
|
|
252
267
|
return [
|
|
253
268
|
{
|
|
254
269
|
backendNodeId,
|
|
270
|
+
tag: details?.tag ?? '',
|
|
271
|
+
attributes: details?.attributes ?? [],
|
|
255
272
|
context: details?.context ?? '',
|
|
256
273
|
text: details?.text ?? '',
|
|
257
274
|
state: details?.state ?? {},
|
|
@@ -259,18 +276,44 @@ async function elementsWithDetails(arrayObjectId) {
|
|
|
259
276
|
},
|
|
260
277
|
];
|
|
261
278
|
});
|
|
279
|
+
return { total, elements: described };
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* A page-side array of the first elements of another (in the same object
|
|
283
|
+
* group), so only those are described.
|
|
284
|
+
*
|
|
285
|
+
* @param arrayObjectId - Remote object id of the array
|
|
286
|
+
* @param count - Elements to keep
|
|
287
|
+
* @returns Remote object id of the shorter array
|
|
288
|
+
* @throws CommandError (91) when the page could not slice it
|
|
289
|
+
*/
|
|
290
|
+
async function sliceArray(arrayObjectId, count) {
|
|
291
|
+
const response = await callCDP('Runtime.callFunctionOn', {
|
|
292
|
+
objectId: arrayObjectId,
|
|
293
|
+
functionDeclaration: SLICE_FUNCTION,
|
|
294
|
+
arguments: [{ value: count }],
|
|
295
|
+
});
|
|
296
|
+
const objectId = response.data?.result
|
|
297
|
+
?.result?.objectId;
|
|
298
|
+
if (!objectId) {
|
|
299
|
+
const err = operationFailedError('describe the matches', response.error ?? 'no result');
|
|
300
|
+
throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.SCRIPT_ERROR);
|
|
301
|
+
}
|
|
302
|
+
return objectId;
|
|
262
303
|
}
|
|
263
304
|
/**
|
|
264
305
|
* Run {@link ELEMENT_DETAILS_FUNCTION} on a page-side array.
|
|
265
306
|
*
|
|
266
307
|
* @param arrayObjectId - Remote object id of the array
|
|
267
|
-
* @
|
|
308
|
+
* @param count - Elements to describe, from the start
|
|
309
|
+
* @returns Details of those elements, the array's length and the viewport size
|
|
268
310
|
* @throws CommandError (91) when the script failed
|
|
269
311
|
*/
|
|
270
|
-
async function readPageDetails(arrayObjectId) {
|
|
312
|
+
async function readPageDetails(arrayObjectId, count) {
|
|
271
313
|
const response = await callCDP('Runtime.callFunctionOn', {
|
|
272
314
|
objectId: arrayObjectId,
|
|
273
315
|
functionDeclaration: ELEMENT_DETAILS_FUNCTION,
|
|
316
|
+
arguments: [{ value: count }],
|
|
274
317
|
returnByValue: true,
|
|
275
318
|
});
|
|
276
319
|
const result = response.data?.result;
|
|
@@ -359,40 +402,49 @@ function mapConcurrently(items, mapper) {
|
|
|
359
402
|
return Promise.all(items.map((item, index) => limiter.run(() => mapper(item, index))));
|
|
360
403
|
}
|
|
361
404
|
/**
|
|
362
|
-
* Query elements by selector for `bdg dom query
|
|
405
|
+
* Query elements by selector for `bdg dom query`: all matches are counted,
|
|
406
|
+
* and the first `limit` (at least {@link QUERY_CACHE_LIMIT}, so indices past
|
|
407
|
+
* the listed ones still work) are described in one page-side pass plus one
|
|
408
|
+
* `DOM.describeNode` each for their backend node id.
|
|
363
409
|
*
|
|
364
410
|
* @param selector - CSS selector
|
|
365
|
-
* @
|
|
411
|
+
* @param limit - Matches the caller lists (0 = all, so all are described)
|
|
412
|
+
* @returns The match count, and the described matches with backend node
|
|
413
|
+
* ids, tags, key attributes, classes and text previews
|
|
366
414
|
*/
|
|
367
|
-
export async function queryDOMElements(selector) {
|
|
368
|
-
const
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
|
|
415
|
+
export async function queryDOMElements(selector, limit = 0) {
|
|
416
|
+
const described = limit === 0 ? 0 : Math.max(limit, QUERY_CACHE_LIMIT);
|
|
417
|
+
const { total, elements } = await withSelection(selector, (arrayObjectId) => elementsWithDetails(arrayObjectId, described));
|
|
418
|
+
if (total > 20)
|
|
419
|
+
log.debug(`Queried ${total} elements with selector: ${selector}`);
|
|
420
|
+
return { selector, count: total, nodes: elements.map(queryNode) };
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* One described match as `dom query` reports it.
|
|
424
|
+
*
|
|
425
|
+
* @param element - Match with its page-side details
|
|
426
|
+
* @param index - Its position among the matches
|
|
427
|
+
* @returns Query node
|
|
428
|
+
*/
|
|
429
|
+
function queryNode(element, index) {
|
|
430
|
+
const { backendNodeId, context, text, state, inViewport, clippedBy } = element;
|
|
431
|
+
const attributes = unpackAttributes(element.attributes);
|
|
432
|
+
const classes = attributes['class']?.split(/\s+/).filter(Boolean);
|
|
433
|
+
const preview = textPreview(text);
|
|
434
|
+
const tag = element.tag.toLowerCase();
|
|
435
|
+
const keys = keyAttributes(tag, attributes, state);
|
|
436
|
+
return {
|
|
437
|
+
index,
|
|
438
|
+
nodeId: backendNodeId,
|
|
439
|
+
tag,
|
|
440
|
+
...identifyingAttributes(attributes, element.tag),
|
|
441
|
+
...(keys && { attributes: keys }),
|
|
442
|
+
...(classes && { classes }),
|
|
443
|
+
...(preview && { preview }),
|
|
444
|
+
...(context && { context }),
|
|
445
|
+
...(inViewport && { inViewport }),
|
|
446
|
+
...(clippedBy && { clippedBy }),
|
|
447
|
+
};
|
|
396
448
|
}
|
|
397
449
|
/**
|
|
398
450
|
* The attributes that tell similar elements apart (form fields especially).
|
|
@@ -420,7 +472,7 @@ function identifyingAttributes(attributes, nodeName) {
|
|
|
420
472
|
*/
|
|
421
473
|
async function elementTextAndState(ref, full) {
|
|
422
474
|
const objectGroup = `bdg-text-${process.pid}-${++queryCount}`;
|
|
423
|
-
const resolved = await
|
|
475
|
+
const resolved = await callBdgScript('DOM.resolveNode', { ...ref, objectGroup });
|
|
424
476
|
const objectId = resolved.data?.result?.object
|
|
425
477
|
.objectId;
|
|
426
478
|
if (!objectId)
|
|
@@ -478,33 +530,40 @@ export async function getDomContext(ref, options = {}) {
|
|
|
478
530
|
}
|
|
479
531
|
/** Child elements named for an element without text */
|
|
480
532
|
const CHILDREN_LISTED = 5;
|
|
533
|
+
/** Children that show nothing themselves, left out of the listing */
|
|
534
|
+
const UNSHOWN_CHILD = /^(SCRIPT|STYLE|LINK|TEMPLATE)$/;
|
|
481
535
|
/**
|
|
482
536
|
* The child elements of an element (for one without text: a body holding
|
|
483
|
-
* only an iframe, an empty app root).
|
|
537
|
+
* only an iframe, an empty app root). A web component is described by its
|
|
538
|
+
* shadow root's children, which is what it shows (an icon button named
|
|
539
|
+
* there); scripts, styles and templates are left out.
|
|
484
540
|
*
|
|
485
541
|
* @param ref - Node reference
|
|
486
|
-
* @returns The first {@link CHILDREN_LISTED} as `tag#id.class
|
|
542
|
+
* @returns The first {@link CHILDREN_LISTED} as `tag#id.class "label"`, how many there are, and whether they are in its shadow root
|
|
487
543
|
*/
|
|
488
544
|
async function childElements(ref) {
|
|
489
|
-
const response = await callCDP('DOM.describeNode', { ...ref, depth:
|
|
545
|
+
const response = await callCDP('DOM.describeNode', { ...ref, depth: 2, pierce: true });
|
|
490
546
|
const node = response.data?.result?.node;
|
|
491
|
-
const
|
|
547
|
+
const shadowRoot = node?.shadowRoots?.find((root) => root.shadowRootType !== 'user-agent');
|
|
548
|
+
const elements = ((shadowRoot ?? node)?.children ?? []).filter((child) => child.nodeType === 1 && !UNSHOWN_CHILD.test(child.nodeName));
|
|
492
549
|
return {
|
|
493
550
|
children: elements.slice(0, CHILDREN_LISTED).map(childLabel),
|
|
494
551
|
childCount: elements.length,
|
|
552
|
+
...(shadowRoot && { shadowChildren: true }),
|
|
495
553
|
};
|
|
496
554
|
}
|
|
497
555
|
/**
|
|
498
|
-
* A child element in a few characters.
|
|
556
|
+
* A child element in a few characters, with its aria-label when it has one.
|
|
499
557
|
*
|
|
500
558
|
* @param node - Child node
|
|
501
|
-
* @returns e.g. `iframe#app.full`
|
|
559
|
+
* @returns e.g. `iframe#app.full`, `button.icon "Close"`
|
|
502
560
|
*/
|
|
503
561
|
function childLabel(node) {
|
|
504
562
|
const attributes = unpackAttributes(node.attributes);
|
|
505
563
|
const id = attributes['id'] ? `#${attributes['id']}` : '';
|
|
506
|
-
const classes = (attributes['class'] ?? '').split(/\s+/).filter(
|
|
507
|
-
|
|
564
|
+
const classes = (attributes['class'] ?? '').split(/\s+/).filter(isLabelClass).slice(0, 2);
|
|
565
|
+
const label = attributes['aria-label']?.trim();
|
|
566
|
+
return `${node.nodeName.toLowerCase()}${id}${classes.map((name) => `.${name}`).join('')}${label ? ` "${label}"` : ''}`;
|
|
508
567
|
}
|
|
509
568
|
/**
|
|
510
569
|
* All matches of a selector.
|
|
@@ -629,7 +688,10 @@ export async function resolveBackendNodeIds(selectors) {
|
|
|
629
688
|
* @throws CommandError (87) when the element is gone
|
|
630
689
|
*/
|
|
631
690
|
export async function assertNodeAttached(backendNodeId, source) {
|
|
632
|
-
const resolved = await
|
|
691
|
+
const resolved = await callBdgScript('DOM.resolveNode', {
|
|
692
|
+
backendNodeId,
|
|
693
|
+
objectGroup: 'bdg-check',
|
|
694
|
+
});
|
|
633
695
|
const objectId = resolved.data?.result?.object
|
|
634
696
|
.objectId;
|
|
635
697
|
let attached = false;
|
|
@@ -648,4 +710,30 @@ export async function assertNodeAttached(backendNodeId, source) {
|
|
|
648
710
|
throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.STALE_CACHE);
|
|
649
711
|
}
|
|
650
712
|
}
|
|
713
|
+
/**
|
|
714
|
+
* Identity of the page's current document: its time origin, which every
|
|
715
|
+
* document load gets anew. Cached backend node ids belong to one document; a
|
|
716
|
+
* page loaded in a new renderer process reuses the same ids for other
|
|
717
|
+
* elements, so an index of an older document must not be used.
|
|
718
|
+
*
|
|
719
|
+
* @returns The identity, or undefined when the page cannot be asked
|
|
720
|
+
* @throws CommandError when the page is busy (102) or crashed (107), as the
|
|
721
|
+
* command itself would
|
|
722
|
+
*/
|
|
723
|
+
export async function pageDocumentId() {
|
|
724
|
+
try {
|
|
725
|
+
const evaluated = await callBdgScript('Runtime.evaluate', {
|
|
726
|
+
expression: 'String(performance.timeOrigin)',
|
|
727
|
+
returnByValue: true,
|
|
728
|
+
});
|
|
729
|
+
const { result } = (evaluated.data?.result ?? {});
|
|
730
|
+
return typeof result?.value === 'string' ? result.value : undefined;
|
|
731
|
+
}
|
|
732
|
+
catch (error) {
|
|
733
|
+
if (error instanceof CommandError)
|
|
734
|
+
throw error;
|
|
735
|
+
log.debug(`Page document not identified: ${getErrorMessage(error)}`);
|
|
736
|
+
return undefined;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
651
739
|
//# sourceMappingURL=query.js.map
|