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,1375 @@
1
+ import { createMCPClient } from './mcp-client.js'
2
+ import { describe, it, expect, beforeAll, afterAll } from 'vitest'
3
+ import { chromium } from '@xmorse/playwright-core'
4
+ import { getCdpUrl } from './utils.js'
5
+ import { getCDPSessionForPage } from './cdp-session.js'
6
+ import { Debugger } from './debugger.js'
7
+ import { Editor } from './editor.js'
8
+ import { PlaywrightExecutor } from './executor.js'
9
+ import {
10
+ setupTestContext,
11
+ cleanupTestContext,
12
+ getExtensionServiceWorker,
13
+ createSseServer,
14
+ safeCloseCDPBrowser,
15
+ type TestContext,
16
+ withTimeout,
17
+ js,
18
+ } from './test-utils.js'
19
+ import './test-declarations.js'
20
+
21
+ const TEST_PORT = 19993
22
+
23
+ // --- CDP Session Tests ---
24
+
25
+ describe('CDP Session Tests', () => {
26
+ let testCtx: TestContext | null = null
27
+
28
+ beforeAll(async () => {
29
+ testCtx = await setupTestContext({ port: TEST_PORT, tempDirPrefix: 'pw-cdp-test-', toggleExtension: true })
30
+
31
+ const serviceWorker = await getExtensionServiceWorker(testCtx.browserContext)
32
+ await serviceWorker.evaluate(async () => {
33
+ await globalThis.disconnectEverything()
34
+ })
35
+ await new Promise((r) => setTimeout(r, 100))
36
+ }, 600000)
37
+
38
+ afterAll(async () => {
39
+ await cleanupTestContext(testCtx)
40
+ testCtx = null
41
+ })
42
+
43
+ const getBrowserContext = () => {
44
+ if (!testCtx?.browserContext) throw new Error('Browser not initialized')
45
+ return testCtx.browserContext
46
+ }
47
+
48
+ it('should use Debugger class to set breakpoints and inspect variables', async () => {
49
+ const browserContext = getBrowserContext()
50
+ const serviceWorker = await getExtensionServiceWorker(browserContext)
51
+
52
+ const page = await browserContext.newPage()
53
+ const testUrl = 'https://example.com/?test=debugger-variables'
54
+ await page.goto(testUrl)
55
+ await page.bringToFront()
56
+
57
+ await serviceWorker.evaluate(async () => {
58
+ await globalThis.toggleExtensionForActiveTab()
59
+ })
60
+ await new Promise((r) => setTimeout(r, 100))
61
+
62
+ const wsUrl = getCdpUrl({ port: TEST_PORT })
63
+ const cdpSession = await getCDPSessionForPage({ page })
64
+ const dbg = new Debugger({ cdp: cdpSession })
65
+
66
+ await dbg.enable()
67
+
68
+ expect(dbg.isPaused()).toBe(false)
69
+
70
+ const pausedPromise = new Promise<void>((resolve) => {
71
+ cdpSession.on('Debugger.paused', () => {
72
+ resolve()
73
+ })
74
+ })
75
+
76
+ const evalPromise = cdpSession.send('Runtime.evaluate', {
77
+ expression: `(function testFunction() {
78
+ const localVar = 'hello';
79
+ const numberVar = 42;
80
+ debugger;
81
+ return localVar + numberVar;
82
+ })()`,
83
+ })
84
+
85
+ await Promise.race([
86
+ pausedPromise,
87
+ new Promise<never>((_, reject) => setTimeout(() => reject(new Error('Debugger.paused timeout')), 5000)),
88
+ ])
89
+
90
+ expect(dbg.isPaused()).toBe(true)
91
+
92
+ const location = await dbg.getLocation()
93
+ expect(location.callstack[0].functionName).toBe('testFunction')
94
+ expect(location.sourceContext).toContain('debugger')
95
+
96
+ const vars = await dbg.inspectLocalVariables()
97
+ expect(vars).toMatchInlineSnapshot(`
98
+ {
99
+ "localVar": "hello",
100
+ "numberVar": 42,
101
+ }
102
+ `)
103
+
104
+ const evalResult = await dbg.evaluate({ expression: 'localVar + " world"' })
105
+ expect(evalResult.value).toBe('hello world')
106
+
107
+ await dbg.resume()
108
+ await new Promise((r) => setTimeout(r, 100))
109
+ expect(dbg.isPaused()).toBe(false)
110
+ await evalPromise
111
+
112
+ await cdpSession.detach()
113
+ await page.close()
114
+ }, 60000)
115
+
116
+ it('should reuse cached CDP session and close on page close', async () => {
117
+ const browserContext = getBrowserContext()
118
+ const serviceWorker = await getExtensionServiceWorker(browserContext)
119
+
120
+ const page = await browserContext.newPage()
121
+ const testUrl = 'https://example.com/?test=debugger-step'
122
+ await page.goto(testUrl)
123
+ await page.bringToFront()
124
+
125
+ await serviceWorker.evaluate(async () => {
126
+ await globalThis.toggleExtensionForActiveTab()
127
+ })
128
+ await new Promise((r) => setTimeout(r, 100))
129
+
130
+ const executor = new PlaywrightExecutor({
131
+ cdpConfig: { port: TEST_PORT },
132
+ logger: {
133
+ log: () => {},
134
+ error: () => {},
135
+ },
136
+ })
137
+
138
+ const result = await executor.execute(js`
139
+ const sessionA = await getCDPSession({ page })
140
+ const sessionB = await getCDPSession({ page })
141
+ await sessionA.send('Runtime.evaluate', { expression: '1 + 1', returnByValue: true })
142
+ const evalResult = await sessionB.send('Runtime.evaluate', { expression: '2 + 2', returnByValue: true })
143
+ return evalResult.result.value
144
+ `)
145
+
146
+ expect(result.isError).toBe(false)
147
+ expect(result.text).toContain('[return value] 4')
148
+
149
+ await page.close()
150
+ }, 60000)
151
+
152
+ it('should list scripts with Debugger class', async () => {
153
+ const browserContext = getBrowserContext()
154
+ const serviceWorker = await getExtensionServiceWorker(browserContext)
155
+
156
+ // Use setContent with external script URLs so Debugger.listScripts returns them
157
+ const page = await browserContext.newPage()
158
+ await page.setContent(`
159
+ <html>
160
+ <head>
161
+ <script src="data:text/javascript,function hello() { return 1; }"></script>
162
+ <script src="data:text/javascript,function world() { return 2; }"></script>
163
+ </head>
164
+ <body><h1>Script test</h1></body>
165
+ </html>
166
+ `)
167
+ await page.bringToFront()
168
+
169
+ await serviceWorker.evaluate(async () => {
170
+ await globalThis.toggleExtensionForActiveTab()
171
+ })
172
+ await new Promise((r) => setTimeout(r, 100))
173
+
174
+ const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
175
+ const cdpPage = browser
176
+ .contexts()[0]
177
+ .pages()
178
+ .find((p) => p.url().startsWith('about:'))
179
+ expect(cdpPage).toBeDefined()
180
+
181
+ const wsUrl = getCdpUrl({ port: TEST_PORT })
182
+ const cdpSession = await getCDPSessionForPage({ page: cdpPage! })
183
+ const dbg = new Debugger({ cdp: cdpSession })
184
+
185
+ await dbg.enable()
186
+
187
+ const scripts = await dbg.listScripts()
188
+ expect(scripts.length).toBeGreaterThan(0)
189
+ expect(scripts[0]).toHaveProperty('scriptId')
190
+ expect(scripts[0]).toHaveProperty('url')
191
+
192
+ await cdpSession.detach()
193
+ await browser.close()
194
+ await page.close()
195
+ }, 60000)
196
+
197
+ it('should manage breakpoints with Debugger class', async () => {
198
+ const browserContext = getBrowserContext()
199
+ const serviceWorker = await getExtensionServiceWorker(browserContext)
200
+
201
+ const page = await browserContext.newPage()
202
+ await page.setContent(`
203
+ <html>
204
+ <head>
205
+ <script src="data:text/javascript,function testFunc() { return 42; }"></script>
206
+ </head>
207
+ <body></body>
208
+ </html>
209
+ `)
210
+ await page.bringToFront()
211
+
212
+ await serviceWorker.evaluate(async () => {
213
+ await globalThis.toggleExtensionForActiveTab()
214
+ })
215
+ await new Promise((r) => setTimeout(r, 100))
216
+
217
+ const wsUrl = getCdpUrl({ port: TEST_PORT })
218
+ const cdpSession = await getCDPSessionForPage({ page })
219
+ const dbg = new Debugger({ cdp: cdpSession })
220
+
221
+ await dbg.enable()
222
+
223
+ expect(dbg.listBreakpoints()).toHaveLength(0)
224
+
225
+ const bpId = await dbg.setBreakpoint({ file: 'https://example.com/test.js', line: 1 })
226
+ expect(typeof bpId).toBe('string')
227
+ expect(dbg.listBreakpoints()).toHaveLength(1)
228
+ expect(dbg.listBreakpoints()[0]).toMatchObject({
229
+ id: bpId,
230
+ file: 'https://example.com/test.js',
231
+ line: 1,
232
+ })
233
+
234
+ await dbg.deleteBreakpoint({ breakpointId: bpId })
235
+ expect(dbg.listBreakpoints()).toHaveLength(0)
236
+
237
+ await cdpSession.detach()
238
+ await page.close()
239
+ }, 60000)
240
+
241
+ it('should step through code with Debugger class', async () => {
242
+ const browserContext = getBrowserContext()
243
+ const serviceWorker = await getExtensionServiceWorker(browserContext)
244
+
245
+ const page = await browserContext.newPage()
246
+ await page.goto('https://example.com/')
247
+ await page.bringToFront()
248
+
249
+ await serviceWorker.evaluate(async () => {
250
+ await globalThis.toggleExtensionForActiveTab()
251
+ })
252
+ await new Promise((r) => setTimeout(r, 100))
253
+
254
+ const wsUrl = getCdpUrl({ port: TEST_PORT })
255
+ const cdpSession = await getCDPSessionForPage({ page })
256
+ const dbg = new Debugger({ cdp: cdpSession })
257
+
258
+ await dbg.enable()
259
+
260
+ const pausedPromise = new Promise<void>((resolve) => {
261
+ cdpSession.on('Debugger.paused', () => resolve())
262
+ })
263
+
264
+ const evalPromise = cdpSession.send('Runtime.evaluate', {
265
+ expression: `(function outer() {
266
+ function inner() {
267
+ const x = 1;
268
+ debugger;
269
+ const y = 2;
270
+ return x + y;
271
+ }
272
+ const result = inner();
273
+ return result;
274
+ })()`,
275
+ })
276
+
277
+ await pausedPromise
278
+ expect(dbg.isPaused()).toBe(true)
279
+
280
+ const location1 = await dbg.getLocation()
281
+ expect(location1.callstack.length).toBeGreaterThanOrEqual(2)
282
+ expect(location1.callstack[0].functionName).toBe('inner')
283
+ expect(location1.callstack[1].functionName).toBe('outer')
284
+
285
+ const stepOverPromise = new Promise<void>((resolve) => {
286
+ cdpSession.on('Debugger.paused', () => resolve())
287
+ })
288
+ await dbg.stepOver()
289
+ await stepOverPromise
290
+
291
+ const location2 = await dbg.getLocation()
292
+ expect(location2.lineNumber).toBeGreaterThan(location1.lineNumber)
293
+
294
+ const stepOutPromise = new Promise<void>((resolve) => {
295
+ cdpSession.on('Debugger.paused', () => resolve())
296
+ })
297
+ await dbg.stepOut()
298
+ await stepOutPromise
299
+
300
+ const location3 = await dbg.getLocation()
301
+ expect(location3.callstack[0].functionName).toBe('outer')
302
+
303
+ await dbg.resume()
304
+ await evalPromise
305
+
306
+ await cdpSession.detach()
307
+ await page.close()
308
+ }, 60000)
309
+
310
+ it('should profile JavaScript execution using CDP Profiler', async () => {
311
+ const browserContext = getBrowserContext()
312
+ const serviceWorker = await getExtensionServiceWorker(browserContext)
313
+
314
+ const page = await browserContext.newPage()
315
+ const testUrl = 'https://example.com/?test=debugger-profiler'
316
+ await page.goto(testUrl)
317
+ await page.bringToFront()
318
+
319
+ await serviceWorker.evaluate(async () => {
320
+ await globalThis.toggleExtensionForActiveTab()
321
+ })
322
+ await new Promise((r) => setTimeout(r, 100))
323
+
324
+ const wsUrl = getCdpUrl({ port: TEST_PORT })
325
+ const cdpSession = await getCDPSessionForPage({ page })
326
+ await cdpSession.send('Profiler.enable')
327
+ await cdpSession.send('Profiler.start')
328
+
329
+ await cdpSession.send('Runtime.evaluate', {
330
+ expression: `(() => {
331
+ function fibonacci(n) {
332
+ if (n <= 1) return n
333
+ return fibonacci(n - 1) + fibonacci(n - 2)
334
+ }
335
+ for (let i = 0; i < 5; i++) {
336
+ fibonacci(20)
337
+ }
338
+ for (let i = 0; i < 1000; i++) {
339
+ document.querySelectorAll('*')
340
+ }
341
+ })()`,
342
+ })
343
+
344
+ const stopResult = await cdpSession.send('Profiler.stop')
345
+ const profile = stopResult.profile
346
+
347
+ const functionNames = profile.nodes
348
+ .map((n) => n.callFrame.functionName)
349
+ .filter((name) => name && name.length > 0)
350
+ .slice(0, 10)
351
+
352
+ expect(profile.nodes.length).toBeGreaterThan(0)
353
+ expect(profile.endTime - profile.startTime).toBeGreaterThan(0)
354
+ expect(functionNames.every((name) => typeof name === 'string')).toBe(true)
355
+
356
+ await cdpSession.send('Profiler.disable')
357
+ await cdpSession.detach()
358
+ await page.close()
359
+ }, 60000)
360
+
361
+ it('should update Target.getTargets URL after page navigation', async () => {
362
+ const browserContext = getBrowserContext()
363
+ const serviceWorker = await getExtensionServiceWorker(browserContext)
364
+
365
+ await serviceWorker.evaluate(async () => {
366
+ await globalThis.disconnectEverything()
367
+ })
368
+
369
+ const page = await browserContext.newPage()
370
+ await page.goto('https://example.com/')
371
+ await page.bringToFront()
372
+
373
+ await serviceWorker.evaluate(async () => {
374
+ await globalThis.toggleExtensionForActiveTab()
375
+ })
376
+ await new Promise((r) => setTimeout(r, 100))
377
+
378
+ const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
379
+ const cdpPage = browser
380
+ .contexts()[0]
381
+ .pages()
382
+ .find((p) => p.url().includes('example.com'))
383
+ expect(cdpPage).toBeDefined()
384
+
385
+ const wsUrl = getCdpUrl({ port: TEST_PORT })
386
+ const cdpSession = await getCDPSessionForPage({ page: cdpPage! })
387
+
388
+ const initialTargets = await cdpSession.send('Target.getTargets')
389
+ const initialPageTarget = initialTargets.targetInfos.find((t) => t.type === 'page' && t.url.includes('example.com'))
390
+ expect(initialPageTarget?.url).toBe('https://example.com/')
391
+
392
+ await cdpPage!.goto('https://example.org/', { waitUntil: 'domcontentloaded' })
393
+ await new Promise((r) => setTimeout(r, 100))
394
+
395
+ const afterNavTargets = await cdpSession.send('Target.getTargets')
396
+ const allPageTargets = afterNavTargets.targetInfos.filter((t) => t.type === 'page')
397
+
398
+ const aboutBlankTargets = allPageTargets.filter((t) => t.url === 'about:blank')
399
+ expect(aboutBlankTargets).toHaveLength(0)
400
+
401
+ const exampleComTargets = allPageTargets.filter((t) => t.url.includes('example.com'))
402
+ expect(exampleComTargets).toHaveLength(0)
403
+
404
+ const exampleOrgTargets = allPageTargets.filter((t) => t.url.includes('example.org'))
405
+ expect(exampleOrgTargets).toHaveLength(1)
406
+
407
+ await cdpSession.detach()
408
+ await browser.close()
409
+ await page.close()
410
+ }, 60000)
411
+
412
+ it('should return correct targets for multiple pages via Target.getTargets', async () => {
413
+ const browserContext = getBrowserContext()
414
+ const serviceWorker = await getExtensionServiceWorker(browserContext)
415
+
416
+ await serviceWorker.evaluate(async () => {
417
+ await globalThis.disconnectEverything()
418
+ })
419
+
420
+ const page1 = await browserContext.newPage()
421
+ await page1.goto('https://example.com/')
422
+ await page1.bringToFront()
423
+ await serviceWorker.evaluate(async () => {
424
+ await globalThis.toggleExtensionForActiveTab()
425
+ })
426
+
427
+ const page2 = await browserContext.newPage()
428
+ await page2.goto('https://example.org/')
429
+ await page2.bringToFront()
430
+ await serviceWorker.evaluate(async () => {
431
+ await globalThis.toggleExtensionForActiveTab()
432
+ })
433
+
434
+ await new Promise((r) => setTimeout(r, 100))
435
+
436
+ const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
437
+ const cdpPage = browser
438
+ .contexts()[0]
439
+ .pages()
440
+ .find((p) => p.url().includes('example.com'))
441
+ expect(cdpPage).toBeDefined()
442
+
443
+ const wsUrl = getCdpUrl({ port: TEST_PORT })
444
+ const cdpSession = await getCDPSessionForPage({ page: cdpPage! })
445
+
446
+ const { targetInfos } = await cdpSession.send('Target.getTargets')
447
+ const allPageTargets = targetInfos.filter((t) => t.type === 'page')
448
+
449
+ const aboutBlankTargets = allPageTargets.filter((t) => t.url === 'about:blank')
450
+ expect(aboutBlankTargets).toHaveLength(0)
451
+
452
+ const pageTargets = allPageTargets
453
+ .map((t) => ({ type: t.type, url: t.url }))
454
+ .sort((a, b) => a.url.localeCompare(b.url))
455
+
456
+ expect(pageTargets).toMatchInlineSnapshot(`
457
+ [
458
+ {
459
+ "type": "page",
460
+ "url": "https://example.com/",
461
+ },
462
+ {
463
+ "type": "page",
464
+ "url": "https://example.org/",
465
+ },
466
+ ]
467
+ `)
468
+
469
+ await cdpSession.detach()
470
+ await browser.close()
471
+ await page1.close()
472
+ await page2.close()
473
+ }, 60000)
474
+
475
+ it('should create CDP session for page after navigation', async () => {
476
+ const browserContext = getBrowserContext()
477
+ const serviceWorker = await getExtensionServiceWorker(browserContext)
478
+
479
+ const page = await browserContext.newPage()
480
+ await page.goto('https://example.com/')
481
+ await page.bringToFront()
482
+
483
+ await serviceWorker.evaluate(async () => {
484
+ await globalThis.toggleExtensionForActiveTab()
485
+ })
486
+ await new Promise((r) => setTimeout(r, 100))
487
+
488
+ await page.goto('https://example.org/', { waitUntil: 'domcontentloaded' })
489
+ await new Promise((r) => setTimeout(r, 100))
490
+
491
+ const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
492
+ const cdpPage = browser
493
+ .contexts()[0]
494
+ .pages()
495
+ .find((p) => p.url().includes('example.org'))
496
+ expect(cdpPage).toBeDefined()
497
+
498
+ const wsUrl = getCdpUrl({ port: TEST_PORT })
499
+ const cdpSession = await getCDPSessionForPage({ page: cdpPage! })
500
+
501
+ const evalResult = await cdpSession.send('Runtime.evaluate', {
502
+ expression: 'document.title',
503
+ returnByValue: true,
504
+ })
505
+ expect(evalResult.result.value).toContain('Example Domain')
506
+
507
+ await cdpSession.detach()
508
+ await browser.close()
509
+ await page.close()
510
+ }, 60000)
511
+
512
+ it('should maintain CDP session functionality after page URL change', async () => {
513
+ const browserContext = getBrowserContext()
514
+ const serviceWorker = await getExtensionServiceWorker(browserContext)
515
+
516
+ const page = await browserContext.newPage()
517
+ const initialUrl = 'https://example.com/'
518
+ await page.goto(initialUrl)
519
+ await page.bringToFront()
520
+
521
+ await serviceWorker.evaluate(async () => {
522
+ await globalThis.toggleExtensionForActiveTab()
523
+ })
524
+ await new Promise((r) => setTimeout(r, 100))
525
+
526
+ const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
527
+ const cdpPage = browser
528
+ .contexts()[0]
529
+ .pages()
530
+ .find((p) => p.url().includes('example.com'))
531
+ expect(cdpPage).toBeDefined()
532
+
533
+ const wsUrl = getCdpUrl({ port: TEST_PORT })
534
+ const cdpSession = await getCDPSessionForPage({ page: cdpPage! })
535
+
536
+ const initialEvalResult = await cdpSession.send('Runtime.evaluate', {
537
+ expression: 'document.title',
538
+ returnByValue: true,
539
+ })
540
+ expect(initialEvalResult.result.value).toBe('Example Domain')
541
+
542
+ const newUrl = 'https://example.org/'
543
+ await cdpPage!.goto(newUrl, { waitUntil: 'domcontentloaded' })
544
+
545
+ expect(cdpPage!.url()).toBe(newUrl)
546
+
547
+ const layoutMetrics = await cdpSession.send('Page.getLayoutMetrics')
548
+ expect(layoutMetrics.cssVisualViewport).toBeDefined()
549
+ expect(layoutMetrics.cssVisualViewport.clientWidth).toBeGreaterThan(0)
550
+
551
+ const afterNavEvalResult = await cdpSession.send('Runtime.evaluate', {
552
+ expression: 'document.title',
553
+ returnByValue: true,
554
+ })
555
+ expect(afterNavEvalResult.result.value).toContain('Example Domain')
556
+
557
+ const locationResult = await cdpSession.send('Runtime.evaluate', {
558
+ expression: 'window.location.href',
559
+ returnByValue: true,
560
+ })
561
+ expect(locationResult.result.value).toBe(newUrl)
562
+
563
+ await cdpSession.detach()
564
+ await browser.close()
565
+ await page.close()
566
+ }, 60000)
567
+
568
+ it('should pause on all exceptions with setPauseOnExceptions', async () => {
569
+ const browserContext = getBrowserContext()
570
+ const serviceWorker = await getExtensionServiceWorker(browserContext)
571
+
572
+ const page = await browserContext.newPage()
573
+ await page.goto('https://example.com/')
574
+ await page.bringToFront()
575
+
576
+ await serviceWorker.evaluate(async () => {
577
+ await globalThis.toggleExtensionForActiveTab()
578
+ })
579
+ await new Promise((r) => setTimeout(r, 100))
580
+
581
+ const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
582
+ const cdpPage = browser
583
+ .contexts()[0]
584
+ .pages()
585
+ .find((p) => p.url().includes('example.com'))
586
+ expect(cdpPage).toBeDefined()
587
+
588
+ const wsUrl = getCdpUrl({ port: TEST_PORT })
589
+ const cdpSession = await getCDPSessionForPage({ page: cdpPage! })
590
+ const dbg = new Debugger({ cdp: cdpSession })
591
+
592
+ await dbg.enable()
593
+ await dbg.setPauseOnExceptions({ state: 'all' })
594
+
595
+ const pausedPromise = new Promise<void>((resolve) => {
596
+ cdpSession.on('Debugger.paused', () => resolve())
597
+ })
598
+
599
+ const evalPromise = cdpSession.send('Runtime.evaluate', {
600
+ expression: `(function() {
601
+ try {
602
+ throw new Error('Caught test error');
603
+ } catch (e) {
604
+ // caught but should still pause with state 'all'
605
+ }
606
+ })()`,
607
+ })
608
+
609
+ await Promise.race([
610
+ pausedPromise,
611
+ new Promise<never>((_, reject) => setTimeout(() => reject(new Error('Debugger.paused timeout')), 5000)),
612
+ ])
613
+
614
+ expect(dbg.isPaused()).toBe(true)
615
+
616
+ const location = await dbg.getLocation()
617
+ expect(location.sourceContext).toContain('throw')
618
+
619
+ await dbg.resume()
620
+ await evalPromise
621
+
622
+ await dbg.setPauseOnExceptions({ state: 'none' })
623
+
624
+ await cdpSession.detach()
625
+ await browser.close()
626
+ await page.close()
627
+ }, 60000)
628
+
629
+ it('should inspect local and global variables with inline snapshots', async () => {
630
+ const browserContext = getBrowserContext()
631
+ const serviceWorker = await getExtensionServiceWorker(browserContext)
632
+
633
+ const page = await browserContext.newPage()
634
+ await page.setContent(`
635
+ <html>
636
+ <head>
637
+ <script>
638
+ const GLOBAL_CONFIG = 'production';
639
+ function runTest() {
640
+ const userName = 'Alice';
641
+ const userAge = 25;
642
+ const settings = { theme: 'dark', lang: 'en' };
643
+ const scores = [10, 20, 30];
644
+ debugger;
645
+ return userName;
646
+ }
647
+ </script>
648
+ </head>
649
+ <body>
650
+ <button onclick="runTest()">Run</button>
651
+ </body>
652
+ </html>
653
+ `)
654
+ await page.bringToFront()
655
+
656
+ await serviceWorker.evaluate(async () => {
657
+ await globalThis.toggleExtensionForActiveTab()
658
+ })
659
+ await new Promise((r) => setTimeout(r, 100))
660
+
661
+ const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
662
+ let cdpPage
663
+ for (const p of browser.contexts()[0].pages()) {
664
+ const html = await p.content()
665
+ if (html.includes('runTest')) {
666
+ cdpPage = p
667
+ break
668
+ }
669
+ }
670
+ expect(cdpPage).toBeDefined()
671
+
672
+ const wsUrl = getCdpUrl({ port: TEST_PORT })
673
+ const cdpSession = await getCDPSessionForPage({ page: cdpPage! })
674
+ const dbg = new Debugger({ cdp: cdpSession })
675
+
676
+ await dbg.enable()
677
+
678
+ const globalVars = await dbg.inspectGlobalVariables()
679
+ expect(globalVars).toMatchInlineSnapshot(`
680
+ [
681
+ "GLOBAL_CONFIG",
682
+ ]
683
+ `)
684
+
685
+ const pausedPromise = new Promise<void>((resolve) => {
686
+ cdpSession.on('Debugger.paused', () => resolve())
687
+ })
688
+
689
+ // Don't await - we want it to pause at breakpoint
690
+ const evalPromise = cdpPage!.evaluate('runTest()').catch(() => {
691
+ // Ignore errors from evaluate when browser closes
692
+ })
693
+
694
+ await pausedPromise
695
+ expect(dbg.isPaused()).toBe(true)
696
+
697
+ const localVars = await dbg.inspectLocalVariables()
698
+ expect(localVars).toMatchInlineSnapshot(`
699
+ {
700
+ "GLOBAL_CONFIG": "production",
701
+ "scores": "[array]",
702
+ "settings": "[object]",
703
+ "userAge": 25,
704
+ "userName": "Alice",
705
+ }
706
+ `)
707
+
708
+ await dbg.resume()
709
+ // Wait for evaluate to complete after resume
710
+ await evalPromise
711
+
712
+ await cdpSession.detach()
713
+ await browser.close()
714
+ await page.close()
715
+ }, 60000)
716
+
717
+ it('should click at correct coordinates on high-DPI simulation', async () => {
718
+ const browserContext = getBrowserContext()
719
+ const serviceWorker = await getExtensionServiceWorker(browserContext)
720
+
721
+ const page = await browserContext.newPage()
722
+ await page.goto('https://example.com/')
723
+ await page.bringToFront()
724
+
725
+ await serviceWorker.evaluate(async () => {
726
+ await globalThis.toggleExtensionForActiveTab()
727
+ })
728
+ await new Promise((r) => setTimeout(r, 100))
729
+
730
+ const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
731
+ const cdpPage = browser
732
+ .contexts()[0]
733
+ .pages()
734
+ .find((p) => p.url().includes('example.com'))
735
+ expect(cdpPage).toBeDefined()
736
+
737
+ const h1Bounds = await cdpPage!.locator('h1').boundingBox()
738
+ expect(h1Bounds).toBeDefined()
739
+ console.log('H1 bounding box:', h1Bounds)
740
+
741
+ await cdpPage!.evaluate(() => {
742
+ ;(window as any).clickedAt = null
743
+ document.addEventListener('click', (e) => {
744
+ ;(window as any).clickedAt = { x: e.clientX, y: e.clientY }
745
+ })
746
+ })
747
+
748
+ await cdpPage!.locator('h1').click()
749
+
750
+ const clickedAt = await cdpPage!.evaluate(() => (window as any).clickedAt)
751
+ console.log('Clicked at:', clickedAt)
752
+
753
+ expect(clickedAt).toBeDefined()
754
+ expect(clickedAt.x).toBeGreaterThan(0)
755
+ expect(clickedAt.y).toBeGreaterThan(0)
756
+
757
+ await browser.close()
758
+ await page.close()
759
+ }, 60000)
760
+
761
+ it('should use Editor class to list, read, and edit scripts', async () => {
762
+ const browserContext = getBrowserContext()
763
+ const serviceWorker = await getExtensionServiceWorker(browserContext)
764
+
765
+ const page = await browserContext.newPage()
766
+ await page.goto('https://example.com/')
767
+ await page.bringToFront()
768
+
769
+ await serviceWorker.evaluate(async () => {
770
+ await globalThis.toggleExtensionForActiveTab()
771
+ })
772
+ await new Promise((r) => setTimeout(r, 100))
773
+
774
+ const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
775
+ const cdpPage = browser
776
+ .contexts()[0]
777
+ .pages()
778
+ .find((p) => p.url().includes('example.com'))
779
+ expect(cdpPage).toBeDefined()
780
+
781
+ const wsUrl = getCdpUrl({ port: TEST_PORT })
782
+ const cdpSession = await getCDPSessionForPage({ page: cdpPage! })
783
+ const editor = new Editor({ cdp: cdpSession })
784
+
785
+ await editor.enable()
786
+
787
+ await cdpPage!.addScriptTag({
788
+ content: `
789
+ function greetUser(name) {
790
+ console.log('Hello, ' + name);
791
+ return 'Hello, ' + name;
792
+ }
793
+ `,
794
+ })
795
+ await new Promise((r) => setTimeout(r, 100))
796
+ const scripts = await editor.list()
797
+ expect(scripts.length).toBeGreaterThan(0)
798
+
799
+ const matches = await editor.grep({ regex: /greetUser/ })
800
+ expect(matches.length).toBeGreaterThan(0)
801
+
802
+ const match = matches[0]
803
+ const { content, totalLines } = await editor.read({ url: match.url })
804
+ expect(content).toContain('greetUser')
805
+ expect(totalLines).toBeGreaterThan(0)
806
+
807
+ await editor.edit({
808
+ url: match.url,
809
+ oldString: "console.log('Hello, ' + name);",
810
+ newString: "console.log('Hello, ' + name); console.log('EDITOR_TEST_MARKER');",
811
+ })
812
+
813
+ const consoleLogs: string[] = []
814
+ cdpPage!.on('console', (msg) => {
815
+ consoleLogs.push(msg.text())
816
+ })
817
+
818
+ await cdpPage!.evaluate(() => {
819
+ ;(window as any).greetUser('World')
820
+ })
821
+ await new Promise((r) => setTimeout(r, 100))
822
+
823
+ expect(consoleLogs).toContain('Hello, World')
824
+ expect(consoleLogs).toContain('EDITOR_TEST_MARKER')
825
+
826
+ await cdpSession.detach()
827
+ await browser.close()
828
+ await page.close()
829
+ }, 60000)
830
+
831
+ it('editor can list, read, and edit CSS stylesheets', async () => {
832
+ const browserContext = getBrowserContext()
833
+ const serviceWorker = await getExtensionServiceWorker(browserContext)
834
+
835
+ const page = await browserContext.newPage()
836
+ await page.goto('https://example.com/')
837
+ await page.bringToFront()
838
+
839
+ await serviceWorker.evaluate(async () => {
840
+ await globalThis.toggleExtensionForActiveTab()
841
+ })
842
+ await new Promise((r) => setTimeout(r, 100))
843
+
844
+ const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
845
+ const cdpPage = browser
846
+ .contexts()[0]
847
+ .pages()
848
+ .find((p) => p.url().includes('example.com'))
849
+ expect(cdpPage).toBeDefined()
850
+
851
+ const wsUrl = getCdpUrl({ port: TEST_PORT })
852
+ const cdpSession = await getCDPSessionForPage({ page: cdpPage! })
853
+ const editor = new Editor({ cdp: cdpSession })
854
+
855
+ await editor.enable()
856
+
857
+ await cdpPage!.addStyleTag({
858
+ content: `
859
+ .editor-test-element {
860
+ color: rgb(255, 0, 0);
861
+ background-color: rgb(0, 0, 255);
862
+ }
863
+ `,
864
+ })
865
+ await new Promise((r) => setTimeout(r, 100))
866
+ const stylesheets = await editor.list({ pattern: /inline-css:/ })
867
+ expect(stylesheets.length).toBeGreaterThan(0)
868
+
869
+ const cssMatches = await editor.grep({ regex: /editor-test-element/, pattern: /inline-css:/ })
870
+ expect(cssMatches.length).toBeGreaterThan(0)
871
+
872
+ const cssMatch = cssMatches[0]
873
+ const { content, totalLines } = await editor.read({ url: cssMatch.url })
874
+ expect(content).toContain('editor-test-element')
875
+ expect(content).toContain('rgb(255, 0, 0)')
876
+ expect(totalLines).toBeGreaterThan(0)
877
+
878
+ await cdpPage!.evaluate(() => {
879
+ const el = document.createElement('div')
880
+ el.className = 'editor-test-element'
881
+ el.id = 'test-div'
882
+ el.textContent = 'Test'
883
+ document.body.appendChild(el)
884
+ })
885
+
886
+ const colorBefore = await cdpPage!.evaluate(() => {
887
+ const el = document.getElementById('test-div')!
888
+ return window.getComputedStyle(el).color
889
+ })
890
+ expect(colorBefore).toBe('rgb(255, 0, 0)')
891
+
892
+ await editor.edit({
893
+ url: cssMatch.url,
894
+ oldString: 'color: rgb(255, 0, 0);',
895
+ newString: 'color: rgb(0, 255, 0);',
896
+ })
897
+
898
+ const colorAfter = await cdpPage!.evaluate(() => {
899
+ const el = document.getElementById('test-div')!
900
+ return window.getComputedStyle(el).color
901
+ })
902
+ expect(colorAfter).toBe('rgb(0, 255, 0)')
903
+
904
+ await cdpSession.detach()
905
+ await browser.close()
906
+ await page.close()
907
+ }, 60000)
908
+
909
+ it('should inject bippy and find React fiber with getReactSource', async () => {
910
+ const browserContext = getBrowserContext()
911
+ const serviceWorker = await getExtensionServiceWorker(browserContext)
912
+
913
+ const page = await browserContext.newPage()
914
+ await page.setContent(`
915
+ <!DOCTYPE html>
916
+ <html>
917
+ <head>
918
+ <script src="https://unpkg.com/react@18/umd/react.development.js"></script>
919
+ <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
920
+ </head>
921
+ <body>
922
+ <div id="root"></div>
923
+ <script>
924
+ function SaveButton(props) {
925
+ return React.createElement('button', { id: 'react-btn' }, props.label);
926
+ }
927
+ function Panel() {
928
+ return React.createElement('section', null, React.createElement(SaveButton, {
929
+ label: 'Click me',
930
+ count: 3,
931
+ config: { variant: 'primary' },
932
+ onClick: () => {}
933
+ }));
934
+ }
935
+ function App() {
936
+ return React.createElement(Panel);
937
+ }
938
+ const root = ReactDOM.createRoot(document.getElementById('root'));
939
+ root.render(React.createElement(App));
940
+ </script>
941
+ <button id="plain-btn">Plain</button>
942
+ </body>
943
+ </html>
944
+ `)
945
+ await page.bringToFront()
946
+
947
+ await serviceWorker.evaluate(async () => {
948
+ await globalThis.toggleExtensionForActiveTab()
949
+ })
950
+ await new Promise((r) => setTimeout(r, 500))
951
+
952
+ const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
953
+ const pages = browser.contexts()[0].pages()
954
+ const cdpPage = pages.find((p) => p.url().startsWith('about:'))
955
+ expect(cdpPage).toBeDefined()
956
+
957
+ const btn = cdpPage!.locator('#react-btn')
958
+ const btnCount = await btn.count()
959
+ expect(btnCount).toBe(1)
960
+
961
+ const hasBippyBefore = await cdpPage!.evaluate(() => !!globalThis.__bippy)
962
+ expect(hasBippyBefore).toBe(false)
963
+
964
+ const wsUrl = getCdpUrl({ port: TEST_PORT })
965
+ const cdpSession = await getCDPSessionForPage({ page: cdpPage! })
966
+
967
+ const { getReactSource, getReactComponentInfo } = await import('./react-source.js')
968
+ const source = await getReactSource({ locator: btn, cdp: cdpSession })
969
+ const info = await getReactComponentInfo({ locator: btn, cdp: cdpSession })
970
+ const plainInfo = await getReactComponentInfo({ locator: cdpPage!.locator('#plain-btn'), cdp: cdpSession })
971
+
972
+ const hasBippyAfter = await cdpPage!.evaluate(() => !!globalThis.__bippy)
973
+ expect(hasBippyAfter).toBe(true)
974
+
975
+ const hasFiber = await btn.evaluate((el) => {
976
+ const bippy = globalThis.__bippy
977
+ if (!bippy) return false
978
+ const fiber = bippy.getFiberFromHostInstance(el)
979
+ return !!fiber
980
+ })
981
+ expect(hasFiber).toBe(true)
982
+
983
+ const componentName = await btn.evaluate((el) => {
984
+ const bippy = globalThis.__bippy
985
+ if (!bippy) return null
986
+ const fiber = bippy.getFiberFromHostInstance(el)
987
+ let current = fiber
988
+ while (current) {
989
+ if (bippy.isCompositeFiber(current)) {
990
+ return bippy.getDisplayName(current.type)
991
+ }
992
+ current = current.return ?? null
993
+ }
994
+ return null
995
+ })
996
+ expect(componentName).toBe('SaveButton')
997
+ expect(plainInfo).toBe(null)
998
+ expect(info?.componentName).toBe('SaveButton')
999
+ expect(info?.hierarchy.map((item) => item.componentName)).toEqual(['SaveButton', 'Panel', 'App'])
1000
+ expect(info?.props).toEqual({
1001
+ label: 'Click me',
1002
+ count: 3,
1003
+ config: { variant: 'primary' },
1004
+ onClick: '[function]',
1005
+ })
1006
+
1007
+ console.log('Component name from fiber:', componentName)
1008
+ console.log('React component info:', info)
1009
+ console.log('Source location (null for UMD React, works on local dev servers with JSX transform):', source)
1010
+
1011
+ await browser.close()
1012
+ await page.close()
1013
+ }, 60000)
1014
+ })
1015
+
1016
+ // --- Service Worker Target Tests ---
1017
+
1018
+ describe('Service Worker Target Tests', () => {
1019
+ let testCtx: TestContext | null = null
1020
+
1021
+ beforeAll(async () => {
1022
+ testCtx = await setupTestContext({ port: TEST_PORT, tempDirPrefix: 'pw-sw-test-', toggleExtension: true })
1023
+ }, 600000)
1024
+
1025
+ afterAll(async () => {
1026
+ await cleanupTestContext(testCtx)
1027
+ testCtx = null
1028
+ })
1029
+
1030
+ const getBrowserContext = () => {
1031
+ if (!testCtx?.browserContext) throw new Error('Browser not initialized')
1032
+ return testCtx.browserContext
1033
+ }
1034
+
1035
+ it('should not expose service worker targets to Playwright (issue #14)', async () => {
1036
+ const browserContext = getBrowserContext()
1037
+ const serviceWorker = await getExtensionServiceWorker(browserContext)
1038
+
1039
+ const page = await browserContext.newPage()
1040
+ await page.goto('https://web.dev/', { waitUntil: 'load' })
1041
+ await page.bringToFront()
1042
+
1043
+ await serviceWorker.evaluate(async () => {
1044
+ await globalThis.toggleExtensionForActiveTab()
1045
+ })
1046
+ await new Promise((r) => setTimeout(r, 500))
1047
+
1048
+ const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
1049
+ const context = browser.contexts()[0]
1050
+
1051
+ const pages = context.pages()
1052
+
1053
+ for (const p of pages) {
1054
+ const url = p.url()
1055
+ console.log('Page URL:', url)
1056
+ expect(url).not.toMatch(/sw\.js$/i)
1057
+ expect(url).not.toMatch(/service.?worker/i)
1058
+ }
1059
+
1060
+ const targetPage = pages.find((p) => p.url().includes('web.dev'))
1061
+ expect(targetPage).toBeDefined()
1062
+
1063
+ const title = await targetPage!.title()
1064
+ expect(title).toBeTruthy()
1065
+
1066
+ await safeCloseCDPBrowser(browser)
1067
+ await page.close()
1068
+ }, 60000)
1069
+
1070
+ it('should allow reading response bodies after re-enabling Network buffering', async () => {
1071
+ const browserContext = getBrowserContext()
1072
+ const serviceWorker = await getExtensionServiceWorker(browserContext)
1073
+
1074
+ const page = await browserContext.newPage()
1075
+ await page.goto('https://example.com/')
1076
+ await page.bringToFront()
1077
+
1078
+ await serviceWorker.evaluate(async () => {
1079
+ await globalThis.toggleExtensionForActiveTab()
1080
+ })
1081
+ await new Promise((r) => setTimeout(r, 100))
1082
+
1083
+ const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
1084
+ const cdpPage = browser
1085
+ .contexts()[0]
1086
+ .pages()
1087
+ .find((p) => p.url().includes('example.com'))
1088
+ expect(cdpPage).toBeDefined()
1089
+
1090
+ const wsUrl = getCdpUrl({ port: TEST_PORT })
1091
+ const cdpSession = await getCDPSessionForPage({ page: cdpPage! })
1092
+
1093
+ await cdpSession.send('Network.disable')
1094
+ await cdpSession.send('Network.enable', {
1095
+ maxTotalBufferSize: 10000000,
1096
+ maxResourceBufferSize: 5000000,
1097
+ })
1098
+
1099
+ const [response] = await Promise.all([
1100
+ cdpPage!.waitForResponse((resp) => resp.url() === 'https://example.com/'),
1101
+ cdpPage!.goto('https://example.com/'),
1102
+ ])
1103
+
1104
+ const body = await response.text()
1105
+
1106
+ expect(body).toBeDefined()
1107
+ expect(body).toContain('Example Domain')
1108
+ expect(body).toContain('</html>')
1109
+
1110
+ await cdpSession.detach()
1111
+ await browser.close()
1112
+ await page.close()
1113
+ }, 60000)
1114
+
1115
+ it('should stream SSE without waiting for response end', async () => {
1116
+ const browserContext = getBrowserContext()
1117
+ const serviceWorker = await withTimeout({
1118
+ promise: getExtensionServiceWorker(browserContext),
1119
+ timeoutMs: 5000,
1120
+ errorMessage: 'getExtensionServiceWorker timed out',
1121
+ })
1122
+ const sseServer = await withTimeout({
1123
+ promise: createSseServer(),
1124
+ timeoutMs: 5000,
1125
+ errorMessage: 'createSseServer timed out',
1126
+ })
1127
+ let page: Awaited<ReturnType<typeof browserContext.newPage>> | null = null
1128
+ let browser: Awaited<ReturnType<typeof chromium.connectOverCDP>> | null = null
1129
+
1130
+ try {
1131
+ page = await withTimeout({
1132
+ promise: browserContext.newPage(),
1133
+ timeoutMs: 5000,
1134
+ errorMessage: 'newPage timed out',
1135
+ })
1136
+ await withTimeout({
1137
+ promise: page.goto(`${sseServer.baseUrl}/`),
1138
+ timeoutMs: 5000,
1139
+ errorMessage: 'page.goto timed out',
1140
+ })
1141
+ await page.bringToFront()
1142
+
1143
+ await withTimeout({
1144
+ promise: serviceWorker.evaluate(async () => {
1145
+ await globalThis.toggleExtensionForActiveTab()
1146
+ }),
1147
+ timeoutMs: 5000,
1148
+ errorMessage: 'toggleExtensionForActiveTab timed out',
1149
+ })
1150
+ await new Promise((resolve) => {
1151
+ setTimeout(resolve, 100)
1152
+ })
1153
+
1154
+ browser = await withTimeout({
1155
+ promise: chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT })),
1156
+ timeoutMs: 5000,
1157
+ errorMessage: 'connectOverCDP timed out',
1158
+ })
1159
+ const cdpPage = browser
1160
+ .contexts()[0]
1161
+ .pages()
1162
+ .find((p) => {
1163
+ return p.url().startsWith(sseServer.baseUrl)
1164
+ })
1165
+ expect(cdpPage).toBeDefined()
1166
+
1167
+ await cdpPage!.evaluate(() => {
1168
+ return window.startSse()
1169
+ })
1170
+ await withTimeout({
1171
+ promise: cdpPage!.waitForFunction(
1172
+ () => {
1173
+ return window.__sseMessages.length > 0
1174
+ },
1175
+ { timeout: 5000 },
1176
+ ),
1177
+ timeoutMs: 7000,
1178
+ errorMessage: 'SSE message not received in time',
1179
+ })
1180
+
1181
+ const firstMessage = await cdpPage!.evaluate(() => {
1182
+ return window.__sseMessages[0]
1183
+ })
1184
+ expect(firstMessage).toBe('hello')
1185
+
1186
+ const sseState = sseServer.getState()
1187
+ expect(sseState.connected).toBe(true)
1188
+ expect(sseState.finished).toBe(false)
1189
+ expect(sseState.closed).toBe(false)
1190
+ expect(sseState.writeCount).toBeGreaterThan(0)
1191
+
1192
+ const readyState = await cdpPage!.evaluate(() => {
1193
+ if (!window.__sseSource) {
1194
+ return -1
1195
+ }
1196
+ return window.__sseSource.readyState
1197
+ })
1198
+ expect(readyState).toBe(1)
1199
+
1200
+ await cdpPage!.evaluate(() => {
1201
+ window.stopSse()
1202
+ })
1203
+ await new Promise((resolve) => {
1204
+ setTimeout(resolve, 100)
1205
+ })
1206
+ } finally {
1207
+ if (browser) {
1208
+ await withTimeout({
1209
+ promise: browser.close(),
1210
+ timeoutMs: 5000,
1211
+ errorMessage: 'browser.close timed out',
1212
+ })
1213
+ }
1214
+ if (page) {
1215
+ await withTimeout({
1216
+ promise: page.close(),
1217
+ timeoutMs: 5000,
1218
+ errorMessage: 'page.close timed out',
1219
+ })
1220
+ }
1221
+ await withTimeout({
1222
+ promise: sseServer.close(),
1223
+ timeoutMs: 5000,
1224
+ errorMessage: 'sseServer.close timed out',
1225
+ })
1226
+ }
1227
+ }, 60000)
1228
+ })
1229
+
1230
+ // --- Auto-enable Tests ---
1231
+
1232
+ describe('Auto-enable Tests', () => {
1233
+ let testCtx: TestContext | null = null
1234
+ let client: Awaited<ReturnType<typeof createMCPClient>>['client']
1235
+ let cleanup: (() => Promise<void>) | null = null
1236
+
1237
+ beforeAll(async () => {
1238
+ process.env.PLAYWRITER_AUTO_ENABLE = '1'
1239
+ testCtx = await setupTestContext({ port: TEST_PORT, tempDirPrefix: 'pw-auto-test-' })
1240
+
1241
+ const result = await createMCPClient({ port: TEST_PORT })
1242
+ client = result.client
1243
+ cleanup = result.cleanup
1244
+
1245
+ // Disconnect all tabs to start with a clean state
1246
+ const serviceWorker = await getExtensionServiceWorker(testCtx.browserContext)
1247
+ await serviceWorker.evaluate(async () => {
1248
+ await globalThis.disconnectEverything()
1249
+ })
1250
+ await new Promise((r) => setTimeout(r, 100))
1251
+ }, 600000)
1252
+
1253
+ afterAll(async () => {
1254
+ delete process.env.PLAYWRITER_AUTO_ENABLE
1255
+ await cleanupTestContext(testCtx, cleanup)
1256
+ cleanup = null
1257
+ testCtx = null
1258
+ })
1259
+
1260
+ const getBrowserContext = () => {
1261
+ if (!testCtx?.browserContext) throw new Error('Browser not initialized')
1262
+ return testCtx.browserContext
1263
+ }
1264
+
1265
+ it('should auto-create a tab when Playwright connects and no tabs exist', async () => {
1266
+ const browserContext = getBrowserContext()
1267
+ const serviceWorker = await getExtensionServiceWorker(browserContext)
1268
+
1269
+ await serviceWorker.evaluate(async () => {
1270
+ await globalThis.disconnectEverything()
1271
+ })
1272
+ await new Promise((r) => setTimeout(r, 100))
1273
+
1274
+ const tabCountBefore = await serviceWorker.evaluate(() => {
1275
+ const state = globalThis.getExtensionState()
1276
+ return state.tabs.size
1277
+ })
1278
+ expect(tabCountBefore).toBe(0)
1279
+
1280
+ const previousAutoEnable = process.env.PLAYWRITER_AUTO_ENABLE
1281
+ delete process.env.PLAYWRITER_AUTO_ENABLE
1282
+ const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT })).finally(() => {
1283
+ if (previousAutoEnable === undefined) {
1284
+ delete process.env.PLAYWRITER_AUTO_ENABLE
1285
+ return
1286
+ }
1287
+ process.env.PLAYWRITER_AUTO_ENABLE = previousAutoEnable
1288
+ })
1289
+
1290
+ const pages = browser.contexts()[0].pages()
1291
+ expect(pages.length).toBeGreaterThan(0)
1292
+ expect(pages.length).toBe(1)
1293
+
1294
+ const autoCreatedPage = pages[0]
1295
+ expect(autoCreatedPage.url()).toBe('about:blank')
1296
+
1297
+ const tabCountAfter = await serviceWorker.evaluate(() => {
1298
+ const state = globalThis.getExtensionState()
1299
+ return state.tabs.size
1300
+ })
1301
+ expect(tabCountAfter).toBe(1)
1302
+
1303
+ await autoCreatedPage.setContent('<h1>Auto-created page</h1>')
1304
+ const title = await autoCreatedPage.locator('h1').textContent()
1305
+ expect(title).toBe('Auto-created page')
1306
+
1307
+ await browser.close()
1308
+ }, 60000)
1309
+
1310
+ it('should auto-create a page when MCP executes with no connected pages', async () => {
1311
+ const browserContext = getBrowserContext()
1312
+ const serviceWorker = await getExtensionServiceWorker(browserContext)
1313
+
1314
+ await serviceWorker.evaluate(async () => {
1315
+ await globalThis.disconnectEverything()
1316
+ })
1317
+ await new Promise((r) => {
1318
+ setTimeout(r, 100)
1319
+ })
1320
+
1321
+ const tabCountBefore = await serviceWorker.evaluate(() => {
1322
+ const state = globalThis.getExtensionState()
1323
+ return state.tabs.size
1324
+ })
1325
+ expect(tabCountBefore).toBe(0)
1326
+
1327
+ const result = await client.callTool({
1328
+ name: 'execute',
1329
+ arguments: {
1330
+ code: js`
1331
+ return { pageCount: context.pages().length, url: page.url() };
1332
+ `,
1333
+ },
1334
+ })
1335
+
1336
+ expect((result as any).isError).toBeFalsy()
1337
+ const text = (result as any).content[0].text
1338
+ expect(text).toContain('pageCount')
1339
+ expect(text).toContain('about:blank')
1340
+
1341
+ const tabCountAfter = await serviceWorker.evaluate(() => {
1342
+ const state = globalThis.getExtensionState()
1343
+ return state.tabs.size
1344
+ })
1345
+ expect(tabCountAfter).toBe(1)
1346
+
1347
+ await client.callTool({
1348
+ name: 'execute',
1349
+ arguments: {
1350
+ code: js`
1351
+ await page.close();
1352
+ return { remaining: context.pages().length };
1353
+ `,
1354
+ },
1355
+ })
1356
+
1357
+ await new Promise((r) => {
1358
+ setTimeout(r, 100)
1359
+ })
1360
+
1361
+ const afterCloseResult = await client.callTool({
1362
+ name: 'execute',
1363
+ arguments: {
1364
+ code: js`
1365
+ return { pageCount: context.pages().length, url: page.url() };
1366
+ `,
1367
+ },
1368
+ })
1369
+
1370
+ expect((afterCloseResult as any).isError).toBeFalsy()
1371
+ const afterCloseText = (afterCloseResult as any).content[0].text
1372
+ expect(afterCloseText).toContain('pageCount')
1373
+ expect(afterCloseText).toContain('about:blank')
1374
+ }, 60000)
1375
+ })