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,566 @@
1
+ import { exec } from 'node:child_process'
2
+ import { promisify } from 'node:util'
3
+ import http from 'node:http'
4
+ import net from 'node:net'
5
+ import { chromium, type BrowserContext } from '@xmorse/playwright-core'
6
+ import path from 'node:path'
7
+ import fs from 'node:fs'
8
+ import os from 'node:os'
9
+ import { startTabwrightCDPRelayServer, type RelayServer } from './cdp-relay.js'
10
+ import { createFileLogger } from './create-logger.js'
11
+ import { killPortProcess } from './kill-port.js'
12
+
13
+ const execAsync = promisify(exec)
14
+ const extensionBuildQueues: Map<string, Promise<void>> = new Map()
15
+ const EXTENSION_SERVICE_WORKER_TIMEOUT_MS = 15_000
16
+
17
+ export function getLocalChromeExecutable(): string | undefined {
18
+ const candidates = [
19
+ process.env.PLAYWRIGHT_CHROME_EXECUTABLE,
20
+ '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
21
+ '/Applications/Chromium.app/Contents/MacOS/Chromium',
22
+ '/usr/bin/google-chrome',
23
+ '/usr/bin/google-chrome-stable',
24
+ '/usr/bin/chromium',
25
+ '/usr/bin/chromium-browser',
26
+ 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
27
+ 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
28
+ ].filter((candidate): candidate is string => {
29
+ return Boolean(candidate)
30
+ })
31
+
32
+ return candidates.find((candidate) => {
33
+ return fs.existsSync(candidate)
34
+ })
35
+ }
36
+
37
+ export function getLegacyExtensionLaunchArgs({ extensionPaths }: { extensionPaths: string[] }): string[] {
38
+ const resolvedExtensionPaths = extensionPaths.map((extensionPath) => {
39
+ return path.resolve(extensionPath)
40
+ })
41
+ const legacyExtensionPaths = resolvedExtensionPaths.join(',')
42
+
43
+ return [
44
+ `--disable-extensions-except=${legacyExtensionPaths}`,
45
+ `--load-extension=${legacyExtensionPaths}`,
46
+ ]
47
+ }
48
+
49
+ export function isExtensionLoadUnavailableError(error: unknown): boolean {
50
+ const message = error instanceof Error ? error.message : String(error)
51
+ return (
52
+ message.includes('Method not found') ||
53
+ message.includes("'Extensions.loadUnpacked' wasn't found") ||
54
+ message.includes('-32601')
55
+ )
56
+ }
57
+
58
+ async function loadUnpackedExtensionsThroughCdp({
59
+ browserContext,
60
+ extensionPaths,
61
+ }: {
62
+ browserContext: BrowserContext
63
+ extensionPaths: string[]
64
+ }): Promise<void> {
65
+ const browser = browserContext.browser()
66
+ if (!browser) {
67
+ throw new Error('Persistent browser context has no browser connection')
68
+ }
69
+
70
+ const session = await browser.newBrowserCDPSession()
71
+ try {
72
+ await Promise.all(
73
+ extensionPaths.map(async (extensionPath) => {
74
+ await session.send('Extensions.loadUnpacked', { path: path.resolve(extensionPath) })
75
+ }),
76
+ )
77
+ } finally {
78
+ await session.detach().catch((error: unknown) => {
79
+ console.error('Failed to detach test extension CDP session:', error)
80
+ })
81
+ }
82
+ }
83
+
84
+ async function launchTestBrowser({
85
+ userDataDir,
86
+ args,
87
+ }: {
88
+ userDataDir: string
89
+ args: string[]
90
+ }): Promise<BrowserContext> {
91
+ const chromeExecutable = getLocalChromeExecutable()
92
+ return await chromium.launchPersistentContext(userDataDir, {
93
+ ...(chromeExecutable ? { executablePath: chromeExecutable } : { channel: 'chromium' }),
94
+ headless: !process.env.HEADFUL,
95
+ colorScheme: 'dark',
96
+ ignoreDefaultArgs: ['--disable-extensions'],
97
+ args,
98
+ })
99
+ }
100
+
101
+ export async function launchPersistentContextWithExtensions({
102
+ userDataDir,
103
+ extensionPaths,
104
+ }: {
105
+ userDataDir: string
106
+ extensionPaths: string[]
107
+ }): Promise<BrowserContext> {
108
+ const browserContext = await launchTestBrowser({
109
+ userDataDir,
110
+ // Chrome 137+ ignores --load-extension for branded builds. Its supported
111
+ // Extensions.loadUnpacked replacement requires this opt-in switch.
112
+ args: ['--enable-unsafe-extension-debugging'],
113
+ })
114
+
115
+ try {
116
+ await loadUnpackedExtensionsThroughCdp({ browserContext, extensionPaths })
117
+ return browserContext
118
+ } catch (error: unknown) {
119
+ await browserContext.close()
120
+ if (!isExtensionLoadUnavailableError(error)) {
121
+ throw new Error(`Failed to load unpacked test extensions: ${extensionPaths.join(', ')}`, { cause: error })
122
+ }
123
+
124
+ // Older Chrome/Chromium does not expose Extensions.loadUnpacked. Relaunch
125
+ // the same isolated profile with the legacy flags it still supports.
126
+ return await launchTestBrowser({
127
+ userDataDir,
128
+ args: getLegacyExtensionLaunchArgs({ extensionPaths }),
129
+ })
130
+ }
131
+ }
132
+
133
+ async function buildExtension({ port, distDir }: { port: number; distDir: string }): Promise<void> {
134
+ const previous = extensionBuildQueues.get(distDir) || Promise.resolve()
135
+ const buildPromise = previous
136
+ .catch((error) => {
137
+ console.error('Previous extension build failed:', error)
138
+ })
139
+ .then(async () => {
140
+ // Build into a per-port dist to avoid parallel test runs overwriting each other.
141
+ await execAsync(`TESTING=1 TABWRIGHT_PORT=${port} TABWRIGHT_EXTENSION_DIST=${distDir} pnpm build`, {
142
+ cwd: '../extension',
143
+ })
144
+ })
145
+
146
+ extensionBuildQueues.set(
147
+ distDir,
148
+ buildPromise.finally(() => {}),
149
+ )
150
+ await buildPromise
151
+ }
152
+
153
+ export async function getExtensionServiceWorker(context: BrowserContext) {
154
+ const serviceWorkers = context.serviceWorkers().filter((sw) => sw.url().startsWith('chrome-extension://'))
155
+ if (serviceWorkers.length === 0) {
156
+ await context
157
+ .waitForEvent('serviceworker', {
158
+ predicate: (sw) => sw.url().startsWith('chrome-extension://'),
159
+ timeout: EXTENSION_SERVICE_WORKER_TIMEOUT_MS,
160
+ })
161
+ .catch((error: unknown) => {
162
+ throw new Error(
163
+ `No extension service worker appeared within ${EXTENSION_SERVICE_WORKER_TIMEOUT_MS}ms`,
164
+ { cause: error },
165
+ )
166
+ })
167
+ }
168
+
169
+ // Check all chrome-extension service workers for the Tabwright one (the one
170
+ // that exposes toggleExtensionForActiveTab). This handles cases where
171
+ // additional test fixture extensions are loaded alongside Tabwright.
172
+ for (let i = 0; i < 50; i++) {
173
+ const allSws = context.serviceWorkers().filter((sw) => sw.url().startsWith('chrome-extension://'))
174
+ for (const sw of allSws) {
175
+ try {
176
+ const isReady = await sw.evaluate(() => {
177
+ // @ts-ignore
178
+ return typeof globalThis.toggleExtensionForActiveTab === 'function'
179
+ })
180
+ if (isReady) {
181
+ return sw
182
+ }
183
+ } catch {
184
+ // Service worker might not be ready yet
185
+ }
186
+ }
187
+ await new Promise((r) => setTimeout(r, 100))
188
+ }
189
+
190
+ const extensionWorkerUrls = context
191
+ .serviceWorkers()
192
+ .filter((sw) => {
193
+ return sw.url().startsWith('chrome-extension://')
194
+ })
195
+ .map((sw) => {
196
+ return sw.url()
197
+ })
198
+ throw new Error(
199
+ `Tabwright extension service worker did not become ready within ${EXTENSION_SERVICE_WORKER_TIMEOUT_MS}ms. Visible extension workers: ${extensionWorkerUrls.join(', ') || 'none'}`,
200
+ )
201
+ }
202
+
203
+ export interface TestContext {
204
+ browserContext: BrowserContext
205
+ userDataDir: string
206
+ relayServer: RelayServer
207
+ }
208
+
209
+ export async function setupTestContext({
210
+ port,
211
+ tempDirPrefix,
212
+ toggleExtension = false,
213
+ additionalExtensions = [],
214
+ }: {
215
+ port: number
216
+ tempDirPrefix: string
217
+ /** Create initial page and toggle extension on it */
218
+ toggleExtension?: boolean
219
+ /** Additional extension paths to load alongside the main Tabwright extension */
220
+ additionalExtensions?: string[]
221
+ }): Promise<TestContext> {
222
+ await killPortProcess({ port }).catch(() => {})
223
+
224
+ // Use a port-scoped dist folder so parallel tests don't replace each other's extension builds.
225
+ const distDir = `dist-${port}`
226
+
227
+ console.log('Building extension...')
228
+ await buildExtension({ port, distDir })
229
+ console.log('Extension built')
230
+
231
+ const localLogPath = path.join(process.cwd(), 'relay-server.log')
232
+ const logger = createFileLogger({ logFilePath: localLogPath })
233
+ const relayServer = await startTabwrightCDPRelayServer({ port, logger })
234
+
235
+ const userDataDir = fs.mkdtempSync(path.join(os.tmpdir(), tempDirPrefix))
236
+ const extensionPath = path.resolve('../extension', distDir)
237
+ try {
238
+ const browserContext = await launchPersistentContextWithExtensions({
239
+ userDataDir,
240
+ extensionPaths: [extensionPath, ...additionalExtensions],
241
+ })
242
+
243
+ try {
244
+ if (toggleExtension) {
245
+ const serviceWorker = await getExtensionServiceWorker(browserContext)
246
+ const page = await browserContext.newPage()
247
+ await page.goto('about:blank')
248
+ await serviceWorker.evaluate(async () => {
249
+ await (globalThis as any).toggleExtensionForActiveTab()
250
+ })
251
+ }
252
+
253
+ return { browserContext, userDataDir, relayServer }
254
+ } catch (error) {
255
+ await browserContext.close().catch((closeError: unknown) => {
256
+ console.error('Failed to close browser after test setup error:', closeError)
257
+ })
258
+ throw error
259
+ }
260
+ } catch (error) {
261
+ relayServer.close()
262
+ try {
263
+ fs.rmSync(userDataDir, { recursive: true, force: true })
264
+ } catch (cleanupError) {
265
+ console.error('Failed to clean test profile after setup error:', cleanupError)
266
+ }
267
+ throw error
268
+ }
269
+ }
270
+
271
+ export async function cleanupTestContext(
272
+ ctx: TestContext | null,
273
+ cleanup?: (() => Promise<void>) | null,
274
+ ): Promise<void> {
275
+ if (ctx?.browserContext) {
276
+ await ctx.browserContext.close()
277
+ }
278
+ if (ctx?.relayServer) {
279
+ ctx.relayServer.close()
280
+ }
281
+
282
+ if (ctx?.userDataDir) {
283
+ try {
284
+ fs.rmSync(ctx.userDataDir, { recursive: true, force: true })
285
+ } catch (e) {
286
+ console.error('Failed to cleanup user data dir:', e)
287
+ }
288
+ }
289
+ if (cleanup) {
290
+ await cleanup()
291
+ }
292
+ }
293
+
294
+ export type SseServerState = {
295
+ connected: boolean
296
+ finished: boolean
297
+ writeCount: number
298
+ closed: boolean
299
+ }
300
+
301
+ export type SseServer = {
302
+ baseUrl: string
303
+ getState: () => SseServerState
304
+ close: () => Promise<void>
305
+ }
306
+
307
+ export async function createSseServer(): Promise<SseServer> {
308
+ let sseResponse: http.ServerResponse | null = null
309
+ let sseFinished = false
310
+ let sseClosed = false
311
+ let sseWriteCount = 0
312
+ let sseInterval: NodeJS.Timeout | null = null
313
+ const openResponses: Set<http.ServerResponse> = new Set()
314
+ const openSockets: Set<net.Socket> = new Set()
315
+
316
+ const server = http.createServer((req, res) => {
317
+ if (req.url === '/') {
318
+ res.writeHead(200, { 'Content-Type': 'text/html' })
319
+ res.end(`<!doctype html>
320
+ <html>
321
+ <head>
322
+ <meta charset="utf-8" />
323
+ <title>SSE Test</title>
324
+ </head>
325
+ <body>
326
+ <script>
327
+ window.__sseMessages = [];
328
+ window.__sseOpen = false;
329
+ window.__sseError = null;
330
+ window.startSse = function () {
331
+ const source = new EventSource('/sse');
332
+ window.__sseSource = source;
333
+ source.onopen = function () {
334
+ window.__sseOpen = true;
335
+ };
336
+ source.onmessage = function (event) {
337
+ window.__sseMessages.push(event.data);
338
+ };
339
+ source.onerror = function () {
340
+ window.__sseError = 'SSE error';
341
+ };
342
+ return true;
343
+ };
344
+ window.stopSse = function () {
345
+ if (window.__sseSource) {
346
+ window.__sseSource.close();
347
+ }
348
+ };
349
+ </script>
350
+ </body>
351
+ </html>`)
352
+ return
353
+ }
354
+
355
+ if (req.url === '/sse') {
356
+ res.writeHead(200, {
357
+ 'Content-Type': 'text/event-stream',
358
+ 'Cache-Control': 'no-cache, no-transform',
359
+ Connection: 'keep-alive',
360
+ })
361
+ res.write('retry: 1000\n\n')
362
+ res.write('data: hello\n\n')
363
+ sseResponse = res
364
+ sseWriteCount += 1
365
+ openResponses.add(res)
366
+
367
+ res.on('finish', () => {
368
+ sseFinished = true
369
+ })
370
+ res.on('close', () => {
371
+ sseClosed = true
372
+ openResponses.delete(res)
373
+ if (sseInterval) {
374
+ clearInterval(sseInterval)
375
+ sseInterval = null
376
+ }
377
+ })
378
+
379
+ sseInterval = setInterval(() => {
380
+ res.write('data: ping\n\n')
381
+ sseWriteCount += 1
382
+ }, 200)
383
+ return
384
+ }
385
+
386
+ res.writeHead(404)
387
+ res.end('Not found')
388
+ })
389
+
390
+ server.on('connection', (socket) => {
391
+ openSockets.add(socket)
392
+ socket.on('close', () => {
393
+ openSockets.delete(socket)
394
+ })
395
+ })
396
+
397
+ await new Promise<void>((resolve) => {
398
+ server.listen(0, '127.0.0.1', () => {
399
+ resolve()
400
+ })
401
+ })
402
+
403
+ const address = server.address()
404
+ if (!address || typeof address === 'string') {
405
+ throw new Error('Failed to bind SSE server')
406
+ }
407
+
408
+ return {
409
+ baseUrl: `http://127.0.0.1:${address.port}`,
410
+ getState: () => ({
411
+ connected: sseResponse !== null,
412
+ finished: sseFinished,
413
+ closed: sseClosed,
414
+ writeCount: sseWriteCount,
415
+ }),
416
+ close: async () => {
417
+ for (const response of openResponses) {
418
+ response.destroy()
419
+ }
420
+ for (const socket of openSockets) {
421
+ socket.destroy()
422
+ }
423
+ if (sseInterval) {
424
+ clearInterval(sseInterval)
425
+ sseInterval = null
426
+ }
427
+ await new Promise<void>((resolve, reject) => {
428
+ server.close((error) => {
429
+ if (error) {
430
+ reject(error)
431
+ return
432
+ }
433
+ resolve()
434
+ })
435
+ })
436
+ },
437
+ }
438
+ }
439
+
440
+ export async function withTimeout<T>({
441
+ promise,
442
+ timeoutMs,
443
+ errorMessage,
444
+ }: {
445
+ promise: Promise<T>
446
+ timeoutMs: number
447
+ errorMessage: string
448
+ }): Promise<T> {
449
+ return await new Promise<T>((resolve, reject) => {
450
+ const timeoutId = setTimeout(() => {
451
+ reject(new Error(errorMessage))
452
+ }, timeoutMs)
453
+
454
+ promise
455
+ .then((value) => {
456
+ clearTimeout(timeoutId)
457
+ resolve(value)
458
+ })
459
+ .catch((error) => {
460
+ clearTimeout(timeoutId)
461
+ reject(error)
462
+ })
463
+ })
464
+ }
465
+
466
+ /** Tagged template for inline JS code strings used in MCP execute calls */
467
+ export function js(strings: TemplateStringsArray, ...values: unknown[]): string {
468
+ return strings.reduce((result, str, i) => result + str + (values[i] || ''), '')
469
+ }
470
+
471
+ export function tryJsonParse(str: string) {
472
+ try {
473
+ return JSON.parse(str)
474
+ } catch {
475
+ return str
476
+ }
477
+ }
478
+
479
+ /**
480
+ * Safely close a browser connected via connectOverCDP.
481
+ *
482
+ * Playwright's CRConnection uses async message handling (messageWrap) that can cause
483
+ * a race condition where _onClose() runs before all pending _onMessage() handlers complete.
484
+ * This results in "Assertion error" from crConnection.js when a CDP response arrives
485
+ * after callbacks were cleared by dispose().
486
+ *
487
+ * This helper waits for the message queue to drain before closing, avoiding the race.
488
+ *
489
+ * @param browser - Browser instance from chromium.connectOverCDP()
490
+ * @param drainDelayMs - Time to wait for pending messages to be processed (default: 50ms)
491
+ */
492
+ export async function safeCloseCDPBrowser(
493
+ browser: Awaited<ReturnType<typeof import('@xmorse/playwright-core').chromium.connectOverCDP>>,
494
+ drainDelayMs = 50,
495
+ ): Promise<void> {
496
+ // Wait for any queued message handlers to run
497
+ // This gives Playwright's messageWrap time to process pending CDP responses
498
+ await new Promise((r) => setTimeout(r, drainDelayMs))
499
+ await browser.close()
500
+ }
501
+
502
+ export type SimpleServer = {
503
+ baseUrl: string
504
+ close: () => Promise<void>
505
+ }
506
+
507
+ /** Minimal local HTTP server for tests that need cross-origin iframes or custom routes */
508
+ export async function createSimpleServer({ routes }: { routes: Record<string, string> }): Promise<SimpleServer> {
509
+ const openSockets: Set<net.Socket> = new Set()
510
+ const server = http.createServer((req, res) => {
511
+ const url = req.url || '/'
512
+ const body = routes[url]
513
+ if (!body) {
514
+ res.writeHead(404, { 'Content-Type': 'text/plain' })
515
+ res.end('not found')
516
+ return
517
+ }
518
+ res.writeHead(200, { 'Content-Type': 'text/html' })
519
+ res.end(body)
520
+ })
521
+
522
+ server.on('connection', (socket) => {
523
+ openSockets.add(socket)
524
+ socket.on('close', () => {
525
+ openSockets.delete(socket)
526
+ })
527
+ })
528
+
529
+ await new Promise<void>((resolve) => {
530
+ server.listen(0, '127.0.0.1', () => {
531
+ resolve()
532
+ })
533
+ })
534
+
535
+ const address = server.address()
536
+ if (!address || typeof address === 'string') {
537
+ await new Promise<void>((resolve, reject) => {
538
+ server.close((error) => {
539
+ if (error) {
540
+ reject(error)
541
+ return
542
+ }
543
+ resolve()
544
+ })
545
+ })
546
+ throw new Error('Failed to start test server')
547
+ }
548
+
549
+ return {
550
+ baseUrl: `http://127.0.0.1:${address.port}`,
551
+ close: async () => {
552
+ for (const socket of openSockets) {
553
+ socket.destroy()
554
+ }
555
+ await new Promise<void>((resolve, reject) => {
556
+ server.close((error) => {
557
+ if (error) {
558
+ reject(error)
559
+ return
560
+ }
561
+ resolve()
562
+ })
563
+ })
564
+ },
565
+ }
566
+ }
package/src/utils.ts ADDED
@@ -0,0 +1,78 @@
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
3
+ import { fileURLToPath } from 'node:url'
4
+ import './env-compat.js'
5
+ import { getTabwrightUserDataDir } from './product-paths.js'
6
+
7
+ // Tabwright extension IDs - used for validation and Chrome flag commands
8
+ export const EXTENSION_IDS = [
9
+ 'jfeammnjpkecdekppnclgkkffahnhfhe', // Production (Chrome Web Store)
10
+ 'pebbngnfojnignonigcnkdilknapkgid', // Dev extension (stable ID from manifest key)
11
+ ]
12
+
13
+ /**
14
+ * Parse a relay host string into HTTP and WebSocket base URLs.
15
+ * Supports both plain hostnames (appends port) and full URLs (uses as-is).
16
+ *
17
+ * Examples:
18
+ * "192.168.1.10" → http://192.168.1.10:19988, ws://192.168.1.10:19988
19
+ * "https://my-machine-tunnel.traforo.dev" → https://my-machine-tunnel.traforo.dev, wss://my-machine-tunnel.traforo.dev
20
+ */
21
+ export function parseRelayHost(host: string, port: number = 19988): { httpBaseUrl: string; wsBaseUrl: string } {
22
+ if (host.startsWith('https://') || host.startsWith('http://')) {
23
+ const url = new URL(host)
24
+ const httpBaseUrl = url.origin
25
+ const wsProtocol = url.protocol === 'https:' ? 'wss:' : 'ws:'
26
+ const wsBaseUrl = `${wsProtocol}//${url.host}`
27
+ return { httpBaseUrl, wsBaseUrl }
28
+ }
29
+ return {
30
+ httpBaseUrl: `http://${host}:${port}`,
31
+ wsBaseUrl: `ws://${host}:${port}`,
32
+ }
33
+ }
34
+
35
+ export function getCdpUrl({
36
+ port = 19988,
37
+ host = '127.0.0.1',
38
+ token,
39
+ extensionId,
40
+ }: {
41
+ port?: number
42
+ host?: string
43
+ token?: string
44
+ extensionId?: string | null
45
+ } = {}) {
46
+ const id = `${Math.random().toString(36).substring(2, 15)}_${Date.now()}`
47
+ const params = new URLSearchParams()
48
+ if (token) {
49
+ params.set('token', token)
50
+ }
51
+ if (extensionId) {
52
+ params.set('extensionId', extensionId)
53
+ }
54
+ const queryString = params.toString()
55
+ const suffix = queryString ? `?${queryString}` : ''
56
+ const { wsBaseUrl } = parseRelayHost(host, port)
57
+ return `${wsBaseUrl}/cdp/${id}${suffix}`
58
+ }
59
+
60
+ export function shouldAutoEnableTabwright(): boolean {
61
+ return (process.env.TABWRIGHT_AUTO_ENABLE || process.env.PLAYWRITER_AUTO_ENABLE)?.toLowerCase() !== 'false'
62
+ }
63
+
64
+ // Existing installations continue using their legacy data directory until users migrate it.
65
+ const LOG_BASE_DIR = getTabwrightUserDataDir()
66
+ export const LOG_FILE_PATH =
67
+ process.env.TABWRIGHT_LOG_FILE_PATH || process.env.PLAYWRITER_LOG_FILE_PATH || path.join(LOG_BASE_DIR, 'relay-server.log')
68
+ export const LOG_CDP_FILE_PATH =
69
+ process.env.TABWRIGHT_CDP_LOG_FILE_PATH ||
70
+ process.env.PLAYWRITER_CDP_LOG_FILE_PATH ||
71
+ path.join(path.dirname(LOG_FILE_PATH), 'cdp.jsonl')
72
+
73
+ const packageJsonPath = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'package.json')
74
+ export const VERSION = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8')).version as string
75
+
76
+ export function sleep(ms: number): Promise<void> {
77
+ return new Promise((resolve) => setTimeout(resolve, ms))
78
+ }