tabwright 1.0.0 → 1.0.1

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 (562) hide show
  1. package/dist/aria-snapshot.d.ts +0 -1
  2. package/dist/aria-snapshot.js +9 -1
  3. package/dist/assets/cursors/screen-studio/pointer-macos-tahoe-data-url.d.ts +0 -1
  4. package/dist/assets/cursors/screen-studio/pointer-macos-tahoe-data-url.js +0 -1
  5. package/dist/bippy.js +1 -1
  6. package/dist/browser-config.d.ts +0 -1
  7. package/dist/browser-config.js +0 -1
  8. package/dist/browser-install.d.ts +0 -1
  9. package/dist/browser-install.js +0 -1
  10. package/dist/browser-launch.d.ts +0 -1
  11. package/dist/browser-launch.js +0 -1
  12. package/dist/builtin-capabilities.d.ts +0 -1
  13. package/dist/builtin-capabilities.js +0 -1
  14. package/dist/capability-agent-skill.d.ts +0 -1
  15. package/dist/capability-agent-skill.js +0 -1
  16. package/dist/capability-auth-state.d.ts +0 -1
  17. package/dist/capability-auth-state.js +0 -1
  18. package/dist/capability-auth.d.ts +0 -1
  19. package/dist/capability-auth.js +0 -1
  20. package/dist/capability-options.d.ts +0 -1
  21. package/dist/capability-options.js +0 -1
  22. package/dist/capability-package.d.ts +0 -1
  23. package/dist/capability-package.js +0 -1
  24. package/dist/capability-registry.d.ts +0 -1
  25. package/dist/capability-registry.js +0 -1
  26. package/dist/capability-runner.d.ts +0 -1
  27. package/dist/capability-runner.js +0 -1
  28. package/dist/capability-studio.d.ts +0 -1
  29. package/dist/capability-studio.js +0 -1
  30. package/dist/cdp-log.d.ts +0 -1
  31. package/dist/cdp-log.js +0 -1
  32. package/dist/cdp-relay.d.ts +0 -1
  33. package/dist/cdp-relay.js +0 -1
  34. package/dist/cdp-session.d.ts +0 -1
  35. package/dist/cdp-session.js +0 -1
  36. package/dist/cdp-types.d.ts +0 -1
  37. package/dist/cdp-types.js +0 -1
  38. package/dist/chrome-discovery.d.ts +0 -1
  39. package/dist/chrome-discovery.js +0 -1
  40. package/dist/clean-html.d.ts +0 -1
  41. package/dist/clean-html.js +0 -1
  42. package/dist/cli.d.ts +0 -1
  43. package/dist/cli.js +9 -2
  44. package/dist/cloud-client.d.ts +0 -1
  45. package/dist/cloud-client.js +0 -1
  46. package/dist/create-logger.d.ts +0 -1
  47. package/dist/create-logger.js +0 -1
  48. package/dist/debugger-examples-types.d.ts +0 -1
  49. package/dist/debugger-examples-types.js +0 -1
  50. package/dist/debugger-examples.d.ts +0 -1
  51. package/dist/debugger-examples.js +0 -1
  52. package/dist/debugger.d.ts +0 -1
  53. package/dist/debugger.js +0 -1
  54. package/dist/diff-utils.d.ts +0 -1
  55. package/dist/diff-utils.js +0 -1
  56. package/dist/doctor.d.ts +0 -1
  57. package/dist/doctor.js +5 -7
  58. package/dist/editor-examples.d.ts +0 -1
  59. package/dist/editor-examples.js +0 -1
  60. package/dist/editor.d.ts +0 -1
  61. package/dist/editor.js +0 -1
  62. package/dist/env-compat.d.ts +0 -1
  63. package/dist/env-compat.js +0 -1
  64. package/dist/executor.d.ts +0 -1
  65. package/dist/executor.js +1 -2
  66. package/dist/extension/background.js +7 -3
  67. package/dist/extension/manifest.json +1 -1
  68. package/dist/ghost-browser.d.ts +0 -1
  69. package/dist/ghost-browser.js +0 -1
  70. package/dist/ghost-cursor-controller.d.ts +0 -1
  71. package/dist/ghost-cursor-controller.js +0 -1
  72. package/dist/ghost-cursor.d.ts +0 -1
  73. package/dist/ghost-cursor.js +0 -1
  74. package/dist/htmlrewrite.d.ts +0 -1
  75. package/dist/htmlrewrite.js +0 -1
  76. package/dist/index.d.ts +0 -1
  77. package/dist/index.js +0 -1
  78. package/dist/kill-port.d.ts +0 -1
  79. package/dist/kill-port.js +0 -1
  80. package/dist/kitty-graphics.d.ts +0 -1
  81. package/dist/kitty-graphics.js +0 -1
  82. package/dist/mcp.d.ts +0 -1
  83. package/dist/mcp.js +0 -1
  84. package/dist/package-paths.d.ts +0 -1
  85. package/dist/package-paths.js +0 -1
  86. package/dist/page-markdown.d.ts +0 -1
  87. package/dist/page-markdown.js +0 -1
  88. package/dist/performance-examples.d.ts +0 -1
  89. package/dist/performance-examples.js +0 -1
  90. package/dist/playwright-import.d.ts +0 -1
  91. package/dist/playwright-import.js +0 -1
  92. package/dist/product-paths.d.ts +0 -1
  93. package/dist/product-paths.js +0 -1
  94. package/dist/protocol.d.ts +0 -1
  95. package/dist/protocol.js +0 -1
  96. package/dist/react-source.d.ts +0 -1
  97. package/dist/react-source.js +0 -1
  98. package/dist/readability.js +1 -1
  99. package/dist/relay-client.d.ts +0 -1
  100. package/dist/relay-client.js +0 -1
  101. package/dist/relay-state.d.ts +0 -1
  102. package/dist/relay-state.js +0 -1
  103. package/dist/replay-ai-index.d.ts +0 -1
  104. package/dist/replay-ai-index.js +0 -1
  105. package/dist/replay-eval.d.ts +0 -1
  106. package/dist/replay-eval.js +0 -1
  107. package/dist/replay-handoff.d.ts +0 -1
  108. package/dist/replay-handoff.js +0 -1
  109. package/dist/replay-workflow-compiler.d.ts +0 -1
  110. package/dist/replay-workflow-compiler.js +0 -1
  111. package/dist/rrweb-recording-relay.d.ts +0 -1
  112. package/dist/rrweb-recording-relay.js +0 -1
  113. package/dist/rrweb-recording.d.ts +0 -1
  114. package/dist/rrweb-recording.js +0 -1
  115. package/dist/scoped-fs.d.ts +0 -1
  116. package/dist/scoped-fs.js +0 -1
  117. package/dist/selector-generator.js +1 -1
  118. package/dist/start-relay-server.d.ts +0 -1
  119. package/dist/start-relay-server.js +0 -1
  120. package/dist/styles-examples.d.ts +0 -1
  121. package/dist/styles-examples.js +0 -1
  122. package/dist/styles.d.ts +0 -1
  123. package/dist/styles.js +0 -1
  124. package/dist/tabwright-agent-skill.d.ts +0 -1
  125. package/dist/tabwright-agent-skill.js +0 -1
  126. package/dist/test-declarations.d.ts +0 -1
  127. package/dist/test-declarations.js +0 -1
  128. package/dist/utils.d.ts +0 -1
  129. package/dist/utils.js +2 -2
  130. package/dist/wait-for-page-load.d.ts +0 -1
  131. package/dist/wait-for-page-load.js +0 -1
  132. package/dist/workflow-capability.d.ts +0 -1
  133. package/dist/workflow-capability.js +0 -1
  134. package/package.json +9 -4
  135. package/readme.md +35 -0
  136. package/dist/aria-snapshot.d.ts.map +0 -1
  137. package/dist/aria-snapshot.js.map +0 -1
  138. package/dist/aria-snapshot.test.d.ts +0 -2
  139. package/dist/aria-snapshot.test.d.ts.map +0 -1
  140. package/dist/aria-snapshot.test.js +0 -270
  141. package/dist/aria-snapshot.test.js.map +0 -1
  142. package/dist/aria-snapshot.unit.test.d.ts +0 -2
  143. package/dist/aria-snapshot.unit.test.d.ts.map +0 -1
  144. package/dist/aria-snapshot.unit.test.js +0 -542
  145. package/dist/aria-snapshot.unit.test.js.map +0 -1
  146. package/dist/assets/cursors/screen-studio/pointer-macos-tahoe-data-url.d.ts.map +0 -1
  147. package/dist/assets/cursors/screen-studio/pointer-macos-tahoe-data-url.js.map +0 -1
  148. package/dist/browser-config.d.ts.map +0 -1
  149. package/dist/browser-config.js.map +0 -1
  150. package/dist/browser-install.d.ts.map +0 -1
  151. package/dist/browser-install.js.map +0 -1
  152. package/dist/browser-launch.d.ts.map +0 -1
  153. package/dist/browser-launch.js.map +0 -1
  154. package/dist/builtin-capabilities.d.ts.map +0 -1
  155. package/dist/builtin-capabilities.js.map +0 -1
  156. package/dist/capability-agent-skill.d.ts.map +0 -1
  157. package/dist/capability-agent-skill.js.map +0 -1
  158. package/dist/capability-auth-state.d.ts.map +0 -1
  159. package/dist/capability-auth-state.js.map +0 -1
  160. package/dist/capability-auth.d.ts.map +0 -1
  161. package/dist/capability-auth.js.map +0 -1
  162. package/dist/capability-cli-confirmation.test.d.ts +0 -2
  163. package/dist/capability-cli-confirmation.test.d.ts.map +0 -1
  164. package/dist/capability-cli-confirmation.test.js +0 -79
  165. package/dist/capability-cli-confirmation.test.js.map +0 -1
  166. package/dist/capability-options.d.ts.map +0 -1
  167. package/dist/capability-options.js.map +0 -1
  168. package/dist/capability-options.test.d.ts +0 -2
  169. package/dist/capability-options.test.d.ts.map +0 -1
  170. package/dist/capability-options.test.js +0 -81
  171. package/dist/capability-options.test.js.map +0 -1
  172. package/dist/capability-package.d.ts.map +0 -1
  173. package/dist/capability-package.js.map +0 -1
  174. package/dist/capability-package.test.d.ts +0 -2
  175. package/dist/capability-package.test.d.ts.map +0 -1
  176. package/dist/capability-package.test.js +0 -179
  177. package/dist/capability-package.test.js.map +0 -1
  178. package/dist/capability-registry.d.ts.map +0 -1
  179. package/dist/capability-registry.js.map +0 -1
  180. package/dist/capability-registry.test.d.ts +0 -2
  181. package/dist/capability-registry.test.d.ts.map +0 -1
  182. package/dist/capability-registry.test.js +0 -1683
  183. package/dist/capability-registry.test.js.map +0 -1
  184. package/dist/capability-runner.d.ts.map +0 -1
  185. package/dist/capability-runner.js.map +0 -1
  186. package/dist/capability-search-relevance.test.d.ts +0 -2
  187. package/dist/capability-search-relevance.test.d.ts.map +0 -1
  188. package/dist/capability-search-relevance.test.js +0 -67
  189. package/dist/capability-search-relevance.test.js.map +0 -1
  190. package/dist/capability-studio.d.ts.map +0 -1
  191. package/dist/capability-studio.js.map +0 -1
  192. package/dist/capability-studio.test.d.ts +0 -2
  193. package/dist/capability-studio.test.d.ts.map +0 -1
  194. package/dist/capability-studio.test.js +0 -41
  195. package/dist/capability-studio.test.js.map +0 -1
  196. package/dist/cdp-log.d.ts.map +0 -1
  197. package/dist/cdp-log.js.map +0 -1
  198. package/dist/cdp-log.test.d.ts +0 -2
  199. package/dist/cdp-log.test.d.ts.map +0 -1
  200. package/dist/cdp-log.test.js +0 -109
  201. package/dist/cdp-log.test.js.map +0 -1
  202. package/dist/cdp-relay.d.ts.map +0 -1
  203. package/dist/cdp-relay.js.map +0 -1
  204. package/dist/cdp-session.d.ts.map +0 -1
  205. package/dist/cdp-session.js.map +0 -1
  206. package/dist/cdp-types.d.ts.map +0 -1
  207. package/dist/cdp-types.js.map +0 -1
  208. package/dist/channel-owner-inspect.test.d.ts +0 -2
  209. package/dist/channel-owner-inspect.test.d.ts.map +0 -1
  210. package/dist/channel-owner-inspect.test.js +0 -75
  211. package/dist/channel-owner-inspect.test.js.map +0 -1
  212. package/dist/chrome-discovery.d.ts.map +0 -1
  213. package/dist/chrome-discovery.js.map +0 -1
  214. package/dist/chrome-discovery.test.d.ts +0 -2
  215. package/dist/chrome-discovery.test.d.ts.map +0 -1
  216. package/dist/chrome-discovery.test.js +0 -41
  217. package/dist/chrome-discovery.test.js.map +0 -1
  218. package/dist/clean-html.d.ts.map +0 -1
  219. package/dist/clean-html.js.map +0 -1
  220. package/dist/cli-help.test.d.ts +0 -2
  221. package/dist/cli-help.test.d.ts.map +0 -1
  222. package/dist/cli-help.test.js +0 -98
  223. package/dist/cli-help.test.js.map +0 -1
  224. package/dist/cli.d.ts.map +0 -1
  225. package/dist/cli.js.map +0 -1
  226. package/dist/cloud-client.d.ts.map +0 -1
  227. package/dist/cloud-client.js.map +0 -1
  228. package/dist/create-logger.d.ts.map +0 -1
  229. package/dist/create-logger.js.map +0 -1
  230. package/dist/debugger-examples-types.d.ts.map +0 -1
  231. package/dist/debugger-examples-types.js.map +0 -1
  232. package/dist/debugger-examples.d.ts.map +0 -1
  233. package/dist/debugger-examples.js.map +0 -1
  234. package/dist/debugger.d.ts.map +0 -1
  235. package/dist/debugger.js.map +0 -1
  236. package/dist/diff-utils.d.ts.map +0 -1
  237. package/dist/diff-utils.js.map +0 -1
  238. package/dist/diff-utils.test.d.ts +0 -2
  239. package/dist/diff-utils.test.d.ts.map +0 -1
  240. package/dist/diff-utils.test.js +0 -137
  241. package/dist/diff-utils.test.js.map +0 -1
  242. package/dist/doctor.d.ts.map +0 -1
  243. package/dist/doctor.js.map +0 -1
  244. package/dist/doctor.test.d.ts +0 -2
  245. package/dist/doctor.test.d.ts.map +0 -1
  246. package/dist/doctor.test.js +0 -208
  247. package/dist/doctor.test.js.map +0 -1
  248. package/dist/editor-examples.d.ts.map +0 -1
  249. package/dist/editor-examples.js.map +0 -1
  250. package/dist/editor.d.ts.map +0 -1
  251. package/dist/editor.js.map +0 -1
  252. package/dist/env-compat.d.ts.map +0 -1
  253. package/dist/env-compat.js.map +0 -1
  254. package/dist/executor.d.ts.map +0 -1
  255. package/dist/executor.js.map +0 -1
  256. package/dist/executor.unit.test.d.ts +0 -2
  257. package/dist/executor.unit.test.d.ts.map +0 -1
  258. package/dist/executor.unit.test.js +0 -143
  259. package/dist/executor.unit.test.js.map +0 -1
  260. package/dist/extension-connection.test.d.ts +0 -2
  261. package/dist/extension-connection.test.d.ts.map +0 -1
  262. package/dist/extension-connection.test.js +0 -708
  263. package/dist/extension-connection.test.js.map +0 -1
  264. package/dist/extension-icon-warning.test.d.ts +0 -2
  265. package/dist/extension-icon-warning.test.d.ts.map +0 -1
  266. package/dist/extension-icon-warning.test.js +0 -33
  267. package/dist/extension-icon-warning.test.js.map +0 -1
  268. package/dist/ghost-browser.d.ts.map +0 -1
  269. package/dist/ghost-browser.js.map +0 -1
  270. package/dist/ghost-cursor-controller.d.ts.map +0 -1
  271. package/dist/ghost-cursor-controller.js.map +0 -1
  272. package/dist/ghost-cursor.d.ts.map +0 -1
  273. package/dist/ghost-cursor.js.map +0 -1
  274. package/dist/htmlrewrite.d.ts.map +0 -1
  275. package/dist/htmlrewrite.js.map +0 -1
  276. package/dist/htmlrewrite.test.d.ts +0 -2
  277. package/dist/htmlrewrite.test.d.ts.map +0 -1
  278. package/dist/htmlrewrite.test.js +0 -7975
  279. package/dist/htmlrewrite.test.js.map +0 -1
  280. package/dist/index.d.ts.map +0 -1
  281. package/dist/index.js.map +0 -1
  282. package/dist/kill-port.d.ts.map +0 -1
  283. package/dist/kill-port.js.map +0 -1
  284. package/dist/kitty-graphics.d.ts.map +0 -1
  285. package/dist/kitty-graphics.js.map +0 -1
  286. package/dist/kitty-graphics.test.d.ts +0 -2
  287. package/dist/kitty-graphics.test.d.ts.map +0 -1
  288. package/dist/kitty-graphics.test.js +0 -125
  289. package/dist/kitty-graphics.test.js.map +0 -1
  290. package/dist/locator-selector.test.d.ts +0 -2
  291. package/dist/locator-selector.test.d.ts.map +0 -1
  292. package/dist/locator-selector.test.js +0 -98
  293. package/dist/locator-selector.test.js.map +0 -1
  294. package/dist/mcp-client.d.ts +0 -20
  295. package/dist/mcp-client.d.ts.map +0 -1
  296. package/dist/mcp-client.js +0 -56
  297. package/dist/mcp-client.js.map +0 -1
  298. package/dist/mcp.d.ts.map +0 -1
  299. package/dist/mcp.js.map +0 -1
  300. package/dist/on-mouse-action.test.d.ts +0 -2
  301. package/dist/on-mouse-action.test.d.ts.map +0 -1
  302. package/dist/on-mouse-action.test.js +0 -180
  303. package/dist/on-mouse-action.test.js.map +0 -1
  304. package/dist/options-page.test.d.ts +0 -2
  305. package/dist/options-page.test.d.ts.map +0 -1
  306. package/dist/options-page.test.js +0 -773
  307. package/dist/options-page.test.js.map +0 -1
  308. package/dist/package-paths.d.ts.map +0 -1
  309. package/dist/package-paths.js.map +0 -1
  310. package/dist/page-markdown.d.ts.map +0 -1
  311. package/dist/page-markdown.js.map +0 -1
  312. package/dist/performance-examples.d.ts.map +0 -1
  313. package/dist/performance-examples.js.map +0 -1
  314. package/dist/playwright-import.d.ts.map +0 -1
  315. package/dist/playwright-import.js.map +0 -1
  316. package/dist/popup-relocation.test.d.ts +0 -7
  317. package/dist/popup-relocation.test.d.ts.map +0 -1
  318. package/dist/popup-relocation.test.js +0 -139
  319. package/dist/popup-relocation.test.js.map +0 -1
  320. package/dist/product-paths.d.ts.map +0 -1
  321. package/dist/product-paths.js.map +0 -1
  322. package/dist/product-paths.test.d.ts +0 -2
  323. package/dist/product-paths.test.d.ts.map +0 -1
  324. package/dist/product-paths.test.js +0 -39
  325. package/dist/product-paths.test.js.map +0 -1
  326. package/dist/protocol.d.ts.map +0 -1
  327. package/dist/protocol.js.map +0 -1
  328. package/dist/protocol.test.d.ts +0 -2
  329. package/dist/protocol.test.d.ts.map +0 -1
  330. package/dist/protocol.test.js +0 -31
  331. package/dist/protocol.test.js.map +0 -1
  332. package/dist/react-source.d.ts.map +0 -1
  333. package/dist/react-source.js.map +0 -1
  334. package/dist/relay-client.d.ts.map +0 -1
  335. package/dist/relay-client.js.map +0 -1
  336. package/dist/relay-client.test.d.ts +0 -2
  337. package/dist/relay-client.test.d.ts.map +0 -1
  338. package/dist/relay-client.test.js +0 -305
  339. package/dist/relay-client.test.js.map +0 -1
  340. package/dist/relay-core.test.d.ts +0 -2
  341. package/dist/relay-core.test.d.ts.map +0 -1
  342. package/dist/relay-core.test.js +0 -1427
  343. package/dist/relay-core.test.js.map +0 -1
  344. package/dist/relay-navigation.test.d.ts +0 -2
  345. package/dist/relay-navigation.test.d.ts.map +0 -1
  346. package/dist/relay-navigation.test.js +0 -661
  347. package/dist/relay-navigation.test.js.map +0 -1
  348. package/dist/relay-session.test.d.ts +0 -2
  349. package/dist/relay-session.test.d.ts.map +0 -1
  350. package/dist/relay-session.test.js +0 -1121
  351. package/dist/relay-session.test.js.map +0 -1
  352. package/dist/relay-state.d.ts.map +0 -1
  353. package/dist/relay-state.js.map +0 -1
  354. package/dist/relay-state.test.d.ts +0 -2
  355. package/dist/relay-state.test.d.ts.map +0 -1
  356. package/dist/relay-state.test.js +0 -594
  357. package/dist/relay-state.test.js.map +0 -1
  358. package/dist/replay-ai-index.d.ts.map +0 -1
  359. package/dist/replay-ai-index.js.map +0 -1
  360. package/dist/replay-ai-index.test.d.ts +0 -2
  361. package/dist/replay-ai-index.test.d.ts.map +0 -1
  362. package/dist/replay-ai-index.test.js +0 -255
  363. package/dist/replay-ai-index.test.js.map +0 -1
  364. package/dist/replay-cli.test.d.ts +0 -2
  365. package/dist/replay-cli.test.d.ts.map +0 -1
  366. package/dist/replay-cli.test.js +0 -275
  367. package/dist/replay-cli.test.js.map +0 -1
  368. package/dist/replay-eval.d.ts.map +0 -1
  369. package/dist/replay-eval.js.map +0 -1
  370. package/dist/replay-handoff.d.ts.map +0 -1
  371. package/dist/replay-handoff.js.map +0 -1
  372. package/dist/replay-handoff.test.d.ts +0 -2
  373. package/dist/replay-handoff.test.d.ts.map +0 -1
  374. package/dist/replay-handoff.test.js +0 -123
  375. package/dist/replay-handoff.test.js.map +0 -1
  376. package/dist/replay-workflow-compiler.d.ts.map +0 -1
  377. package/dist/replay-workflow-compiler.js.map +0 -1
  378. package/dist/replay-workflow-compiler.test.d.ts +0 -2
  379. package/dist/replay-workflow-compiler.test.d.ts.map +0 -1
  380. package/dist/replay-workflow-compiler.test.js +0 -223
  381. package/dist/replay-workflow-compiler.test.js.map +0 -1
  382. package/dist/rrweb-recording-relay.d.ts.map +0 -1
  383. package/dist/rrweb-recording-relay.js.map +0 -1
  384. package/dist/rrweb-recording-relay.test.d.ts +0 -2
  385. package/dist/rrweb-recording-relay.test.d.ts.map +0 -1
  386. package/dist/rrweb-recording-relay.test.js +0 -85
  387. package/dist/rrweb-recording-relay.test.js.map +0 -1
  388. package/dist/rrweb-recording.d.ts.map +0 -1
  389. package/dist/rrweb-recording.js.map +0 -1
  390. package/dist/scoped-fs.d.ts.map +0 -1
  391. package/dist/scoped-fs.js.map +0 -1
  392. package/dist/scoped-fs.test.d.ts +0 -5
  393. package/dist/scoped-fs.test.d.ts.map +0 -1
  394. package/dist/scoped-fs.test.js +0 -73
  395. package/dist/scoped-fs.test.js.map +0 -1
  396. package/dist/skill-stub.test.d.ts +0 -2
  397. package/dist/skill-stub.test.d.ts.map +0 -1
  398. package/dist/skill-stub.test.js +0 -22
  399. package/dist/skill-stub.test.js.map +0 -1
  400. package/dist/snapshot-tools.test.d.ts +0 -2
  401. package/dist/snapshot-tools.test.d.ts.map +0 -1
  402. package/dist/snapshot-tools.test.js +0 -856
  403. package/dist/snapshot-tools.test.js.map +0 -1
  404. package/dist/start-relay-server.d.ts.map +0 -1
  405. package/dist/start-relay-server.js.map +0 -1
  406. package/dist/styles-examples.d.ts.map +0 -1
  407. package/dist/styles-examples.js.map +0 -1
  408. package/dist/styles.d.ts.map +0 -1
  409. package/dist/styles.js.map +0 -1
  410. package/dist/tabwright-agent-skill.d.ts.map +0 -1
  411. package/dist/tabwright-agent-skill.js.map +0 -1
  412. package/dist/tabwright-agent-skill.test.d.ts +0 -2
  413. package/dist/tabwright-agent-skill.test.d.ts.map +0 -1
  414. package/dist/tabwright-agent-skill.test.js +0 -51
  415. package/dist/tabwright-agent-skill.test.js.map +0 -1
  416. package/dist/test-declarations.d.ts.map +0 -1
  417. package/dist/test-declarations.js.map +0 -1
  418. package/dist/test-utils.d.ts +0 -69
  419. package/dist/test-utils.d.ts.map +0 -1
  420. package/dist/test-utils.js +0 -448
  421. package/dist/test-utils.js.map +0 -1
  422. package/dist/test-utils.test.d.ts +0 -2
  423. package/dist/test-utils.test.d.ts.map +0 -1
  424. package/dist/test-utils.test.js +0 -55
  425. package/dist/test-utils.test.js.map +0 -1
  426. package/dist/utils.d.ts.map +0 -1
  427. package/dist/utils.js.map +0 -1
  428. package/dist/wait-for-page-load.d.ts.map +0 -1
  429. package/dist/wait-for-page-load.js.map +0 -1
  430. package/dist/workflow-capability.d.ts.map +0 -1
  431. package/dist/workflow-capability.js.map +0 -1
  432. package/dist/workflow-script-flow.test.d.ts +0 -2
  433. package/dist/workflow-script-flow.test.d.ts.map +0 -1
  434. package/dist/workflow-script-flow.test.js +0 -285
  435. package/dist/workflow-script-flow.test.js.map +0 -1
  436. package/src/__snapshots__/x.com.processed.html +0 -1003
  437. package/src/__snapshots__/x.com.processed.withStyles.html +0 -2720
  438. package/src/a11y-client.ts +0 -241
  439. package/src/aria-snapshot.test.ts +0 -311
  440. package/src/aria-snapshot.ts +0 -1681
  441. package/src/aria-snapshot.unit.test.ts +0 -615
  442. package/src/aria-snapshots/github-interactive.txt +0 -193
  443. package/src/aria-snapshots/github-raw.txt +0 -309
  444. package/src/aria-snapshots/hackernews-interactive.txt +0 -477
  445. package/src/aria-snapshots/hackernews-raw.txt +0 -428
  446. package/src/aria-snapshots/prosemirror-interactive.txt +0 -66
  447. package/src/aria-snapshots/prosemirror-raw.txt +0 -145
  448. package/src/assets/aria-labels-hacker-news.png +0 -0
  449. package/src/assets/aria-labels-old-reddit.png +0 -0
  450. package/src/assets/cursors/screen-studio/pointer-macos-tahoe-data-url.ts +0 -5
  451. package/src/assets/cursors/screen-studio/pointer-macos-tahoe.svg +0 -18
  452. package/src/assets/x.com.html +0 -32946
  453. package/src/browser-config.ts +0 -179
  454. package/src/browser-install.ts +0 -284
  455. package/src/browser-launch.ts +0 -75
  456. package/src/builtin-capabilities.ts +0 -2765
  457. package/src/capability-agent-skill.ts +0 -305
  458. package/src/capability-auth-state.ts +0 -197
  459. package/src/capability-auth.ts +0 -249
  460. package/src/capability-cli-confirmation.test.ts +0 -90
  461. package/src/capability-options.test.ts +0 -89
  462. package/src/capability-options.ts +0 -57
  463. package/src/capability-package.test.ts +0 -204
  464. package/src/capability-package.ts +0 -555
  465. package/src/capability-registry.test.ts +0 -1905
  466. package/src/capability-registry.ts +0 -1234
  467. package/src/capability-runner.ts +0 -801
  468. package/src/capability-search-relevance.test.ts +0 -73
  469. package/src/capability-studio.test.ts +0 -45
  470. package/src/capability-studio.ts +0 -641
  471. package/src/cdp-log.test.ts +0 -131
  472. package/src/cdp-log.ts +0 -112
  473. package/src/cdp-relay.ts +0 -2944
  474. package/src/cdp-session.ts +0 -77
  475. package/src/cdp-types.ts +0 -158
  476. package/src/channel-owner-inspect.test.ts +0 -94
  477. package/src/chrome-discovery.test.ts +0 -50
  478. package/src/chrome-discovery.ts +0 -220
  479. package/src/clean-html.ts +0 -141
  480. package/src/cli-help.test.ts +0 -123
  481. package/src/cli.ts +0 -3181
  482. package/src/cloud-client.ts +0 -179
  483. package/src/create-logger.ts +0 -38
  484. package/src/debugger-examples-types.ts +0 -16
  485. package/src/debugger-examples.ts +0 -66
  486. package/src/debugger.ts +0 -708
  487. package/src/diff-utils.test.ts +0 -152
  488. package/src/diff-utils.ts +0 -70
  489. package/src/doctor.test.ts +0 -244
  490. package/src/doctor.ts +0 -321
  491. package/src/editor-examples.ts +0 -158
  492. package/src/editor.ts +0 -426
  493. package/src/env-compat.ts +0 -28
  494. package/src/executor.ts +0 -1824
  495. package/src/executor.unit.test.ts +0 -168
  496. package/src/extension-connection.test.ts +0 -855
  497. package/src/extension-icon-warning.test.ts +0 -45
  498. package/src/ghost-browser.ts +0 -149
  499. package/src/ghost-cursor-client.ts +0 -494
  500. package/src/ghost-cursor-controller.ts +0 -129
  501. package/src/ghost-cursor.ts +0 -123
  502. package/src/htmlrewrite.test.ts +0 -8008
  503. package/src/htmlrewrite.ts +0 -390
  504. package/src/index.ts +0 -53
  505. package/src/kill-port.ts +0 -219
  506. package/src/kitty-graphics.test.ts +0 -139
  507. package/src/kitty-graphics.ts +0 -79
  508. package/src/locator-selector.test.ts +0 -119
  509. package/src/mcp-client.ts +0 -78
  510. package/src/mcp.ts +0 -629
  511. package/src/on-mouse-action.test.ts +0 -226
  512. package/src/options-page.test.ts +0 -839
  513. package/src/package-paths.ts +0 -38
  514. package/src/page-markdown.ts +0 -240
  515. package/src/performance-examples.ts +0 -186
  516. package/src/playwright-import.ts +0 -59
  517. package/src/popup-relocation.test.ts +0 -163
  518. package/src/product-paths.test.ts +0 -47
  519. package/src/product-paths.ts +0 -28
  520. package/src/protocol.test.ts +0 -52
  521. package/src/protocol.ts +0 -351
  522. package/src/react-source.ts +0 -379
  523. package/src/relay-client.test.ts +0 -375
  524. package/src/relay-client.ts +0 -572
  525. package/src/relay-core.test.ts +0 -1603
  526. package/src/relay-navigation.test.ts +0 -790
  527. package/src/relay-session.test.ts +0 -1375
  528. package/src/relay-state.test.ts +0 -729
  529. package/src/relay-state.ts +0 -561
  530. package/src/replay-ai-index.test.ts +0 -278
  531. package/src/replay-ai-index.ts +0 -872
  532. package/src/replay-cli.test.ts +0 -325
  533. package/src/replay-eval.ts +0 -1039
  534. package/src/replay-handoff.test.ts +0 -163
  535. package/src/replay-handoff.ts +0 -109
  536. package/src/replay-workflow-compiler.test.ts +0 -250
  537. package/src/replay-workflow-compiler.ts +0 -700
  538. package/src/resource.md +0 -409
  539. package/src/rrweb-recording-relay.test.ts +0 -99
  540. package/src/rrweb-recording-relay.ts +0 -400
  541. package/src/rrweb-recording.ts +0 -322
  542. package/src/scoped-fs.test.ts +0 -80
  543. package/src/scoped-fs.ts +0 -420
  544. package/src/skill-stub.test.ts +0 -24
  545. package/src/snapshot-tools.test.ts +0 -1017
  546. package/src/snapshots/hacker-news-accessibility-full.md +0 -81
  547. package/src/snapshots/hacker-news-accessibility-interactive.md +0 -43
  548. package/src/snapshots/page-markdown-output.txt +0 -16
  549. package/src/snapshots/shadcn-ui-accessibility-full.md +0 -201
  550. package/src/snapshots/shadcn-ui-accessibility-interactive.md +0 -109
  551. package/src/start-relay-server.ts +0 -69
  552. package/src/styles-examples.ts +0 -84
  553. package/src/styles.ts +0 -343
  554. package/src/tabwright-agent-skill.test.ts +0 -61
  555. package/src/tabwright-agent-skill.ts +0 -105
  556. package/src/test-declarations.ts +0 -13
  557. package/src/test-utils.test.ts +0 -70
  558. package/src/test-utils.ts +0 -566
  559. package/src/utils.ts +0 -78
  560. package/src/wait-for-page-load.ts +0 -198
  561. package/src/workflow-capability.ts +0 -417
  562. package/src/workflow-script-flow.test.ts +0 -388
