browser-debugger-cli 0.8.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -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 +29 -6
- 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 +189 -119
- 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/keyAttributes.d.ts +20 -0
- package/dist/commands/dom/helpers/keyAttributes.js +54 -0
- package/dist/commands/dom/helpers/query.d.ts +44 -17
- package/dist/commands/dom/helpers/query.js +300 -106
- package/dist/commands/dom/helpers/runElementCommand.d.ts +10 -2
- package/dist/commands/dom/helpers/runElementCommand.js +98 -30
- package/dist/commands/dom/helpers/screenshot.d.ts +4 -1
- package/dist/commands/dom/helpers/screenshot.js +239 -51
- package/dist/commands/dom/index.d.ts +4 -1
- package/dist/commands/dom/index.js +22 -7
- package/dist/commands/dom/inspect.d.ts +15 -0
- package/dist/commands/dom/inspect.js +82 -0
- 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 +15 -5
- 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 +16 -10
- package/dist/commands/dom/semanticUtils.js +53 -16
- 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 +17 -13
- package/dist/commands/optionBehaviors.js +154 -21
- package/dist/commands/page.d.ts +3 -2
- package/dist/commands/page.js +100 -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/commonOptions.d.ts +4 -0
- package/dist/commands/shared/commonOptions.js +9 -0
- 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 +61 -5
- package/dist/commands/shared/startHelpers.d.ts +66 -0
- package/dist/commands/shared/startHelpers.js +103 -13
- package/dist/commands/shared/validation.d.ts +14 -2
- package/dist/commands/shared/validation.js +20 -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 +10 -2
- package/dist/daemon/launcher.js +8 -7
- package/dist/daemon/server/SocketServer.js +1 -2
- package/dist/daemon/session/Session.d.ts +20 -0
- package/dist/daemon/session/Session.js +80 -9
- package/dist/daemon/session/chromeConnection.d.ts +9 -0
- package/dist/daemon/session/chromeConnection.js +45 -8
- package/dist/daemon/session/commandRegistry.d.ts +14 -1
- package/dist/daemon/session/commandRegistry.js +113 -67
- package/dist/daemon/session/interactions.d.ts +48 -9
- package/dist/daemon/session/interactions.js +46 -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 +10630 -3601
- package/dist/errors/messages.d.ts +456 -24
- package/dist/errors/messages.js +862 -67
- package/dist/index.js +6915 -3401
- package/dist/ipc/client.d.ts +21 -1
- package/dist/ipc/client.js +35 -3
- package/dist/ipc/protocol/commands.d.ts +145 -5
- package/dist/ipc/protocol/commands.js +4 -0
- package/dist/ipc/protocol/domTypes.d.ts +291 -7
- package/dist/ipc/protocol/inspectTypes.d.ts +388 -0
- package/dist/ipc/protocol/inspectTypes.js +10 -0
- 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 +185 -0
- package/dist/runtime/dom/actionEffects.js +402 -0
- package/dist/runtime/dom/actionEffectsScripts.d.ts +90 -0
- package/dist/runtime/dom/actionEffectsScripts.js +426 -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 +103 -0
- package/dist/runtime/dom/elementInfo.js +256 -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 +184 -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 +9 -0
- package/dist/runtime/dom/formFillHelpers/fill.js +178 -14
- 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 +12 -1
- package/dist/runtime/dom/frameSelection.js +22 -3
- package/dist/runtime/dom/frames.d.ts +61 -5
- package/dist/runtime/dom/frames.js +329 -75
- package/dist/runtime/dom/inspect.d.ts +28 -0
- package/dist/runtime/dom/inspect.js +557 -0
- package/dist/runtime/dom/inspectAllStyles.d.ts +62 -0
- package/dist/runtime/dom/inspectAllStyles.js +385 -0
- package/dist/runtime/dom/inspectCascade.d.ts +94 -0
- package/dist/runtime/dom/inspectCascade.js +371 -0
- package/dist/runtime/dom/inspectCascadeModel.d.ts +39 -0
- package/dist/runtime/dom/inspectCascadeModel.js +232 -0
- package/dist/runtime/dom/inspectHints.d.ts +62 -0
- package/dist/runtime/dom/inspectHints.js +305 -0
- package/dist/runtime/dom/inspectLayoutModel.d.ts +87 -0
- package/dist/runtime/dom/inspectLayoutModel.js +346 -0
- package/dist/runtime/dom/inspectModel.d.ts +74 -0
- package/dist/runtime/dom/inspectModel.js +184 -0
- package/dist/runtime/dom/inspectPaintModel.d.ts +157 -0
- package/dist/runtime/dom/inspectPaintModel.js +461 -0
- package/dist/runtime/dom/inspectRules.d.ts +37 -0
- package/dist/runtime/dom/inspectRules.js +101 -0
- package/dist/runtime/dom/inspectScripts.d.ts +132 -0
- package/dist/runtime/dom/inspectScripts.js +263 -0
- package/dist/runtime/dom/inspectTree.d.ts +40 -0
- package/dist/runtime/dom/inspectTree.js +134 -0
- package/dist/runtime/dom/inspectVariables.d.ts +33 -0
- package/dist/runtime/dom/inspectVariables.js +94 -0
- package/dist/runtime/dom/inspectWhyModel.d.ts +20 -0
- package/dist/runtime/dom/inspectWhyModel.js +134 -0
- package/dist/runtime/dom/layout.d.ts +71 -0
- package/dist/runtime/dom/layout.js +340 -0
- package/dist/runtime/dom/listenerPageScripts.d.ts +72 -0
- package/dist/runtime/dom/listenerPageScripts.js +365 -0
- package/dist/runtime/dom/listenerSummary.d.ts +136 -11
- package/dist/runtime/dom/listenerSummary.js +361 -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 +63 -2
- package/dist/runtime/dom/reactEventHelpers.js +220 -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 +71 -0
- package/dist/runtime/page/emulation.js +117 -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 +18 -3
- package/dist/telemetry/a11y.js +170 -29
- package/dist/telemetry/console.d.ts +1 -0
- package/dist/telemetry/console.js +100 -5
- package/dist/telemetry/network.js +3 -1
- 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 +112 -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.d.ts +8 -0
- package/dist/ui/formatters/details.js +61 -4
- package/dist/ui/formatters/dom.d.ts +27 -14
- package/dist/ui/formatters/dom.js +88 -59
- package/dist/ui/formatters/form.js +29 -18
- package/dist/ui/formatters/inspect.d.ts +39 -0
- package/dist/ui/formatters/inspect.js +596 -0
- package/dist/ui/formatters/keyAttributes.d.ts +19 -0
- package/dist/ui/formatters/keyAttributes.js +84 -0
- package/dist/ui/formatters/layout.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.d.ts +13 -0
- package/dist/ui/formatters/networkHeaders.js +36 -3
- package/dist/ui/formatters/networkList.d.ts +29 -1
- package/dist/ui/formatters/networkList.js +86 -20
- 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 +19 -0
- package/dist/ui/formatting.js +31 -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 +504 -14
- package/dist/ui/messages/commands.js +835 -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/networkMessages.d.ts +14 -0
- package/dist/ui/messages/networkMessages.js +18 -0
- package/dist/ui/messages/preview.js +5 -4
- package/dist/ui/messages/session.d.ts +30 -21
- package/dist/ui/messages/session.js +48 -26
- package/dist/ui/messages/sessionCommand.d.ts +43 -0
- package/dist/ui/messages/sessionCommand.js +52 -0
- package/dist/utils/async.d.ts +17 -0
- package/dist/utils/async.js +36 -0
- package/dist/utils/color.d.ts +84 -0
- package/dist/utils/color.js +376 -0
- package/dist/utils/cssValues.d.ts +109 -0
- package/dist/utils/cssValues.js +236 -0
- package/dist/utils/http.d.ts +22 -1
- package/dist/utils/http.js +28 -9
- package/dist/utils/selectorFilters.d.ts +48 -8
- package/dist/utils/selectorFilters.js +296 -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, shadowBoundaryLine, 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,101 @@ 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, a
|
|
114
|
+
* shadow host the selector tries to cross,
|
|
115
|
+
* the places selectors do not search (for a page script that found nothing)
|
|
116
|
+
* and the still-loading hint while the page loads.
|
|
117
|
+
*
|
|
118
|
+
* @param failure - Failed command result
|
|
119
|
+
* @param selector - Selector that was looked for (the cached query's for an index)
|
|
120
|
+
* @param searched - The page script searched the page (the daemon did not fail first)
|
|
121
|
+
* @returns The failure, with the context in its suggestion
|
|
122
|
+
*/
|
|
123
|
+
async function withNotFoundContext(failure, selector, searched) {
|
|
124
|
+
if (failure.exitCode !== EXIT_CODES.RESOURCE_NOT_FOUND)
|
|
125
|
+
return failure;
|
|
126
|
+
const context = await noMatchContext(selector);
|
|
127
|
+
const note = searched ? unreachableElementsNote(selector, context.unsearched) : '';
|
|
128
|
+
const suggestion = withLoadingHint(joinLines(context.similar, context.shadowHost && shadowBoundaryLine(context.shadowHost), failure.errorContext?.suggestion, note ? note : undefined), context.readyState, selector);
|
|
129
|
+
return suggestion ? { ...failure, errorContext: { suggestion } } : failure;
|
|
62
130
|
}
|
|
63
131
|
//# 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,52 @@ async function restoreScrollPosition(position) {
|
|
|
136
138
|
returnByValue: true,
|
|
137
139
|
});
|
|
138
140
|
}
|
|
141
|
+
/**
|
|
142
|
+
* The window's size with its scrollbars (`innerWidth`/`innerHeight`): an
|
|
143
|
+
* override at this size keeps the page's layout, where the visible size
|
|
144
|
+
* (without scrollbars) would narrow it and move centered content.
|
|
145
|
+
*
|
|
146
|
+
* @param viewport - Visible viewport size, used when the page does not answer
|
|
147
|
+
* @returns Width and height in CSS px
|
|
148
|
+
*/
|
|
149
|
+
async function windowSize(viewport) {
|
|
150
|
+
const response = await callCDP('Runtime.evaluate', {
|
|
151
|
+
expression: '[window.innerWidth, window.innerHeight]',
|
|
152
|
+
returnByValue: true,
|
|
153
|
+
});
|
|
154
|
+
const value = response.data?.result?.result?.value;
|
|
155
|
+
const [width, height] = Array.isArray(value) ? value : [];
|
|
156
|
+
return {
|
|
157
|
+
width: Math.round(width ?? viewport.clientWidth),
|
|
158
|
+
height: Math.round(height ?? viewport.clientHeight),
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Capture at a pixel ratio of 1 (CSS px = image px) on a high-DPI display:
|
|
163
|
+
* the viewport is overridden at the window's size (the session's
|
|
164
|
+
* `--viewport`, else the window with its scrollbars, so the layout does not
|
|
165
|
+
* change) until the returned function puts back what was there before, the
|
|
166
|
+
* session's viewport or none.
|
|
167
|
+
*
|
|
168
|
+
* @param devicePixelRatio - Page's pixel ratio
|
|
169
|
+
* @param viewport - Visible viewport size
|
|
170
|
+
* @returns Function restoring the device metrics
|
|
171
|
+
*/
|
|
172
|
+
async function useUnitPixelRatio(devicePixelRatio, viewport) {
|
|
173
|
+
if (devicePixelRatio === 1)
|
|
174
|
+
return () => Promise.resolve();
|
|
175
|
+
const sessionViewport = readSessionMetadata()?.viewport;
|
|
176
|
+
const size = sessionViewport ?? (await windowSize(viewport));
|
|
177
|
+
await callCDP('Emulation.setDeviceMetricsOverride', viewportOverride(size, 1));
|
|
178
|
+
return async () => {
|
|
179
|
+
if (sessionViewport) {
|
|
180
|
+
await callCDP('Emulation.setDeviceMetricsOverride', viewportOverride(sessionViewport));
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
await callCDP('Emulation.clearDeviceMetricsOverride', {});
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
}
|
|
139
187
|
/**
|
|
140
188
|
* Get the bounding box (border box, so padding and border are included) of an
|
|
141
189
|
* element via CDP DOM.getBoxModel.
|
|
@@ -161,6 +209,20 @@ export async function getElementBounds(ref) {
|
|
|
161
209
|
}
|
|
162
210
|
return { x, y, width, height };
|
|
163
211
|
}
|
|
212
|
+
/**
|
|
213
|
+
* Page coordinates of the visible area's top-left corner. A capture clip is
|
|
214
|
+
* in page coordinates, so a viewport capture must start at the scroll
|
|
215
|
+
* position, not at the page origin (which shows nothing once scrolled). Read
|
|
216
|
+
* after any metrics override, which can move the scroll position.
|
|
217
|
+
*
|
|
218
|
+
* @returns Scroll offset of the visual viewport in CSS pixels
|
|
219
|
+
*/
|
|
220
|
+
async function visibleAreaOrigin() {
|
|
221
|
+
const response = await callCDP('Page.getLayoutMetrics', {});
|
|
222
|
+
const metrics = response.data?.result;
|
|
223
|
+
const viewport = metrics?.cssVisualViewport;
|
|
224
|
+
return { x: viewport?.pageX ?? 0, y: viewport?.pageY ?? 0 };
|
|
225
|
+
}
|
|
164
226
|
/**
|
|
165
227
|
* Capture a screenshot of the page. Auto-resizes oversized pages by default
|
|
166
228
|
* to keep Claude Vision token cost bounded; falls back to viewport capture
|
|
@@ -193,13 +255,8 @@ export async function capturePageScreenshot(outputPath, options = {}) {
|
|
|
193
255
|
const scale = resized ? calculateResizeScale(captureWidth, captureHeight) : 1;
|
|
194
256
|
const finalWidth = Math.round(captureWidth * scale);
|
|
195
257
|
const finalHeight = Math.round(captureHeight * scale);
|
|
258
|
+
const restoreMetrics = await useUnitPixelRatio(devicePixelRatio, viewport);
|
|
196
259
|
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
260
|
if (options.scroll) {
|
|
204
261
|
await callCDP('Runtime.evaluate', {
|
|
205
262
|
expression: `(${DEEP_QUERY_JS})(${selectorArgsJS(options.scroll)})[0]?.scrollIntoView({ block: 'center', behavior: 'instant' })`,
|
|
@@ -207,18 +264,7 @@ export async function capturePageScreenshot(outputPath, options = {}) {
|
|
|
207
264
|
});
|
|
208
265
|
}
|
|
209
266
|
}
|
|
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
|
-
}
|
|
267
|
+
const clipOrigin = effectiveFullPage ? { x: 0, y: 0 } : await visibleAreaOrigin();
|
|
222
268
|
let screenshotResult;
|
|
223
269
|
try {
|
|
224
270
|
const screenshotResponse = await callCDP('Page.captureScreenshot', {
|
|
@@ -226,8 +272,8 @@ export async function capturePageScreenshot(outputPath, options = {}) {
|
|
|
226
272
|
...(quality !== undefined && { quality }),
|
|
227
273
|
captureBeyondViewport: effectiveFullPage,
|
|
228
274
|
clip: {
|
|
229
|
-
x:
|
|
230
|
-
y:
|
|
275
|
+
x: clipOrigin.x,
|
|
276
|
+
y: clipOrigin.y,
|
|
231
277
|
width: captureWidth,
|
|
232
278
|
height: captureHeight,
|
|
233
279
|
scale,
|
|
@@ -236,9 +282,7 @@ export async function capturePageScreenshot(outputPath, options = {}) {
|
|
|
236
282
|
screenshotResult = screenshotResponse.data?.result;
|
|
237
283
|
}
|
|
238
284
|
finally {
|
|
239
|
-
|
|
240
|
-
await callCDP('Emulation.clearDeviceMetricsOverride', {});
|
|
241
|
-
}
|
|
285
|
+
await restoreMetrics();
|
|
242
286
|
}
|
|
243
287
|
if (!screenshotResult?.data) {
|
|
244
288
|
throw new CDPConnectionError('No screenshot data returned', new Error('Empty response'));
|
|
@@ -287,11 +331,143 @@ export async function capturePageScreenshot(outputPath, options = {}) {
|
|
|
287
331
|
}
|
|
288
332
|
return result;
|
|
289
333
|
}
|
|
334
|
+
/** Descendants {@link CONTENT_OVERFLOW_JS} looks at, so a huge element stays cheap */
|
|
335
|
+
const OVERFLOW_SCAN_LIMIT = 2000;
|
|
336
|
+
/**
|
|
337
|
+
* Page-side distances (CSS px, never negative) by which an element's rendered
|
|
338
|
+
* descendants reach beyond its border box on each side: uncleared floats,
|
|
339
|
+
* absolutely positioned and transformed children. Descendants of an element
|
|
340
|
+
* that clips its overflow (`overflow` other than `visible`) are cut off by it
|
|
341
|
+
* and not counted, nor are fixed ones (they belong to the viewport) or what
|
|
342
|
+
* lies outside the document (skip links at -9999px). Zero everywhere when the
|
|
343
|
+
* element clips its own overflow.
|
|
344
|
+
*/
|
|
345
|
+
const CONTENT_OVERFLOW_JS = `function () {
|
|
346
|
+
const view = this.ownerDocument.defaultView;
|
|
347
|
+
const scroller = this.ownerDocument.scrollingElement || this.ownerDocument.documentElement;
|
|
348
|
+
const own = this.getBoundingClientRect();
|
|
349
|
+
const reach = { left: own.left, top: own.top, right: own.right, bottom: own.bottom };
|
|
350
|
+
const page = { left: -view.scrollX, top: -view.scrollY, right: scroller.scrollWidth - view.scrollX, bottom: scroller.scrollHeight - view.scrollY };
|
|
351
|
+
const clips = (style) => style.overflowX !== 'visible' || style.overflowY !== 'visible';
|
|
352
|
+
let budget = ${OVERFLOW_SCAN_LIMIT};
|
|
353
|
+
const walk = (el) => {
|
|
354
|
+
for (const child of el.children) {
|
|
355
|
+
if (--budget < 0) return;
|
|
356
|
+
const style = view.getComputedStyle(child);
|
|
357
|
+
if (style.display === 'none' || style.position === 'fixed') continue;
|
|
358
|
+
const r = child.getBoundingClientRect();
|
|
359
|
+
if (r.width > 0 && r.height > 0 && style.visibility === 'visible') {
|
|
360
|
+
reach.left = Math.min(reach.left, Math.max(r.left, page.left));
|
|
361
|
+
reach.top = Math.min(reach.top, Math.max(r.top, page.top));
|
|
362
|
+
reach.right = Math.max(reach.right, Math.min(r.right, page.right));
|
|
363
|
+
reach.bottom = Math.max(reach.bottom, Math.min(r.bottom, page.bottom));
|
|
364
|
+
}
|
|
365
|
+
if (!clips(style)) walk(child);
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
if (!clips(view.getComputedStyle(this))) walk(this);
|
|
369
|
+
return { left: own.left - reach.left, top: own.top - reach.top, right: reach.right - own.right, bottom: reach.bottom - own.bottom };
|
|
370
|
+
}`;
|
|
371
|
+
/**
|
|
372
|
+
* The visible viewport (without scrollbars) in CSS px.
|
|
373
|
+
*
|
|
374
|
+
* @returns Width and height
|
|
375
|
+
*/
|
|
376
|
+
async function visibleViewport() {
|
|
377
|
+
const metrics = (await callCDP('Page.getLayoutMetrics', {})).data?.result;
|
|
378
|
+
const view = metrics?.cssVisualViewport ?? metrics?.visualViewport;
|
|
379
|
+
return { width: view?.clientWidth ?? 0, height: view?.clientHeight ?? 0 };
|
|
380
|
+
}
|
|
290
381
|
/**
|
|
291
|
-
*
|
|
382
|
+
* Whether an area (viewport coordinates) lies inside the viewport.
|
|
383
|
+
*
|
|
384
|
+
* @param area - Area
|
|
385
|
+
* @param view - Viewport size
|
|
386
|
+
* @returns True when fully inside
|
|
387
|
+
*/
|
|
388
|
+
function insideView(area, view) {
|
|
389
|
+
return (area.x >= 0 &&
|
|
390
|
+
area.y >= 0 &&
|
|
391
|
+
area.x + area.width <= view.width &&
|
|
392
|
+
area.y + area.height <= view.height);
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Measure the area to capture and, when it fits in the viewport but is not
|
|
396
|
+
* in view, scroll it to the middle first. A capture inside the viewport
|
|
397
|
+
* keeps the page as it is; one beyond it makes Chrome lay the page out
|
|
398
|
+
* without its scrollbar, which moves centered content by half the
|
|
399
|
+
* scrollbar's width, so it is used only for areas larger than the viewport.
|
|
400
|
+
*
|
|
401
|
+
* @param ref - Node reference
|
|
402
|
+
* @returns Border box, area to capture (viewport coordinates) and whether it is in view
|
|
403
|
+
*/
|
|
404
|
+
async function measureInView(ref) {
|
|
405
|
+
const view = await visibleViewport();
|
|
406
|
+
let box = await getElementBounds(ref);
|
|
407
|
+
let bounds = await captureArea(ref, box);
|
|
408
|
+
const fits = bounds.width <= view.width && bounds.height <= view.height;
|
|
409
|
+
if (fits && !insideView(bounds, view)) {
|
|
410
|
+
const dx = bounds.x + bounds.width / 2 - view.width / 2;
|
|
411
|
+
const dy = bounds.y + bounds.height / 2 - view.height / 2;
|
|
412
|
+
await callCDP('Runtime.evaluate', { expression: `window.scrollBy(${dx}, ${dy})` });
|
|
413
|
+
box = await getElementBounds(ref);
|
|
414
|
+
bounds = await captureArea(ref, box);
|
|
415
|
+
}
|
|
416
|
+
return { box, bounds, inView: insideView(bounds, view) };
|
|
417
|
+
}
|
|
418
|
+
/** Overflow (px) below which the capture keeps to the border box (subpixel rounding) */
|
|
419
|
+
const OVERFLOW_SLACK = 1;
|
|
420
|
+
/**
|
|
421
|
+
* Area an element screenshot captures: the border box, grown to the content
|
|
422
|
+
* that overflows it ({@link CONTENT_OVERFLOW_JS}), so floated children are
|
|
423
|
+
* not cropped away.
|
|
424
|
+
*
|
|
425
|
+
* @param ref - Node reference
|
|
426
|
+
* @param bounds - Border box (DOM.getBoxModel coordinates)
|
|
427
|
+
* @returns The area, or the border box when nothing overflows (or the page cannot be asked)
|
|
428
|
+
*/
|
|
429
|
+
async function captureArea(ref, bounds) {
|
|
430
|
+
const objectGroup = `bdg-shot-${process.pid}`;
|
|
431
|
+
try {
|
|
432
|
+
const resolved = await callCDP('DOM.resolveNode', { ...ref, objectGroup });
|
|
433
|
+
const objectId = resolved.data?.result?.object
|
|
434
|
+
.objectId;
|
|
435
|
+
if (!objectId)
|
|
436
|
+
return bounds;
|
|
437
|
+
const response = await callCDP('Runtime.callFunctionOn', {
|
|
438
|
+
objectId,
|
|
439
|
+
functionDeclaration: CONTENT_OVERFLOW_JS,
|
|
440
|
+
returnByValue: true,
|
|
441
|
+
});
|
|
442
|
+
const overflow = response.data?.result
|
|
443
|
+
?.result?.value;
|
|
444
|
+
if (!overflow)
|
|
445
|
+
return bounds;
|
|
446
|
+
const [left, top, right, bottom] = ['left', 'top', 'right', 'bottom'].map((side) => Math.max(0, overflow[side] ?? 0));
|
|
447
|
+
if (Math.max(left, top, right, bottom) <= OVERFLOW_SLACK)
|
|
448
|
+
return bounds;
|
|
449
|
+
return {
|
|
450
|
+
x: bounds.x - left,
|
|
451
|
+
y: bounds.y - top,
|
|
452
|
+
width: bounds.width + left + right,
|
|
453
|
+
height: bounds.height + top + bottom,
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
catch (error) {
|
|
457
|
+
log.debug(`Could not measure overflowing content: ${String(error)}`);
|
|
458
|
+
return bounds;
|
|
459
|
+
}
|
|
460
|
+
finally {
|
|
461
|
+
await callCDP('Runtime.releaseObjectGroup', { objectGroup }).catch(() => undefined);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* Capture a screenshot of a single element: its border box, grown to include
|
|
466
|
+
* content overflowing it ({@link captureArea}). The box model is relative to
|
|
467
|
+
* the viewport and the capture clip to the page, so the page scroll is added
|
|
468
|
+
* (the reported bounds are page coordinates, like `dom layout`'s).
|
|
292
469
|
*/
|
|
293
470
|
export async function captureElementScreenshot(outputPath, ref, options = {}) {
|
|
294
|
-
const bounds = await getElementBounds(ref);
|
|
295
471
|
const format = options.format ?? 'png';
|
|
296
472
|
const quality = format === 'jpeg' ? (options.quality ?? 90) : undefined;
|
|
297
473
|
const noResize = options.noResize ?? false;
|
|
@@ -300,6 +476,18 @@ export async function captureElementScreenshot(outputPath, ref, options = {}) {
|
|
|
300
476
|
returnByValue: true,
|
|
301
477
|
});
|
|
302
478
|
const devicePixelRatio = dprResponse.data?.result?.result?.value ?? 1;
|
|
479
|
+
const before = (await callCDP('Page.getLayoutMetrics', {})).data?.result;
|
|
480
|
+
const restoreMetrics = await useUnitPixelRatio(devicePixelRatio, before?.visualViewport ?? { clientWidth: 800, clientHeight: 600 });
|
|
481
|
+
let box;
|
|
482
|
+
let bounds;
|
|
483
|
+
let inView;
|
|
484
|
+
try {
|
|
485
|
+
({ box, bounds, inView } = await measureInView(ref));
|
|
486
|
+
}
|
|
487
|
+
catch (error) {
|
|
488
|
+
await restoreMetrics();
|
|
489
|
+
throw error;
|
|
490
|
+
}
|
|
303
491
|
const originalWidth = bounds.width;
|
|
304
492
|
const originalHeight = bounds.height;
|
|
305
493
|
const resized = shouldResize(originalWidth, originalHeight, noResize);
|
|
@@ -308,35 +496,25 @@ export async function captureElementScreenshot(outputPath, ref, options = {}) {
|
|
|
308
496
|
const finalHeight = Math.round(originalHeight * scale);
|
|
309
497
|
const metricsResponse = await callCDP('Page.getLayoutMetrics', {});
|
|
310
498
|
const metricsResult = metricsResponse.data?.result;
|
|
311
|
-
const
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
});
|
|
319
|
-
}
|
|
499
|
+
const scroll = metricsResult?.cssLayoutViewport ?? { pageX: 0, pageY: 0 };
|
|
500
|
+
const onPage = (area) => ({
|
|
501
|
+
...area,
|
|
502
|
+
x: area.x + scroll.pageX,
|
|
503
|
+
y: area.y + scroll.pageY,
|
|
504
|
+
});
|
|
505
|
+
const clip = onPage(bounds);
|
|
320
506
|
let screenshotResult;
|
|
321
507
|
try {
|
|
322
508
|
const screenshotResponse = await callCDP('Page.captureScreenshot', {
|
|
323
509
|
format,
|
|
324
510
|
...(quality !== undefined && { quality }),
|
|
325
|
-
clip: {
|
|
326
|
-
|
|
327
|
-
y: bounds.y,
|
|
328
|
-
width: bounds.width,
|
|
329
|
-
height: bounds.height,
|
|
330
|
-
scale,
|
|
331
|
-
},
|
|
332
|
-
captureBeyondViewport: true,
|
|
511
|
+
clip: { ...clip, scale },
|
|
512
|
+
captureBeyondViewport: !inView,
|
|
333
513
|
});
|
|
334
514
|
screenshotResult = screenshotResponse.data?.result;
|
|
335
515
|
}
|
|
336
516
|
finally {
|
|
337
|
-
|
|
338
|
-
await callCDP('Emulation.clearDeviceMetricsOverride', {});
|
|
339
|
-
}
|
|
517
|
+
await restoreMetrics();
|
|
340
518
|
}
|
|
341
519
|
if (!screenshotResult?.data) {
|
|
342
520
|
throw new CDPConnectionError('No screenshot data returned', new Error('Empty response'));
|
|
@@ -352,12 +530,8 @@ export async function captureElementScreenshot(outputPath, ref, options = {}) {
|
|
|
352
530
|
fullPage: false,
|
|
353
531
|
finalTokens: calculateImageTokens(finalWidth, finalHeight),
|
|
354
532
|
element: {
|
|
355
|
-
bounds:
|
|
356
|
-
|
|
357
|
-
y: Math.round(bounds.y),
|
|
358
|
-
width: Math.round(bounds.width),
|
|
359
|
-
height: Math.round(bounds.height),
|
|
360
|
-
},
|
|
533
|
+
bounds: roundBounds(onPage(box)),
|
|
534
|
+
...(bounds !== box && { captured: roundBounds(clip) }),
|
|
361
535
|
},
|
|
362
536
|
};
|
|
363
537
|
if (quality !== undefined) {
|
|
@@ -371,4 +545,18 @@ export async function captureElementScreenshot(outputPath, ref, options = {}) {
|
|
|
371
545
|
}
|
|
372
546
|
return result;
|
|
373
547
|
}
|
|
548
|
+
/**
|
|
549
|
+
* Bounds in whole pixels.
|
|
550
|
+
*
|
|
551
|
+
* @param bounds - Bounds
|
|
552
|
+
* @returns Rounded bounds
|
|
553
|
+
*/
|
|
554
|
+
function roundBounds(bounds) {
|
|
555
|
+
return {
|
|
556
|
+
x: Math.round(bounds.x),
|
|
557
|
+
y: Math.round(bounds.y),
|
|
558
|
+
width: Math.round(bounds.width),
|
|
559
|
+
height: Math.round(bounds.height),
|
|
560
|
+
};
|
|
561
|
+
}
|
|
374
562
|
//# sourceMappingURL=screenshot.js.map
|
|
@@ -8,11 +8,14 @@
|
|
|
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
|
+
* - `inspect.ts` — what one element looks like (styles, box, layout, child tree)
|
|
13
|
+
* - `wait.ts` — wait for elements to appear, show, contain a text or go away
|
|
11
14
|
*
|
|
12
15
|
* Form-related commands register via `form.ts` and `formInteraction.ts`.
|
|
13
16
|
* Accessibility commands register via `a11y.ts`.
|
|
14
17
|
*/
|
|
15
|
-
import type
|
|
18
|
+
import { type Command } from 'commander';
|
|
16
19
|
/**
|
|
17
20
|
* Register DOM telemetry commands on the root Commander program.
|
|
18
21
|
*/
|
|
@@ -8,18 +8,26 @@
|
|
|
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
|
+
* - `inspect.ts` — what one element looks like (styles, box, layout, child tree)
|
|
13
|
+
* - `wait.ts` — wait for elements to appear, show, contain a text or go away
|
|
11
14
|
*
|
|
12
15
|
* Form-related commands register via `form.ts` and `formInteraction.ts`.
|
|
13
16
|
* Accessibility commands register via `a11y.ts`.
|
|
14
17
|
*/
|
|
18
|
+
import { Option } from 'commander';
|
|
15
19
|
import { registerA11yCommands } from './a11y.js';
|
|
16
20
|
import { handleDomEval } from './eval.js';
|
|
17
21
|
import { registerFormCommand } from './form.js';
|
|
18
22
|
import { handleDomFrames } from './frames.js';
|
|
19
|
-
import { handleDomGet } from './get.js';
|
|
23
|
+
import { DOM_GET_DEFAULT_SELECTOR, handleDomGet } from './get.js';
|
|
24
|
+
import { registerInspectCommand } from './inspect.js';
|
|
25
|
+
import { registerLayoutCommand } from './layout.js';
|
|
20
26
|
import { registerListenersCommand } from './listeners.js';
|
|
21
27
|
import { handleDomQuery } from './query.js';
|
|
22
28
|
import { handleDomScreenshot } from './screenshot.js';
|
|
29
|
+
import { registerWaitCommand } from './wait.js';
|
|
30
|
+
import { SELECTOR_OR_INDEX_ARGUMENT, SELECTOR_SCOPE_HELP, } from '../shared/commonOptions.js';
|
|
23
31
|
import { integerOption, screenshotFormatOption } from '../shared/validation.js';
|
|
24
32
|
/**
|
|
25
33
|
* Register DOM telemetry commands on the root Commander program.
|
|
@@ -32,11 +40,15 @@ export function registerDomCommands(program) {
|
|
|
32
40
|
registerA11yCommands(dom);
|
|
33
41
|
registerFormCommand(dom);
|
|
34
42
|
registerListenersCommand(dom);
|
|
43
|
+
registerLayoutCommand(dom);
|
|
44
|
+
registerInspectCommand(dom);
|
|
45
|
+
registerWaitCommand(dom);
|
|
35
46
|
dom
|
|
36
47
|
.command('query')
|
|
37
48
|
.description('Find elements by CSS selector')
|
|
38
49
|
.argument('<selector>', 'CSS selector (e.g., ".error", "#app", "button")')
|
|
39
50
|
.option('-j, --json', 'Output as JSON')
|
|
51
|
+
.addHelpText('after', SELECTOR_SCOPE_HELP)
|
|
40
52
|
.action(async (selector, options) => {
|
|
41
53
|
await handleDomQuery(selector, options);
|
|
42
54
|
});
|
|
@@ -44,7 +56,7 @@ export function registerDomCommands(program) {
|
|
|
44
56
|
.command('eval')
|
|
45
57
|
.description('Evaluate JavaScript expression in the page context')
|
|
46
58
|
.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
|
|
59
|
+
.option('--frame <frame>', 'Evaluate in an iframe, cross-origin ones included: index (from dom frames; 87 when stale), name/id attribute, or part of the name, id or URL')
|
|
48
60
|
.option('-j, --json', 'Output as JSON')
|
|
49
61
|
.action(async (script, options) => {
|
|
50
62
|
await handleDomEval(script, options);
|
|
@@ -60,7 +72,7 @@ export function registerDomCommands(program) {
|
|
|
60
72
|
});
|
|
61
73
|
dom
|
|
62
74
|
.command('frames')
|
|
63
|
-
.description("List the page's iframes (nested and cross-origin ones included) for eval --frame")
|
|
75
|
+
.description("List the page's iframes in document order (nested and cross-origin ones included) for eval --frame")
|
|
64
76
|
.option('-j, --json', 'Output as JSON')
|
|
65
77
|
.action(async (options) => {
|
|
66
78
|
await handleDomFrames(options);
|
|
@@ -68,10 +80,12 @@ export function registerDomCommands(program) {
|
|
|
68
80
|
dom
|
|
69
81
|
.command('get')
|
|
70
82
|
.description('Get semantic accessibility structure (default) or raw HTML (--raw)')
|
|
71
|
-
.argument('[
|
|
83
|
+
.argument('[selectorOrIndex]', `${SELECTOR_OR_INDEX_ARGUMENT} (e.g. ".error", "#app", 0); default: ${DOM_GET_DEFAULT_SELECTOR}`)
|
|
72
84
|
.option('--raw', 'Output raw HTML with all filtering options')
|
|
85
|
+
.option('--full', 'Show all of the element text (default: the first 500 characters)')
|
|
73
86
|
.option('--all', 'Get all matches (only with --raw)')
|
|
74
|
-
.option('--
|
|
87
|
+
.option('--index <n>', 'Element index if selector matches multiple (0-based)', integerOption(0))
|
|
88
|
+
.addOption(new Option('--nth <n>', 'Alias of --index').argParser(integerOption(0)).hideHelp())
|
|
75
89
|
.option('--node-id <id>', 'Get the element with this node id (from dom query/get --raw or a11y describe; implies --raw)', integerOption(1))
|
|
76
90
|
.option('-j, --json', 'Output as JSON')
|
|
77
91
|
.action(async (selector, options) => {
|
|
@@ -81,6 +95,7 @@ export function registerDomCommands(program) {
|
|
|
81
95
|
.command('screenshot')
|
|
82
96
|
.description('Capture page or element screenshot')
|
|
83
97
|
.argument('<path>', 'Output file path, or directory for --follow mode')
|
|
98
|
+
.argument('[selector]', 'Element to capture: CSS selector or index from a query (same as --selector / --index)')
|
|
84
99
|
.option('--selector <selector>', 'CSS selector for element capture')
|
|
85
100
|
.option('--index <number>', 'Cached element index (0-based) from previous query', integerOption(0))
|
|
86
101
|
.option('--format <format>', 'Image format: png or jpeg/jpg (default: from the file extension, else png)', screenshotFormatOption)
|
|
@@ -92,8 +107,8 @@ export function registerDomCommands(program) {
|
|
|
92
107
|
.option('--interval <ms>', 'Capture interval for --follow (default: 1000)')
|
|
93
108
|
.option('--limit <count>', 'Max frames for --follow')
|
|
94
109
|
.option('-j, --json', 'Output as JSON')
|
|
95
|
-
.action(async (path, options) => {
|
|
96
|
-
await handleDomScreenshot(path, options);
|
|
110
|
+
.action(async (path, target, options) => {
|
|
111
|
+
await handleDomScreenshot(path, target, options);
|
|
97
112
|
});
|
|
98
113
|
}
|
|
99
114
|
//# sourceMappingURL=index.js.map
|