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.
Files changed (599) hide show
  1. package/LICENSE +21 -0
  2. package/bin.js +4 -0
  3. package/dist/a11y-client.js +204 -0
  4. package/dist/agent-skills/tabwright/SKILL.md +119 -0
  5. package/dist/aria-snapshot.d.ts +262 -0
  6. package/dist/aria-snapshot.d.ts.map +1 -0
  7. package/dist/aria-snapshot.js +1256 -0
  8. package/dist/aria-snapshot.js.map +1 -0
  9. package/dist/aria-snapshot.test.d.ts +2 -0
  10. package/dist/aria-snapshot.test.d.ts.map +1 -0
  11. package/dist/aria-snapshot.test.js +270 -0
  12. package/dist/aria-snapshot.test.js.map +1 -0
  13. package/dist/aria-snapshot.unit.test.d.ts +2 -0
  14. package/dist/aria-snapshot.unit.test.d.ts.map +1 -0
  15. package/dist/aria-snapshot.unit.test.js +542 -0
  16. package/dist/aria-snapshot.unit.test.js.map +1 -0
  17. package/dist/assets/cursors/screen-studio/pointer-macos-tahoe-data-url.d.ts +5 -0
  18. package/dist/assets/cursors/screen-studio/pointer-macos-tahoe-data-url.d.ts.map +1 -0
  19. package/dist/assets/cursors/screen-studio/pointer-macos-tahoe-data-url.js +5 -0
  20. package/dist/assets/cursors/screen-studio/pointer-macos-tahoe-data-url.js.map +1 -0
  21. package/dist/bippy.js +894 -0
  22. package/dist/browser-config.d.ts +13 -0
  23. package/dist/browser-config.d.ts.map +1 -0
  24. package/dist/browser-config.js +126 -0
  25. package/dist/browser-config.js.map +1 -0
  26. package/dist/browser-install.d.ts +16 -0
  27. package/dist/browser-install.d.ts.map +1 -0
  28. package/dist/browser-install.js +238 -0
  29. package/dist/browser-install.js.map +1 -0
  30. package/dist/browser-launch.d.ts +17 -0
  31. package/dist/browser-launch.d.ts.map +1 -0
  32. package/dist/browser-launch.js +44 -0
  33. package/dist/browser-launch.js.map +1 -0
  34. package/dist/builtin-capabilities.d.ts +28 -0
  35. package/dist/builtin-capabilities.d.ts.map +1 -0
  36. package/dist/builtin-capabilities.js +2673 -0
  37. package/dist/builtin-capabilities.js.map +1 -0
  38. package/dist/capability-agent-skill.d.ts +60 -0
  39. package/dist/capability-agent-skill.d.ts.map +1 -0
  40. package/dist/capability-agent-skill.js +225 -0
  41. package/dist/capability-agent-skill.js.map +1 -0
  42. package/dist/capability-auth-state.d.ts +34 -0
  43. package/dist/capability-auth-state.d.ts.map +1 -0
  44. package/dist/capability-auth-state.js +157 -0
  45. package/dist/capability-auth-state.js.map +1 -0
  46. package/dist/capability-auth.d.ts +36 -0
  47. package/dist/capability-auth.d.ts.map +1 -0
  48. package/dist/capability-auth.js +198 -0
  49. package/dist/capability-auth.js.map +1 -0
  50. package/dist/capability-cli-confirmation.test.d.ts +2 -0
  51. package/dist/capability-cli-confirmation.test.d.ts.map +1 -0
  52. package/dist/capability-cli-confirmation.test.js +79 -0
  53. package/dist/capability-cli-confirmation.test.js.map +1 -0
  54. package/dist/capability-options.d.ts +20 -0
  55. package/dist/capability-options.d.ts.map +1 -0
  56. package/dist/capability-options.js +31 -0
  57. package/dist/capability-options.js.map +1 -0
  58. package/dist/capability-options.test.d.ts +2 -0
  59. package/dist/capability-options.test.d.ts.map +1 -0
  60. package/dist/capability-options.test.js +81 -0
  61. package/dist/capability-options.test.js.map +1 -0
  62. package/dist/capability-package.d.ts +27 -0
  63. package/dist/capability-package.d.ts.map +1 -0
  64. package/dist/capability-package.js +444 -0
  65. package/dist/capability-package.js.map +1 -0
  66. package/dist/capability-package.test.d.ts +2 -0
  67. package/dist/capability-package.test.d.ts.map +1 -0
  68. package/dist/capability-package.test.js +179 -0
  69. package/dist/capability-package.test.js.map +1 -0
  70. package/dist/capability-registry.d.ts +262 -0
  71. package/dist/capability-registry.d.ts.map +1 -0
  72. package/dist/capability-registry.js +931 -0
  73. package/dist/capability-registry.js.map +1 -0
  74. package/dist/capability-registry.test.d.ts +2 -0
  75. package/dist/capability-registry.test.d.ts.map +1 -0
  76. package/dist/capability-registry.test.js +1683 -0
  77. package/dist/capability-registry.test.js.map +1 -0
  78. package/dist/capability-runner.d.ts +99 -0
  79. package/dist/capability-runner.d.ts.map +1 -0
  80. package/dist/capability-runner.js +608 -0
  81. package/dist/capability-runner.js.map +1 -0
  82. package/dist/capability-search-relevance.test.d.ts +2 -0
  83. package/dist/capability-search-relevance.test.d.ts.map +1 -0
  84. package/dist/capability-search-relevance.test.js +67 -0
  85. package/dist/capability-search-relevance.test.js.map +1 -0
  86. package/dist/capability-studio.d.ts +11 -0
  87. package/dist/capability-studio.d.ts.map +1 -0
  88. package/dist/capability-studio.js +593 -0
  89. package/dist/capability-studio.js.map +1 -0
  90. package/dist/capability-studio.test.d.ts +2 -0
  91. package/dist/capability-studio.test.d.ts.map +1 -0
  92. package/dist/capability-studio.test.js +41 -0
  93. package/dist/capability-studio.test.js.map +1 -0
  94. package/dist/cdp-log.d.ts +19 -0
  95. package/dist/cdp-log.d.ts.map +1 -0
  96. package/dist/cdp-log.js +83 -0
  97. package/dist/cdp-log.js.map +1 -0
  98. package/dist/cdp-log.test.d.ts +2 -0
  99. package/dist/cdp-log.test.d.ts.map +1 -0
  100. package/dist/cdp-log.test.js +109 -0
  101. package/dist/cdp-log.test.js.map +1 -0
  102. package/dist/cdp-relay.d.ts +19 -0
  103. package/dist/cdp-relay.d.ts.map +1 -0
  104. package/dist/cdp-relay.js +2375 -0
  105. package/dist/cdp-relay.js.map +1 -0
  106. package/dist/cdp-session.d.ts +37 -0
  107. package/dist/cdp-session.d.ts.map +1 -0
  108. package/dist/cdp-session.js +36 -0
  109. package/dist/cdp-session.js.map +1 -0
  110. package/dist/cdp-types.d.ts +65 -0
  111. package/dist/cdp-types.d.ts.map +1 -0
  112. package/dist/cdp-types.js +91 -0
  113. package/dist/cdp-types.js.map +1 -0
  114. package/dist/channel-owner-inspect.test.d.ts +2 -0
  115. package/dist/channel-owner-inspect.test.d.ts.map +1 -0
  116. package/dist/channel-owner-inspect.test.js +75 -0
  117. package/dist/channel-owner-inspect.test.js.map +1 -0
  118. package/dist/chrome-discovery.d.ts +65 -0
  119. package/dist/chrome-discovery.d.ts.map +1 -0
  120. package/dist/chrome-discovery.js +173 -0
  121. package/dist/chrome-discovery.js.map +1 -0
  122. package/dist/chrome-discovery.test.d.ts +2 -0
  123. package/dist/chrome-discovery.test.d.ts.map +1 -0
  124. package/dist/chrome-discovery.test.js +41 -0
  125. package/dist/chrome-discovery.test.js.map +1 -0
  126. package/dist/clean-html.d.ts +11 -0
  127. package/dist/clean-html.d.ts.map +1 -0
  128. package/dist/clean-html.js +106 -0
  129. package/dist/clean-html.js.map +1 -0
  130. package/dist/cli-help.test.d.ts +2 -0
  131. package/dist/cli-help.test.d.ts.map +1 -0
  132. package/dist/cli-help.test.js +98 -0
  133. package/dist/cli-help.test.js.map +1 -0
  134. package/dist/cli.d.ts +3 -0
  135. package/dist/cli.d.ts.map +1 -0
  136. package/dist/cli.js +2709 -0
  137. package/dist/cli.js.map +1 -0
  138. package/dist/cloud-client.d.ts +56 -0
  139. package/dist/cloud-client.d.ts.map +1 -0
  140. package/dist/cloud-client.js +127 -0
  141. package/dist/cloud-client.js.map +1 -0
  142. package/dist/create-logger.d.ts +9 -0
  143. package/dist/create-logger.d.ts.map +1 -0
  144. package/dist/create-logger.js +27 -0
  145. package/dist/create-logger.js.map +1 -0
  146. package/dist/debugger-api.md +458 -0
  147. package/dist/debugger-examples-types.d.ts +24 -0
  148. package/dist/debugger-examples-types.d.ts.map +1 -0
  149. package/dist/debugger-examples-types.js +2 -0
  150. package/dist/debugger-examples-types.js.map +1 -0
  151. package/dist/debugger-examples.d.ts +6 -0
  152. package/dist/debugger-examples.d.ts.map +1 -0
  153. package/dist/debugger-examples.js +53 -0
  154. package/dist/debugger-examples.js.map +1 -0
  155. package/dist/debugger.d.ts +381 -0
  156. package/dist/debugger.d.ts.map +1 -0
  157. package/dist/debugger.js +630 -0
  158. package/dist/debugger.js.map +1 -0
  159. package/dist/diff-utils.d.ts +20 -0
  160. package/dist/diff-utils.d.ts.map +1 -0
  161. package/dist/diff-utils.js +50 -0
  162. package/dist/diff-utils.js.map +1 -0
  163. package/dist/diff-utils.test.d.ts +2 -0
  164. package/dist/diff-utils.test.d.ts.map +1 -0
  165. package/dist/diff-utils.test.js +137 -0
  166. package/dist/diff-utils.test.js.map +1 -0
  167. package/dist/doctor.d.ts +44 -0
  168. package/dist/doctor.d.ts.map +1 -0
  169. package/dist/doctor.js +246 -0
  170. package/dist/doctor.js.map +1 -0
  171. package/dist/doctor.test.d.ts +2 -0
  172. package/dist/doctor.test.d.ts.map +1 -0
  173. package/dist/doctor.test.js +208 -0
  174. package/dist/doctor.test.js.map +1 -0
  175. package/dist/editor-api.md +374 -0
  176. package/dist/editor-examples.d.ts +11 -0
  177. package/dist/editor-examples.d.ts.map +1 -0
  178. package/dist/editor-examples.js +124 -0
  179. package/dist/editor-examples.js.map +1 -0
  180. package/dist/editor.d.ts +203 -0
  181. package/dist/editor.d.ts.map +1 -0
  182. package/dist/editor.js +363 -0
  183. package/dist/editor.js.map +1 -0
  184. package/dist/env-compat.d.ts +2 -0
  185. package/dist/env-compat.d.ts.map +1 -0
  186. package/dist/env-compat.js +27 -0
  187. package/dist/env-compat.js.map +1 -0
  188. package/dist/executor.d.ts +229 -0
  189. package/dist/executor.d.ts.map +1 -0
  190. package/dist/executor.js +1465 -0
  191. package/dist/executor.js.map +1 -0
  192. package/dist/executor.unit.test.d.ts +2 -0
  193. package/dist/executor.unit.test.d.ts.map +1 -0
  194. package/dist/executor.unit.test.js +143 -0
  195. package/dist/executor.unit.test.js.map +1 -0
  196. package/dist/extension/_locales/en/messages.json +278 -0
  197. package/dist/extension/_locales/zh_CN/messages.json +256 -0
  198. package/dist/extension/assets/options-CYH96Lcx.css +1349 -0
  199. package/dist/extension/background.js +3994 -0
  200. package/dist/extension/icons/SolarCursorSquareBold.png +0 -0
  201. package/dist/extension/icons/icon-black-128.png +0 -0
  202. package/dist/extension/icons/icon-black-16.png +0 -0
  203. package/dist/extension/icons/icon-black-32.png +0 -0
  204. package/dist/extension/icons/icon-black-48.png +0 -0
  205. package/dist/extension/icons/icon-gray-128.png +0 -0
  206. package/dist/extension/icons/icon-gray-16.png +0 -0
  207. package/dist/extension/icons/icon-gray-32.png +0 -0
  208. package/dist/extension/icons/icon-gray-48.png +0 -0
  209. package/dist/extension/icons/icon-green-128.png +0 -0
  210. package/dist/extension/icons/icon-green-16.png +0 -0
  211. package/dist/extension/icons/icon-green-32.png +0 -0
  212. package/dist/extension/icons/icon-green-48.png +0 -0
  213. package/dist/extension/icons/tabwright-icon-black.png +0 -0
  214. package/dist/extension/icons/tabwright-icon-gray-disabled.png +0 -0
  215. package/dist/extension/icons/tabwright-icon-gray-disabled.svg +1 -0
  216. package/dist/extension/manifest.json +54 -0
  217. package/dist/extension/options.js +16145 -0
  218. package/dist/extension/rrweb-recorder.js +12952 -0
  219. package/dist/extension/src/options.html +165 -0
  220. package/dist/extension/src/prism-bash.min.js +1 -0
  221. package/dist/extension/src/prism.min.js +1 -0
  222. package/dist/extension/src/welcome.html +396 -0
  223. package/dist/extension-connection.test.d.ts +2 -0
  224. package/dist/extension-connection.test.d.ts.map +1 -0
  225. package/dist/extension-connection.test.js +708 -0
  226. package/dist/extension-connection.test.js.map +1 -0
  227. package/dist/extension-icon-warning.test.d.ts +2 -0
  228. package/dist/extension-icon-warning.test.d.ts.map +1 -0
  229. package/dist/extension-icon-warning.test.js +33 -0
  230. package/dist/extension-icon-warning.test.js.map +1 -0
  231. package/dist/ghost-browser.d.ts +62 -0
  232. package/dist/ghost-browser.d.ts.map +1 -0
  233. package/dist/ghost-browser.js +107 -0
  234. package/dist/ghost-browser.js.map +1 -0
  235. package/dist/ghost-cursor-client.js +374 -0
  236. package/dist/ghost-cursor-controller.d.ts +46 -0
  237. package/dist/ghost-cursor-controller.d.ts.map +1 -0
  238. package/dist/ghost-cursor-controller.js +98 -0
  239. package/dist/ghost-cursor-controller.js.map +1 -0
  240. package/dist/ghost-cursor.d.ts +27 -0
  241. package/dist/ghost-cursor.d.ts.map +1 -0
  242. package/dist/ghost-cursor.js +79 -0
  243. package/dist/ghost-cursor.js.map +1 -0
  244. package/dist/htmlrewrite.d.ts +8 -0
  245. package/dist/htmlrewrite.d.ts.map +1 -0
  246. package/dist/htmlrewrite.js +339 -0
  247. package/dist/htmlrewrite.js.map +1 -0
  248. package/dist/htmlrewrite.test.d.ts +2 -0
  249. package/dist/htmlrewrite.test.d.ts.map +1 -0
  250. package/dist/htmlrewrite.test.js +7975 -0
  251. package/dist/htmlrewrite.test.js.map +1 -0
  252. package/dist/index.d.ts +20 -0
  253. package/dist/index.d.ts.map +1 -0
  254. package/dist/index.js +11 -0
  255. package/dist/index.js.map +1 -0
  256. package/dist/kill-port.d.ts +29 -0
  257. package/dist/kill-port.d.ts.map +1 -0
  258. package/dist/kill-port.js +190 -0
  259. package/dist/kill-port.js.map +1 -0
  260. package/dist/kitty-graphics.d.ts +22 -0
  261. package/dist/kitty-graphics.d.ts.map +1 -0
  262. package/dist/kitty-graphics.js +71 -0
  263. package/dist/kitty-graphics.js.map +1 -0
  264. package/dist/kitty-graphics.test.d.ts +2 -0
  265. package/dist/kitty-graphics.test.d.ts.map +1 -0
  266. package/dist/kitty-graphics.test.js +125 -0
  267. package/dist/kitty-graphics.test.js.map +1 -0
  268. package/dist/locator-selector.test.d.ts +2 -0
  269. package/dist/locator-selector.test.d.ts.map +1 -0
  270. package/dist/locator-selector.test.js +98 -0
  271. package/dist/locator-selector.test.js.map +1 -0
  272. package/dist/mcp-client.d.ts +20 -0
  273. package/dist/mcp-client.d.ts.map +1 -0
  274. package/dist/mcp-client.js +56 -0
  275. package/dist/mcp-client.js.map +1 -0
  276. package/dist/mcp.d.ts +6 -0
  277. package/dist/mcp.d.ts.map +1 -0
  278. package/dist/mcp.js +511 -0
  279. package/dist/mcp.js.map +1 -0
  280. package/dist/on-mouse-action.test.d.ts +2 -0
  281. package/dist/on-mouse-action.test.d.ts.map +1 -0
  282. package/dist/on-mouse-action.test.js +180 -0
  283. package/dist/on-mouse-action.test.js.map +1 -0
  284. package/dist/options-page.test.d.ts +2 -0
  285. package/dist/options-page.test.d.ts.map +1 -0
  286. package/dist/options-page.test.js +773 -0
  287. package/dist/options-page.test.js.map +1 -0
  288. package/dist/package-paths.d.ts +4 -0
  289. package/dist/package-paths.d.ts.map +1 -0
  290. package/dist/package-paths.js +30 -0
  291. package/dist/package-paths.js.map +1 -0
  292. package/dist/page-markdown.d.ts +40 -0
  293. package/dist/page-markdown.d.ts.map +1 -0
  294. package/dist/page-markdown.js +181 -0
  295. package/dist/page-markdown.js.map +1 -0
  296. package/dist/performance-examples.d.ts +5 -0
  297. package/dist/performance-examples.d.ts.map +1 -0
  298. package/dist/performance-examples.js +112 -0
  299. package/dist/performance-examples.js.map +1 -0
  300. package/dist/performance-profiling.md +417 -0
  301. package/dist/playwright-import.d.ts +19 -0
  302. package/dist/playwright-import.d.ts.map +1 -0
  303. package/dist/playwright-import.js +40 -0
  304. package/dist/playwright-import.js.map +1 -0
  305. package/dist/popup-relocation.test.d.ts +7 -0
  306. package/dist/popup-relocation.test.d.ts.map +1 -0
  307. package/dist/popup-relocation.test.js +139 -0
  308. package/dist/popup-relocation.test.js.map +1 -0
  309. package/dist/product-paths.d.ts +9 -0
  310. package/dist/product-paths.d.ts.map +1 -0
  311. package/dist/product-paths.js +24 -0
  312. package/dist/product-paths.js.map +1 -0
  313. package/dist/product-paths.test.d.ts +2 -0
  314. package/dist/product-paths.test.d.ts.map +1 -0
  315. package/dist/product-paths.test.js +39 -0
  316. package/dist/product-paths.test.js.map +1 -0
  317. package/dist/prompt.md +1004 -0
  318. package/dist/protocol.d.ts +248 -0
  319. package/dist/protocol.d.ts.map +1 -0
  320. package/dist/protocol.js +59 -0
  321. package/dist/protocol.js.map +1 -0
  322. package/dist/protocol.test.d.ts +2 -0
  323. package/dist/protocol.test.d.ts.map +1 -0
  324. package/dist/protocol.test.js +31 -0
  325. package/dist/protocol.test.js.map +1 -0
  326. package/dist/react-source.d.ts +57 -0
  327. package/dist/react-source.d.ts.map +1 -0
  328. package/dist/react-source.js +254 -0
  329. package/dist/react-source.js.map +1 -0
  330. package/dist/readability.js +1674 -0
  331. package/dist/relay-client.d.ts +104 -0
  332. package/dist/relay-client.d.ts.map +1 -0
  333. package/dist/relay-client.js +426 -0
  334. package/dist/relay-client.js.map +1 -0
  335. package/dist/relay-client.test.d.ts +2 -0
  336. package/dist/relay-client.test.d.ts.map +1 -0
  337. package/dist/relay-client.test.js +305 -0
  338. package/dist/relay-client.test.js.map +1 -0
  339. package/dist/relay-core.test.d.ts +2 -0
  340. package/dist/relay-core.test.d.ts.map +1 -0
  341. package/dist/relay-core.test.js +1427 -0
  342. package/dist/relay-core.test.js.map +1 -0
  343. package/dist/relay-navigation.test.d.ts +2 -0
  344. package/dist/relay-navigation.test.d.ts.map +1 -0
  345. package/dist/relay-navigation.test.js +661 -0
  346. package/dist/relay-navigation.test.js.map +1 -0
  347. package/dist/relay-session.test.d.ts +2 -0
  348. package/dist/relay-session.test.d.ts.map +1 -0
  349. package/dist/relay-session.test.js +1121 -0
  350. package/dist/relay-session.test.js.map +1 -0
  351. package/dist/relay-state.d.ts +178 -0
  352. package/dist/relay-state.d.ts.map +1 -0
  353. package/dist/relay-state.js +346 -0
  354. package/dist/relay-state.js.map +1 -0
  355. package/dist/relay-state.test.d.ts +2 -0
  356. package/dist/relay-state.test.d.ts.map +1 -0
  357. package/dist/relay-state.test.js +594 -0
  358. package/dist/relay-state.test.js.map +1 -0
  359. package/dist/replay-ai-index.d.ts +99 -0
  360. package/dist/replay-ai-index.d.ts.map +1 -0
  361. package/dist/replay-ai-index.js +677 -0
  362. package/dist/replay-ai-index.js.map +1 -0
  363. package/dist/replay-ai-index.test.d.ts +2 -0
  364. package/dist/replay-ai-index.test.d.ts.map +1 -0
  365. package/dist/replay-ai-index.test.js +255 -0
  366. package/dist/replay-ai-index.test.js.map +1 -0
  367. package/dist/replay-cli.test.d.ts +2 -0
  368. package/dist/replay-cli.test.d.ts.map +1 -0
  369. package/dist/replay-cli.test.js +275 -0
  370. package/dist/replay-cli.test.js.map +1 -0
  371. package/dist/replay-eval.d.ts +44 -0
  372. package/dist/replay-eval.d.ts.map +1 -0
  373. package/dist/replay-eval.js +902 -0
  374. package/dist/replay-eval.js.map +1 -0
  375. package/dist/replay-handoff.d.ts +29 -0
  376. package/dist/replay-handoff.d.ts.map +1 -0
  377. package/dist/replay-handoff.js +83 -0
  378. package/dist/replay-handoff.js.map +1 -0
  379. package/dist/replay-handoff.test.d.ts +2 -0
  380. package/dist/replay-handoff.test.d.ts.map +1 -0
  381. package/dist/replay-handoff.test.js +123 -0
  382. package/dist/replay-handoff.test.js.map +1 -0
  383. package/dist/replay-workflow-compiler.d.ts +44 -0
  384. package/dist/replay-workflow-compiler.d.ts.map +1 -0
  385. package/dist/replay-workflow-compiler.js +601 -0
  386. package/dist/replay-workflow-compiler.js.map +1 -0
  387. package/dist/replay-workflow-compiler.test.d.ts +2 -0
  388. package/dist/replay-workflow-compiler.test.d.ts.map +1 -0
  389. package/dist/replay-workflow-compiler.test.js +223 -0
  390. package/dist/replay-workflow-compiler.test.js.map +1 -0
  391. package/dist/rrweb-recording-relay.d.ts +56 -0
  392. package/dist/rrweb-recording-relay.d.ts.map +1 -0
  393. package/dist/rrweb-recording-relay.js +299 -0
  394. package/dist/rrweb-recording-relay.js.map +1 -0
  395. package/dist/rrweb-recording-relay.test.d.ts +2 -0
  396. package/dist/rrweb-recording-relay.test.d.ts.map +1 -0
  397. package/dist/rrweb-recording-relay.test.js +85 -0
  398. package/dist/rrweb-recording-relay.test.js.map +1 -0
  399. package/dist/rrweb-recording.d.ts +104 -0
  400. package/dist/rrweb-recording.d.ts.map +1 -0
  401. package/dist/rrweb-recording.js +180 -0
  402. package/dist/rrweb-recording.js.map +1 -0
  403. package/dist/scoped-fs.d.ts +95 -0
  404. package/dist/scoped-fs.d.ts.map +1 -0
  405. package/dist/scoped-fs.js +358 -0
  406. package/dist/scoped-fs.js.map +1 -0
  407. package/dist/scoped-fs.test.d.ts +5 -0
  408. package/dist/scoped-fs.test.d.ts.map +1 -0
  409. package/dist/scoped-fs.test.js +73 -0
  410. package/dist/scoped-fs.test.js.map +1 -0
  411. package/dist/selector-generator.js +7378 -0
  412. package/dist/skill-stub.test.d.ts +2 -0
  413. package/dist/skill-stub.test.d.ts.map +1 -0
  414. package/dist/skill-stub.test.js +22 -0
  415. package/dist/skill-stub.test.js.map +1 -0
  416. package/dist/snapshot-tools.test.d.ts +2 -0
  417. package/dist/snapshot-tools.test.d.ts.map +1 -0
  418. package/dist/snapshot-tools.test.js +856 -0
  419. package/dist/snapshot-tools.test.js.map +1 -0
  420. package/dist/start-relay-server.d.ts +6 -0
  421. package/dist/start-relay-server.d.ts.map +1 -0
  422. package/dist/start-relay-server.js +57 -0
  423. package/dist/start-relay-server.js.map +1 -0
  424. package/dist/styles-api.md +124 -0
  425. package/dist/styles-examples.d.ts +8 -0
  426. package/dist/styles-examples.d.ts.map +1 -0
  427. package/dist/styles-examples.js +64 -0
  428. package/dist/styles-examples.js.map +1 -0
  429. package/dist/styles.d.ts +27 -0
  430. package/dist/styles.d.ts.map +1 -0
  431. package/dist/styles.js +231 -0
  432. package/dist/styles.js.map +1 -0
  433. package/dist/tabwright-agent-skill.d.ts +30 -0
  434. package/dist/tabwright-agent-skill.d.ts.map +1 -0
  435. package/dist/tabwright-agent-skill.js +74 -0
  436. package/dist/tabwright-agent-skill.js.map +1 -0
  437. package/dist/tabwright-agent-skill.test.d.ts +2 -0
  438. package/dist/tabwright-agent-skill.test.d.ts.map +1 -0
  439. package/dist/tabwright-agent-skill.test.js +51 -0
  440. package/dist/tabwright-agent-skill.test.js.map +1 -0
  441. package/dist/test-declarations.d.ts +13 -0
  442. package/dist/test-declarations.d.ts.map +1 -0
  443. package/dist/test-declarations.js +2 -0
  444. package/dist/test-declarations.js.map +1 -0
  445. package/dist/test-utils.d.ts +69 -0
  446. package/dist/test-utils.d.ts.map +1 -0
  447. package/dist/test-utils.js +448 -0
  448. package/dist/test-utils.js.map +1 -0
  449. package/dist/test-utils.test.d.ts +2 -0
  450. package/dist/test-utils.test.d.ts.map +1 -0
  451. package/dist/test-utils.test.js +55 -0
  452. package/dist/test-utils.test.js.map +1 -0
  453. package/dist/utils.d.ts +26 -0
  454. package/dist/utils.d.ts.map +1 -0
  455. package/dist/utils.js +60 -0
  456. package/dist/utils.js.map +1 -0
  457. package/dist/wait-for-page-load.d.ts +16 -0
  458. package/dist/wait-for-page-load.d.ts.map +1 -0
  459. package/dist/wait-for-page-load.js +146 -0
  460. package/dist/wait-for-page-load.js.map +1 -0
  461. package/dist/workflow-capability.d.ts +72 -0
  462. package/dist/workflow-capability.d.ts.map +1 -0
  463. package/dist/workflow-capability.js +316 -0
  464. package/dist/workflow-capability.js.map +1 -0
  465. package/dist/workflow-script-flow.test.d.ts +2 -0
  466. package/dist/workflow-script-flow.test.d.ts.map +1 -0
  467. package/dist/workflow-script-flow.test.js +285 -0
  468. package/dist/workflow-script-flow.test.js.map +1 -0
  469. package/package.json +80 -0
  470. package/src/__snapshots__/x.com.processed.html +1003 -0
  471. package/src/__snapshots__/x.com.processed.withStyles.html +2720 -0
  472. package/src/a11y-client.ts +241 -0
  473. package/src/agent-skills/conan-config/SKILL.md +141 -0
  474. package/src/agent-skills/conan-config/agents/openai.yaml +4 -0
  475. package/src/aria-snapshot.test.ts +311 -0
  476. package/src/aria-snapshot.ts +1681 -0
  477. package/src/aria-snapshot.unit.test.ts +615 -0
  478. package/src/aria-snapshots/github-interactive.txt +193 -0
  479. package/src/aria-snapshots/github-raw.txt +309 -0
  480. package/src/aria-snapshots/hackernews-interactive.txt +477 -0
  481. package/src/aria-snapshots/hackernews-raw.txt +428 -0
  482. package/src/aria-snapshots/prosemirror-interactive.txt +66 -0
  483. package/src/aria-snapshots/prosemirror-raw.txt +145 -0
  484. package/src/assets/aria-labels-hacker-news.png +0 -0
  485. package/src/assets/aria-labels-old-reddit.png +0 -0
  486. package/src/assets/cursors/screen-studio/pointer-macos-tahoe-data-url.ts +5 -0
  487. package/src/assets/cursors/screen-studio/pointer-macos-tahoe.svg +18 -0
  488. package/src/assets/x.com.html +32946 -0
  489. package/src/browser-config.ts +179 -0
  490. package/src/browser-install.ts +284 -0
  491. package/src/browser-launch.ts +75 -0
  492. package/src/builtin-capabilities.ts +2765 -0
  493. package/src/capability-agent-skill.ts +305 -0
  494. package/src/capability-auth-state.ts +197 -0
  495. package/src/capability-auth.ts +249 -0
  496. package/src/capability-cli-confirmation.test.ts +90 -0
  497. package/src/capability-options.test.ts +89 -0
  498. package/src/capability-options.ts +57 -0
  499. package/src/capability-package.test.ts +204 -0
  500. package/src/capability-package.ts +555 -0
  501. package/src/capability-registry.test.ts +1905 -0
  502. package/src/capability-registry.ts +1234 -0
  503. package/src/capability-runner.ts +801 -0
  504. package/src/capability-search-relevance.test.ts +73 -0
  505. package/src/capability-studio.test.ts +45 -0
  506. package/src/capability-studio.ts +641 -0
  507. package/src/cdp-log.test.ts +131 -0
  508. package/src/cdp-log.ts +112 -0
  509. package/src/cdp-relay.ts +2944 -0
  510. package/src/cdp-session.ts +77 -0
  511. package/src/cdp-types.ts +158 -0
  512. package/src/channel-owner-inspect.test.ts +94 -0
  513. package/src/chrome-discovery.test.ts +50 -0
  514. package/src/chrome-discovery.ts +220 -0
  515. package/src/clean-html.ts +141 -0
  516. package/src/cli-help.test.ts +123 -0
  517. package/src/cli.ts +3181 -0
  518. package/src/cloud-client.ts +179 -0
  519. package/src/create-logger.ts +38 -0
  520. package/src/debugger-examples-types.ts +16 -0
  521. package/src/debugger-examples.ts +66 -0
  522. package/src/debugger.ts +708 -0
  523. package/src/diff-utils.test.ts +152 -0
  524. package/src/diff-utils.ts +70 -0
  525. package/src/doctor.test.ts +244 -0
  526. package/src/doctor.ts +321 -0
  527. package/src/editor-examples.ts +158 -0
  528. package/src/editor.ts +426 -0
  529. package/src/env-compat.ts +28 -0
  530. package/src/executor.ts +1824 -0
  531. package/src/executor.unit.test.ts +168 -0
  532. package/src/extension-connection.test.ts +855 -0
  533. package/src/extension-icon-warning.test.ts +45 -0
  534. package/src/ghost-browser.ts +149 -0
  535. package/src/ghost-cursor-client.ts +494 -0
  536. package/src/ghost-cursor-controller.ts +129 -0
  537. package/src/ghost-cursor.ts +123 -0
  538. package/src/htmlrewrite.test.ts +8008 -0
  539. package/src/htmlrewrite.ts +390 -0
  540. package/src/index.ts +53 -0
  541. package/src/kill-port.ts +219 -0
  542. package/src/kitty-graphics.test.ts +139 -0
  543. package/src/kitty-graphics.ts +79 -0
  544. package/src/locator-selector.test.ts +119 -0
  545. package/src/mcp-client.ts +78 -0
  546. package/src/mcp.ts +629 -0
  547. package/src/on-mouse-action.test.ts +226 -0
  548. package/src/options-page.test.ts +839 -0
  549. package/src/package-paths.ts +38 -0
  550. package/src/page-markdown.ts +240 -0
  551. package/src/performance-examples.ts +186 -0
  552. package/src/playwright-import.ts +59 -0
  553. package/src/popup-relocation.test.ts +163 -0
  554. package/src/product-paths.test.ts +47 -0
  555. package/src/product-paths.ts +28 -0
  556. package/src/protocol.test.ts +52 -0
  557. package/src/protocol.ts +351 -0
  558. package/src/react-source.ts +379 -0
  559. package/src/relay-client.test.ts +375 -0
  560. package/src/relay-client.ts +572 -0
  561. package/src/relay-core.test.ts +1603 -0
  562. package/src/relay-navigation.test.ts +790 -0
  563. package/src/relay-session.test.ts +1375 -0
  564. package/src/relay-state.test.ts +729 -0
  565. package/src/relay-state.ts +561 -0
  566. package/src/replay-ai-index.test.ts +278 -0
  567. package/src/replay-ai-index.ts +872 -0
  568. package/src/replay-cli.test.ts +325 -0
  569. package/src/replay-eval.ts +1039 -0
  570. package/src/replay-handoff.test.ts +163 -0
  571. package/src/replay-handoff.ts +109 -0
  572. package/src/replay-workflow-compiler.test.ts +250 -0
  573. package/src/replay-workflow-compiler.ts +700 -0
  574. package/src/resource.md +409 -0
  575. package/src/rrweb-recording-relay.test.ts +99 -0
  576. package/src/rrweb-recording-relay.ts +400 -0
  577. package/src/rrweb-recording.ts +322 -0
  578. package/src/scoped-fs.test.ts +80 -0
  579. package/src/scoped-fs.ts +420 -0
  580. package/src/skill-stub.test.ts +24 -0
  581. package/src/skill.md +1413 -0
  582. package/src/snapshot-tools.test.ts +1017 -0
  583. package/src/snapshots/hacker-news-accessibility-full.md +81 -0
  584. package/src/snapshots/hacker-news-accessibility-interactive.md +43 -0
  585. package/src/snapshots/page-markdown-output.txt +16 -0
  586. package/src/snapshots/shadcn-ui-accessibility-full.md +201 -0
  587. package/src/snapshots/shadcn-ui-accessibility-interactive.md +109 -0
  588. package/src/start-relay-server.ts +69 -0
  589. package/src/styles-examples.ts +84 -0
  590. package/src/styles.ts +343 -0
  591. package/src/tabwright-agent-skill.test.ts +61 -0
  592. package/src/tabwright-agent-skill.ts +105 -0
  593. package/src/test-declarations.ts +13 -0
  594. package/src/test-utils.test.ts +70 -0
  595. package/src/test-utils.ts +566 -0
  596. package/src/utils.ts +78 -0
  597. package/src/wait-for-page-load.ts +198 -0
  598. package/src/workflow-capability.ts +417 -0
  599. package/src/workflow-script-flow.test.ts +388 -0