@@ -1,1603 +0,0 @@
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 { getCDPSessionForPage } from './cdp-session.js'
5
- import { getCdpUrl, LOG_CDP_FILE_PATH } from './utils.js'
6
- import fs from 'node:fs'
7
- import {
8
- setupTestContext,
9
- cleanupTestContext,
10
- getExtensionServiceWorker,
11
- type TestContext,
12
- withTimeout,
13
- js,
14
- tryJsonParse,
15
- createSimpleServer,
16
- safeCloseCDPBrowser,
17
- } from './test-utils.js'
18
- import './test-declarations.js'
19
-
20
- const TEST_PORT = 19987
21
-
22
- describe('Relay Core Tests', () => {
23
- let client: Awaited<ReturnType<typeof createMCPClient>>['client']
24
- let cleanup: (() => Promise<void>) | null = null
25
- let testCtx: TestContext | null = null
26
-
27
- beforeAll(async () => {
28
- testCtx = await setupTestContext({ port: TEST_PORT, tempDirPrefix: 'pw-test-', toggleExtension: true })
29
-
30
- const result = await createMCPClient({ port: TEST_PORT })
31
- client = result.client
32
- cleanup = result.cleanup
33
- }, 600000)
34
-
35
- afterAll(async () => {
36
- await cleanupTestContext(testCtx, cleanup)
37
- cleanup = null
38
- testCtx = null
39
- })
40
-
41
- const getBrowserContext = () => {
42
- if (!testCtx?.browserContext) throw new Error('Browser not initialized')
43
- return testCtx.browserContext
44
- }
45
-
46
- const ensureConnectedTabForExecute = async (): Promise<void> => {
47
- const browserContext = getBrowserContext()
48
- const serviceWorker = await getExtensionServiceWorker(browserContext)
49
- const connectedTabCount = await serviceWorker.evaluate(async () => {
50
- const state = globalThis.getExtensionState()
51
- return state.tabs.size
52
- })
53
- if (connectedTabCount > 0) {
54
- return
55
- }
56
-
57
- const page = await browserContext.newPage()
58
- await page.goto('about:blank')
59
- await page.bringToFront()
60
-
61
- await serviceWorker.evaluate(async () => {
62
- await globalThis.toggleExtensionForActiveTab()
63
- })
64
-
65
- await new Promise((r) => {
66
- setTimeout(r, 100)
67
- })
68
- }
69
-
70
- it('should inject script via addScriptTag through CDP relay', async () => {
71
- const browserContext = getBrowserContext()
72
- const serviceWorker = await withTimeout({
73
- promise: getExtensionServiceWorker(browserContext),
74
- timeoutMs: 5000,
75
- errorMessage: 'Timed out waiting for extension service worker for iframe test',
76
- })
77
-
78
- const page = await browserContext.newPage()
79
- const html = '<html><body><button id="btn">Click</button></body></html>'
80
- const dataUrl = `data:text/html,${encodeURIComponent(html)}`
81
- await page.goto(dataUrl)
82
- await page.bringToFront()
83
-
84
- await withTimeout({
85
- promise: serviceWorker.evaluate(async () => {
86
- await globalThis.toggleExtensionForActiveTab()
87
- }),
88
- timeoutMs: 10000,
89
- errorMessage: 'Timed out toggling extension for active tab',
90
- })
91
- await new Promise((r) => {
92
- setTimeout(r, 100)
93
- })
94
-
95
- const cdpSession = await withTimeout({
96
- promise: getCDPSessionForPage({ page }),
97
- timeoutMs: 10000,
98
- errorMessage: 'Timed out creating CDP session for page',
99
- })
100
-
101
- const hasGlobalBefore = await page.evaluate(() => {
102
- return Boolean((globalThis as { __testGlobal?: unknown }).__testGlobal)
103
- })
104
- expect(hasGlobalBefore).toBe(false)
105
-
106
- await withTimeout({
107
- promise: (async () => {
108
- await cdpSession.send('Page.enable')
109
- await cdpSession.send('Page.addScriptToEvaluateOnNewDocument', {
110
- source: 'globalThis.__testGlobal = { foo: "bar" }',
111
- })
112
- await page.reload({ waitUntil: 'domcontentloaded' })
113
- })(),
114
- timeoutMs: 10000,
115
- errorMessage: 'Timed out injecting script via CDP session',
116
- })
117
-
118
- const hasGlobalAfter = await page.evaluate(() => {
119
- return (globalThis as { __testGlobal?: unknown }).__testGlobal
120
- })
121
- expect(hasGlobalAfter).toEqual({ foo: 'bar' })
122
-
123
- await cdpSession.detach()
124
- await page.close()
125
- }, 60000)
126
-
127
- it('should emit download events for both Browser and Page domains in extension mode', async () => {
128
- const browserContext = getBrowserContext()
129
- const serviceWorker = await getExtensionServiceWorker(browserContext)
130
- const logFilePath = LOG_CDP_FILE_PATH
131
- const logLineCountBefore = fs.existsSync(logFilePath)
132
- ? fs
133
- .readFileSync(logFilePath, 'utf-8')
134
- .split('\n')
135
- .filter((line) => {
136
- return line.trim().length > 0
137
- }).length
138
- : 0
139
-
140
- const server = await createSimpleServer({
141
- routes: {
142
- '/': `<!doctype html>
143
- <html>
144
- <body>
145
- <button id="download-button">Download</button>
146
- <script>
147
- const button = document.getElementById('download-button');
148
- button.addEventListener('click', () => {
149
- const blob = new Blob(['tabwright-download-test'], { type: 'text/plain' });
150
- const url = URL.createObjectURL(blob);
151
- const anchor = document.createElement('a');
152
- anchor.href = url;
153
- anchor.download = 'tabwright-download-test.txt';
154
- document.body.appendChild(anchor);
155
- anchor.click();
156
- anchor.remove();
157
- setTimeout(() => {
158
- URL.revokeObjectURL(url);
159
- }, 1000);
160
- });
161
- </script>
162
- </body>
163
- </html>`,
164
- },
165
- })
166
-
167
- const page = await browserContext.newPage()
168
- await page.goto(server.baseUrl, { waitUntil: 'domcontentloaded' })
169
- await page.bringToFront()
170
-
171
- await serviceWorker.evaluate(async () => {
172
- await globalThis.toggleExtensionForActiveTab()
173
- })
174
-
175
- const directBrowser = await withTimeout({
176
- promise: chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT })),
177
- timeoutMs: 10000,
178
- errorMessage: 'Timed out connecting over CDP for download reproduction test',
179
- })
180
-
181
- const connectedPage = directBrowser
182
- .contexts()[0]
183
- .pages()
184
- .find((candidatePage) => {
185
- return candidatePage.url() === server.baseUrl + '/'
186
- })
187
- if (!connectedPage) {
188
- throw new Error('Connected page not found for download reproduction test')
189
- }
190
-
191
- const downloadResult = await Promise.all([
192
- connectedPage.waitForEvent('download', { timeout: 3000 }).then(
193
- (download) => {
194
- return { timedOut: false, suggestedFilename: download.suggestedFilename() }
195
- },
196
- (error: Error) => {
197
- return { timedOut: true, errorMessage: error.message }
198
- },
199
- ),
200
- connectedPage.click('#download-button'),
201
- ])
202
-
203
- expect(downloadResult[0]).toMatchInlineSnapshot(`
204
- {
205
- "suggestedFilename": "tabwright-download-test.txt",
206
- "timedOut": false,
207
- }
208
- `)
209
-
210
- await directBrowser.close()
211
- await page.close()
212
- await server.close()
213
-
214
- const logLinesAfter = fs
215
- .readFileSync(logFilePath, 'utf-8')
216
- .split('\n')
217
- .filter((line) => {
218
- return line.trim().length > 0
219
- })
220
- .slice(logLineCountBefore)
221
-
222
- const newEntries = logLinesAfter
223
- .map((line) => {
224
- return tryJsonParse(line)
225
- })
226
- .filter((entry): entry is { direction: string; message: { method?: string } } => {
227
- return Boolean(entry && typeof entry === 'object' && 'direction' in entry && 'message' in entry)
228
- })
229
-
230
- const methods = newEntries
231
- .map((entry) => {
232
- return {
233
- direction: entry.direction,
234
- method: typeof entry.message?.method === 'string' ? entry.message.method : 'response',
235
- }
236
- })
237
- .filter((entry) => {
238
- return (
239
- entry.method.includes('download') ||
240
- entry.method === 'Browser.setDownloadBehavior' ||
241
- entry.method === 'Page.setDownloadBehavior'
242
- )
243
- })
244
-
245
- const summary = {
246
- hasBrowserSetDownloadBehavior: methods.some((entry) => {
247
- return entry.direction === 'from-playwright' && entry.method === 'Browser.setDownloadBehavior'
248
- }),
249
- hasPageSetDownloadBehavior: methods.some((entry) => {
250
- return entry.direction === 'to-extension' && entry.method === 'Page.setDownloadBehavior'
251
- }),
252
- hasPageDownloadWillBegin: methods.some((entry) => {
253
- return entry.method === 'Page.downloadWillBegin'
254
- }),
255
- hasPageDownloadProgress: methods.some((entry) => {
256
- return entry.method === 'Page.downloadProgress'
257
- }),
258
- hasBrowserDownloadWillBegin: methods.some((entry) => {
259
- return entry.method === 'Browser.downloadWillBegin'
260
- }),
261
- hasBrowserDownloadProgress: methods.some((entry) => {
262
- return entry.method === 'Browser.downloadProgress'
263
- }),
264
- }
265
-
266
- expect(summary).toMatchInlineSnapshot(`
267
- {
268
- "hasBrowserDownloadProgress": true,
269
- "hasBrowserDownloadWillBegin": true,
270
- "hasBrowserSetDownloadBehavior": true,
271
- "hasPageDownloadProgress": true,
272
- "hasPageDownloadWillBegin": true,
273
- "hasPageSetDownloadBehavior": true,
274
- }
275
- `)
276
- }, 120000)
277
-
278
- it('should ignore duplicate dialog dismissals from multiple CDP clients', async () => {
279
- const browserContext = getBrowserContext()
280
- const serviceWorker = await getExtensionServiceWorker(browserContext)
281
- const logFilePath = `${process.cwd()}/relay-server.log`
282
- const logLineCountBefore = fs.existsSync(logFilePath)
283
- ? fs
284
- .readFileSync(logFilePath, 'utf-8')
285
- .split('\n')
286
- .filter((line) => {
287
- return line.trim().length > 0
288
- }).length
289
- : 0
290
-
291
- const server = await createSimpleServer({
292
- routes: {
293
- '/': `<!doctype html>
294
- <html>
295
- <body>
296
- <button id="open-dialog">Open dialog</button>
297
- <script>
298
- document.getElementById('open-dialog').addEventListener('click', () => {
299
- alert('shared dialog')
300
- })
301
- </script>
302
- </body>
303
- </html>`,
304
- },
305
- })
306
-
307
- const page = await browserContext.newPage()
308
- let browserA: Awaited<ReturnType<typeof chromium.connectOverCDP>> | null = null
309
- let browserB: Awaited<ReturnType<typeof chromium.connectOverCDP>> | null = null
310
-
311
- try {
312
- await page.goto(server.baseUrl, { waitUntil: 'domcontentloaded' })
313
- await page.bringToFront()
314
-
315
- await serviceWorker.evaluate(async () => {
316
- await globalThis.toggleExtensionForActiveTab()
317
- })
318
-
319
- await new Promise((r) => {
320
- setTimeout(r, 100)
321
- })
322
-
323
- browserA = await withTimeout({
324
- promise: chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT })),
325
- timeoutMs: 10000,
326
- errorMessage: 'Timed out connecting first CDP client for dialog test',
327
- })
328
- browserB = await withTimeout({
329
- promise: chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT })),
330
- timeoutMs: 10000,
331
- errorMessage: 'Timed out connecting second CDP client for dialog test',
332
- })
333
-
334
- const getConnectedPage = (browser: Awaited<ReturnType<typeof chromium.connectOverCDP>>) => {
335
- return browser
336
- .contexts()[0]
337
- ?.pages()
338
- .find((candidatePage) => {
339
- return candidatePage.url() === server.baseUrl + '/'
340
- })
341
- }
342
-
343
- const connectedPageA = getConnectedPage(browserA)
344
- const connectedPageB = getConnectedPage(browserB)
345
-
346
- expect(connectedPageA).toBeDefined()
347
- expect(connectedPageB).toBeDefined()
348
-
349
- await withTimeout({
350
- promise: connectedPageA!.click('#open-dialog'),
351
- timeoutMs: 5000,
352
- errorMessage: 'Timed out opening dialog with first CDP client',
353
- })
354
-
355
- const secondClientResult = await withTimeout({
356
- promise: connectedPageB!.evaluate(() => {
357
- return 2 + 2
358
- }),
359
- timeoutMs: 5000,
360
- errorMessage: 'Second CDP client stopped responding after shared dialog dismissal',
361
- })
362
-
363
- expect(secondClientResult).toBe(4)
364
-
365
- await new Promise((r) => {
366
- setTimeout(r, 200)
367
- })
368
- } finally {
369
- if (browserA) {
370
- await safeCloseCDPBrowser(browserA)
371
- }
372
- if (browserB) {
373
- await safeCloseCDPBrowser(browserB)
374
- }
375
- await page.close()
376
- await server.close()
377
- }
378
-
379
- const logLinesAfter = fs
380
- .readFileSync(logFilePath, 'utf-8')
381
- .split('\n')
382
- .filter((line) => {
383
- return line.trim().length > 0
384
- })
385
- .slice(logLineCountBefore)
386
-
387
- const unexpectedRelayCrashes = logLinesAfter.filter((line) => {
388
- return line.includes('Unhandled Rejection:')
389
- })
390
-
391
- expect(unexpectedRelayCrashes).toEqual([])
392
- }, 60000)
393
-
394
- it('should execute code and capture console output', async () => {
395
- await client.callTool({
396
- name: 'execute',
397
- arguments: {
398
- code: js`
399
- const newPage = await context.newPage();
400
- state.page = newPage;
401
- if (!state.pages) state.pages = [];
402
- state.pages.push(newPage);
403
- `,
404
- },
405
- })
406
-
407
- const result = await client.callTool({
408
- name: 'execute',
409
- arguments: {
410
- code: js`
411
- await state.page.goto('https://example.com');
412
- const title = await state.page.title();
413
- console.log('Page title:', title);
414
- return { url: state.page.url(), title };
415
- `,
416
- },
417
- })
418
- expect(result.content).toMatchInlineSnapshot(`
419
- [
420
- {
421
- "text": "Console output:
422
- [log] Page title: Example Domain
423
-
424
- [return value] { url: 'https://example.com/', title: 'Example Domain' }",
425
- "type": "text",
426
- },
427
- ]
428
- `)
429
- expect(result.content).toBeDefined()
430
- }, 30000)
431
-
432
- // Repro test for https://github.com/remorses/playwriter/issues/66.
433
- // Current limitation: extension-mode routing does not support root-session
434
- // Storage.getCookies in playwriter. MUST use Network.getCookies via page CDP
435
- // session instead (see test below), so this repro stays skipped.
436
- it.skip('should reproduce page.route failure in MCP execute path (issue #66)', async () => {
437
- const server = await createSimpleServer({
438
- routes: {
439
- '/': '<!doctype html><html><body>route issue repro</body></html>',
440
- '/api/data': '{"ok":true}',
441
- },
442
- })
443
-
444
- try {
445
- const result = await client.callTool({
446
- name: 'execute',
447
- arguments: {
448
- code: js`
449
- const newPage = await context.newPage();
450
- state.issue66Page = newPage;
451
- await newPage.goto('${server.baseUrl}', { waitUntil: 'domcontentloaded' });
452
-
453
- let routeFetchError = null;
454
- await newPage.route('**/api/**', async (route) => {
455
- try {
456
- const response = await route.fetch();
457
- await route.fulfill({ response });
458
- } catch (error) {
459
- routeFetchError = error instanceof Error ? error.message : String(error);
460
- await route.abort();
461
- }
462
- });
463
-
464
- await newPage.evaluate(async () => {
465
- await fetch('/api/data').catch(() => null);
466
- });
467
-
468
- return { routeFetchError };
469
- `,
470
- },
471
- })
472
-
473
- const resultWithContent = result as { content?: unknown }
474
- const content = Array.isArray(resultWithContent.content) ? resultWithContent.content : []
475
- const firstContent = content[0]
476
- const output =
477
- typeof firstContent === 'object' && firstContent !== null && 'text' in firstContent
478
- ? String((firstContent as { text?: unknown }).text ?? '')
479
- : ''
480
- expect(output).toContain('routeFetchError')
481
- expect(output).toContain('Storage.getCookies')
482
- expect(output).toContain('No tab found for method Storage.getCookies')
483
- } finally {
484
- try {
485
- await client.callTool({
486
- name: 'execute',
487
- arguments: {
488
- code: js`
489
- if (state.issue66Page && !state.issue66Page.isClosed()) {
490
- await state.issue66Page.close();
491
- }
492
- delete state.issue66Page;
493
- `,
494
- },
495
- })
496
- } catch {
497
- // Ignore cleanup failure if MCP disconnected due to the repro.
498
- }
499
- await server.close()
500
- }
501
- }, 30000)
502
-
503
- it('should read cookies via Network.getCookies through page CDP session', async () => {
504
- const browserContext = getBrowserContext()
505
- const serviceWorker = await getExtensionServiceWorker(browserContext)
506
-
507
- const server = await createSimpleServer({
508
- routes: {
509
- '/': '<!doctype html><html><body>cookies test</body></html>',
510
- },
511
- })
512
-
513
- const page = await browserContext.newPage()
514
- try {
515
- await page.goto(server.baseUrl, { waitUntil: 'domcontentloaded' })
516
- await page.bringToFront()
517
-
518
- await serviceWorker.evaluate(async () => {
519
- await globalThis.toggleExtensionForActiveTab()
520
- })
521
-
522
- await new Promise((r) => {
523
- setTimeout(r, 200)
524
- })
525
-
526
- await page.evaluate(() => {
527
- document.cookie = 'issue66=ok; path=/'
528
- })
529
-
530
- const cdpSession = await getCDPSessionForPage({ page })
531
- const cookiesResult = await cdpSession.send('Network.getCookies', { urls: [page.url()] })
532
- const cookie = cookiesResult.cookies.find((value) => {
533
- return value.name === 'issue66'
534
- })
535
- expect(cookie?.value).toBe('ok')
536
- } finally {
537
- await page.close()
538
- await server.close()
539
- }
540
- }, 30000)
541
-
542
- it('should show extension as connected for pages created via newPage()', async () => {
543
- const browserContext = getBrowserContext()
544
- const serviceWorker = await getExtensionServiceWorker(browserContext)
545
-
546
- // Create a page via MCP (which uses context.newPage())
547
- await client.callTool({
548
- name: 'execute',
549
- arguments: {
550
- code: js`
551
- const newPage = await context.newPage();
552
- state.testPage = newPage;
553
- await newPage.goto('https://example.com/mcp-test');
554
- return newPage.url();
555
- `,
556
- },
557
- })
558
-
559
- // Get extension state to verify the page is marked as connected
560
- const extensionState = await serviceWorker.evaluate(async () => {
561
- const state = globalThis.getExtensionState()
562
- const tabs = await chrome.tabs.query({})
563
- const testTab = tabs.find((t: any) => t.url?.includes('mcp-test'))
564
- return {
565
- connected: !!testTab && !!testTab.id && state.tabs.has(testTab.id),
566
- tabId: testTab?.id,
567
- tabInfo: testTab?.id ? state.tabs.get(testTab.id) : null,
568
- connectionState: state.connectionState,
569
- }
570
- })
571
-
572
- expect(extensionState.connected).toBe(true)
573
- expect(extensionState.tabInfo?.state).toBe('connected')
574
- expect(extensionState.connectionState).toBe('connected')
575
-
576
- // Clean up
577
- await client.callTool({
578
- name: 'execute',
579
- arguments: {
580
- code: js`
581
- if (state.testPage) {
582
- await state.testPage.close();
583
- delete state.testPage;
584
- }
585
- `,
586
- },
587
- })
588
- }, 30000)
589
-
590
- const snapshotTestCases = [
591
- {
592
- name: 'hacker-news',
593
- url: 'https://news.ycombinator.com/item?id=1',
594
- expectedContent: ['role=link', 'Hacker News'],
595
- waitForCode: js`
596
- await state.page.locator('a[href="news"]').first().waitFor({ timeout: 10000 });
597
- await state.page.locator('a.hnuser').first().waitFor({ timeout: 10000 });
598
- `,
599
- },
600
- {
601
- name: 'shadcn-ui',
602
- url: 'https://ui.shadcn.com/',
603
- expectedContent: ['shadcn'],
604
- waitForCode: js`
605
- await state.page.getByRole('heading', { name: 'The Foundation for your Design System' }).waitFor({ timeout: 10000 });
606
- `,
607
- },
608
- ]
609
-
610
- for (const testCase of snapshotTestCases) {
611
- it(`should get accessibility snapshot of ${testCase.name}`, async () => {
612
- await client.callTool({
613
- name: 'execute',
614
- arguments: {
615
- code: js`
616
- const newPage = await context.newPage();
617
- state.page = newPage;
618
- if (!state.pages) state.pages = [];
619
- state.pages.push(newPage);
620
- `,
621
- },
622
- })
623
-
624
- // Capture interactiveOnly=true snapshot (default)
625
- const interactiveResult = await client.callTool({
626
- name: 'execute',
627
- arguments: {
628
- code: js`
629
- // External pages can expose a partial AX tree right after domcontentloaded,
630
- // so wait for stable page-specific content before snapshotting.
631
- await state.page.goto('${testCase.url}', { waitUntil: 'domcontentloaded' });
632
- ${testCase.waitForCode}
633
- const snap = await snapshot({ page: state.page, showDiffSinceLastCall: false, interactiveOnly: true });
634
- return snap;
635
- `,
636
- timeout: 30000,
637
- },
638
- })
639
-
640
- const interactiveData =
641
- typeof interactiveResult === 'object' && interactiveResult.content?.[0]?.text
642
- ? tryJsonParse(interactiveResult.content[0].text)
643
- : interactiveResult
644
- await expect(interactiveData).toMatchFileSnapshot(`snapshots/${testCase.name}-accessibility-interactive.md`)
645
- expect(interactiveResult.content).toBeDefined()
646
- for (const expected of testCase.expectedContent) {
647
- expect(interactiveData).toContain(expected)
648
- }
649
-
650
- // Capture interactiveOnly=false snapshot (full tree)
651
- const fullResult = await client.callTool({
652
- name: 'execute',
653
- arguments: {
654
- code: js`
655
- const snap = await snapshot({ page: state.page, showDiffSinceLastCall: false, interactiveOnly: false });
656
- return snap;
657
- `,
658
- },
659
- })
660
-
661
- const fullData =
662
- typeof fullResult === 'object' && fullResult.content?.[0]?.text
663
- ? tryJsonParse(fullResult.content[0].text)
664
- : fullResult
665
- await expect(fullData).toMatchFileSnapshot(`snapshots/${testCase.name}-accessibility-full.md`)
666
- expect(fullResult.content).toBeDefined()
667
- for (const expected of testCase.expectedContent) {
668
- expect(fullData).toContain(expected)
669
- }
670
- }, 60000)
671
- }
672
-
673
- it('should close all created pages', async () => {
674
- const result = await client.callTool({
675
- name: 'execute',
676
- arguments: {
677
- code: js`
678
- if (state.pages && state.pages.length > 0) {
679
- for (const page of state.pages) {
680
- await page.close();
681
- }
682
- const closedCount = state.pages.length;
683
- state.pages = [];
684
- return { closedCount };
685
- }
686
- return { closedCount: 0 };
687
- `,
688
- },
689
- })
690
- })
691
-
692
- it('should capture browser console logs with getLatestLogs', async () => {
693
- // Ensure clean state and clear any existing logs
694
- const resetResult = await client.callTool({
695
- name: 'execute',
696
- arguments: {
697
- code: js`
698
- // Clear any existing logs from previous tests
699
- clearAllLogs();
700
- console.log('Cleared all existing logs');
701
-
702
- // Verify connection is working
703
- const pages = context.pages();
704
- console.log('Current pages count:', pages.length);
705
-
706
- return { success: true, pagesCount: pages.length };
707
- `,
708
- },
709
- })
710
- console.log('Cleanup result:', resetResult)
711
-
712
- // Create a new page for this test
713
- await client.callTool({
714
- name: 'execute',
715
- arguments: {
716
- code: js`
717
- const newPage = await context.newPage();
718
- state.testLogPage = newPage;
719
- await newPage.goto('about:blank');
720
- `,
721
- },
722
- })
723
-
724
- // Generate some console logs in the browser
725
- await client.callTool({
726
- name: 'execute',
727
- arguments: {
728
- code: js`
729
- await state.testLogPage.evaluate(() => {
730
- console.log('Test log 12345');
731
- console.error('Test error 67890');
732
- console.warn('Test warning 11111');
733
- console.log('Test log 2 with', { data: 'object' });
734
- setTimeout(() => { throw new Error('Test pageerror 22222'); }, 0);
735
- });
736
- // Wait for logs to be captured
737
- await new Promise(resolve => setTimeout(resolve, 100));
738
- `,
739
- },
740
- })
741
-
742
- // Test getting all logs
743
- const allLogsResult = await client.callTool({
744
- name: 'execute',
745
- arguments: {
746
- code: js`
747
- const logs = await getLatestLogs();
748
- logs.forEach(log => console.log(log));
749
- `,
750
- },
751
- })
752
-
753
- const output = (allLogsResult as any).content[0].text
754
- expect(output).toContain('[log] Test log 12345')
755
- expect(output).toContain('[error] Test error 67890')
756
- expect(output).toContain('[warning] Test warning 11111')
757
- expect(output).toContain('[pageerror] Test pageerror 22222')
758
-
759
- // Test filtering by search string
760
- const errorLogsResult = await client.callTool({
761
- name: 'execute',
762
- arguments: {
763
- code: js`
764
- const logs = await getLatestLogs({ search: 'error' });
765
- logs.forEach(log => console.log(log));
766
- `,
767
- },
768
- })
769
-
770
- const errorOutput = (errorLogsResult as any).content[0].text
771
- expect(errorOutput).toContain('[error] Test error 67890')
772
- // With context lines (5 above/below), nearby logs are also included
773
- expect(errorOutput).toContain('[log] Test log 12345')
774
-
775
- // Test that logs persist across page reload
776
- await client.callTool({
777
- name: 'execute',
778
- arguments: {
779
- code: js`
780
- // First add a log before reload
781
- await state.testLogPage.evaluate(() => {
782
- console.log('Before reload 99999');
783
- });
784
- await new Promise(resolve => setTimeout(resolve, 100));
785
- `,
786
- },
787
- })
788
-
789
- // Verify the log exists
790
- const beforeReloadResult = await client.callTool({
791
- name: 'execute',
792
- arguments: {
793
- code: js`
794
- const logs = await getLatestLogs({ page: state.testLogPage });
795
- console.log('Logs before reload:', logs.length);
796
- logs.forEach(log => console.log(log));
797
- `,
798
- },
799
- })
800
-
801
- const beforeReloadOutput = (beforeReloadResult as any).content[0].text
802
- expect(beforeReloadOutput).toContain('[log] Before reload 99999')
803
-
804
- // Reload the page
805
- await client.callTool({
806
- name: 'execute',
807
- arguments: {
808
- code: js`
809
- await state.testLogPage.reload();
810
- await state.testLogPage.evaluate(() => {
811
- console.log('After reload 88888');
812
- });
813
- await new Promise(resolve => setTimeout(resolve, 100));
814
- `,
815
- },
816
- })
817
-
818
- // Check logs after reload - old logs persist (no longer cleared on navigation)
819
- // so both pre- and post-reload logs are present. Use sinceLastCall to get
820
- // only new logs if needed.
821
- const afterReloadResult = await client.callTool({
822
- name: 'execute',
823
- arguments: {
824
- code: js`
825
- const logs = await getLatestLogs({ page: state.testLogPage });
826
- console.log('Logs after reload:', logs.length);
827
- logs.forEach(log => console.log(log));
828
- `,
829
- },
830
- })
831
-
832
- const afterReloadOutput = (afterReloadResult as any).content[0].text
833
- expect(afterReloadOutput).toContain('[log] After reload 88888')
834
- expect(afterReloadOutput).toContain('[log] Before reload 99999')
835
-
836
- // Clean up
837
- await client.callTool({
838
- name: 'execute',
839
- arguments: {
840
- code: js`
841
- await state.testLogPage.close();
842
- delete state.testLogPage;
843
- `,
844
- },
845
- })
846
- }, 30000)
847
-
848
- it('should keep logs separate between different pages', async () => {
849
- // Clear any existing logs from previous tests
850
- await client.callTool({
851
- name: 'execute',
852
- arguments: {
853
- code: js`
854
- clearAllLogs();
855
- console.log('Cleared all existing logs for second log test');
856
- `,
857
- },
858
- })
859
-
860
- // Create two pages
861
- await client.callTool({
862
- name: 'execute',
863
- arguments: {
864
- code: js`
865
- state.pageA = await context.newPage();
866
- state.pageB = await context.newPage();
867
- await state.pageA.goto('about:blank');
868
- await state.pageB.goto('about:blank');
869
- `,
870
- },
871
- })
872
-
873
- // Generate logs in page A
874
- await client.callTool({
875
- name: 'execute',
876
- arguments: {
877
- code: js`
878
- await state.pageA.evaluate(() => {
879
- console.log('PageA log 11111');
880
- console.error('PageA error 22222');
881
- });
882
- await new Promise(resolve => setTimeout(resolve, 100));
883
- `,
884
- },
885
- })
886
-
887
- // Generate logs in page B
888
- await client.callTool({
889
- name: 'execute',
890
- arguments: {
891
- code: js`
892
- await state.pageB.evaluate(() => {
893
- console.log('PageB log 33333');
894
- console.error('PageB error 44444');
895
- });
896
- await new Promise(resolve => setTimeout(resolve, 100));
897
- `,
898
- },
899
- })
900
-
901
- // Check logs for page A - should only have page A logs
902
- const pageALogsResult = await client.callTool({
903
- name: 'execute',
904
- arguments: {
905
- code: js`
906
- const logs = await getLatestLogs({ page: state.pageA });
907
- console.log('Page A logs:', logs.length);
908
- logs.forEach(log => console.log(log));
909
- `,
910
- },
911
- })
912
-
913
- const pageAOutput = (pageALogsResult as any).content[0].text
914
- expect(pageAOutput).toContain('[log] PageA log 11111')
915
- expect(pageAOutput).toContain('[error] PageA error 22222')
916
- expect(pageAOutput).not.toContain('PageB')
917
-
918
- // Check logs for page B - should only have page B logs
919
- const pageBLogsResult = await client.callTool({
920
- name: 'execute',
921
- arguments: {
922
- code: js`
923
- const logs = await getLatestLogs({ page: state.pageB });
924
- console.log('Page B logs:', logs.length);
925
- logs.forEach(log => console.log(log));
926
- `,
927
- },
928
- })
929
-
930
- const pageBOutput = (pageBLogsResult as any).content[0].text
931
- expect(pageBOutput).toContain('[log] PageB log 33333')
932
- expect(pageBOutput).toContain('[error] PageB error 44444')
933
- expect(pageBOutput).not.toContain('PageA')
934
-
935
- // Check all logs - should have logs from both pages
936
- const allLogsResult = await client.callTool({
937
- name: 'execute',
938
- arguments: {
939
- code: js`
940
- const logs = await getLatestLogs();
941
- console.log('All logs:', logs.length);
942
- logs.forEach(log => console.log(log));
943
- `,
944
- },
945
- })
946
-
947
- const allOutput = (allLogsResult as any).content[0].text
948
- expect(allOutput).toContain('[log] PageA log 11111')
949
- expect(allOutput).toContain('[log] PageB log 33333')
950
-
951
- // Test that reloading page A preserves logs (no longer cleared on navigation)
952
- await client.callTool({
953
- name: 'execute',
954
- arguments: {
955
- code: js`
956
- await state.pageA.reload();
957
- await state.pageA.evaluate(() => {
958
- console.log('PageA after reload 55555');
959
- });
960
- await new Promise(resolve => setTimeout(resolve, 100));
961
- `,
962
- },
963
- })
964
-
965
- // Check page A logs - logs persist across navigation, so both old and new are present
966
- const pageAAfterReloadResult = await client.callTool({
967
- name: 'execute',
968
- arguments: {
969
- code: js`
970
- const logs = await getLatestLogs({ page: state.pageA });
971
- console.log('Page A logs after reload:', logs.length);
972
- logs.forEach(log => console.log(log));
973
- `,
974
- },
975
- })
976
-
977
- const pageAAfterReloadOutput = (pageAAfterReloadResult as any).content[0].text
978
- expect(pageAAfterReloadOutput).toContain('[log] PageA after reload 55555')
979
- expect(pageAAfterReloadOutput).toContain('[log] PageA log 11111')
980
-
981
- // Check page B logs - should still have original logs
982
- const pageBAfterAReloadResult = await client.callTool({
983
- name: 'execute',
984
- arguments: {
985
- code: js`
986
- const logs = await getLatestLogs({ page: state.pageB });
987
- console.log('Page B logs after A reload:', logs.length);
988
- logs.forEach(log => console.log(log));
989
- `,
990
- },
991
- })
992
-
993
- const pageBAfterAReloadOutput = (pageBAfterAReloadResult as any).content[0].text
994
- expect(pageBAfterAReloadOutput).toContain('[log] PageB log 33333')
995
- expect(pageBAfterAReloadOutput).toContain('[error] PageB error 44444')
996
-
997
- // Test that logs are deleted when page is closed
998
- await client.callTool({
999
- name: 'execute',
1000
- arguments: {
1001
- code: js`
1002
- // Close page A
1003
- await state.pageA.close();
1004
- await new Promise(resolve => setTimeout(resolve, 100));
1005
- `,
1006
- },
1007
- })
1008
-
1009
- // Check all logs - page A logs should be gone
1010
- const logsAfterCloseResult = await client.callTool({
1011
- name: 'execute',
1012
- arguments: {
1013
- code: js`
1014
- const logs = await getLatestLogs();
1015
- console.log('All logs after closing page A:', logs.length);
1016
- logs.forEach(log => console.log(log));
1017
- `,
1018
- },
1019
- })
1020
-
1021
- const logsAfterCloseOutput = (logsAfterCloseResult as any).content[0].text
1022
- expect(logsAfterCloseOutput).not.toContain('PageA')
1023
- expect(logsAfterCloseOutput).toContain('[log] PageB log 33333')
1024
-
1025
- // Clean up remaining page
1026
- await client.callTool({
1027
- name: 'execute',
1028
- arguments: {
1029
- code: js`
1030
- await state.pageB.close();
1031
- delete state.pageA;
1032
- delete state.pageB;
1033
- `,
1034
- },
1035
- })
1036
- }, 30000)
1037
-
1038
- it('should capture console logs from cross-origin iframes', async () => {
1039
- // Two servers on different ports = different origins
1040
- const iframeServer = await createSimpleServer({
1041
- routes: {
1042
- '/iframe.html': `<!doctype html><html><body>
1043
- <script>
1044
- console.log('iframe-log-ALPHA');
1045
- console.error('iframe-error-BETA');
1046
- console.warn('iframe-warn-GAMMA');
1047
- </script>
1048
- <p>cross-origin iframe</p>
1049
- </body></html>`,
1050
- },
1051
- })
1052
-
1053
- const parentServer = await createSimpleServer({
1054
- routes: {
1055
- '/': `<!doctype html><html><body>
1056
- <script>console.log('parent-log-DELTA');</script>
1057
- <iframe src="${iframeServer.baseUrl}/iframe.html"></iframe>
1058
- </body></html>`,
1059
- },
1060
- })
1061
-
1062
- try {
1063
- // Clear logs and navigate to the parent page with cross-origin iframe
1064
- await client.callTool({
1065
- name: 'execute',
1066
- arguments: {
1067
- code: js`
1068
- clearAllLogs();
1069
- state.iframePage = await context.newPage();
1070
- await state.iframePage.goto('${parentServer.baseUrl}', { waitUntil: 'networkidle' });
1071
- // Wait for iframe to load and logs to be captured
1072
- await state.iframePage.frameLocator('iframe').locator('p').waitFor({ timeout: 5000 });
1073
- await new Promise(resolve => setTimeout(resolve, 500));
1074
- `,
1075
- },
1076
- })
1077
-
1078
- // Retrieve logs and verify both parent and iframe logs are captured
1079
- const logsResult = await client.callTool({
1080
- name: 'execute',
1081
- arguments: {
1082
- code: js`
1083
- const logs = await getLatestLogs({ page: state.iframePage });
1084
- console.log('Cross-origin iframe logs count:', logs.length);
1085
- logs.forEach(log => console.log(log));
1086
- `,
1087
- },
1088
- })
1089
-
1090
- const output = (logsResult as any).content[0].text
1091
- // Parent page log
1092
- expect(output).toContain('parent-log-DELTA')
1093
- // Cross-origin iframe logs
1094
- expect(output).toContain('iframe-log-ALPHA')
1095
- expect(output).toContain('iframe-error-BETA')
1096
- expect(output).toContain('iframe-warn-GAMMA')
1097
-
1098
- // Clean up
1099
- await client.callTool({
1100
- name: 'execute',
1101
- arguments: {
1102
- code: js`
1103
- await state.iframePage.close();
1104
- delete state.iframePage;
1105
- `,
1106
- },
1107
- })
1108
- } finally {
1109
- await Promise.all([parentServer.close(), iframeServer.close()])
1110
- }
1111
- }, 60000)
1112
-
1113
- it(
1114
- 'should preserve system color scheme instead of forcing light mode',
1115
- async () => {
1116
- const browserContext = getBrowserContext()
1117
- const serviceWorker = await getExtensionServiceWorker(browserContext)
1118
-
1119
- const page = await browserContext.newPage()
1120
- await page.goto('https://example.com')
1121
- await page.bringToFront()
1122
-
1123
- // test-utils launches with colorScheme: 'dark', so before MCP connection
1124
- // the browser should report dark mode
1125
- const colorSchemeBefore = await page.evaluate(() => {
1126
- return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
1127
- })
1128
- expect(colorSchemeBefore).toBe('dark')
1129
-
1130
- await serviceWorker.evaluate(async () => {
1131
- await globalThis.toggleExtensionForActiveTab()
1132
- })
1133
- await new Promise((r) => setTimeout(r, 500))
1134
-
1135
- const result = await client.callTool({
1136
- name: 'execute',
1137
- arguments: {
1138
- code: js`
1139
- const pages = context.pages();
1140
- const urls = pages.map(p => p.url());
1141
- const targetPage = pages.find(p => p.url().includes('example.com'));
1142
- if (!targetPage) {
1143
- return { error: 'Page not found', urls };
1144
- }
1145
- const isDark = await targetPage.evaluate(() => window.matchMedia('(prefers-color-scheme: dark)').matches);
1146
- const isLight = await targetPage.evaluate(() => window.matchMedia('(prefers-color-scheme: light)').matches);
1147
- return { matchesDark: isDark, matchesLight: isLight };
1148
- `,
1149
- },
1150
- })
1151
-
1152
- console.log('Color scheme after MCP connection:', result.content)
1153
-
1154
- // After MCP connection, color scheme should NOT be forced to light.
1155
- // The page.ts default is now 'no-override', so the browser's actual
1156
- // color scheme (dark, from test-utils launch config) should be preserved.
1157
- expect(result.content).toMatchInlineSnapshot(`
1158
- [
1159
- {
1160
- "text": "[return value] { matchesDark: true, matchesLight: false }",
1161
- "type": "text",
1162
- },
1163
- ]
1164
- `)
1165
-
1166
- await page.close()
1167
- },
1168
- 60000,
1169
- )
1170
-
1171
- it('should get clean HTML with getCleanHTML', async () => {
1172
- const browserContext = getBrowserContext()
1173
- const serviceWorker = await getExtensionServiceWorker(browserContext)
1174
-
1175
- const page = await browserContext.newPage()
1176
- await page.setContent(`
1177
- <html>
1178
- <head>
1179
- <style>.hidden { display: none; }</style>
1180
- <script>console.log('test')</script>
1181
- </head>
1182
- <body>
1183
- <div class="container" data-testid="main">
1184
- <h1>Hello World</h1>
1185
- <button id="btn" aria-label="Click me">Submit</button>
1186
- <a href="/about" title="About page">About</a>
1187
- <input type="text" placeholder="Enter name" />
1188
- </div>
1189
- </body>
1190
- </html>
1191
- `)
1192
- await page.bringToFront()
1193
-
1194
- await serviceWorker.evaluate(async () => {
1195
- await globalThis.toggleExtensionForActiveTab()
1196
- })
1197
- await new Promise((r) => setTimeout(r, 400))
1198
-
1199
- // Test basic getCleanHTML
1200
- const result = await client.callTool({
1201
- name: 'execute',
1202
- arguments: {
1203
- code: js`
1204
- let testPage;
1205
- for (const p of context.pages()) {
1206
- const html = await p.content();
1207
- if (html.includes('Hello World')) { testPage = p; break; }
1208
- }
1209
- if (!testPage) throw new Error('Test page not found');
1210
- const html = await getCleanHTML({ locator: testPage.locator('body') });
1211
- return html;
1212
- `,
1213
- timeout: 15000,
1214
- },
1215
- })
1216
-
1217
- expect(result.isError).toBeFalsy()
1218
- const text = (result.content as any)[0]?.text || ''
1219
-
1220
- // Inline snapshot of cleaned HTML
1221
- expect(text).toMatchInlineSnapshot(`
1222
- "[return value] <div data-testid="main">
1223
- <h1>Hello World</h1>
1224
- <button aria-label="Click me">Submit</button>
1225
- <a href="/about" title="About page">About</a>
1226
- <input type="text" placeholder="Enter name">
1227
- </div>"
1228
- `)
1229
-
1230
- // Should NOT contain script/style tags (they're removed)
1231
- expect(text).not.toContain('<script')
1232
- expect(text).not.toContain('<style')
1233
- expect(text).not.toContain('console.log')
1234
-
1235
- // Test search functionality
1236
- const searchResult = await client.callTool({
1237
- name: 'execute',
1238
- arguments: {
1239
- code: js`
1240
- let testPage;
1241
- for (const p of context.pages()) {
1242
- const html = await p.content();
1243
- if (html.includes('Hello World')) { testPage = p; break; }
1244
- }
1245
- if (!testPage) throw new Error('Test page not found');
1246
- const html = await getCleanHTML({ locator: testPage, search: /button/i });
1247
- return html;
1248
- `,
1249
- timeout: 15000,
1250
- },
1251
- })
1252
-
1253
- expect(searchResult.isError).toBeFalsy()
1254
- const searchText = (searchResult.content as any)[0]?.text || ''
1255
- expect(searchText).toContain('button')
1256
-
1257
- await page.close()
1258
- }, 60000)
1259
-
1260
- it('should extract page content as markdown with getPageMarkdown', async () => {
1261
- const browserContext = getBrowserContext()
1262
- const serviceWorker = await getExtensionServiceWorker(browserContext)
1263
-
1264
- const page = await browserContext.newPage()
1265
- // Create a realistic article-like page structure
1266
- await page.setContent(`
1267
- <html>
1268
- <head>
1269
- <title>Test Article Title</title>
1270
- <meta name="author" content="John Doe">
1271
- <script>console.log('analytics')</script>
1272
- <style>.nav { background: blue; }</style>
1273
- </head>
1274
- <body>
1275
- <nav class="nav">
1276
- <a href="/">Home</a>
1277
- <a href="/about">About</a>
1278
- </nav>
1279
- <article>
1280
- <h1>Test Article Title</h1>
1281
- <p>This is the first paragraph of the article content.</p>
1282
- <p>This is the second paragraph with more details about the topic.</p>
1283
- <p>The article continues with important information here.</p>
1284
- </article>
1285
- <aside>
1286
- <h3>Related Posts</h3>
1287
- <ul><li>Post 1</li><li>Post 2</li></ul>
1288
- </aside>
1289
- <footer>Copyright 2024</footer>
1290
- </body>
1291
- </html>
1292
- `)
1293
- await page.bringToFront()
1294
-
1295
- await serviceWorker.evaluate(async () => {
1296
- await globalThis.toggleExtensionForActiveTab()
1297
- })
1298
- await new Promise((r) => setTimeout(r, 400))
1299
-
1300
- // Test basic getPageMarkdown
1301
- const result = await client.callTool({
1302
- name: 'execute',
1303
- arguments: {
1304
- code: js`
1305
- let testPage;
1306
- for (const p of context.pages()) {
1307
- const html = await p.content();
1308
- if (html.includes('Test Article Title')) { testPage = p; break; }
1309
- }
1310
- if (!testPage) throw new Error('Test page not found');
1311
- const content = await getPageMarkdown({ page: testPage });
1312
- console.log(content);
1313
- `,
1314
- timeout: 15000,
1315
- },
1316
- })
1317
-
1318
- expect(result.isError).toBeFalsy()
1319
- const text = (result.content as any)[0]?.text || ''
1320
-
1321
- // Snapshot the full output
1322
- await expect(text).toMatchFileSnapshot('./snapshots/page-markdown-output.txt')
1323
-
1324
- // Should contain article content
1325
- expect(text).toContain('Test Article Title')
1326
- expect(text).toContain('first paragraph')
1327
- expect(text).toContain('second paragraph')
1328
-
1329
- // Should NOT contain script/style content
1330
- expect(text).not.toContain('analytics')
1331
- expect(text).not.toContain('background: blue')
1332
-
1333
- // Test search functionality
1334
- const searchResult = await client.callTool({
1335
- name: 'execute',
1336
- arguments: {
1337
- code: js`
1338
- let testPage;
1339
- for (const p of context.pages()) {
1340
- const html = await p.content();
1341
- if (html.includes('Test Article Title')) { testPage = p; break; }
1342
- }
1343
- if (!testPage) throw new Error('Test page not found');
1344
- const content = await getPageMarkdown({ page: testPage, search: /important/i, showDiffSinceLastCall: false });
1345
- return content;
1346
- `,
1347
- timeout: 15000,
1348
- },
1349
- })
1350
-
1351
- expect(searchResult.isError).toBeFalsy()
1352
- const searchText = (searchResult.content as any)[0]?.text || ''
1353
- expect(searchText).toContain('important')
1354
-
1355
- await page.close()
1356
- }, 60000)
1357
-
1358
- it('should handle default page being closed and switch to another available page', async () => {
1359
- // This test verifies that when the default `page` in MCP scope is closed,
1360
- // the MCP automatically switches to another available page instead of failing
1361
- // with cryptic "page closed" errors.
1362
-
1363
- const browserContext = getBrowserContext()
1364
- const serviceWorker = await getExtensionServiceWorker(browserContext)
1365
-
1366
- // 1. Disconnect everything to start fresh
1367
- await serviceWorker.evaluate(async () => {
1368
- await globalThis.disconnectEverything()
1369
- })
1370
- await new Promise((r) => setTimeout(r, 100))
1371
-
1372
- // 2. Create first page and enable extension
1373
- const page1 = await browserContext.newPage()
1374
- await page1.goto('https://example.com/first-page')
1375
- await page1.bringToFront()
1376
-
1377
- await serviceWorker.evaluate(async () => {
1378
- await globalThis.toggleExtensionForActiveTab()
1379
- })
1380
- await new Promise((r) => setTimeout(r, 100))
1381
-
1382
- // 3. Reset MCP to ensure page1 becomes the default page (only page available)
1383
- const resetResult = await client.callTool({
1384
- name: 'reset',
1385
- arguments: {},
1386
- })
1387
- expect((resetResult as any).content[0].text).toContain('Connection reset successfully')
1388
-
1389
- // 4. Verify initial page is accessible via default `page`
1390
- const initialResult = await client.callTool({
1391
- name: 'execute',
1392
- arguments: {
1393
- code: js`
1394
- const url = page.url();
1395
- console.log('Initial page URL:', url);
1396
- return { url };
1397
- `,
1398
- },
1399
- })
1400
- expect((initialResult as any).content[0].text).toContain('first-page')
1401
-
1402
- // 5. Create second page and enable extension
1403
- const page2 = await browserContext.newPage()
1404
- await page2.goto('https://example.com/second-page')
1405
- await page2.bringToFront()
1406
-
1407
- await serviceWorker.evaluate(async () => {
1408
- await globalThis.toggleExtensionForActiveTab()
1409
- })
1410
- await new Promise((r) => setTimeout(r, 100))
1411
-
1412
- // 6. Close the first page (which is the default `page` in MCP scope)
1413
- await page1.close()
1414
- await new Promise((r) => setTimeout(r, 100))
1415
-
1416
- // 7. Execute code via MCP - should NOT fail with "page closed" error
1417
- // Instead, it should automatically switch to the second page
1418
- const afterCloseResult = await client.callTool({
1419
- name: 'execute',
1420
- arguments: {
1421
- code: js`
1422
- const url = page.url();
1423
- console.log('Page URL after close:', url);
1424
- const title = await page.title();
1425
- return { url, title };
1426
- `,
1427
- },
1428
- })
1429
-
1430
- // Should succeed and return the second page's info
1431
- expect((afterCloseResult as any).isError).toBeFalsy()
1432
- const output = (afterCloseResult as any).content[0].text
1433
- expect(output).toContain('second-page')
1434
- expect(output).not.toContain('page closed')
1435
- expect(output).not.toContain('Target closed')
1436
-
1437
- // Cleanup
1438
- await page2.close()
1439
- }, 60000)
1440
-
1441
- it('should show descriptive error when clicking a hidden element', async () => {
1442
- await ensureConnectedTabForExecute()
1443
-
1444
- // Create a fresh page and set content with a collapsed details element
1445
- await client.callTool({
1446
- name: 'execute',
1447
- arguments: {
1448
- code: js`
1449
- state.errorTestPage = await context.newPage();
1450
- await state.errorTestPage.setContent(\`
1451
- <details>
1452
- <summary>Toggle</summary>
1453
- <button id="hidden-btn">Hidden Button</button>
1454
- </details>
1455
- \`);
1456
- `,
1457
- },
1458
- })
1459
- const result = await client.callTool({
1460
- name: 'execute',
1461
- arguments: {
1462
- code: js`
1463
- await state.errorTestPage.click('#hidden-btn', { timeout: 100 });
1464
- `,
1465
- },
1466
- })
1467
- expect(result).toMatchInlineSnapshot(`
1468
- {
1469
- "content": [
1470
- {
1471
- "text": "
1472
- Error executing code: page.click: Timeout 100ms exceeded. Element is not visible — it may be hidden by CSS, inside a collapsed <details>, inactive tab, or closed accordion. Try: interact with the page to reveal it first, or use { force: true } to skip visibility checks
1473
- Call log:
1474
-  - waiting for locator('#hidden-btn')
1475
-  - locator resolved to <button id="hidden-btn">Hidden Button</button>
1476
-  - attempting click action
1477
-  2 × waiting for element to be visible, enabled and stable
1478
-  - element is not visible
1479
-  - retrying click action
1480
-  - waiting 20ms
1481
-  - waiting for element to be visible, enabled and stable
1482
-  - element is not visible
1483
-  - retrying click action
1484
-  - waiting 100ms
1485
- ",
1486
- "type": "text",
1487
- },
1488
- ],
1489
- "isError": true,
1490
- }
1491
- `)
1492
- // Cleanup
1493
- await client.callTool({ name: 'execute', arguments: { code: js`await state.errorTestPage.close(); delete state.errorTestPage;` } })
1494
- }, 30000)
1495
-
1496
- it('should show descriptive error when clicking an element covered by another', async () => {
1497
- await ensureConnectedTabForExecute()
1498
-
1499
- await client.callTool({
1500
- name: 'execute',
1501
- arguments: {
1502
- code: js`
1503
- state.errorTestPage = await context.newPage();
1504
- await state.errorTestPage.setContent(\`
1505
- <div style="position:relative">
1506
- <button id="covered-btn" style="position:absolute;top:0;left:0">Covered</button>
1507
- <div id="overlay" style="position:absolute;top:0;left:0;width:200px;height:200px;background:red;z-index:10">Overlay</div>
1508
- </div>
1509
- \`);
1510
- `,
1511
- },
1512
- })
1513
- const result = await client.callTool({
1514
- name: 'execute',
1515
- arguments: {
1516
- code: js`
1517
- await state.errorTestPage.click('#covered-btn', { timeout: 100 });
1518
- `,
1519
- },
1520
- })
1521
- expect(result).toMatchInlineSnapshot(`
1522
- {
1523
- "content": [
1524
- {
1525
- "text": "
1526
- Error executing code: page.click: Timeout 100ms exceeded. <div id="overlay">Overlay</div> intercepts pointer events
1527
- Call log:
1528
-  - waiting for locator('#covered-btn')
1529
-  - locator resolved to <button id="covered-btn">Covered</button>
1530
-  - attempting click action
1531
-  2 × waiting for element to be visible, enabled and stable
1532
-  - element is visible, enabled and stable
1533
-  - scrolling into view if needed
1534
-  - done scrolling
1535
-  - <div id="overlay">Overlay</div> intercepts pointer events
1536
-  - retrying click action
1537
-  - waiting 20ms
1538
-  - waiting for element to be visible, enabled and stable
1539
-  - element is visible, enabled and stable
1540
-  - scrolling into view if needed
1541
-  - done scrolling
1542
-  - <div id="overlay">Overlay</div> intercepts pointer events
1543
-  - retrying click action
1544
-  - waiting 100ms
1545
- ",
1546
- "type": "text",
1547
- },
1548
- ],
1549
- "isError": true,
1550
- }
1551
- `)
1552
- await client.callTool({ name: 'execute', arguments: { code: js`await state.errorTestPage.close(); delete state.errorTestPage;` } })
1553
- }, 30000)
1554
-
1555
- it('should show descriptive error when clicking a display:none element', async () => {
1556
- await ensureConnectedTabForExecute()
1557
-
1558
- await client.callTool({
1559
- name: 'execute',
1560
- arguments: {
1561
- code: js`
1562
- state.errorTestPage = await context.newPage();
1563
- await state.errorTestPage.setContent('<button id="invisible" style="display:none">Invisible</button>');
1564
- `,
1565
- },
1566
- })
1567
- const result = await client.callTool({
1568
- name: 'execute',
1569
- arguments: {
1570
- code: js`
1571
- await state.errorTestPage.click('#invisible', { timeout: 100 });
1572
- `,
1573
- },
1574
- })
1575
- expect(result).toMatchInlineSnapshot(`
1576
- {
1577
- "content": [
1578
- {
1579
- "text": "
1580
- Error executing code: page.click: Timeout 100ms exceeded. Element is not visible — it may be hidden by CSS, inside a collapsed <details>, inactive tab, or closed accordion. Try: interact with the page to reveal it first, or use { force: true } to skip visibility checks
1581
- Call log:
1582
-  - waiting for locator('#invisible')
1583
-  - locator resolved to <button id="invisible">Invisible</button>
1584
-  - attempting click action
1585
-  2 × waiting for element to be visible, enabled and stable
1586
-  - element is not visible
1587
-  - retrying click action
1588
-  - waiting 20ms
1589
-  - waiting for element to be visible, enabled and stable
1590
-  - element is not visible
1591
-  - retrying click action
1592
-  - waiting 100ms
1593
- ",
1594
- "type": "text",
1595
- },
1596
- ],
1597
- "isError": true,
1598
- }
1599
- `)
1600
- await client.callTool({ name: 'execute', arguments: { code: js`await state.errorTestPage.close(); delete state.errorTestPage;` } })
1601
- }, 30000)
1602
-
1603
- })