browser-debugger-cli 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -1
- package/dist/cdp/schema.d.ts +4 -1
- package/dist/cdp/schema.js +48 -7
- package/dist/commands/cdp.js +3 -2
- package/dist/commands/cleanup.d.ts +11 -0
- package/dist/commands/cleanup.js +161 -57
- package/dist/commands/console.d.ts +20 -1
- package/dist/commands/console.js +57 -17
- package/dist/commands/details.js +3 -2
- package/dist/commands/dom/DomElementResolver.d.ts +10 -3
- package/dist/commands/dom/DomElementResolver.js +35 -17
- package/dist/commands/dom/a11y.d.ts +10 -0
- package/dist/commands/dom/a11y.js +27 -5
- package/dist/commands/dom/eval.d.ts +3 -1
- package/dist/commands/dom/eval.js +29 -4
- package/dist/commands/dom/form.js +16 -62
- package/dist/commands/dom/formInteraction.js +152 -113
- package/dist/commands/dom/formSummary.d.ts +49 -0
- package/dist/commands/dom/formSummary.js +180 -0
- package/dist/commands/dom/frames.d.ts +2 -1
- package/dist/commands/dom/frames.js +17 -2
- package/dist/commands/dom/get.d.ts +6 -5
- package/dist/commands/dom/get.js +92 -82
- package/dist/commands/dom/helpers/index.d.ts +1 -1
- package/dist/commands/dom/helpers/index.js +1 -1
- package/dist/commands/dom/helpers/query.d.ts +44 -17
- package/dist/commands/dom/helpers/query.js +244 -97
- package/dist/commands/dom/helpers/runElementCommand.d.ts +10 -2
- package/dist/commands/dom/helpers/runElementCommand.js +97 -30
- package/dist/commands/dom/helpers/screenshot.d.ts +4 -1
- package/dist/commands/dom/helpers/screenshot.js +164 -49
- package/dist/commands/dom/index.d.ts +3 -1
- package/dist/commands/dom/index.js +16 -6
- package/dist/commands/dom/layout.d.ts +14 -0
- package/dist/commands/dom/layout.js +54 -0
- package/dist/commands/dom/listeners.d.ts +5 -1
- package/dist/commands/dom/listeners.js +13 -3
- package/dist/commands/dom/query.js +2 -3
- package/dist/commands/dom/screenshot.d.ts +12 -2
- package/dist/commands/dom/screenshot.js +27 -3
- package/dist/commands/dom/semanticUtils.d.ts +6 -13
- package/dist/commands/dom/semanticUtils.js +15 -19
- package/dist/commands/dom/wait.d.ts +13 -0
- package/dist/commands/dom/wait.js +83 -0
- package/dist/commands/helpJson.js +2 -2
- package/dist/commands/network/list.js +4 -11
- package/dist/commands/optionBehaviors.js +112 -21
- package/dist/commands/page.d.ts +2 -1
- package/dist/commands/page.js +41 -5
- package/dist/commands/peek.js +4 -11
- package/dist/commands/sessions.d.ts +8 -0
- package/dist/commands/sessions.js +19 -0
- package/dist/commands/shared/CommandRunner.js +4 -4
- package/dist/commands/shared/dataFetcher.js +2 -2
- package/dist/commands/shared/followMode.d.ts +21 -1
- package/dist/commands/shared/followMode.js +29 -2
- package/dist/commands/shared/handleValidationError.d.ts +2 -2
- package/dist/commands/shared/handleValidationError.js +12 -3
- package/dist/commands/shared/optionTypes.d.ts +40 -5
- package/dist/commands/shared/startHelpers.js +12 -3
- package/dist/commands/shared/validation.d.ts +3 -2
- package/dist/commands/shared/validation.js +4 -3
- package/dist/commands/start.d.ts +63 -0
- package/dist/commands/start.js +115 -15
- package/dist/commands/status.js +29 -7
- package/dist/commands/stop.js +7 -6
- package/dist/commands/tail.js +4 -11
- package/dist/commands/types.d.ts +2 -0
- package/dist/commands.js +2 -0
- package/dist/connection/chromeIdentity.d.ts +65 -0
- package/dist/connection/chromeIdentity.js +143 -0
- package/dist/connection/launcher/profilePreferences.d.ts +47 -0
- package/dist/connection/launcher/profilePreferences.js +151 -0
- package/dist/connection/launcher.d.ts +21 -2
- package/dist/connection/launcher.js +42 -16
- package/dist/connection/portReservation.d.ts +14 -4
- package/dist/connection/portReservation.js +21 -6
- package/dist/connection/startupExit.d.ts +8 -0
- package/dist/connection/startupExit.js +15 -6
- package/dist/constants.d.ts +6 -2
- package/dist/constants.js +9 -2
- package/dist/daemon/SessionController.js +23 -7
- package/dist/daemon/errors.d.ts +1 -1
- package/dist/daemon/errors.js +1 -1
- package/dist/daemon/launcher.d.ts +2 -1
- package/dist/daemon/launcher.js +5 -6
- package/dist/daemon/server/SocketServer.js +1 -2
- package/dist/daemon/session/Session.d.ts +13 -0
- package/dist/daemon/session/Session.js +57 -8
- package/dist/daemon/session/chromeConnection.d.ts +9 -0
- package/dist/daemon/session/chromeConnection.js +45 -8
- package/dist/daemon/session/commandRegistry.js +52 -62
- package/dist/daemon/session/interactions.d.ts +35 -9
- package/dist/daemon/session/interactions.js +36 -9
- package/dist/daemon/session/triggeredRequests.d.ts +67 -0
- package/dist/daemon/session/triggeredRequests.js +157 -0
- package/dist/daemon/session/types.d.ts +5 -1
- package/dist/daemon.js +5393 -1600
- package/dist/errors/messages.d.ts +387 -24
- package/dist/errors/messages.js +761 -67
- package/dist/index.js +3976 -1558
- package/dist/ipc/client.d.ts +12 -1
- package/dist/ipc/client.js +22 -3
- package/dist/ipc/protocol/commands.d.ts +89 -4
- package/dist/ipc/protocol/commands.js +2 -0
- package/dist/ipc/protocol/domTypes.d.ts +258 -7
- package/dist/ipc/session/lifecycle.d.ts +8 -1
- package/dist/ipc/session/queries.d.ts +5 -1
- package/dist/ipc/session/types.d.ts +5 -0
- package/dist/ipc/transport/index.d.ts +2 -1
- package/dist/ipc/transport/index.js +2 -2
- package/dist/runtime/dom/actionEffects.d.ts +106 -0
- package/dist/runtime/dom/actionEffects.js +256 -0
- package/dist/runtime/dom/actionEffectsScripts.d.ts +52 -0
- package/dist/runtime/dom/actionEffectsScripts.js +234 -0
- package/dist/runtime/dom/elementGeometry.d.ts +170 -0
- package/dist/runtime/dom/elementGeometry.js +553 -0
- package/dist/runtime/dom/elementInfo.d.ts +77 -0
- package/dist/runtime/dom/elementInfo.js +191 -0
- package/dist/runtime/dom/evalHelpers.d.ts +51 -6
- package/dist/runtime/dom/evalHelpers.js +136 -26
- package/dist/runtime/dom/eventListeners.d.ts +2 -1
- package/dist/runtime/dom/eventListeners.js +174 -47
- package/dist/runtime/dom/formDiscovery.d.ts +1 -1
- package/dist/runtime/dom/formDiscovery.js +116 -16
- package/dist/runtime/dom/formFillHelpers/fill.d.ts +10 -0
- package/dist/runtime/dom/formFillHelpers/fill.js +125 -10
- package/dist/runtime/dom/formFillHelpers/index.d.ts +2 -2
- package/dist/runtime/dom/formFillHelpers/index.js +2 -2
- package/dist/runtime/dom/formFillHelpers/pressKey.js +14 -3
- package/dist/runtime/dom/formFillHelpers/scroll.d.ts +3 -0
- package/dist/runtime/dom/formFillHelpers/scroll.js +60 -18
- package/dist/runtime/dom/formFillHelpers/shared.d.ts +17 -0
- package/dist/runtime/dom/formFillHelpers/shared.js +25 -1
- package/dist/runtime/dom/formFillHelpers/stability.d.ts +20 -6
- package/dist/runtime/dom/formFillHelpers/stability.js +50 -19
- package/dist/runtime/dom/formSubmitHelpers.d.ts +3 -0
- package/dist/runtime/dom/formSubmitHelpers.js +89 -15
- package/dist/runtime/dom/frameLayout.d.ts +60 -0
- package/dist/runtime/dom/frameLayout.js +140 -0
- package/dist/runtime/dom/frameOrigin.d.ts +50 -0
- package/dist/runtime/dom/frameOrigin.js +62 -0
- package/dist/runtime/dom/frameScopedConnection.d.ts +92 -0
- package/dist/runtime/dom/frameScopedConnection.js +252 -0
- package/dist/runtime/dom/frameSelection.d.ts +1 -1
- package/dist/runtime/dom/frameSelection.js +2 -2
- package/dist/runtime/dom/frames.d.ts +25 -2
- package/dist/runtime/dom/frames.js +202 -63
- package/dist/runtime/dom/layout.d.ts +67 -0
- package/dist/runtime/dom/layout.js +333 -0
- package/dist/runtime/dom/listenerPageScripts.d.ts +66 -0
- package/dist/runtime/dom/listenerPageScripts.js +279 -0
- package/dist/runtime/dom/listenerSummary.d.ts +132 -11
- package/dist/runtime/dom/listenerSummary.js +344 -22
- package/dist/runtime/dom/pageActivity.d.ts +41 -0
- package/dist/runtime/dom/pageActivity.js +123 -0
- package/dist/runtime/dom/reactEventHelpers.d.ts +58 -2
- package/dist/runtime/dom/reactEventHelpers.js +212 -41
- package/dist/runtime/dom/targetNode.d.ts +80 -27
- package/dist/runtime/dom/targetNode.js +249 -33
- package/dist/runtime/dom/wait.d.ts +25 -0
- package/dist/runtime/dom/wait.js +199 -0
- package/dist/runtime/dom/waitCondition.d.ts +71 -0
- package/dist/runtime/dom/waitCondition.js +75 -0
- package/dist/runtime/page/emulation.d.ts +51 -0
- package/dist/runtime/page/emulation.js +80 -0
- package/dist/runtime/page/loadingState.d.ts +36 -0
- package/dist/runtime/page/loadingState.js +86 -0
- package/dist/runtime/page/navigation.d.ts +46 -2
- package/dist/runtime/page/navigation.js +69 -33
- package/dist/session/QueryCacheManager.d.ts +11 -1
- package/dist/session/QueryCacheManager.js +25 -3
- package/dist/session/chromeOwners.d.ts +34 -0
- package/dist/session/chromeOwners.js +51 -0
- package/dist/session/cleanup/staleSession.d.ts +11 -1
- package/dist/session/cleanup/staleSession.js +17 -6
- package/dist/session/cleanup/userCommands.js +2 -4
- package/dist/session/metadata.d.ts +5 -1
- package/dist/session/metadata.js +2 -1
- package/dist/session/paths.d.ts +77 -3
- package/dist/session/paths.js +111 -5
- package/dist/session/port.d.ts +31 -7
- package/dist/session/port.js +50 -43
- package/dist/session/portClaims.d.ts +66 -0
- package/dist/session/portClaims.js +284 -0
- package/dist/session/sessionList.d.ts +58 -0
- package/dist/session/sessionList.js +199 -0
- package/dist/session/sessionName.d.ts +46 -0
- package/dist/session/sessionName.js +97 -0
- package/dist/telemetry/a11y.d.ts +8 -3
- package/dist/telemetry/a11y.js +92 -28
- package/dist/telemetry/requestKinds.d.ts +32 -0
- package/dist/telemetry/requestKinds.js +61 -0
- package/dist/telemetry/requestState.d.ts +31 -0
- package/dist/telemetry/requestState.js +38 -0
- package/dist/types.d.ts +80 -3
- package/dist/ui/formatters/a11y.js +3 -0
- package/dist/ui/formatters/console/chronological.d.ts +8 -0
- package/dist/ui/formatters/console/chronological.js +17 -4
- package/dist/ui/formatters/console/json.js +3 -4
- package/dist/ui/formatters/console/shared.d.ts +12 -0
- package/dist/ui/formatters/console.d.ts +2 -2
- package/dist/ui/formatters/console.js +1 -1
- package/dist/ui/formatters/details.js +2 -1
- package/dist/ui/formatters/dom.d.ts +26 -14
- package/dist/ui/formatters/dom.js +65 -52
- package/dist/ui/formatters/form.js +29 -18
- package/dist/ui/formatters/layout.d.ts +31 -0
- package/dist/ui/formatters/layout.js +53 -0
- package/dist/ui/formatters/listeners.d.ts +3 -2
- package/dist/ui/formatters/listeners.js +73 -9
- package/dist/ui/formatters/networkHeaders.js +13 -0
- package/dist/ui/formatters/preview.js +2 -1
- package/dist/ui/formatters/requestStatus.d.ts +1 -17
- package/dist/ui/formatters/requestStatus.js +2 -30
- package/dist/ui/formatters/sessions.d.ts +12 -0
- package/dist/ui/formatters/sessions.js +40 -0
- package/dist/ui/formatters/status.d.ts +21 -2
- package/dist/ui/formatters/status.js +47 -10
- package/dist/ui/formatters/triggeredRequests.d.ts +36 -0
- package/dist/ui/formatters/triggeredRequests.js +65 -0
- package/dist/ui/formatting.d.ts +10 -0
- package/dist/ui/formatting.js +28 -36
- package/dist/ui/messages/chrome.d.ts +9 -0
- package/dist/ui/messages/chrome.js +17 -5
- package/dist/ui/messages/commands.d.ts +388 -14
- package/dist/ui/messages/commands.js +664 -21
- package/dist/ui/messages/consoleMessages.d.ts +10 -0
- package/dist/ui/messages/consoleMessages.js +17 -0
- package/dist/ui/messages/hints.js +2 -1
- package/dist/ui/messages/preview.js +5 -4
- package/dist/ui/messages/session.d.ts +16 -21
- package/dist/ui/messages/session.js +28 -26
- package/dist/ui/messages/sessionCommand.d.ts +43 -0
- package/dist/ui/messages/sessionCommand.js +52 -0
- package/dist/utils/async.d.ts +8 -0
- package/dist/utils/async.js +19 -0
- package/dist/utils/http.d.ts +22 -1
- package/dist/utils/http.js +28 -9
- package/dist/utils/selectorFilters.d.ts +36 -8
- package/dist/utils/selectorFilters.js +267 -53
- package/dist/utils/shellDetection.d.ts +8 -2
- package/dist/utils/shellDetection.js +120 -33
- package/dist/utils/suggestions.d.ts +26 -0
- package/dist/utils/suggestions.js +73 -0
- package/dist/utils/taskMappings.js +10 -0
- package/dist/utils/url.d.ts +12 -2
- package/dist/utils/url.js +69 -7
- package/package.json +1 -1
- package/dist/ui/formatters/sessionFormatters.d.ts +0 -58
- package/dist/ui/formatters/sessionFormatters.js +0 -121
|
@@ -7,17 +7,18 @@
|
|
|
7
7
|
* subscriptions for network-stability waits) on behalf of the CLI.
|
|
8
8
|
*/
|
|
9
9
|
import { InvalidArgumentError } from 'commander';
|
|
10
|
-
import { DomElementResolver } from './DomElementResolver.js';
|
|
11
10
|
import { runElementCommand } from './helpers/runElementCommand.js';
|
|
12
11
|
import { runCommand } from '../shared/CommandRunner.js';
|
|
13
12
|
import { jsonOption } from '../shared/commonOptions.js';
|
|
14
13
|
import { integerOption } from '../shared/validation.js';
|
|
15
14
|
import { CommandError } from '../../errors/index.js';
|
|
16
|
-
import { conflictingOptionsMessage, internalError, scrollOptionsError } from '../../errors/messages.js';
|
|
15
|
+
import { VIA_LABEL_SUFFIX, conflictingOptionsMessage, indexSourceText, internalError, scrollOptionsError, } from '../../errors/messages.js';
|
|
17
16
|
import { domClick, domFill, domPressKey, domScroll, domSubmit } from '../../ipc/client.js';
|
|
18
17
|
import { findUnknownModifiers } from '../../runtime/dom/keyMapping.js';
|
|
18
|
+
import { formatTriggeredRequestLines, formatTriggeredRequestsTitle, } from '../../ui/formatters/triggeredRequests.js';
|
|
19
19
|
import { OutputFormatter } from '../../ui/formatting.js';
|
|
20
|
-
import { POINTER_ACTION_DONE, dialogConsoleText } from '../../ui/messages/commands.js';
|
|
20
|
+
import { CLICK_RESULT_WAIT_HELP, POINTER_ACTION_DONE, actionStatusLine, dialogConsoleText, newMessageText, pageNavigationText, } from '../../ui/messages/commands.js';
|
|
21
|
+
import { sessionCommand } from '../../ui/messages/sessionCommand.js';
|
|
21
22
|
import { EXIT_CODES } from '../../utils/exitCodes.js';
|
|
22
23
|
/**
|
|
23
24
|
* Commander parser for `--modifiers`: rejects unknown names instead of
|
|
@@ -71,6 +72,7 @@ export function registerFormInteractionCommands(program) {
|
|
|
71
72
|
wait: options.wait !== false,
|
|
72
73
|
}),
|
|
73
74
|
call: domFill,
|
|
75
|
+
command: 'fill',
|
|
74
76
|
action: 'fill element',
|
|
75
77
|
failureSuggestion: 'Verify the selector matches a fillable element (input, textarea, select)',
|
|
76
78
|
}), options, formatFillOutput);
|
|
@@ -84,6 +86,7 @@ export function registerFormInteractionCommands(program) {
|
|
|
84
86
|
.option('--right', 'Right-click (opens the context menu)')
|
|
85
87
|
.option('--no-wait', 'Skip waiting for network stability after click')
|
|
86
88
|
.addOption(jsonOption())
|
|
89
|
+
.addHelpText('after', CLICK_RESULT_WAIT_HELP)
|
|
87
90
|
.action(async (selectorOrIndex, options) => {
|
|
88
91
|
const action = options.double ? 'double' : options.right ? 'right' : 'click';
|
|
89
92
|
await runPointerCommand(selectorOrIndex, options, action);
|
|
@@ -107,6 +110,7 @@ export function registerFormInteractionCommands(program) {
|
|
|
107
110
|
.option('--wait-network <ms>', 'Wait for network idle after submit (milliseconds)', integerOption(0), 1000)
|
|
108
111
|
.option('--timeout <ms>', 'Maximum time to wait (milliseconds)', integerOption(1), 10000)
|
|
109
112
|
.addOption(jsonOption())
|
|
113
|
+
.addHelpText('after', CLICK_RESULT_WAIT_HELP)
|
|
110
114
|
.action(async (selectorOrIndex, options) => {
|
|
111
115
|
await runCommand(() => runElementCommand({
|
|
112
116
|
selectorOrIndex,
|
|
@@ -120,6 +124,7 @@ export function registerFormInteractionCommands(program) {
|
|
|
120
124
|
timeout: options.timeout,
|
|
121
125
|
}),
|
|
122
126
|
call: domSubmit,
|
|
127
|
+
command: 'submit',
|
|
123
128
|
action: 'submit form',
|
|
124
129
|
failureSuggestion: 'Verify the selector matches a form or submit button',
|
|
125
130
|
}), options, formatSubmitOutput);
|
|
@@ -146,6 +151,7 @@ export function registerFormInteractionCommands(program) {
|
|
|
146
151
|
wait: options.wait !== false,
|
|
147
152
|
}),
|
|
148
153
|
call: domPressKey,
|
|
154
|
+
command: 'pressKey',
|
|
149
155
|
action: 'press key',
|
|
150
156
|
failureSuggestion: 'Verify the selector matches a focusable element',
|
|
151
157
|
}), options, formatPressKeyOutput);
|
|
@@ -164,64 +170,62 @@ export function registerFormInteractionCommands(program) {
|
|
|
164
170
|
.option('--no-wait', 'Skip waiting for lazy-loaded content after scroll')
|
|
165
171
|
.addOption(jsonOption())
|
|
166
172
|
.action(async (selector, options) => {
|
|
167
|
-
await runCommand(
|
|
168
|
-
const problem = scrollOptionsProblem(selector, options);
|
|
169
|
-
if (problem) {
|
|
170
|
-
const err = scrollOptionsError(problem);
|
|
171
|
-
return {
|
|
172
|
-
success: false,
|
|
173
|
-
error: err.message,
|
|
174
|
-
exitCode: EXIT_CODES.INVALID_ARGUMENTS,
|
|
175
|
-
errorContext: { suggestion: err.suggestion },
|
|
176
|
-
};
|
|
177
|
-
}
|
|
178
|
-
const target = selector
|
|
179
|
-
? await DomElementResolver.getInstance().resolve(selector, options.index)
|
|
180
|
-
: undefined;
|
|
181
|
-
if (target && !target.success) {
|
|
182
|
-
return {
|
|
183
|
-
success: false,
|
|
184
|
-
error: target.error,
|
|
185
|
-
exitCode: target.exitCode,
|
|
186
|
-
...(target.suggestion && { errorContext: { suggestion: target.suggestion } }),
|
|
187
|
-
};
|
|
188
|
-
}
|
|
189
|
-
const response = await domScroll({
|
|
190
|
-
...(target && { selector: target.selector }),
|
|
191
|
-
...(target?.index !== undefined && { index: target.index }),
|
|
192
|
-
...(target?.backendNodeId !== undefined && { backendNodeId: target.backendNodeId }),
|
|
193
|
-
...(options.down !== undefined && { down: options.down }),
|
|
194
|
-
...(options.up !== undefined && { up: options.up }),
|
|
195
|
-
...(options.left !== undefined && { left: options.left }),
|
|
196
|
-
...(options.right !== undefined && { right: options.right }),
|
|
197
|
-
...(options.top !== undefined && { top: options.top }),
|
|
198
|
-
...(options.bottom !== undefined && { bottom: options.bottom }),
|
|
199
|
-
wait: options.wait !== false,
|
|
200
|
-
});
|
|
201
|
-
if (response.status === 'error' || !response.data) {
|
|
202
|
-
return {
|
|
203
|
-
success: false,
|
|
204
|
-
error: response.error ?? 'Failed to scroll',
|
|
205
|
-
exitCode: response.exitCode ?? EXIT_CODES.INVALID_ARGUMENTS,
|
|
206
|
-
...(response.suggestion && { errorContext: { suggestion: response.suggestion } }),
|
|
207
|
-
};
|
|
208
|
-
}
|
|
209
|
-
const result = response.data;
|
|
210
|
-
if (!result.success) {
|
|
211
|
-
return {
|
|
212
|
-
success: false,
|
|
213
|
-
error: result.error ?? 'Failed to scroll',
|
|
214
|
-
exitCode: result.exitCode ?? EXIT_CODES.INVALID_ARGUMENTS,
|
|
215
|
-
errorContext: {
|
|
216
|
-
suggestion: result.suggestion ?? 'Verify the selector exists on the page',
|
|
217
|
-
},
|
|
218
|
-
};
|
|
219
|
-
}
|
|
220
|
-
const { success: _success, ...data } = result;
|
|
221
|
-
return { success: true, data };
|
|
222
|
-
}, options, formatScrollOutput);
|
|
173
|
+
await runCommand(() => runScroll(selector, options), options, formatScrollOutput);
|
|
223
174
|
});
|
|
224
175
|
}
|
|
176
|
+
/**
|
|
177
|
+
* Scroll to an element (selector or cached index) or by offsets/to an edge.
|
|
178
|
+
*
|
|
179
|
+
* @param selector - Selector or index argument, if any
|
|
180
|
+
* @param options - Scroll options
|
|
181
|
+
* @returns Command result
|
|
182
|
+
*/
|
|
183
|
+
async function runScroll(selector, options) {
|
|
184
|
+
const problem = scrollOptionsProblem(selector, options);
|
|
185
|
+
if (problem) {
|
|
186
|
+
const err = scrollOptionsError(problem);
|
|
187
|
+
return {
|
|
188
|
+
success: false,
|
|
189
|
+
error: err.message,
|
|
190
|
+
exitCode: EXIT_CODES.INVALID_ARGUMENTS,
|
|
191
|
+
errorContext: { suggestion: err.suggestion },
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
const request = {
|
|
195
|
+
...(options.down !== undefined && { down: options.down }),
|
|
196
|
+
...(options.up !== undefined && { up: options.up }),
|
|
197
|
+
...(options.left !== undefined && { left: options.left }),
|
|
198
|
+
...(options.right !== undefined && { right: options.right }),
|
|
199
|
+
...(options.top !== undefined && { top: options.top }),
|
|
200
|
+
...(options.bottom !== undefined && { bottom: options.bottom }),
|
|
201
|
+
wait: options.wait !== false,
|
|
202
|
+
};
|
|
203
|
+
if (selector) {
|
|
204
|
+
return runElementCommand({
|
|
205
|
+
selectorOrIndex: selector,
|
|
206
|
+
index: options.index,
|
|
207
|
+
buildRequest: (target) => ({ ...target, ...request }),
|
|
208
|
+
call: domScroll,
|
|
209
|
+
command: 'scroll',
|
|
210
|
+
action: 'scroll',
|
|
211
|
+
failureSuggestion: 'Verify the selector exists on the page',
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
const response = await domScroll(request);
|
|
215
|
+
const result = response.data;
|
|
216
|
+
if (response.status === 'error' || !result?.success) {
|
|
217
|
+
return {
|
|
218
|
+
success: false,
|
|
219
|
+
error: result?.error ?? response.error ?? 'Failed to scroll',
|
|
220
|
+
exitCode: result?.exitCode ?? response.exitCode ?? EXIT_CODES.INVALID_ARGUMENTS,
|
|
221
|
+
...((result?.suggestion ?? response.suggestion) && {
|
|
222
|
+
errorContext: { suggestion: result?.suggestion ?? response.suggestion ?? '' },
|
|
223
|
+
}),
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
const { success: _success, ...data } = result;
|
|
227
|
+
return { success: true, data };
|
|
228
|
+
}
|
|
225
229
|
/**
|
|
226
230
|
* The first rule `bdg dom scroll` options break, if any: one target (an
|
|
227
231
|
* element, or offsets/edges), and no opposite directions.
|
|
@@ -268,87 +272,130 @@ async function runPointerCommand(selectorOrIndex, options, action) {
|
|
|
268
272
|
...(action !== 'click' && { action }),
|
|
269
273
|
}),
|
|
270
274
|
call: domClick,
|
|
275
|
+
command: action === 'hover' ? 'hover' : 'click',
|
|
271
276
|
action: action === 'hover' ? 'hover element' : 'click element',
|
|
272
277
|
failureSuggestion: 'Verify the selector matches a clickable element',
|
|
273
278
|
}), options, formatClickOutput);
|
|
274
279
|
}
|
|
275
280
|
/**
|
|
276
|
-
*
|
|
281
|
+
* Build an action's output: the status line ("✓ Element Clicked",
|
|
282
|
+
* "⚠ Element Clicked (with warnings)" with the warning right below it, or
|
|
283
|
+
* "⚠ Element Clicked (no visible effect: …)"), the details, what changed on
|
|
284
|
+
* the page (`Page:` navigation, `New text:` messages), then the network
|
|
285
|
+
* requests it triggered and the dialogs it caused. No request list is shown
|
|
286
|
+
* when there were none (JSON has an empty `triggeredRequests` then).
|
|
277
287
|
*
|
|
278
|
-
* @param
|
|
288
|
+
* @param done - What was done, e.g. "Element Clicked"
|
|
289
|
+
* @param details - Label/value rows
|
|
279
290
|
* @param result - Action result
|
|
291
|
+
* @param keyWidth - Width of the labels
|
|
292
|
+
* @returns Output being built (more can be appended)
|
|
280
293
|
*/
|
|
281
|
-
function
|
|
282
|
-
|
|
283
|
-
|
|
294
|
+
function formatActionOutput(done, details, result, keyWidth = 15) {
|
|
295
|
+
const fmt = new OutputFormatter();
|
|
296
|
+
fmt.text(actionStatusLine(done, result.warning !== undefined, result.effect === 'none'));
|
|
297
|
+
if (result.warning)
|
|
284
298
|
fmt.text(`⚠ Warning: ${result.warning}`);
|
|
299
|
+
fmt.blank();
|
|
300
|
+
fmt.keyValueList(details, keyWidth);
|
|
301
|
+
if (result.navigation)
|
|
302
|
+
fmt.keyValue('Page', pageNavigationText(result.navigation), keyWidth);
|
|
303
|
+
(result.messages ?? []).forEach((message, index) => {
|
|
304
|
+
const text = newMessageText(message);
|
|
305
|
+
if (index === 0)
|
|
306
|
+
fmt.keyValue('New text', text, keyWidth);
|
|
307
|
+
else
|
|
308
|
+
fmt.text(' '.repeat(keyWidth) + text);
|
|
309
|
+
});
|
|
310
|
+
const omitted = result.triggeredRequestsOmitted;
|
|
311
|
+
const requests = formatTriggeredRequestLines(result.triggeredRequests ?? [], omitted);
|
|
312
|
+
if (requests.length > 0) {
|
|
313
|
+
fmt
|
|
314
|
+
.blank()
|
|
315
|
+
.section(formatTriggeredRequestsTitle(result.triggeredRequests ?? [], omitted), requests);
|
|
285
316
|
}
|
|
286
317
|
for (const dialog of result.dialogs ?? []) {
|
|
287
318
|
fmt.blank();
|
|
288
319
|
fmt.text(`Dialog: ${dialogConsoleText(dialog)}`);
|
|
289
320
|
}
|
|
321
|
+
return fmt;
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Row naming the element an action hit, e.g.
|
|
325
|
+
* `Element: input.toggle in div.view "Write report"` (just its tag when the
|
|
326
|
+
* page could not describe it), with the list a numeric index refers to
|
|
327
|
+
* (`(index 0 of the last dom query "h3")`).
|
|
328
|
+
*
|
|
329
|
+
* @param result - Action result
|
|
330
|
+
* @returns Label/value row
|
|
331
|
+
*/
|
|
332
|
+
function elementRow(result) {
|
|
333
|
+
const source = result.indexSource ? ` (${indexSourceText(result.indexSource)})` : '';
|
|
334
|
+
if (result.element === undefined) {
|
|
335
|
+
return ['Element Type', `${result.elementType ?? 'unknown'}${source}`];
|
|
336
|
+
}
|
|
337
|
+
const viaLabel = result.elementType?.endsWith(VIA_LABEL_SUFFIX) ? VIA_LABEL_SUFFIX : '';
|
|
338
|
+
return ['Element', `${result.element}${viaLabel}${source}`];
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Selector row of an action's output; none for an index into a11y query
|
|
342
|
+
* results, whose "selector" is the a11y pattern (the element row names it).
|
|
343
|
+
*
|
|
344
|
+
* @param result - Action result
|
|
345
|
+
* @returns The row, or none
|
|
346
|
+
*/
|
|
347
|
+
function selectorRows(result) {
|
|
348
|
+
if (result.indexSource?.command === 'dom a11y query')
|
|
349
|
+
return [];
|
|
350
|
+
return [['Selector', result.selector ?? 'unknown']];
|
|
290
351
|
}
|
|
291
352
|
/**
|
|
292
353
|
* Format fill command output for human-readable display.
|
|
293
354
|
*/
|
|
294
355
|
function formatFillOutput(result) {
|
|
295
|
-
const
|
|
296
|
-
fmt.text('✓ Element Filled');
|
|
297
|
-
fmt.blank();
|
|
298
|
-
const details = [
|
|
299
|
-
['Selector', result.selector ?? 'unknown'],
|
|
300
|
-
['Element Type', result.elementType ?? 'unknown'],
|
|
301
|
-
];
|
|
356
|
+
const details = [...selectorRows(result), elementRow(result)];
|
|
302
357
|
if (result.inputType)
|
|
303
358
|
details.push(['Input Type', result.inputType]);
|
|
304
359
|
if (result.checked !== undefined) {
|
|
305
360
|
details.push(['Checked', result.checked ? 'true' : 'false']);
|
|
306
361
|
}
|
|
307
|
-
else if (result.value) {
|
|
308
|
-
details.push(['Value', result.value]);
|
|
362
|
+
else if (result.value !== undefined) {
|
|
363
|
+
details.push(['Value', result.value === '' ? '(empty)' : result.value]);
|
|
309
364
|
}
|
|
310
|
-
|
|
311
|
-
appendNotices(fmt, result);
|
|
312
|
-
return fmt.build();
|
|
365
|
+
return formatActionOutput('Element Filled', details, result).build();
|
|
313
366
|
}
|
|
314
367
|
/**
|
|
315
368
|
* Format click command output for human-readable display.
|
|
316
369
|
*/
|
|
317
370
|
function formatClickOutput(result) {
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
fmt.keyValueList([
|
|
322
|
-
['Selector', result.selector ?? 'unknown'],
|
|
323
|
-
['Element Type', result.elementType ?? 'unknown'],
|
|
371
|
+
return formatActionOutput(`Element ${POINTER_ACTION_DONE[result.action ?? 'click']}`, [
|
|
372
|
+
...selectorRows(result),
|
|
373
|
+
elementRow(result),
|
|
324
374
|
['Method', result.method === 'dom' ? 'DOM events' : 'mouse events'],
|
|
325
|
-
],
|
|
326
|
-
appendNotices(fmt, result);
|
|
327
|
-
return fmt.build();
|
|
375
|
+
], result).build();
|
|
328
376
|
}
|
|
329
377
|
/**
|
|
330
378
|
* Format submit command output for human-readable display.
|
|
331
379
|
*/
|
|
332
380
|
function formatSubmitOutput(result) {
|
|
333
|
-
const fmt = new OutputFormatter();
|
|
334
|
-
fmt.text('✓ Form Submitted');
|
|
335
|
-
fmt.blank();
|
|
336
381
|
const details = [
|
|
337
|
-
|
|
382
|
+
...selectorRows(result),
|
|
383
|
+
...(result.element !== undefined ? [elementRow(result)] : []),
|
|
338
384
|
['Submit Button', result.clicked ? 'used' : 'none'],
|
|
339
385
|
];
|
|
340
386
|
if (result.networkRequests !== undefined)
|
|
341
387
|
details.push(['Network Requests', result.networkRequests.toString()]);
|
|
342
|
-
if (result.navigationOccurred
|
|
343
|
-
details.push(['Navigation',
|
|
388
|
+
if (result.navigationOccurred === false)
|
|
389
|
+
details.push(['Navigation', 'no']);
|
|
390
|
+
if (result.navigationOccurred && !result.navigation)
|
|
391
|
+
details.push(['Navigation', 'yes']);
|
|
344
392
|
if (result.waitTimeMs !== undefined)
|
|
345
393
|
details.push(['Wait Time', `${result.waitTimeMs}ms`]);
|
|
346
|
-
fmt
|
|
347
|
-
appendNotices(fmt, result);
|
|
394
|
+
const fmt = formatActionOutput('Form Submitted', details, result, 20);
|
|
348
395
|
fmt.hints('Next steps:', [
|
|
349
|
-
'bdg network list --last 10
|
|
350
|
-
'bdg console --last 5
|
|
351
|
-
'bdg status
|
|
396
|
+
`${sessionCommand('bdg network list --last 10').padEnd(32)} Check network requests`,
|
|
397
|
+
`${sessionCommand('bdg console --last 5').padEnd(32)} Check console messages`,
|
|
398
|
+
`${sessionCommand('bdg status').padEnd(32)} Check session state`,
|
|
352
399
|
]);
|
|
353
400
|
return fmt.build();
|
|
354
401
|
}
|
|
@@ -356,42 +403,34 @@ function formatSubmitOutput(result) {
|
|
|
356
403
|
* Format pressKey command output for human-readable display.
|
|
357
404
|
*/
|
|
358
405
|
function formatPressKeyOutput(result) {
|
|
359
|
-
const fmt = new OutputFormatter();
|
|
360
|
-
fmt.text('✓ Key Pressed');
|
|
361
|
-
fmt.blank();
|
|
362
406
|
const details = [
|
|
363
407
|
['Key', result.key ?? 'unknown'],
|
|
364
|
-
|
|
365
|
-
|
|
408
|
+
...selectorRows(result),
|
|
409
|
+
elementRow(result),
|
|
366
410
|
];
|
|
367
411
|
if (result.times && result.times > 1)
|
|
368
412
|
details.push(['Times', result.times.toString()]);
|
|
369
413
|
if (result.modifiers?.length)
|
|
370
414
|
details.push(['Modifiers', result.modifiers.join('+')]);
|
|
371
|
-
|
|
372
|
-
appendNotices(fmt, result);
|
|
373
|
-
return fmt.build();
|
|
415
|
+
return formatActionOutput('Key Pressed', details, result).build();
|
|
374
416
|
}
|
|
375
417
|
/**
|
|
376
418
|
* Format scroll command output for human-readable display.
|
|
377
419
|
*/
|
|
378
420
|
function formatScrollOutput(result) {
|
|
379
|
-
const fmt = new OutputFormatter();
|
|
380
|
-
fmt.text('✓ Page Scrolled');
|
|
381
|
-
fmt.blank();
|
|
382
421
|
const details = [['Scroll Type', result.scrollType]];
|
|
383
422
|
if (result.selector)
|
|
384
|
-
details.push(
|
|
423
|
+
details.push(...selectorRows(result));
|
|
424
|
+
if (result.element)
|
|
425
|
+
details.push(elementRow(result));
|
|
385
426
|
if (result.scrolledTo)
|
|
386
427
|
details.push(['Position', `(${result.scrolledTo.x}, ${result.scrolledTo.y})`]);
|
|
387
428
|
if (result.scrolledBy && (result.scrolledBy.x !== 0 || result.scrolledBy.y !== 0))
|
|
388
429
|
details.push(['Scrolled By', `(${result.scrolledBy.x}, ${result.scrolledBy.y})`]);
|
|
389
430
|
if (result.viewportSize)
|
|
390
|
-
details.push(['Viewport', `${result.viewportSize.width}
|
|
431
|
+
details.push(['Viewport', `${result.viewportSize.width}×${result.viewportSize.height}`]);
|
|
391
432
|
if (result.pageSize)
|
|
392
|
-
details.push(['Page Size', `${result.pageSize.width}
|
|
393
|
-
|
|
394
|
-
appendNotices(fmt, result);
|
|
395
|
-
return fmt.build();
|
|
433
|
+
details.push(['Page Size', `${result.pageSize.width}×${result.pageSize.height}`]);
|
|
434
|
+
return formatActionOutput('Page Scrolled', details, result).build();
|
|
396
435
|
}
|
|
397
436
|
//# sourceMappingURL=formInteraction.js.map
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Readiness of a discovered form (`bdg dom form`): which fields are filled,
|
|
3
|
+
* which required ones are still empty, and which button submits it.
|
|
4
|
+
*/
|
|
5
|
+
import type { FormButton, FormField, FormSummary, RawButton, RawForm } from '../../runtime/dom/formTypes.js';
|
|
6
|
+
/**
|
|
7
|
+
* Fields a user fills as one: the options of a radio or checkbox group (same
|
|
8
|
+
* name in the same form), or a single field.
|
|
9
|
+
*
|
|
10
|
+
* @param fields - Form fields
|
|
11
|
+
* @returns Groups, in the order their first field appears
|
|
12
|
+
*/
|
|
13
|
+
export declare function groupFields(fields: FormField[]): FormField[][];
|
|
14
|
+
/**
|
|
15
|
+
* Summarize a form's state. Only visible, editable fields count, and a radio
|
|
16
|
+
* or checkbox group counts once (filled when any option is checked; required
|
|
17
|
+
* when any option is). The form is ready when nothing blocks it and at least
|
|
18
|
+
* one field is filled (an untouched form is not ready), or it has no fields.
|
|
19
|
+
*
|
|
20
|
+
* @param fields - Form fields
|
|
21
|
+
* @param buttons - Form buttons
|
|
22
|
+
* @returns Form summary
|
|
23
|
+
*/
|
|
24
|
+
export declare function calculateSummary(fields: FormField[], buttons: FormButton[]): FormSummary;
|
|
25
|
+
/**
|
|
26
|
+
* The button that submits the form, as a user would pick it. Buttons that
|
|
27
|
+
* cancel, reset, go back or delete ({@link DISMISSIVE_BUTTON_LABEL}) never
|
|
28
|
+
* are. Then, in order: a button written as a submit button
|
|
29
|
+
* (`type="submit"`, `<input type=submit>`); among the form's untyped
|
|
30
|
+
* `<button>`s (each submits it) one styled as primary, else the last one
|
|
31
|
+
* (forms put Cancel or Back first); else any button styled as primary.
|
|
32
|
+
* Buttons outside a form submit nothing, so only their style counts.
|
|
33
|
+
*
|
|
34
|
+
* @param buttons - Raw buttons of one form
|
|
35
|
+
* @returns Index of the primary button, or undefined when there is none
|
|
36
|
+
*/
|
|
37
|
+
export declare function primaryButtonIndex(buttons: RawButton[]): number | undefined;
|
|
38
|
+
/**
|
|
39
|
+
* Put forms in listing order (by {@link visibilityRank}, then by relevance)
|
|
40
|
+
* and number them, their fields and their buttons in that order, so the
|
|
41
|
+
* first field listed is 0 and `bdg dom fill <index>` / `click <index>` reach
|
|
42
|
+
* the element listed with that index (the indices are cached in this order).
|
|
43
|
+
* Within a form, fields come before buttons, both in document order.
|
|
44
|
+
*
|
|
45
|
+
* @param forms - Raw forms in document order
|
|
46
|
+
* @returns Forms in listing order, renumbered (the input is not changed)
|
|
47
|
+
*/
|
|
48
|
+
export declare function orderForms(forms: RawForm[]): RawForm[];
|
|
49
|
+
//# sourceMappingURL=formSummary.d.ts.map
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Readiness of a discovered form (`bdg dom form`): which fields are filled,
|
|
3
|
+
* which required ones are still empty, and which button submits it.
|
|
4
|
+
*/
|
|
5
|
+
import { REQUIRED_FIELD_EMPTY_REASON } from '../../ui/messages/commands.js';
|
|
6
|
+
/**
|
|
7
|
+
* Button labels that leave or clear a form instead of submitting it (English,
|
|
8
|
+
* German, French, Spanish, Italian and Polish cancel/back words)
|
|
9
|
+
*/
|
|
10
|
+
const DISMISSIVE_BUTTON_LABEL = /^(cancel|reset|back|go back|previous|prev|close|clear|discard|delete|remove|abbrechen|zurück|annuler|retour|cancelar|volver|annulla|indietro|anuluj|wstecz)\b/iu;
|
|
11
|
+
/**
|
|
12
|
+
* Fields a user fills as one: the options of a radio or checkbox group (same
|
|
13
|
+
* name in the same form), or a single field.
|
|
14
|
+
*
|
|
15
|
+
* @param fields - Form fields
|
|
16
|
+
* @returns Groups, in the order their first field appears
|
|
17
|
+
*/
|
|
18
|
+
export function groupFields(fields) {
|
|
19
|
+
const groups = new Map();
|
|
20
|
+
for (const field of fields) {
|
|
21
|
+
const type = (field.inputType ?? field.type).toLowerCase();
|
|
22
|
+
const choice = (type === 'radio' || type === 'checkbox') && field.name;
|
|
23
|
+
const key = choice ? `${field.formIndex}:${type}:${field.name}` : `#${field.index}`;
|
|
24
|
+
groups.set(key, [...(groups.get(key) ?? []), field]);
|
|
25
|
+
}
|
|
26
|
+
return [...groups.values()];
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Whether a field has a value (a checked option for radios and checkboxes).
|
|
30
|
+
*
|
|
31
|
+
* @param field - Form field
|
|
32
|
+
* @returns True when filled or checked
|
|
33
|
+
*/
|
|
34
|
+
function isFilled(field) {
|
|
35
|
+
return field.state === 'filled' || field.state === 'checked';
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Label of a field group: the group's name (legend, radiogroup label, name)
|
|
39
|
+
* for several options, the field's own label otherwise.
|
|
40
|
+
*
|
|
41
|
+
* @param group - Fields of one group (at least one)
|
|
42
|
+
* @returns Label
|
|
43
|
+
*/
|
|
44
|
+
function groupLabel(group) {
|
|
45
|
+
const [first] = group;
|
|
46
|
+
return group.length > 1 ? (first.groupLabel ?? first.label) : first.label;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* What keeps a form from being submitted: required groups left empty,
|
|
50
|
+
* invalid fields, a disabled submit button.
|
|
51
|
+
*
|
|
52
|
+
* @param groups - Field groups of the editable, visible fields
|
|
53
|
+
* @param buttons - Form buttons
|
|
54
|
+
* @returns Blockers, required fields first
|
|
55
|
+
*/
|
|
56
|
+
function findBlockers(groups, buttons) {
|
|
57
|
+
const blockers = [];
|
|
58
|
+
for (const group of groups) {
|
|
59
|
+
const first = group[0];
|
|
60
|
+
if (group.some((field) => field.required) && !group.some(isFilled)) {
|
|
61
|
+
blockers.push({
|
|
62
|
+
index: first.index,
|
|
63
|
+
label: groupLabel(group),
|
|
64
|
+
reason: REQUIRED_FIELD_EMPTY_REASON,
|
|
65
|
+
command: first.command,
|
|
66
|
+
});
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
const invalid = group.find((field) => !field.validation.valid);
|
|
70
|
+
if (invalid) {
|
|
71
|
+
blockers.push({
|
|
72
|
+
index: invalid.index,
|
|
73
|
+
label: invalid.label,
|
|
74
|
+
reason: invalid.validation.message ?? 'Validation failed',
|
|
75
|
+
command: invalid.command,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
const submitButton = buttons.find((b) => b.type === 'submit' && b.primary);
|
|
80
|
+
if (submitButton && !submitButton.enabled) {
|
|
81
|
+
blockers.push({
|
|
82
|
+
index: submitButton.index,
|
|
83
|
+
label: submitButton.label,
|
|
84
|
+
reason: submitButton.disabledReason ?? 'Submit button is disabled',
|
|
85
|
+
command: submitButton.command,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
return blockers;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Summarize a form's state. Only visible, editable fields count, and a radio
|
|
92
|
+
* or checkbox group counts once (filled when any option is checked; required
|
|
93
|
+
* when any option is). The form is ready when nothing blocks it and at least
|
|
94
|
+
* one field is filled (an untouched form is not ready), or it has no fields.
|
|
95
|
+
*
|
|
96
|
+
* @param fields - Form fields
|
|
97
|
+
* @param buttons - Form buttons
|
|
98
|
+
* @returns Form summary
|
|
99
|
+
*/
|
|
100
|
+
export function calculateSummary(fields, buttons) {
|
|
101
|
+
const editable = fields.filter((f) => !f.hidden && !f.disabled && !f.readOnly);
|
|
102
|
+
const groups = groupFields(editable);
|
|
103
|
+
const filled = groups.filter((group) => group.some(isFilled));
|
|
104
|
+
const empty = groups.filter((group) => !group.some(isFilled));
|
|
105
|
+
const required = groups.filter((group) => group.some((field) => field.required));
|
|
106
|
+
const requiredRemaining = required.filter((group) => !group.some(isFilled)).length;
|
|
107
|
+
const invalid = groups.filter((group) => group.some((field) => !field.validation.valid)).length;
|
|
108
|
+
const blockers = findBlockers(groups, buttons);
|
|
109
|
+
return {
|
|
110
|
+
totalFields: groups.length,
|
|
111
|
+
filledFields: filled.length,
|
|
112
|
+
emptyFields: empty.length,
|
|
113
|
+
validFields: groups.length - invalid,
|
|
114
|
+
invalidFields: invalid,
|
|
115
|
+
requiredTotal: required.length,
|
|
116
|
+
requiredFilled: required.length - requiredRemaining,
|
|
117
|
+
requiredRemaining,
|
|
118
|
+
emptyFieldLabels: empty.map(groupLabel),
|
|
119
|
+
readyToSubmit: blockers.length === 0 && (filled.length > 0 || groups.length === 0),
|
|
120
|
+
blockers,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* The button that submits the form, as a user would pick it. Buttons that
|
|
125
|
+
* cancel, reset, go back or delete ({@link DISMISSIVE_BUTTON_LABEL}) never
|
|
126
|
+
* are. Then, in order: a button written as a submit button
|
|
127
|
+
* (`type="submit"`, `<input type=submit>`); among the form's untyped
|
|
128
|
+
* `<button>`s (each submits it) one styled as primary, else the last one
|
|
129
|
+
* (forms put Cancel or Back first); else any button styled as primary.
|
|
130
|
+
* Buttons outside a form submit nothing, so only their style counts.
|
|
131
|
+
*
|
|
132
|
+
* @param buttons - Raw buttons of one form
|
|
133
|
+
* @returns Index of the primary button, or undefined when there is none
|
|
134
|
+
*/
|
|
135
|
+
export function primaryButtonIndex(buttons) {
|
|
136
|
+
const candidates = buttons.filter((button) => button.type !== 'reset' && !DISMISSIVE_BUTTON_LABEL.test(button.label.trim()));
|
|
137
|
+
const formDefaults = candidates.filter((button) => button.formDefault);
|
|
138
|
+
const primary = candidates.find((button) => button.explicitSubmit) ??
|
|
139
|
+
formDefaults.find((button) => button.primaryClass) ??
|
|
140
|
+
formDefaults.at(-1) ??
|
|
141
|
+
candidates.find((button) => button.primaryClass);
|
|
142
|
+
return primary?.index;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Where a form is listed: shown in an open dialog first (what the user is
|
|
146
|
+
* looking at), then other visible forms, then hidden ones.
|
|
147
|
+
*
|
|
148
|
+
* @param form - Raw form
|
|
149
|
+
* @returns Rank, lower first
|
|
150
|
+
*/
|
|
151
|
+
function visibilityRank(form) {
|
|
152
|
+
if (form.hidden)
|
|
153
|
+
return 2;
|
|
154
|
+
return form.inDialog ? 0 : 1;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Put forms in listing order (by {@link visibilityRank}, then by relevance)
|
|
158
|
+
* and number them, their fields and their buttons in that order, so the
|
|
159
|
+
* first field listed is 0 and `bdg dom fill <index>` / `click <index>` reach
|
|
160
|
+
* the element listed with that index (the indices are cached in this order).
|
|
161
|
+
* Within a form, fields come before buttons, both in document order.
|
|
162
|
+
*
|
|
163
|
+
* @param forms - Raw forms in document order
|
|
164
|
+
* @returns Forms in listing order, renumbered (the input is not changed)
|
|
165
|
+
*/
|
|
166
|
+
export function orderForms(forms) {
|
|
167
|
+
const ordered = forms
|
|
168
|
+
.map((form, position) => ({ form, position }))
|
|
169
|
+
.sort((a, b) => visibilityRank(a.form) - visibilityRank(b.form) ||
|
|
170
|
+
b.form.relevanceScore - a.form.relevanceScore ||
|
|
171
|
+
a.position - b.position);
|
|
172
|
+
let next = 0;
|
|
173
|
+
return ordered.map(({ form }, formIndex) => ({
|
|
174
|
+
...form,
|
|
175
|
+
index: formIndex,
|
|
176
|
+
fields: form.fields.map((field) => ({ ...field, index: next++, formIndex })),
|
|
177
|
+
buttons: form.buttons.map((button) => ({ ...button, index: next++ })),
|
|
178
|
+
}));
|
|
179
|
+
}
|
|
180
|
+
//# sourceMappingURL=formSummary.js.map
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import type { DomFramesCommandOptions } from '../shared/optionTypes.js';
|
|
8
8
|
/**
|
|
9
|
-
* Handle `bdg dom frames`.
|
|
9
|
+
* Handle `bdg dom frames`. The list of a page still loading says it may be
|
|
10
|
+
* incomplete (`readyState` in the data): its iframes may not exist yet.
|
|
10
11
|
*/
|
|
11
12
|
export declare function handleDomFrames(options: DomFramesCommandOptions): Promise<void>;
|
|
12
13
|
//# sourceMappingURL=frames.d.ts.map
|
|
@@ -4,12 +4,14 @@
|
|
|
4
4
|
* CLI-side handler. Frames are discovered in the daemon via the `dom_frames`
|
|
5
5
|
* IPC command, out-of-process (cross-site) ones included.
|
|
6
6
|
*/
|
|
7
|
+
import { documentReadyState } from './helpers/query.js';
|
|
7
8
|
import { runCommand } from '../shared/CommandRunner.js';
|
|
8
9
|
import { domFrames } from '../../ipc/client.js';
|
|
9
10
|
import { formatDomFrames } from '../../ui/formatters/dom.js';
|
|
10
11
|
import { EXIT_CODES } from '../../utils/exitCodes.js';
|
|
11
12
|
/**
|
|
12
|
-
* Handle `bdg dom frames`.
|
|
13
|
+
* Handle `bdg dom frames`. The list of a page still loading says it may be
|
|
14
|
+
* incomplete (`readyState` in the data): its iframes may not exist yet.
|
|
13
15
|
*/
|
|
14
16
|
export async function handleDomFrames(options) {
|
|
15
17
|
await runCommand(async () => {
|
|
@@ -22,7 +24,20 @@ export async function handleDomFrames(options) {
|
|
|
22
24
|
...(response.suggestion && { errorContext: { suggestion: response.suggestion } }),
|
|
23
25
|
};
|
|
24
26
|
}
|
|
25
|
-
return { success: true, data:
|
|
27
|
+
return { success: true, data: await withReadyState(response.data.frames) };
|
|
26
28
|
}, options, formatDomFrames);
|
|
27
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* The frames, with the page's `readyState` while it is still loading (one
|
|
32
|
+
* page evaluation).
|
|
33
|
+
*
|
|
34
|
+
* @param frames - Frames of the page
|
|
35
|
+
* @returns Frames, and `readyState` unless the page has loaded (or did not answer)
|
|
36
|
+
*/
|
|
37
|
+
async function withReadyState(frames) {
|
|
38
|
+
const readyState = await documentReadyState();
|
|
39
|
+
return readyState === undefined || readyState === 'complete'
|
|
40
|
+
? { frames }
|
|
41
|
+
: { frames, readyState };
|
|
42
|
+
}
|
|
28
43
|
//# sourceMappingURL=frames.js.map
|