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
|
@@ -9,27 +9,75 @@
|
|
|
9
9
|
* {@link BOUND_TARGET_SELECTOR}.
|
|
10
10
|
*/
|
|
11
11
|
import type { CDPConnection } from '../../connection/cdp.js';
|
|
12
|
+
import { CommandError } from '../../errors/index.js';
|
|
12
13
|
/** Selector placeholder that makes page scripts use the bound node. */
|
|
13
14
|
export declare const BOUND_TARGET_SELECTOR = "__bdg_bound_target__";
|
|
15
|
+
/** Removes the node bound for index-based commands from the window it was stored on */
|
|
16
|
+
export declare const UNBIND_TARGET_SCRIPT = "delete window.__bdgTarget";
|
|
17
|
+
/**
|
|
18
|
+
* Page-side matching of filters ({@link SelectorFilter}) and scoped steps
|
|
19
|
+
* ({@link ScopedStep}): called with the open shadow roots of the page, returns
|
|
20
|
+
* `{ passesAll, descend }`.
|
|
21
|
+
*
|
|
22
|
+
* Visible means rendered (`checkVisibility()`: not inside a closed
|
|
23
|
+
* `<details>` or under `content-visibility: hidden`), a non-empty box and
|
|
24
|
+
* `visibility: visible`, as in Playwright (`opacity: 0` still counts as
|
|
25
|
+
* visible). Text is the rendered text (`innerText`) of visible elements and
|
|
26
|
+
* the text nodes of hidden ones (display or visibility; not those of
|
|
27
|
+
* `<script>`, `<style>` or `<noscript>`), so text filters match hidden
|
|
28
|
+
* elements like Playwright's and `:visible` decides visibility; button inputs
|
|
29
|
+
* use their value. Whitespace is collapsed; filter texts arrive normalized
|
|
30
|
+
* (`has-text` lowercased). `:visible` is checked before the text filters,
|
|
31
|
+
* which read the text.
|
|
32
|
+
*
|
|
33
|
+
* Steps and `:has()` match CSS relative to an element (`:scope > css`). A
|
|
34
|
+
* descendant step also searches the open shadow roots under the element (the
|
|
35
|
+
* whole step CSS inside one shadow tree); a child step stays in the
|
|
36
|
+
* element's own tree.
|
|
37
|
+
*/
|
|
38
|
+
export declare const FILTER_MATCHING_JS = "(shadowRoots) => {\n const skipped = /^(script|style|noscript|template)$/;\n const hiddenText = (el) => {\n const walker = el.ownerDocument.createTreeWalker(el, 5, {\n acceptNode: (node) => (node.nodeType === 1 && skipped.test(node.localName) ? 2 : 1)\n });\n let text = '';\n for (let node = walker.nextNode(); node; node = walker.nextNode()) {\n if (node.nodeType === 3) text += node.data;\n }\n return text;\n };\n const textOf = (el) => {\n if (el.localName === 'input' && /^(submit|button|reset)$/i.test(el.type)) return el.value;\n const rendered = typeof el.innerText === 'string' &&\n (typeof el.checkVisibility !== 'function' || el.checkVisibility({ visibilityProperty: true }));\n return (rendered ? el.innerText : hiddenText(el)).replace(/\\s+/g, ' ').trim();\n };\n const passes = (el, filter) => {\n if (filter.kind === 'visible') {\n if (typeof el.checkVisibility === 'function' && !el.checkVisibility()) return false;\n const rect = el.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0 && el.ownerDocument.defaultView.getComputedStyle(el).visibility === 'visible';\n }\n if (filter.kind === 'has') return filter.selectors.some((chain) => descend(el, chain).length > 0);\n const text = textOf(el);\n return filter.kind === 'text-is' ? text === filter.text : text.toLowerCase().includes(filter.text);\n };\n const passesAll = (el, filters) =>\n filters.every((filter) => filter.kind !== 'visible' || passes(el, filter)) &&\n filters.every((filter) => filter.kind === 'visible' || passes(el, filter));\n const isUnder = (scope, node) => {\n for (let current = node; current; current = current.getRootNode().host) {\n if (scope === current || scope.contains(current)) return true;\n }\n return false;\n };\n const stepMatches = (scope, step) => {\n const matches = [...scope.querySelectorAll(':scope ' + step.combinator + ' ' + step.css)];\n if (step.combinator !== ' ') return matches;\n for (const root of shadowRoots) {\n if (isUnder(scope, root.host)) matches.push(...root.querySelectorAll(step.css));\n }\n return matches;\n };\n const descend = (scope, steps) => {\n let current = [scope];\n for (const step of steps) {\n const next = new Set();\n for (const el of current) {\n for (const match of stepMatches(el, step)) {\n if (passesAll(match, step.filters)) next.add(match);\n }\n }\n current = [...next];\n }\n return current;\n };\n return { passesAll: passesAll, descend: descend };\n}";
|
|
14
39
|
/**
|
|
15
40
|
* Page-side selector search that also reaches into open shadow roots and
|
|
16
41
|
* same-origin iframes (recursively), like a user sees the page.
|
|
17
42
|
*
|
|
18
43
|
* Takes the selector and, when it has text or visibility filters, its parts
|
|
19
44
|
* from {@link parseSelectorFilters}: their CSS runs as one selector list and
|
|
20
|
-
* a match is kept when it passes the filters of a part it matches
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
45
|
+
* a match is kept when it passes the filters of a part it matches; a part
|
|
46
|
+
* with scoped steps contributes the steps' matches under it instead (each
|
|
47
|
+
* element once). Matches come in document order: those of the document
|
|
48
|
+
* first, then those of each shadow root and frame in the order they are
|
|
49
|
+
* reached. Cross-origin iframes are separate processes and cannot be
|
|
50
|
+
* searched. Throws a `SyntaxError` for an invalid selector.
|
|
24
51
|
*/
|
|
25
|
-
export declare const DEEP_QUERY_JS = "function (selector, parts) {\n const css = parts ? parts.map((part) => part.css).join(', ') : selector;\n const found = [];\n const visit = (root) => {\n for (const match of root.querySelectorAll(css)) found.push(match);\n for (const el of root.querySelectorAll('*')) {\n if (el.shadowRoot) visit(el.shadowRoot);\n if (el.tagName === 'IFRAME' || el.tagName === 'FRAME') {\n let frameDocument = null;\n try { frameDocument = el.contentDocument; } catch (e) { frameDocument = null; }\n if (frameDocument) visit(frameDocument);\n }\n }\n };\n visit(document);\n if (!parts) return found;\n const passes = (el, filter) => {\n
|
|
52
|
+
export declare const DEEP_QUERY_JS = "function (selector, parts) {\n const css = parts ? parts.map((part) => part.css).join(', ') : selector;\n const found = [];\n const roots = [];\n const visit = (root) => {\n roots.push(root);\n for (const match of root.querySelectorAll(css)) found.push(match);\n for (const el of root.querySelectorAll('*')) {\n if (el.shadowRoot) visit(el.shadowRoot);\n if (el.tagName === 'IFRAME' || el.tagName === 'FRAME') {\n let frameDocument = null;\n try { frameDocument = el.contentDocument; } catch (e) { frameDocument = null; }\n if (frameDocument) visit(frameDocument);\n }\n }\n };\n visit(document);\n if (!parts) return found;\n const shadowRoots = roots.filter((root) => root.host);\n const { passesAll, descend } = ((shadowRoots) => {\n const skipped = /^(script|style|noscript|template)$/;\n const hiddenText = (el) => {\n const walker = el.ownerDocument.createTreeWalker(el, 5, {\n acceptNode: (node) => (node.nodeType === 1 && skipped.test(node.localName) ? 2 : 1)\n });\n let text = '';\n for (let node = walker.nextNode(); node; node = walker.nextNode()) {\n if (node.nodeType === 3) text += node.data;\n }\n return text;\n };\n const textOf = (el) => {\n if (el.localName === 'input' && /^(submit|button|reset)$/i.test(el.type)) return el.value;\n const rendered = typeof el.innerText === 'string' &&\n (typeof el.checkVisibility !== 'function' || el.checkVisibility({ visibilityProperty: true }));\n return (rendered ? el.innerText : hiddenText(el)).replace(/\\s+/g, ' ').trim();\n };\n const passes = (el, filter) => {\n if (filter.kind === 'visible') {\n if (typeof el.checkVisibility === 'function' && !el.checkVisibility()) return false;\n const rect = el.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0 && el.ownerDocument.defaultView.getComputedStyle(el).visibility === 'visible';\n }\n if (filter.kind === 'has') return filter.selectors.some((chain) => descend(el, chain).length > 0);\n const text = textOf(el);\n return filter.kind === 'text-is' ? text === filter.text : text.toLowerCase().includes(filter.text);\n };\n const passesAll = (el, filters) =>\n filters.every((filter) => filter.kind !== 'visible' || passes(el, filter)) &&\n filters.every((filter) => filter.kind === 'visible' || passes(el, filter));\n const isUnder = (scope, node) => {\n for (let current = node; current; current = current.getRootNode().host) {\n if (scope === current || scope.contains(current)) return true;\n }\n return false;\n };\n const stepMatches = (scope, step) => {\n const matches = [...scope.querySelectorAll(':scope ' + step.combinator + ' ' + step.css)];\n if (step.combinator !== ' ') return matches;\n for (const root of shadowRoots) {\n if (isUnder(scope, root.host)) matches.push(...root.querySelectorAll(step.css));\n }\n return matches;\n };\n const descend = (scope, steps) => {\n let current = [scope];\n for (const step of steps) {\n const next = new Set();\n for (const el of current) {\n for (const match of stepMatches(el, step)) {\n if (passesAll(match, step.filters)) next.add(match);\n }\n }\n current = [...next];\n }\n return current;\n };\n return { passesAll: passesAll, descend: descend };\n})(shadowRoots);\n const result = new Set();\n for (const el of found) {\n for (const part of parts) {\n if (!el.matches(part.css) || !passesAll(el, part.filters)) continue;\n for (const match of descend(el, part.steps || [])) result.add(match);\n }\n }\n const rootOrder = new Map(roots.map((root, i) => [root, i]));\n const rootIndex = (el) => rootOrder.get(el.getRootNode()) ?? roots.length;\n return [...result].sort((a, b) =>\n rootIndex(a) - rootIndex(b) || (a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1)\n );\n}";
|
|
26
53
|
/**
|
|
27
54
|
* Page-side element lookup shared by the interaction scripts.
|
|
28
55
|
*
|
|
29
56
|
* Returns the bound node (if still in the page) for the placeholder selector,
|
|
30
57
|
* otherwise all matches of the selector ({@link DEEP_QUERY_JS}).
|
|
31
58
|
*/
|
|
32
|
-
export declare const FIND_ELEMENTS_JS = "function (selector, parts) {\n if (selector === '__bdg_bound_target__') {\n const el = window.__bdgTarget;\n return el && el.isConnected ? [el] : [];\n }\n return (function (selector, parts) {\n const css = parts ? parts.map((part) => part.css).join(', ') : selector;\n const found = [];\n const visit = (root) => {\n for (const match of root.querySelectorAll(css)) found.push(match);\n for (const el of root.querySelectorAll('*')) {\n if (el.shadowRoot) visit(el.shadowRoot);\n if (el.tagName === 'IFRAME' || el.tagName === 'FRAME') {\n let frameDocument = null;\n try { frameDocument = el.contentDocument; } catch (e) { frameDocument = null; }\n if (frameDocument) visit(frameDocument);\n }\n }\n };\n visit(document);\n if (!parts) return found;\n const passes = (el, filter) => {\n
|
|
59
|
+
export declare const FIND_ELEMENTS_JS = "function (selector, parts) {\n if (selector === '__bdg_bound_target__') {\n const el = window.__bdgTarget;\n return el && el.isConnected ? [el] : [];\n }\n return (function (selector, parts) {\n const css = parts ? parts.map((part) => part.css).join(', ') : selector;\n const found = [];\n const roots = [];\n const visit = (root) => {\n roots.push(root);\n for (const match of root.querySelectorAll(css)) found.push(match);\n for (const el of root.querySelectorAll('*')) {\n if (el.shadowRoot) visit(el.shadowRoot);\n if (el.tagName === 'IFRAME' || el.tagName === 'FRAME') {\n let frameDocument = null;\n try { frameDocument = el.contentDocument; } catch (e) { frameDocument = null; }\n if (frameDocument) visit(frameDocument);\n }\n }\n };\n visit(document);\n if (!parts) return found;\n const shadowRoots = roots.filter((root) => root.host);\n const { passesAll, descend } = ((shadowRoots) => {\n const skipped = /^(script|style|noscript|template)$/;\n const hiddenText = (el) => {\n const walker = el.ownerDocument.createTreeWalker(el, 5, {\n acceptNode: (node) => (node.nodeType === 1 && skipped.test(node.localName) ? 2 : 1)\n });\n let text = '';\n for (let node = walker.nextNode(); node; node = walker.nextNode()) {\n if (node.nodeType === 3) text += node.data;\n }\n return text;\n };\n const textOf = (el) => {\n if (el.localName === 'input' && /^(submit|button|reset)$/i.test(el.type)) return el.value;\n const rendered = typeof el.innerText === 'string' &&\n (typeof el.checkVisibility !== 'function' || el.checkVisibility({ visibilityProperty: true }));\n return (rendered ? el.innerText : hiddenText(el)).replace(/\\s+/g, ' ').trim();\n };\n const passes = (el, filter) => {\n if (filter.kind === 'visible') {\n if (typeof el.checkVisibility === 'function' && !el.checkVisibility()) return false;\n const rect = el.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0 && el.ownerDocument.defaultView.getComputedStyle(el).visibility === 'visible';\n }\n if (filter.kind === 'has') return filter.selectors.some((chain) => descend(el, chain).length > 0);\n const text = textOf(el);\n return filter.kind === 'text-is' ? text === filter.text : text.toLowerCase().includes(filter.text);\n };\n const passesAll = (el, filters) =>\n filters.every((filter) => filter.kind !== 'visible' || passes(el, filter)) &&\n filters.every((filter) => filter.kind === 'visible' || passes(el, filter));\n const isUnder = (scope, node) => {\n for (let current = node; current; current = current.getRootNode().host) {\n if (scope === current || scope.contains(current)) return true;\n }\n return false;\n };\n const stepMatches = (scope, step) => {\n const matches = [...scope.querySelectorAll(':scope ' + step.combinator + ' ' + step.css)];\n if (step.combinator !== ' ') return matches;\n for (const root of shadowRoots) {\n if (isUnder(scope, root.host)) matches.push(...root.querySelectorAll(step.css));\n }\n return matches;\n };\n const descend = (scope, steps) => {\n let current = [scope];\n for (const step of steps) {\n const next = new Set();\n for (const el of current) {\n for (const match of stepMatches(el, step)) {\n if (passesAll(match, step.filters)) next.add(match);\n }\n }\n current = [...next];\n }\n return current;\n };\n return { passesAll: passesAll, descend: descend };\n})(shadowRoots);\n const result = new Set();\n for (const el of found) {\n for (const part of parts) {\n if (!el.matches(part.css) || !passesAll(el, part.filters)) continue;\n for (const match of descend(el, part.steps || [])) result.add(match);\n }\n }\n const rootOrder = new Map(roots.map((root, i) => [root, i]));\n const rootIndex = (el) => rootOrder.get(el.getRootNode()) ?? roots.length;\n return [...result].sort((a, b) =>\n rootIndex(a) - rootIndex(b) || (a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1)\n );\n})(selector, parts);\n}";
|
|
60
|
+
/**
|
|
61
|
+
* Page-side check of what selectors cannot search in the top document:
|
|
62
|
+
* iframes whose document cannot be read (cross-origin) and `<object>`/`<embed>`
|
|
63
|
+
* elements. Cheap: no frame is entered.
|
|
64
|
+
*/
|
|
65
|
+
export declare const UNSEARCHED_CONTENT_JS = "(() => {\n const frames = Array.from(document.querySelectorAll('iframe, frame'));\n const unreadable = (frame) => {\n try { return !frame.contentDocument; } catch (e) { return true; }\n };\n return { crossOriginFrames: frames.some(unreadable), embeds: !!document.querySelector('object, embed') };\n})()";
|
|
66
|
+
/**
|
|
67
|
+
* Page-side ids (`kind` "id") or classes (`kind` "class") in the top
|
|
68
|
+
* document, from the first {@link NAME_SCAN_LIMIT} elements that have one.
|
|
69
|
+
*
|
|
70
|
+
* @param kind - Which names to read
|
|
71
|
+
* @returns Expression evaluating to the distinct names
|
|
72
|
+
*/
|
|
73
|
+
export declare function pageNamesJS(kind: 'id' | 'class'): string;
|
|
74
|
+
/**
|
|
75
|
+
* Page-side: the form control a `<label>` stands for (`label.control`: its
|
|
76
|
+
* `for` target or the control inside it), or null for other elements and
|
|
77
|
+
* labels without one. Fill, click and key presses act on that control, like
|
|
78
|
+
* Playwright.
|
|
79
|
+
*/
|
|
80
|
+
export declare const LABEL_CONTROL_JS = "(el) => (el && el.localName === 'label' ? el.control : null)";
|
|
33
81
|
/**
|
|
34
82
|
* Arguments for {@link DEEP_QUERY_JS} / {@link FIND_ELEMENTS_JS} and the page
|
|
35
83
|
* scripts built on them: the selector as a JS string literal and its parts
|
|
@@ -37,42 +85,47 @@ export declare const FIND_ELEMENTS_JS = "function (selector, parts) {\n if (sel
|
|
|
37
85
|
*
|
|
38
86
|
* @param selector - Selector as the user gave it (or the bound-node placeholder)
|
|
39
87
|
* @returns JS source of the two arguments, e.g. `"li:visible", [{...}]`
|
|
40
|
-
* @throws CommandError (81) for a misplaced or malformed filter
|
|
88
|
+
* @throws CommandError (81) for an empty selector or a misplaced or malformed filter
|
|
41
89
|
*/
|
|
42
90
|
export declare function selectorArgsJS(selector: string): string;
|
|
43
|
-
/** Selector and index the page scripts should use. */
|
|
91
|
+
/** Selector and index the page scripts should use, and the connection to run them on. */
|
|
44
92
|
export interface ScriptTarget {
|
|
45
93
|
selector: string;
|
|
46
94
|
index?: number;
|
|
95
|
+
/** Connection for the page scripts (scoped to the element's frame when the top page cannot reach it) */
|
|
96
|
+
cdp: CDPConnection;
|
|
47
97
|
}
|
|
48
98
|
/**
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
* @param backendNodeId - Backend node id from the query cache
|
|
53
|
-
* @throws CommandError (exit 87) when the node no longer exists in the page
|
|
54
|
-
*/
|
|
55
|
-
export declare function bindTargetNode(cdp: CDPConnection, backendNodeId: number): Promise<void>;
|
|
56
|
-
/**
|
|
57
|
-
* Resolve what a page script should target for an interaction request.
|
|
99
|
+
* Run an interaction on the element a request targets. Results and errors
|
|
100
|
+
* never show the internal placeholder: a bound node the page scripts could
|
|
101
|
+
* not find is reported as stale (87), and results carry the user's selector.
|
|
58
102
|
*
|
|
59
103
|
* @param cdp - CDP connection
|
|
60
104
|
* @param params - Request with a selector (and optional index) or a backend node id
|
|
61
|
-
* @
|
|
105
|
+
* @param work - The interaction, given the script target
|
|
106
|
+
* @returns The interaction's result
|
|
107
|
+
* @throws CommandError (87) when the bound node left the page during the action
|
|
62
108
|
*/
|
|
63
|
-
export declare function
|
|
109
|
+
export declare function onScriptTarget<T extends {
|
|
110
|
+
selector?: string;
|
|
111
|
+
error?: string;
|
|
112
|
+
suggestion?: string;
|
|
113
|
+
exitCode?: number;
|
|
114
|
+
}>(cdp: CDPConnection, params: {
|
|
64
115
|
selector?: string;
|
|
65
116
|
index?: number;
|
|
66
117
|
backendNodeId?: number;
|
|
67
|
-
}): Promise<
|
|
118
|
+
}, work: (target: ScriptTarget) => Promise<T>): Promise<T>;
|
|
68
119
|
/**
|
|
69
|
-
*
|
|
120
|
+
* Error for an element a command could not find.
|
|
70
121
|
*
|
|
71
|
-
* @param
|
|
72
|
-
* @param
|
|
73
|
-
* @returns
|
|
122
|
+
* @param target - Selector (and index) or backend node id the command was given
|
|
123
|
+
* @param matchCount - Elements the selector matched
|
|
124
|
+
* @returns Stale (87), out of range (81) or not found (83) error
|
|
74
125
|
*/
|
|
75
|
-
export declare function
|
|
76
|
-
selector
|
|
77
|
-
|
|
126
|
+
export declare function missingElementError(target: {
|
|
127
|
+
selector: string;
|
|
128
|
+
index?: number;
|
|
129
|
+
backendNodeId?: number;
|
|
130
|
+
}, matchCount: number): CommandError;
|
|
78
131
|
//# sourceMappingURL=targetNode.d.ts.map
|
|
@@ -9,25 +9,97 @@
|
|
|
9
9
|
* {@link BOUND_TARGET_SELECTOR}.
|
|
10
10
|
*/
|
|
11
11
|
import { CommandError } from '../../errors/index.js';
|
|
12
|
-
import { staleNodeError } from '../../errors/messages.js';
|
|
12
|
+
import { emptySelectorError, indexOutOfRangeError, noNodesFoundError, staleNodeError, } from '../../errors/messages.js';
|
|
13
|
+
import { frameScopedConnection } from './frameScopedConnection.js';
|
|
14
|
+
import { createLogger } from '../../ui/logging/index.js';
|
|
15
|
+
import { getErrorMessage } from '../../utils/errors.js';
|
|
13
16
|
import { EXIT_CODES } from '../../utils/exitCodes.js';
|
|
14
|
-
import { parseSelectorFilters } from '../../utils/selectorFilters.js';
|
|
17
|
+
import { parseSelectorFilters, } from '../../utils/selectorFilters.js';
|
|
15
18
|
/** Selector placeholder that makes page scripts use the bound node. */
|
|
16
19
|
export const BOUND_TARGET_SELECTOR = '__bdg_bound_target__';
|
|
20
|
+
/** Removes the node bound for index-based commands from the window it was stored on */
|
|
21
|
+
export const UNBIND_TARGET_SCRIPT = 'delete window.__bdgTarget';
|
|
22
|
+
const log = createLogger('dom');
|
|
17
23
|
/**
|
|
18
|
-
* Page-side
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
24
|
+
* Page-side matching of filters ({@link SelectorFilter}) and scoped steps
|
|
25
|
+
* ({@link ScopedStep}): called with the open shadow roots of the page, returns
|
|
26
|
+
* `{ passesAll, descend }`.
|
|
27
|
+
*
|
|
28
|
+
* Visible means rendered (`checkVisibility()`: not inside a closed
|
|
29
|
+
* `<details>` or under `content-visibility: hidden`), a non-empty box and
|
|
30
|
+
* `visibility: visible`, as in Playwright (`opacity: 0` still counts as
|
|
31
|
+
* visible). Text is the rendered text (`innerText`) of visible elements and
|
|
32
|
+
* the text nodes of hidden ones (display or visibility; not those of
|
|
33
|
+
* `<script>`, `<style>` or `<noscript>`), so text filters match hidden
|
|
34
|
+
* elements like Playwright's and `:visible` decides visibility; button inputs
|
|
35
|
+
* use their value. Whitespace is collapsed; filter texts arrive normalized
|
|
36
|
+
* (`has-text` lowercased). `:visible` is checked before the text filters,
|
|
37
|
+
* which read the text.
|
|
38
|
+
*
|
|
39
|
+
* Steps and `:has()` match CSS relative to an element (`:scope > css`). A
|
|
40
|
+
* descendant step also searches the open shadow roots under the element (the
|
|
41
|
+
* whole step CSS inside one shadow tree); a child step stays in the
|
|
42
|
+
* element's own tree.
|
|
23
43
|
*/
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
44
|
+
export const FILTER_MATCHING_JS = `(shadowRoots) => {
|
|
45
|
+
const skipped = /^(script|style|noscript|template)$/;
|
|
46
|
+
const hiddenText = (el) => {
|
|
47
|
+
const walker = el.ownerDocument.createTreeWalker(el, 5, {
|
|
48
|
+
acceptNode: (node) => (node.nodeType === 1 && skipped.test(node.localName) ? 2 : 1)
|
|
49
|
+
});
|
|
50
|
+
let text = '';
|
|
51
|
+
for (let node = walker.nextNode(); node; node = walker.nextNode()) {
|
|
52
|
+
if (node.nodeType === 3) text += node.data;
|
|
53
|
+
}
|
|
54
|
+
return text;
|
|
55
|
+
};
|
|
56
|
+
const textOf = (el) => {
|
|
57
|
+
if (el.localName === 'input' && /^(submit|button|reset)$/i.test(el.type)) return el.value;
|
|
58
|
+
const rendered = typeof el.innerText === 'string' &&
|
|
59
|
+
(typeof el.checkVisibility !== 'function' || el.checkVisibility({ visibilityProperty: true }));
|
|
60
|
+
return (rendered ? el.innerText : hiddenText(el)).replace(/\\s+/g, ' ').trim();
|
|
61
|
+
};
|
|
62
|
+
const passes = (el, filter) => {
|
|
63
|
+
if (filter.kind === 'visible') {
|
|
64
|
+
if (typeof el.checkVisibility === 'function' && !el.checkVisibility()) return false;
|
|
65
|
+
const rect = el.getBoundingClientRect();
|
|
66
|
+
return rect.width > 0 && rect.height > 0 && el.ownerDocument.defaultView.getComputedStyle(el).visibility === 'visible';
|
|
67
|
+
}
|
|
68
|
+
if (filter.kind === 'has') return filter.selectors.some((chain) => descend(el, chain).length > 0);
|
|
69
|
+
const text = textOf(el);
|
|
70
|
+
return filter.kind === 'text-is' ? text === filter.text : text.toLowerCase().includes(filter.text);
|
|
71
|
+
};
|
|
72
|
+
const passesAll = (el, filters) =>
|
|
73
|
+
filters.every((filter) => filter.kind !== 'visible' || passes(el, filter)) &&
|
|
74
|
+
filters.every((filter) => filter.kind === 'visible' || passes(el, filter));
|
|
75
|
+
const isUnder = (scope, node) => {
|
|
76
|
+
for (let current = node; current; current = current.getRootNode().host) {
|
|
77
|
+
if (scope === current || scope.contains(current)) return true;
|
|
78
|
+
}
|
|
79
|
+
return false;
|
|
80
|
+
};
|
|
81
|
+
const stepMatches = (scope, step) => {
|
|
82
|
+
const matches = [...scope.querySelectorAll(':scope ' + step.combinator + ' ' + step.css)];
|
|
83
|
+
if (step.combinator !== ' ') return matches;
|
|
84
|
+
for (const root of shadowRoots) {
|
|
85
|
+
if (isUnder(scope, root.host)) matches.push(...root.querySelectorAll(step.css));
|
|
86
|
+
}
|
|
87
|
+
return matches;
|
|
88
|
+
};
|
|
89
|
+
const descend = (scope, steps) => {
|
|
90
|
+
let current = [scope];
|
|
91
|
+
for (const step of steps) {
|
|
92
|
+
const next = new Set();
|
|
93
|
+
for (const el of current) {
|
|
94
|
+
for (const match of stepMatches(el, step)) {
|
|
95
|
+
if (passesAll(match, step.filters)) next.add(match);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
current = [...next];
|
|
99
|
+
}
|
|
100
|
+
return current;
|
|
101
|
+
};
|
|
102
|
+
return { passesAll: passesAll, descend: descend };
|
|
31
103
|
}`;
|
|
32
104
|
/**
|
|
33
105
|
* Page-side selector search that also reaches into open shadow roots and
|
|
@@ -35,15 +107,19 @@ const FILTER_MATCHES_JS = `(el, filter) => {
|
|
|
35
107
|
*
|
|
36
108
|
* Takes the selector and, when it has text or visibility filters, its parts
|
|
37
109
|
* from {@link parseSelectorFilters}: their CSS runs as one selector list and
|
|
38
|
-
* a match is kept when it passes the filters of a part it matches
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
110
|
+
* a match is kept when it passes the filters of a part it matches; a part
|
|
111
|
+
* with scoped steps contributes the steps' matches under it instead (each
|
|
112
|
+
* element once). Matches come in document order: those of the document
|
|
113
|
+
* first, then those of each shadow root and frame in the order they are
|
|
114
|
+
* reached. Cross-origin iframes are separate processes and cannot be
|
|
115
|
+
* searched. Throws a `SyntaxError` for an invalid selector.
|
|
42
116
|
*/
|
|
43
117
|
export const DEEP_QUERY_JS = `function (selector, parts) {
|
|
44
118
|
const css = parts ? parts.map((part) => part.css).join(', ') : selector;
|
|
45
119
|
const found = [];
|
|
120
|
+
const roots = [];
|
|
46
121
|
const visit = (root) => {
|
|
122
|
+
roots.push(root);
|
|
47
123
|
for (const match of root.querySelectorAll(css)) found.push(match);
|
|
48
124
|
for (const el of root.querySelectorAll('*')) {
|
|
49
125
|
if (el.shadowRoot) visit(el.shadowRoot);
|
|
@@ -56,8 +132,20 @@ export const DEEP_QUERY_JS = `function (selector, parts) {
|
|
|
56
132
|
};
|
|
57
133
|
visit(document);
|
|
58
134
|
if (!parts) return found;
|
|
59
|
-
const
|
|
60
|
-
|
|
135
|
+
const shadowRoots = roots.filter((root) => root.host);
|
|
136
|
+
const { passesAll, descend } = (${FILTER_MATCHING_JS})(shadowRoots);
|
|
137
|
+
const result = new Set();
|
|
138
|
+
for (const el of found) {
|
|
139
|
+
for (const part of parts) {
|
|
140
|
+
if (!el.matches(part.css) || !passesAll(el, part.filters)) continue;
|
|
141
|
+
for (const match of descend(el, part.steps || [])) result.add(match);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
const rootOrder = new Map(roots.map((root, i) => [root, i]));
|
|
145
|
+
const rootIndex = (el) => rootOrder.get(el.getRootNode()) ?? roots.length;
|
|
146
|
+
return [...result].sort((a, b) =>
|
|
147
|
+
rootIndex(a) - rootIndex(b) || (a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1)
|
|
148
|
+
);
|
|
61
149
|
}`;
|
|
62
150
|
/**
|
|
63
151
|
* Page-side element lookup shared by the interaction scripts.
|
|
@@ -72,6 +160,45 @@ export const FIND_ELEMENTS_JS = `function (selector, parts) {
|
|
|
72
160
|
}
|
|
73
161
|
return (${DEEP_QUERY_JS})(selector, parts);
|
|
74
162
|
}`;
|
|
163
|
+
/**
|
|
164
|
+
* Page-side check of what selectors cannot search in the top document:
|
|
165
|
+
* iframes whose document cannot be read (cross-origin) and `<object>`/`<embed>`
|
|
166
|
+
* elements. Cheap: no frame is entered.
|
|
167
|
+
*/
|
|
168
|
+
export const UNSEARCHED_CONTENT_JS = `(() => {
|
|
169
|
+
const frames = Array.from(document.querySelectorAll('iframe, frame'));
|
|
170
|
+
const unreadable = (frame) => {
|
|
171
|
+
try { return !frame.contentDocument; } catch (e) { return true; }
|
|
172
|
+
};
|
|
173
|
+
return { crossOriginFrames: frames.some(unreadable), embeds: !!document.querySelector('object, embed') };
|
|
174
|
+
})()`;
|
|
175
|
+
/** Elements whose ids or classes are read for "did you mean" (bounds the work on huge pages) */
|
|
176
|
+
const NAME_SCAN_LIMIT = 5000;
|
|
177
|
+
/**
|
|
178
|
+
* Page-side ids (`kind` "id") or classes (`kind` "class") in the top
|
|
179
|
+
* document, from the first {@link NAME_SCAN_LIMIT} elements that have one.
|
|
180
|
+
*
|
|
181
|
+
* @param kind - Which names to read
|
|
182
|
+
* @returns Expression evaluating to the distinct names
|
|
183
|
+
*/
|
|
184
|
+
export function pageNamesJS(kind) {
|
|
185
|
+
return `(() => {
|
|
186
|
+
const names = new Set();
|
|
187
|
+
const elements = document.querySelectorAll('[${kind}]');
|
|
188
|
+
for (let i = 0; i < elements.length && i < ${NAME_SCAN_LIMIT}; i++) {
|
|
189
|
+
const el = elements[i];
|
|
190
|
+
${kind === 'id' ? 'if (el.id) names.add(el.id);' : 'for (const name of el.classList) names.add(name);'}
|
|
191
|
+
}
|
|
192
|
+
return Array.from(names);
|
|
193
|
+
})()`;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Page-side: the form control a `<label>` stands for (`label.control`: its
|
|
197
|
+
* `for` target or the control inside it), or null for other elements and
|
|
198
|
+
* labels without one. Fill, click and key presses act on that control, like
|
|
199
|
+
* Playwright.
|
|
200
|
+
*/
|
|
201
|
+
export const LABEL_CONTROL_JS = `(el) => (el && el.localName === 'label' ? el.control : null)`;
|
|
75
202
|
/**
|
|
76
203
|
* Arguments for {@link DEEP_QUERY_JS} / {@link FIND_ELEMENTS_JS} and the page
|
|
77
204
|
* scripts built on them: the selector as a JS string literal and its parts
|
|
@@ -79,29 +206,36 @@ export const FIND_ELEMENTS_JS = `function (selector, parts) {
|
|
|
79
206
|
*
|
|
80
207
|
* @param selector - Selector as the user gave it (or the bound-node placeholder)
|
|
81
208
|
* @returns JS source of the two arguments, e.g. `"li:visible", [{...}]`
|
|
82
|
-
* @throws CommandError (81) for a misplaced or malformed filter
|
|
209
|
+
* @throws CommandError (81) for an empty selector or a misplaced or malformed filter
|
|
83
210
|
*/
|
|
84
211
|
export function selectorArgsJS(selector) {
|
|
212
|
+
if (selector.trim() === '') {
|
|
213
|
+
const err = emptySelectorError();
|
|
214
|
+
throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.INVALID_ARGUMENTS);
|
|
215
|
+
}
|
|
85
216
|
return `${JSON.stringify(selector)}, ${JSON.stringify(parseSelectorFilters(selector))}`;
|
|
86
217
|
}
|
|
87
218
|
/**
|
|
88
219
|
* Store the node for the page scripts. It runs in the node's own frame, while
|
|
89
|
-
* the scripts run in the top page, so the node goes on the top window
|
|
90
|
-
*
|
|
220
|
+
* the scripts run in the top page, so the node goes on the top window, which
|
|
221
|
+
* same-origin frames can reach. A cross-origin frame cannot write to the top
|
|
222
|
+
* window: the node then goes on its own window (`"frame"`) and the scripts
|
|
223
|
+
* run in that frame ({@link frameScopedConnection}).
|
|
91
224
|
*/
|
|
92
225
|
const BIND_FUNCTION = `function () {
|
|
93
|
-
|
|
94
|
-
try {
|
|
95
|
-
return this.isConnected;
|
|
226
|
+
if (!this.isConnected) return false;
|
|
227
|
+
try { window.top.__bdgTarget = this; return 'top'; } catch (e) { window.__bdgTarget = this; return 'frame'; }
|
|
96
228
|
}`;
|
|
97
229
|
/**
|
|
98
230
|
* Make the node with `backendNodeId` the target of the next page script.
|
|
99
231
|
*
|
|
100
232
|
* @param cdp - CDP connection
|
|
101
233
|
* @param backendNodeId - Backend node id from the query cache
|
|
234
|
+
* @returns Connection for the page scripts: the session's, or one running them
|
|
235
|
+
* in the node's frame when that frame is cross-origin
|
|
102
236
|
* @throws CommandError (exit 87) when the node no longer exists in the page
|
|
103
237
|
*/
|
|
104
|
-
|
|
238
|
+
async function bindTargetNode(cdp, backendNodeId) {
|
|
105
239
|
const err = staleNodeError();
|
|
106
240
|
const stale = new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.STALE_CACHE);
|
|
107
241
|
let objectId;
|
|
@@ -119,25 +253,88 @@ export async function bindTargetNode(cdp, backendNodeId) {
|
|
|
119
253
|
functionDeclaration: BIND_FUNCTION,
|
|
120
254
|
returnByValue: true,
|
|
121
255
|
}));
|
|
122
|
-
|
|
256
|
+
const scope = bound.result?.value;
|
|
257
|
+
if (scope === 'frame')
|
|
258
|
+
return frameScopedConnection(cdp, objectId);
|
|
259
|
+
if (scope !== 'top')
|
|
123
260
|
throw stale;
|
|
261
|
+
return cdp;
|
|
124
262
|
}
|
|
125
263
|
/**
|
|
126
264
|
* Resolve what a page script should target for an interaction request.
|
|
127
265
|
*
|
|
128
266
|
* @param cdp - CDP connection
|
|
129
267
|
* @param params - Request with a selector (and optional index) or a backend node id
|
|
130
|
-
* @returns Selector/index for the page script
|
|
268
|
+
* @returns Selector/index for the page script and the connection to run it on
|
|
131
269
|
*/
|
|
132
|
-
|
|
270
|
+
async function resolveScriptTarget(cdp, params) {
|
|
133
271
|
if (params.backendNodeId === undefined) {
|
|
134
272
|
return {
|
|
135
273
|
selector: params.selector ?? '',
|
|
136
274
|
...(params.index !== undefined && { index: params.index }),
|
|
275
|
+
cdp,
|
|
137
276
|
};
|
|
138
277
|
}
|
|
139
|
-
await bindTargetNode(cdp, params.backendNodeId);
|
|
140
|
-
|
|
278
|
+
return { selector: BOUND_TARGET_SELECTOR, cdp: await bindTargetNode(cdp, params.backendNodeId) };
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Whether an error is about the bound node: the placeholder only appears in
|
|
282
|
+
* "not found" messages, which for a bound node mean it left the page.
|
|
283
|
+
*
|
|
284
|
+
* @param text - Error message
|
|
285
|
+
* @returns True when the bound node was not found
|
|
286
|
+
*/
|
|
287
|
+
function boundNodeMissing(text) {
|
|
288
|
+
return text?.includes(BOUND_TARGET_SELECTOR) === true;
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Run an interaction on the element a request targets. Results and errors
|
|
292
|
+
* never show the internal placeholder: a bound node the page scripts could
|
|
293
|
+
* not find is reported as stale (87), and results carry the user's selector.
|
|
294
|
+
*
|
|
295
|
+
* @param cdp - CDP connection
|
|
296
|
+
* @param params - Request with a selector (and optional index) or a backend node id
|
|
297
|
+
* @param work - The interaction, given the script target
|
|
298
|
+
* @returns The interaction's result
|
|
299
|
+
* @throws CommandError (87) when the bound node left the page during the action
|
|
300
|
+
*/
|
|
301
|
+
export async function onScriptTarget(cdp, params, work) {
|
|
302
|
+
const err = staleNodeError();
|
|
303
|
+
let target;
|
|
304
|
+
try {
|
|
305
|
+
target = await resolveScriptTarget(cdp, params);
|
|
306
|
+
const result = withUserSelector(await work(target), params.selector);
|
|
307
|
+
if (!boundNodeMissing(result.error))
|
|
308
|
+
return result;
|
|
309
|
+
return {
|
|
310
|
+
...result,
|
|
311
|
+
error: err.message,
|
|
312
|
+
suggestion: err.suggestion,
|
|
313
|
+
exitCode: EXIT_CODES.STALE_CACHE,
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
catch (error) {
|
|
317
|
+
if (!(error instanceof CommandError) || !boundNodeMissing(error.message))
|
|
318
|
+
throw error;
|
|
319
|
+
throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.STALE_CACHE);
|
|
320
|
+
}
|
|
321
|
+
finally {
|
|
322
|
+
if (target && target.cdp !== cdp)
|
|
323
|
+
unbindInFrame(target.cdp);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Remove a node bound in a cross-origin frame from that frame's window
|
|
328
|
+
* (through the scoped connection, so the script runs where the bind ran; the
|
|
329
|
+
* session removes the one bound on the top window after every interaction).
|
|
330
|
+
* Not waited for.
|
|
331
|
+
*
|
|
332
|
+
* @param frameConnection - Connection scoped to the node's frame
|
|
333
|
+
*/
|
|
334
|
+
function unbindInFrame(frameConnection) {
|
|
335
|
+
void frameConnection
|
|
336
|
+
.send('Runtime.evaluate', { expression: UNBIND_TARGET_SCRIPT })
|
|
337
|
+
.catch((error) => log.debug(`Frame target not unbound: ${getErrorMessage(error)}`));
|
|
141
338
|
}
|
|
142
339
|
/**
|
|
143
340
|
* Report the user's selector instead of the internal placeholder.
|
|
@@ -146,9 +343,28 @@ export async function resolveScriptTarget(cdp, params) {
|
|
|
146
343
|
* @param selector - Selector the user gave (or the cached query's selector)
|
|
147
344
|
* @returns Result with the placeholder replaced
|
|
148
345
|
*/
|
|
149
|
-
|
|
346
|
+
function withUserSelector(result, selector) {
|
|
150
347
|
if (result.selector !== BOUND_TARGET_SELECTOR)
|
|
151
348
|
return result;
|
|
152
349
|
return { ...result, ...(selector !== undefined && { selector }) };
|
|
153
350
|
}
|
|
351
|
+
/**
|
|
352
|
+
* Error for an element a command could not find.
|
|
353
|
+
*
|
|
354
|
+
* @param target - Selector (and index) or backend node id the command was given
|
|
355
|
+
* @param matchCount - Elements the selector matched
|
|
356
|
+
* @returns Stale (87), out of range (81) or not found (83) error
|
|
357
|
+
*/
|
|
358
|
+
export function missingElementError(target, matchCount) {
|
|
359
|
+
if (target.backendNodeId !== undefined) {
|
|
360
|
+
const err = staleNodeError();
|
|
361
|
+
return new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.STALE_CACHE);
|
|
362
|
+
}
|
|
363
|
+
if (matchCount > 0) {
|
|
364
|
+
const err = indexOutOfRangeError(target.index ?? 0, matchCount - 1);
|
|
365
|
+
return new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.INVALID_ARGUMENTS);
|
|
366
|
+
}
|
|
367
|
+
const err = noNodesFoundError(target.selector);
|
|
368
|
+
return new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.RESOURCE_NOT_FOUND);
|
|
369
|
+
}
|
|
154
370
|
//# sourceMappingURL=targetNode.js.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `bdg dom wait`: wait in the page until elements appear, become visible,
|
|
3
|
+
* contain a text or are gone, and/or the page has loaded.
|
|
4
|
+
*
|
|
5
|
+
* The page watches itself (a MutationObserver plus a short poll for style
|
|
6
|
+
* and frame changes the observer cannot see) and answers as soon as what it
|
|
7
|
+
* shows changes; the condition is decided here ({@link isWaitConditionMet}).
|
|
8
|
+
* Each page call lasts at most {@link WAIT_SLICE_MS}, so a navigation during
|
|
9
|
+
* the wait (the page's context is destroyed) only costs a retry on the new
|
|
10
|
+
* document. `--gone` is confirmed by a second snapshot of the same settled
|
|
11
|
+
* document, so the empty document right after a navigation does not count.
|
|
12
|
+
*/
|
|
13
|
+
import type { DomWaitCommand, DomWaitData } from '../../ipc/protocol/commands.js';
|
|
14
|
+
import type { CDPSender } from '../../telemetry/objectExpander.js';
|
|
15
|
+
/**
|
|
16
|
+
* Wait until the page meets the condition.
|
|
17
|
+
*
|
|
18
|
+
* @param cdp - CDP connection
|
|
19
|
+
* @param params - Condition and timeout
|
|
20
|
+
* @returns What the page showed when it was met, and how long it took
|
|
21
|
+
* @throws CommandError (81) for an invalid selector, (102) when the timeout passed;
|
|
22
|
+
* CDP errors other than a lost page context (a closed connection) as they are
|
|
23
|
+
*/
|
|
24
|
+
export declare function waitForCondition(cdp: CDPSender, params: DomWaitCommand): Promise<DomWaitData>;
|
|
25
|
+
//# sourceMappingURL=wait.d.ts.map
|