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.
- package/LICENSE +21 -0
- package/README.md +87 -2
- package/dist/bytes.js +37 -0
- package/dist/channel-placeholder.js +5 -0
- package/dist/collection-scan.js +17 -0
- package/dist/collections.html +353 -0
- package/dist/compare.html +358 -0
- package/dist/composed-source.js +205 -0
- package/dist/connect-prompt.html +47 -0
- package/dist/debug/icons.html +211 -0
- package/dist/debug/storage.html +163 -0
- package/dist/debug-log.js +75 -0
- package/dist/diagnostic-location.js +64 -0
- package/dist/eval-hook.js +308 -0
- package/dist/extension/background.js +5263 -0
- package/dist/extension/channel.js +133 -0
- package/dist/extension/code-links.js +187 -0
- package/dist/extension/code-tabs.js +460 -0
- package/dist/extension/collection-dialog.js +619 -0
- package/dist/extension/collection-display.js +322 -0
- package/dist/extension/collections.js +2905 -0
- package/dist/extension/compare.js +867 -0
- package/dist/extension/concurrency.js +186 -0
- package/dist/extension/connect-grants.js +109 -0
- package/dist/extension/connect-prompt-page.js +70 -0
- package/dist/extension/connect-prompt.js +266 -0
- package/dist/extension/copy-button.js +63 -0
- package/dist/extension/copy-dialog.js +502 -0
- package/dist/extension/copy-preference.js +66 -0
- package/dist/extension/debug/icons.js +203 -0
- package/dist/extension/debug/storage.js +365 -0
- package/dist/extension/deep-link.js +77 -0
- package/dist/extension/delete-dialog.js +432 -0
- package/dist/extension/delete-icon.js +40 -0
- package/dist/extension/details-tooltips.js +66 -0
- package/dist/extension/directories.js +679 -0
- package/dist/extension/export-backup.js +407 -0
- package/dist/extension/export-dialog.js +142 -0
- package/dist/extension/file-access.js +74 -0
- package/dist/extension/file-changes.js +346 -0
- package/dist/extension/frame-info.js +85 -0
- package/dist/extension/github-provider.js +336 -0
- package/dist/extension/glyph.js +84 -0
- package/dist/extension/gm-bindings.js +601 -0
- package/dist/extension/gm-download.js +419 -0
- package/dist/extension/gm-menu.js +228 -0
- package/dist/extension/gm-notification.js +141 -0
- package/dist/extension/gm-originator-map.js +23 -0
- package/dist/extension/gm-tab-data.js +121 -0
- package/dist/extension/gm-tab.js +160 -0
- package/dist/extension/gm-url-change.js +111 -0
- package/dist/extension/gm-url-scheme.js +27 -0
- package/dist/extension/gm-values.js +176 -0
- package/dist/extension/gm-window-control.js +104 -0
- package/dist/extension/gm-xhr-headers.js +333 -0
- package/dist/extension/gm-xhr.js +584 -0
- package/dist/extension/injector.js +3145 -0
- package/dist/extension/install-capture.js +277 -0
- package/dist/extension/line-diff.js +233 -0
- package/dist/extension/local-copy.js +560 -0
- package/dist/extension/local-delete.js +276 -0
- package/dist/extension/location-kinds.js +108 -0
- package/dist/extension/log-format.js +158 -0
- package/dist/extension/managed-changes.js +361 -0
- package/dist/extension/managed-items.js +57 -0
- package/dist/extension/match-index.js +375 -0
- package/dist/extension/missing-scripts.js +66 -0
- package/dist/extension/more-menu.js +109 -0
- package/dist/extension/move-dialog.js +197 -0
- package/dist/extension/offscreen.js +37 -0
- package/dist/extension/options.js +504 -0
- package/dist/extension/page-heading.js +170 -0
- package/dist/extension/page-links.js +199 -0
- package/dist/extension/parse-check-client.js +70 -0
- package/dist/extension/parse-check.js +161 -0
- package/dist/extension/parse-sandbox-host.js +172 -0
- package/dist/extension/parse-sandbox.js +175 -0
- package/dist/extension/popup-commands-open.js +41 -0
- package/dist/extension/popup-view.js +387 -0
- package/dist/extension/popup.js +703 -0
- package/dist/extension/prism-userscript.js +53 -0
- package/dist/extension/refresh-error.js +136 -0
- package/dist/extension/refresh-targets.js +160 -0
- package/dist/extension/related-versions.js +1144 -0
- package/dist/extension/resize-observer-loop.js +19 -0
- package/dist/extension/row-cache.js +387 -0
- package/dist/extension/script-blobs.js +413 -0
- package/dist/extension/script-details.js +658 -0
- package/dist/extension/script-enablement.js +285 -0
- package/dist/extension/script-identity.js +310 -0
- package/dist/extension/script-keys.js +54 -0
- package/dist/extension/script-log-id.js +155 -0
- package/dist/extension/script-log.js +290 -0
- package/dist/extension/script-rejection.js +104 -0
- package/dist/extension/script-sites.js +158 -0
- package/dist/extension/script-store.js +3507 -0
- package/dist/extension/script-updater.js +991 -0
- package/dist/extension/script-url-hash.js +61 -0
- package/dist/extension/select-all.js +37 -0
- package/dist/extension/select-open-guard.js +90 -0
- package/dist/extension/site-icon.js +1261 -0
- package/dist/extension/sm-dev-link.js +302 -0
- package/dist/extension/solo-click.js +58 -0
- package/dist/extension/sticky-numbers.js +188 -0
- package/dist/extension/storage-breakdown.js +253 -0
- package/dist/extension/storage-scan.js +86 -0
- package/dist/extension/storage-usage.js +194 -0
- package/dist/extension/structural-keys.js +29 -0
- package/dist/extension/sw-teardown.js +36 -0
- package/dist/extension/tab-status.js +403 -0
- package/dist/extension/toolbar-summary.js +105 -0
- package/dist/extension/user-scripts-access.js +43 -0
- package/dist/extension/userscript.js +3672 -0
- package/dist/extension/value-store.js +204 -0
- package/dist/extension/version-probe-fetch.js +68 -0
- package/dist/extension/worker-client.js +109 -0
- package/dist/extension/worker-ops.js +171 -0
- package/dist/extension/zip-writer.js +24 -0
- package/dist/external-files.js +878 -0
- package/dist/file-loader.js +436 -0
- package/dist/file-locations.js +439 -0
- package/dist/generated-wrappers.js +5 -0
- package/dist/github-auth.js +148 -0
- package/dist/github-urls.js +46 -0
- package/dist/gm-api-catalog.js +217 -0
- package/dist/gm-connect.js +273 -0
- package/dist/gm-xhr-types.js +5 -0
- package/dist/http-read.js +142 -0
- package/dist/icons/icon-128.png +0 -0
- package/dist/icons/icon-16.png +0 -0
- package/dist/icons/icon-48.png +0 -0
- package/dist/local-file-overrides.js +558 -0
- package/dist/manifest.json +38 -0
- package/dist/markup-response.js +126 -0
- package/dist/match-targets.js +305 -0
- package/dist/menu-captions.js +22 -0
- package/dist/offscreen.html +39 -0
- package/dist/options.html +197 -0
- package/dist/parse-check-protocol.js +51 -0
- package/dist/parse-location.js +171 -0
- package/dist/parse-sandbox.html +56 -0
- package/dist/phase-timing.js +155 -0
- package/dist/popup.html +197 -0
- package/dist/public-suffixes.js +6957 -0
- package/dist/run-context.js +31 -0
- package/dist/runtime-messages.js +34 -0
- package/dist/sandboxed-page.js +35 -0
- package/dist/script-build.js +787 -0
- package/dist/script-display.js +383 -0
- package/dist/script-icon.js +43 -0
- package/dist/script-links.js +204 -0
- package/dist/script-source-url.js +214 -0
- package/dist/script-world.js +65 -0
- package/dist/sm-dev-protocol.js +84 -0
- package/dist/source-map.js +177 -0
- package/dist/stack-link.js +138 -0
- package/dist/styles.css +825 -0
- package/dist/unsafe-window-use.js +229 -0
- package/dist/url-match.js +734 -0
- package/dist/userscript-header.js +248 -0
- package/dist/userscript.html +1165 -0
- package/dist/vendor/fflate.LICENSE +21 -0
- package/dist/vendor/fflate.js +2692 -0
- package/dist/vendor/prism/prism-line-numbers.css +1 -0
- package/dist/vendor/prism/prism-line-numbers.js +1 -0
- package/dist/vendor/prism/prism.css +1 -0
- package/dist/vendor/prism/prism.js +1946 -0
- package/dist/version-compare.js +141 -0
- package/dist/version-probe.js +120 -0
- package/lib/build-info.json +1 -0
- package/lib/bytes.d.ts +3 -0
- package/lib/bytes.js +37 -0
- package/lib/channel-placeholder.d.ts +1 -0
- package/lib/channel-placeholder.js +5 -0
- package/lib/collection-scan.d.ts +8 -0
- package/lib/collection-scan.js +17 -0
- package/lib/composed-source.d.ts +28 -0
- package/lib/composed-source.js +205 -0
- package/lib/debug-log.d.ts +9 -0
- package/lib/debug-log.js +75 -0
- package/lib/diagnostic-location.d.ts +8 -0
- package/lib/diagnostic-location.js +64 -0
- package/lib/eval-hook.d.ts +34 -0
- package/lib/eval-hook.js +308 -0
- package/lib/external-files.d.ts +111 -0
- package/lib/external-files.js +878 -0
- package/lib/file-loader.d.ts +48 -0
- package/lib/file-loader.js +436 -0
- package/lib/file-locations.d.ts +44 -0
- package/lib/file-locations.js +439 -0
- package/lib/generated-wrappers.d.ts +11 -0
- package/lib/generated-wrappers.js +5 -0
- package/lib/github-auth.d.ts +12 -0
- package/lib/github-auth.js +148 -0
- package/lib/github-urls.d.ts +1 -0
- package/lib/github-urls.js +46 -0
- package/lib/gm-api-catalog.d.ts +24 -0
- package/lib/gm-api-catalog.js +217 -0
- package/lib/gm-connect.d.ts +34 -0
- package/lib/gm-connect.js +273 -0
- package/lib/gm-xhr-types.d.ts +68 -0
- package/lib/gm-xhr-types.js +5 -0
- package/lib/harness/browser-extensions.d.ts +73 -0
- package/lib/harness/browser-extensions.js +285 -0
- package/lib/harness/browser-lang.d.ts +20 -0
- package/lib/harness/browser-lang.js +86 -0
- package/lib/harness/browser-launch.d.ts +14 -0
- package/lib/harness/browser-launch.js +184 -0
- package/lib/harness/checks.d.ts +110 -0
- package/lib/harness/checks.js +321 -0
- package/lib/harness/cli.d.ts +2 -0
- package/lib/harness/cli.js +2259 -0
- package/lib/harness/command-args.d.ts +91 -0
- package/lib/harness/command-args.js +369 -0
- package/lib/harness/command-lines.d.ts +80 -0
- package/lib/harness/command-lines.js +180 -0
- package/lib/harness/command-list.d.ts +206 -0
- package/lib/harness/command-list.js +1475 -0
- package/lib/harness/console-commands.d.ts +463 -0
- package/lib/harness/console-commands.js +3216 -0
- package/lib/harness/download-watch.d.ts +21 -0
- package/lib/harness/download-watch.js +156 -0
- package/lib/harness/extension-backend.d.ts +41 -0
- package/lib/harness/extension-backend.js +100 -0
- package/lib/harness/extension-host.d.ts +112 -0
- package/lib/harness/extension-host.js +552 -0
- package/lib/harness/extension-launch.d.ts +67 -0
- package/lib/harness/extension-launch.js +285 -0
- package/lib/harness/extension-link.d.ts +315 -0
- package/lib/harness/extension-link.js +607 -0
- package/lib/harness/external-manager.d.ts +146 -0
- package/lib/harness/external-manager.js +414 -0
- package/lib/harness/fixture.d.ts +165 -0
- package/lib/harness/fixture.js +461 -0
- package/lib/harness/frame-sessions.d.ts +71 -0
- package/lib/harness/frame-sessions.js +248 -0
- package/lib/harness/frames.d.ts +68 -0
- package/lib/harness/frames.js +216 -0
- package/lib/harness/help-format.d.ts +17 -0
- package/lib/harness/help-format.js +127 -0
- package/lib/harness/host.d.ts +352 -0
- package/lib/harness/host.js +423 -0
- package/lib/harness/index.d.ts +8 -0
- package/lib/harness/index.js +10 -0
- package/lib/harness/input-file.d.ts +18 -0
- package/lib/harness/input-file.js +61 -0
- package/lib/harness/json-output.d.ts +565 -0
- package/lib/harness/json-output.js +71 -0
- package/lib/harness/manager-actions.d.ts +21 -0
- package/lib/harness/manager-actions.js +129 -0
- package/lib/harness/manager-commands.d.ts +142 -0
- package/lib/harness/manager-commands.js +1196 -0
- package/lib/harness/manager-fetch.d.ts +33 -0
- package/lib/harness/manager-fetch.js +239 -0
- package/lib/harness/managers.d.ts +24 -0
- package/lib/harness/managers.js +27 -0
- package/lib/harness/output-style.d.ts +125 -0
- package/lib/harness/output-style.js +322 -0
- package/lib/harness/page-commands.d.ts +134 -0
- package/lib/harness/page-commands.js +1141 -0
- package/lib/harness/page-output.d.ts +67 -0
- package/lib/harness/page-output.js +139 -0
- package/lib/harness/page-watch.d.ts +171 -0
- package/lib/harness/page-watch.js +607 -0
- package/lib/harness/parse-check-node.d.ts +3 -0
- package/lib/harness/parse-check-node.js +132 -0
- package/lib/harness/path-args.d.ts +22 -0
- package/lib/harness/path-args.js +148 -0
- package/lib/harness/playwright-chromium.d.ts +16 -0
- package/lib/harness/playwright-chromium.js +86 -0
- package/lib/harness/prepare.d.ts +168 -0
- package/lib/harness/prepare.js +364 -0
- package/lib/harness/prompt-editor.d.ts +17 -0
- package/lib/harness/prompt-editor.js +354 -0
- package/lib/harness/prompt-history.d.ts +13 -0
- package/lib/harness/prompt-history.js +89 -0
- package/lib/harness/rebuild-advice.d.ts +6 -0
- package/lib/harness/rebuild-advice.js +39 -0
- package/lib/harness/run-log.d.ts +47 -0
- package/lib/harness/run-log.js +140 -0
- package/lib/harness/run-logs.d.ts +132 -0
- package/lib/harness/run-logs.js +241 -0
- package/lib/harness/run.d.ts +194 -0
- package/lib/harness/run.js +1973 -0
- package/lib/harness/runs.d.ts +229 -0
- package/lib/harness/runs.js +726 -0
- package/lib/harness/save-html.d.ts +39 -0
- package/lib/harness/save-html.js +89 -0
- package/lib/harness/saved-pages.d.ts +120 -0
- package/lib/harness/saved-pages.js +275 -0
- package/lib/harness/script-mark.d.ts +17 -0
- package/lib/harness/script-mark.js +56 -0
- package/lib/harness/snapshot.d.ts +143 -0
- package/lib/harness/snapshot.js +474 -0
- package/lib/harness/stuck-tabs.d.ts +19 -0
- package/lib/harness/stuck-tabs.js +131 -0
- package/lib/harness/tab-refs.d.ts +20 -0
- package/lib/harness/tab-refs.js +80 -0
- package/lib/harness/tampermonkey.d.ts +71 -0
- package/lib/harness/tampermonkey.js +356 -0
- package/lib/harness/tm-ids.d.ts +18 -0
- package/lib/harness/tm-ids.js +88 -0
- package/lib/harness/violentmonkey.d.ts +34 -0
- package/lib/harness/violentmonkey.js +162 -0
- package/lib/harness/wait.d.ts +77 -0
- package/lib/harness/wait.js +458 -0
- package/lib/http-read.d.ts +30 -0
- package/lib/http-read.js +142 -0
- package/lib/local-file-overrides.d.ts +35 -0
- package/lib/local-file-overrides.js +558 -0
- package/lib/manifest.json +38 -0
- package/lib/markup-response.d.ts +11 -0
- package/lib/markup-response.js +126 -0
- package/lib/match-targets.d.ts +18 -0
- package/lib/match-targets.js +305 -0
- package/lib/menu-captions.d.ts +1 -0
- package/lib/menu-captions.js +22 -0
- package/lib/parse-check-protocol.d.ts +27 -0
- package/lib/parse-check-protocol.js +51 -0
- package/lib/parse-location.d.ts +12 -0
- package/lib/parse-location.js +171 -0
- package/lib/phase-timing.d.ts +12 -0
- package/lib/phase-timing.js +155 -0
- package/lib/public-suffixes.d.ts +1 -0
- package/lib/public-suffixes.js +6957 -0
- package/lib/run-context.d.ts +2 -0
- package/lib/run-context.js +31 -0
- package/lib/runtime-messages.d.ts +10 -0
- package/lib/runtime-messages.js +34 -0
- package/lib/sandboxed-page.d.ts +5 -0
- package/lib/sandboxed-page.js +35 -0
- package/lib/script-build.d.ts +111 -0
- package/lib/script-build.js +787 -0
- package/lib/script-display.d.ts +51 -0
- package/lib/script-display.js +383 -0
- package/lib/script-icon.d.ts +16 -0
- package/lib/script-icon.js +43 -0
- package/lib/script-links.d.ts +24 -0
- package/lib/script-links.js +204 -0
- package/lib/script-source-url.d.ts +8 -0
- package/lib/script-source-url.js +214 -0
- package/lib/script-world.d.ts +6 -0
- package/lib/script-world.js +65 -0
- package/lib/sm-dev-protocol.d.ts +251 -0
- package/lib/sm-dev-protocol.js +84 -0
- package/lib/source-map.d.ts +32 -0
- package/lib/source-map.js +177 -0
- package/lib/stack-link.d.ts +15 -0
- package/lib/stack-link.js +138 -0
- package/lib/unsafe-window-use.d.ts +1 -0
- package/lib/unsafe-window-use.js +229 -0
- package/lib/url-match.d.ts +40 -0
- package/lib/url-match.js +734 -0
- package/lib/userscript-header.d.ts +33 -0
- package/lib/userscript-header.js +248 -0
- package/lib/version-compare.d.ts +6 -0
- package/lib/version-compare.js +141 -0
- package/lib/version-probe.d.ts +17 -0
- package/lib/version-probe.js +120 -0
- package/package.json +55 -3
- package/skills/SourceMonkey-dev/SKILL.md +868 -0
- package/skills/SourceMonkey-dev/references/cdp.md +293 -0
- package/skills/SourceMonkey-dev/references/commands.md +297 -0
- package/skills/SourceMonkey-dev/references/other-managers.md +63 -0
- package/skills/SourceMonkey-dev/references/specs.md +46 -0
- package/skills/SourceMonkey-friction/SKILL.md +103 -0
- 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>;
|