tabwright 1.0.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/bin.js +4 -0
- package/dist/a11y-client.js +204 -0
- package/dist/agent-skills/tabwright/SKILL.md +119 -0
- package/dist/aria-snapshot.d.ts +262 -0
- package/dist/aria-snapshot.d.ts.map +1 -0
- package/dist/aria-snapshot.js +1256 -0
- package/dist/aria-snapshot.js.map +1 -0
- package/dist/aria-snapshot.test.d.ts +2 -0
- package/dist/aria-snapshot.test.d.ts.map +1 -0
- package/dist/aria-snapshot.test.js +270 -0
- package/dist/aria-snapshot.test.js.map +1 -0
- package/dist/aria-snapshot.unit.test.d.ts +2 -0
- package/dist/aria-snapshot.unit.test.d.ts.map +1 -0
- package/dist/aria-snapshot.unit.test.js +542 -0
- package/dist/aria-snapshot.unit.test.js.map +1 -0
- package/dist/assets/cursors/screen-studio/pointer-macos-tahoe-data-url.d.ts +5 -0
- package/dist/assets/cursors/screen-studio/pointer-macos-tahoe-data-url.d.ts.map +1 -0
- package/dist/assets/cursors/screen-studio/pointer-macos-tahoe-data-url.js +5 -0
- package/dist/assets/cursors/screen-studio/pointer-macos-tahoe-data-url.js.map +1 -0
- package/dist/bippy.js +894 -0
- package/dist/browser-config.d.ts +13 -0
- package/dist/browser-config.d.ts.map +1 -0
- package/dist/browser-config.js +126 -0
- package/dist/browser-config.js.map +1 -0
- package/dist/browser-install.d.ts +16 -0
- package/dist/browser-install.d.ts.map +1 -0
- package/dist/browser-install.js +238 -0
- package/dist/browser-install.js.map +1 -0
- package/dist/browser-launch.d.ts +17 -0
- package/dist/browser-launch.d.ts.map +1 -0
- package/dist/browser-launch.js +44 -0
- package/dist/browser-launch.js.map +1 -0
- package/dist/builtin-capabilities.d.ts +28 -0
- package/dist/builtin-capabilities.d.ts.map +1 -0
- package/dist/builtin-capabilities.js +2673 -0
- package/dist/builtin-capabilities.js.map +1 -0
- package/dist/capability-agent-skill.d.ts +60 -0
- package/dist/capability-agent-skill.d.ts.map +1 -0
- package/dist/capability-agent-skill.js +225 -0
- package/dist/capability-agent-skill.js.map +1 -0
- package/dist/capability-auth-state.d.ts +34 -0
- package/dist/capability-auth-state.d.ts.map +1 -0
- package/dist/capability-auth-state.js +157 -0
- package/dist/capability-auth-state.js.map +1 -0
- package/dist/capability-auth.d.ts +36 -0
- package/dist/capability-auth.d.ts.map +1 -0
- package/dist/capability-auth.js +198 -0
- package/dist/capability-auth.js.map +1 -0
- package/dist/capability-cli-confirmation.test.d.ts +2 -0
- package/dist/capability-cli-confirmation.test.d.ts.map +1 -0
- package/dist/capability-cli-confirmation.test.js +79 -0
- package/dist/capability-cli-confirmation.test.js.map +1 -0
- package/dist/capability-options.d.ts +20 -0
- package/dist/capability-options.d.ts.map +1 -0
- package/dist/capability-options.js +31 -0
- package/dist/capability-options.js.map +1 -0
- package/dist/capability-options.test.d.ts +2 -0
- package/dist/capability-options.test.d.ts.map +1 -0
- package/dist/capability-options.test.js +81 -0
- package/dist/capability-options.test.js.map +1 -0
- package/dist/capability-package.d.ts +27 -0
- package/dist/capability-package.d.ts.map +1 -0
- package/dist/capability-package.js +444 -0
- package/dist/capability-package.js.map +1 -0
- package/dist/capability-package.test.d.ts +2 -0
- package/dist/capability-package.test.d.ts.map +1 -0
- package/dist/capability-package.test.js +179 -0
- package/dist/capability-package.test.js.map +1 -0
- package/dist/capability-registry.d.ts +262 -0
- package/dist/capability-registry.d.ts.map +1 -0
- package/dist/capability-registry.js +931 -0
- package/dist/capability-registry.js.map +1 -0
- package/dist/capability-registry.test.d.ts +2 -0
- package/dist/capability-registry.test.d.ts.map +1 -0
- package/dist/capability-registry.test.js +1683 -0
- package/dist/capability-registry.test.js.map +1 -0
- package/dist/capability-runner.d.ts +99 -0
- package/dist/capability-runner.d.ts.map +1 -0
- package/dist/capability-runner.js +608 -0
- package/dist/capability-runner.js.map +1 -0
- package/dist/capability-search-relevance.test.d.ts +2 -0
- package/dist/capability-search-relevance.test.d.ts.map +1 -0
- package/dist/capability-search-relevance.test.js +67 -0
- package/dist/capability-search-relevance.test.js.map +1 -0
- package/dist/capability-studio.d.ts +11 -0
- package/dist/capability-studio.d.ts.map +1 -0
- package/dist/capability-studio.js +593 -0
- package/dist/capability-studio.js.map +1 -0
- package/dist/capability-studio.test.d.ts +2 -0
- package/dist/capability-studio.test.d.ts.map +1 -0
- package/dist/capability-studio.test.js +41 -0
- package/dist/capability-studio.test.js.map +1 -0
- package/dist/cdp-log.d.ts +19 -0
- package/dist/cdp-log.d.ts.map +1 -0
- package/dist/cdp-log.js +83 -0
- package/dist/cdp-log.js.map +1 -0
- package/dist/cdp-log.test.d.ts +2 -0
- package/dist/cdp-log.test.d.ts.map +1 -0
- package/dist/cdp-log.test.js +109 -0
- package/dist/cdp-log.test.js.map +1 -0
- package/dist/cdp-relay.d.ts +19 -0
- package/dist/cdp-relay.d.ts.map +1 -0
- package/dist/cdp-relay.js +2375 -0
- package/dist/cdp-relay.js.map +1 -0
- package/dist/cdp-session.d.ts +37 -0
- package/dist/cdp-session.d.ts.map +1 -0
- package/dist/cdp-session.js +36 -0
- package/dist/cdp-session.js.map +1 -0
- package/dist/cdp-types.d.ts +65 -0
- package/dist/cdp-types.d.ts.map +1 -0
- package/dist/cdp-types.js +91 -0
- package/dist/cdp-types.js.map +1 -0
- package/dist/channel-owner-inspect.test.d.ts +2 -0
- package/dist/channel-owner-inspect.test.d.ts.map +1 -0
- package/dist/channel-owner-inspect.test.js +75 -0
- package/dist/channel-owner-inspect.test.js.map +1 -0
- package/dist/chrome-discovery.d.ts +65 -0
- package/dist/chrome-discovery.d.ts.map +1 -0
- package/dist/chrome-discovery.js +173 -0
- package/dist/chrome-discovery.js.map +1 -0
- package/dist/chrome-discovery.test.d.ts +2 -0
- package/dist/chrome-discovery.test.d.ts.map +1 -0
- package/dist/chrome-discovery.test.js +41 -0
- package/dist/chrome-discovery.test.js.map +1 -0
- package/dist/clean-html.d.ts +11 -0
- package/dist/clean-html.d.ts.map +1 -0
- package/dist/clean-html.js +106 -0
- package/dist/clean-html.js.map +1 -0
- package/dist/cli-help.test.d.ts +2 -0
- package/dist/cli-help.test.d.ts.map +1 -0
- package/dist/cli-help.test.js +98 -0
- package/dist/cli-help.test.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +2709 -0
- package/dist/cli.js.map +1 -0
- package/dist/cloud-client.d.ts +56 -0
- package/dist/cloud-client.d.ts.map +1 -0
- package/dist/cloud-client.js +127 -0
- package/dist/cloud-client.js.map +1 -0
- package/dist/create-logger.d.ts +9 -0
- package/dist/create-logger.d.ts.map +1 -0
- package/dist/create-logger.js +27 -0
- package/dist/create-logger.js.map +1 -0
- package/dist/debugger-api.md +458 -0
- package/dist/debugger-examples-types.d.ts +24 -0
- package/dist/debugger-examples-types.d.ts.map +1 -0
- package/dist/debugger-examples-types.js +2 -0
- package/dist/debugger-examples-types.js.map +1 -0
- package/dist/debugger-examples.d.ts +6 -0
- package/dist/debugger-examples.d.ts.map +1 -0
- package/dist/debugger-examples.js +53 -0
- package/dist/debugger-examples.js.map +1 -0
- package/dist/debugger.d.ts +381 -0
- package/dist/debugger.d.ts.map +1 -0
- package/dist/debugger.js +630 -0
- package/dist/debugger.js.map +1 -0
- package/dist/diff-utils.d.ts +20 -0
- package/dist/diff-utils.d.ts.map +1 -0
- package/dist/diff-utils.js +50 -0
- package/dist/diff-utils.js.map +1 -0
- package/dist/diff-utils.test.d.ts +2 -0
- package/dist/diff-utils.test.d.ts.map +1 -0
- package/dist/diff-utils.test.js +137 -0
- package/dist/diff-utils.test.js.map +1 -0
- package/dist/doctor.d.ts +44 -0
- package/dist/doctor.d.ts.map +1 -0
- package/dist/doctor.js +246 -0
- package/dist/doctor.js.map +1 -0
- package/dist/doctor.test.d.ts +2 -0
- package/dist/doctor.test.d.ts.map +1 -0
- package/dist/doctor.test.js +208 -0
- package/dist/doctor.test.js.map +1 -0
- package/dist/editor-api.md +374 -0
- package/dist/editor-examples.d.ts +11 -0
- package/dist/editor-examples.d.ts.map +1 -0
- package/dist/editor-examples.js +124 -0
- package/dist/editor-examples.js.map +1 -0
- package/dist/editor.d.ts +203 -0
- package/dist/editor.d.ts.map +1 -0
- package/dist/editor.js +363 -0
- package/dist/editor.js.map +1 -0
- package/dist/env-compat.d.ts +2 -0
- package/dist/env-compat.d.ts.map +1 -0
- package/dist/env-compat.js +27 -0
- package/dist/env-compat.js.map +1 -0
- package/dist/executor.d.ts +229 -0
- package/dist/executor.d.ts.map +1 -0
- package/dist/executor.js +1465 -0
- package/dist/executor.js.map +1 -0
- package/dist/executor.unit.test.d.ts +2 -0
- package/dist/executor.unit.test.d.ts.map +1 -0
- package/dist/executor.unit.test.js +143 -0
- package/dist/executor.unit.test.js.map +1 -0
- package/dist/extension/_locales/en/messages.json +278 -0
- package/dist/extension/_locales/zh_CN/messages.json +256 -0
- package/dist/extension/assets/options-CYH96Lcx.css +1349 -0
- package/dist/extension/background.js +3994 -0
- package/dist/extension/icons/SolarCursorSquareBold.png +0 -0
- package/dist/extension/icons/icon-black-128.png +0 -0
- package/dist/extension/icons/icon-black-16.png +0 -0
- package/dist/extension/icons/icon-black-32.png +0 -0
- package/dist/extension/icons/icon-black-48.png +0 -0
- package/dist/extension/icons/icon-gray-128.png +0 -0
- package/dist/extension/icons/icon-gray-16.png +0 -0
- package/dist/extension/icons/icon-gray-32.png +0 -0
- package/dist/extension/icons/icon-gray-48.png +0 -0
- package/dist/extension/icons/icon-green-128.png +0 -0
- package/dist/extension/icons/icon-green-16.png +0 -0
- package/dist/extension/icons/icon-green-32.png +0 -0
- package/dist/extension/icons/icon-green-48.png +0 -0
- package/dist/extension/icons/tabwright-icon-black.png +0 -0
- package/dist/extension/icons/tabwright-icon-gray-disabled.png +0 -0
- package/dist/extension/icons/tabwright-icon-gray-disabled.svg +1 -0
- package/dist/extension/manifest.json +54 -0
- package/dist/extension/options.js +16145 -0
- package/dist/extension/rrweb-recorder.js +12952 -0
- package/dist/extension/src/options.html +165 -0
- package/dist/extension/src/prism-bash.min.js +1 -0
- package/dist/extension/src/prism.min.js +1 -0
- package/dist/extension/src/welcome.html +396 -0
- package/dist/extension-connection.test.d.ts +2 -0
- package/dist/extension-connection.test.d.ts.map +1 -0
- package/dist/extension-connection.test.js +708 -0
- package/dist/extension-connection.test.js.map +1 -0
- package/dist/extension-icon-warning.test.d.ts +2 -0
- package/dist/extension-icon-warning.test.d.ts.map +1 -0
- package/dist/extension-icon-warning.test.js +33 -0
- package/dist/extension-icon-warning.test.js.map +1 -0
- package/dist/ghost-browser.d.ts +62 -0
- package/dist/ghost-browser.d.ts.map +1 -0
- package/dist/ghost-browser.js +107 -0
- package/dist/ghost-browser.js.map +1 -0
- package/dist/ghost-cursor-client.js +374 -0
- package/dist/ghost-cursor-controller.d.ts +46 -0
- package/dist/ghost-cursor-controller.d.ts.map +1 -0
- package/dist/ghost-cursor-controller.js +98 -0
- package/dist/ghost-cursor-controller.js.map +1 -0
- package/dist/ghost-cursor.d.ts +27 -0
- package/dist/ghost-cursor.d.ts.map +1 -0
- package/dist/ghost-cursor.js +79 -0
- package/dist/ghost-cursor.js.map +1 -0
- package/dist/htmlrewrite.d.ts +8 -0
- package/dist/htmlrewrite.d.ts.map +1 -0
- package/dist/htmlrewrite.js +339 -0
- package/dist/htmlrewrite.js.map +1 -0
- package/dist/htmlrewrite.test.d.ts +2 -0
- package/dist/htmlrewrite.test.d.ts.map +1 -0
- package/dist/htmlrewrite.test.js +7975 -0
- package/dist/htmlrewrite.test.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/kill-port.d.ts +29 -0
- package/dist/kill-port.d.ts.map +1 -0
- package/dist/kill-port.js +190 -0
- package/dist/kill-port.js.map +1 -0
- package/dist/kitty-graphics.d.ts +22 -0
- package/dist/kitty-graphics.d.ts.map +1 -0
- package/dist/kitty-graphics.js +71 -0
- package/dist/kitty-graphics.js.map +1 -0
- package/dist/kitty-graphics.test.d.ts +2 -0
- package/dist/kitty-graphics.test.d.ts.map +1 -0
- package/dist/kitty-graphics.test.js +125 -0
- package/dist/kitty-graphics.test.js.map +1 -0
- package/dist/locator-selector.test.d.ts +2 -0
- package/dist/locator-selector.test.d.ts.map +1 -0
- package/dist/locator-selector.test.js +98 -0
- package/dist/locator-selector.test.js.map +1 -0
- package/dist/mcp-client.d.ts +20 -0
- package/dist/mcp-client.d.ts.map +1 -0
- package/dist/mcp-client.js +56 -0
- package/dist/mcp-client.js.map +1 -0
- package/dist/mcp.d.ts +6 -0
- package/dist/mcp.d.ts.map +1 -0
- package/dist/mcp.js +511 -0
- package/dist/mcp.js.map +1 -0
- package/dist/on-mouse-action.test.d.ts +2 -0
- package/dist/on-mouse-action.test.d.ts.map +1 -0
- package/dist/on-mouse-action.test.js +180 -0
- package/dist/on-mouse-action.test.js.map +1 -0
- package/dist/options-page.test.d.ts +2 -0
- package/dist/options-page.test.d.ts.map +1 -0
- package/dist/options-page.test.js +773 -0
- package/dist/options-page.test.js.map +1 -0
- package/dist/package-paths.d.ts +4 -0
- package/dist/package-paths.d.ts.map +1 -0
- package/dist/package-paths.js +30 -0
- package/dist/package-paths.js.map +1 -0
- package/dist/page-markdown.d.ts +40 -0
- package/dist/page-markdown.d.ts.map +1 -0
- package/dist/page-markdown.js +181 -0
- package/dist/page-markdown.js.map +1 -0
- package/dist/performance-examples.d.ts +5 -0
- package/dist/performance-examples.d.ts.map +1 -0
- package/dist/performance-examples.js +112 -0
- package/dist/performance-examples.js.map +1 -0
- package/dist/performance-profiling.md +417 -0
- package/dist/playwright-import.d.ts +19 -0
- package/dist/playwright-import.d.ts.map +1 -0
- package/dist/playwright-import.js +40 -0
- package/dist/playwright-import.js.map +1 -0
- package/dist/popup-relocation.test.d.ts +7 -0
- package/dist/popup-relocation.test.d.ts.map +1 -0
- package/dist/popup-relocation.test.js +139 -0
- package/dist/popup-relocation.test.js.map +1 -0
- package/dist/product-paths.d.ts +9 -0
- package/dist/product-paths.d.ts.map +1 -0
- package/dist/product-paths.js +24 -0
- package/dist/product-paths.js.map +1 -0
- package/dist/product-paths.test.d.ts +2 -0
- package/dist/product-paths.test.d.ts.map +1 -0
- package/dist/product-paths.test.js +39 -0
- package/dist/product-paths.test.js.map +1 -0
- package/dist/prompt.md +1004 -0
- package/dist/protocol.d.ts +248 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +59 -0
- package/dist/protocol.js.map +1 -0
- package/dist/protocol.test.d.ts +2 -0
- package/dist/protocol.test.d.ts.map +1 -0
- package/dist/protocol.test.js +31 -0
- package/dist/protocol.test.js.map +1 -0
- package/dist/react-source.d.ts +57 -0
- package/dist/react-source.d.ts.map +1 -0
- package/dist/react-source.js +254 -0
- package/dist/react-source.js.map +1 -0
- package/dist/readability.js +1674 -0
- package/dist/relay-client.d.ts +104 -0
- package/dist/relay-client.d.ts.map +1 -0
- package/dist/relay-client.js +426 -0
- package/dist/relay-client.js.map +1 -0
- package/dist/relay-client.test.d.ts +2 -0
- package/dist/relay-client.test.d.ts.map +1 -0
- package/dist/relay-client.test.js +305 -0
- package/dist/relay-client.test.js.map +1 -0
- package/dist/relay-core.test.d.ts +2 -0
- package/dist/relay-core.test.d.ts.map +1 -0
- package/dist/relay-core.test.js +1427 -0
- package/dist/relay-core.test.js.map +1 -0
- package/dist/relay-navigation.test.d.ts +2 -0
- package/dist/relay-navigation.test.d.ts.map +1 -0
- package/dist/relay-navigation.test.js +661 -0
- package/dist/relay-navigation.test.js.map +1 -0
- package/dist/relay-session.test.d.ts +2 -0
- package/dist/relay-session.test.d.ts.map +1 -0
- package/dist/relay-session.test.js +1121 -0
- package/dist/relay-session.test.js.map +1 -0
- package/dist/relay-state.d.ts +178 -0
- package/dist/relay-state.d.ts.map +1 -0
- package/dist/relay-state.js +346 -0
- package/dist/relay-state.js.map +1 -0
- package/dist/relay-state.test.d.ts +2 -0
- package/dist/relay-state.test.d.ts.map +1 -0
- package/dist/relay-state.test.js +594 -0
- package/dist/relay-state.test.js.map +1 -0
- package/dist/replay-ai-index.d.ts +99 -0
- package/dist/replay-ai-index.d.ts.map +1 -0
- package/dist/replay-ai-index.js +677 -0
- package/dist/replay-ai-index.js.map +1 -0
- package/dist/replay-ai-index.test.d.ts +2 -0
- package/dist/replay-ai-index.test.d.ts.map +1 -0
- package/dist/replay-ai-index.test.js +255 -0
- package/dist/replay-ai-index.test.js.map +1 -0
- package/dist/replay-cli.test.d.ts +2 -0
- package/dist/replay-cli.test.d.ts.map +1 -0
- package/dist/replay-cli.test.js +275 -0
- package/dist/replay-cli.test.js.map +1 -0
- package/dist/replay-eval.d.ts +44 -0
- package/dist/replay-eval.d.ts.map +1 -0
- package/dist/replay-eval.js +902 -0
- package/dist/replay-eval.js.map +1 -0
- package/dist/replay-handoff.d.ts +29 -0
- package/dist/replay-handoff.d.ts.map +1 -0
- package/dist/replay-handoff.js +83 -0
- package/dist/replay-handoff.js.map +1 -0
- package/dist/replay-handoff.test.d.ts +2 -0
- package/dist/replay-handoff.test.d.ts.map +1 -0
- package/dist/replay-handoff.test.js +123 -0
- package/dist/replay-handoff.test.js.map +1 -0
- package/dist/replay-workflow-compiler.d.ts +44 -0
- package/dist/replay-workflow-compiler.d.ts.map +1 -0
- package/dist/replay-workflow-compiler.js +601 -0
- package/dist/replay-workflow-compiler.js.map +1 -0
- package/dist/replay-workflow-compiler.test.d.ts +2 -0
- package/dist/replay-workflow-compiler.test.d.ts.map +1 -0
- package/dist/replay-workflow-compiler.test.js +223 -0
- package/dist/replay-workflow-compiler.test.js.map +1 -0
- package/dist/rrweb-recording-relay.d.ts +56 -0
- package/dist/rrweb-recording-relay.d.ts.map +1 -0
- package/dist/rrweb-recording-relay.js +299 -0
- package/dist/rrweb-recording-relay.js.map +1 -0
- package/dist/rrweb-recording-relay.test.d.ts +2 -0
- package/dist/rrweb-recording-relay.test.d.ts.map +1 -0
- package/dist/rrweb-recording-relay.test.js +85 -0
- package/dist/rrweb-recording-relay.test.js.map +1 -0
- package/dist/rrweb-recording.d.ts +104 -0
- package/dist/rrweb-recording.d.ts.map +1 -0
- package/dist/rrweb-recording.js +180 -0
- package/dist/rrweb-recording.js.map +1 -0
- package/dist/scoped-fs.d.ts +95 -0
- package/dist/scoped-fs.d.ts.map +1 -0
- package/dist/scoped-fs.js +358 -0
- package/dist/scoped-fs.js.map +1 -0
- package/dist/scoped-fs.test.d.ts +5 -0
- package/dist/scoped-fs.test.d.ts.map +1 -0
- package/dist/scoped-fs.test.js +73 -0
- package/dist/scoped-fs.test.js.map +1 -0
- package/dist/selector-generator.js +7378 -0
- package/dist/skill-stub.test.d.ts +2 -0
- package/dist/skill-stub.test.d.ts.map +1 -0
- package/dist/skill-stub.test.js +22 -0
- package/dist/skill-stub.test.js.map +1 -0
- package/dist/snapshot-tools.test.d.ts +2 -0
- package/dist/snapshot-tools.test.d.ts.map +1 -0
- package/dist/snapshot-tools.test.js +856 -0
- package/dist/snapshot-tools.test.js.map +1 -0
- package/dist/start-relay-server.d.ts +6 -0
- package/dist/start-relay-server.d.ts.map +1 -0
- package/dist/start-relay-server.js +57 -0
- package/dist/start-relay-server.js.map +1 -0
- package/dist/styles-api.md +124 -0
- package/dist/styles-examples.d.ts +8 -0
- package/dist/styles-examples.d.ts.map +1 -0
- package/dist/styles-examples.js +64 -0
- package/dist/styles-examples.js.map +1 -0
- package/dist/styles.d.ts +27 -0
- package/dist/styles.d.ts.map +1 -0
- package/dist/styles.js +231 -0
- package/dist/styles.js.map +1 -0
- package/dist/tabwright-agent-skill.d.ts +30 -0
- package/dist/tabwright-agent-skill.d.ts.map +1 -0
- package/dist/tabwright-agent-skill.js +74 -0
- package/dist/tabwright-agent-skill.js.map +1 -0
- package/dist/tabwright-agent-skill.test.d.ts +2 -0
- package/dist/tabwright-agent-skill.test.d.ts.map +1 -0
- package/dist/tabwright-agent-skill.test.js +51 -0
- package/dist/tabwright-agent-skill.test.js.map +1 -0
- package/dist/test-declarations.d.ts +13 -0
- package/dist/test-declarations.d.ts.map +1 -0
- package/dist/test-declarations.js +2 -0
- package/dist/test-declarations.js.map +1 -0
- package/dist/test-utils.d.ts +69 -0
- package/dist/test-utils.d.ts.map +1 -0
- package/dist/test-utils.js +448 -0
- package/dist/test-utils.js.map +1 -0
- package/dist/test-utils.test.d.ts +2 -0
- package/dist/test-utils.test.d.ts.map +1 -0
- package/dist/test-utils.test.js +55 -0
- package/dist/test-utils.test.js.map +1 -0
- package/dist/utils.d.ts +26 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +60 -0
- package/dist/utils.js.map +1 -0
- package/dist/wait-for-page-load.d.ts +16 -0
- package/dist/wait-for-page-load.d.ts.map +1 -0
- package/dist/wait-for-page-load.js +146 -0
- package/dist/wait-for-page-load.js.map +1 -0
- package/dist/workflow-capability.d.ts +72 -0
- package/dist/workflow-capability.d.ts.map +1 -0
- package/dist/workflow-capability.js +316 -0
- package/dist/workflow-capability.js.map +1 -0
- package/dist/workflow-script-flow.test.d.ts +2 -0
- package/dist/workflow-script-flow.test.d.ts.map +1 -0
- package/dist/workflow-script-flow.test.js +285 -0
- package/dist/workflow-script-flow.test.js.map +1 -0
- package/package.json +80 -0
- package/src/__snapshots__/x.com.processed.html +1003 -0
- package/src/__snapshots__/x.com.processed.withStyles.html +2720 -0
- package/src/a11y-client.ts +241 -0
- package/src/agent-skills/conan-config/SKILL.md +141 -0
- package/src/agent-skills/conan-config/agents/openai.yaml +4 -0
- package/src/aria-snapshot.test.ts +311 -0
- package/src/aria-snapshot.ts +1681 -0
- package/src/aria-snapshot.unit.test.ts +615 -0
- package/src/aria-snapshots/github-interactive.txt +193 -0
- package/src/aria-snapshots/github-raw.txt +309 -0
- package/src/aria-snapshots/hackernews-interactive.txt +477 -0
- package/src/aria-snapshots/hackernews-raw.txt +428 -0
- package/src/aria-snapshots/prosemirror-interactive.txt +66 -0
- package/src/aria-snapshots/prosemirror-raw.txt +145 -0
- package/src/assets/aria-labels-hacker-news.png +0 -0
- package/src/assets/aria-labels-old-reddit.png +0 -0
- package/src/assets/cursors/screen-studio/pointer-macos-tahoe-data-url.ts +5 -0
- package/src/assets/cursors/screen-studio/pointer-macos-tahoe.svg +18 -0
- package/src/assets/x.com.html +32946 -0
- package/src/browser-config.ts +179 -0
- package/src/browser-install.ts +284 -0
- package/src/browser-launch.ts +75 -0
- package/src/builtin-capabilities.ts +2765 -0
- package/src/capability-agent-skill.ts +305 -0
- package/src/capability-auth-state.ts +197 -0
- package/src/capability-auth.ts +249 -0
- package/src/capability-cli-confirmation.test.ts +90 -0
- package/src/capability-options.test.ts +89 -0
- package/src/capability-options.ts +57 -0
- package/src/capability-package.test.ts +204 -0
- package/src/capability-package.ts +555 -0
- package/src/capability-registry.test.ts +1905 -0
- package/src/capability-registry.ts +1234 -0
- package/src/capability-runner.ts +801 -0
- package/src/capability-search-relevance.test.ts +73 -0
- package/src/capability-studio.test.ts +45 -0
- package/src/capability-studio.ts +641 -0
- package/src/cdp-log.test.ts +131 -0
- package/src/cdp-log.ts +112 -0
- package/src/cdp-relay.ts +2944 -0
- package/src/cdp-session.ts +77 -0
- package/src/cdp-types.ts +158 -0
- package/src/channel-owner-inspect.test.ts +94 -0
- package/src/chrome-discovery.test.ts +50 -0
- package/src/chrome-discovery.ts +220 -0
- package/src/clean-html.ts +141 -0
- package/src/cli-help.test.ts +123 -0
- package/src/cli.ts +3181 -0
- package/src/cloud-client.ts +179 -0
- package/src/create-logger.ts +38 -0
- package/src/debugger-examples-types.ts +16 -0
- package/src/debugger-examples.ts +66 -0
- package/src/debugger.ts +708 -0
- package/src/diff-utils.test.ts +152 -0
- package/src/diff-utils.ts +70 -0
- package/src/doctor.test.ts +244 -0
- package/src/doctor.ts +321 -0
- package/src/editor-examples.ts +158 -0
- package/src/editor.ts +426 -0
- package/src/env-compat.ts +28 -0
- package/src/executor.ts +1824 -0
- package/src/executor.unit.test.ts +168 -0
- package/src/extension-connection.test.ts +855 -0
- package/src/extension-icon-warning.test.ts +45 -0
- package/src/ghost-browser.ts +149 -0
- package/src/ghost-cursor-client.ts +494 -0
- package/src/ghost-cursor-controller.ts +129 -0
- package/src/ghost-cursor.ts +123 -0
- package/src/htmlrewrite.test.ts +8008 -0
- package/src/htmlrewrite.ts +390 -0
- package/src/index.ts +53 -0
- package/src/kill-port.ts +219 -0
- package/src/kitty-graphics.test.ts +139 -0
- package/src/kitty-graphics.ts +79 -0
- package/src/locator-selector.test.ts +119 -0
- package/src/mcp-client.ts +78 -0
- package/src/mcp.ts +629 -0
- package/src/on-mouse-action.test.ts +226 -0
- package/src/options-page.test.ts +839 -0
- package/src/package-paths.ts +38 -0
- package/src/page-markdown.ts +240 -0
- package/src/performance-examples.ts +186 -0
- package/src/playwright-import.ts +59 -0
- package/src/popup-relocation.test.ts +163 -0
- package/src/product-paths.test.ts +47 -0
- package/src/product-paths.ts +28 -0
- package/src/protocol.test.ts +52 -0
- package/src/protocol.ts +351 -0
- package/src/react-source.ts +379 -0
- package/src/relay-client.test.ts +375 -0
- package/src/relay-client.ts +572 -0
- package/src/relay-core.test.ts +1603 -0
- package/src/relay-navigation.test.ts +790 -0
- package/src/relay-session.test.ts +1375 -0
- package/src/relay-state.test.ts +729 -0
- package/src/relay-state.ts +561 -0
- package/src/replay-ai-index.test.ts +278 -0
- package/src/replay-ai-index.ts +872 -0
- package/src/replay-cli.test.ts +325 -0
- package/src/replay-eval.ts +1039 -0
- package/src/replay-handoff.test.ts +163 -0
- package/src/replay-handoff.ts +109 -0
- package/src/replay-workflow-compiler.test.ts +250 -0
- package/src/replay-workflow-compiler.ts +700 -0
- package/src/resource.md +409 -0
- package/src/rrweb-recording-relay.test.ts +99 -0
- package/src/rrweb-recording-relay.ts +400 -0
- package/src/rrweb-recording.ts +322 -0
- package/src/scoped-fs.test.ts +80 -0
- package/src/scoped-fs.ts +420 -0
- package/src/skill-stub.test.ts +24 -0
- package/src/skill.md +1413 -0
- package/src/snapshot-tools.test.ts +1017 -0
- package/src/snapshots/hacker-news-accessibility-full.md +81 -0
- package/src/snapshots/hacker-news-accessibility-interactive.md +43 -0
- package/src/snapshots/page-markdown-output.txt +16 -0
- package/src/snapshots/shadcn-ui-accessibility-full.md +201 -0
- package/src/snapshots/shadcn-ui-accessibility-interactive.md +109 -0
- package/src/start-relay-server.ts +69 -0
- package/src/styles-examples.ts +84 -0
- package/src/styles.ts +343 -0
- package/src/tabwright-agent-skill.test.ts +61 -0
- package/src/tabwright-agent-skill.ts +105 -0
- package/src/test-declarations.ts +13 -0
- package/src/test-utils.test.ts +70 -0
- package/src/test-utils.ts +566 -0
- package/src/utils.ts +78 -0
- package/src/wait-for-page-load.ts +198 -0
- package/src/workflow-capability.ts +417 -0
- package/src/workflow-script-flow.test.ts +388 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import { fileURLToPath } from 'node:url'
|
|
4
|
+
import { createRequire } from 'node:module'
|
|
5
|
+
|
|
6
|
+
const require = createRequire(import.meta.url)
|
|
7
|
+
|
|
8
|
+
export function getInstalledTabwrightPackageDir(): string {
|
|
9
|
+
const localPackageDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
|
|
10
|
+
const localPackageJsonPath = path.join(localPackageDir, 'package.json')
|
|
11
|
+
if (fs.existsSync(localPackageJsonPath)) {
|
|
12
|
+
return localPackageDir
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const packageJsonPath = require.resolve('tabwright/package.json')
|
|
16
|
+
return path.dirname(packageJsonPath)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function getBundledExtensionPath(): string {
|
|
20
|
+
const packageDir = getInstalledTabwrightPackageDir()
|
|
21
|
+
const candidates = [
|
|
22
|
+
path.join(packageDir, 'dist', 'extension'),
|
|
23
|
+
path.join(packageDir, '..', 'extension', 'dist'),
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
for (const extensionPath of candidates) {
|
|
27
|
+
const manifestPath = path.join(extensionPath, 'manifest.json')
|
|
28
|
+
if (fs.existsSync(manifestPath)) {
|
|
29
|
+
return extensionPath
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
throw new Error(
|
|
34
|
+
`Bundled Tabwright extension not found under ${packageDir}. Rebuild or reinstall the tabwright package.`,
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const getInstalledPlaywriterPackageDir = getInstalledTabwrightPackageDir
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extract page content as markdown using Mozilla Readability.
|
|
3
|
+
*
|
|
4
|
+
* This utility injects the Readability library into the page and extracts
|
|
5
|
+
* the main content, similar to Firefox Reader View.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import fs from 'node:fs'
|
|
9
|
+
import path from 'node:path'
|
|
10
|
+
import { fileURLToPath } from 'node:url'
|
|
11
|
+
import type { Page } from '@xmorse/playwright-core'
|
|
12
|
+
import { createSmartDiff } from './diff-utils.js'
|
|
13
|
+
|
|
14
|
+
export interface PageMarkdownResult {
|
|
15
|
+
/** Extracted content as plain text (HTML tags stripped) */
|
|
16
|
+
content: string
|
|
17
|
+
/** Article title */
|
|
18
|
+
title: string | null
|
|
19
|
+
/** Article author/byline */
|
|
20
|
+
author: string | null
|
|
21
|
+
/** Article excerpt/description */
|
|
22
|
+
excerpt: string | null
|
|
23
|
+
/** Site name */
|
|
24
|
+
siteName: string | null
|
|
25
|
+
/** Content language */
|
|
26
|
+
lang: string | null
|
|
27
|
+
/** Published time */
|
|
28
|
+
publishedTime: string | null
|
|
29
|
+
/** Word count */
|
|
30
|
+
wordCount: number
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface GetPageMarkdownOptions {
|
|
34
|
+
page: Page
|
|
35
|
+
/** String or regex to filter content (returns matching lines with context) */
|
|
36
|
+
search?: string | RegExp
|
|
37
|
+
/** Return diff since last call for this page */
|
|
38
|
+
showDiffSinceLastCall?: boolean
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Cache for the bundled readability code
|
|
42
|
+
let readabilityCode: string | null = null
|
|
43
|
+
|
|
44
|
+
function getReadabilityCode(): string {
|
|
45
|
+
if (readabilityCode) {
|
|
46
|
+
return readabilityCode
|
|
47
|
+
}
|
|
48
|
+
const currentDir = path.dirname(fileURLToPath(import.meta.url))
|
|
49
|
+
const readabilityPath = path.join(currentDir, '..', 'dist', 'readability.js')
|
|
50
|
+
readabilityCode = fs.readFileSync(readabilityPath, 'utf-8')
|
|
51
|
+
return readabilityCode
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Store last snapshots per page for diffing
|
|
55
|
+
const lastMarkdownSnapshots: WeakMap<Page, string> = new WeakMap()
|
|
56
|
+
|
|
57
|
+
function isRegExp(value: unknown): value is RegExp {
|
|
58
|
+
return (
|
|
59
|
+
typeof value === 'object' &&
|
|
60
|
+
value !== null &&
|
|
61
|
+
typeof (value as RegExp).test === 'function' &&
|
|
62
|
+
typeof (value as RegExp).exec === 'function'
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Extract page content as markdown using Mozilla Readability.
|
|
68
|
+
*
|
|
69
|
+
* Injects Readability into the page if not already present, then extracts
|
|
70
|
+
* the main content. Returns plain text content (no HTML).
|
|
71
|
+
*/
|
|
72
|
+
export async function getPageMarkdown(options: GetPageMarkdownOptions): Promise<string> {
|
|
73
|
+
const { page, search, showDiffSinceLastCall = !search } = options
|
|
74
|
+
|
|
75
|
+
// Check if readability is already injected
|
|
76
|
+
const hasReadability = await page.evaluate(() => !!(globalThis as any).__readability)
|
|
77
|
+
|
|
78
|
+
if (!hasReadability) {
|
|
79
|
+
const code = getReadabilityCode()
|
|
80
|
+
await page.evaluate(code)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Extract content using Readability
|
|
84
|
+
const result = (await page.evaluate(() => {
|
|
85
|
+
const readability = (globalThis as any).__readability
|
|
86
|
+
if (!readability) {
|
|
87
|
+
throw new Error('Readability not loaded')
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Clone document to avoid modifying the original
|
|
91
|
+
const documentClone = document.cloneNode(true)
|
|
92
|
+
|
|
93
|
+
// Check if page is probably readable
|
|
94
|
+
if (!readability.isProbablyReaderable(documentClone)) {
|
|
95
|
+
return {
|
|
96
|
+
content: document.body?.innerText || '',
|
|
97
|
+
title: document.title || null,
|
|
98
|
+
author: null,
|
|
99
|
+
excerpt: null,
|
|
100
|
+
siteName: null,
|
|
101
|
+
lang: document.documentElement?.lang || null,
|
|
102
|
+
publishedTime: null,
|
|
103
|
+
wordCount: (document.body?.innerText || '').split(/\s+/).filter(Boolean).length,
|
|
104
|
+
_notReadable: true,
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const article = new readability.Readability(documentClone).parse()
|
|
109
|
+
|
|
110
|
+
if (!article) {
|
|
111
|
+
return {
|
|
112
|
+
content: document.body?.innerText || '',
|
|
113
|
+
title: document.title || null,
|
|
114
|
+
author: null,
|
|
115
|
+
excerpt: null,
|
|
116
|
+
siteName: null,
|
|
117
|
+
lang: document.documentElement?.lang || null,
|
|
118
|
+
publishedTime: null,
|
|
119
|
+
wordCount: (document.body?.innerText || '').split(/\s+/).filter(Boolean).length,
|
|
120
|
+
_notReadable: true,
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
content: article.textContent || '',
|
|
126
|
+
title: article.title || null,
|
|
127
|
+
author: article.byline || null,
|
|
128
|
+
excerpt: article.excerpt || null,
|
|
129
|
+
siteName: article.siteName || null,
|
|
130
|
+
lang: article.lang || null,
|
|
131
|
+
publishedTime: article.publishedTime || null,
|
|
132
|
+
wordCount: (article.textContent || '').split(/\s+/).filter(Boolean).length,
|
|
133
|
+
}
|
|
134
|
+
})) as PageMarkdownResult & { _notReadable?: boolean }
|
|
135
|
+
|
|
136
|
+
// Format output
|
|
137
|
+
const lines: string[] = []
|
|
138
|
+
|
|
139
|
+
if (result.title) {
|
|
140
|
+
lines.push(`# ${result.title}`)
|
|
141
|
+
lines.push('')
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const metadata: string[] = []
|
|
145
|
+
if (result.author) {
|
|
146
|
+
metadata.push(`Author: ${result.author}`)
|
|
147
|
+
}
|
|
148
|
+
if (result.siteName) {
|
|
149
|
+
metadata.push(`Site: ${result.siteName}`)
|
|
150
|
+
}
|
|
151
|
+
if (result.publishedTime) {
|
|
152
|
+
metadata.push(`Published: ${result.publishedTime}`)
|
|
153
|
+
}
|
|
154
|
+
if (metadata.length > 0) {
|
|
155
|
+
lines.push(metadata.join(' | '))
|
|
156
|
+
lines.push('')
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (result.excerpt && result.excerpt !== result.content.slice(0, result.excerpt.length)) {
|
|
160
|
+
lines.push(`> ${result.excerpt}`)
|
|
161
|
+
lines.push('')
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
lines.push(result.content)
|
|
165
|
+
|
|
166
|
+
let markdown = lines.join('\n').trim()
|
|
167
|
+
|
|
168
|
+
// Sanitize to remove unpaired surrogates that break JSON encoding
|
|
169
|
+
markdown = markdown.toWellFormed?.() ?? markdown
|
|
170
|
+
|
|
171
|
+
// Store snapshot and handle diffing
|
|
172
|
+
const previousSnapshot = lastMarkdownSnapshots.get(page)
|
|
173
|
+
lastMarkdownSnapshots.set(page, markdown)
|
|
174
|
+
|
|
175
|
+
// Diff defaults off when search is provided, but agent can explicitly enable both
|
|
176
|
+
if (showDiffSinceLastCall && previousSnapshot) {
|
|
177
|
+
const diffResult = createSmartDiff({
|
|
178
|
+
oldContent: previousSnapshot,
|
|
179
|
+
newContent: markdown,
|
|
180
|
+
label: 'content',
|
|
181
|
+
})
|
|
182
|
+
if (diffResult.type === 'no-change') {
|
|
183
|
+
return 'No changes since last call. Use showDiffSinceLastCall: false to see full content.'
|
|
184
|
+
}
|
|
185
|
+
return diffResult.content
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Handle search
|
|
189
|
+
if (search) {
|
|
190
|
+
const contentLines = markdown.split('\n')
|
|
191
|
+
const matchIndices: number[] = []
|
|
192
|
+
|
|
193
|
+
for (let i = 0; i < contentLines.length; i++) {
|
|
194
|
+
const line = contentLines[i]
|
|
195
|
+
let isMatch = false
|
|
196
|
+
if (isRegExp(search)) {
|
|
197
|
+
isMatch = search.test(line)
|
|
198
|
+
} else {
|
|
199
|
+
isMatch = line.toLowerCase().includes(search.toLowerCase())
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (isMatch) {
|
|
203
|
+
matchIndices.push(i)
|
|
204
|
+
if (matchIndices.length >= 10) {
|
|
205
|
+
break
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (matchIndices.length === 0) {
|
|
211
|
+
return 'No matches found'
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Collect lines with 5 lines of context above and below each match
|
|
215
|
+
const CONTEXT_LINES = 5
|
|
216
|
+
const includedLines = new Set<number>()
|
|
217
|
+
for (const idx of matchIndices) {
|
|
218
|
+
const start = Math.max(0, idx - CONTEXT_LINES)
|
|
219
|
+
const end = Math.min(contentLines.length - 1, idx + CONTEXT_LINES)
|
|
220
|
+
for (let i = start; i <= end; i++) {
|
|
221
|
+
includedLines.add(i)
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Build result with separators between non-contiguous sections
|
|
226
|
+
const sortedIndices = [...includedLines].sort((a, b) => a - b)
|
|
227
|
+
const resultLines: string[] = []
|
|
228
|
+
for (let i = 0; i < sortedIndices.length; i++) {
|
|
229
|
+
const lineIdx = sortedIndices[i]
|
|
230
|
+
if (i > 0 && sortedIndices[i - 1] !== lineIdx - 1) {
|
|
231
|
+
resultLines.push('---')
|
|
232
|
+
}
|
|
233
|
+
resultLines.push(contentLines[lineIdx])
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return resultLines.join('\n')
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return markdown
|
|
240
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
// Example snippets for profiling website performance with Tabwright and CDP.
|
|
2
|
+
|
|
3
|
+
import { console, getCDPSession, page } from './debugger-examples-types.js'
|
|
4
|
+
|
|
5
|
+
type PerfMetrics = {
|
|
6
|
+
paints: Record<string, number>
|
|
7
|
+
lcp: number
|
|
8
|
+
cls: number
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
type ObservedPerfEntry = {
|
|
12
|
+
name: string
|
|
13
|
+
startTime: number
|
|
14
|
+
duration: number
|
|
15
|
+
hadRecentInput?: boolean
|
|
16
|
+
value?: number
|
|
17
|
+
interactionId?: number
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
type NavigationTimingEntry = {
|
|
21
|
+
responseStart: number
|
|
22
|
+
domContentLoadedEventEnd: number
|
|
23
|
+
loadEventEnd: number
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
type LongTaskEntry = {
|
|
27
|
+
startTime: number
|
|
28
|
+
duration: number
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
type EventTimingEntry = {
|
|
32
|
+
name: string
|
|
33
|
+
duration: number
|
|
34
|
+
interactionId: number
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Example: Collect navigation timing and basic web vitals from the current page
|
|
38
|
+
async function collectWebVitals() {
|
|
39
|
+
await page.evaluate(() => {
|
|
40
|
+
const metrics: PerfMetrics = {
|
|
41
|
+
paints: {},
|
|
42
|
+
lcp: 0,
|
|
43
|
+
cls: 0,
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const perfGlobal = globalThis as typeof globalThis & {
|
|
47
|
+
__pwPerfMetrics?: PerfMetrics
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
perfGlobal.__pwPerfMetrics = metrics
|
|
51
|
+
|
|
52
|
+
new PerformanceObserver((list) => {
|
|
53
|
+
for (const entry of list.getEntries() as ObservedPerfEntry[]) {
|
|
54
|
+
metrics.paints[entry.name] = entry.startTime
|
|
55
|
+
}
|
|
56
|
+
}).observe({ type: 'paint', buffered: true } as never)
|
|
57
|
+
|
|
58
|
+
new PerformanceObserver((list) => {
|
|
59
|
+
const entries = list.getEntries() as ObservedPerfEntry[]
|
|
60
|
+
const lastEntry = entries[entries.length - 1]
|
|
61
|
+
if (!lastEntry) {
|
|
62
|
+
return
|
|
63
|
+
}
|
|
64
|
+
metrics.lcp = lastEntry.startTime
|
|
65
|
+
}).observe({ type: 'largest-contentful-paint', buffered: true } as never)
|
|
66
|
+
|
|
67
|
+
new PerformanceObserver((list) => {
|
|
68
|
+
for (const entry of list.getEntries() as ObservedPerfEntry[]) {
|
|
69
|
+
if (entry.hadRecentInput) {
|
|
70
|
+
continue
|
|
71
|
+
}
|
|
72
|
+
metrics.cls += entry.value || 0
|
|
73
|
+
}
|
|
74
|
+
}).observe({ type: 'layout-shift', buffered: true } as never)
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
await page.reload({ waitUntil: 'domcontentloaded' })
|
|
78
|
+
|
|
79
|
+
const report = await page.evaluate(() => {
|
|
80
|
+
const perfGlobal = globalThis as typeof globalThis & {
|
|
81
|
+
__pwPerfMetrics?: PerfMetrics
|
|
82
|
+
}
|
|
83
|
+
const nav = performance.getEntriesByType('navigation' as never)[0] as unknown as
|
|
84
|
+
| NavigationTimingEntry
|
|
85
|
+
| undefined
|
|
86
|
+
const metrics = perfGlobal.__pwPerfMetrics
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
ttfb: nav?.responseStart || 0,
|
|
90
|
+
domContentLoaded: nav?.domContentLoadedEventEnd || 0,
|
|
91
|
+
load: nav?.loadEventEnd || 0,
|
|
92
|
+
fcp: metrics?.paints['first-contentful-paint'] || 0,
|
|
93
|
+
lcp: metrics?.lcp || 0,
|
|
94
|
+
cls: metrics?.cls || 0,
|
|
95
|
+
}
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
console.log(report)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Example: Measure the biggest transferred requests with raw CDP network events
|
|
102
|
+
async function collectHeaviestRequests() {
|
|
103
|
+
const cdp = await getCDPSession({ page })
|
|
104
|
+
await cdp.send('Network.enable')
|
|
105
|
+
await cdp.send('Network.setCacheDisabled', { cacheDisabled: true })
|
|
106
|
+
|
|
107
|
+
const responses = new Map<string, { url: string; mimeType: string }>()
|
|
108
|
+
const finished = new Map<string, number>()
|
|
109
|
+
|
|
110
|
+
cdp.on('Network.responseReceived', (event) => {
|
|
111
|
+
responses.set(event.requestId, {
|
|
112
|
+
url: event.response.url,
|
|
113
|
+
mimeType: event.response.mimeType,
|
|
114
|
+
})
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
cdp.on('Network.loadingFinished', (event) => {
|
|
118
|
+
finished.set(event.requestId, event.encodedDataLength)
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
await page.reload({ waitUntil: 'domcontentloaded' })
|
|
122
|
+
|
|
123
|
+
const largest = [...responses.entries()]
|
|
124
|
+
.map(([requestId, response]) => {
|
|
125
|
+
return {
|
|
126
|
+
url: response.url,
|
|
127
|
+
mimeType: response.mimeType,
|
|
128
|
+
bytes: finished.get(requestId) || 0,
|
|
129
|
+
}
|
|
130
|
+
})
|
|
131
|
+
.sort((a, b) => b.bytes - a.bytes)
|
|
132
|
+
.slice(0, 10)
|
|
133
|
+
|
|
134
|
+
console.log(largest)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Example: Check whether interactivity is blocked by long tasks or slow events
|
|
138
|
+
async function measureInteractivity() {
|
|
139
|
+
await page.evaluate(() => {
|
|
140
|
+
const perfGlobal = globalThis as typeof globalThis & {
|
|
141
|
+
__pwLongTasks?: LongTaskEntry[]
|
|
142
|
+
__pwEventTimings?: EventTimingEntry[]
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
perfGlobal.__pwLongTasks = []
|
|
146
|
+
perfGlobal.__pwEventTimings = []
|
|
147
|
+
|
|
148
|
+
new PerformanceObserver((list) => {
|
|
149
|
+
perfGlobal.__pwLongTasks?.push(
|
|
150
|
+
...(list.getEntries() as ObservedPerfEntry[]).map((entry) => ({
|
|
151
|
+
startTime: entry.startTime,
|
|
152
|
+
duration: entry.duration,
|
|
153
|
+
})),
|
|
154
|
+
)
|
|
155
|
+
}).observe({ type: 'longtask', buffered: true } as never)
|
|
156
|
+
|
|
157
|
+
new PerformanceObserver((list) => {
|
|
158
|
+
perfGlobal.__pwEventTimings?.push(
|
|
159
|
+
...(list.getEntries() as ObservedPerfEntry[]).map((entry) => ({
|
|
160
|
+
name: entry.name,
|
|
161
|
+
duration: entry.duration,
|
|
162
|
+
interactionId: entry.interactionId || 0,
|
|
163
|
+
})),
|
|
164
|
+
)
|
|
165
|
+
}).observe({ type: 'event', buffered: true, durationThreshold: 16 } as never)
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
const button = page.getByRole('button').first()
|
|
169
|
+
await button.click()
|
|
170
|
+
|
|
171
|
+
const report = await page.evaluate(() => {
|
|
172
|
+
const perfGlobal = globalThis as typeof globalThis & {
|
|
173
|
+
__pwLongTasks?: LongTaskEntry[]
|
|
174
|
+
__pwEventTimings?: EventTimingEntry[]
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return {
|
|
178
|
+
longTasks: (perfGlobal.__pwLongTasks || []).filter((entry) => entry.duration >= 50),
|
|
179
|
+
events: (perfGlobal.__pwEventTimings || []).filter((entry) => entry.interactionId !== 0),
|
|
180
|
+
}
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
console.log(report)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export { collectWebVitals, collectHeaviestRequests, measureInteractivity }
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conditional playwright-core import. When patchright mode is enabled via
|
|
3
|
+
* TABWRIGHT_PATCHRIGHT=1 env var, imports from @playwriter/patchright-core
|
|
4
|
+
* instead of @xmorse/playwright-core. Both packages expose identical APIs.
|
|
5
|
+
*
|
|
6
|
+
* Type imports continue to use @xmorse/playwright-core directly (types are
|
|
7
|
+
* the same in both packages) so TypeScript resolution works without the
|
|
8
|
+
* optional patchright dep being installed.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export type {
|
|
12
|
+
Page,
|
|
13
|
+
Frame,
|
|
14
|
+
Browser,
|
|
15
|
+
BrowserContext,
|
|
16
|
+
Locator,
|
|
17
|
+
FrameLocator,
|
|
18
|
+
ElementHandle,
|
|
19
|
+
CDPSession,
|
|
20
|
+
MouseActionEvent,
|
|
21
|
+
} from '@xmorse/playwright-core'
|
|
22
|
+
|
|
23
|
+
export type { BrowserType } from '@xmorse/playwright-core'
|
|
24
|
+
|
|
25
|
+
type Chromium = typeof import('@xmorse/playwright-core').chromium
|
|
26
|
+
|
|
27
|
+
let _chromium: Chromium | undefined
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Returns the chromium BrowserType, loading from patchright-core if enabled.
|
|
31
|
+
* Caches after first call.
|
|
32
|
+
*/
|
|
33
|
+
export async function getChromium(): Promise<Chromium> {
|
|
34
|
+
if (_chromium) {
|
|
35
|
+
return _chromium
|
|
36
|
+
}
|
|
37
|
+
if (isPatchrightEnabled()) {
|
|
38
|
+
try {
|
|
39
|
+
// Dynamic import — @playwriter/patchright-core is an optional dependency.
|
|
40
|
+
// Types come from @xmorse/playwright-core (identical API surface).
|
|
41
|
+
const mod: { chromium: Chromium } = await import('@playwriter/patchright-core' as string)
|
|
42
|
+
_chromium = mod.chromium
|
|
43
|
+
} catch (e: unknown) {
|
|
44
|
+
throw new Error(
|
|
45
|
+
'@playwriter/patchright-core is not installed. Install it with: pnpm add @playwriter/patchright-core',
|
|
46
|
+
{ cause: e },
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
} else {
|
|
50
|
+
const mod = await import('@xmorse/playwright-core')
|
|
51
|
+
_chromium = mod.chromium
|
|
52
|
+
}
|
|
53
|
+
return _chromium!
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function isPatchrightEnabled(): boolean {
|
|
57
|
+
const value = process.env.TABWRIGHT_PATCHRIGHT || process.env.PLAYWRITER_PATCHRIGHT
|
|
58
|
+
return value === '1' || value === 'true'
|
|
59
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for auto-relocation of popup windows to tabs.
|
|
3
|
+
* - Connected source tab → popup gets relocated + auto-attached.
|
|
4
|
+
* - No source tab connected → popup is left alone.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { createMCPClient } from './mcp-client.js'
|
|
8
|
+
import { describe, it, expect, beforeAll, afterAll } from 'vitest'
|
|
9
|
+
import {
|
|
10
|
+
setupTestContext,
|
|
11
|
+
cleanupTestContext,
|
|
12
|
+
getExtensionServiceWorker,
|
|
13
|
+
createSimpleServer,
|
|
14
|
+
type TestContext,
|
|
15
|
+
type SimpleServer,
|
|
16
|
+
js,
|
|
17
|
+
} from './test-utils.js'
|
|
18
|
+
import './test-declarations.js'
|
|
19
|
+
|
|
20
|
+
const TEST_PORT = 19995
|
|
21
|
+
|
|
22
|
+
describe('Popup window relocation', () => {
|
|
23
|
+
let client: Awaited<ReturnType<typeof createMCPClient>>['client']
|
|
24
|
+
let cleanup: (() => Promise<void>) | null = null
|
|
25
|
+
let testCtx: TestContext | null = null
|
|
26
|
+
let htmlServer: SimpleServer | null = null
|
|
27
|
+
|
|
28
|
+
beforeAll(async () => {
|
|
29
|
+
testCtx = await setupTestContext({
|
|
30
|
+
port: TEST_PORT,
|
|
31
|
+
tempDirPrefix: 'pw-popup-test-',
|
|
32
|
+
toggleExtension: true,
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
const result = await createMCPClient({ port: TEST_PORT })
|
|
36
|
+
client = result.client
|
|
37
|
+
cleanup = result.cleanup
|
|
38
|
+
|
|
39
|
+
htmlServer = await createSimpleServer({
|
|
40
|
+
routes: {
|
|
41
|
+
'/opener': `<!doctype html>
|
|
42
|
+
<html>
|
|
43
|
+
<body>
|
|
44
|
+
<button id="open-popup" onclick="window.open('/target', '', 'width=400,height=300,popup=1')">Open popup</button>
|
|
45
|
+
</body>
|
|
46
|
+
</html>`,
|
|
47
|
+
'/target': `<!doctype html>
|
|
48
|
+
<html>
|
|
49
|
+
<body>
|
|
50
|
+
<h1 id="target-heading">Popup target page</h1>
|
|
51
|
+
</body>
|
|
52
|
+
</html>`,
|
|
53
|
+
},
|
|
54
|
+
})
|
|
55
|
+
}, 600000)
|
|
56
|
+
|
|
57
|
+
afterAll(async () => {
|
|
58
|
+
if (htmlServer) {
|
|
59
|
+
await htmlServer.close()
|
|
60
|
+
}
|
|
61
|
+
await cleanupTestContext(testCtx, cleanup)
|
|
62
|
+
cleanup = null
|
|
63
|
+
testCtx = null
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it('relocates popup window into main browser context as a tab', async () => {
|
|
67
|
+
if (!htmlServer) throw new Error('html server not initialized')
|
|
68
|
+
const browserContext = testCtx!.browserContext
|
|
69
|
+
const serviceWorker = await getExtensionServiceWorker(browserContext)
|
|
70
|
+
|
|
71
|
+
await client.callTool({
|
|
72
|
+
name: 'execute',
|
|
73
|
+
arguments: {
|
|
74
|
+
code: js`
|
|
75
|
+
state.popupTestPage = context.pages().find((p) => p.url() === 'about:blank') ?? (await context.newPage());
|
|
76
|
+
await state.popupTestPage.goto('${htmlServer.baseUrl}/opener', { waitUntil: 'domcontentloaded' });
|
|
77
|
+
return { url: state.popupTestPage.url(), pagesBefore: context.pages().length };
|
|
78
|
+
`,
|
|
79
|
+
},
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
const clickResult = await client.callTool({
|
|
83
|
+
name: 'execute',
|
|
84
|
+
arguments: {
|
|
85
|
+
code: js`
|
|
86
|
+
await state.popupTestPage.click('#open-popup');
|
|
87
|
+
await state.popupTestPage.waitForTimeout(1500);
|
|
88
|
+
return { pagesAfter: context.pages().length, allUrls: context.pages().map((p) => p.url()) };
|
|
89
|
+
`,
|
|
90
|
+
},
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
const clickOutput = (clickResult as any).content[0].text as string
|
|
94
|
+
|
|
95
|
+
expect(clickOutput).toContain('/target')
|
|
96
|
+
expect((clickResult as any).isError).not.toBe(true)
|
|
97
|
+
expect(clickOutput).toContain('[WARNING] New page opened from current page')
|
|
98
|
+
expect(clickOutput).not.toContain('Popup window detected')
|
|
99
|
+
expect(clickOutput).not.toContain('cannot be controlled by playwriter')
|
|
100
|
+
|
|
101
|
+
const windowTypes = await serviceWorker.evaluate(async () => {
|
|
102
|
+
const windows = await chrome.windows.getAll({ populate: false })
|
|
103
|
+
return windows.map((w) => w.type)
|
|
104
|
+
})
|
|
105
|
+
expect(windowTypes).not.toContain('popup')
|
|
106
|
+
expect(windowTypes).toContain('normal')
|
|
107
|
+
|
|
108
|
+
await client.callTool({
|
|
109
|
+
name: 'execute',
|
|
110
|
+
arguments: {
|
|
111
|
+
code: js`
|
|
112
|
+
const targetPage = context.pages().find((p) => p.url().endsWith('/target'));
|
|
113
|
+
if (targetPage) { await targetPage.close(); }
|
|
114
|
+
await state.popupTestPage.close();
|
|
115
|
+
delete state.popupTestPage;
|
|
116
|
+
`,
|
|
117
|
+
},
|
|
118
|
+
})
|
|
119
|
+
}, 60000)
|
|
120
|
+
|
|
121
|
+
it('leaves popup windows alone when no Tabwright tab is connected', async () => {
|
|
122
|
+
if (!htmlServer) throw new Error('html server not initialized')
|
|
123
|
+
const browserContext = testCtx!.browserContext
|
|
124
|
+
const serviceWorker = await getExtensionServiceWorker(browserContext)
|
|
125
|
+
|
|
126
|
+
await serviceWorker.evaluate(async () => {
|
|
127
|
+
await globalThis.disconnectEverything()
|
|
128
|
+
})
|
|
129
|
+
await new Promise((r) => {
|
|
130
|
+
setTimeout(r, 200)
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
// Drive the browser directly via browserContext (not MCP/extension) so
|
|
134
|
+
// the opener tab stays unconnected.
|
|
135
|
+
const unconnectedPage = await browserContext.newPage()
|
|
136
|
+
await unconnectedPage.goto(`${htmlServer.baseUrl}/opener`, { waitUntil: 'domcontentloaded' })
|
|
137
|
+
await unconnectedPage.bringToFront()
|
|
138
|
+
await unconnectedPage.click('#open-popup')
|
|
139
|
+
// If relocation DID happen, the popup window would be gone by now.
|
|
140
|
+
await new Promise((r) => {
|
|
141
|
+
setTimeout(r, 1500)
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
const windowTypes = await serviceWorker.evaluate(async () => {
|
|
145
|
+
const windows = await chrome.windows.getAll({ populate: false })
|
|
146
|
+
return windows.map((w) => w.type)
|
|
147
|
+
})
|
|
148
|
+
expect(windowTypes).toContain('popup')
|
|
149
|
+
|
|
150
|
+
const windowIdsToClose = await serviceWorker.evaluate(async () => {
|
|
151
|
+
const windows = await chrome.windows.getAll({ populate: false })
|
|
152
|
+
return windows.filter((w) => w.type === 'popup').map((w) => w.id)
|
|
153
|
+
})
|
|
154
|
+
for (const windowId of windowIdsToClose) {
|
|
155
|
+
if (windowId !== undefined) {
|
|
156
|
+
await serviceWorker.evaluate(async (id: number) => {
|
|
157
|
+
await chrome.windows.remove(id)
|
|
158
|
+
}, windowId)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
await unconnectedPage.close()
|
|
162
|
+
}, 60000)
|
|
163
|
+
})
|