sourcemonkey 0.0.0-stage → 0.5.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 (367) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +87 -2
  3. package/dist/bytes.js +37 -0
  4. package/dist/channel-placeholder.js +5 -0
  5. package/dist/collection-scan.js +17 -0
  6. package/dist/collections.html +353 -0
  7. package/dist/compare.html +358 -0
  8. package/dist/composed-source.js +205 -0
  9. package/dist/connect-prompt.html +47 -0
  10. package/dist/debug/icons.html +211 -0
  11. package/dist/debug/storage.html +163 -0
  12. package/dist/debug-log.js +75 -0
  13. package/dist/diagnostic-location.js +64 -0
  14. package/dist/eval-hook.js +308 -0
  15. package/dist/extension/background.js +5263 -0
  16. package/dist/extension/channel.js +133 -0
  17. package/dist/extension/code-links.js +187 -0
  18. package/dist/extension/code-tabs.js +460 -0
  19. package/dist/extension/collection-dialog.js +619 -0
  20. package/dist/extension/collection-display.js +322 -0
  21. package/dist/extension/collections.js +2905 -0
  22. package/dist/extension/compare.js +867 -0
  23. package/dist/extension/concurrency.js +186 -0
  24. package/dist/extension/connect-grants.js +109 -0
  25. package/dist/extension/connect-prompt-page.js +70 -0
  26. package/dist/extension/connect-prompt.js +266 -0
  27. package/dist/extension/copy-button.js +63 -0
  28. package/dist/extension/copy-dialog.js +502 -0
  29. package/dist/extension/copy-preference.js +66 -0
  30. package/dist/extension/debug/icons.js +203 -0
  31. package/dist/extension/debug/storage.js +365 -0
  32. package/dist/extension/deep-link.js +77 -0
  33. package/dist/extension/delete-dialog.js +432 -0
  34. package/dist/extension/delete-icon.js +40 -0
  35. package/dist/extension/details-tooltips.js +66 -0
  36. package/dist/extension/directories.js +679 -0
  37. package/dist/extension/export-backup.js +407 -0
  38. package/dist/extension/export-dialog.js +142 -0
  39. package/dist/extension/file-access.js +74 -0
  40. package/dist/extension/file-changes.js +346 -0
  41. package/dist/extension/frame-info.js +85 -0
  42. package/dist/extension/github-provider.js +336 -0
  43. package/dist/extension/glyph.js +84 -0
  44. package/dist/extension/gm-bindings.js +601 -0
  45. package/dist/extension/gm-download.js +419 -0
  46. package/dist/extension/gm-menu.js +228 -0
  47. package/dist/extension/gm-notification.js +141 -0
  48. package/dist/extension/gm-originator-map.js +23 -0
  49. package/dist/extension/gm-tab-data.js +121 -0
  50. package/dist/extension/gm-tab.js +160 -0
  51. package/dist/extension/gm-url-change.js +111 -0
  52. package/dist/extension/gm-url-scheme.js +27 -0
  53. package/dist/extension/gm-values.js +176 -0
  54. package/dist/extension/gm-window-control.js +104 -0
  55. package/dist/extension/gm-xhr-headers.js +333 -0
  56. package/dist/extension/gm-xhr.js +584 -0
  57. package/dist/extension/injector.js +3145 -0
  58. package/dist/extension/install-capture.js +277 -0
  59. package/dist/extension/line-diff.js +233 -0
  60. package/dist/extension/local-copy.js +560 -0
  61. package/dist/extension/local-delete.js +276 -0
  62. package/dist/extension/location-kinds.js +108 -0
  63. package/dist/extension/log-format.js +158 -0
  64. package/dist/extension/managed-changes.js +361 -0
  65. package/dist/extension/managed-items.js +57 -0
  66. package/dist/extension/match-index.js +375 -0
  67. package/dist/extension/missing-scripts.js +66 -0
  68. package/dist/extension/more-menu.js +109 -0
  69. package/dist/extension/move-dialog.js +197 -0
  70. package/dist/extension/offscreen.js +37 -0
  71. package/dist/extension/options.js +504 -0
  72. package/dist/extension/page-heading.js +170 -0
  73. package/dist/extension/page-links.js +199 -0
  74. package/dist/extension/parse-check-client.js +70 -0
  75. package/dist/extension/parse-check.js +161 -0
  76. package/dist/extension/parse-sandbox-host.js +172 -0
  77. package/dist/extension/parse-sandbox.js +175 -0
  78. package/dist/extension/popup-commands-open.js +41 -0
  79. package/dist/extension/popup-view.js +387 -0
  80. package/dist/extension/popup.js +703 -0
  81. package/dist/extension/prism-userscript.js +53 -0
  82. package/dist/extension/refresh-error.js +136 -0
  83. package/dist/extension/refresh-targets.js +160 -0
  84. package/dist/extension/related-versions.js +1144 -0
  85. package/dist/extension/resize-observer-loop.js +19 -0
  86. package/dist/extension/row-cache.js +387 -0
  87. package/dist/extension/script-blobs.js +413 -0
  88. package/dist/extension/script-details.js +658 -0
  89. package/dist/extension/script-enablement.js +285 -0
  90. package/dist/extension/script-identity.js +310 -0
  91. package/dist/extension/script-keys.js +54 -0
  92. package/dist/extension/script-log-id.js +155 -0
  93. package/dist/extension/script-log.js +290 -0
  94. package/dist/extension/script-rejection.js +104 -0
  95. package/dist/extension/script-sites.js +158 -0
  96. package/dist/extension/script-store.js +3507 -0
  97. package/dist/extension/script-updater.js +991 -0
  98. package/dist/extension/script-url-hash.js +61 -0
  99. package/dist/extension/select-all.js +37 -0
  100. package/dist/extension/select-open-guard.js +90 -0
  101. package/dist/extension/site-icon.js +1261 -0
  102. package/dist/extension/sm-dev-link.js +302 -0
  103. package/dist/extension/solo-click.js +58 -0
  104. package/dist/extension/sticky-numbers.js +188 -0
  105. package/dist/extension/storage-breakdown.js +253 -0
  106. package/dist/extension/storage-scan.js +86 -0
  107. package/dist/extension/storage-usage.js +194 -0
  108. package/dist/extension/structural-keys.js +29 -0
  109. package/dist/extension/sw-teardown.js +36 -0
  110. package/dist/extension/tab-status.js +403 -0
  111. package/dist/extension/toolbar-summary.js +105 -0
  112. package/dist/extension/user-scripts-access.js +43 -0
  113. package/dist/extension/userscript.js +3672 -0
  114. package/dist/extension/value-store.js +204 -0
  115. package/dist/extension/version-probe-fetch.js +68 -0
  116. package/dist/extension/worker-client.js +109 -0
  117. package/dist/extension/worker-ops.js +171 -0
  118. package/dist/extension/zip-writer.js +24 -0
  119. package/dist/external-files.js +878 -0
  120. package/dist/file-loader.js +436 -0
  121. package/dist/file-locations.js +439 -0
  122. package/dist/generated-wrappers.js +5 -0
  123. package/dist/github-auth.js +148 -0
  124. package/dist/github-urls.js +46 -0
  125. package/dist/gm-api-catalog.js +217 -0
  126. package/dist/gm-connect.js +273 -0
  127. package/dist/gm-xhr-types.js +5 -0
  128. package/dist/http-read.js +142 -0
  129. package/dist/icons/icon-128.png +0 -0
  130. package/dist/icons/icon-16.png +0 -0
  131. package/dist/icons/icon-48.png +0 -0
  132. package/dist/local-file-overrides.js +558 -0
  133. package/dist/manifest.json +38 -0
  134. package/dist/markup-response.js +126 -0
  135. package/dist/match-targets.js +305 -0
  136. package/dist/menu-captions.js +22 -0
  137. package/dist/offscreen.html +39 -0
  138. package/dist/options.html +197 -0
  139. package/dist/parse-check-protocol.js +51 -0
  140. package/dist/parse-location.js +171 -0
  141. package/dist/parse-sandbox.html +56 -0
  142. package/dist/phase-timing.js +155 -0
  143. package/dist/popup.html +197 -0
  144. package/dist/public-suffixes.js +6957 -0
  145. package/dist/run-context.js +31 -0
  146. package/dist/runtime-messages.js +34 -0
  147. package/dist/sandboxed-page.js +35 -0
  148. package/dist/script-build.js +787 -0
  149. package/dist/script-display.js +383 -0
  150. package/dist/script-icon.js +43 -0
  151. package/dist/script-links.js +204 -0
  152. package/dist/script-source-url.js +214 -0
  153. package/dist/script-world.js +65 -0
  154. package/dist/sm-dev-protocol.js +84 -0
  155. package/dist/source-map.js +177 -0
  156. package/dist/stack-link.js +138 -0
  157. package/dist/styles.css +825 -0
  158. package/dist/unsafe-window-use.js +229 -0
  159. package/dist/url-match.js +734 -0
  160. package/dist/userscript-header.js +248 -0
  161. package/dist/userscript.html +1165 -0
  162. package/dist/vendor/fflate.LICENSE +21 -0
  163. package/dist/vendor/fflate.js +2692 -0
  164. package/dist/vendor/prism/prism-line-numbers.css +1 -0
  165. package/dist/vendor/prism/prism-line-numbers.js +1 -0
  166. package/dist/vendor/prism/prism.css +1 -0
  167. package/dist/vendor/prism/prism.js +1946 -0
  168. package/dist/version-compare.js +141 -0
  169. package/dist/version-probe.js +120 -0
  170. package/lib/build-info.json +1 -0
  171. package/lib/bytes.d.ts +3 -0
  172. package/lib/bytes.js +37 -0
  173. package/lib/channel-placeholder.d.ts +1 -0
  174. package/lib/channel-placeholder.js +5 -0
  175. package/lib/collection-scan.d.ts +8 -0
  176. package/lib/collection-scan.js +17 -0
  177. package/lib/composed-source.d.ts +28 -0
  178. package/lib/composed-source.js +205 -0
  179. package/lib/debug-log.d.ts +9 -0
  180. package/lib/debug-log.js +75 -0
  181. package/lib/diagnostic-location.d.ts +8 -0
  182. package/lib/diagnostic-location.js +64 -0
  183. package/lib/eval-hook.d.ts +34 -0
  184. package/lib/eval-hook.js +308 -0
  185. package/lib/external-files.d.ts +111 -0
  186. package/lib/external-files.js +878 -0
  187. package/lib/file-loader.d.ts +48 -0
  188. package/lib/file-loader.js +436 -0
  189. package/lib/file-locations.d.ts +44 -0
  190. package/lib/file-locations.js +439 -0
  191. package/lib/generated-wrappers.d.ts +11 -0
  192. package/lib/generated-wrappers.js +5 -0
  193. package/lib/github-auth.d.ts +12 -0
  194. package/lib/github-auth.js +148 -0
  195. package/lib/github-urls.d.ts +1 -0
  196. package/lib/github-urls.js +46 -0
  197. package/lib/gm-api-catalog.d.ts +24 -0
  198. package/lib/gm-api-catalog.js +217 -0
  199. package/lib/gm-connect.d.ts +34 -0
  200. package/lib/gm-connect.js +273 -0
  201. package/lib/gm-xhr-types.d.ts +68 -0
  202. package/lib/gm-xhr-types.js +5 -0
  203. package/lib/harness/browser-extensions.d.ts +73 -0
  204. package/lib/harness/browser-extensions.js +285 -0
  205. package/lib/harness/browser-lang.d.ts +20 -0
  206. package/lib/harness/browser-lang.js +86 -0
  207. package/lib/harness/browser-launch.d.ts +14 -0
  208. package/lib/harness/browser-launch.js +184 -0
  209. package/lib/harness/checks.d.ts +110 -0
  210. package/lib/harness/checks.js +321 -0
  211. package/lib/harness/cli.d.ts +2 -0
  212. package/lib/harness/cli.js +2259 -0
  213. package/lib/harness/command-args.d.ts +91 -0
  214. package/lib/harness/command-args.js +369 -0
  215. package/lib/harness/command-lines.d.ts +80 -0
  216. package/lib/harness/command-lines.js +180 -0
  217. package/lib/harness/command-list.d.ts +206 -0
  218. package/lib/harness/command-list.js +1475 -0
  219. package/lib/harness/console-commands.d.ts +463 -0
  220. package/lib/harness/console-commands.js +3216 -0
  221. package/lib/harness/download-watch.d.ts +21 -0
  222. package/lib/harness/download-watch.js +156 -0
  223. package/lib/harness/extension-backend.d.ts +41 -0
  224. package/lib/harness/extension-backend.js +100 -0
  225. package/lib/harness/extension-host.d.ts +112 -0
  226. package/lib/harness/extension-host.js +552 -0
  227. package/lib/harness/extension-launch.d.ts +67 -0
  228. package/lib/harness/extension-launch.js +285 -0
  229. package/lib/harness/extension-link.d.ts +315 -0
  230. package/lib/harness/extension-link.js +607 -0
  231. package/lib/harness/external-manager.d.ts +146 -0
  232. package/lib/harness/external-manager.js +414 -0
  233. package/lib/harness/fixture.d.ts +165 -0
  234. package/lib/harness/fixture.js +461 -0
  235. package/lib/harness/frame-sessions.d.ts +71 -0
  236. package/lib/harness/frame-sessions.js +248 -0
  237. package/lib/harness/frames.d.ts +68 -0
  238. package/lib/harness/frames.js +216 -0
  239. package/lib/harness/help-format.d.ts +17 -0
  240. package/lib/harness/help-format.js +127 -0
  241. package/lib/harness/host.d.ts +352 -0
  242. package/lib/harness/host.js +423 -0
  243. package/lib/harness/index.d.ts +8 -0
  244. package/lib/harness/index.js +10 -0
  245. package/lib/harness/input-file.d.ts +18 -0
  246. package/lib/harness/input-file.js +61 -0
  247. package/lib/harness/json-output.d.ts +565 -0
  248. package/lib/harness/json-output.js +71 -0
  249. package/lib/harness/manager-actions.d.ts +21 -0
  250. package/lib/harness/manager-actions.js +129 -0
  251. package/lib/harness/manager-commands.d.ts +142 -0
  252. package/lib/harness/manager-commands.js +1196 -0
  253. package/lib/harness/manager-fetch.d.ts +33 -0
  254. package/lib/harness/manager-fetch.js +239 -0
  255. package/lib/harness/managers.d.ts +24 -0
  256. package/lib/harness/managers.js +27 -0
  257. package/lib/harness/output-style.d.ts +125 -0
  258. package/lib/harness/output-style.js +322 -0
  259. package/lib/harness/page-commands.d.ts +134 -0
  260. package/lib/harness/page-commands.js +1141 -0
  261. package/lib/harness/page-output.d.ts +67 -0
  262. package/lib/harness/page-output.js +139 -0
  263. package/lib/harness/page-watch.d.ts +171 -0
  264. package/lib/harness/page-watch.js +607 -0
  265. package/lib/harness/parse-check-node.d.ts +3 -0
  266. package/lib/harness/parse-check-node.js +132 -0
  267. package/lib/harness/path-args.d.ts +22 -0
  268. package/lib/harness/path-args.js +148 -0
  269. package/lib/harness/playwright-chromium.d.ts +16 -0
  270. package/lib/harness/playwright-chromium.js +86 -0
  271. package/lib/harness/prepare.d.ts +168 -0
  272. package/lib/harness/prepare.js +364 -0
  273. package/lib/harness/prompt-editor.d.ts +17 -0
  274. package/lib/harness/prompt-editor.js +354 -0
  275. package/lib/harness/prompt-history.d.ts +13 -0
  276. package/lib/harness/prompt-history.js +89 -0
  277. package/lib/harness/rebuild-advice.d.ts +6 -0
  278. package/lib/harness/rebuild-advice.js +39 -0
  279. package/lib/harness/run-log.d.ts +47 -0
  280. package/lib/harness/run-log.js +140 -0
  281. package/lib/harness/run-logs.d.ts +132 -0
  282. package/lib/harness/run-logs.js +241 -0
  283. package/lib/harness/run.d.ts +194 -0
  284. package/lib/harness/run.js +1973 -0
  285. package/lib/harness/runs.d.ts +229 -0
  286. package/lib/harness/runs.js +726 -0
  287. package/lib/harness/save-html.d.ts +39 -0
  288. package/lib/harness/save-html.js +89 -0
  289. package/lib/harness/saved-pages.d.ts +120 -0
  290. package/lib/harness/saved-pages.js +275 -0
  291. package/lib/harness/script-mark.d.ts +17 -0
  292. package/lib/harness/script-mark.js +56 -0
  293. package/lib/harness/snapshot.d.ts +143 -0
  294. package/lib/harness/snapshot.js +474 -0
  295. package/lib/harness/stuck-tabs.d.ts +19 -0
  296. package/lib/harness/stuck-tabs.js +131 -0
  297. package/lib/harness/tab-refs.d.ts +20 -0
  298. package/lib/harness/tab-refs.js +80 -0
  299. package/lib/harness/tampermonkey.d.ts +71 -0
  300. package/lib/harness/tampermonkey.js +356 -0
  301. package/lib/harness/tm-ids.d.ts +18 -0
  302. package/lib/harness/tm-ids.js +88 -0
  303. package/lib/harness/violentmonkey.d.ts +34 -0
  304. package/lib/harness/violentmonkey.js +162 -0
  305. package/lib/harness/wait.d.ts +77 -0
  306. package/lib/harness/wait.js +458 -0
  307. package/lib/http-read.d.ts +30 -0
  308. package/lib/http-read.js +142 -0
  309. package/lib/local-file-overrides.d.ts +35 -0
  310. package/lib/local-file-overrides.js +558 -0
  311. package/lib/manifest.json +38 -0
  312. package/lib/markup-response.d.ts +11 -0
  313. package/lib/markup-response.js +126 -0
  314. package/lib/match-targets.d.ts +18 -0
  315. package/lib/match-targets.js +305 -0
  316. package/lib/menu-captions.d.ts +1 -0
  317. package/lib/menu-captions.js +22 -0
  318. package/lib/parse-check-protocol.d.ts +27 -0
  319. package/lib/parse-check-protocol.js +51 -0
  320. package/lib/parse-location.d.ts +12 -0
  321. package/lib/parse-location.js +171 -0
  322. package/lib/phase-timing.d.ts +12 -0
  323. package/lib/phase-timing.js +155 -0
  324. package/lib/public-suffixes.d.ts +1 -0
  325. package/lib/public-suffixes.js +6957 -0
  326. package/lib/run-context.d.ts +2 -0
  327. package/lib/run-context.js +31 -0
  328. package/lib/runtime-messages.d.ts +10 -0
  329. package/lib/runtime-messages.js +34 -0
  330. package/lib/sandboxed-page.d.ts +5 -0
  331. package/lib/sandboxed-page.js +35 -0
  332. package/lib/script-build.d.ts +111 -0
  333. package/lib/script-build.js +787 -0
  334. package/lib/script-display.d.ts +51 -0
  335. package/lib/script-display.js +383 -0
  336. package/lib/script-icon.d.ts +16 -0
  337. package/lib/script-icon.js +43 -0
  338. package/lib/script-links.d.ts +24 -0
  339. package/lib/script-links.js +204 -0
  340. package/lib/script-source-url.d.ts +8 -0
  341. package/lib/script-source-url.js +214 -0
  342. package/lib/script-world.d.ts +6 -0
  343. package/lib/script-world.js +65 -0
  344. package/lib/sm-dev-protocol.d.ts +251 -0
  345. package/lib/sm-dev-protocol.js +84 -0
  346. package/lib/source-map.d.ts +32 -0
  347. package/lib/source-map.js +177 -0
  348. package/lib/stack-link.d.ts +15 -0
  349. package/lib/stack-link.js +138 -0
  350. package/lib/unsafe-window-use.d.ts +1 -0
  351. package/lib/unsafe-window-use.js +229 -0
  352. package/lib/url-match.d.ts +40 -0
  353. package/lib/url-match.js +734 -0
  354. package/lib/userscript-header.d.ts +33 -0
  355. package/lib/userscript-header.js +248 -0
  356. package/lib/version-compare.d.ts +6 -0
  357. package/lib/version-compare.js +141 -0
  358. package/lib/version-probe.d.ts +17 -0
  359. package/lib/version-probe.js +120 -0
  360. package/package.json +55 -3
  361. package/skills/SourceMonkey-dev/SKILL.md +868 -0
  362. package/skills/SourceMonkey-dev/references/cdp.md +293 -0
  363. package/skills/SourceMonkey-dev/references/commands.md +297 -0
  364. package/skills/SourceMonkey-dev/references/other-managers.md +63 -0
  365. package/skills/SourceMonkey-dev/references/specs.md +46 -0
  366. package/skills/SourceMonkey-friction/SKILL.md +103 -0
  367. package/skills/SourceMonkey-friction/references/template.md +59 -0
