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
|
@@ -6,11 +6,11 @@ import * as fs from 'node:fs';
|
|
|
6
6
|
import * as path from 'node:path';
|
|
7
7
|
import { CommandError } from '../../../errors/index.js';
|
|
8
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';
|
|
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, 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,67 @@ 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.
|
|
270
|
+
*/
|
|
271
|
+
const PRESS_PROBE_READ_SCRIPT = `(() => {
|
|
272
|
+
const probe = window.__bdgPressProbe;
|
|
273
|
+
delete window.__bdgPressProbe;
|
|
274
|
+
if (!probe) return true;
|
|
275
|
+
probe.stop();
|
|
276
|
+
return probe.reached;
|
|
277
|
+
})()`;
|
|
278
|
+
/**
|
|
279
|
+
* Whether the mouse press just dispatched reached the target element.
|
|
280
|
+
*
|
|
281
|
+
* Any doubt (no probe, evaluation failure) counts as reached, so only a
|
|
282
|
+
* press that the page provably never saw is reported.
|
|
283
|
+
*
|
|
284
|
+
* @param cdp - CDP connection
|
|
285
|
+
* @returns False only when the target received no pointerdown/mousedown
|
|
286
|
+
*/
|
|
287
|
+
export async function pressReachedTarget(cdp) {
|
|
288
|
+
try {
|
|
289
|
+
const response = (await cdp.send('Runtime.evaluate', {
|
|
290
|
+
expression: PRESS_PROBE_READ_SCRIPT,
|
|
291
|
+
returnByValue: true,
|
|
292
|
+
}));
|
|
293
|
+
return response.result?.value !== false;
|
|
294
|
+
}
|
|
295
|
+
catch (error) {
|
|
296
|
+
log.debug(`Press probe not read: ${getErrorMessage(error)}`);
|
|
297
|
+
return true;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Dispatch real mouse events for an action, checking after the first press
|
|
302
|
+
* that the target received it. If dispatching fails first, the press probe
|
|
303
|
+
* is still removed from the page.
|
|
304
|
+
*
|
|
305
|
+
* @param cdp - CDP connection
|
|
306
|
+
* @param action - What to do
|
|
307
|
+
* @param x - Page x
|
|
308
|
+
* @param y - Page y
|
|
309
|
+
* @returns False if the press never reached the target
|
|
310
|
+
*/
|
|
311
|
+
async function dispatchMouseAction(cdp, action, x, y) {
|
|
312
|
+
let reached;
|
|
313
|
+
try {
|
|
314
|
+
for (const event of mouseEvents(action, x, y)) {
|
|
315
|
+
await cdp.send('Input.dispatchMouseEvent', event);
|
|
316
|
+
if (event['type'] === 'mousePressed' && reached === undefined) {
|
|
317
|
+
reached = await pressReachedTarget(cdp);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
catch (error) {
|
|
322
|
+
if (reached === undefined)
|
|
323
|
+
await pressReachedTarget(cdp);
|
|
324
|
+
throw error;
|
|
325
|
+
}
|
|
326
|
+
return reached ?? true;
|
|
327
|
+
}
|
|
216
328
|
/**
|
|
217
329
|
* Drop the located element from the page without waiting: right after a
|
|
218
330
|
* click on a link, evaluation waits until the new page commits (seconds for a
|
|
@@ -244,11 +356,14 @@ async function performClick(cdp, located, action) {
|
|
|
244
356
|
if (!result.success)
|
|
245
357
|
return result;
|
|
246
358
|
if (hittable && x !== undefined && y !== undefined) {
|
|
247
|
-
|
|
248
|
-
await cdp.send('Input.dispatchMouseEvent', event);
|
|
249
|
-
}
|
|
359
|
+
const reached = await dispatchMouseAction(cdp, action, x, y);
|
|
250
360
|
releaseClickTarget(cdp);
|
|
251
|
-
return withMultipleMatchesWarning({
|
|
361
|
+
return withMultipleMatchesWarning({
|
|
362
|
+
...result,
|
|
363
|
+
action,
|
|
364
|
+
method: 'mouse',
|
|
365
|
+
...(!reached && { warning: CLICK_NOT_RECEIVED_WARNING }),
|
|
366
|
+
}, result.selectedIndex, `${POINTER_ACTION_DONE[action].toLowerCase()} the first visible one`);
|
|
252
367
|
}
|
|
253
368
|
const response = (await cdp.send('Runtime.evaluate', {
|
|
254
369
|
expression: domFallbackScript(action),
|
|
@@ -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;
|
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
* page boundaries.
|
|
4
4
|
*/
|
|
5
5
|
import { CommandError } from '../../../errors/index.js';
|
|
6
|
+
import { scrollNoEffectWarning } from '../../../errors/messages.js';
|
|
7
|
+
import { SCROLL_RANGE_JS, VIEWPORT_SIZE_JS, } from '../elementGeometry.js';
|
|
8
|
+
import { ELEMENT_IDENTITY_JS } from '../elementInfo.js';
|
|
6
9
|
import { throwIfInvalidSelector, withMultipleMatchesWarning, } from './shared.js';
|
|
7
10
|
import { FIND_ELEMENTS_JS, selectorArgsJS } from '../targetNode.js';
|
|
8
11
|
import { EXIT_CODES } from '../../../utils/exitCodes.js';
|
|
@@ -29,26 +32,20 @@ const SCROLL_TO_ELEMENT_SCRIPT = `
|
|
|
29
32
|
|
|
30
33
|
el.scrollIntoView({ behavior: 'instant', block: 'center' });
|
|
31
34
|
|
|
32
|
-
const rect = el.getBoundingClientRect();
|
|
33
|
-
const scrollX = window.scrollX + rect.left + rect.width / 2 - window.innerWidth / 2;
|
|
34
|
-
const scrollY = window.scrollY + rect.top + rect.height / 2 - window.innerHeight / 2;
|
|
35
|
-
|
|
36
35
|
return {
|
|
37
36
|
success: true,
|
|
38
37
|
scrollType: 'element',
|
|
39
38
|
selector: selector,
|
|
39
|
+
element: (${ELEMENT_IDENTITY_JS})(el),
|
|
40
40
|
matchCount: allMatches.length,
|
|
41
41
|
scrolledTo: {
|
|
42
42
|
x: Math.round(window.scrollX),
|
|
43
43
|
y: Math.round(window.scrollY)
|
|
44
44
|
},
|
|
45
|
-
viewportSize: {
|
|
46
|
-
width: window.innerWidth,
|
|
47
|
-
height: window.innerHeight
|
|
48
|
-
},
|
|
45
|
+
viewportSize: (${VIEWPORT_SIZE_JS})(window),
|
|
49
46
|
pageSize: {
|
|
50
|
-
width: document.documentElement.scrollWidth,
|
|
51
|
-
height: document.documentElement.scrollHeight
|
|
47
|
+
width: (document.scrollingElement || document.documentElement).scrollWidth,
|
|
48
|
+
height: (document.scrollingElement || document.documentElement).scrollHeight
|
|
52
49
|
}
|
|
53
50
|
};
|
|
54
51
|
})`;
|
|
@@ -56,6 +53,7 @@ const SCROLL_BY_SCRIPT = `
|
|
|
56
53
|
(function(options) {
|
|
57
54
|
const beforeX = window.scrollX;
|
|
58
55
|
const beforeY = window.scrollY;
|
|
56
|
+
const range = (${SCROLL_RANGE_JS})(window);
|
|
59
57
|
|
|
60
58
|
if (options.top) {
|
|
61
59
|
window.scrollTo({ top: 0, behavior: 'instant' });
|
|
@@ -81,16 +79,60 @@ const SCROLL_BY_SCRIPT = `
|
|
|
81
79
|
scrollType: options.top || options.bottom ? 'position' : 'offset',
|
|
82
80
|
scrolledTo: { x: Math.round(afterX), y: Math.round(afterY) },
|
|
83
81
|
scrolledBy: { x: Math.round(afterX - beforeX), y: Math.round(afterY - beforeY) },
|
|
84
|
-
viewportSize: {
|
|
85
|
-
width: window.innerWidth,
|
|
86
|
-
height: window.innerHeight
|
|
87
|
-
},
|
|
82
|
+
viewportSize: (${VIEWPORT_SIZE_JS})(window),
|
|
88
83
|
pageSize: {
|
|
89
|
-
width: document.documentElement.scrollWidth,
|
|
90
|
-
height: document.documentElement.scrollHeight
|
|
91
|
-
}
|
|
84
|
+
width: (document.scrollingElement || document.documentElement).scrollWidth,
|
|
85
|
+
height: (document.scrollingElement || document.documentElement).scrollHeight
|
|
86
|
+
},
|
|
87
|
+
rangeBefore: range,
|
|
88
|
+
readyState: document.readyState
|
|
92
89
|
};
|
|
93
90
|
})`;
|
|
91
|
+
/**
|
|
92
|
+
* The direction a page scroll asked for (the options allow one per axis;
|
|
93
|
+
* only one axis is checked, vertical first).
|
|
94
|
+
*
|
|
95
|
+
* @param options - Scroll options
|
|
96
|
+
* @returns Direction, or undefined when no distance was asked for
|
|
97
|
+
*/
|
|
98
|
+
function requestedDirection(options) {
|
|
99
|
+
if (options.bottom || (options.down ?? 0) > 0)
|
|
100
|
+
return 'down';
|
|
101
|
+
if (options.top || (options.up ?? 0) > 0)
|
|
102
|
+
return 'up';
|
|
103
|
+
if ((options.right ?? 0) > 0)
|
|
104
|
+
return 'right';
|
|
105
|
+
if ((options.left ?? 0) > 0)
|
|
106
|
+
return 'left';
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Warn when a page scroll moved nothing: the document does not scroll in
|
|
111
|
+
* that direction, the page was already at its edge, or scrolling is locked;
|
|
112
|
+
* while the page is still loading, also that it may grow.
|
|
113
|
+
*
|
|
114
|
+
* @param result - Page script result
|
|
115
|
+
* @param options - Scroll options
|
|
116
|
+
* @returns The scroll result, with `warning` when nothing scrolled
|
|
117
|
+
*/
|
|
118
|
+
function withNoEffectWarning(result, options) {
|
|
119
|
+
const { rangeBefore, readyState, ...scroll } = result;
|
|
120
|
+
const direction = requestedDirection(options);
|
|
121
|
+
const moved = scroll.scrolledBy && (scroll.scrolledBy.x !== 0 || scroll.scrolledBy.y !== 0);
|
|
122
|
+
if (!direction || moved || !scroll.viewportSize)
|
|
123
|
+
return scroll;
|
|
124
|
+
const vertical = direction === 'up' || direction === 'down';
|
|
125
|
+
const scrollable = vertical
|
|
126
|
+
? rangeBefore.up + rangeBefore.down > 0
|
|
127
|
+
: rangeBefore.left + rangeBefore.right > 0;
|
|
128
|
+
const warning = scrollNoEffectWarning({
|
|
129
|
+
direction,
|
|
130
|
+
reason: !scrollable ? 'too-small' : rangeBefore[direction] <= 0 ? 'at-edge' : 'locked',
|
|
131
|
+
viewport: vertical ? scroll.viewportSize.height : scroll.viewportSize.width,
|
|
132
|
+
readyState,
|
|
133
|
+
});
|
|
134
|
+
return { ...scroll, warning };
|
|
135
|
+
}
|
|
94
136
|
function isScrollResult(value) {
|
|
95
137
|
if (typeof value !== 'object' || value === null)
|
|
96
138
|
return false;
|
|
@@ -164,7 +206,7 @@ export async function scrollPage(cdp, selector, options = {}) {
|
|
|
164
206
|
};
|
|
165
207
|
}
|
|
166
208
|
if (cdpResponse.result?.value && isScrollResult(cdpResponse.result.value)) {
|
|
167
|
-
return cdpResponse.result.value;
|
|
209
|
+
return withNoEffectWarning(cdpResponse.result.value, options);
|
|
168
210
|
}
|
|
169
211
|
return {
|
|
170
212
|
success: false,
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* and JS string escaping.
|
|
4
4
|
*/
|
|
5
5
|
import type { Protocol } from '../../../connection/typed-cdp.js';
|
|
6
|
+
import type { FillResult } from '../../../ipc/protocol/domTypes.js';
|
|
6
7
|
/**
|
|
7
8
|
* Escape a CSS selector for embedding in a single-quoted JS string.
|
|
8
9
|
*/
|
|
@@ -20,6 +21,14 @@ export declare function withMultipleMatchesWarning<T extends {
|
|
|
20
21
|
matchCount?: number | undefined;
|
|
21
22
|
warning?: string | undefined;
|
|
22
23
|
}>(result: T, index: number | undefined, action: string): T;
|
|
24
|
+
/**
|
|
25
|
+
* Put a warning first when the filled field's value is not the one given
|
|
26
|
+
* (see `valueMismatch`). Keeps an existing warning after it.
|
|
27
|
+
*
|
|
28
|
+
* @param result - Fill result
|
|
29
|
+
* @returns The result, with the warning when the value differs
|
|
30
|
+
*/
|
|
31
|
+
export declare function withValueMismatchWarning(result: FillResult): FillResult;
|
|
23
32
|
/**
|
|
24
33
|
* Escape a value for embedding in a single-quoted JS string.
|
|
25
34
|
*/
|
|
@@ -30,6 +39,14 @@ export declare function escapeValueForJS(value: string): string;
|
|
|
30
39
|
* query-then-act pattern.
|
|
31
40
|
*/
|
|
32
41
|
export declare function formatScriptExecutionError(exceptionDetails: Protocol.Runtime.ExceptionDetails, selector: string, operationType?: 'fill' | 'click', expression?: string): string;
|
|
42
|
+
/**
|
|
43
|
+
* What a page script threw, in one line (`TypeError: Cannot read …` rather
|
|
44
|
+
* than CDP's bare "Uncaught").
|
|
45
|
+
*
|
|
46
|
+
* @param details - Exception details from Runtime.evaluate
|
|
47
|
+
* @returns First line of the exception's description
|
|
48
|
+
*/
|
|
49
|
+
export declare function exceptionSummary(details: Protocol.Runtime.ExceptionDetails): string;
|
|
33
50
|
/**
|
|
34
51
|
* Throw a user error when a page script failed because the CSS selector is
|
|
35
52
|
* invalid (the browser's `querySelectorAll` rejected it), instead of reporting
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { CommandError } from '../../../errors/index.js';
|
|
6
6
|
import { invalidSelectorError } from '../../../errors/messages.js';
|
|
7
|
-
import { multipleMatchesWarning } from '../../../ui/messages/commands.js';
|
|
7
|
+
import { multipleMatchesWarning, valueMismatchWarning } from '../../../ui/messages/commands.js';
|
|
8
8
|
import { EXIT_CODES } from '../../../utils/exitCodes.js';
|
|
9
9
|
import { detectSelectorQuoteDamage } from '../../../utils/shellDetection.js';
|
|
10
10
|
/**
|
|
@@ -29,6 +29,19 @@ export function withMultipleMatchesWarning(result, index, action) {
|
|
|
29
29
|
const warning = multipleMatchesWarning(count, action);
|
|
30
30
|
return { ...result, warning: result.warning ? `${result.warning}; ${warning}` : warning };
|
|
31
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* Put a warning first when the filled field's value is not the one given
|
|
34
|
+
* (see `valueMismatch`). Keeps an existing warning after it.
|
|
35
|
+
*
|
|
36
|
+
* @param result - Fill result
|
|
37
|
+
* @returns The result, with the warning when the value differs
|
|
38
|
+
*/
|
|
39
|
+
export function withValueMismatchWarning(result) {
|
|
40
|
+
if (!result.valueMismatch)
|
|
41
|
+
return result;
|
|
42
|
+
const warning = valueMismatchWarning(result.valueMismatch);
|
|
43
|
+
return { ...result, warning: result.warning ? `${warning}; ${result.warning}` : warning };
|
|
44
|
+
}
|
|
32
45
|
/**
|
|
33
46
|
* Escape a value for embedding in a single-quoted JS string.
|
|
34
47
|
*/
|
|
@@ -81,6 +94,17 @@ export function formatScriptExecutionError(exceptionDetails, selector, operation
|
|
|
81
94
|
lines.push(` ${troubleshootingSteps.join('\n ')}`);
|
|
82
95
|
return lines.join('\n');
|
|
83
96
|
}
|
|
97
|
+
/**
|
|
98
|
+
* What a page script threw, in one line (`TypeError: Cannot read …` rather
|
|
99
|
+
* than CDP's bare "Uncaught").
|
|
100
|
+
*
|
|
101
|
+
* @param details - Exception details from Runtime.evaluate
|
|
102
|
+
* @returns First line of the exception's description
|
|
103
|
+
*/
|
|
104
|
+
export function exceptionSummary(details) {
|
|
105
|
+
const description = details.exception?.description ?? details.text;
|
|
106
|
+
return description.split('\n')[0] ?? description;
|
|
107
|
+
}
|
|
84
108
|
/**
|
|
85
109
|
* Throw a user error when a page script failed because the CSS selector is
|
|
86
110
|
* invalid (the browser's `querySelectorAll` rejected it), instead of reporting
|
|
@@ -1,14 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Post-action network-stability wait.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Used around fill/click/hover/pressKey/scroll: tracks in-flight requests
|
|
5
|
+
* from before the action runs (so a request its handler sends synchronously
|
|
6
|
+
* is seen), then waits until no request has been pending for
|
|
7
|
+
* ACTION_NETWORK_IDLE_MS, giving up after ACTION_STABILITY_TIMEOUT_MS.
|
|
7
8
|
*/
|
|
8
9
|
import type { CDPConnection } from '../../../connection/cdp.js';
|
|
9
10
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
11
|
+
* Run an action and, when it succeeds, wait for the network to settle.
|
|
12
|
+
*
|
|
13
|
+
* Requests are tracked from before the action, so one an event handler
|
|
14
|
+
* starts while the action runs (`onclick = () => fetch(...)`) is waited for.
|
|
15
|
+
* `Network.enable` is sent without waiting for it: the session's network
|
|
16
|
+
* collector has already enabled the domain, and Chrome holds the call until
|
|
17
|
+
* a pending navigation commits, which would otherwise block for as long as
|
|
18
|
+
* the new page's server takes to answer.
|
|
19
|
+
*
|
|
20
|
+
* @param cdp - CDP connection
|
|
21
|
+
* @param action - Action to run (its result's `success` decides whether to wait)
|
|
22
|
+
* @param wait - Wait for the network after the action (false for `--no-wait`)
|
|
23
|
+
* @returns The action's result, once the network is idle or the wait timed out
|
|
12
24
|
*/
|
|
13
|
-
export declare function
|
|
25
|
+
export declare function withActionStability<T extends {
|
|
26
|
+
success: boolean;
|
|
27
|
+
}>(cdp: CDPConnection, action: () => Promise<T>, wait?: boolean): Promise<T>;
|
|
14
28
|
//# sourceMappingURL=stability.d.ts.map
|
|
@@ -1,40 +1,71 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Post-action network-stability wait.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Used around fill/click/hover/pressKey/scroll: tracks in-flight requests
|
|
5
|
+
* from before the action runs (so a request its handler sends synchronously
|
|
6
|
+
* is seen), then waits until no request has been pending for
|
|
7
|
+
* ACTION_NETWORK_IDLE_MS, giving up after ACTION_STABILITY_TIMEOUT_MS.
|
|
7
8
|
*/
|
|
8
9
|
import { trackInFlightRequests } from '../../../connection/inFlightRequests.js';
|
|
9
10
|
import { createLogger } from '../../../ui/logging/index.js';
|
|
10
11
|
import { delay } from '../../../utils/async.js';
|
|
12
|
+
import { getErrorMessage } from '../../../utils/errors.js';
|
|
11
13
|
const log = createLogger('dom');
|
|
12
14
|
const ACTION_NETWORK_IDLE_MS = 150;
|
|
13
15
|
const ACTION_STABILITY_TIMEOUT_MS = 2000;
|
|
14
16
|
const STABILITY_CHECK_INTERVAL_MS = 50;
|
|
15
17
|
/**
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
+
* Run an action and, when it succeeds, wait for the network to settle.
|
|
19
|
+
*
|
|
20
|
+
* Requests are tracked from before the action, so one an event handler
|
|
21
|
+
* starts while the action runs (`onclick = () => fetch(...)`) is waited for.
|
|
22
|
+
* `Network.enable` is sent without waiting for it: the session's network
|
|
23
|
+
* collector has already enabled the domain, and Chrome holds the call until
|
|
24
|
+
* a pending navigation commits, which would otherwise block for as long as
|
|
25
|
+
* the new page's server takes to answer.
|
|
26
|
+
*
|
|
27
|
+
* @param cdp - CDP connection
|
|
28
|
+
* @param action - Action to run (its result's `success` decides whether to wait)
|
|
29
|
+
* @param wait - Wait for the network after the action (false for `--no-wait`)
|
|
30
|
+
* @returns The action's result, once the network is idle or the wait timed out
|
|
18
31
|
*/
|
|
19
|
-
export async function
|
|
20
|
-
|
|
21
|
-
|
|
32
|
+
export async function withActionStability(cdp, action, wait = true) {
|
|
33
|
+
if (!wait)
|
|
34
|
+
return action();
|
|
35
|
+
void cdp
|
|
36
|
+
.send('Network.enable')
|
|
37
|
+
.catch((error) => log.debug(`Network.enable failed: ${getErrorMessage(error)}`));
|
|
22
38
|
const requests = trackInFlightRequests(cdp);
|
|
23
39
|
try {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
log.debug(`Network stable after ${idleTime}ms idle`);
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
await delay(STABILITY_CHECK_INTERVAL_MS);
|
|
33
|
-
}
|
|
34
|
-
log.debug('Stability timeout reached, proceeding');
|
|
40
|
+
const result = await action();
|
|
41
|
+
if (result.success)
|
|
42
|
+
await waitForNetworkIdle(requests);
|
|
43
|
+
return result;
|
|
35
44
|
}
|
|
36
45
|
finally {
|
|
37
46
|
requests.dispose();
|
|
38
47
|
}
|
|
39
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* Wait until no request has been in flight for ACTION_NETWORK_IDLE_MS since
|
|
51
|
+
* the wait began (a request the action started counts until it ends), or
|
|
52
|
+
* until ACTION_STABILITY_TIMEOUT_MS passed.
|
|
53
|
+
*
|
|
54
|
+
* @param requests - Tracker started before the action
|
|
55
|
+
*/
|
|
56
|
+
async function waitForNetworkIdle(requests) {
|
|
57
|
+
const waitStart = Date.now();
|
|
58
|
+
const deadline = waitStart + ACTION_STABILITY_TIMEOUT_MS;
|
|
59
|
+
while (Date.now() < deadline) {
|
|
60
|
+
if (requests.count === 0) {
|
|
61
|
+
const idleTime = Date.now() - Math.max(requests.lastActivity, waitStart);
|
|
62
|
+
if (idleTime >= ACTION_NETWORK_IDLE_MS) {
|
|
63
|
+
log.debug(`Network stable after ${idleTime}ms idle`);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
await delay(STABILITY_CHECK_INTERVAL_MS);
|
|
68
|
+
}
|
|
69
|
+
log.debug(`Stability timeout reached with ${requests.count} request(s) in flight, proceeding`);
|
|
70
|
+
}
|
|
40
71
|
//# sourceMappingURL=stability.js.map
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import type { CDPConnection } from '../../connection/cdp.js';
|
|
5
5
|
import type { SubmitResult } from '../../ipc/protocol/domTypes.js';
|
|
6
|
+
import type { PendingRequest } from '../../telemetry/network.js';
|
|
6
7
|
/**
|
|
7
8
|
* Options for submitting a form.
|
|
8
9
|
*/
|
|
@@ -15,6 +16,8 @@ export interface SubmitOptions {
|
|
|
15
16
|
waitNetwork?: number;
|
|
16
17
|
/** Maximum time to wait in milliseconds (default: 10000) */
|
|
17
18
|
timeout?: number;
|
|
19
|
+
/** Requests still running in the session (named when the wait times out) */
|
|
20
|
+
pendingRequests?: () => Iterable<PendingRequest>;
|
|
18
21
|
}
|
|
19
22
|
export type { SubmitResult } from '../../ipc/protocol/domTypes.js';
|
|
20
23
|
/**
|