@@ -0,0 +1,790 @@
1
+ import { describe, it, expect, beforeAll, afterAll } from 'vitest'
2
+ import { chromium, type Page } from '@xmorse/playwright-core'
3
+ import WebSocket from 'ws'
4
+ import path from 'node:path'
5
+ import { getCdpUrl } from './utils.js'
6
+ import {
7
+ setupTestContext,
8
+ cleanupTestContext,
9
+ getExtensionServiceWorker,
10
+ type TestContext,
11
+ withTimeout,
12
+ createSimpleServer,
13
+ } from './test-utils.js'
14
+ import './test-declarations.js'
15
+
16
+ const TEST_PORT = 19992
17
+ const FIXTURE_EXTENSION_PATH = path.resolve('../extension/test-fixtures/fixture-extension')
18
+
19
+ describe('Relay Navigation Tests', () => {
20
+ let testCtx: TestContext | null = null
21
+
22
+ beforeAll(async () => {
23
+ testCtx = await setupTestContext({
24
+ port: TEST_PORT,
25
+ tempDirPrefix: 'pw-nav-test-',
26
+ toggleExtension: true,
27
+ additionalExtensions: [FIXTURE_EXTENSION_PATH],
28
+ })
29
+ }, 600000)
30
+
31
+ afterAll(async () => {
32
+ await cleanupTestContext(testCtx)
33
+ testCtx = null
34
+ })
35
+
36
+ const getBrowserContext = () => {
37
+ if (!testCtx?.browserContext) throw new Error('Browser not initialized')
38
+ return testCtx.browserContext
39
+ }
40
+
41
+ const waitForStableDocumentReadyState = async ({ page, timeoutMs }: { page: Page; timeoutMs: number }) => {
42
+ const startTime = Date.now()
43
+
44
+ while (Date.now() - startTime < timeoutMs) {
45
+ try {
46
+ const readyState = await page.evaluate(() => {
47
+ return document.readyState
48
+ })
49
+ if (readyState !== 'loading') {
50
+ return
51
+ }
52
+ } catch (e) {
53
+ if (!(e instanceof Error) || !e.message.includes('Execution context was destroyed')) {
54
+ throw new Error('Failed while waiting for stable document readyState', { cause: e })
55
+ }
56
+ }
57
+
58
+ await page.waitForTimeout(100)
59
+ }
60
+
61
+ throw new Error(`Timed out waiting for stable document readyState after ${timeoutMs}ms`)
62
+ }
63
+
64
+ it('should be usable after toggle with valid URL', async () => {
65
+ // Validates the extension waits for a non-empty URL before attaching.
66
+
67
+ const browserContext = getBrowserContext()
68
+ const serviceWorker = await getExtensionServiceWorker(browserContext)
69
+ const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
70
+ const context = browser.contexts()[0]
71
+
72
+ const server = await createSimpleServer({
73
+ routes: {
74
+ '/': '<!doctype html><html><body>ok</body></html>',
75
+ },
76
+ })
77
+
78
+ const page = await browserContext.newPage()
79
+ try {
80
+ await page.goto(server.baseUrl, { waitUntil: 'domcontentloaded' })
81
+ await page.bringToFront()
82
+
83
+ const pagePromise = context.waitForEvent('page', { timeout: 5000 })
84
+
85
+ await serviceWorker.evaluate(async () => {
86
+ await globalThis.toggleExtensionForActiveTab()
87
+ })
88
+
89
+ const targetPage = await pagePromise
90
+ console.log('Page URL when event fired:', targetPage.url())
91
+
92
+ expect(targetPage.url()).not.toBe('')
93
+ expect(targetPage.url()).not.toBe(':')
94
+ expect(targetPage.url()).toContain(server.baseUrl)
95
+
96
+ const result = await targetPage.evaluate(() => window.location.href)
97
+ expect(result).toContain(server.baseUrl)
98
+ } finally {
99
+ await browser.close()
100
+ await page.close()
101
+ await server.close()
102
+ }
103
+ }, 15000)
104
+
105
+ it('should expose iframe frames when connecting to an existing page over CDP', async () => {
106
+ const browserContext = getBrowserContext()
107
+ const serviceWorker = await getExtensionServiceWorker(browserContext)
108
+
109
+ const childServer = await createSimpleServer({
110
+ routes: {
111
+ '/child.html': '<!doctype html><html><body>child</body></html>',
112
+ },
113
+ })
114
+ const childUrl = `${childServer.baseUrl}/child.html`
115
+
116
+ const parentServer = await createSimpleServer({
117
+ routes: {
118
+ '/': `<!doctype html><html><body><iframe src="${childUrl}"></iframe></body></html>`,
119
+ },
120
+ })
121
+
122
+ const page = await browserContext.newPage()
123
+ try {
124
+ await withTimeout({
125
+ promise: page.goto(parentServer.baseUrl, { waitUntil: 'domcontentloaded', timeout: 5000 }),
126
+ timeoutMs: 6000,
127
+ errorMessage: 'Timed out loading parent page for iframe test',
128
+ })
129
+ await withTimeout({
130
+ promise: page.frameLocator('iframe').locator('body').waitFor({ timeout: 5000 }),
131
+ timeoutMs: 6000,
132
+ errorMessage: 'Timed out waiting for iframe to attach in parent page',
133
+ })
134
+ expect(page.frames().map((frame) => frame.url())).toContain(childUrl)
135
+ await page.bringToFront()
136
+
137
+ await withTimeout({
138
+ promise: serviceWorker.evaluate(async () => {
139
+ await globalThis.toggleExtensionForActiveTab()
140
+ }),
141
+ timeoutMs: 5000,
142
+ errorMessage: 'Timed out toggling extension for iframe test',
143
+ })
144
+ await new Promise((r) => {
145
+ setTimeout(r, 400)
146
+ })
147
+
148
+ const browser = await withTimeout({
149
+ promise: chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT })),
150
+ timeoutMs: 5000,
151
+ errorMessage: 'Timed out connecting over CDP for iframe test',
152
+ })
153
+ const context = browser.contexts()[0]
154
+ const cdpPage = context.pages().find((candidate) => {
155
+ return candidate.url().startsWith(parentServer.baseUrl)
156
+ })
157
+ expect(cdpPage).toBeDefined()
158
+
159
+ const frames = cdpPage!.frames()
160
+ const childFrame = frames.find((frame) => {
161
+ return frame.url() === childUrl
162
+ })
163
+
164
+ expect(frames.length).toBe(2)
165
+ expect(childFrame).toBeDefined()
166
+
167
+ await withTimeout({
168
+ promise: browser.close(),
169
+ timeoutMs: 5000,
170
+ errorMessage: 'Timed out closing CDP browser for iframe test',
171
+ })
172
+ } finally {
173
+ await withTimeout({
174
+ promise: page.close(),
175
+ timeoutMs: 5000,
176
+ errorMessage: 'Timed out closing page for iframe test',
177
+ })
178
+ await Promise.all([parentServer.close(), childServer.close()])
179
+ }
180
+ }, 60000)
181
+
182
+ it('should resolve locators for cross-origin iframe that starts with empty src', async () => {
183
+ const browserContext = getBrowserContext()
184
+ const serviceWorker = await getExtensionServiceWorker(browserContext)
185
+
186
+ const childServer = await createSimpleServer({
187
+ routes: {
188
+ '/login.html': '<!doctype html><html><body><button id="login-btn">Login</button></body></html>',
189
+ '/canvas.html': '<!doctype html><html><body><button id="canvas-btn">Canvas</button></body></html>',
190
+ },
191
+ })
192
+ const loginUrl = `${childServer.baseUrl}/login.html`
193
+ const canvasUrl = `${childServer.baseUrl}/canvas.html`
194
+
195
+ const parentServer = await createSimpleServer({
196
+ routes: {
197
+ // Reproduces Framer-like plugin iframes: attached with empty src first,
198
+ // then navigated cross-origin after auto-attach is active.
199
+ '/': `<!doctype html>
200
+ <html>
201
+ <body>
202
+ <iframe id="plugin-frame"></iframe>
203
+ <script>
204
+ window.startPluginFlow = () => {
205
+ const frame = document.getElementById('plugin-frame');
206
+ frame.src = '${loginUrl}';
207
+ setTimeout(() => {
208
+ frame.src = '${canvasUrl}';
209
+ }, 150);
210
+ };
211
+ </script>
212
+ </body>
213
+ </html>`,
214
+ },
215
+ })
216
+
217
+ const page = await browserContext.newPage()
218
+ try {
219
+ await withTimeout({
220
+ promise: page.goto(parentServer.baseUrl, { waitUntil: 'domcontentloaded', timeout: 5000 }),
221
+ timeoutMs: 6000,
222
+ errorMessage: 'Timed out loading parent page for empty-src iframe test',
223
+ })
224
+ await page.bringToFront()
225
+
226
+ await withTimeout({
227
+ promise: serviceWorker.evaluate(async () => {
228
+ await globalThis.toggleExtensionForActiveTab()
229
+ }),
230
+ timeoutMs: 5000,
231
+ errorMessage: 'Timed out toggling extension for empty-src iframe test',
232
+ })
233
+
234
+ const browser = await withTimeout({
235
+ promise: chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT })),
236
+ timeoutMs: 5000,
237
+ errorMessage: 'Timed out connecting over CDP for empty-src iframe test',
238
+ })
239
+
240
+ try {
241
+ const context = browser.contexts()[0]
242
+ const cdpPage = context.pages().find((candidate) => {
243
+ return candidate.url().startsWith(parentServer.baseUrl)
244
+ })
245
+ expect(cdpPage).toBeDefined()
246
+
247
+ await withTimeout({
248
+ promise: page.evaluate(() => {
249
+ ;(window as Window & { startPluginFlow?: () => void }).startPluginFlow?.()
250
+ }),
251
+ timeoutMs: 3000,
252
+ errorMessage: 'Timed out starting plugin iframe flow',
253
+ })
254
+
255
+ const pluginFrame = await withTimeout({
256
+ promise: (async () => {
257
+ for (let attempt = 0; attempt < 40; attempt += 1) {
258
+ const frame = cdpPage!.frames().find((candidate) => {
259
+ return candidate.url() === loginUrl || candidate.url() === canvasUrl
260
+ })
261
+ if (frame) {
262
+ return frame
263
+ }
264
+ await cdpPage!.waitForTimeout(100)
265
+ }
266
+ throw new Error('Plugin frame did not appear with expected URL')
267
+ })(),
268
+ timeoutMs: 5000,
269
+ errorMessage: 'Timed out waiting for plugin frame URL in empty-src iframe test',
270
+ })
271
+
272
+ await withTimeout({
273
+ promise: pluginFrame.locator('button').first().waitFor({ state: 'attached' }),
274
+ timeoutMs: 5000,
275
+ errorMessage: 'Timed out waiting for button locator in empty-src iframe test',
276
+ })
277
+
278
+ const buttonCount = await pluginFrame.locator('button').count()
279
+ expect(buttonCount).toBe(1)
280
+ } finally {
281
+ await withTimeout({
282
+ promise: browser.close(),
283
+ timeoutMs: 5000,
284
+ errorMessage: 'Timed out closing CDP browser for empty-src iframe test',
285
+ })
286
+ }
287
+ } finally {
288
+ await withTimeout({
289
+ promise: page.close(),
290
+ timeoutMs: 5000,
291
+ errorMessage: 'Timed out closing page for empty-src iframe test',
292
+ })
293
+ await Promise.all([parentServer.close(), childServer.close()])
294
+ }
295
+ }, 60000)
296
+
297
+ it('should have non-empty URLs when connecting to already-loaded pages', async () => {
298
+ const _browserContext = getBrowserContext()
299
+ const serviceWorker = await getExtensionServiceWorker(_browserContext)
300
+
301
+ const page = await _browserContext.newPage()
302
+ await page.goto('https://discord.com/login', { waitUntil: 'load' })
303
+ await page.bringToFront()
304
+
305
+ await serviceWorker.evaluate(async () => {
306
+ await globalThis.toggleExtensionForActiveTab()
307
+ })
308
+
309
+ const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
310
+ const context = browser.contexts()[0]
311
+
312
+ const pages = context.pages()
313
+ console.log(
314
+ 'All page URLs:',
315
+ pages.map((p) => p.url()),
316
+ )
317
+
318
+ expect(pages.length).toBeGreaterThan(0)
319
+ for (const p of pages) {
320
+ expect(p.url()).not.toBe('')
321
+ expect(p.url()).not.toBe(':')
322
+ expect(p.url()).not.toBeUndefined()
323
+ }
324
+
325
+ const discordPage = pages.find((p) => p.url().includes('discord.com'))
326
+ expect(discordPage).toBeDefined()
327
+
328
+ const result = await discordPage!.evaluate(() => window.location.href)
329
+ expect(result).toContain('discord.com')
330
+
331
+ await browser.close()
332
+ await page.close()
333
+ }, 60000)
334
+
335
+ it('should navigate to notion without hanging', async () => {
336
+ const browserContext = getBrowserContext()
337
+ const serviceWorker = await getExtensionServiceWorker(browserContext)
338
+
339
+ const page = await browserContext.newPage()
340
+ const initialUrl = 'https://example.com/notion-repro'
341
+ await page.goto(initialUrl)
342
+ await page.bringToFront()
343
+
344
+ await serviceWorker.evaluate(async () => {
345
+ await globalThis.toggleExtensionForActiveTab()
346
+ })
347
+
348
+ await new Promise((r) => setTimeout(r, 100))
349
+
350
+ const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
351
+ const cdpPage = browser
352
+ .contexts()[0]
353
+ .pages()
354
+ .find((p) => p.url() === initialUrl)
355
+ expect(cdpPage).toBeDefined()
356
+
357
+ const response = await cdpPage!.goto('https://www.notion.so', { waitUntil: 'domcontentloaded', timeout: 20000 })
358
+
359
+ const currentUrl = cdpPage!.url()
360
+ const responseUrl = response?.url() ?? ''
361
+ expect(responseUrl).toMatch(/notion\.(so|com)/)
362
+ expect(currentUrl).toMatch(/notion\.(so|com)/)
363
+ expect(await cdpPage!.evaluate(() => document.readyState)).not.toBe('loading')
364
+
365
+ await browser.close()
366
+ await page.close()
367
+ }, 60000)
368
+
369
+ it('should navigate to youtube without hanging', async () => {
370
+ const browserContext = getBrowserContext()
371
+ const serviceWorker = await getExtensionServiceWorker(browserContext)
372
+
373
+ const page = await browserContext.newPage()
374
+ await page.goto('about:blank')
375
+ await page.bringToFront()
376
+
377
+ await serviceWorker.evaluate(async () => {
378
+ await globalThis.toggleExtensionForActiveTab()
379
+ })
380
+
381
+ await new Promise((r) => setTimeout(r, 100))
382
+
383
+ const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
384
+ const cdpPage = browser
385
+ .contexts()[0]
386
+ .pages()
387
+ .find((p) => p.url().includes('about:'))
388
+ expect(cdpPage).toBeDefined()
389
+
390
+ const response = await cdpPage!.goto('https://www.youtube.com', { waitUntil: 'domcontentloaded', timeout: 20000 })
391
+ const currentUrl = cdpPage!.url()
392
+ const responseUrl = response?.url() ?? ''
393
+
394
+ expect(responseUrl).toContain('youtube')
395
+ expect(currentUrl).toContain('youtube')
396
+ await waitForStableDocumentReadyState({ page: cdpPage!, timeoutMs: 5000 })
397
+
398
+ await browser.close()
399
+ await page.close()
400
+ }, 60000)
401
+
402
+ it('should maintain correct page.url() with iframe-heavy pages', async () => {
403
+ const browserContext = getBrowserContext()
404
+ const serviceWorker = await getExtensionServiceWorker(browserContext)
405
+
406
+ const page = await browserContext.newPage()
407
+ await page.setContent(`
408
+ <html>
409
+ <head><title>Iframe Test Page</title></head>
410
+ <body>
411
+ <h1>Iframe Heavy Page</h1>
412
+ <iframe src="about:blank" id="frame1"></iframe>
413
+ <iframe src="about:blank" id="frame2"></iframe>
414
+ <iframe src="about:blank" id="frame3"></iframe>
415
+ </body>
416
+ </html>
417
+ `)
418
+ await page.bringToFront()
419
+
420
+ await serviceWorker.evaluate(async () => {
421
+ await globalThis.toggleExtensionForActiveTab()
422
+ })
423
+
424
+ await new Promise((r) => setTimeout(r, 100))
425
+
426
+ for (let i = 0; i < 3; i++) {
427
+ const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
428
+ const pages = browser.contexts()[0].pages()
429
+ let iframePage
430
+ for (const p of pages) {
431
+ const html = await p.content()
432
+ if (html.includes('Iframe Heavy Page')) {
433
+ iframePage = p
434
+ break
435
+ }
436
+ }
437
+
438
+ expect(iframePage).toBeDefined()
439
+ expect(iframePage?.url()).toContain('about:')
440
+
441
+ await browser.close()
442
+ await new Promise((r) => setTimeout(r, 100))
443
+ }
444
+
445
+ await page.close()
446
+ }, 30000)
447
+
448
+ it('should work with stagehand', async () => {
449
+ const browserContext = getBrowserContext()
450
+ const serviceWorker = await getExtensionServiceWorker(browserContext)
451
+
452
+ await serviceWorker.evaluate(async () => {
453
+ await globalThis.disconnectEverything()
454
+ })
455
+ await new Promise((r) => setTimeout(r, 100))
456
+
457
+ const targetUrl = 'https://example.com/'
458
+
459
+ const enableResult = await serviceWorker.evaluate(async (url) => {
460
+ const tab = await chrome.tabs.create({ url, active: true })
461
+ await new Promise((r) => setTimeout(r, 100))
462
+ return await globalThis.toggleExtensionForActiveTab()
463
+ }, targetUrl)
464
+
465
+ console.log('Extension enabled:', enableResult)
466
+ expect(enableResult.isConnected).toBe(true)
467
+
468
+ await new Promise((r) => setTimeout(r, 100))
469
+
470
+ const { Stagehand } = await import('@browserbasehq/stagehand')
471
+
472
+ const stagehand = new Stagehand({
473
+ env: 'LOCAL',
474
+ verbose: 1,
475
+ disablePino: true,
476
+ localBrowserLaunchOptions: {
477
+ cdpUrl: getCdpUrl({ port: TEST_PORT }),
478
+ },
479
+ })
480
+
481
+ console.log('Initializing Stagehand...')
482
+ await stagehand.init()
483
+ console.log('Stagehand initialized')
484
+
485
+ const context = stagehand.context
486
+ expect(context).toBeDefined()
487
+
488
+ const pages = context.pages()
489
+ console.log(
490
+ 'Stagehand pages:',
491
+ pages.length,
492
+ pages.map((p) => p.url()),
493
+ )
494
+
495
+ const stagehandPage = pages.find((p) => p.url().includes('example.com'))
496
+ expect(stagehandPage).toBeDefined()
497
+
498
+ const url = stagehandPage!.url()
499
+ console.log('Stagehand page URL:', url)
500
+ expect(url).toContain('example.com')
501
+
502
+ await stagehand.close()
503
+ }, 60000)
504
+
505
+ it('should expose CDP discovery endpoints /json/version and /json/list', async () => {
506
+ const browserContext = getBrowserContext()
507
+ const serviceWorker = await getExtensionServiceWorker(browserContext)
508
+
509
+ const page = await browserContext.newPage()
510
+ await page.goto('https://example.com')
511
+ await page.bringToFront()
512
+
513
+ await serviceWorker.evaluate(async () => {
514
+ await globalThis.toggleExtensionForActiveTab()
515
+ })
516
+ await new Promise((r) => setTimeout(r, 200))
517
+
518
+ // Test /json/version
519
+ const versionRes = await fetch(`http://127.0.0.1:${TEST_PORT}/json/version`)
520
+ expect(versionRes.status).toBe(200)
521
+ const versionJson = (await versionRes.json()) as { webSocketDebuggerUrl: string }
522
+ expect(versionJson).toMatchObject({
523
+ Browser: expect.stringContaining('Tabwright/'),
524
+ 'Protocol-Version': '1.3',
525
+ webSocketDebuggerUrl: expect.stringContaining('ws://'),
526
+ })
527
+ expect(versionJson.webSocketDebuggerUrl).toContain(`127.0.0.1:${TEST_PORT}/cdp`)
528
+
529
+ // Test /json/version/ (trailing slash)
530
+ const versionSlashRes = await fetch(`http://127.0.0.1:${TEST_PORT}/json/version/`)
531
+ expect(versionSlashRes.status).toBe(200)
532
+
533
+ // Test /json/list
534
+ const listRes = await fetch(`http://127.0.0.1:${TEST_PORT}/json/list`)
535
+ expect(listRes.status).toBe(200)
536
+ const listJson = (await listRes.json()) as Array<{ url?: string }>
537
+ expect(Array.isArray(listJson)).toBe(true)
538
+ expect(listJson.length).toBeGreaterThan(0)
539
+
540
+ const examplePage = listJson.find((t) => t.url?.includes('example.com'))
541
+ expect(examplePage).toBeDefined()
542
+ expect(examplePage).toMatchObject({
543
+ id: expect.any(String),
544
+ type: 'page',
545
+ url: expect.stringContaining('example.com'),
546
+ webSocketDebuggerUrl: expect.stringContaining('ws://'),
547
+ })
548
+
549
+ // Test /json (alias for /json/list)
550
+ const jsonRes = await fetch(`http://127.0.0.1:${TEST_PORT}/json`)
551
+ expect(jsonRes.status).toBe(200)
552
+ const jsonData = await jsonRes.json()
553
+ expect(Array.isArray(jsonData)).toBe(true)
554
+
555
+ // Test PUT method (Chrome 66+ prefers PUT)
556
+ const putRes = await fetch(`http://127.0.0.1:${TEST_PORT}/json/version`, { method: 'PUT' })
557
+ expect(putRes.status).toBe(200)
558
+
559
+ await page.close()
560
+ }, 60000)
561
+
562
+ // Regression test for https://github.com/remorses/playwriter/issues/40
563
+ // When Playwright sends Target.detachFromTarget on the root CDP session (no top-level
564
+ // sessionId), the extension must still route the command by looking at params.sessionId.
565
+ // Previously the extension threw "No tab found for method Target.detachFromTarget"
566
+ // because it only checked the top-level sessionId for routing, which is absent on root
567
+ // session commands. This caused cascading disconnects and instability.
568
+ it('should route Target.detachFromTarget without top-level sessionId (issue #40)', async () => {
569
+ const browserContext = getBrowserContext()
570
+ const serviceWorker = await getExtensionServiceWorker(browserContext)
571
+
572
+ const server = await createSimpleServer({
573
+ routes: { '/': '<!doctype html><html><body>detach test</body></html>' },
574
+ })
575
+
576
+ const page = await browserContext.newPage()
577
+ try {
578
+ await page.goto(server.baseUrl, { waitUntil: 'domcontentloaded' })
579
+ await page.bringToFront()
580
+
581
+ await withTimeout({
582
+ promise: serviceWorker.evaluate(async () => {
583
+ await globalThis.toggleExtensionForActiveTab()
584
+ }),
585
+ timeoutMs: 5000,
586
+ errorMessage: 'Timed out toggling extension for detach test',
587
+ })
588
+ await new Promise((r) => {
589
+ setTimeout(r, 400)
590
+ })
591
+
592
+ // Connect a raw WebSocket to the relay — this lets us send CDP messages
593
+ // exactly as they appear on the wire, without Playwright adding sessionId.
594
+ const ws = new WebSocket(`ws://localhost:${TEST_PORT}/cdp/test-detach-raw`)
595
+ await new Promise<void>((resolve, reject) => {
596
+ ws.on('open', () => {
597
+ resolve()
598
+ })
599
+ ws.on('error', reject)
600
+ })
601
+
602
+ let nextId = 1
603
+ const sendCdp = <T = unknown>(msg: Record<string, unknown>): Promise<T> => {
604
+ return new Promise((resolve, reject) => {
605
+ const id = nextId++
606
+ const timeout = setTimeout(() => {
607
+ ws.off('message', handler)
608
+ reject(new Error(`CDP response timeout for id ${id}`))
609
+ }, 5000)
610
+
611
+ const handler = (data: WebSocket.RawData) => {
612
+ const parsed = JSON.parse(data.toString())
613
+ if (parsed.id === id) {
614
+ ws.off('message', handler)
615
+ clearTimeout(timeout)
616
+ resolve(parsed as T)
617
+ }
618
+ }
619
+ ws.on('message', handler)
620
+ ws.send(JSON.stringify({ id, ...msg }))
621
+ })
622
+ }
623
+
624
+ // Collect async events from the relay
625
+ const events: Array<{ method: string; params: Record<string, unknown>; sessionId?: string }> = []
626
+ ws.on('message', (data) => {
627
+ const msg = JSON.parse(data.toString())
628
+ if (!msg.id && msg.method) {
629
+ events.push(msg)
630
+ }
631
+ })
632
+
633
+ // Trigger Target.setAutoAttach so the relay sends Target.attachedToTarget for
634
+ // all connected tabs. This gives us the page's pw-tab-* sessionId.
635
+ await sendCdp({
636
+ method: 'Target.setAutoAttach',
637
+ params: { autoAttach: true, waitForDebuggerOnStart: false, flatten: true },
638
+ })
639
+
640
+ // Wait for events to arrive
641
+ await new Promise((r) => {
642
+ setTimeout(r, 500)
643
+ })
644
+
645
+ // Filter for the specific page target by URL to avoid grabbing wrong sessions
646
+ // (welcome tab, extension pages, etc.)
647
+ type AttachParams = { sessionId?: string; targetInfo?: { type?: string; url?: string } }
648
+ const attachEvent = events.find((e) => {
649
+ if (e.method !== 'Target.attachedToTarget') {
650
+ return false
651
+ }
652
+ const p = e.params as AttachParams
653
+ return p.targetInfo?.type === 'page' && p.targetInfo?.url?.startsWith(server.baseUrl)
654
+ })
655
+ expect(attachEvent).toBeDefined()
656
+ const pageSessionId = (attachEvent!.params as AttachParams).sessionId
657
+ expect(pageSessionId).toBeTruthy()
658
+
659
+ // Verify the session is usable before detach — send a command that requires routing.
660
+ const evalBefore = await sendCdp<{ id: number; error?: { message: string }; result?: unknown }>({
661
+ method: 'Runtime.evaluate',
662
+ sessionId: pageSessionId,
663
+ params: { expression: '1 + 1', returnByValue: true },
664
+ })
665
+ expect(evalBefore.error).toBeUndefined()
666
+ expect((evalBefore.result as { result?: { value?: number } })?.result?.value).toBe(2)
667
+
668
+ // NOW: send Target.detachFromTarget WITHOUT a top-level sessionId.
669
+ // This is the exact wire format Playwright uses when sending on the root session
670
+ // (e.g. from CRSession.detach() where _parentSession is the root browser session).
671
+ // The extension must route this by looking at params.sessionId.
672
+ const detachResult = await sendCdp<{ id: number; error?: { message: string }; result?: unknown }>({
673
+ method: 'Target.detachFromTarget',
674
+ // Intentionally NO sessionId field — this is the root session
675
+ params: { sessionId: pageSessionId },
676
+ })
677
+
678
+ // Must not fail with extension routing error — the command must reach Chrome.
679
+ // Chrome returns "No session with given id" because pw-tab-* is a virtual session
680
+ // managed by the relay, not a real Chrome CDP session. This is expected — the key
681
+ // proof is that the extension routed the command to Chrome instead of throwing
682
+ // "No tab found" at the routing layer.
683
+ expect(detachResult.error?.message).not.toContain('No tab found')
684
+ expect(detachResult.error?.message).toContain('No session with given id')
685
+
686
+ ws.close()
687
+ } finally {
688
+ await page.close()
689
+ await server.close()
690
+ }
691
+ }, 30000)
692
+
693
+ it('should not crash when page has chrome-extension:// iframe from another extension', async () => {
694
+ // Reproduces https://github.com/remorses/playwriter/issues/18
695
+ // Extensions like LastPass, SurfingKeys inject chrome-extension:// iframes into every page.
696
+ // When Tabwright attaches the debugger and Target.setAutoAttach is active, Chrome
697
+ // auto-attaches to these restricted iframe targets. Without filtering, the relay tries
698
+ // to send Runtime.runIfWaitingForDebugger to the restricted child session, which Chrome
699
+ // blocks with "Cannot access a chrome-extension:// URL of a different extension",
700
+ // causing the entire debugger to detach.
701
+
702
+ const browserContext = getBrowserContext()
703
+ const serviceWorker = await getExtensionServiceWorker(browserContext)
704
+
705
+ // Discover the fixture extension's ID from its service worker
706
+ const tabwrightExtId = serviceWorker.url().match(/chrome-extension:\/\/([^/]+)/)?.[1]
707
+ expect(tabwrightExtId).toBeTruthy()
708
+
709
+ let fixtureExtId: string | undefined
710
+ for (let i = 0; i < 50; i++) {
711
+ const allSws = browserContext.serviceWorkers()
712
+ const fixtureSw = allSws.find((sw) => {
713
+ const id = sw.url().match(/chrome-extension:\/\/([^/]+)/)?.[1]
714
+ return id && id !== tabwrightExtId
715
+ })
716
+ if (fixtureSw) {
717
+ fixtureExtId = fixtureSw.url().match(/chrome-extension:\/\/([^/]+)/)?.[1]
718
+ break
719
+ }
720
+ await new Promise((r) => {
721
+ setTimeout(r, 100)
722
+ })
723
+ }
724
+ expect(fixtureExtId).toBeTruthy()
725
+ console.log('Fixture extension ID:', fixtureExtId)
726
+
727
+ // Create a page that embeds the fixture extension's page as an iframe,
728
+ // reproducing what extensions like SurfingKeys/LastPass do.
729
+ const server = await createSimpleServer({
730
+ routes: {
731
+ '/': `<!doctype html><html><body>
732
+ <h1>Main page</h1>
733
+ <iframe src="chrome-extension://${fixtureExtId}/page.html" id="ext-iframe"></iframe>
734
+ </body></html>`,
735
+ },
736
+ })
737
+
738
+ const page = await browserContext.newPage()
739
+ try {
740
+ await page.goto(server.baseUrl, { waitUntil: 'domcontentloaded' })
741
+ await page.bringToFront()
742
+
743
+ // Enable Tabwright on this page — this must NOT crash the debugger
744
+ await withTimeout({
745
+ promise: serviceWorker.evaluate(async () => {
746
+ await globalThis.toggleExtensionForActiveTab()
747
+ }),
748
+ timeoutMs: 5000,
749
+ errorMessage: 'Timed out toggling extension on page with chrome-extension:// iframe',
750
+ })
751
+
752
+ // Give time for any async errors (Target.attachedToTarget for the iframe) to surface
753
+ await new Promise((r) => {
754
+ setTimeout(r, 1500)
755
+ })
756
+
757
+ // Verify the extension is still connected by connecting over CDP and interacting
758
+ const browser = await withTimeout({
759
+ promise: chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT })),
760
+ timeoutMs: 5000,
761
+ errorMessage: 'Timed out connecting over CDP — extension likely crashed',
762
+ })
763
+ const context = browser.contexts()[0]
764
+ const cdpPage = context.pages().find((p) => p.url().startsWith(server.baseUrl))
765
+ expect(cdpPage).toBeDefined()
766
+
767
+ // Verify we can execute JS on the page (proves the debugger session is alive)
768
+ const title = await cdpPage!.evaluate(() => document.querySelector('h1')?.textContent)
769
+ expect(title).toBe('Main page')
770
+
771
+ // Verify the chrome-extension:// iframe did NOT get exposed as a frame
772
+ // (it should be filtered out as a restricted target)
773
+ const frames = cdpPage!.frames()
774
+ const extFrame = frames.find((f) => f.url().startsWith('chrome-extension://'))
775
+ expect(extFrame).toBeUndefined()
776
+
777
+ await browser.close()
778
+ } finally {
779
+ // Toggle off to clean up
780
+ await page.bringToFront()
781
+ await serviceWorker
782
+ .evaluate(async () => {
783
+ await globalThis.toggleExtensionForActiveTab()
784
+ })
785
+ .catch(() => {})
786
+ await page.close()
787
+ await server.close()
788
+ }
789
+ }, 30000)
790
+ })