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.
Files changed (251) hide show
  1. package/README.md +4 -1
  2. package/dist/cdp/schema.d.ts +4 -1
  3. package/dist/cdp/schema.js +48 -7
  4. package/dist/commands/cdp.js +3 -2
  5. package/dist/commands/cleanup.d.ts +11 -0
  6. package/dist/commands/cleanup.js +161 -57
  7. package/dist/commands/console.d.ts +20 -1
  8. package/dist/commands/console.js +57 -17
  9. package/dist/commands/details.js +3 -2
  10. package/dist/commands/dom/DomElementResolver.d.ts +10 -3
  11. package/dist/commands/dom/DomElementResolver.js +35 -17
  12. package/dist/commands/dom/a11y.d.ts +10 -0
  13. package/dist/commands/dom/a11y.js +27 -5
  14. package/dist/commands/dom/eval.d.ts +3 -1
  15. package/dist/commands/dom/eval.js +29 -4
  16. package/dist/commands/dom/form.js +16 -62
  17. package/dist/commands/dom/formInteraction.js +152 -113
  18. package/dist/commands/dom/formSummary.d.ts +49 -0
  19. package/dist/commands/dom/formSummary.js +180 -0
  20. package/dist/commands/dom/frames.d.ts +2 -1
  21. package/dist/commands/dom/frames.js +17 -2
  22. package/dist/commands/dom/get.d.ts +6 -5
  23. package/dist/commands/dom/get.js +92 -82
  24. package/dist/commands/dom/helpers/index.d.ts +1 -1
  25. package/dist/commands/dom/helpers/index.js +1 -1
  26. package/dist/commands/dom/helpers/query.d.ts +44 -17
  27. package/dist/commands/dom/helpers/query.js +244 -97
  28. package/dist/commands/dom/helpers/runElementCommand.d.ts +10 -2
  29. package/dist/commands/dom/helpers/runElementCommand.js +97 -30
  30. package/dist/commands/dom/helpers/screenshot.d.ts +4 -1
  31. package/dist/commands/dom/helpers/screenshot.js +164 -49
  32. package/dist/commands/dom/index.d.ts +3 -1
  33. package/dist/commands/dom/index.js +16 -6
  34. package/dist/commands/dom/layout.d.ts +14 -0
  35. package/dist/commands/dom/layout.js +54 -0
  36. package/dist/commands/dom/listeners.d.ts +5 -1
  37. package/dist/commands/dom/listeners.js +13 -3
  38. package/dist/commands/dom/query.js +2 -3
  39. package/dist/commands/dom/screenshot.d.ts +12 -2
  40. package/dist/commands/dom/screenshot.js +27 -3
  41. package/dist/commands/dom/semanticUtils.d.ts +6 -13
  42. package/dist/commands/dom/semanticUtils.js +15 -19
  43. package/dist/commands/dom/wait.d.ts +13 -0
  44. package/dist/commands/dom/wait.js +83 -0
  45. package/dist/commands/helpJson.js +2 -2
  46. package/dist/commands/network/list.js +4 -11
  47. package/dist/commands/optionBehaviors.js +112 -21
  48. package/dist/commands/page.d.ts +2 -1
  49. package/dist/commands/page.js +41 -5
  50. package/dist/commands/peek.js +4 -11
  51. package/dist/commands/sessions.d.ts +8 -0
  52. package/dist/commands/sessions.js +19 -0
  53. package/dist/commands/shared/CommandRunner.js +4 -4
  54. package/dist/commands/shared/dataFetcher.js +2 -2
  55. package/dist/commands/shared/followMode.d.ts +21 -1
  56. package/dist/commands/shared/followMode.js +29 -2
  57. package/dist/commands/shared/handleValidationError.d.ts +2 -2
  58. package/dist/commands/shared/handleValidationError.js +12 -3
  59. package/dist/commands/shared/optionTypes.d.ts +40 -5
  60. package/dist/commands/shared/startHelpers.js +12 -3
  61. package/dist/commands/shared/validation.d.ts +3 -2
  62. package/dist/commands/shared/validation.js +4 -3
  63. package/dist/commands/start.d.ts +63 -0
  64. package/dist/commands/start.js +115 -15
  65. package/dist/commands/status.js +29 -7
  66. package/dist/commands/stop.js +7 -6
  67. package/dist/commands/tail.js +4 -11
  68. package/dist/commands/types.d.ts +2 -0
  69. package/dist/commands.js +2 -0
  70. package/dist/connection/chromeIdentity.d.ts +65 -0
  71. package/dist/connection/chromeIdentity.js +143 -0
  72. package/dist/connection/launcher/profilePreferences.d.ts +47 -0
  73. package/dist/connection/launcher/profilePreferences.js +151 -0
  74. package/dist/connection/launcher.d.ts +21 -2
  75. package/dist/connection/launcher.js +42 -16
  76. package/dist/connection/portReservation.d.ts +14 -4
  77. package/dist/connection/portReservation.js +21 -6
  78. package/dist/connection/startupExit.d.ts +8 -0
  79. package/dist/connection/startupExit.js +15 -6
  80. package/dist/constants.d.ts +6 -2
  81. package/dist/constants.js +9 -2
  82. package/dist/daemon/SessionController.js +23 -7
  83. package/dist/daemon/errors.d.ts +1 -1
  84. package/dist/daemon/errors.js +1 -1
  85. package/dist/daemon/launcher.d.ts +2 -1
  86. package/dist/daemon/launcher.js +5 -6
  87. package/dist/daemon/server/SocketServer.js +1 -2
  88. package/dist/daemon/session/Session.d.ts +13 -0
  89. package/dist/daemon/session/Session.js +57 -8
  90. package/dist/daemon/session/chromeConnection.d.ts +9 -0
  91. package/dist/daemon/session/chromeConnection.js +45 -8
  92. package/dist/daemon/session/commandRegistry.js +52 -62
  93. package/dist/daemon/session/interactions.d.ts +35 -9
  94. package/dist/daemon/session/interactions.js +36 -9
  95. package/dist/daemon/session/triggeredRequests.d.ts +67 -0
  96. package/dist/daemon/session/triggeredRequests.js +157 -0
  97. package/dist/daemon/session/types.d.ts +5 -1
  98. package/dist/daemon.js +5393 -1600
  99. package/dist/errors/messages.d.ts +387 -24
  100. package/dist/errors/messages.js +761 -67
  101. package/dist/index.js +3976 -1558
  102. package/dist/ipc/client.d.ts +12 -1
  103. package/dist/ipc/client.js +22 -3
  104. package/dist/ipc/protocol/commands.d.ts +89 -4
  105. package/dist/ipc/protocol/commands.js +2 -0
  106. package/dist/ipc/protocol/domTypes.d.ts +258 -7
  107. package/dist/ipc/session/lifecycle.d.ts +8 -1
  108. package/dist/ipc/session/queries.d.ts +5 -1
  109. package/dist/ipc/session/types.d.ts +5 -0
  110. package/dist/ipc/transport/index.d.ts +2 -1
  111. package/dist/ipc/transport/index.js +2 -2
  112. package/dist/runtime/dom/actionEffects.d.ts +106 -0
  113. package/dist/runtime/dom/actionEffects.js +256 -0
  114. package/dist/runtime/dom/actionEffectsScripts.d.ts +52 -0
  115. package/dist/runtime/dom/actionEffectsScripts.js +234 -0
  116. package/dist/runtime/dom/elementGeometry.d.ts +170 -0
  117. package/dist/runtime/dom/elementGeometry.js +553 -0
  118. package/dist/runtime/dom/elementInfo.d.ts +77 -0
  119. package/dist/runtime/dom/elementInfo.js +191 -0
  120. package/dist/runtime/dom/evalHelpers.d.ts +51 -6
  121. package/dist/runtime/dom/evalHelpers.js +136 -26
  122. package/dist/runtime/dom/eventListeners.d.ts +2 -1
  123. package/dist/runtime/dom/eventListeners.js +174 -47
  124. package/dist/runtime/dom/formDiscovery.d.ts +1 -1
  125. package/dist/runtime/dom/formDiscovery.js +116 -16
  126. package/dist/runtime/dom/formFillHelpers/fill.d.ts +10 -0
  127. package/dist/runtime/dom/formFillHelpers/fill.js +125 -10
  128. package/dist/runtime/dom/formFillHelpers/index.d.ts +2 -2
  129. package/dist/runtime/dom/formFillHelpers/index.js +2 -2
  130. package/dist/runtime/dom/formFillHelpers/pressKey.js +14 -3
  131. package/dist/runtime/dom/formFillHelpers/scroll.d.ts +3 -0
  132. package/dist/runtime/dom/formFillHelpers/scroll.js +60 -18
  133. package/dist/runtime/dom/formFillHelpers/shared.d.ts +17 -0
  134. package/dist/runtime/dom/formFillHelpers/shared.js +25 -1
  135. package/dist/runtime/dom/formFillHelpers/stability.d.ts +20 -6
  136. package/dist/runtime/dom/formFillHelpers/stability.js +50 -19
  137. package/dist/runtime/dom/formSubmitHelpers.d.ts +3 -0
  138. package/dist/runtime/dom/formSubmitHelpers.js +89 -15
  139. package/dist/runtime/dom/frameLayout.d.ts +60 -0
  140. package/dist/runtime/dom/frameLayout.js +140 -0
  141. package/dist/runtime/dom/frameOrigin.d.ts +50 -0
  142. package/dist/runtime/dom/frameOrigin.js +62 -0
  143. package/dist/runtime/dom/frameScopedConnection.d.ts +92 -0
  144. package/dist/runtime/dom/frameScopedConnection.js +252 -0
  145. package/dist/runtime/dom/frameSelection.d.ts +1 -1
  146. package/dist/runtime/dom/frameSelection.js +2 -2
  147. package/dist/runtime/dom/frames.d.ts +25 -2
  148. package/dist/runtime/dom/frames.js +202 -63
  149. package/dist/runtime/dom/layout.d.ts +67 -0
  150. package/dist/runtime/dom/layout.js +333 -0
  151. package/dist/runtime/dom/listenerPageScripts.d.ts +66 -0
  152. package/dist/runtime/dom/listenerPageScripts.js +279 -0
  153. package/dist/runtime/dom/listenerSummary.d.ts +132 -11
  154. package/dist/runtime/dom/listenerSummary.js +344 -22
  155. package/dist/runtime/dom/pageActivity.d.ts +41 -0
  156. package/dist/runtime/dom/pageActivity.js +123 -0
  157. package/dist/runtime/dom/reactEventHelpers.d.ts +58 -2
  158. package/dist/runtime/dom/reactEventHelpers.js +212 -41
  159. package/dist/runtime/dom/targetNode.d.ts +80 -27
  160. package/dist/runtime/dom/targetNode.js +249 -33
  161. package/dist/runtime/dom/wait.d.ts +25 -0
  162. package/dist/runtime/dom/wait.js +199 -0
  163. package/dist/runtime/dom/waitCondition.d.ts +71 -0
  164. package/dist/runtime/dom/waitCondition.js +75 -0
  165. package/dist/runtime/page/emulation.d.ts +51 -0
  166. package/dist/runtime/page/emulation.js +80 -0
  167. package/dist/runtime/page/loadingState.d.ts +36 -0
  168. package/dist/runtime/page/loadingState.js +86 -0
  169. package/dist/runtime/page/navigation.d.ts +46 -2
  170. package/dist/runtime/page/navigation.js +69 -33
  171. package/dist/session/QueryCacheManager.d.ts +11 -1
  172. package/dist/session/QueryCacheManager.js +25 -3
  173. package/dist/session/chromeOwners.d.ts +34 -0
  174. package/dist/session/chromeOwners.js +51 -0
  175. package/dist/session/cleanup/staleSession.d.ts +11 -1
  176. package/dist/session/cleanup/staleSession.js +17 -6
  177. package/dist/session/cleanup/userCommands.js +2 -4
  178. package/dist/session/metadata.d.ts +5 -1
  179. package/dist/session/metadata.js +2 -1
  180. package/dist/session/paths.d.ts +77 -3
  181. package/dist/session/paths.js +111 -5
  182. package/dist/session/port.d.ts +31 -7
  183. package/dist/session/port.js +50 -43
  184. package/dist/session/portClaims.d.ts +66 -0
  185. package/dist/session/portClaims.js +284 -0
  186. package/dist/session/sessionList.d.ts +58 -0
  187. package/dist/session/sessionList.js +199 -0
  188. package/dist/session/sessionName.d.ts +46 -0
  189. package/dist/session/sessionName.js +97 -0
  190. package/dist/telemetry/a11y.d.ts +8 -3
  191. package/dist/telemetry/a11y.js +92 -28
  192. package/dist/telemetry/requestKinds.d.ts +32 -0
  193. package/dist/telemetry/requestKinds.js +61 -0
  194. package/dist/telemetry/requestState.d.ts +31 -0
  195. package/dist/telemetry/requestState.js +38 -0
  196. package/dist/types.d.ts +80 -3
  197. package/dist/ui/formatters/a11y.js +3 -0
  198. package/dist/ui/formatters/console/chronological.d.ts +8 -0
  199. package/dist/ui/formatters/console/chronological.js +17 -4
  200. package/dist/ui/formatters/console/json.js +3 -4
  201. package/dist/ui/formatters/console/shared.d.ts +12 -0
  202. package/dist/ui/formatters/console.d.ts +2 -2
  203. package/dist/ui/formatters/console.js +1 -1
  204. package/dist/ui/formatters/details.js +2 -1
  205. package/dist/ui/formatters/dom.d.ts +26 -14
  206. package/dist/ui/formatters/dom.js +65 -52
  207. package/dist/ui/formatters/form.js +29 -18
  208. package/dist/ui/formatters/layout.d.ts +31 -0
  209. package/dist/ui/formatters/layout.js +53 -0
  210. package/dist/ui/formatters/listeners.d.ts +3 -2
  211. package/dist/ui/formatters/listeners.js +73 -9
  212. package/dist/ui/formatters/networkHeaders.js +13 -0
  213. package/dist/ui/formatters/preview.js +2 -1
  214. package/dist/ui/formatters/requestStatus.d.ts +1 -17
  215. package/dist/ui/formatters/requestStatus.js +2 -30
  216. package/dist/ui/formatters/sessions.d.ts +12 -0
  217. package/dist/ui/formatters/sessions.js +40 -0
  218. package/dist/ui/formatters/status.d.ts +21 -2
  219. package/dist/ui/formatters/status.js +47 -10
  220. package/dist/ui/formatters/triggeredRequests.d.ts +36 -0
  221. package/dist/ui/formatters/triggeredRequests.js +65 -0
  222. package/dist/ui/formatting.d.ts +10 -0
  223. package/dist/ui/formatting.js +28 -36
  224. package/dist/ui/messages/chrome.d.ts +9 -0
  225. package/dist/ui/messages/chrome.js +17 -5
  226. package/dist/ui/messages/commands.d.ts +388 -14
  227. package/dist/ui/messages/commands.js +664 -21
  228. package/dist/ui/messages/consoleMessages.d.ts +10 -0
  229. package/dist/ui/messages/consoleMessages.js +17 -0
  230. package/dist/ui/messages/hints.js +2 -1
  231. package/dist/ui/messages/preview.js +5 -4
  232. package/dist/ui/messages/session.d.ts +16 -21
  233. package/dist/ui/messages/session.js +28 -26
  234. package/dist/ui/messages/sessionCommand.d.ts +43 -0
  235. package/dist/ui/messages/sessionCommand.js +52 -0
  236. package/dist/utils/async.d.ts +8 -0
  237. package/dist/utils/async.js +19 -0
  238. package/dist/utils/http.d.ts +22 -1
  239. package/dist/utils/http.js +28 -9
  240. package/dist/utils/selectorFilters.d.ts +36 -8
  241. package/dist/utils/selectorFilters.js +267 -53
  242. package/dist/utils/shellDetection.d.ts +8 -2
  243. package/dist/utils/shellDetection.js +120 -33
  244. package/dist/utils/suggestions.d.ts +26 -0
  245. package/dist/utils/suggestions.js +73 -0
  246. package/dist/utils/taskMappings.js +10 -0
  247. package/dist/utils/url.d.ts +12 -2
  248. package/dist/utils/url.js +69 -7
  249. package/package.json +1 -1
  250. package/dist/ui/formatters/sessionFormatters.d.ts +0 -58
  251. package/dist/ui/formatters/sessionFormatters.js +0 -121
