browser-debugger-cli 0.8.0 → 0.9.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/cdp/schema.d.ts +4 -1
- package/dist/cdp/schema.js +48 -7
- package/dist/commands/cdp.js +3 -2
- package/dist/commands/cleanup.d.ts +11 -0
- package/dist/commands/cleanup.js +161 -57
- package/dist/commands/console.d.ts +20 -1
- package/dist/commands/console.js +57 -17
- package/dist/commands/details.js +3 -2
- package/dist/commands/dom/DomElementResolver.d.ts +10 -3
- package/dist/commands/dom/DomElementResolver.js +35 -17
- package/dist/commands/dom/a11y.d.ts +10 -0
- package/dist/commands/dom/a11y.js +27 -5
- package/dist/commands/dom/eval.d.ts +3 -1
- package/dist/commands/dom/eval.js +29 -4
- package/dist/commands/dom/form.js +16 -62
- package/dist/commands/dom/formInteraction.js +152 -113
- package/dist/commands/dom/formSummary.d.ts +49 -0
- package/dist/commands/dom/formSummary.js +180 -0
- package/dist/commands/dom/frames.d.ts +2 -1
- package/dist/commands/dom/frames.js +17 -2
- package/dist/commands/dom/get.d.ts +6 -5
- package/dist/commands/dom/get.js +92 -82
- 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 +44 -17
- package/dist/commands/dom/helpers/query.js +244 -97
- package/dist/commands/dom/helpers/runElementCommand.d.ts +10 -2
- package/dist/commands/dom/helpers/runElementCommand.js +97 -30
- package/dist/commands/dom/helpers/screenshot.d.ts +4 -1
- package/dist/commands/dom/helpers/screenshot.js +164 -49
- package/dist/commands/dom/index.d.ts +3 -1
- package/dist/commands/dom/index.js +16 -6
- package/dist/commands/dom/layout.d.ts +14 -0
- package/dist/commands/dom/layout.js +54 -0
- package/dist/commands/dom/listeners.d.ts +5 -1
- package/dist/commands/dom/listeners.js +13 -3
- package/dist/commands/dom/query.js +2 -3
- package/dist/commands/dom/screenshot.d.ts +12 -2
- package/dist/commands/dom/screenshot.js +27 -3
- package/dist/commands/dom/semanticUtils.d.ts +6 -13
- package/dist/commands/dom/semanticUtils.js +15 -19
- package/dist/commands/dom/wait.d.ts +13 -0
- package/dist/commands/dom/wait.js +83 -0
- package/dist/commands/helpJson.js +2 -2
- package/dist/commands/network/list.js +4 -11
- package/dist/commands/optionBehaviors.js +112 -21
- package/dist/commands/page.d.ts +2 -1
- package/dist/commands/page.js +41 -5
- package/dist/commands/peek.js +4 -11
- package/dist/commands/sessions.d.ts +8 -0
- package/dist/commands/sessions.js +19 -0
- package/dist/commands/shared/CommandRunner.js +4 -4
- package/dist/commands/shared/dataFetcher.js +2 -2
- package/dist/commands/shared/followMode.d.ts +21 -1
- package/dist/commands/shared/followMode.js +29 -2
- package/dist/commands/shared/handleValidationError.d.ts +2 -2
- package/dist/commands/shared/handleValidationError.js +12 -3
- package/dist/commands/shared/optionTypes.d.ts +40 -5
- package/dist/commands/shared/startHelpers.js +12 -3
- package/dist/commands/shared/validation.d.ts +3 -2
- package/dist/commands/shared/validation.js +4 -3
- package/dist/commands/start.d.ts +63 -0
- package/dist/commands/start.js +115 -15
- package/dist/commands/status.js +29 -7
- package/dist/commands/stop.js +7 -6
- package/dist/commands/tail.js +4 -11
- package/dist/commands/types.d.ts +2 -0
- package/dist/commands.js +2 -0
- package/dist/connection/chromeIdentity.d.ts +65 -0
- package/dist/connection/chromeIdentity.js +143 -0
- package/dist/connection/launcher/profilePreferences.d.ts +47 -0
- package/dist/connection/launcher/profilePreferences.js +151 -0
- package/dist/connection/launcher.d.ts +21 -2
- package/dist/connection/launcher.js +42 -16
- package/dist/connection/portReservation.d.ts +14 -4
- package/dist/connection/portReservation.js +21 -6
- package/dist/connection/startupExit.d.ts +8 -0
- package/dist/connection/startupExit.js +15 -6
- package/dist/constants.d.ts +6 -2
- package/dist/constants.js +9 -2
- package/dist/daemon/SessionController.js +23 -7
- package/dist/daemon/errors.d.ts +1 -1
- package/dist/daemon/errors.js +1 -1
- package/dist/daemon/launcher.d.ts +2 -1
- package/dist/daemon/launcher.js +5 -6
- package/dist/daemon/server/SocketServer.js +1 -2
- package/dist/daemon/session/Session.d.ts +13 -0
- package/dist/daemon/session/Session.js +57 -8
- package/dist/daemon/session/chromeConnection.d.ts +9 -0
- package/dist/daemon/session/chromeConnection.js +45 -8
- package/dist/daemon/session/commandRegistry.js +52 -62
- package/dist/daemon/session/interactions.d.ts +35 -9
- package/dist/daemon/session/interactions.js +36 -9
- package/dist/daemon/session/triggeredRequests.d.ts +67 -0
- package/dist/daemon/session/triggeredRequests.js +157 -0
- package/dist/daemon/session/types.d.ts +5 -1
- package/dist/daemon.js +5393 -1600
- package/dist/errors/messages.d.ts +387 -24
- package/dist/errors/messages.js +761 -67
- package/dist/index.js +3976 -1558
- package/dist/ipc/client.d.ts +12 -1
- package/dist/ipc/client.js +22 -3
- package/dist/ipc/protocol/commands.d.ts +89 -4
- package/dist/ipc/protocol/commands.js +2 -0
- package/dist/ipc/protocol/domTypes.d.ts +258 -7
- package/dist/ipc/session/lifecycle.d.ts +8 -1
- package/dist/ipc/session/queries.d.ts +5 -1
- package/dist/ipc/session/types.d.ts +5 -0
- package/dist/ipc/transport/index.d.ts +2 -1
- package/dist/ipc/transport/index.js +2 -2
- package/dist/runtime/dom/actionEffects.d.ts +106 -0
- package/dist/runtime/dom/actionEffects.js +256 -0
- package/dist/runtime/dom/actionEffectsScripts.d.ts +52 -0
- package/dist/runtime/dom/actionEffectsScripts.js +234 -0
- package/dist/runtime/dom/elementGeometry.d.ts +170 -0
- package/dist/runtime/dom/elementGeometry.js +553 -0
- package/dist/runtime/dom/elementInfo.d.ts +77 -0
- package/dist/runtime/dom/elementInfo.js +191 -0
- package/dist/runtime/dom/evalHelpers.d.ts +51 -6
- package/dist/runtime/dom/evalHelpers.js +136 -26
- package/dist/runtime/dom/eventListeners.d.ts +2 -1
- package/dist/runtime/dom/eventListeners.js +174 -47
- package/dist/runtime/dom/formDiscovery.d.ts +1 -1
- package/dist/runtime/dom/formDiscovery.js +116 -16
- package/dist/runtime/dom/formFillHelpers/fill.d.ts +10 -0
- package/dist/runtime/dom/formFillHelpers/fill.js +125 -10
- package/dist/runtime/dom/formFillHelpers/index.d.ts +2 -2
- package/dist/runtime/dom/formFillHelpers/index.js +2 -2
- package/dist/runtime/dom/formFillHelpers/pressKey.js +14 -3
- package/dist/runtime/dom/formFillHelpers/scroll.d.ts +3 -0
- package/dist/runtime/dom/formFillHelpers/scroll.js +60 -18
- package/dist/runtime/dom/formFillHelpers/shared.d.ts +17 -0
- package/dist/runtime/dom/formFillHelpers/shared.js +25 -1
- package/dist/runtime/dom/formFillHelpers/stability.d.ts +20 -6
- package/dist/runtime/dom/formFillHelpers/stability.js +50 -19
- package/dist/runtime/dom/formSubmitHelpers.d.ts +3 -0
- package/dist/runtime/dom/formSubmitHelpers.js +89 -15
- package/dist/runtime/dom/frameLayout.d.ts +60 -0
- package/dist/runtime/dom/frameLayout.js +140 -0
- package/dist/runtime/dom/frameOrigin.d.ts +50 -0
- package/dist/runtime/dom/frameOrigin.js +62 -0
- package/dist/runtime/dom/frameScopedConnection.d.ts +92 -0
- package/dist/runtime/dom/frameScopedConnection.js +252 -0
- package/dist/runtime/dom/frameSelection.d.ts +1 -1
- package/dist/runtime/dom/frameSelection.js +2 -2
- package/dist/runtime/dom/frames.d.ts +25 -2
- package/dist/runtime/dom/frames.js +202 -63
- package/dist/runtime/dom/layout.d.ts +67 -0
- package/dist/runtime/dom/layout.js +333 -0
- package/dist/runtime/dom/listenerPageScripts.d.ts +66 -0
- package/dist/runtime/dom/listenerPageScripts.js +279 -0
- package/dist/runtime/dom/listenerSummary.d.ts +132 -11
- package/dist/runtime/dom/listenerSummary.js +344 -22
- package/dist/runtime/dom/pageActivity.d.ts +41 -0
- package/dist/runtime/dom/pageActivity.js +123 -0
- package/dist/runtime/dom/reactEventHelpers.d.ts +58 -2
- package/dist/runtime/dom/reactEventHelpers.js +212 -41
- package/dist/runtime/dom/targetNode.d.ts +80 -27
- package/dist/runtime/dom/targetNode.js +249 -33
- package/dist/runtime/dom/wait.d.ts +25 -0
- package/dist/runtime/dom/wait.js +199 -0
- package/dist/runtime/dom/waitCondition.d.ts +71 -0
- package/dist/runtime/dom/waitCondition.js +75 -0
- package/dist/runtime/page/emulation.d.ts +51 -0
- package/dist/runtime/page/emulation.js +80 -0
- package/dist/runtime/page/loadingState.d.ts +36 -0
- package/dist/runtime/page/loadingState.js +86 -0
- package/dist/runtime/page/navigation.d.ts +46 -2
- package/dist/runtime/page/navigation.js +69 -33
- package/dist/session/QueryCacheManager.d.ts +11 -1
- package/dist/session/QueryCacheManager.js +25 -3
- package/dist/session/chromeOwners.d.ts +34 -0
- package/dist/session/chromeOwners.js +51 -0
- package/dist/session/cleanup/staleSession.d.ts +11 -1
- package/dist/session/cleanup/staleSession.js +17 -6
- package/dist/session/cleanup/userCommands.js +2 -4
- package/dist/session/metadata.d.ts +5 -1
- package/dist/session/metadata.js +2 -1
- package/dist/session/paths.d.ts +77 -3
- package/dist/session/paths.js +111 -5
- package/dist/session/port.d.ts +31 -7
- package/dist/session/port.js +50 -43
- package/dist/session/portClaims.d.ts +66 -0
- package/dist/session/portClaims.js +284 -0
- package/dist/session/sessionList.d.ts +58 -0
- package/dist/session/sessionList.js +199 -0
- package/dist/session/sessionName.d.ts +46 -0
- package/dist/session/sessionName.js +97 -0
- package/dist/telemetry/a11y.d.ts +8 -3
- package/dist/telemetry/a11y.js +92 -28
- package/dist/telemetry/requestKinds.d.ts +32 -0
- package/dist/telemetry/requestKinds.js +61 -0
- package/dist/telemetry/requestState.d.ts +31 -0
- package/dist/telemetry/requestState.js +38 -0
- package/dist/types.d.ts +80 -3
- package/dist/ui/formatters/a11y.js +3 -0
- package/dist/ui/formatters/console/chronological.d.ts +8 -0
- package/dist/ui/formatters/console/chronological.js +17 -4
- package/dist/ui/formatters/console/json.js +3 -4
- package/dist/ui/formatters/console/shared.d.ts +12 -0
- package/dist/ui/formatters/console.d.ts +2 -2
- package/dist/ui/formatters/console.js +1 -1
- package/dist/ui/formatters/details.js +2 -1
- package/dist/ui/formatters/dom.d.ts +26 -14
- package/dist/ui/formatters/dom.js +65 -52
- package/dist/ui/formatters/form.js +29 -18
- package/dist/ui/formatters/layout.d.ts +31 -0
- package/dist/ui/formatters/layout.js +53 -0
- package/dist/ui/formatters/listeners.d.ts +3 -2
- package/dist/ui/formatters/listeners.js +73 -9
- package/dist/ui/formatters/networkHeaders.js +13 -0
- package/dist/ui/formatters/preview.js +2 -1
- package/dist/ui/formatters/requestStatus.d.ts +1 -17
- package/dist/ui/formatters/requestStatus.js +2 -30
- package/dist/ui/formatters/sessions.d.ts +12 -0
- package/dist/ui/formatters/sessions.js +40 -0
- package/dist/ui/formatters/status.d.ts +21 -2
- package/dist/ui/formatters/status.js +47 -10
- package/dist/ui/formatters/triggeredRequests.d.ts +36 -0
- package/dist/ui/formatters/triggeredRequests.js +65 -0
- package/dist/ui/formatting.d.ts +10 -0
- package/dist/ui/formatting.js +28 -36
- package/dist/ui/messages/chrome.d.ts +9 -0
- package/dist/ui/messages/chrome.js +17 -5
- package/dist/ui/messages/commands.d.ts +388 -14
- package/dist/ui/messages/commands.js +664 -21
- package/dist/ui/messages/consoleMessages.d.ts +10 -0
- package/dist/ui/messages/consoleMessages.js +17 -0
- package/dist/ui/messages/hints.js +2 -1
- package/dist/ui/messages/preview.js +5 -4
- package/dist/ui/messages/session.d.ts +16 -21
- package/dist/ui/messages/session.js +28 -26
- package/dist/ui/messages/sessionCommand.d.ts +43 -0
- package/dist/ui/messages/sessionCommand.js +52 -0
- package/dist/utils/async.d.ts +8 -0
- package/dist/utils/async.js +19 -0
- package/dist/utils/http.d.ts +22 -1
- package/dist/utils/http.js +28 -9
- package/dist/utils/selectorFilters.d.ts +36 -8
- package/dist/utils/selectorFilters.js +267 -53
- package/dist/utils/shellDetection.d.ts +8 -2
- package/dist/utils/shellDetection.js +120 -33
- package/dist/utils/suggestions.d.ts +26 -0
- package/dist/utils/suggestions.js +73 -0
- package/dist/utils/taskMappings.js +10 -0
- package/dist/utils/url.d.ts +12 -2
- package/dist/utils/url.js +69 -7
- package/package.json +1 -1
- package/dist/ui/formatters/sessionFormatters.d.ts +0 -58
- package/dist/ui/formatters/sessionFormatters.js +0 -121
|
@@ -5,15 +5,18 @@
|
|
|
5
5
|
* Keeps individual command handlers focused on option wiring and output formatting.
|
|
6
6
|
*/
|
|
7
7
|
import { DomElementResolver } from '../DomElementResolver.js';
|
|
8
|
-
import {
|
|
8
|
+
import { noMatchContext } from './query.js';
|
|
9
|
+
import { otherIndexSourceNote, staleNodeError, unreachableElementsNote, withLoadingHint, } from '../../../errors/messages.js';
|
|
10
|
+
import { joinLines } from '../../../ui/formatting.js';
|
|
9
11
|
import { EXIT_CODES } from '../../../utils/exitCodes.js';
|
|
10
12
|
/**
|
|
11
13
|
* Resolve an element target, invoke the IPC call, and normalize failures
|
|
12
|
-
* into the structured `CommandRunner` result shape.
|
|
14
|
+
* into the structured `CommandRunner` result shape. A numeric index names the
|
|
15
|
+
* list it refers to (`indexSource` in the data, and in errors).
|
|
13
16
|
*/
|
|
14
17
|
export async function runElementCommand(options) {
|
|
15
|
-
const { selectorOrIndex, index, buildRequest, call
|
|
16
|
-
const target = await DomElementResolver.getInstance().resolve(selectorOrIndex, index);
|
|
18
|
+
const { selectorOrIndex, index, command, buildRequest, call } = options;
|
|
19
|
+
const target = await DomElementResolver.getInstance().resolve(selectorOrIndex, index, command);
|
|
17
20
|
if (!target.success) {
|
|
18
21
|
return {
|
|
19
22
|
success: false,
|
|
@@ -28,36 +31,100 @@ export async function runElementCommand(options) {
|
|
|
28
31
|
...(target.backendNodeId !== undefined && { backendNodeId: target.backendNodeId }),
|
|
29
32
|
});
|
|
30
33
|
const response = await call(request);
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
exitCode: response.exitCode ?? EXIT_CODES.INVALID_ARGUMENTS,
|
|
39
|
-
...(response.suggestion && { errorContext: { suggestion: response.suggestion } }),
|
|
40
|
-
};
|
|
34
|
+
const failure = response.status === 'error' || !response.data
|
|
35
|
+
? errorResponseFailure(response, options)
|
|
36
|
+
: response.data.success
|
|
37
|
+
? undefined
|
|
38
|
+
: failedResultFailure(response.data, options);
|
|
39
|
+
if (failure && target.source) {
|
|
40
|
+
return indexFailure(failure, target.source, target.preview, response.data);
|
|
41
41
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
42
|
+
if (failure)
|
|
43
|
+
return withNotFoundContext(failure, target.selector, response.status !== 'error');
|
|
44
|
+
const { success: _success, ...data } = response.data;
|
|
45
|
+
return { success: true, data: { ...data, ...(target.source && { indexSource: target.source }) } };
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* A failure on a cached index, told in terms of the index: a stale element
|
|
49
|
+
* (87) names the index and the command that refreshes it, and an element a
|
|
50
|
+
* form command cannot act on, from the results of another command, gets a
|
|
51
|
+
* note on which list the index refers to.
|
|
52
|
+
*
|
|
53
|
+
* @param failure - Failed command result
|
|
54
|
+
* @param source - The index and the list it refers to
|
|
55
|
+
* @param preview - What the cached element was when listed
|
|
56
|
+
* @param result - Action result, when the daemon answered
|
|
57
|
+
* @returns The failure in terms of the index
|
|
58
|
+
*/
|
|
59
|
+
function indexFailure(failure, source, preview, result) {
|
|
60
|
+
if (failure.exitCode === EXIT_CODES.STALE_CACHE) {
|
|
61
|
+
const err = staleNodeError(source.index, source);
|
|
49
62
|
return {
|
|
50
63
|
success: false,
|
|
51
|
-
error:
|
|
52
|
-
exitCode,
|
|
53
|
-
errorContext: {
|
|
54
|
-
suggestion: exitCode === EXIT_CODES.RESOURCE_NOT_FOUND
|
|
55
|
-
? `${suggestion} (${UNREACHABLE_ELEMENTS_HINT})`
|
|
56
|
-
: suggestion,
|
|
57
|
-
},
|
|
64
|
+
error: err.message,
|
|
65
|
+
exitCode: EXIT_CODES.STALE_CACHE,
|
|
66
|
+
errorContext: { suggestion: err.suggestion },
|
|
58
67
|
};
|
|
59
68
|
}
|
|
60
|
-
|
|
61
|
-
|
|
69
|
+
if (!result?.unsuitableElement || source.command === 'dom form')
|
|
70
|
+
return failure;
|
|
71
|
+
const note = otherIndexSourceNote(source, preview);
|
|
72
|
+
return {
|
|
73
|
+
...failure,
|
|
74
|
+
errorContext: { suggestion: joinLines(failure.errorContext?.suggestion, note) },
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Failure for an error response of the daemon.
|
|
79
|
+
*
|
|
80
|
+
* @param response - Error response
|
|
81
|
+
* @param options - Command options (selector or index, action)
|
|
82
|
+
* @returns Failed command result
|
|
83
|
+
*/
|
|
84
|
+
function errorResponseFailure(response, options) {
|
|
85
|
+
return {
|
|
86
|
+
success: false,
|
|
87
|
+
error: response.error ?? `Failed to ${options.action}`,
|
|
88
|
+
exitCode: response.exitCode ?? EXIT_CODES.INVALID_ARGUMENTS,
|
|
89
|
+
...(response.suggestion && { errorContext: { suggestion: response.suggestion } }),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Failure for an action whose page script reported failure.
|
|
94
|
+
*
|
|
95
|
+
* @param result - Action result
|
|
96
|
+
* @param options - Command options (action, fallback suggestion)
|
|
97
|
+
* @returns Failed command result
|
|
98
|
+
*/
|
|
99
|
+
function failedResultFailure(result, options) {
|
|
100
|
+
const exitCode = result.exitCode ??
|
|
101
|
+
(result.error?.includes('not found')
|
|
102
|
+
? EXIT_CODES.RESOURCE_NOT_FOUND
|
|
103
|
+
: EXIT_CODES.INVALID_ARGUMENTS);
|
|
104
|
+
return {
|
|
105
|
+
success: false,
|
|
106
|
+
error: result.error ?? `Failed to ${options.action}`,
|
|
107
|
+
exitCode,
|
|
108
|
+
errorContext: { suggestion: result.suggestion ?? options.failureSuggestion },
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Add what the page says to a "not found" failure (one page evaluation, on
|
|
113
|
+
* this failure path only, {@link noMatchContext}): similar ids or classes,
|
|
114
|
+
* the places selectors do not search (for a page script that found nothing)
|
|
115
|
+
* and the still-loading hint while the page loads.
|
|
116
|
+
*
|
|
117
|
+
* @param failure - Failed command result
|
|
118
|
+
* @param selector - Selector that was looked for (the cached query's for an index)
|
|
119
|
+
* @param searched - The page script searched the page (the daemon did not fail first)
|
|
120
|
+
* @returns The failure, with the context in its suggestion
|
|
121
|
+
*/
|
|
122
|
+
async function withNotFoundContext(failure, selector, searched) {
|
|
123
|
+
if (failure.exitCode !== EXIT_CODES.RESOURCE_NOT_FOUND)
|
|
124
|
+
return failure;
|
|
125
|
+
const context = await noMatchContext(selector);
|
|
126
|
+
const note = searched ? unreachableElementsNote(selector, context.unsearched) : '';
|
|
127
|
+
const suggestion = withLoadingHint(joinLines(context.similar, failure.errorContext?.suggestion, note ? note : undefined), context.readyState, selector);
|
|
128
|
+
return suggestion ? { ...failure, errorContext: { suggestion } } : failure;
|
|
62
129
|
}
|
|
63
130
|
//# sourceMappingURL=runElementCommand.js.map
|
|
@@ -20,7 +20,10 @@ export declare function getElementBounds(ref: NodeRef): Promise<ElementBounds>;
|
|
|
20
20
|
*/
|
|
21
21
|
export declare function capturePageScreenshot(outputPath: string, options?: ScreenshotOptions): Promise<ScreenshotResult>;
|
|
22
22
|
/**
|
|
23
|
-
* Capture a screenshot of a single element,
|
|
23
|
+
* Capture a screenshot of a single element: its border box, grown to include
|
|
24
|
+
* content overflowing it ({@link captureArea}). The box model is relative to
|
|
25
|
+
* the viewport and the capture clip to the page, so the page scroll is added
|
|
26
|
+
* (the reported bounds are page coordinates, like `dom layout`'s).
|
|
24
27
|
*/
|
|
25
28
|
export declare function captureElementScreenshot(outputPath: string, ref: NodeRef, options?: {
|
|
26
29
|
format?: 'png' | 'jpeg';
|
|
@@ -11,6 +11,8 @@ import { CommandError } from '../../../errors/index.js';
|
|
|
11
11
|
import { noNodesFoundError, elementNotVisibleError, elementZeroDimensionsError, } from '../../../errors/messages.js';
|
|
12
12
|
import { callCDP } from '../../../ipc/client.js';
|
|
13
13
|
import { DEEP_QUERY_JS, selectorArgsJS } from '../../../runtime/dom/targetNode.js';
|
|
14
|
+
import { viewportOverride } from '../../../runtime/page/emulation.js';
|
|
15
|
+
import { readSessionMetadata } from '../../../session/metadata.js';
|
|
14
16
|
import { createLogger } from '../../../ui/logging/index.js';
|
|
15
17
|
import { EXIT_CODES } from '../../../utils/exitCodes.js';
|
|
16
18
|
const log = createLogger('dom');
|
|
@@ -136,6 +138,34 @@ async function restoreScrollPosition(position) {
|
|
|
136
138
|
returnByValue: true,
|
|
137
139
|
});
|
|
138
140
|
}
|
|
141
|
+
/**
|
|
142
|
+
* Capture at a pixel ratio of 1 (CSS px = image px) on a high-DPI display:
|
|
143
|
+
* the viewport is overridden at its size (the session's `--viewport`, else
|
|
144
|
+
* the visible one) until the returned function puts back what was there
|
|
145
|
+
* before, the session's viewport or none.
|
|
146
|
+
*
|
|
147
|
+
* @param devicePixelRatio - Page's pixel ratio
|
|
148
|
+
* @param viewport - Visible viewport size
|
|
149
|
+
* @returns Function restoring the device metrics
|
|
150
|
+
*/
|
|
151
|
+
async function useUnitPixelRatio(devicePixelRatio, viewport) {
|
|
152
|
+
if (devicePixelRatio === 1)
|
|
153
|
+
return () => Promise.resolve();
|
|
154
|
+
const sessionViewport = readSessionMetadata()?.viewport;
|
|
155
|
+
const size = sessionViewport ?? {
|
|
156
|
+
width: Math.round(viewport.clientWidth),
|
|
157
|
+
height: Math.round(viewport.clientHeight),
|
|
158
|
+
};
|
|
159
|
+
await callCDP('Emulation.setDeviceMetricsOverride', viewportOverride(size, 1));
|
|
160
|
+
return async () => {
|
|
161
|
+
if (sessionViewport) {
|
|
162
|
+
await callCDP('Emulation.setDeviceMetricsOverride', viewportOverride(sessionViewport));
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
await callCDP('Emulation.clearDeviceMetricsOverride', {});
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
}
|
|
139
169
|
/**
|
|
140
170
|
* Get the bounding box (border box, so padding and border are included) of an
|
|
141
171
|
* element via CDP DOM.getBoxModel.
|
|
@@ -161,6 +191,20 @@ export async function getElementBounds(ref) {
|
|
|
161
191
|
}
|
|
162
192
|
return { x, y, width, height };
|
|
163
193
|
}
|
|
194
|
+
/**
|
|
195
|
+
* Page coordinates of the visible area's top-left corner. A capture clip is
|
|
196
|
+
* in page coordinates, so a viewport capture must start at the scroll
|
|
197
|
+
* position, not at the page origin (which shows nothing once scrolled). Read
|
|
198
|
+
* after any metrics override, which can move the scroll position.
|
|
199
|
+
*
|
|
200
|
+
* @returns Scroll offset of the visual viewport in CSS pixels
|
|
201
|
+
*/
|
|
202
|
+
async function visibleAreaOrigin() {
|
|
203
|
+
const response = await callCDP('Page.getLayoutMetrics', {});
|
|
204
|
+
const metrics = response.data?.result;
|
|
205
|
+
const viewport = metrics?.cssVisualViewport;
|
|
206
|
+
return { x: viewport?.pageX ?? 0, y: viewport?.pageY ?? 0 };
|
|
207
|
+
}
|
|
164
208
|
/**
|
|
165
209
|
* Capture a screenshot of the page. Auto-resizes oversized pages by default
|
|
166
210
|
* to keep Claude Vision token cost bounded; falls back to viewport capture
|
|
@@ -193,13 +237,8 @@ export async function capturePageScreenshot(outputPath, options = {}) {
|
|
|
193
237
|
const scale = resized ? calculateResizeScale(captureWidth, captureHeight) : 1;
|
|
194
238
|
const finalWidth = Math.round(captureWidth * scale);
|
|
195
239
|
const finalHeight = Math.round(captureHeight * scale);
|
|
240
|
+
const restoreMetrics = await useUnitPixelRatio(devicePixelRatio, viewport);
|
|
196
241
|
if (devicePixelRatio !== 1) {
|
|
197
|
-
await callCDP('Emulation.setDeviceMetricsOverride', {
|
|
198
|
-
width: Math.round(viewport.clientWidth),
|
|
199
|
-
height: Math.round(viewport.clientHeight),
|
|
200
|
-
deviceScaleFactor: 1,
|
|
201
|
-
mobile: false,
|
|
202
|
-
});
|
|
203
242
|
if (options.scroll) {
|
|
204
243
|
await callCDP('Runtime.evaluate', {
|
|
205
244
|
expression: `(${DEEP_QUERY_JS})(${selectorArgsJS(options.scroll)})[0]?.scrollIntoView({ block: 'center', behavior: 'instant' })`,
|
|
@@ -207,18 +246,7 @@ export async function capturePageScreenshot(outputPath, options = {}) {
|
|
|
207
246
|
});
|
|
208
247
|
}
|
|
209
248
|
}
|
|
210
|
-
|
|
211
|
-
let clipY = 0;
|
|
212
|
-
if (useScroll && !effectiveFullPage) {
|
|
213
|
-
const scrollResponse = await callCDP('Runtime.evaluate', {
|
|
214
|
-
expression: 'JSON.stringify({ x: window.scrollX, y: window.scrollY })',
|
|
215
|
-
returnByValue: true,
|
|
216
|
-
});
|
|
217
|
-
const scrollPos = JSON.parse(scrollResponse.data?.result?.result?.value ??
|
|
218
|
-
'{"x":0,"y":0}');
|
|
219
|
-
clipX = scrollPos.x;
|
|
220
|
-
clipY = scrollPos.y;
|
|
221
|
-
}
|
|
249
|
+
const clipOrigin = effectiveFullPage ? { x: 0, y: 0 } : await visibleAreaOrigin();
|
|
222
250
|
let screenshotResult;
|
|
223
251
|
try {
|
|
224
252
|
const screenshotResponse = await callCDP('Page.captureScreenshot', {
|
|
@@ -226,8 +254,8 @@ export async function capturePageScreenshot(outputPath, options = {}) {
|
|
|
226
254
|
...(quality !== undefined && { quality }),
|
|
227
255
|
captureBeyondViewport: effectiveFullPage,
|
|
228
256
|
clip: {
|
|
229
|
-
x:
|
|
230
|
-
y:
|
|
257
|
+
x: clipOrigin.x,
|
|
258
|
+
y: clipOrigin.y,
|
|
231
259
|
width: captureWidth,
|
|
232
260
|
height: captureHeight,
|
|
233
261
|
scale,
|
|
@@ -236,9 +264,7 @@ export async function capturePageScreenshot(outputPath, options = {}) {
|
|
|
236
264
|
screenshotResult = screenshotResponse.data?.result;
|
|
237
265
|
}
|
|
238
266
|
finally {
|
|
239
|
-
|
|
240
|
-
await callCDP('Emulation.clearDeviceMetricsOverride', {});
|
|
241
|
-
}
|
|
267
|
+
await restoreMetrics();
|
|
242
268
|
}
|
|
243
269
|
if (!screenshotResult?.data) {
|
|
244
270
|
throw new CDPConnectionError('No screenshot data returned', new Error('Empty response'));
|
|
@@ -287,11 +313,98 @@ export async function capturePageScreenshot(outputPath, options = {}) {
|
|
|
287
313
|
}
|
|
288
314
|
return result;
|
|
289
315
|
}
|
|
316
|
+
/** Descendants {@link CONTENT_OVERFLOW_JS} looks at, so a huge element stays cheap */
|
|
317
|
+
const OVERFLOW_SCAN_LIMIT = 2000;
|
|
318
|
+
/**
|
|
319
|
+
* Page-side distances (CSS px, never negative) by which an element's rendered
|
|
320
|
+
* descendants reach beyond its border box on each side: uncleared floats,
|
|
321
|
+
* absolutely positioned and transformed children. Descendants of an element
|
|
322
|
+
* that clips its overflow (`overflow` other than `visible`) are cut off by it
|
|
323
|
+
* and not counted, nor are fixed ones (they belong to the viewport) or what
|
|
324
|
+
* lies outside the document (skip links at -9999px). Zero everywhere when the
|
|
325
|
+
* element clips its own overflow.
|
|
326
|
+
*/
|
|
327
|
+
const CONTENT_OVERFLOW_JS = `function () {
|
|
328
|
+
const view = this.ownerDocument.defaultView;
|
|
329
|
+
const scroller = this.ownerDocument.scrollingElement || this.ownerDocument.documentElement;
|
|
330
|
+
const own = this.getBoundingClientRect();
|
|
331
|
+
const reach = { left: own.left, top: own.top, right: own.right, bottom: own.bottom };
|
|
332
|
+
const page = { left: -view.scrollX, top: -view.scrollY, right: scroller.scrollWidth - view.scrollX, bottom: scroller.scrollHeight - view.scrollY };
|
|
333
|
+
const clips = (style) => style.overflowX !== 'visible' || style.overflowY !== 'visible';
|
|
334
|
+
let budget = ${OVERFLOW_SCAN_LIMIT};
|
|
335
|
+
const walk = (el) => {
|
|
336
|
+
for (const child of el.children) {
|
|
337
|
+
if (--budget < 0) return;
|
|
338
|
+
const style = view.getComputedStyle(child);
|
|
339
|
+
if (style.display === 'none' || style.position === 'fixed') continue;
|
|
340
|
+
const r = child.getBoundingClientRect();
|
|
341
|
+
if (r.width > 0 && r.height > 0 && style.visibility === 'visible') {
|
|
342
|
+
reach.left = Math.min(reach.left, Math.max(r.left, page.left));
|
|
343
|
+
reach.top = Math.min(reach.top, Math.max(r.top, page.top));
|
|
344
|
+
reach.right = Math.max(reach.right, Math.min(r.right, page.right));
|
|
345
|
+
reach.bottom = Math.max(reach.bottom, Math.min(r.bottom, page.bottom));
|
|
346
|
+
}
|
|
347
|
+
if (!clips(style)) walk(child);
|
|
348
|
+
}
|
|
349
|
+
};
|
|
350
|
+
if (!clips(view.getComputedStyle(this))) walk(this);
|
|
351
|
+
return { left: own.left - reach.left, top: own.top - reach.top, right: reach.right - own.right, bottom: reach.bottom - own.bottom };
|
|
352
|
+
}`;
|
|
353
|
+
/** Overflow (px) below which the capture keeps to the border box (subpixel rounding) */
|
|
354
|
+
const OVERFLOW_SLACK = 1;
|
|
355
|
+
/**
|
|
356
|
+
* Area an element screenshot captures: the border box, grown to the content
|
|
357
|
+
* that overflows it ({@link CONTENT_OVERFLOW_JS}), so floated children are
|
|
358
|
+
* not cropped away.
|
|
359
|
+
*
|
|
360
|
+
* @param ref - Node reference
|
|
361
|
+
* @param bounds - Border box (DOM.getBoxModel coordinates)
|
|
362
|
+
* @returns The area, or the border box when nothing overflows (or the page cannot be asked)
|
|
363
|
+
*/
|
|
364
|
+
async function captureArea(ref, bounds) {
|
|
365
|
+
const objectGroup = `bdg-shot-${process.pid}`;
|
|
366
|
+
try {
|
|
367
|
+
const resolved = await callCDP('DOM.resolveNode', { ...ref, objectGroup });
|
|
368
|
+
const objectId = resolved.data?.result?.object
|
|
369
|
+
.objectId;
|
|
370
|
+
if (!objectId)
|
|
371
|
+
return bounds;
|
|
372
|
+
const response = await callCDP('Runtime.callFunctionOn', {
|
|
373
|
+
objectId,
|
|
374
|
+
functionDeclaration: CONTENT_OVERFLOW_JS,
|
|
375
|
+
returnByValue: true,
|
|
376
|
+
});
|
|
377
|
+
const overflow = response.data?.result
|
|
378
|
+
?.result?.value;
|
|
379
|
+
if (!overflow)
|
|
380
|
+
return bounds;
|
|
381
|
+
const [left, top, right, bottom] = ['left', 'top', 'right', 'bottom'].map((side) => Math.max(0, overflow[side] ?? 0));
|
|
382
|
+
if (Math.max(left, top, right, bottom) <= OVERFLOW_SLACK)
|
|
383
|
+
return bounds;
|
|
384
|
+
return {
|
|
385
|
+
x: bounds.x - left,
|
|
386
|
+
y: bounds.y - top,
|
|
387
|
+
width: bounds.width + left + right,
|
|
388
|
+
height: bounds.height + top + bottom,
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
catch (error) {
|
|
392
|
+
log.debug(`Could not measure overflowing content: ${String(error)}`);
|
|
393
|
+
return bounds;
|
|
394
|
+
}
|
|
395
|
+
finally {
|
|
396
|
+
await callCDP('Runtime.releaseObjectGroup', { objectGroup }).catch(() => undefined);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
290
399
|
/**
|
|
291
|
-
* Capture a screenshot of a single element,
|
|
400
|
+
* Capture a screenshot of a single element: its border box, grown to include
|
|
401
|
+
* content overflowing it ({@link captureArea}). The box model is relative to
|
|
402
|
+
* the viewport and the capture clip to the page, so the page scroll is added
|
|
403
|
+
* (the reported bounds are page coordinates, like `dom layout`'s).
|
|
292
404
|
*/
|
|
293
405
|
export async function captureElementScreenshot(outputPath, ref, options = {}) {
|
|
294
|
-
const
|
|
406
|
+
const box = await getElementBounds(ref);
|
|
407
|
+
const bounds = await captureArea(ref, box);
|
|
295
408
|
const format = options.format ?? 'png';
|
|
296
409
|
const quality = format === 'jpeg' ? (options.quality ?? 90) : undefined;
|
|
297
410
|
const noResize = options.noResize ?? false;
|
|
@@ -309,34 +422,26 @@ export async function captureElementScreenshot(outputPath, ref, options = {}) {
|
|
|
309
422
|
const metricsResponse = await callCDP('Page.getLayoutMetrics', {});
|
|
310
423
|
const metricsResult = metricsResponse.data?.result;
|
|
311
424
|
const viewport = metricsResult?.visualViewport ?? { clientWidth: 800, clientHeight: 600 };
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
425
|
+
const scroll = metricsResult?.cssLayoutViewport ?? { pageX: 0, pageY: 0 };
|
|
426
|
+
const onPage = (area) => ({
|
|
427
|
+
...area,
|
|
428
|
+
x: area.x + scroll.pageX,
|
|
429
|
+
y: area.y + scroll.pageY,
|
|
430
|
+
});
|
|
431
|
+
const clip = onPage(bounds);
|
|
432
|
+
const restoreMetrics = await useUnitPixelRatio(devicePixelRatio, viewport);
|
|
320
433
|
let screenshotResult;
|
|
321
434
|
try {
|
|
322
435
|
const screenshotResponse = await callCDP('Page.captureScreenshot', {
|
|
323
436
|
format,
|
|
324
437
|
...(quality !== undefined && { quality }),
|
|
325
|
-
clip: {
|
|
326
|
-
x: bounds.x,
|
|
327
|
-
y: bounds.y,
|
|
328
|
-
width: bounds.width,
|
|
329
|
-
height: bounds.height,
|
|
330
|
-
scale,
|
|
331
|
-
},
|
|
438
|
+
clip: { ...clip, scale },
|
|
332
439
|
captureBeyondViewport: true,
|
|
333
440
|
});
|
|
334
441
|
screenshotResult = screenshotResponse.data?.result;
|
|
335
442
|
}
|
|
336
443
|
finally {
|
|
337
|
-
|
|
338
|
-
await callCDP('Emulation.clearDeviceMetricsOverride', {});
|
|
339
|
-
}
|
|
444
|
+
await restoreMetrics();
|
|
340
445
|
}
|
|
341
446
|
if (!screenshotResult?.data) {
|
|
342
447
|
throw new CDPConnectionError('No screenshot data returned', new Error('Empty response'));
|
|
@@ -352,12 +457,8 @@ export async function captureElementScreenshot(outputPath, ref, options = {}) {
|
|
|
352
457
|
fullPage: false,
|
|
353
458
|
finalTokens: calculateImageTokens(finalWidth, finalHeight),
|
|
354
459
|
element: {
|
|
355
|
-
bounds:
|
|
356
|
-
|
|
357
|
-
y: Math.round(bounds.y),
|
|
358
|
-
width: Math.round(bounds.width),
|
|
359
|
-
height: Math.round(bounds.height),
|
|
360
|
-
},
|
|
460
|
+
bounds: roundBounds(onPage(box)),
|
|
461
|
+
...(bounds !== box && { captured: roundBounds(clip) }),
|
|
361
462
|
},
|
|
362
463
|
};
|
|
363
464
|
if (quality !== undefined) {
|
|
@@ -371,4 +472,18 @@ export async function captureElementScreenshot(outputPath, ref, options = {}) {
|
|
|
371
472
|
}
|
|
372
473
|
return result;
|
|
373
474
|
}
|
|
475
|
+
/**
|
|
476
|
+
* Bounds in whole pixels.
|
|
477
|
+
*
|
|
478
|
+
* @param bounds - Bounds
|
|
479
|
+
* @returns Rounded bounds
|
|
480
|
+
*/
|
|
481
|
+
function roundBounds(bounds) {
|
|
482
|
+
return {
|
|
483
|
+
x: Math.round(bounds.x),
|
|
484
|
+
y: Math.round(bounds.y),
|
|
485
|
+
width: Math.round(bounds.width),
|
|
486
|
+
height: Math.round(bounds.height),
|
|
487
|
+
};
|
|
488
|
+
}
|
|
374
489
|
//# sourceMappingURL=screenshot.js.map
|
|
@@ -8,11 +8,13 @@
|
|
|
8
8
|
* - `eval.ts` — evaluate JavaScript in the page (or an iframe)
|
|
9
9
|
* - `frames.ts` — list the page's iframes
|
|
10
10
|
* - `listeners.ts` — list event listeners that run for an element
|
|
11
|
+
* - `layout.ts` — positions, sizes and visibility of elements
|
|
12
|
+
* - `wait.ts` — wait for elements to appear, show, contain a text or go away
|
|
11
13
|
*
|
|
12
14
|
* Form-related commands register via `form.ts` and `formInteraction.ts`.
|
|
13
15
|
* Accessibility commands register via `a11y.ts`.
|
|
14
16
|
*/
|
|
15
|
-
import type
|
|
17
|
+
import { type Command } from 'commander';
|
|
16
18
|
/**
|
|
17
19
|
* Register DOM telemetry commands on the root Commander program.
|
|
18
20
|
*/
|
|
@@ -8,18 +8,23 @@
|
|
|
8
8
|
* - `eval.ts` — evaluate JavaScript in the page (or an iframe)
|
|
9
9
|
* - `frames.ts` — list the page's iframes
|
|
10
10
|
* - `listeners.ts` — list event listeners that run for an element
|
|
11
|
+
* - `layout.ts` — positions, sizes and visibility of elements
|
|
12
|
+
* - `wait.ts` — wait for elements to appear, show, contain a text or go away
|
|
11
13
|
*
|
|
12
14
|
* Form-related commands register via `form.ts` and `formInteraction.ts`.
|
|
13
15
|
* Accessibility commands register via `a11y.ts`.
|
|
14
16
|
*/
|
|
17
|
+
import { Option } from 'commander';
|
|
15
18
|
import { registerA11yCommands } from './a11y.js';
|
|
16
19
|
import { handleDomEval } from './eval.js';
|
|
17
20
|
import { registerFormCommand } from './form.js';
|
|
18
21
|
import { handleDomFrames } from './frames.js';
|
|
19
|
-
import { handleDomGet } from './get.js';
|
|
22
|
+
import { DOM_GET_DEFAULT_SELECTOR, handleDomGet } from './get.js';
|
|
23
|
+
import { registerLayoutCommand } from './layout.js';
|
|
20
24
|
import { registerListenersCommand } from './listeners.js';
|
|
21
25
|
import { handleDomQuery } from './query.js';
|
|
22
26
|
import { handleDomScreenshot } from './screenshot.js';
|
|
27
|
+
import { registerWaitCommand } from './wait.js';
|
|
23
28
|
import { integerOption, screenshotFormatOption } from '../shared/validation.js';
|
|
24
29
|
/**
|
|
25
30
|
* Register DOM telemetry commands on the root Commander program.
|
|
@@ -32,6 +37,8 @@ export function registerDomCommands(program) {
|
|
|
32
37
|
registerA11yCommands(dom);
|
|
33
38
|
registerFormCommand(dom);
|
|
34
39
|
registerListenersCommand(dom);
|
|
40
|
+
registerLayoutCommand(dom);
|
|
41
|
+
registerWaitCommand(dom);
|
|
35
42
|
dom
|
|
36
43
|
.command('query')
|
|
37
44
|
.description('Find elements by CSS selector')
|
|
@@ -44,7 +51,7 @@ export function registerDomCommands(program) {
|
|
|
44
51
|
.command('eval')
|
|
45
52
|
.description('Evaluate JavaScript expression in the page context')
|
|
46
53
|
.argument('<script>', 'JavaScript to execute (e.g., "document.title", "window.location.href")')
|
|
47
|
-
.option('--frame <frame>', 'Evaluate in an iframe, cross-origin ones included: index, name/id attribute, or part of the URL (see dom frames)')
|
|
54
|
+
.option('--frame <frame>', 'Evaluate in an iframe, cross-origin ones included: index, name/id attribute, or part of the name, id or URL (see dom frames)')
|
|
48
55
|
.option('-j, --json', 'Output as JSON')
|
|
49
56
|
.action(async (script, options) => {
|
|
50
57
|
await handleDomEval(script, options);
|
|
@@ -68,10 +75,12 @@ export function registerDomCommands(program) {
|
|
|
68
75
|
dom
|
|
69
76
|
.command('get')
|
|
70
77
|
.description('Get semantic accessibility structure (default) or raw HTML (--raw)')
|
|
71
|
-
.argument('[
|
|
78
|
+
.argument('[selectorOrIndex]', `CSS selector or numeric index from query results (0-based; e.g. ".error", "#app", 0); default: ${DOM_GET_DEFAULT_SELECTOR}`)
|
|
72
79
|
.option('--raw', 'Output raw HTML with all filtering options')
|
|
80
|
+
.option('--full', 'Show all of the element text (default: the first 500 characters)')
|
|
73
81
|
.option('--all', 'Get all matches (only with --raw)')
|
|
74
|
-
.option('--
|
|
82
|
+
.option('--index <n>', 'Element index if selector matches multiple (0-based)', integerOption(0))
|
|
83
|
+
.addOption(new Option('--nth <n>', 'Alias of --index').argParser(integerOption(0)).hideHelp())
|
|
75
84
|
.option('--node-id <id>', 'Get the element with this node id (from dom query/get --raw or a11y describe; implies --raw)', integerOption(1))
|
|
76
85
|
.option('-j, --json', 'Output as JSON')
|
|
77
86
|
.action(async (selector, options) => {
|
|
@@ -81,6 +90,7 @@ export function registerDomCommands(program) {
|
|
|
81
90
|
.command('screenshot')
|
|
82
91
|
.description('Capture page or element screenshot')
|
|
83
92
|
.argument('<path>', 'Output file path, or directory for --follow mode')
|
|
93
|
+
.argument('[selector]', 'Element to capture: CSS selector or index from a query (same as --selector / --index)')
|
|
84
94
|
.option('--selector <selector>', 'CSS selector for element capture')
|
|
85
95
|
.option('--index <number>', 'Cached element index (0-based) from previous query', integerOption(0))
|
|
86
96
|
.option('--format <format>', 'Image format: png or jpeg/jpg (default: from the file extension, else png)', screenshotFormatOption)
|
|
@@ -92,8 +102,8 @@ export function registerDomCommands(program) {
|
|
|
92
102
|
.option('--interval <ms>', 'Capture interval for --follow (default: 1000)')
|
|
93
103
|
.option('--limit <count>', 'Max frames for --follow')
|
|
94
104
|
.option('-j, --json', 'Output as JSON')
|
|
95
|
-
.action(async (path, options) => {
|
|
96
|
-
await handleDomScreenshot(path, options);
|
|
105
|
+
.action(async (path, target, options) => {
|
|
106
|
+
await handleDomScreenshot(path, target, options);
|
|
97
107
|
});
|
|
98
108
|
}
|
|
99
109
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `bdg dom layout <selector|index>` - where elements are on the page and
|
|
3
|
+
* whether a user can see them, without a screenshot: page and viewport
|
|
4
|
+
* coordinates, size, viewport position (visible, partly, above/below the
|
|
5
|
+
* fold, hidden), what covers them and the styles that decide how they show.
|
|
6
|
+
*/
|
|
7
|
+
import type { Command } from 'commander';
|
|
8
|
+
/**
|
|
9
|
+
* Register `bdg dom layout`.
|
|
10
|
+
*
|
|
11
|
+
* @param dom - The `dom` command group
|
|
12
|
+
*/
|
|
13
|
+
export declare function registerLayoutCommand(dom: Command): void;
|
|
14
|
+
//# sourceMappingURL=layout.d.ts.map
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `bdg dom layout <selector|index>` - where elements are on the page and
|
|
3
|
+
* whether a user can see them, without a screenshot: page and viewport
|
|
4
|
+
* coordinates, size, viewport position (visible, partly, above/below the
|
|
5
|
+
* fold, hidden), what covers them and the styles that decide how they show.
|
|
6
|
+
*/
|
|
7
|
+
import { DomElementResolver } from './DomElementResolver.js';
|
|
8
|
+
import { runElementCommand } from './helpers/runElementCommand.js';
|
|
9
|
+
import { runCommand } from '../shared/CommandRunner.js';
|
|
10
|
+
import { jsonOption } from '../shared/commonOptions.js';
|
|
11
|
+
import { integerOption } from '../shared/validation.js';
|
|
12
|
+
import { domLayout } from '../../ipc/client.js';
|
|
13
|
+
import { formatLayout } from '../../ui/formatters/layout.js';
|
|
14
|
+
/**
|
|
15
|
+
* Register `bdg dom layout`.
|
|
16
|
+
*
|
|
17
|
+
* @param dom - The `dom` command group
|
|
18
|
+
*/
|
|
19
|
+
export function registerLayoutCommand(dom) {
|
|
20
|
+
dom
|
|
21
|
+
.command('layout')
|
|
22
|
+
.description('Positions, sizes and visibility of elements (above/below the fold, hidden, covered) without a screenshot')
|
|
23
|
+
.argument('<selectorOrIndex>', 'CSS selector or numeric index from query results (0-based)')
|
|
24
|
+
.option('--index <n>', 'Only this match of the selector (0-based)', integerOption(0))
|
|
25
|
+
.addOption(jsonOption())
|
|
26
|
+
.action(async (selectorOrIndex, options) => {
|
|
27
|
+
await runCommand(() => measureLayout(selectorOrIndex, options), options, formatLayout);
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Resolve the target and ask the daemon to measure it.
|
|
32
|
+
*
|
|
33
|
+
* @param selectorOrIndex - CSS selector or cached query index
|
|
34
|
+
* @param options - Command options
|
|
35
|
+
* @returns Command result; an index argument is reported as the element's `index`
|
|
36
|
+
*/
|
|
37
|
+
async function measureLayout(selectorOrIndex, options) {
|
|
38
|
+
const result = await runElementCommand({
|
|
39
|
+
selectorOrIndex,
|
|
40
|
+
index: options.index,
|
|
41
|
+
buildRequest: (target) => target,
|
|
42
|
+
call: domLayout,
|
|
43
|
+
command: 'layout',
|
|
44
|
+
action: 'measure the layout',
|
|
45
|
+
failureSuggestion: 'Verify the selector matches an element: bdg dom query "<selector>"',
|
|
46
|
+
});
|
|
47
|
+
if (!result.data || !DomElementResolver.getInstance().isNumericIndex(selectorOrIndex)) {
|
|
48
|
+
return result;
|
|
49
|
+
}
|
|
50
|
+
const index = Number(selectorOrIndex);
|
|
51
|
+
const elements = result.data.elements.map((element) => ({ ...element, index }));
|
|
52
|
+
return { ...result, data: { ...result.data, elements } };
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=layout.js.map
|
|
@@ -4,7 +4,11 @@
|
|
|
4
4
|
*
|
|
5
5
|
* The daemon collects them with `DOMDebugger.getEventListeners`; listeners on
|
|
6
6
|
* ancestors matter because frameworks (React, jQuery) attach their handlers
|
|
7
|
-
* to a root container or the document and dispatch from there.
|
|
7
|
+
* to a root container or the document and dispatch from there. jQuery's
|
|
8
|
+
* dispatcher is replaced by the jQuery handlers it runs for the element,
|
|
9
|
+
* React's `on…` props of the element and its ancestors are listed, and
|
|
10
|
+
* framework roots (React's root container) are summarised per node unless
|
|
11
|
+
* `--all` is given.
|
|
8
12
|
*/
|
|
9
13
|
import type { Command } from 'commander';
|
|
10
14
|
/**
|