browser-debugger-cli 0.12.0 → 0.14.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 +100 -186
- package/README.md +5 -4
- 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 +67 -13
- package/dist/commands/dom/DomElementResolver.d.ts +3 -1
- package/dist/commands/dom/DomElementResolver.js +10 -3
- package/dist/commands/dom/a11y.d.ts +1 -1
- package/dist/commands/dom/a11y.js +23 -22
- package/dist/commands/dom/eval.d.ts +4 -2
- package/dist/commands/dom/eval.js +31 -7
- package/dist/commands/dom/form.js +10 -9
- package/dist/commands/dom/formInteraction.js +9 -8
- package/dist/commands/dom/get.js +32 -14
- package/dist/commands/dom/helpers/index.d.ts +1 -1
- package/dist/commands/dom/helpers/index.js +1 -1
- 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.js +13 -13
- package/dist/commands/dom/index.js +10 -3
- package/dist/commands/dom/query.d.ts +20 -2
- package/dist/commands/dom/query.js +39 -6
- package/dist/commands/dom/screenshot.js +3 -1
- package/dist/commands/dom/semanticUtils.d.ts +3 -2
- package/dist/commands/dom/semanticUtils.js +40 -9
- 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 +65 -12
- package/dist/commands/optionBehaviors.d.ts +25 -2
- package/dist/commands/optionBehaviors.js +81 -46
- package/dist/commands/peek.js +3 -0
- package/dist/commands/shared/CommandRunner.js +13 -13
- package/dist/commands/shared/daemonErrorHandler.d.ts +5 -2
- package/dist/commands/shared/daemonErrorHandler.js +21 -10
- package/dist/commands/shared/dataFetcher.d.ts +14 -4
- package/dist/commands/shared/dataFetcher.js +20 -4
- package/dist/commands/shared/followMode.d.ts +9 -1
- package/dist/commands/shared/followMode.js +22 -4
- package/dist/commands/shared/handleValidationError.js +3 -3
- package/dist/commands/shared/optionTypes.d.ts +17 -3
- package/dist/commands/shared/outputFile.js +6 -1
- package/dist/commands/shared/startHelpers.js +3 -3
- package/dist/commands/start.d.ts +7 -5
- package/dist/commands/start.js +65 -21
- package/dist/commands/stop.d.ts +11 -0
- package/dist/commands/stop.js +24 -1
- package/dist/commands.js +1 -1
- package/dist/connection/cdp.d.ts +7 -0
- package/dist/connection/cdp.js +9 -0
- package/dist/connection/chromeIdentity.d.ts +8 -2
- package/dist/connection/chromeIdentity.js +85 -13
- package/dist/connection/launcher.js +3 -2
- package/dist/constants.d.ts +29 -1
- package/dist/constants.js +35 -1
- package/dist/daemon/SessionController.js +8 -1
- package/dist/daemon/launcher.d.ts +3 -2
- package/dist/daemon/launcher.js +47 -3
- package/dist/daemon/session/Session.d.ts +5 -1
- package/dist/daemon/session/Session.js +42 -3
- package/dist/daemon/session/TelemetryStore.d.ts +15 -1
- package/dist/daemon/session/TelemetryStore.js +19 -1
- package/dist/daemon/session/commandRegistry.js +52 -18
- package/dist/daemon/session/interactions.d.ts +2 -1
- package/dist/daemon/session/interactions.js +13 -1
- package/dist/daemon/session/matchedStylesReset.d.ts +26 -0
- package/dist/daemon/session/matchedStylesReset.js +46 -0
- package/dist/daemon/session/plugins.js +17 -2
- package/dist/daemon/session/teardown.js +1 -1
- package/dist/daemon/session/triggeredRequests.d.ts +0 -5
- package/dist/daemon/session/triggeredRequests.js +13 -7
- package/dist/daemon.js +2385 -1229
- package/dist/errors/messages.d.ts +62 -11
- package/dist/errors/messages.js +119 -22
- package/dist/index.js +14995 -9866
- package/dist/ipc/client.d.ts +18 -2
- package/dist/ipc/client.js +26 -5
- package/dist/ipc/protocol/auditTypes.d.ts +8 -2
- package/dist/ipc/protocol/commands.d.ts +16 -0
- package/dist/ipc/protocol/domTypes.d.ts +12 -0
- package/dist/ipc/protocol/inspectTypes.d.ts +7 -2
- package/dist/ipc/session/types.d.ts +7 -1
- package/dist/program.d.ts +14 -0
- package/dist/program.js +53 -0
- package/dist/runtime/dom/actionEffects.d.ts +5 -1
- package/dist/runtime/dom/actionEffects.js +26 -14
- package/dist/runtime/dom/audit.js +3 -2
- package/dist/runtime/dom/auditModel.js +6 -1
- package/dist/runtime/dom/auditScripts.d.ts +9 -3
- package/dist/runtime/dom/auditScripts.js +41 -5
- package/dist/runtime/dom/elementGeometry.d.ts +33 -3
- package/dist/runtime/dom/elementGeometry.js +44 -19
- package/dist/runtime/dom/elementInfo.d.ts +76 -18
- package/dist/runtime/dom/elementInfo.js +190 -40
- 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 +7 -11
- 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/frameScopedConnection.d.ts +7 -0
- package/dist/runtime/dom/frameScopedConnection.js +2 -2
- package/dist/runtime/dom/inspect.d.ts +17 -3
- package/dist/runtime/dom/inspect.js +45 -32
- package/dist/runtime/dom/inspectAllStyles.js +1 -0
- package/dist/runtime/dom/inspectHints.d.ts +1 -1
- package/dist/runtime/dom/inspectModel.d.ts +5 -4
- package/dist/runtime/dom/inspectModel.js +7 -3
- package/dist/runtime/dom/inspectPaintModel.d.ts +2 -0
- package/dist/runtime/dom/inspectPaintModel.js +3 -1
- package/dist/runtime/dom/inspectRules.d.ts +29 -3
- package/dist/runtime/dom/inspectRules.js +205 -11
- package/dist/runtime/dom/inspectScripts.d.ts +29 -2
- package/dist/runtime/dom/inspectScripts.js +49 -10
- package/dist/runtime/dom/layout.d.ts +0 -2
- package/dist/runtime/dom/layout.js +10 -9
- package/dist/runtime/dom/reactEventHelpers.d.ts +17 -4
- package/dist/runtime/dom/reactEventHelpers.js +71 -28
- package/dist/runtime/dom/targetNode.d.ts +27 -10
- package/dist/runtime/dom/targetNode.js +283 -16
- 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/replacedBuiltins.d.ts +28 -0
- package/dist/runtime/page/replacedBuiltins.js +136 -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.d.ts +15 -1
- package/dist/telemetry/a11y.js +85 -2
- package/dist/telemetry/console.d.ts +2 -1
- package/dist/telemetry/console.js +30 -21
- package/dist/telemetry/har/builder.js +1 -1
- package/dist/telemetry/network.d.ts +13 -16
- package/dist/telemetry/network.js +30 -52
- package/dist/telemetry/networkRetention.d.ts +83 -0
- package/dist/telemetry/networkRetention.js +117 -0
- package/dist/telemetry/pageCrash.d.ts +26 -0
- package/dist/telemetry/pageCrash.js +53 -0
- package/dist/types.d.ts +42 -0
- package/dist/ui/OutputBuilder.d.ts +10 -0
- package/dist/ui/OutputBuilder.js +12 -0
- package/dist/ui/formatters/a11y.d.ts +5 -7
- package/dist/ui/formatters/a11y.js +7 -61
- package/dist/ui/formatters/audit.js +14 -5
- package/dist/ui/formatters/cdp.d.ts +138 -0
- package/dist/ui/formatters/cdp.js +131 -0
- package/dist/ui/formatters/console/chronological.js +7 -5
- package/dist/ui/formatters/console/follow.d.ts +5 -2
- package/dist/ui/formatters/console/follow.js +7 -4
- package/dist/ui/formatters/console/json.d.ts +4 -7
- package/dist/ui/formatters/console/json.js +16 -14
- package/dist/ui/formatters/console/shared.d.ts +47 -2
- package/dist/ui/formatters/console/shared.js +33 -0
- package/dist/ui/formatters/console/summarize.d.ts +9 -2
- package/dist/ui/formatters/console/summarize.js +57 -11
- package/dist/ui/formatters/console.d.ts +3 -2
- package/dist/ui/formatters/console.js +8 -10
- package/dist/ui/formatters/details.js +4 -2
- package/dist/ui/formatters/dom.d.ts +14 -5
- package/dist/ui/formatters/dom.js +30 -13
- package/dist/ui/formatters/helpFormatters.js +1 -1
- package/dist/ui/formatters/inspect.js +9 -3
- package/dist/ui/formatters/installSkill.d.ts +9 -1
- package/dist/ui/formatters/installSkill.js +32 -6
- package/dist/ui/formatters/layout.js +4 -2
- package/dist/ui/formatters/longValues.d.ts +14 -0
- package/dist/ui/formatters/longValues.js +23 -0
- package/dist/ui/formatters/networkList.d.ts +8 -2
- package/dist/ui/formatters/networkList.js +11 -3
- package/dist/ui/formatters/preview.d.ts +6 -1
- package/dist/ui/formatters/preview.js +67 -15
- package/dist/ui/formatters/sessions.d.ts +2 -2
- package/dist/ui/formatters/sessions.js +9 -2
- package/dist/ui/formatters/status.js +7 -0
- package/dist/ui/formatters/triggeredRequests.js +2 -1
- package/dist/ui/logging/logger.d.ts +1 -1
- package/dist/ui/messages/chrome.d.ts +20 -1
- package/dist/ui/messages/chrome.js +29 -3
- package/dist/ui/messages/commands.d.ts +153 -12
- package/dist/ui/messages/commands.js +198 -15
- package/dist/ui/messages/consoleMessages.d.ts +24 -0
- package/dist/ui/messages/consoleMessages.js +32 -0
- package/dist/ui/messages/networkMessages.d.ts +24 -0
- package/dist/ui/messages/networkMessages.js +45 -0
- package/dist/ui/messages/preview.d.ts +6 -0
- package/dist/ui/messages/preview.js +9 -1
- package/dist/ui/messages/session.d.ts +13 -2
- package/dist/ui/messages/session.js +22 -3
- 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/http.d.ts +9 -2
- package/dist/utils/http.js +4 -3
- package/dist/utils/process.d.ts +12 -0
- package/dist/utils/process.js +25 -0
- package/dist/utils/strings.d.ts +19 -0
- package/dist/utils/strings.js +16 -0
- package/package.json +2 -2
|
@@ -5,11 +5,12 @@
|
|
|
5
5
|
* validation state, and suggested commands for agent consumption.
|
|
6
6
|
*/
|
|
7
7
|
import { calculateSummary, orderForms, primaryButtonIndex } from './formSummary.js';
|
|
8
|
-
import { resolveBackendNodeIds } from './helpers/index.js';
|
|
8
|
+
import { pageDocumentId, resolveBackendNodeIds } from './helpers/index.js';
|
|
9
9
|
import { runCommand } from '../shared/CommandRunner.js';
|
|
10
10
|
import { jsonOption } from '../shared/commonOptions.js';
|
|
11
11
|
import { noFormsFoundError, formInIframeError } from '../../errors/messages.js';
|
|
12
12
|
import { domFormDiscover } from '../../ipc/client.js';
|
|
13
|
+
import { MASKED_VALUE } from '../../runtime/dom/elementInfo.js';
|
|
13
14
|
import { FORM_DISCOVERY_CACHE_SELECTOR, QueryCacheManager } from '../../session/QueryCacheManager.js';
|
|
14
15
|
import { formatFormDiscovery } from '../../ui/formatters/form.js';
|
|
15
16
|
import { createLogger } from '../../ui/logging/index.js';
|
|
@@ -84,16 +85,14 @@ function buildFieldState(raw) {
|
|
|
84
85
|
return 'empty';
|
|
85
86
|
}
|
|
86
87
|
/**
|
|
87
|
-
*
|
|
88
|
+
* The masked value of a sensitive field (the page script never sends its
|
|
89
|
+
* real value).
|
|
88
90
|
*
|
|
89
91
|
* @param raw - Raw field data
|
|
90
92
|
* @returns Masked value string
|
|
91
93
|
*/
|
|
92
94
|
function buildMaskedValue(raw) {
|
|
93
|
-
|
|
94
|
-
return '•'.repeat(Math.min(raw.value.length, 8));
|
|
95
|
-
}
|
|
96
|
-
return undefined;
|
|
95
|
+
return raw.value === MASKED_VALUE ? MASKED_VALUE : undefined;
|
|
97
96
|
}
|
|
98
97
|
/**
|
|
99
98
|
* Build interaction warning for non-native fields.
|
|
@@ -244,8 +243,9 @@ function transformForm(raw) {
|
|
|
244
243
|
* the selector later.
|
|
245
244
|
*
|
|
246
245
|
* @param forms - Discovered forms
|
|
246
|
+
* @param document - Identity of the page document they were found in, read before discovery
|
|
247
247
|
*/
|
|
248
|
-
async function cacheFormElements(forms) {
|
|
248
|
+
async function cacheFormElements(forms, document) {
|
|
249
249
|
const elements = forms.flatMap((form) => [...form.fields, ...form.buttons]);
|
|
250
250
|
const backendNodeIds = await resolveBackendNodeIds(elements.map((el) => el.selector)).catch((error) => {
|
|
251
251
|
log.debug(`Form fields not cached by node: ${getErrorMessage(error)}`);
|
|
@@ -259,7 +259,7 @@ async function cacheFormElements(forms) {
|
|
|
259
259
|
nodeId: backendNodeIds[i] ?? 0,
|
|
260
260
|
selector: el.selector,
|
|
261
261
|
})),
|
|
262
|
-
});
|
|
262
|
+
}, document);
|
|
263
263
|
log.debug(`Cached ${elements.length} form elements`);
|
|
264
264
|
}
|
|
265
265
|
/**
|
|
@@ -269,6 +269,7 @@ async function cacheFormElements(forms) {
|
|
|
269
269
|
*/
|
|
270
270
|
async function handleFormCommand(options) {
|
|
271
271
|
await runCommand(async () => {
|
|
272
|
+
const document = await pageDocumentId();
|
|
272
273
|
const response = await domFormDiscover();
|
|
273
274
|
if (response.status === 'error' || !response.data) {
|
|
274
275
|
return {
|
|
@@ -311,7 +312,7 @@ async function handleFormCommand(options) {
|
|
|
311
312
|
const allForms = orderForms(rawData.forms).map(transformForm);
|
|
312
313
|
const forms = options.all ? allForms : [allForms[0]];
|
|
313
314
|
// Cache ALL forms so global indices work with bdg dom fill/click
|
|
314
|
-
await cacheFormElements(allForms);
|
|
315
|
+
await cacheFormElements(allForms, document);
|
|
315
316
|
const result = {
|
|
316
317
|
formCount: rawData.forms.length,
|
|
317
318
|
selectedForm: 0,
|
|
@@ -10,14 +10,15 @@ import { InvalidArgumentError } from 'commander';
|
|
|
10
10
|
import { runElementCommand } from './helpers/runElementCommand.js';
|
|
11
11
|
import { runCommand } from '../shared/CommandRunner.js';
|
|
12
12
|
import { jsonOption, SELECTOR_OR_INDEX_ARGUMENT } from '../shared/commonOptions.js';
|
|
13
|
+
import { createErrorResult } from '../shared/dataFetcher.js';
|
|
13
14
|
import { integerOption } from '../shared/validation.js';
|
|
14
15
|
import { CommandError } from '../../errors/index.js';
|
|
15
|
-
import { VIA_LABEL_SUFFIX,
|
|
16
|
+
import { VIA_LABEL_SUFFIX, conflictingOptionsError, hoverOffWithTargetError, missingArgumentError, indexSourceText, internalError, scrollOptionsError, } from '../../errors/messages.js';
|
|
16
17
|
import { callCDP, domClick, domFill, domPressKey, domScroll, domSubmit } from '../../ipc/client.js';
|
|
17
18
|
import { findUnknownModifiers } from '../../runtime/dom/keyMapping.js';
|
|
18
19
|
import { formatTriggeredRequestLines, formatTriggeredRequestsTitle, } from '../../ui/formatters/triggeredRequests.js';
|
|
19
20
|
import { OutputFormatter } from '../../ui/formatting.js';
|
|
20
|
-
import { CLICK_RESULT_WAIT_HELP, HOVER_OFF_DONE, HOVER_USAGE, POINTER_ACTION_DONE, POINTER_ACTION_NOUN, actionStatusLine, dialogConsoleText, moreMessagesText, newMessageText, pageNavigationText, shownElementText, stillChangingNote, } from '../../ui/messages/commands.js';
|
|
21
|
+
import { CLICK_RESULT_WAIT_HELP, HOVER_OFF_DONE, HOVER_USAGE, POINTER_ACTION_DONE, POINTER_ACTION_NOUN, pointerScrollText, actionStatusLine, dialogConsoleText, moreMessagesText, newMessageText, pageNavigationText, shownElementText, stillChangingNote, } from '../../ui/messages/commands.js';
|
|
21
22
|
import { sessionCommand } from '../../ui/messages/sessionCommand.js';
|
|
22
23
|
import { EXIT_CODES } from '../../utils/exitCodes.js';
|
|
23
24
|
/** Help of `--strict` on click and hover */
|
|
@@ -57,7 +58,7 @@ export function registerFormInteractionCommands(program) {
|
|
|
57
58
|
.command('fill')
|
|
58
59
|
.description('Fill a form field with a value (React-compatible, waits for stability)')
|
|
59
60
|
.argument('<selectorOrIndex>', SELECTOR_OR_INDEX_ARGUMENT)
|
|
60
|
-
.argument('<value>', 'Value to fill (file inputs: paths separated by commas, "" clears)')
|
|
61
|
+
.argument('<value>', 'Value to fill (file inputs: local file paths, separated by commas, that are uploaded to the page; "" clears)')
|
|
61
62
|
.option('--index <n>', 'Element index if selector matches multiple (0-based)', integerOption(0))
|
|
62
63
|
.option('--no-blur', 'Do not blur after filling (keeps focus on element)')
|
|
63
64
|
.option('--no-wait', 'Skip waiting for network stability after fill')
|
|
@@ -288,12 +289,9 @@ async function hoverOff(selectorOrIndex, options) {
|
|
|
288
289
|
}, options, () => HOVER_OFF_DONE);
|
|
289
290
|
}
|
|
290
291
|
async function runPointerCommand(selectorOrIndex, options, action) {
|
|
292
|
+
const conflict = conflictingOptionsError('--double', '--right');
|
|
291
293
|
await runCommand(() => options.double && options.right
|
|
292
|
-
? Promise.resolve(
|
|
293
|
-
success: false,
|
|
294
|
-
error: conflictingOptionsMessage('--double', '--right'),
|
|
295
|
-
exitCode: EXIT_CODES.INVALID_ARGUMENTS,
|
|
296
|
-
})
|
|
294
|
+
? Promise.resolve(createErrorResult(conflict.message, EXIT_CODES.INVALID_ARGUMENTS, conflict.suggestion))
|
|
297
295
|
: runElementCommand({
|
|
298
296
|
selectorOrIndex,
|
|
299
297
|
index: options.index,
|
|
@@ -430,6 +428,9 @@ function formatClickOutput(result) {
|
|
|
430
428
|
...selectorRows(result),
|
|
431
429
|
elementRow(result),
|
|
432
430
|
['Method', result.method === 'dom' ? 'DOM events' : 'mouse events'],
|
|
431
|
+
...(result.scrolledBy
|
|
432
|
+
? [['Scrolled', pointerScrollText(result.scrolledBy)]]
|
|
433
|
+
: []),
|
|
433
434
|
], result, { action: POINTER_ACTION_NOUN[result.action ?? 'click'] }).build();
|
|
434
435
|
}
|
|
435
436
|
/**
|
package/dist/commands/dom/get.js
CHANGED
|
@@ -9,12 +9,14 @@ import { DomElementResolver } from './DomElementResolver.js';
|
|
|
9
9
|
import { getDOMElements, getDomContext, selectMatch, } from './helpers/index.js';
|
|
10
10
|
import { formatSemanticNodeWithContext, resolveNodeWithFallback, } from './semanticUtils.js';
|
|
11
11
|
import { runCommand } from '../shared/CommandRunner.js';
|
|
12
|
+
import { MAX_VALUE_LENGTH } from '../../constants.js';
|
|
12
13
|
import { CommandError } from '../../errors/index.js';
|
|
13
|
-
import {
|
|
14
|
+
import { conflictingOptionsError, indexWithIndexOptionError, nodeIdNotFoundError, optionRequiresError, } from '../../errors/messages.js';
|
|
14
15
|
import { resolveA11yNode } from '../../telemetry/a11y.js';
|
|
15
16
|
import { formatDomGet } from '../../ui/formatters/dom.js';
|
|
16
17
|
import { EXIT_CODES } from '../../utils/exitCodes.js';
|
|
17
18
|
import { filterDefined } from '../../utils/objects.js';
|
|
19
|
+
import { capLength } from '../../utils/strings.js';
|
|
18
20
|
/** What `bdg dom get` reads without a selector */
|
|
19
21
|
export const DOM_GET_DEFAULT_SELECTOR = 'body';
|
|
20
22
|
/**
|
|
@@ -58,7 +60,7 @@ async function semanticElement(backendNodeId, full) {
|
|
|
58
60
|
* Raw view: the element(s) with attributes and outer HTML.
|
|
59
61
|
*
|
|
60
62
|
* @param selectorOrIndex - CSS selector or cached index
|
|
61
|
-
* @param options - Command options (`--all`, `--index`)
|
|
63
|
+
* @param options - Command options (`--all`, `--index`, `--full`)
|
|
62
64
|
*/
|
|
63
65
|
async function handleRawGet(selectorOrIndex, options) {
|
|
64
66
|
await runCommand(async () => {
|
|
@@ -70,8 +72,27 @@ async function handleRawGet(selectorOrIndex, options) {
|
|
|
70
72
|
all: options.all,
|
|
71
73
|
nth: matchIndex(options),
|
|
72
74
|
});
|
|
73
|
-
return { success: true, data: await getDOMElements(getOptions) };
|
|
74
|
-
}, options, formatDomGet);
|
|
75
|
+
return { success: true, data: rawGetData(await getDOMElements(getOptions), options) };
|
|
76
|
+
}, options, (data) => formatDomGet(data, { full: options.full }));
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Raw elements for the output: in JSON each outer HTML is cut to
|
|
80
|
+
* {@link MAX_VALUE_LENGTH} characters with `truncatedFrom`, unless `--full`
|
|
81
|
+
* (human output is cut when formatted).
|
|
82
|
+
*
|
|
83
|
+
* @param result - Elements read
|
|
84
|
+
* @param options - `--json`, `--full`
|
|
85
|
+
* @returns Elements to output
|
|
86
|
+
*/
|
|
87
|
+
function rawGetData(result, options) {
|
|
88
|
+
if (!options.json || options.full)
|
|
89
|
+
return result;
|
|
90
|
+
return {
|
|
91
|
+
nodes: result.nodes.map((node) => {
|
|
92
|
+
const { text, truncatedFrom } = capLength(node.outerHTML ?? '', MAX_VALUE_LENGTH);
|
|
93
|
+
return truncatedFrom === undefined ? node : { ...node, outerHTML: text, truncatedFrom };
|
|
94
|
+
}),
|
|
95
|
+
};
|
|
75
96
|
}
|
|
76
97
|
/**
|
|
77
98
|
* Semantic view of the target.
|
|
@@ -99,23 +120,20 @@ function matchIndex(options) {
|
|
|
99
120
|
*
|
|
100
121
|
* @param selectorOrIndex - Selector or index argument
|
|
101
122
|
* @param options - Command options
|
|
102
|
-
* @returns What conflicts, or null
|
|
123
|
+
* @returns What conflicts and how to fix it, or null
|
|
103
124
|
*/
|
|
104
125
|
function getOptionsConflict(selectorOrIndex, options) {
|
|
105
126
|
if (options.nodeId !== undefined && selectorOrIndex !== undefined) {
|
|
106
|
-
return
|
|
107
|
-
}
|
|
108
|
-
if (options.full && (options.raw || options.nodeId !== undefined)) {
|
|
109
|
-
return conflictingOptionsMessage('--full', options.raw ? '--raw' : '--node-id');
|
|
127
|
+
return conflictingOptionsError('--node-id', 'a selector or index');
|
|
110
128
|
}
|
|
111
129
|
if (options.index !== undefined && options.nth !== undefined) {
|
|
112
|
-
return
|
|
130
|
+
return conflictingOptionsError('--index', '--nth');
|
|
113
131
|
}
|
|
114
132
|
if (options.all && matchIndex(options) !== undefined) {
|
|
115
|
-
return
|
|
133
|
+
return conflictingOptionsError('--all', '--index');
|
|
116
134
|
}
|
|
117
135
|
if (options.all && !options.raw)
|
|
118
|
-
return
|
|
136
|
+
return optionRequiresError('--all', '--raw');
|
|
119
137
|
return null;
|
|
120
138
|
}
|
|
121
139
|
/**
|
|
@@ -130,11 +148,11 @@ function getOptionsConflict(selectorOrIndex, options) {
|
|
|
130
148
|
export async function handleDomGet(selectorOrIndex, options) {
|
|
131
149
|
const conflict = getOptionsConflict(selectorOrIndex, options);
|
|
132
150
|
if (conflict) {
|
|
133
|
-
throw new CommandError(conflict, {}, EXIT_CODES.INVALID_ARGUMENTS);
|
|
151
|
+
throw new CommandError(conflict.message, { suggestion: conflict.suggestion }, EXIT_CODES.INVALID_ARGUMENTS);
|
|
134
152
|
}
|
|
135
153
|
const { nodeId } = options;
|
|
136
154
|
if (nodeId !== undefined) {
|
|
137
|
-
await runCommand(async () => ({ success: true, data: await getDOMElements({ nodeId }) }), options, formatDomGet);
|
|
155
|
+
await runCommand(async () => ({ success: true, data: rawGetData(await getDOMElements({ nodeId }), options) }), options, (data) => formatDomGet(data, { full: options.full }));
|
|
138
156
|
return;
|
|
139
157
|
}
|
|
140
158
|
const target = selectorOrIndex ?? DOM_GET_DEFAULT_SELECTOR;
|
|
@@ -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
|
|
@@ -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
|