@@ -0,0 +1,191 @@
1
+ /**
2
+ * Page-side descriptions of elements for DOM command output (`dom query`,
3
+ * `dom layout`, DOM context of console messages): their text as a user sees
4
+ * it and where they live (iframes, shadow roots).
5
+ */
6
+ /** Length of the text preview shown for elements. */
7
+ const PREVIEW_LENGTH = 80;
8
+ /** Length of the text `dom get` shows for an element. */
9
+ export const ELEMENT_TEXT_LENGTH = 500;
10
+ /**
11
+ * Page-side removal of decorations from an element's text: close buttons
12
+ * (`.close`, `aria-label="Close"` or `"Dismiss"`, a button or link showing
13
+ * just `×`) and `aria-hidden` icons (text without letters or digits), so a
14
+ * flash message does not end in "×". `aria-hidden` text with words is kept:
15
+ * it is often the visible twin of a screen-reader text. Each decoration's
16
+ * text is removed once, from the end.
17
+ */
18
+ export const WITHOUT_DECORATIONS_JS = `(el, text) => {
19
+ if (!text || typeof el.querySelectorAll !== 'function') return text;
20
+ const glyph = /^\\s*[×✕✖✗⨯]\\s*$/;
21
+ const textOf = (node) => (typeof node.innerText === 'string' ? node.innerText : node.textContent || '').trim();
22
+ const closer = '.close, [aria-label="close" i], [aria-label="dismiss" i]';
23
+ const rendered = (node) => !node.checkVisibility || node.checkVisibility();
24
+ const found = Array.from(el.querySelectorAll(closer + ', [aria-hidden="true"]'))
25
+ .filter((node) => rendered(node) && (node.matches(closer) || !/[\\p{L}\\p{N}]/u.test(textOf(node))));
26
+ if (/[×✕✖✗⨯]/.test(text)) {
27
+ found.push(...Array.from(el.querySelectorAll('button, a, [role="button"]')).filter((node) => glyph.test(node.textContent || '')));
28
+ }
29
+ const unique = Array.from(new Set(found));
30
+ const outermost = unique.filter((node) => !unique.some((other) => other !== node && other.contains(node)));
31
+ let result = text;
32
+ for (const node of outermost) {
33
+ const part = textOf(node);
34
+ const at = part ? result.lastIndexOf(part) : -1;
35
+ if (at >= 0) result = result.slice(0, at) + result.slice(at + part.length);
36
+ }
37
+ return result;
38
+ }`;
39
+ /**
40
+ * Page-side text of an element as a user sees it: `innerText` for a rendered
41
+ * element (CSS-hidden parts left out, inline elements not split apart), none
42
+ * for an element that is not rendered, `textContent` for SVG and other
43
+ * elements without `innerText` and for `display: contents` wrappers (no box
44
+ * of their own, but their children are shown), and the label of an
45
+ * `<option>` (which its `<select>` renders). For large containers (more
46
+ * than 2000 characters of text) only the start is read, from the text nodes
47
+ * whose parent is rendered, so a preview never lays out a whole page's text,
48
+ * unless `full` is set. Decorations are left out ({@link WITHOUT_DECORATIONS_JS}).
49
+ */
50
+ export const ELEMENT_TEXT_JS = `(el, full) => {
51
+ const withoutDecorations = ${WITHOUT_DECORATIONS_JS};
52
+ const all = el.textContent || '';
53
+ if (el.tagName === 'OPTION') return el.label;
54
+ if (typeof el.innerText !== 'string') return full ? all : all.slice(0, 2000);
55
+ const rendered = (node) => !node.checkVisibility || node.checkVisibility();
56
+ if (!rendered(el)) {
57
+ const boxless = el.ownerDocument.defaultView.getComputedStyle(el).display === 'contents';
58
+ return boxless ? withoutDecorations(el, full ? all : all.slice(0, 2000)) : '';
59
+ }
60
+ if (full || all.length <= 2000) return withoutDecorations(el, el.innerText);
61
+ const walker = el.ownerDocument.createTreeWalker(el, NodeFilter.SHOW_TEXT);
62
+ let start = '';
63
+ while (start.length < 1000 && walker.nextNode()) {
64
+ const parent = walker.currentNode.parentElement;
65
+ if (!parent || rendered(parent)) start += walker.currentNode.data;
66
+ }
67
+ return withoutDecorations(el, start);
68
+ }`;
69
+ /**
70
+ * Page-side short description of an element: tag, id and up to two classes,
71
+ * e.g. `button#save.primary.large`.
72
+ */
73
+ export const ELEMENT_DESCRIPTION_JS = `(node) => node.tagName.toLowerCase() +
74
+ (node.id ? '#' + node.id : '') +
75
+ (node.classList && node.classList.length ? '.' + Array.from(node.classList).slice(0, 2).join('.') : '')`;
76
+ /**
77
+ * Page-side position of an element among its parent's children with the
78
+ * same short description ({@link ELEMENT_DESCRIPTION_JS}), e.g. `(2nd of 3)`;
79
+ * empty when it is the only one.
80
+ */
81
+ export const SIBLING_POSITION_JS = `(el) => {
82
+ const describe = ${ELEMENT_DESCRIPTION_JS};
83
+ const parent = el.parentElement;
84
+ if (!parent) return '';
85
+ const own = describe(el);
86
+ const same = Array.from(parent.children).filter((child) => describe(child) === own);
87
+ if (same.length < 2) return '';
88
+ const n = same.indexOf(el) + 1;
89
+ const teen = n % 100 >= 11 && n % 100 <= 13;
90
+ const suffix = teen ? 'th' : ({ 1: 'st', 2: 'nd', 3: 'rd' })[n % 10] || 'th';
91
+ return '(' + n + suffix + ' of ' + same.length + ')';
92
+ }`;
93
+ /**
94
+ * Page-side identity of an element an action hit, so the output says which
95
+ * one it was, as one string: its short description
96
+ * ({@link ELEMENT_DESCRIPTION_JS}) and visible text (button value for button
97
+ * inputs, decorations left out), e.g. `button#add.btn "Add to cart"`. A
98
+ * `<select>` is named by its label, aria-label or name, else by its selected
99
+ * option, e.g. `select.sort "Sort products"`. An element without visible
100
+ * text is described by itself first: by its position among same-looking
101
+ * siblings ({@link SIBLING_POSITION_JS}), e.g. `div.figure (2nd of 3)`, with
102
+ * its aria-label, placeholder or title when it has one. Only an element
103
+ * without an id that is the only one of its kind is named by the nearest of
104
+ * three ancestors that has text, e.g.
105
+ * `input.toggle in div.view "Write report"` (rows of a list share their
106
+ * aria-label), leaving out the options
107
+ * of `<select>`s in it (read like `innerText`: CSS-hidden text is left out of
108
+ * a rendered ancestor; at most 500 text nodes; selects in shadow roots are
109
+ * not looked into), and never past an editable ancestor, whose text may be
110
+ * typed input; otherwise its aria-label, placeholder or title is used. Texts
111
+ * are cut at 40 characters. Contenteditable elements count as controls: what
112
+ * was typed into them is never echoed (nor their ancestors' text).
113
+ */
114
+ export const ELEMENT_IDENTITY_JS = `(el) => {
115
+ const describe = ${ELEMENT_DESCRIPTION_JS};
116
+ const siblingPosition = ${SIBLING_POSITION_JS};
117
+ const withoutDecorations = ${WITHOUT_DECORATIONS_JS};
118
+ const clean = (text) => (text || '').replace(/\\s+/g, ' ').trim();
119
+ const cut = (text) => {
120
+ const characters = Array.from(text);
121
+ return characters.length > 40 ? characters.slice(0, 40).join('') + '…' : text;
122
+ };
123
+ const isControl = (node) => /^(input|select|textarea)$/.test(node.localName) || node.isContentEditable;
124
+ const shownText = (node) =>
125
+ isControl(node) ? '' : clean(withoutDecorations(node, typeof node.innerText === 'string' ? node.innerText : node.textContent));
126
+ const buttonValue = (node) => (node.localName === 'input' && /^(submit|button|reset)$/i.test(node.type) ? clean(node.value) : '');
127
+ const attributeText = (node) =>
128
+ clean(node.getAttribute('aria-label')) || clean(node.getAttribute('placeholder')) || clean(node.getAttribute('title'));
129
+ const selectName = (node) =>
130
+ clean(node.labels && node.labels[0] && node.labels[0].innerText) ||
131
+ clean(node.getAttribute('aria-label')) ||
132
+ clean(node.getAttribute('name')) ||
133
+ clean(node.selectedOptions && node.selectedOptions[0] && node.selectedOptions[0].label);
134
+ const shown = (node, options) => !node.checkVisibility || node.checkVisibility(options);
135
+ const textOutsideSelects = (node) => {
136
+ if (!node.querySelector('select')) return shownText(node);
137
+ const hiddenLeftOut = shown(node);
138
+ const walker = node.ownerDocument.createTreeWalker(node, NodeFilter.SHOW_TEXT);
139
+ let text = '';
140
+ for (let visited = 0; visited < 500 && text.length < 200 && walker.nextNode(); visited++) {
141
+ const parent = walker.currentNode.parentElement;
142
+ if (!parent || parent.closest('select, textarea')) continue;
143
+ if (!hiddenLeftOut || shown(parent, { visibilityProperty: true })) text += ' ' + walker.currentNode.data;
144
+ }
145
+ return clean(text);
146
+ };
147
+ const quoted = (text) => (text ? ' "' + cut(text) + '"' : '');
148
+ const visible = shownText(el) || buttonValue(el) || (el.localName === 'select' ? selectName(el) : '');
149
+ if (visible) return describe(el) + quoted(visible);
150
+ const attribute = attributeText(el);
151
+ const position = siblingPosition(el);
152
+ if (position) return describe(el) + ' ' + position + quoted(attribute);
153
+ let ancestor = el.id || el.isContentEditable ? null : el.parentElement;
154
+ for (let depth = 0; ancestor && depth < 3; depth++, ancestor = ancestor.parentElement) {
155
+ if (isControl(ancestor)) break;
156
+ const text = textOutsideSelects(ancestor);
157
+ if (text) return describe(el) + ' in ' + describe(ancestor) + quoted(text);
158
+ }
159
+ return describe(el) + quoted(attribute);
160
+ }`;
161
+ /**
162
+ * Page-side location of an element: the iframes it is in (outermost first)
163
+ * and the shadow root holding it, e.g. `iframe#pay > shadow root of <x-card>`;
164
+ * empty for the main document.
165
+ */
166
+ export const ELEMENT_CONTEXT_JS = `(el) => {
167
+ const describe = (node) => node.tagName.toLowerCase() + (node.id ? '#' + node.id : '');
168
+ const parts = [];
169
+ for (let doc = el.ownerDocument; doc && doc.defaultView && doc.defaultView.frameElement; ) {
170
+ const frame = doc.defaultView.frameElement;
171
+ parts.unshift(describe(frame));
172
+ doc = frame.ownerDocument;
173
+ }
174
+ const root = el.getRootNode();
175
+ if (root.host) parts.push('shadow root of <' + describe(root.host) + '>');
176
+ return parts.join(' > ');
177
+ }`;
178
+ /**
179
+ * Short text preview of an element's text: whitespace collapsed, cut on a
180
+ * whole character (an emoji is never split, which would make JSON invalid).
181
+ *
182
+ * @param text - Element text as the page renders it
183
+ * @param length - Characters kept
184
+ * @returns Collapsed text, truncated to `length` characters (followed by `...`)
185
+ */
186
+ export function textPreview(text, length = PREVIEW_LENGTH) {
187
+ const collapsed = text.replace(/\s+/g, ' ').trim();
188
+ const characters = Array.from(collapsed);
189
+ return characters.length > length ? characters.slice(0, length).join('') + '...' : collapsed;
190
+ }
191
+ //# sourceMappingURL=elementInfo.js.map
@@ -31,6 +31,25 @@ export interface EvalResult {
31
31
  * throwing getter becomes `[Error: …]`.
32
32
  */
33
33
  export declare const JSON_SAFE_COPY_FUNCTION = "function () {\n const MAX_ITEMS = 1000;\n const ancestors = new Set();\n const kind = (value) => Object.prototype.toString.call(value).slice(8, -1);\n const isNode = (value) => typeof value.nodeType === 'number' && typeof value.nodeName === 'string';\n const describeNode = (node) => {\n if (node.nodeType !== 1) return node.nodeName.toLowerCase();\n return node.tagName.toLowerCase() + (node.id ? '#' + node.id : '') +\n (node.classList && node.classList.length ? '.' + Array.from(node.classList).join('.') : '');\n };\n const items = (list, next) => {\n const result = [];\n let count = 0;\n for (const item of list) {\n if (count++ === MAX_ITEMS) { result.push('\u2026'); break; }\n result.push(next(item));\n }\n return result;\n };\n const leaf = (value) => {\n if (value === undefined) return null;\n if (typeof value === 'number') {\n if (Number.isNaN(value) || !Number.isFinite(value)) return String(value);\n return Object.is(value, -0) ? '-0' : value;\n }\n if (typeof value === 'bigint') return value + 'n';\n if (typeof value === 'symbol') return value.toString();\n if (typeof value === 'function') return 'function ' + (value.name || '(anonymous)') + '()';\n return value;\n };\n const copy = (value, depth) => {\n if (value === null || typeof value !== 'object') return leaf(value);\n if (ancestors.has(value)) return '[Circular]';\n if (depth > 20) return '[\u2026]';\n const next = (item) => copy(item, depth + 1);\n const type = kind(value);\n if (isNode(value)) return describeNode(value);\n if (value.window === value) return 'Window';\n if (type === 'Date') return isNaN(value) ? 'Invalid Date' : value.toISOString();\n if (type === 'RegExp') return String(value);\n if (type === 'Error' || value instanceof Error) return value.name + ': ' + value.message;\n ancestors.add(value);\n try {\n if (type === 'Map') return items(value, ([k, v]) => [next(k), next(v)]);\n if (type === 'Set' || type === 'NodeList' || type === 'HTMLCollection') return items(value, next);\n if (ArrayBuffer.isView(value) && type !== 'DataView') return items(value, next);\n if (Array.isArray(value)) return items(value, next);\n const result = {};\n const keys = Object.keys(value);\n for (const key of keys.slice(0, MAX_ITEMS)) {\n try { result[key] = next(value[key]); } catch (e) { result[key] = '[Error: ' + (e && e.message) + ']'; }\n }\n if (keys.length > MAX_ITEMS) result['\u2026'] = (keys.length - MAX_ITEMS) + ' more keys';\n return result;\n } finally {\n ancestors.delete(value);\n }\n };\n return copy(this, 0);\n}";
34
+ /** What a busy target is called in messages: the page, or an iframe (`--frame`) */
35
+ export type BusyScope = 'page' | 'frame';
36
+ /** When {@link withBusyPageRecovery} checks the target, and what it calls it */
37
+ export interface BusyRecoveryOptions {
38
+ /** Time after which a command that has not answered gets the target checked */
39
+ busyAfterMs?: number;
40
+ /** How long the target gets to answer the check */
41
+ livenessMs?: number;
42
+ /** The page (default) or an iframe, for messages */
43
+ scope?: BusyScope;
44
+ }
45
+ /**
46
+ * Whether Chrome failed a command because the document it ran in went away
47
+ * (navigation, a removed iframe).
48
+ *
49
+ * @param error - Error thrown by a CDP command
50
+ * @returns True for a lost context
51
+ */
52
+ export declare function isContextLostError(error: unknown): boolean;
34
53
  /**
35
54
  * Run a CDP command on the page, recovering a page kept busy by its own
36
55
  * scripts. If the command has not answered when a script would have been
@@ -41,21 +60,45 @@ export declare const JSON_SAFE_COPY_FUNCTION = "function () {\n const MAX_ITEMS
41
60
  *
42
61
  * @param cdp - CDP connection
43
62
  * @param command - The pending command
44
- * @param limits - When to check the page, and how long it gets to answer
63
+ * @param options - When to check the page, how long it gets to answer, what to call it
45
64
  * @returns The command's result
46
65
  * @throws CommandError (102) when the page was busy
47
66
  */
48
- export declare function withBusyPageRecovery<T>(cdp: CDPSender, command: Promise<T>, limits?: {
49
- busyAfterMs: number;
50
- livenessMs: number;
51
- }): Promise<T>;
67
+ export declare function withBusyPageRecovery<T>(cdp: CDPSender, command: Promise<T>, options?: BusyRecoveryOptions): Promise<T>;
68
+ /**
69
+ * The work's value if it settles within `ms` (its rejection is passed on).
70
+ *
71
+ * @param work - Pending work
72
+ * @param ms - Time limit
73
+ * @returns `{ settled: true, value }`, or `{ settled: false }` when time ran out
74
+ */
75
+ export declare function settledWithin<T>(work: Promise<T>, ms: number): Promise<{
76
+ settled: true;
77
+ value: T;
78
+ } | {
79
+ settled: false;
80
+ }>;
52
81
  /** Where a `bdg dom eval` script runs (default: the page's main frame) */
53
82
  export interface EvalTarget {
54
83
  /** Session of an attached target (out-of-process iframe) */
55
84
  sessionId?: string;
85
+ /**
86
+ * Where to check and recover the target when it does not answer (default:
87
+ * the session itself); a session attached while the target was busy gets
88
+ * no answer at all
89
+ */
90
+ recovery?: CDPSender;
56
91
  /** Execution context of a frame (`ExecutionContextDescription.uniqueId`) */
57
92
  uniqueContextId?: string;
58
93
  }
94
+ /**
95
+ * Whether the page still answers. A page that does not answer in time is
96
+ * taken as open: commands wait while a navigation is pending.
97
+ *
98
+ * @param cdp - CDP connection of the page
99
+ * @returns False when the page (tab) was closed
100
+ */
101
+ export declare function pageStillOpen(cdp: CDPSender): Promise<boolean>;
59
102
  /**
60
103
  * Evaluate a `bdg dom eval` script.
61
104
  *
@@ -70,7 +113,9 @@ export interface EvalTarget {
70
113
  * @param script - JavaScript expression
71
114
  * @param target - Session and execution context to run in (default: the page)
72
115
  * @returns Value and type
73
- * @throws CommandError (102) when the script was terminated, (91) when it threw
116
+ * @throws CommandError (102) when the script was terminated or a promise did
117
+ * not settle, (91) when it threw, (83) when the page navigated while it ran
118
+ * (in a frame, Chrome's context-lost error is passed on for the caller to explain)
74
119
  */
75
120
  export declare function evaluateScript(cdp: CDPConnection, script: string, target?: EvalTarget): Promise<EvalResult>;
76
121
  //# sourceMappingURL=evalHelpers.d.ts.map
@@ -1,5 +1,6 @@
1
+ import { CDPProtocolError } from '../../connection/errors.js';
1
2
  import { CommandError } from '../../errors/index.js';
2
- import { navigationPendingError, pageBusyError, promiseTimeoutError, scriptExecutionError, scriptTimeoutError, } from '../../errors/messages.js';
3
+ import { navigationPendingError, pageBusyError, pageClosedDuringEvalError, pageNavigatedDuringEvalError, promiseTimeoutError, scriptExecutionError, scriptTimeoutError, } from '../../errors/messages.js';
3
4
  import { pendingNavigationUrl } from '../page/navigation.js';
4
5
  import { senderFor } from '../../telemetry/objectExpander.js';
5
6
  import { formatRemoteObject } from '../../telemetry/remoteObject.js';
@@ -302,9 +303,10 @@ async function withDeadline(work, ms, onTimeout) {
302
303
  * answering because a navigation still waits for the server is left alone.
303
304
  *
304
305
  * @param cdp - CDP connection
306
+ * @param scope - Whether the target is the page or an iframe
305
307
  * @returns The timeout error to report
306
308
  */
307
- async function terminatePageScripts(cdp) {
309
+ async function terminatePageScripts(cdp, scope) {
308
310
  const pendingUrl = await pendingNavigationUrl(cdp).catch((error) => {
309
311
  log.debug(`Could not check for a pending navigation: ${getErrorMessage(error)}`);
310
312
  return undefined;
@@ -313,14 +315,50 @@ async function terminatePageScripts(cdp) {
313
315
  const err = navigationPendingError(pendingUrl);
314
316
  return new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.CDP_TIMEOUT);
315
317
  }
316
- await cdp
317
- .send('Runtime.terminateExecution')
318
- .catch((error) => log.debug(`terminateExecution failed: ${getErrorMessage(error)}`));
319
- const err = pageBusyError(EVAL_TIMEOUT_MS);
318
+ const err = pageBusyError(EVAL_TIMEOUT_MS, scope, await terminateExecution(cdp));
320
319
  return new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.CDP_TIMEOUT);
321
320
  }
321
+ /**
322
+ * Ask V8 to terminate the target's running script.
323
+ *
324
+ * @param cdp - CDP connection of the target
325
+ * @returns Whether the target acknowledged it in time
326
+ */
327
+ async function terminateExecution(cdp) {
328
+ try {
329
+ const stopped = await settledWithin(cdp.send('Runtime.terminateExecution'), LIVENESS_CHECK_MS);
330
+ return stopped.settled;
331
+ }
332
+ catch (error) {
333
+ log.debug(`terminateExecution failed: ${getErrorMessage(error)}`);
334
+ return false;
335
+ }
336
+ }
322
337
  /** How long a page gets to answer a liveness check before it counts as busy */
323
338
  const LIVENESS_CHECK_MS = 2_000;
339
+ /**
340
+ * Whether the target answers a trivial evaluation in time.
341
+ *
342
+ * @param cdp - CDP connection
343
+ * @param ms - Time limit
344
+ * @returns False when its scripts keep it busy
345
+ */
346
+ async function isAnswering(cdp, ms) {
347
+ const probe = await settledWithin(cdp.send('Runtime.evaluate', { expression: '1', returnByValue: true }), ms);
348
+ return probe.settled;
349
+ }
350
+ /** Chrome's messages for a command whose execution context or target went away */
351
+ const CONTEXT_LOST_PATTERN = /Execution context was destroyed|Inspected target navigated or closed|Cannot find context with specified id|uniqueContextId not found|Session with given id not found/i;
352
+ /**
353
+ * Whether Chrome failed a command because the document it ran in went away
354
+ * (navigation, a removed iframe).
355
+ *
356
+ * @param error - Error thrown by a CDP command
357
+ * @returns True for a lost context
358
+ */
359
+ export function isContextLostError(error) {
360
+ return error instanceof CDPProtocolError && CONTEXT_LOST_PATTERN.test(error.message);
361
+ }
324
362
  /**
325
363
  * Run a CDP command on the page, recovering a page kept busy by its own
326
364
  * scripts. If the command has not answered when a script would have been
@@ -331,19 +369,19 @@ const LIVENESS_CHECK_MS = 2_000;
331
369
  *
332
370
  * @param cdp - CDP connection
333
371
  * @param command - The pending command
334
- * @param limits - When to check the page, and how long it gets to answer
372
+ * @param options - When to check the page, how long it gets to answer, what to call it
335
373
  * @returns The command's result
336
374
  * @throws CommandError (102) when the page was busy
337
375
  */
338
- export async function withBusyPageRecovery(cdp, command, limits = { busyAfterMs: EVAL_TIMEOUT_MS + TERMINATION_GRACE_MS, livenessMs: LIVENESS_CHECK_MS }) {
376
+ export async function withBusyPageRecovery(cdp, command, options = {}) {
377
+ const { busyAfterMs = EVAL_TIMEOUT_MS + TERMINATION_GRACE_MS, livenessMs = LIVENESS_CHECK_MS, scope = 'page', } = options;
339
378
  command.catch(() => undefined);
340
- const early = await settledWithin(command, limits.busyAfterMs);
379
+ const early = await settledWithin(command, busyAfterMs);
341
380
  if (early.settled)
342
381
  return early.value;
343
- const probe = await settledWithin(cdp.send('Runtime.evaluate', { expression: '1', returnByValue: true }), limits.livenessMs);
344
- if (probe.settled)
382
+ if (await isAnswering(cdp, livenessMs))
345
383
  return command;
346
- throw await terminatePageScripts(cdp);
384
+ throw await terminatePageScripts(cdp, scope);
347
385
  }
348
386
  /**
349
387
  * The work's value if it settles within `ms` (its rejection is passed on).
@@ -352,7 +390,7 @@ export async function withBusyPageRecovery(cdp, command, limits = { busyAfterMs:
352
390
  * @param ms - Time limit
353
391
  * @returns `{ settled: true, value }`, or `{ settled: false }` when time ran out
354
392
  */
355
- async function settledWithin(work, ms) {
393
+ export async function settledWithin(work, ms) {
356
394
  let timer;
357
395
  const timeout = new Promise((resolve) => {
358
396
  timer = setTimeout(() => resolve({ settled: false }), ms);
@@ -364,6 +402,80 @@ async function settledWithin(work, ms) {
364
402
  clearTimeout(timer);
365
403
  }
366
404
  }
405
+ /**
406
+ * The error for an evaluation that did not answer in time: a target that
407
+ * still answers was only waiting for a promise that never settled
408
+ * (`await new Promise(() => {})`); one that does not answer is busy, and its
409
+ * scripts are terminated.
410
+ *
411
+ * @param cdp - CDP connection of the target
412
+ * @param scope - Whether the script ran in the page or an iframe
413
+ * @returns The error to report (102)
414
+ */
415
+ async function evaluationTimeoutError(cdp, scope) {
416
+ if (await isAnswering(cdp, LIVENESS_CHECK_MS))
417
+ return promiseTimeout();
418
+ return terminatePageScripts(cdp, scope);
419
+ }
420
+ /**
421
+ * A promise the script awaited or returned did not settle in time.
422
+ *
423
+ * @returns Command error (102)
424
+ */
425
+ function promiseTimeout() {
426
+ const err = promiseTimeoutError(EVAL_TIMEOUT_MS);
427
+ return new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.CDP_TIMEOUT);
428
+ }
429
+ /** Chrome's message for a command sent to a session (target) that is gone */
430
+ const SESSION_GONE_PATTERN = /Session with given id not found/i;
431
+ /**
432
+ * Whether the page still answers. A page that does not answer in time is
433
+ * taken as open: commands wait while a navigation is pending.
434
+ *
435
+ * @param cdp - CDP connection of the page
436
+ * @returns False when the page (tab) was closed
437
+ */
438
+ export async function pageStillOpen(cdp) {
439
+ try {
440
+ await settledWithin(cdp.send('Runtime.evaluate', { expression: '1', returnByValue: true }), LIVENESS_CHECK_MS);
441
+ return true;
442
+ }
443
+ catch (error) {
444
+ log.debug(`Page did not answer: ${getErrorMessage(error)}`);
445
+ return false;
446
+ }
447
+ }
448
+ /**
449
+ * The error for a page script whose context went away: the page navigated,
450
+ * or the tab was closed.
451
+ *
452
+ * @param error - Chrome's context-lost error
453
+ * @param cdp - CDP connection of the page
454
+ * @returns Command error (83)
455
+ */
456
+ async function pageContextLostError(error, cdp) {
457
+ const sessionGone = SESSION_GONE_PATTERN.test(getErrorMessage(error));
458
+ const err = sessionGone || !(await pageStillOpen(cdp))
459
+ ? pageClosedDuringEvalError()
460
+ : pageNavigatedDuringEvalError();
461
+ return new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.RESOURCE_NOT_FOUND);
462
+ }
463
+ /**
464
+ * `Runtime.evaluate` options of a `bdg dom eval` script.
465
+ *
466
+ * @param target - Execution context to run in
467
+ * @returns Options (REPL mode, previews, 20 s limit)
468
+ */
469
+ function evaluateOptions(target) {
470
+ return {
471
+ returnByValue: false,
472
+ generatePreview: true,
473
+ replMode: true,
474
+ objectGroup: EVAL_OBJECT_GROUP,
475
+ timeout: EVAL_TIMEOUT_MS,
476
+ ...(target.uniqueContextId && { uniqueContextId: target.uniqueContextId }),
477
+ };
478
+ }
367
479
  /**
368
480
  * Evaluate a `bdg dom eval` script.
369
481
  *
@@ -378,25 +490,23 @@ async function settledWithin(work, ms) {
378
490
  * @param script - JavaScript expression
379
491
  * @param target - Session and execution context to run in (default: the page)
380
492
  * @returns Value and type
381
- * @throws CommandError (102) when the script was terminated, (91) when it threw
493
+ * @throws CommandError (102) when the script was terminated or a promise did
494
+ * not settle, (91) when it threw, (83) when the page navigated while it ran
495
+ * (in a frame, Chrome's context-lost error is passed on for the caller to explain)
382
496
  */
383
497
  export async function evaluateScript(cdp, script, target = {}) {
384
498
  const session = senderFor(cdp, target.sessionId);
499
+ const scope = target.uniqueContextId ? 'frame' : 'page';
385
500
  try {
386
- const response = await withDeadline(executeScript(session, script, {
387
- returnByValue: false,
388
- generatePreview: true,
389
- replMode: true,
390
- objectGroup: EVAL_OBJECT_GROUP,
391
- timeout: EVAL_TIMEOUT_MS,
392
- ...(target.uniqueContextId && { uniqueContextId: target.uniqueContextId }),
393
- }), EVAL_TIMEOUT_MS + TERMINATION_GRACE_MS, () => terminatePageScripts(session));
394
- const settled = await withDeadline(settlePromise(session, response.result, script), EVAL_TIMEOUT_MS, () => {
395
- const err = promiseTimeoutError(EVAL_TIMEOUT_MS);
396
- return new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.CDP_TIMEOUT);
397
- });
501
+ const response = await withDeadline(executeScript(session, script, evaluateOptions(target)), EVAL_TIMEOUT_MS + TERMINATION_GRACE_MS, () => evaluationTimeoutError(target.recovery ?? session, scope));
502
+ const settled = await withDeadline(settlePromise(session, response.result, script), EVAL_TIMEOUT_MS, promiseTimeout);
398
503
  return await toEvalResult(session, settled);
399
504
  }
505
+ catch (error) {
506
+ throw scope === 'page' && isContextLostError(error)
507
+ ? await pageContextLostError(error, session)
508
+ : error;
509
+ }
400
510
  finally {
401
511
  void session
402
512
  .send('Runtime.releaseObjectGroup', { objectGroup: EVAL_OBJECT_GROUP })
@@ -4,7 +4,8 @@
4
4
  * Collects the listeners of the element, every ancestor (through open shadow
5
5
  * roots), its document and its window with `DOMDebugger.getEventListeners`,
6
6
  * so handlers that frameworks attach by delegation (React on its root
7
- * container, jQuery on `document`) are found too. The Debugger domain is not
7
+ * container, jQuery on `document`) are found too, along with the handlers
8
+ * behind them (jQuery's handlers, React's `on…` props). The Debugger domain is not
8
9
  * enabled: that would make `debugger;` statements pause the page.
9
10
  */
10
11
  import type { CDPConnection } from '../../connection/cdp.js';