@@ -0,0 +1,143 @@
1
+ import type { Frame, Locator, Page } from '@playwright/test';
2
+ /** The ref a selector names, if it's a ref: `e4` for `[ref=e4]`. */
3
+ export declare function refOf(selector: string): string | null;
4
+ /**
5
+ * The elements a selector names, or the element a ref does.
6
+ *
7
+ * A ref is looked up in the frame the latest snapshot showed it in,
8
+ * through its iframes' refs, not by its prefix. Playwright finds a
9
+ * ref's frame by the number in its prefix, and a page restored by Back
10
+ * keeps refs numbered for a frame Playwright has since renumbered
11
+ * ("Invalid frame in aria-ref selector"). Starting from `:root` keeps
12
+ * Playwright from reading the prefix at all.
13
+ */
14
+ export declare function locate(page: Page, selector: string, within?: Frame): Locator;
15
+ /**
16
+ * Notes where each ref of a snapshot is, for `locate`: each frame's
17
+ * latest snapshot is the only one Playwright resolves its refs against.
18
+ * `tree` is the whole page's, or with `within` an iframe's; `document`,
19
+ * the page's document it was of, from `documentOf`.
20
+ *
21
+ * An iframe's snapshot adds to the page's latest of the same document
22
+ * rather than replacing it: the page's other frames keep theirs, and an
23
+ * element keeps its ref from one snapshot to the next.
24
+ */
25
+ export declare function noteRefs(page: Page, tree: string, document: number, within?: Frame): void;
26
+ /**
27
+ * Which document the page shows: its `performance.timeOrigin`, new
28
+ * with each load, the same across a single-page app's route changes
29
+ * and back again after Back restores a page from the browser's cache.
30
+ * A new document has none of the old one's refs.
31
+ */
32
+ export declare function documentOf(page: Page): Promise<number>;
33
+ /**
34
+ * Why a ref can't name anything on the page now, when that's because
35
+ * there's no snapshot of the document showing: none taken, or the page
36
+ * has loaded since. Null otherwise.
37
+ */
38
+ export declare function refProblem(page: Page, selector: string): Promise<string | null>;
39
+ /** Where a snapshot's refs are: the refs of the iframes each is in. */
40
+ export declare function framePaths(tree: string): Map<string, string[]>;
41
+ /**
42
+ * What each ref's line calls its element, its role and name as in
43
+ * `link "past"`, so a command can say what a ref named.
44
+ */
45
+ export declare function refLabels(tree: string): Map<string, string>;
46
+ /** A selector as a command says it back: a ref with what it named, `e5 (link "past")`. */
47
+ export declare function named(page: Page, selector: string): string;
48
+ /**
49
+ * A tree of the whole page (`:root`) without its top `- document`
50
+ * line, which only wraps the rest, and with the rest a level less
51
+ * indented: as a snapshot of `body` would look, plus what is outside
52
+ * it. A tree that doesn't start that way is left as it is.
53
+ */
54
+ export declare function withoutDocument(tree: string): string;
55
+ /**
56
+ * A tree as `snapshot` prints it: without `[cursor=pointer]` on links
57
+ * and buttons, which always have it. Elsewhere it stays, since it marks
58
+ * something clickable that isn't a link or button.
59
+ */
60
+ export declare function tidy(tree: string): string;
61
+ /**
62
+ * The top refs of a snapshot of one element: its own line's, or its
63
+ * children's when the tree leaves the element itself out. For finding
64
+ * where it is in a snapshot of the whole page (`treeParts`).
65
+ */
66
+ export declare function topRefs(tree: string): string[];
67
+ /**
68
+ * Where a selector's match is in the whole page's tree, as `topRefs`.
69
+ * The whole tree folds a match that is only text (a span in a link or
70
+ * a paragraph) into the element above it, so it's found as the nearest
71
+ * one above that has a line. None for a match the tree leaves out
72
+ * (hidden). Depth 1 keeps each snapshot short; Playwright still walks
73
+ * the match's whole subtree.
74
+ */
75
+ export declare function refsOf(match: Locator, timeout: number): Promise<string[]>;
76
+ /**
77
+ * The parts of a whole page's tree `snapshot` shows for a selector or
78
+ * `--find`, each dedented to start at the left.
79
+ *
80
+ * - `matches`: a selector's matches, as each one's `topRefs`. A match
81
+ * not in the tree (hidden, or off screen) is left out.
82
+ * - `text`: the elements containing it, within `matches` when given. A text
83
+ * or /url line stands for the element it's in.
84
+ *
85
+ * Each part then goes `up` levels, then with `context`, as far up as
86
+ * adds at most `CONTEXT_LINES` lines (wrappers add one each). A part
87
+ * inside another is left out.
88
+ *
89
+ * The text is matched without regard to case or runs of spaces, against
90
+ * a line's content (names, text, values, URLs), not its role or
91
+ * `[ref=e4]`-style attributes, so `button` doesn't find every button.
92
+ */
93
+ export declare function treeParts(tree: string, { matches, text, up, context }: {
94
+ matches?: string[][];
95
+ text?: string;
96
+ up?: number;
97
+ context?: boolean;
98
+ }): string[];
99
+ /**
100
+ * The lines of a tree taken with `boxes: true` that show on screen, in
101
+ * a viewport of `width` by `height`, with the boxes taken out. A line
102
+ * stays when its element's box is at least partly on screen, or when a
103
+ * line under it stays, so each kept line still has its parents. A line
104
+ * without a box (text, a link's /url) goes with the element it's in.
105
+ */
106
+ export declare function onScreen(tree: string, width: number, height: number): string;
107
+ /** One of a selector's matches, as `probe` shows it. */
108
+ export interface Match {
109
+ /** A selector that finds only it (see `cssFor`), or null. */
110
+ css: string | null;
111
+ /** Whether it's in a shadow root, where a selector path can't reach. */
112
+ shadow: boolean;
113
+ /** Its HTML on one line, cut at `HTML_MAX` characters. */
114
+ html: string;
115
+ }
116
+ /**
117
+ * The first `max` elements a selector matches, in one call to the page,
118
+ * and how many it matched then. `framed` when they're in a frame, where
119
+ * a selector would only work within it.
120
+ */
121
+ export declare function probeMatches(page: Page, selector: string, max: number, timeout: number, within?: Frame): Promise<{
122
+ framed: boolean;
123
+ count: number;
124
+ matches: Match[];
125
+ }>;
126
+ /**
127
+ * The document and every open shadow root in it, which Playwright's CSS
128
+ * engine looks inside too. Runs in the page, so it's self-contained.
129
+ */
130
+ export declare function openRoots(): (Document | ShadowRoot)[];
131
+ /**
132
+ * A CSS selector that finds only `el`, or null. The first of its id,
133
+ * tag, telling attributes and classes that does, else a `>` path up
134
+ * to an ancestor that has one, with `:nth-of-type` where siblings look
135
+ * alike. Names that look generated (`:r1:`, `css-1x2y3z`, `x1lliihq`,
136
+ * or with a long number in them) are passed over, since they change
137
+ * between loads or builds of a site.
138
+ *
139
+ * Unique as Playwright's CSS engine sees it, across `roots` (from
140
+ * `openRoots`). An element in a shadow root gets none, since a path
141
+ * can't cross into one. Runs in the page, so it's self-contained.
142
+ */
143
+ export declare function cssFor(el: Element, roots: (Document | ShadowRoot)[]): string | null;
@@ -0,0 +1,474 @@
1
+ // What `snapshot` adds to Playwright's accessibility tree: element refs
2
+ // the other page commands take, and only the lines on screen
3
+ // (--visible). Also `probe --css`'s suggested CSS selectors.
4
+ //
5
+ // The tree is Playwright's `ariaSnapshot({ mode: 'ai' })`: YAML-like
6
+ // lines, two spaces of indent a level, each element with a `[ref=e4]`
7
+ // (`f3e4` in the frame Playwright numbers 3: an iframe, or the page's
8
+ // own frame once the tab has had a few documents).
9
+ //
10
+ // Playwright resolves a ref against the latest snapshot of the page (or
11
+ // frame) only. An element keeps its ref from one snapshot to the next,
12
+ // unless its role or name changed, which gives it a new one.
13
+ import { CONTEXT_LINES } from './command-list.js';
14
+ // A ref as `snapshot` shows it, and as typed: e4, ref=e4 or [ref=e4].
15
+ const REF = /^\[?(?:ref=)?((?:f\d+)?e\d+)\]?$/;
16
+ /** The ref a selector names, if it's a ref: `e4` for `[ref=e4]`. */
17
+ export function refOf(selector) {
18
+ return REF.exec(selector)?.[1] ?? null;
19
+ }
20
+ const snapshots = new WeakMap();
21
+ /**
22
+ * The elements a selector names, or the element a ref does.
23
+ *
24
+ * A ref is looked up in the frame the latest snapshot showed it in,
25
+ * through its iframes' refs, not by its prefix. Playwright finds a
26
+ * ref's frame by the number in its prefix, and a page restored by Back
27
+ * keeps refs numbered for a frame Playwright has since renumbered
28
+ * ("Invalid frame in aria-ref selector"). Starting from `:root` keeps
29
+ * Playwright from reading the prefix at all.
30
+ */
31
+ export function locate(page, selector, within) {
32
+ const ref = refOf(selector);
33
+ // A selector looks in `within` (`--frame`), else the page's own frame.
34
+ if (!ref)
35
+ return (within ?? page).locator(selector);
36
+ const noted = snapshots.get(page);
37
+ let scope = noted?.roots.get(ref) ?? page;
38
+ for (const frame of noted?.frames.get(ref) ?? [])
39
+ scope = scope.locator(`:root >> aria-ref=${frame}`).contentFrame();
40
+ return scope.locator(`:root >> aria-ref=${ref}`);
41
+ }
42
+ /**
43
+ * Notes where each ref of a snapshot is, for `locate`: each frame's
44
+ * latest snapshot is the only one Playwright resolves its refs against.
45
+ * `tree` is the whole page's, or with `within` an iframe's; `document`,
46
+ * the page's document it was of, from `documentOf`.
47
+ *
48
+ * An iframe's snapshot adds to the page's latest of the same document
49
+ * rather than replacing it: the page's other frames keep theirs, and an
50
+ * element keeps its ref from one snapshot to the next.
51
+ */
52
+ export function noteRefs(page, tree, document, within) {
53
+ const paths = framePaths(tree);
54
+ const labels = refLabels(tree);
55
+ const before = snapshots.get(page);
56
+ if (!within || before?.document !== document) {
57
+ const roots = new Map();
58
+ if (within)
59
+ for (const ref of paths.keys())
60
+ roots.set(ref, within);
61
+ snapshots.set(page, { frames: paths, labels, roots, document });
62
+ return;
63
+ }
64
+ for (const [ref, path] of paths) {
65
+ before.frames.set(ref, path);
66
+ before.roots.set(ref, within);
67
+ }
68
+ for (const [ref, label] of labels)
69
+ before.labels.set(ref, label);
70
+ }
71
+ /**
72
+ * Which document the page shows: its `performance.timeOrigin`, new
73
+ * with each load, the same across a single-page app's route changes
74
+ * and back again after Back restores a page from the browser's cache.
75
+ * A new document has none of the old one's refs.
76
+ */
77
+ export function documentOf(page) {
78
+ return page.evaluate(() => performance.timeOrigin);
79
+ }
80
+ /**
81
+ * Why a ref can't name anything on the page now, when that's because
82
+ * there's no snapshot of the document showing: none taken, or the page
83
+ * has loaded since. Null otherwise.
84
+ */
85
+ export async function refProblem(page, selector) {
86
+ if (!refOf(selector))
87
+ return null;
88
+ const noted = snapshots.get(page);
89
+ if (!noted)
90
+ return 'This tab has no snapshot yet. Take one with snapshot.';
91
+ if (noted.document !== await documentOf(page))
92
+ return 'The page reloaded since the last snapshot. Take a new snapshot.';
93
+ return null;
94
+ }
95
+ /** Where a snapshot's refs are: the refs of the iframes each is in. */
96
+ export function framePaths(tree) {
97
+ const paths = new Map();
98
+ // The iframe lines this one is under, with their indents.
99
+ const open = [];
100
+ for (const line of tree.split('\n')) {
101
+ const depth = /^ */.exec(line)[0].length;
102
+ while (open.length && open[open.length - 1].depth >= depth)
103
+ open.pop();
104
+ const ref = /\[ref=([^\]]+)\]/.exec(line)?.[1];
105
+ if (!ref)
106
+ continue;
107
+ paths.set(ref, [...open.map((o) => o.ref)]);
108
+ if (/^ *- '?iframe\b/.test(line))
109
+ open.push({ depth, ref });
110
+ }
111
+ return paths;
112
+ }
113
+ // How long a label `named` gives can be.
114
+ const LABEL_LIMIT = 60;
115
+ /**
116
+ * What each ref's line calls its element, its role and name as in
117
+ * `link "past"`, so a command can say what a ref named.
118
+ */
119
+ export function refLabels(tree) {
120
+ const labels = new Map();
121
+ for (const line of tree.split('\n')) {
122
+ const ref = /\[ref=([^\]]+)\]/.exec(line)?.[1];
123
+ if (!ref)
124
+ continue;
125
+ // `- 'link "a: b" [ref=e2]':` is YAML-quoted, its ' doubled.
126
+ const quoted = /^ *- '/.test(line);
127
+ let label = line.replace(/^ *- '?/, '').split(' [')[0];
128
+ if (quoted)
129
+ label = label.replace(/''/g, '\'');
130
+ labels.set(ref, label.length > LABEL_LIMIT ? `${label.slice(0, LABEL_LIMIT)}…` : label);
131
+ }
132
+ return labels;
133
+ }
134
+ /** A selector as a command says it back: a ref with what it named, `e5 (link "past")`. */
135
+ export function named(page, selector) {
136
+ const ref = refOf(selector);
137
+ const label = ref && snapshots.get(page)?.labels.get(ref);
138
+ return label ? `${selector} (${label})` : selector;
139
+ }
140
+ /**
141
+ * A tree of the whole page (`:root`) without its top `- document`
142
+ * line, which only wraps the rest, and with the rest a level less
143
+ * indented: as a snapshot of `body` would look, plus what is outside
144
+ * it. A tree that doesn't start that way is left as it is.
145
+ */
146
+ export function withoutDocument(tree) {
147
+ const [first, ...rest] = tree.split('\n');
148
+ if (!/^- document\b.*:$/.test(first) || rest.some((l) => !l.startsWith(' ')))
149
+ return tree;
150
+ return rest.map((l) => l.slice(2)).join('\n');
151
+ }
152
+ /**
153
+ * A tree as `snapshot` prints it: without `[cursor=pointer]` on links
154
+ * and buttons, which always have it. Elsewhere it stays, since it marks
155
+ * something clickable that isn't a link or button.
156
+ */
157
+ export function tidy(tree) {
158
+ return tree.replace(/^( *- '?(?:link|button)\b.*?) \[cursor=pointer\]/gm, '$1');
159
+ }
160
+ /**
161
+ * The top refs of a snapshot of one element: its own line's, or its
162
+ * children's when the tree leaves the element itself out. For finding
163
+ * where it is in a snapshot of the whole page (`treeParts`).
164
+ */
165
+ export function topRefs(tree) {
166
+ return tree.split('\n').filter((l) => l.startsWith('- ')).map((l) => /\[ref=([^\]]+)\]/.exec(l)?.[1]).filter((r) => r !== undefined);
167
+ }
168
+ /**
169
+ * Where a selector's match is in the whole page's tree, as `topRefs`.
170
+ * The whole tree folds a match that is only text (a span in a link or
171
+ * a paragraph) into the element above it, so it's found as the nearest
172
+ * one above that has a line. None for a match the tree leaves out
173
+ * (hidden). Depth 1 keeps each snapshot short; Playwright still walks
174
+ * the match's whole subtree.
175
+ */
176
+ export async function refsOf(match, timeout) {
177
+ for (let at = match;; at = at.locator('xpath=..')) {
178
+ const tree = await at.ariaSnapshot({ mode: 'ai', depth: 1, timeout });
179
+ if (tree.trim() === '')
180
+ return [];
181
+ const refs = topRefs(tree);
182
+ if (refs.length)
183
+ return refs;
184
+ }
185
+ }
186
+ /**
187
+ * The parts of a whole page's tree `snapshot` shows for a selector or
188
+ * `--find`, each dedented to start at the left.
189
+ *
190
+ * - `matches`: a selector's matches, as each one's `topRefs`. A match
191
+ * not in the tree (hidden, or off screen) is left out.
192
+ * - `text`: the elements containing it, within `matches` when given. A text
193
+ * or /url line stands for the element it's in.
194
+ *
195
+ * Each part then goes `up` levels, then with `context`, as far up as
196
+ * adds at most `CONTEXT_LINES` lines (wrappers add one each). A part
197
+ * inside another is left out.
198
+ *
199
+ * The text is matched without regard to case or runs of spaces, against
200
+ * a line's content (names, text, values, URLs), not its role or
201
+ * `[ref=e4]`-style attributes, so `button` doesn't find every button.
202
+ */
203
+ export function treeParts(tree, { matches, text, up = 0, context = false }) {
204
+ const lines = tree.split('\n');
205
+ const depth = lines.map((l) => /^ */.exec(l)[0].length / 2);
206
+ // Where the part a line starts ends: the next line no deeper.
207
+ const end = (i) => {
208
+ let j = i + 1;
209
+ while (j < lines.length && depth[j] > depth[i])
210
+ j++;
211
+ return j;
212
+ };
213
+ const parent = (i) => {
214
+ for (let j = i - 1; j >= 0; j--)
215
+ if (depth[j] < depth[i])
216
+ return j;
217
+ return -1;
218
+ };
219
+ // Each part as the lines [start, end).
220
+ let parts;
221
+ if (matches) {
222
+ const lineOf = new Map();
223
+ lines.forEach((l, i) => {
224
+ const ref = /\[ref=([^\]]+)\]/.exec(l)?.[1];
225
+ if (ref)
226
+ lineOf.set(ref, i);
227
+ });
228
+ parts = [];
229
+ for (const refs of matches) {
230
+ const at = refs.map((r) => lineOf.get(r)).filter((i) => i !== undefined);
231
+ if (at.length)
232
+ parts.push([Math.min(...at), end(Math.max(...at))]);
233
+ }
234
+ }
235
+ else {
236
+ parts = [[0, lines.length]];
237
+ }
238
+ if (text !== undefined) {
239
+ const wanted = squash(text);
240
+ const hits = [];
241
+ for (let i = 0; i < lines.length; i++) {
242
+ if (!parts.some(([a, b]) => i >= a && i < b) || !squash(contentOf(lines[i])).includes(wanted))
243
+ continue;
244
+ const root = /\[ref=/.test(lines[i]) ? i : Math.max(0, parent(i));
245
+ hits.push([root, end(root)]);
246
+ }
247
+ parts = hits;
248
+ }
249
+ parts = parts.map(([a, b]) => {
250
+ for (let n = 0; n < up && parent(a) >= 0; n++)
251
+ [a, b] = [parent(a), end(parent(a))];
252
+ if (context) {
253
+ const size = b - a;
254
+ while (parent(a) >= 0 && end(parent(a)) - parent(a) <= size + CONTEXT_LINES)
255
+ [a, b] = [parent(a), end(parent(a))];
256
+ }
257
+ return [a, b];
258
+ });
259
+ // Parts are nested or apart: the outer one sorts first.
260
+ parts.sort((x, y) => x[0] - y[0] || y[1] - x[1]);
261
+ const shown = [];
262
+ let covered = -1;
263
+ for (const [a, b] of parts) {
264
+ if (a < covered)
265
+ continue;
266
+ covered = b;
267
+ shown.push(lines.slice(a, b).map((l) => l.slice(depth[a] * 2)).join('\n'));
268
+ }
269
+ return shown;
270
+ }
271
+ // A tree line's content, for `treeParts`: without its indent, YAML
272
+ // quoting, role or `text:`-style key, and `[...]` attributes.
273
+ function contentOf(line) {
274
+ let rest = line.replace(/^ *- /, '');
275
+ if (rest.startsWith('\''))
276
+ rest = rest.replace(/^'|'(:?)$/g, '$1').replace(/''/g, '\'');
277
+ // role "name" [attr] [attr]: text, each part but the role optional.
278
+ const parts = /^[\w/-]+(?: ("(?:[^"\\]|\\.)*"))?(?: \[[^\]]*\])*(?::(.*))?$/.exec(rest);
279
+ if (!parts)
280
+ return rest;
281
+ return `${parts[1] ?? ''} ${parts[2] ?? ''}`.replace(/\\"/g, '"');
282
+ }
283
+ function squash(text) {
284
+ return text.toLowerCase().replace(/\s+/g, ' ').trim();
285
+ }
286
+ const BOX = / \[box=(-?[\d.]+),(-?[\d.]+),([\d.]+),([\d.]+)\]/;
287
+ /**
288
+ * The lines of a tree taken with `boxes: true` that show on screen, in
289
+ * a viewport of `width` by `height`, with the boxes taken out. A line
290
+ * stays when its element's box is at least partly on screen, or when a
291
+ * line under it stays, so each kept line still has its parents. A line
292
+ * without a box (text, a link's /url) goes with the element it's in.
293
+ */
294
+ export function onScreen(tree, width, height) {
295
+ const kept = [];
296
+ const open = [];
297
+ const lines = tree.split('\n');
298
+ const keep = lines.map(() => false);
299
+ const texts = [];
300
+ const screen = { x: 0, y: 0, width, height };
301
+ for (let i = 0; i < lines.length; i++) {
302
+ const line = lines[i];
303
+ const depth = (/^ */.exec(line)[0].length) / 2;
304
+ while (open.length && open[open.length - 1].depth >= depth)
305
+ open.pop();
306
+ const parent = open[open.length - 1];
307
+ const match = BOX.exec(line);
308
+ texts.push(match ? line.replace(BOX, '') : line);
309
+ // Inside a frame, boxes are from the frame's corner.
310
+ const offset = parent ? (parent.frame && parent.box ? { x: parent.box.x, y: parent.box.y } : parent.offset) : { x: 0, y: 0 };
311
+ const clip = parent ? (parent.frame && parent.box ? overlap(parent.clip, parent.box) : parent.clip) : screen;
312
+ let box = null;
313
+ let shows;
314
+ if (match) {
315
+ box = { x: offset.x + Number(match[1]), y: offset.y + Number(match[2]), width: Number(match[3]), height: Number(match[4]) };
316
+ shows = meets(box, clip);
317
+ }
318
+ else {
319
+ shows = parent ? parent.shows : true;
320
+ }
321
+ const frame = /^ *- '?iframe\b/.test(line);
322
+ open.push({ depth, shows, offset, clip, box, frame, index: i, kept: false });
323
+ if (shows) {
324
+ // It, and every line above it, stays.
325
+ for (let j = open.length - 1; j >= 0 && !open[j].kept; j--) {
326
+ open[j].kept = true;
327
+ keep[open[j].index] = true;
328
+ }
329
+ }
330
+ }
331
+ for (let i = 0; i < texts.length; i++)
332
+ if (keep[i])
333
+ kept.push(texts[i]);
334
+ return kept.join('\n');
335
+ }
336
+ // Whether a box is at least partly inside `clip`. An element with no
337
+ // size shows when its corner does.
338
+ function meets(box, clip) {
339
+ return box.x < clip.x + clip.width && box.y < clip.y + clip.height
340
+ && box.x + Math.max(box.width, 1) > clip.x && box.y + Math.max(box.height, 1) > clip.y;
341
+ }
342
+ function overlap(a, b) {
343
+ const x = Math.max(a.x, b.x);
344
+ const y = Math.max(a.y, b.y);
345
+ return {
346
+ x, y,
347
+ width: Math.max(0, Math.min(a.x + a.width, b.x + b.width) - x),
348
+ height: Math.max(0, Math.min(a.y + a.height, b.y + b.height) - y),
349
+ };
350
+ }
351
+ const HTML_MAX = 500;
352
+ /**
353
+ * The first `max` elements a selector matches, in one call to the page,
354
+ * and how many it matched then. `framed` when they're in a frame, where
355
+ * a selector would only work within it.
356
+ */
357
+ export async function probeMatches(page, selector, max, timeout, within) {
358
+ const locator = locate(page, selector, within);
359
+ const limits = { max, htmlMax: HTML_MAX };
360
+ // Playwright finds a ref's element in the world it took the snapshot
361
+ // in, which `evaluate` looks in and `evaluateAll` doesn't.
362
+ if (refOf(selector))
363
+ return locator.evaluate(matchesOf, limits, { timeout });
364
+ return locator.evaluateAll(matchesOf, limits);
365
+ }
366
+ // Playwright sends a page function as its source, so openRoots and
367
+ // cssFor (which are self-contained) go along by being written into it.
368
+ // Made with `new Function` only to have that source: it never runs here.
369
+ const matchesOf = new Function(`return (els, { max, htmlMax }) => {
370
+ const openRoots = ${openRoots.toString()};
371
+ const cssFor = ${cssFor.toString()};
372
+ const list = Array.isArray(els) ? els : [els];
373
+ const roots = openRoots();
374
+ return {
375
+ framed: window !== window.top,
376
+ count: list.length,
377
+ matches: list.slice(0, max).map((el) => {
378
+ const html = el.outerHTML.replace(/\\s*\\n\\s*/g, ' ');
379
+ return {
380
+ css: cssFor(el, roots),
381
+ shadow: !(el.getRootNode() instanceof Document),
382
+ html: html.length > htmlMax ? html.slice(0, htmlMax) + '\\u2026' : html,
383
+ };
384
+ }),
385
+ };
386
+ };`)();
387
+ /**
388
+ * The document and every open shadow root in it, which Playwright's CSS
389
+ * engine looks inside too. Runs in the page, so it's self-contained.
390
+ */
391
+ export function openRoots() {
392
+ const roots = [document];
393
+ for (let i = 0; i < roots.length; i++) {
394
+ for (const e of roots[i].querySelectorAll('*'))
395
+ if (e.shadowRoot)
396
+ roots.push(e.shadowRoot);
397
+ }
398
+ return roots;
399
+ }
400
+ /**
401
+ * A CSS selector that finds only `el`, or null. The first of its id,
402
+ * tag, telling attributes and classes that does, else a `>` path up
403
+ * to an ancestor that has one, with `:nth-of-type` where siblings look
404
+ * alike. Names that look generated (`:r1:`, `css-1x2y3z`, `x1lliihq`,
405
+ * or with a long number in them) are passed over, since they change
406
+ * between loads or builds of a site.
407
+ *
408
+ * Unique as Playwright's CSS engine sees it, across `roots` (from
409
+ * `openRoots`). An element in a shadow root gets none, since a path
410
+ * can't cross into one. Runs in the page, so it's self-contained.
411
+ */
412
+ export function cssFor(el, roots) {
413
+ if (!(el.getRootNode() instanceof Document))
414
+ return null;
415
+ const all = roots;
416
+ const only = (selector) => {
417
+ try {
418
+ let count = 0;
419
+ for (const root of all) {
420
+ const found = root.querySelectorAll(selector);
421
+ count += found.length;
422
+ if (count > 1 || (found.length === 1 && found[0] !== el))
423
+ return false;
424
+ }
425
+ return count === 1;
426
+ }
427
+ catch {
428
+ return false;
429
+ }
430
+ };
431
+ const generated = (name) => /^[:_\d]|:|\d{3,}|^(css|sc|jsx|svelte|emotion)-/.test(name)
432
+ || (/^[a-z]{1,2}\d/i.test(name) && name.length >= 6 && !/[-_]/.test(name));
433
+ const quote = (value) => `"${value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
434
+ const choices = (e) => {
435
+ const tag = CSS.escape(e.localName);
436
+ const found = [];
437
+ if (e.id && !generated(e.id))
438
+ found.push(`#${CSS.escape(e.id)}`);
439
+ found.push(tag);
440
+ for (const name of ['data-testid', 'data-test', 'data-qa', 'name', 'aria-label', 'placeholder', 'title', 'alt', 'type', 'role', 'href']) {
441
+ const value = e.getAttribute(name);
442
+ if (value && value.length <= 60)
443
+ found.push(`${tag}[${name}=${quote(value)}]`);
444
+ }
445
+ const classes = [...e.classList].filter((c) => !generated(c)).slice(0, 3).map((c) => `.${CSS.escape(c)}`);
446
+ for (const c of classes)
447
+ found.push(tag + c);
448
+ if (classes.length > 1)
449
+ found.push(tag + classes.join(''));
450
+ return found;
451
+ };
452
+ const path = [];
453
+ for (let e = el; e; e = e.parentElement) {
454
+ const options = choices(e);
455
+ const unique = options.find((o) => only([o, ...path].join(' > ')));
456
+ if (unique)
457
+ return [unique, ...path].join(' > ');
458
+ // Among its siblings, what tells it apart.
459
+ const siblings = e.parentElement ? [...e.parentElement.children] : [e];
460
+ const apart = options.find((o) => {
461
+ try {
462
+ return siblings.filter((s) => s.matches(o)).length === 1;
463
+ }
464
+ catch {
465
+ return false;
466
+ }
467
+ });
468
+ const tag = CSS.escape(e.localName);
469
+ path.unshift(apart ?? `${tag}:nth-of-type(${siblings.filter((s) => s.localName === e.localName).indexOf(e) + 1})`);
470
+ if (only(path.join(' > ')))
471
+ return path.join(' > ');
472
+ }
473
+ return null;
474
+ }
@@ -0,0 +1,19 @@
1
+ /** A tab whose page didn't answer. */
2
+ export interface StuckTab {
3
+ targetId: string;
4
+ title: string;
5
+ url: string;
6
+ }
7
+ /**
8
+ * The browser's tabs whose pages don't answer, of `only` when given.
9
+ * Throws when the browser itself can't be reached: the caller's own
10
+ * connect then says so.
11
+ */
12
+ export declare function stuckTabs(endpoint: string, only?: Set<string>): Promise<StuckTab[]>;
13
+ /**
14
+ * Waits until every tab's page answers, saying which don't and why they
15
+ * might not. True at once when they all do, and when the browser can't
16
+ * be reached (the connect that follows says so). False when `until`
17
+ * resolves first (--stop-after-seconds, or the run's output closing).
18
+ */
19
+ export declare function waitForStuckTabs(endpoint: string, out: (line: string) => void, until?: Promise<void>): Promise<boolean>;