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
|
@@ -18,6 +18,55 @@ export const FORM_DISCOVERY_SCRIPT = `
|
|
|
18
18
|
(function() {
|
|
19
19
|
const result = { forms: [] };
|
|
20
20
|
|
|
21
|
+
// Rendered and not visibility-hidden, ancestors included (opacity is left
|
|
22
|
+
// out: styled checkboxes and radios are often transparent)
|
|
23
|
+
function isShown(element) {
|
|
24
|
+
if (typeof element.checkVisibility === 'function') {
|
|
25
|
+
return element.checkVisibility({ visibilityProperty: true });
|
|
26
|
+
}
|
|
27
|
+
const style = window.getComputedStyle(element);
|
|
28
|
+
return style.display !== 'none' && style.visibility !== 'hidden';
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Inside an open dialog: <dialog open>, aria-modal or a dialog role, or a
|
|
32
|
+
// modal without them (DocSearch): an overlay ancestor below <body>
|
|
33
|
+
// (position fixed or absolute) that is raised (z-index > 0) or has a
|
|
34
|
+
// backdrop and whose class names a modal or dialog, or a fixed overlay over
|
|
35
|
+
// half the viewport or more that is raised or lies over other page content
|
|
36
|
+
// at the viewport centre. Static wrappers (Drupal's
|
|
37
|
+
// dialog-off-canvas-main-canvas) and fixed app shells holding the whole
|
|
38
|
+
// page are not dialogs.
|
|
39
|
+
function inDialog(element) {
|
|
40
|
+
if (element.closest('dialog[open], [aria-modal="true"], [role="dialog"], [role="alertdialog"]')) return true;
|
|
41
|
+
const viewportArea = window.innerWidth * window.innerHeight;
|
|
42
|
+
for (let node = element.parentElement; node && node !== document.body; node = node.parentElement) {
|
|
43
|
+
const style = window.getComputedStyle(node);
|
|
44
|
+
if (style.position !== 'fixed' && style.position !== 'absolute') continue;
|
|
45
|
+
const raised = parseInt(style.zIndex, 10) > 0;
|
|
46
|
+
const className = typeof node.className === 'string' ? node.className : '';
|
|
47
|
+
if (/(^|[\\s_-])(modal|dialog)($|[\\s_-])/i.test(className) && (raised || hasBackdrop(node))) return true;
|
|
48
|
+
if (style.position !== 'fixed') continue;
|
|
49
|
+
const box = node.getBoundingClientRect();
|
|
50
|
+
if (box.width * box.height >= viewportArea / 2 && (raised || coversContent(node))) return true;
|
|
51
|
+
}
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// A backdrop element next to or inside an overlay
|
|
56
|
+
function hasBackdrop(node) {
|
|
57
|
+
const backdrop = /backdrop|overlay/i;
|
|
58
|
+
const near = [node.previousElementSibling, node.nextElementSibling, ...Array.from(node.children)];
|
|
59
|
+
return near.some((n) => n && typeof n.className === 'string' && backdrop.test(n.className));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// The overlay is on top at the viewport centre with other page content
|
|
63
|
+
// (not its ancestors) below it
|
|
64
|
+
function coversContent(node) {
|
|
65
|
+
const stack = document.elementsFromPoint(window.innerWidth / 2, window.innerHeight / 2);
|
|
66
|
+
if (!stack.length || !node.contains(stack[0])) return false;
|
|
67
|
+
return stack.some((n) => !n.contains(node) && !node.contains(n));
|
|
68
|
+
}
|
|
69
|
+
|
|
21
70
|
function isUnique(selector) {
|
|
22
71
|
try {
|
|
23
72
|
return document.querySelectorAll(selector).length === 1;
|
|
@@ -66,9 +115,50 @@ export const FORM_DISCOVERY_SCRIPT = `
|
|
|
66
115
|
cleaned = cleaned.replace(pattern, '');
|
|
67
116
|
}
|
|
68
117
|
cleaned = cleaned.replace(/\\s{2,}/g, ' ').trim();
|
|
118
|
+
cleaned = cleaned.replace(/^\\*\\s+/, '').replace(/\\s+\\*(\\s*:?)$/, '$1');
|
|
69
119
|
return cleaned || text.trim();
|
|
70
120
|
}
|
|
71
121
|
|
|
122
|
+
// The element labelling a field: label[for], aria-labelledby, wrapping label
|
|
123
|
+
function labelElement(element) {
|
|
124
|
+
const labelFor = element.id ? document.querySelector('label[for="' + CSS.escape(element.id) + '"]') : null;
|
|
125
|
+
const labelledBy = element.getAttribute('aria-labelledby');
|
|
126
|
+
return labelFor || (labelledBy && document.getElementById(labelledBy)) || element.closest('label');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// A standalone required star in label text: "* Name", "Name *", "Name *:"
|
|
130
|
+
// (not a footnote mark such as "Terms*")
|
|
131
|
+
function labelMarksRequired(text) {
|
|
132
|
+
return /^\\*\\s|\\s\\*\\s*:?$/.test(String(text).trim());
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Required by attribute, ARIA, or a label marked with a standalone star,
|
|
136
|
+
// or with an element of its own holding just "*" (<span aria-hidden>*</span>)
|
|
137
|
+
function isRequired(element) {
|
|
138
|
+
if (element.required || element.getAttribute('aria-required') === 'true') return true;
|
|
139
|
+
const label = labelElement(element);
|
|
140
|
+
if (!label) return false;
|
|
141
|
+
const clone = label.cloneNode(true);
|
|
142
|
+
clone.querySelectorAll('input, select, textarea, button').forEach(i => i.remove());
|
|
143
|
+
return labelMarksRequired(clone.textContent || '') ||
|
|
144
|
+
Array.from(clone.querySelectorAll('*')).some(n => n.textContent.trim() === '*');
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Name of the radio or checkbox group a field belongs to: its radiogroup's
|
|
148
|
+
// label, its fieldset's legend, or its name
|
|
149
|
+
function groupLabel(element) {
|
|
150
|
+
const type = element.type?.toLowerCase();
|
|
151
|
+
if ((type !== 'radio' && type !== 'checkbox') || !element.name) return undefined;
|
|
152
|
+
const group = element.closest('[role="radiogroup"], fieldset');
|
|
153
|
+
const named = group && (group.getAttribute('aria-label') ||
|
|
154
|
+
(group.querySelector(':scope > legend') || {}).textContent);
|
|
155
|
+
if (named && named.trim()) return cleanLabelText(named);
|
|
156
|
+
return element.name
|
|
157
|
+
.replace(/[_-]/g, ' ')
|
|
158
|
+
.replace(/([a-z])([A-Z])/g, '$1 $2')
|
|
159
|
+
.replace(/^./, s => s.toUpperCase());
|
|
160
|
+
}
|
|
161
|
+
|
|
72
162
|
function extractLabel(element) {
|
|
73
163
|
if (element.id) {
|
|
74
164
|
const labelFor = document.querySelector('label[for="' + CSS.escape(element.id) + '"]');
|
|
@@ -237,7 +327,7 @@ export const FORM_DISCOVERY_SCRIPT = `
|
|
|
237
327
|
score += Math.min(distinctFields * 3, 30);
|
|
238
328
|
const textTypes = ['text', 'search', 'email', 'password', 'textarea', 'tel', 'url', 'number', 'textbox'];
|
|
239
329
|
if (fields.some((f) => textTypes.includes(f.type))) score += 10;
|
|
240
|
-
const hasSubmit = buttons.some(b => b.type === 'submit' || b.
|
|
330
|
+
const hasSubmit = buttons.some(b => b.type === 'submit' || b.primaryClass);
|
|
241
331
|
if (hasSubmit) score += 10;
|
|
242
332
|
const style = window.getComputedStyle(formEl);
|
|
243
333
|
if (style.display === 'none' || style.visibility === 'hidden') score -= 100;
|
|
@@ -377,8 +467,7 @@ export const FORM_DISCOVERY_SCRIPT = `
|
|
|
377
467
|
const allInputs = new Set([...nativeInputs, ...customInputs]);
|
|
378
468
|
let idx = startIndex;
|
|
379
469
|
for (const el of allInputs) {
|
|
380
|
-
const
|
|
381
|
-
const isHidden = style.display === 'none' || style.visibility === 'hidden' || el.type === 'hidden';
|
|
470
|
+
const isHidden = el.type === 'hidden' || !isShown(el);
|
|
382
471
|
const value = getFieldValue(el);
|
|
383
472
|
fields.push({
|
|
384
473
|
index: idx,
|
|
@@ -389,7 +478,8 @@ export const FORM_DISCOVERY_SCRIPT = `
|
|
|
389
478
|
label: extractLabel(el),
|
|
390
479
|
name: el.name || null,
|
|
391
480
|
placeholder: el.placeholder || undefined,
|
|
392
|
-
required: el
|
|
481
|
+
required: isRequired(el),
|
|
482
|
+
groupLabel: groupLabel(el),
|
|
393
483
|
disabled: el.disabled || el.getAttribute('aria-disabled') === 'true',
|
|
394
484
|
readOnly: el.readOnly || false,
|
|
395
485
|
hidden: isHidden,
|
|
@@ -420,21 +510,22 @@ export const FORM_DISCOVERY_SCRIPT = `
|
|
|
420
510
|
);
|
|
421
511
|
let idx = startIndex;
|
|
422
512
|
for (const el of buttonEls) {
|
|
423
|
-
|
|
424
|
-
if (style.display === 'none' || style.visibility === 'hidden') continue;
|
|
513
|
+
if (!isShown(el)) continue;
|
|
425
514
|
const type = el.type?.toLowerCase() || 'button';
|
|
426
515
|
const btnType = type === 'submit' ? 'submit' : type === 'reset' ? 'reset' : 'button';
|
|
427
|
-
const
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
516
|
+
const explicitSubmit = btnType === 'submit' &&
|
|
517
|
+
(el.tagName === 'INPUT' || (el.getAttribute('type') || '').toLowerCase() === 'submit');
|
|
518
|
+
const formDefault = btnType === 'submit' && !explicitSubmit && Boolean(el.form);
|
|
519
|
+
const primaryClass = Array.from(el.classList).some(c => /^(btn[-_])?(primary|submit)$/i.test(c));
|
|
431
520
|
buttons.push({
|
|
432
521
|
index: idx,
|
|
433
522
|
selector: generateSelector(el),
|
|
434
523
|
label: extractButtonLabel(el),
|
|
435
524
|
type: btnType,
|
|
436
525
|
disabled: el.disabled || el.getAttribute('aria-disabled') === 'true',
|
|
437
|
-
|
|
526
|
+
explicitSubmit: explicitSubmit,
|
|
527
|
+
formDefault: formDefault,
|
|
528
|
+
primaryClass: primaryClass
|
|
438
529
|
});
|
|
439
530
|
idx++;
|
|
440
531
|
}
|
|
@@ -444,9 +535,11 @@ export const FORM_DISCOVERY_SCRIPT = `
|
|
|
444
535
|
const forms = document.querySelectorAll('form');
|
|
445
536
|
let globalIndex = 0;
|
|
446
537
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
538
|
+
// A page still loading may not have a body yet
|
|
539
|
+
const pageRoot = document.body || document.documentElement;
|
|
540
|
+
if (forms.length === 0 && pageRoot) {
|
|
541
|
+
const bodyFields = discoverFields(pageRoot, 0, 0);
|
|
542
|
+
const bodyButtons = discoverButtons(pageRoot, bodyFields.length);
|
|
450
543
|
if (bodyFields.length > 0) {
|
|
451
544
|
result.forms.push({
|
|
452
545
|
index: 0,
|
|
@@ -455,6 +548,8 @@ export const FORM_DISCOVERY_SCRIPT = `
|
|
|
455
548
|
method: 'GET',
|
|
456
549
|
step: null,
|
|
457
550
|
relevanceScore: bodyFields.length * 3,
|
|
551
|
+
hidden: false,
|
|
552
|
+
inDialog: false,
|
|
458
553
|
inIframe: false,
|
|
459
554
|
fields: bodyFields,
|
|
460
555
|
buttons: bodyButtons
|
|
@@ -468,6 +563,10 @@ export const FORM_DISCOVERY_SCRIPT = `
|
|
|
468
563
|
const buttons = discoverButtons(formEl, globalIndex);
|
|
469
564
|
globalIndex += buttons.length;
|
|
470
565
|
const inIframe = formEl.ownerDocument !== document;
|
|
566
|
+
// Shown when a field or a button is (buttons list shown ones only), or
|
|
567
|
+
// it has neither (a form with only type=hidden inputs and no button)
|
|
568
|
+
const shown = isShown(formEl) &&
|
|
569
|
+
(fields.some((f) => !f.hidden) || buttons.length > 0 || fields.length === 0);
|
|
471
570
|
result.forms.push({
|
|
472
571
|
index: i,
|
|
473
572
|
name: extractFormName(formEl),
|
|
@@ -475,6 +574,8 @@ export const FORM_DISCOVERY_SCRIPT = `
|
|
|
475
574
|
method: (formEl.method || 'GET').toUpperCase(),
|
|
476
575
|
step: detectFormStep(formEl),
|
|
477
576
|
relevanceScore: calculateRelevance(formEl, fields, buttons),
|
|
577
|
+
hidden: !shown,
|
|
578
|
+
inDialog: shown && inDialog(formEl),
|
|
478
579
|
inIframe: inIframe,
|
|
479
580
|
fields: fields,
|
|
480
581
|
buttons: buttons
|
|
@@ -482,8 +583,6 @@ export const FORM_DISCOVERY_SCRIPT = `
|
|
|
482
583
|
}
|
|
483
584
|
}
|
|
484
585
|
|
|
485
|
-
result.forms.sort((a, b) => b.relevanceScore - a.relevanceScore);
|
|
486
|
-
|
|
487
586
|
// Forms the main document does not contain may be in its same-origin iframes
|
|
488
587
|
if (result.forms.length === 0) {
|
|
489
588
|
result.frameForms = [];
|
|
@@ -496,6 +595,7 @@ export const FORM_DISCOVERY_SCRIPT = `
|
|
|
496
595
|
}
|
|
497
596
|
}
|
|
498
597
|
|
|
598
|
+
result.readyState = document.readyState;
|
|
499
599
|
return result;
|
|
500
600
|
})()
|
|
501
601
|
`;
|
|
@@ -19,11 +19,20 @@ export type PointerAction = 'click' | 'double' | 'right' | 'hover';
|
|
|
19
19
|
* @returns `Input.dispatchMouseEvent` parameters, in order
|
|
20
20
|
*/
|
|
21
21
|
export declare function mouseEvents(action: PointerAction, x: number, y: number): Array<Record<string, unknown>>;
|
|
22
|
+
/**
|
|
23
|
+
* Whether the mouse press just dispatched reached the target element
|
|
24
|
+
* (see {@link readPressProbe}).
|
|
25
|
+
*
|
|
26
|
+
* @param cdp - CDP connection
|
|
27
|
+
* @returns False only when the target received no pointerdown/mousedown
|
|
28
|
+
*/
|
|
29
|
+
export declare function pressReachedTarget(cdp: CDPConnection): Promise<boolean>;
|
|
22
30
|
/**
|
|
23
31
|
* Click an element.
|
|
24
32
|
*/
|
|
25
33
|
export declare function clickElement(cdp: CDPConnection, selector: string, options?: {
|
|
26
34
|
index?: number;
|
|
27
35
|
action?: PointerAction;
|
|
36
|
+
strict?: boolean;
|
|
28
37
|
}): Promise<ClickResult>;
|
|
29
38
|
//# sourceMappingURL=fill.d.ts.map
|
|
@@ -5,12 +5,12 @@
|
|
|
5
5
|
import * as fs from 'node:fs';
|
|
6
6
|
import * as path from 'node:path';
|
|
7
7
|
import { CommandError } from '../../../errors/index.js';
|
|
8
|
-
import { fileNotFoundError, uploadDirectoryError, singleFileInputError, fillableElementNotFoundError, clickableElementNotFoundError, clickTargetDetachedError, unexpectedResponseFormatError, operationFailedError, } from '../../../errors/messages.js';
|
|
9
|
-
import { escapeValueForJS, formatScriptExecutionError, throwIfInvalidSelector, withMultipleMatchesWarning, } from './shared.js';
|
|
10
|
-
import { REACT_FILL_SCRIPT, CLICK_ELEMENT_SCRIPT, isFillResult, isClickResult, } from '../reactEventHelpers.js';
|
|
11
|
-
import { FIND_ELEMENTS_JS, selectorArgsJS } from '../targetNode.js';
|
|
8
|
+
import { fileNotFoundError, uploadDirectoryError, singleFileInputError, fillableElementNotFoundError, clickableElementNotFoundError, clickTargetDetachedError, unexpectedResponseFormatError, operationFailedError, pressNotReceivedError, unreachableElementError, } from '../../../errors/messages.js';
|
|
9
|
+
import { escapeValueForJS, formatScriptExecutionError, throwIfInvalidSelector, withMultipleMatchesWarning, withValueMismatchWarning, } from './shared.js';
|
|
10
|
+
import { FILL_READ_BACK_SCRIPT, REACT_FILL_SCRIPT, CLICK_ELEMENT_SCRIPT, isFillResult, isClickResult, } from '../reactEventHelpers.js';
|
|
11
|
+
import { FIND_ELEMENTS_JS, LABEL_CONTROL_JS, selectorArgsJS } from '../targetNode.js';
|
|
12
12
|
import { createLogger } from '../../../ui/logging/index.js';
|
|
13
|
-
import { POINTER_ACTION_DONE, domClickFallbackWarning } from '../../../ui/messages/commands.js';
|
|
13
|
+
import { CLICK_NOT_RECEIVED_WARNING, POINTER_ACTION_DONE, POINTER_ACTION_NOUN, domClickFallbackWarning, } from '../../../ui/messages/commands.js';
|
|
14
14
|
import { getErrorMessage } from '../../../utils/errors.js';
|
|
15
15
|
import { EXIT_CODES } from '../../../utils/exitCodes.js';
|
|
16
16
|
const log = createLogger('dom');
|
|
@@ -38,7 +38,12 @@ export async function fillElement(cdp, selector, value, options = {}) {
|
|
|
38
38
|
}
|
|
39
39
|
if (cdpResponse.result?.value && isFillResult(cdpResponse.result.value)) {
|
|
40
40
|
const result = cdpResponse.result.value;
|
|
41
|
-
|
|
41
|
+
if (!result.fileInput)
|
|
42
|
+
return withValueMismatchWarning(await withReadBack(cdp, result));
|
|
43
|
+
const uploaded = await setFileInput(cdp, selector, value, options);
|
|
44
|
+
return uploaded.success && result.elementType
|
|
45
|
+
? { ...uploaded, elementType: result.elementType }
|
|
46
|
+
: uploaded;
|
|
42
47
|
}
|
|
43
48
|
const err = unexpectedResponseFormatError('FillResult');
|
|
44
49
|
throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.SOFTWARE_ERROR);
|
|
@@ -52,6 +57,52 @@ export async function fillElement(cdp, selector, value, options = {}) {
|
|
|
52
57
|
throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.SOFTWARE_ERROR);
|
|
53
58
|
}
|
|
54
59
|
}
|
|
60
|
+
/** How long reading a filled value back may take before it is skipped */
|
|
61
|
+
const READ_BACK_TIMEOUT_MS = 1000;
|
|
62
|
+
/**
|
|
63
|
+
* Add the read-back of a successful fill ({@link FILL_READ_BACK_SCRIPT}):
|
|
64
|
+
* `valueMismatch` when the field's value is not the one given. The fill
|
|
65
|
+
* stands without it when the read-back fails, takes longer than
|
|
66
|
+
* {@link READ_BACK_TIMEOUT_MS} or finds nothing (a change handler navigated
|
|
67
|
+
* or replaced the page).
|
|
68
|
+
*
|
|
69
|
+
* @param cdp - CDP connection
|
|
70
|
+
* @param result - Fill result
|
|
71
|
+
* @returns The result, with `valueMismatch` when the value differs
|
|
72
|
+
*/
|
|
73
|
+
async function withReadBack(cdp, result) {
|
|
74
|
+
if (!result.success)
|
|
75
|
+
return result;
|
|
76
|
+
let timer;
|
|
77
|
+
const timedOut = new Promise((resolve) => {
|
|
78
|
+
timer = setTimeout(() => resolve(undefined), READ_BACK_TIMEOUT_MS);
|
|
79
|
+
});
|
|
80
|
+
const readBack = cdp
|
|
81
|
+
.send('Runtime.evaluate', {
|
|
82
|
+
expression: FILL_READ_BACK_SCRIPT,
|
|
83
|
+
returnByValue: true,
|
|
84
|
+
awaitPromise: true,
|
|
85
|
+
})
|
|
86
|
+
.then((response) => response.result?.value)
|
|
87
|
+
.catch((error) => {
|
|
88
|
+
log.debug(`Filled value not read back: ${getErrorMessage(error)}`);
|
|
89
|
+
return undefined;
|
|
90
|
+
});
|
|
91
|
+
const mismatch = await Promise.race([readBack, timedOut]).finally(() => clearTimeout(timer));
|
|
92
|
+
return isValueMismatch(mismatch) ? { ...result, valueMismatch: mismatch } : result;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Whether a read-back value is a mismatch.
|
|
96
|
+
*
|
|
97
|
+
* @param value - Value from {@link FILL_READ_BACK_SCRIPT}
|
|
98
|
+
* @returns True for `{ expected, actual }`
|
|
99
|
+
*/
|
|
100
|
+
function isValueMismatch(value) {
|
|
101
|
+
if (typeof value !== 'object' || value === null)
|
|
102
|
+
return false;
|
|
103
|
+
const obj = value;
|
|
104
|
+
return typeof obj['expected'] === 'string' && typeof obj['actual'] === 'string';
|
|
105
|
+
}
|
|
55
106
|
/** Empties a file input as a user removing the selection would (with events). */
|
|
56
107
|
const CLEAR_FILE_INPUT_FUNCTION = `function () {
|
|
57
108
|
this.value = '';
|
|
@@ -125,7 +176,7 @@ async function setFileInput(cdp, selector, value, options) {
|
|
|
125
176
|
return problem;
|
|
126
177
|
try {
|
|
127
178
|
const located = (await cdp.send('Runtime.evaluate', {
|
|
128
|
-
expression: `(${FIND_ELEMENTS_JS})(${selectorArgsJS(selector)})[${options.index ?? 0}]`,
|
|
179
|
+
expression: `((el) => (${LABEL_CONTROL_JS})(el) || el)((${FIND_ELEMENTS_JS})(${selectorArgsJS(selector)})[${options.index ?? 0}])`,
|
|
129
180
|
objectGroup: UPLOAD_OBJECT_GROUP,
|
|
130
181
|
}));
|
|
131
182
|
if (located.exceptionDetails)
|
|
@@ -213,6 +264,87 @@ export function mouseEvents(action, x, y) {
|
|
|
213
264
|
];
|
|
214
265
|
return action === 'double' ? [moved, ...press(1), ...press(2)] : [moved, ...press(1)];
|
|
215
266
|
}
|
|
267
|
+
/**
|
|
268
|
+
* Page script that stops the press probe installed by CLICK_ELEMENT_SCRIPT
|
|
269
|
+
* and evaluates to whether the mouse press reached the target and, when it
|
|
270
|
+
* did not, the element it landed on (if the page saw it at all).
|
|
271
|
+
*/
|
|
272
|
+
const PRESS_PROBE_READ_SCRIPT = `(() => {
|
|
273
|
+
const probe = window.__bdgPressProbe;
|
|
274
|
+
delete window.__bdgPressProbe;
|
|
275
|
+
if (!probe) return { reached: true };
|
|
276
|
+
probe.stop();
|
|
277
|
+
return { reached: probe.reached, landedOn: probe.landedOn };
|
|
278
|
+
})()`;
|
|
279
|
+
/**
|
|
280
|
+
* Read the press probe: whether the mouse press just dispatched reached the
|
|
281
|
+
* target element, and otherwise where it landed.
|
|
282
|
+
*
|
|
283
|
+
* Any doubt (no probe, evaluation failure) counts as reached, so only a
|
|
284
|
+
* press that the page provably never saw is reported.
|
|
285
|
+
*
|
|
286
|
+
* @param cdp - CDP connection
|
|
287
|
+
* @returns Outcome; not reached only when the target received no pointerdown/mousedown
|
|
288
|
+
*/
|
|
289
|
+
async function readPressProbe(cdp) {
|
|
290
|
+
try {
|
|
291
|
+
const response = (await cdp.send('Runtime.evaluate', {
|
|
292
|
+
expression: PRESS_PROBE_READ_SCRIPT,
|
|
293
|
+
returnByValue: true,
|
|
294
|
+
}));
|
|
295
|
+
const value = response.result?.value;
|
|
296
|
+
if (value?.reached !== false)
|
|
297
|
+
return { reached: true };
|
|
298
|
+
return typeof value.landedOn === 'string'
|
|
299
|
+
? { reached: false, landedOn: value.landedOn }
|
|
300
|
+
: { reached: false };
|
|
301
|
+
}
|
|
302
|
+
catch (error) {
|
|
303
|
+
log.debug(`Press probe not read: ${getErrorMessage(error)}`);
|
|
304
|
+
return { reached: true };
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Whether the mouse press just dispatched reached the target element
|
|
309
|
+
* (see {@link readPressProbe}).
|
|
310
|
+
*
|
|
311
|
+
* @param cdp - CDP connection
|
|
312
|
+
* @returns False only when the target received no pointerdown/mousedown
|
|
313
|
+
*/
|
|
314
|
+
export async function pressReachedTarget(cdp) {
|
|
315
|
+
return (await readPressProbe(cdp)).reached;
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Dispatch real mouse events for an action, checking after the first press
|
|
319
|
+
* that the target received it. With `stopIfMissed`, a press that missed is
|
|
320
|
+
* only released (no further presses). If dispatching fails first, the press
|
|
321
|
+
* probe is still removed from the page.
|
|
322
|
+
*
|
|
323
|
+
* @param cdp - CDP connection
|
|
324
|
+
* @param action - What to do
|
|
325
|
+
* @param point - Page coordinates
|
|
326
|
+
* @param stopIfMissed - Stop after releasing a press that missed (--strict)
|
|
327
|
+
* @returns Whether the first press reached the target, and where it landed otherwise
|
|
328
|
+
*/
|
|
329
|
+
async function dispatchMouseAction(cdp, action, point, stopIfMissed) {
|
|
330
|
+
let outcome;
|
|
331
|
+
try {
|
|
332
|
+
for (const event of mouseEvents(action, point.x, point.y)) {
|
|
333
|
+
await cdp.send('Input.dispatchMouseEvent', event);
|
|
334
|
+
if (event['type'] === 'mouseReleased' && outcome?.reached === false && stopIfMissed)
|
|
335
|
+
break;
|
|
336
|
+
if (event['type'] === 'mousePressed' && outcome === undefined) {
|
|
337
|
+
outcome = await readPressProbe(cdp);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
catch (error) {
|
|
342
|
+
if (outcome === undefined)
|
|
343
|
+
await readPressProbe(cdp);
|
|
344
|
+
throw error;
|
|
345
|
+
}
|
|
346
|
+
return outcome ?? { reached: true };
|
|
347
|
+
}
|
|
216
348
|
/**
|
|
217
349
|
* Drop the located element from the page without waiting: right after a
|
|
218
350
|
* click on a link, evaluation waits until the new page commits (seconds for a
|
|
@@ -225,30 +357,62 @@ function releaseClickTarget(cdp) {
|
|
|
225
357
|
.send('Runtime.evaluate', { expression: 'delete window.__bdgClickTarget' })
|
|
226
358
|
.catch((error) => log.debug(`Click target not released: ${getErrorMessage(error)}`));
|
|
227
359
|
}
|
|
360
|
+
/**
|
|
361
|
+
* Refuse a pointer action under `--strict` (exit 90, the page's state
|
|
362
|
+
* conflicts with the request): the element is unreachable by the mouse, or
|
|
363
|
+
* the press never reached it.
|
|
364
|
+
*
|
|
365
|
+
* @param result - Located click (selector and element description)
|
|
366
|
+
* @param action - What was refused
|
|
367
|
+
* @param why - Why the mouse could not reach it (nothing was sent), or the
|
|
368
|
+
* press that was sent and missed
|
|
369
|
+
* @returns Never
|
|
370
|
+
* @throws CommandError always
|
|
371
|
+
*/
|
|
372
|
+
function refuseUnreachable(result, action, why) {
|
|
373
|
+
const target = { selector: result.selector ?? '', element: result.element };
|
|
374
|
+
const verb = POINTER_ACTION_NOUN[action];
|
|
375
|
+
const err = 'missed' in why
|
|
376
|
+
? pressNotReceivedError(target, verb, why.missed.landedOn)
|
|
377
|
+
: unreachableElementError(target, why.obstruction, verb);
|
|
378
|
+
throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.RESOURCE_CONFLICT);
|
|
379
|
+
}
|
|
228
380
|
/**
|
|
229
381
|
* Click a located element.
|
|
230
382
|
*
|
|
231
383
|
* Uses real mouse events (pointerdown/mousedown/pointerup/mouseup/click, all
|
|
232
384
|
* trusted) at the element's center when it is the topmost element there, so
|
|
233
385
|
* components that react to pointer or mouse events (menus, selects) respond.
|
|
234
|
-
* Otherwise (covered or zero-size) falls back to `el.click()` and says so
|
|
386
|
+
* Otherwise (covered or zero-size) falls back to `el.click()` and says so,
|
|
387
|
+
* or with `strict` refuses (as it does when the press never reached it).
|
|
235
388
|
* Double and right clicks, and hovering, work the same way.
|
|
236
389
|
*
|
|
237
390
|
* @param cdp - CDP connection
|
|
238
391
|
* @param located - Locate result with center point
|
|
239
392
|
* @param action - Click, double click, right click or hover
|
|
393
|
+
* @param strict - Refuse instead of falling back to DOM events
|
|
240
394
|
* @returns Click result
|
|
395
|
+
* @throws CommandError under `strict` when a user could not reach the element
|
|
241
396
|
*/
|
|
242
|
-
async function performClick(cdp, located, action) {
|
|
397
|
+
async function performClick(cdp, located, action, strict) {
|
|
243
398
|
const { x, y, hittable, obstruction, ...result } = located;
|
|
244
399
|
if (!result.success)
|
|
245
400
|
return result;
|
|
246
401
|
if (hittable && x !== undefined && y !== undefined) {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
402
|
+
const press = await dispatchMouseAction(cdp, action, { x, y }, strict);
|
|
403
|
+
releaseClickTarget(cdp);
|
|
404
|
+
if (!press.reached && strict)
|
|
405
|
+
refuseUnreachable(result, action, { missed: press });
|
|
406
|
+
return withMultipleMatchesWarning({
|
|
407
|
+
...result,
|
|
408
|
+
action,
|
|
409
|
+
method: 'mouse',
|
|
410
|
+
...(!press.reached && { warning: CLICK_NOT_RECEIVED_WARNING }),
|
|
411
|
+
}, result.selectedIndex, `${POINTER_ACTION_DONE[action].toLowerCase()} the first visible one`);
|
|
412
|
+
}
|
|
413
|
+
if (strict) {
|
|
250
414
|
releaseClickTarget(cdp);
|
|
251
|
-
|
|
415
|
+
refuseUnreachable(result, action, { obstruction: obstruction ?? null });
|
|
252
416
|
}
|
|
253
417
|
const response = (await cdp.send('Runtime.evaluate', {
|
|
254
418
|
expression: domFallbackScript(action),
|
|
@@ -282,7 +446,7 @@ export async function clickElement(cdp, selector, options = {}) {
|
|
|
282
446
|
throw new CommandError(errorMessage, { suggestion: err.suggestion }, EXIT_CODES.SOFTWARE_ERROR);
|
|
283
447
|
}
|
|
284
448
|
if (cdpResponse.result?.value && isClickResult(cdpResponse.result.value)) {
|
|
285
|
-
return await performClick(cdp, cdpResponse.result.value, action);
|
|
449
|
+
return await performClick(cdp, cdpResponse.result.value, action, options.strict === true);
|
|
286
450
|
}
|
|
287
451
|
const err = unexpectedResponseFormatError('ClickResult');
|
|
288
452
|
throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.SOFTWARE_ERROR);
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* - `fill.ts` — fillElement, clickElement
|
|
5
5
|
* - `pressKey.ts` — pressKeyElement + types
|
|
6
6
|
* - `scroll.ts` — scrollPage + types
|
|
7
|
-
* - `stability.ts` —
|
|
7
|
+
* - `stability.ts` — withActionStability
|
|
8
8
|
* - `shared.ts` — internal script/error helpers
|
|
9
9
|
*
|
|
10
10
|
* Callers keep importing from `@/commands/dom/formFillHelpers.js` which
|
|
@@ -13,5 +13,5 @@
|
|
|
13
13
|
export { fillElement, clickElement } from './fill.js';
|
|
14
14
|
export { pressKeyElement, type PressKeyOptions, type PressKeyResult, } from './pressKey.js';
|
|
15
15
|
export { scrollPage, type ScrollOptions, type ScrollResult, } from './scroll.js';
|
|
16
|
-
export {
|
|
16
|
+
export { withActionStability } from './stability.js';
|
|
17
17
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* - `fill.ts` — fillElement, clickElement
|
|
5
5
|
* - `pressKey.ts` — pressKeyElement + types
|
|
6
6
|
* - `scroll.ts` — scrollPage + types
|
|
7
|
-
* - `stability.ts` —
|
|
7
|
+
* - `stability.ts` — withActionStability
|
|
8
8
|
* - `shared.ts` — internal script/error helpers
|
|
9
9
|
*
|
|
10
10
|
* Callers keep importing from `@/commands/dom/formFillHelpers.js` which
|
|
@@ -13,5 +13,5 @@
|
|
|
13
13
|
export { fillElement, clickElement } from './fill.js';
|
|
14
14
|
export { pressKeyElement, } from './pressKey.js';
|
|
15
15
|
export { scrollPage, } from './scroll.js';
|
|
16
|
-
export {
|
|
16
|
+
export { withActionStability } from './stability.js';
|
|
17
17
|
//# sourceMappingURL=index.js.map
|
|
@@ -4,10 +4,11 @@
|
|
|
4
4
|
* that CDP skips (keypress / input / change / submit).
|
|
5
5
|
*/
|
|
6
6
|
import { CommandError } from '../../../errors/index.js';
|
|
7
|
-
import { keyPressFailedError, operationFailedError, unknownKeyError } from '../../../errors/messages.js';
|
|
7
|
+
import { VIA_LABEL_SUFFIX, keyPressFailedError, operationFailedError, unknownKeyError, } from '../../../errors/messages.js';
|
|
8
|
+
import { ELEMENT_IDENTITY_JS } from '../elementInfo.js';
|
|
8
9
|
import { throwIfInvalidSelector, withMultipleMatchesWarning, } from './shared.js';
|
|
9
10
|
import { describeModifiers, getKeyDefinition, impliesShift, MODIFIER_FLAGS, parseModifiers, similarKeyNames, shortcutCommands, } from '../keyMapping.js';
|
|
10
|
-
import { FIND_ELEMENTS_JS, selectorArgsJS } from '../targetNode.js';
|
|
11
|
+
import { FIND_ELEMENTS_JS, LABEL_CONTROL_JS, selectorArgsJS } from '../targetNode.js';
|
|
11
12
|
import { EXIT_CODES } from '../../../utils/exitCodes.js';
|
|
12
13
|
const FOCUS_ELEMENT_SCRIPT = `
|
|
13
14
|
(function(selector, parts, index) {
|
|
@@ -29,6 +30,9 @@ const FOCUS_ELEMENT_SCRIPT = `
|
|
|
29
30
|
} else {
|
|
30
31
|
el = allMatches[0];
|
|
31
32
|
}
|
|
33
|
+
const labelControl = (${LABEL_CONTROL_JS})(el);
|
|
34
|
+
const viaLabel = labelControl ? ${JSON.stringify(VIA_LABEL_SUFFIX)} : '';
|
|
35
|
+
if (labelControl) el = labelControl;
|
|
32
36
|
|
|
33
37
|
const pageLevel = el === document.body || el === document.documentElement;
|
|
34
38
|
if (!pageLevel) {
|
|
@@ -61,7 +65,13 @@ const FOCUS_ELEMENT_SCRIPT = `
|
|
|
61
65
|
}
|
|
62
66
|
}
|
|
63
67
|
|
|
64
|
-
return {
|
|
68
|
+
return {
|
|
69
|
+
success: true,
|
|
70
|
+
selector: selector,
|
|
71
|
+
element: (${ELEMENT_IDENTITY_JS})(el),
|
|
72
|
+
elementType: el.tagName.toLowerCase() + viaLabel,
|
|
73
|
+
matchCount: allMatches.length
|
|
74
|
+
};
|
|
65
75
|
})`;
|
|
66
76
|
/** Exit codes and suggestions for focus failures, by reason. */
|
|
67
77
|
const FOCUS_FAILURES = {
|
|
@@ -135,6 +145,7 @@ export async function pressKeyElement(cdp, selector, keyName, options = {}) {
|
|
|
135
145
|
key: keyName,
|
|
136
146
|
times,
|
|
137
147
|
...(modifierFlags > 0 && { modifiers: describeModifiers(modifierFlags) }),
|
|
148
|
+
...(focusResult.element !== undefined && { element: focusResult.element }),
|
|
138
149
|
elementType: focusResult.elementType,
|
|
139
150
|
...(focusResult.matchCount !== undefined && { matchCount: focusResult.matchCount }),
|
|
140
151
|
}, options.index, 'pressed the key on the first');
|
|
@@ -6,6 +6,9 @@ import type { CDPConnection } from '../../../connection/cdp.js';
|
|
|
6
6
|
import type { ScrollResult } from '../../../ipc/protocol/domTypes.js';
|
|
7
7
|
/**
|
|
8
8
|
* Scroll options.
|
|
9
|
+
*
|
|
10
|
+
* The reported viewport is the layout viewport without scrollbars and the
|
|
11
|
+
* page size the scrolling element's, as in `bdg dom layout`.
|
|
9
12
|
*/
|
|
10
13
|
export interface ScrollOptions {
|
|
11
14
|
down?: number;
|