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,1017 @@
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 type { Page } from '@xmorse/playwright-core'
5
+ import type { AriaSnapshotNode } from './aria-snapshot.js'
6
+ import path from 'node:path'
7
+ import fs from 'node:fs'
8
+ import os from 'node:os'
9
+ import { imageSize } from 'image-size'
10
+ import { getCdpUrl } from './utils.js'
11
+ import { getCDPSessionForPage } from './cdp-session.js'
12
+ import type { CDPCommand } from './cdp-types.js'
13
+ import { screenshotWithAccessibilityLabels } from './aria-snapshot.js'
14
+ import { setupTestContext, cleanupTestContext, getExtensionServiceWorker, type TestContext, js } from './test-utils.js'
15
+ import './test-declarations.js'
16
+
17
+ const TEST_PORT = 19991
18
+
19
+ describe('Snapshot & Screenshot Tests', () => {
20
+ let client: Awaited<ReturnType<typeof createMCPClient>>['client']
21
+ let cleanup: (() => Promise<void>) | null = null
22
+ let testCtx: TestContext | null = null
23
+
24
+ beforeAll(async () => {
25
+ testCtx = await setupTestContext({ port: TEST_PORT, tempDirPrefix: 'pw-snap-test-', toggleExtension: true })
26
+
27
+ const result = await createMCPClient({ port: TEST_PORT })
28
+ client = result.client
29
+ cleanup = result.cleanup
30
+ }, 600000)
31
+
32
+ afterAll(async () => {
33
+ await cleanupTestContext(testCtx, cleanup)
34
+ cleanup = null
35
+ testCtx = null
36
+ })
37
+
38
+ const getBrowserContext = () => {
39
+ if (!testCtx?.browserContext) throw new Error('Browser not initialized')
40
+ return testCtx.browserContext
41
+ }
42
+
43
+ it('should capture screenshot correctly', async () => {
44
+ const browserContext = getBrowserContext()
45
+ const serviceWorker = await getExtensionServiceWorker(browserContext)
46
+
47
+ const page = await browserContext.newPage()
48
+ await page.goto('https://example.com/')
49
+ await page.bringToFront()
50
+
51
+ await serviceWorker.evaluate(async () => {
52
+ await globalThis.toggleExtensionForActiveTab()
53
+ })
54
+
55
+ await new Promise((r) => setTimeout(r, 100))
56
+
57
+ const capturedCommands: CDPCommand[] = []
58
+ const commandHandler = ({ command }: { clientId: string; command: CDPCommand }) => {
59
+ if (command.method === 'Page.captureScreenshot') {
60
+ capturedCommands.push(command)
61
+ }
62
+ }
63
+ testCtx!.relayServer.on('cdp:command', commandHandler)
64
+
65
+ const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
66
+ const cdpPage = browser
67
+ .contexts()[0]
68
+ .pages()
69
+ .find((p) => p.url().includes('example.com'))
70
+
71
+ expect(cdpPage).toBeDefined()
72
+
73
+ const viewportSize = cdpPage!.viewportSize()
74
+ console.log('Viewport size:', viewportSize)
75
+
76
+ const viewportScreenshot = await cdpPage!.screenshot()
77
+ expect(viewportScreenshot).toBeDefined()
78
+
79
+ const viewportDimensions = imageSize(viewportScreenshot)
80
+ console.log('Viewport screenshot dimensions:', viewportDimensions)
81
+ expect(viewportDimensions.width).toBeGreaterThan(0)
82
+ expect(viewportDimensions.height).toBeGreaterThan(0)
83
+ if (viewportSize) {
84
+ expect(viewportDimensions.width).toBe(viewportSize.width)
85
+ expect(viewportDimensions.height).toBe(viewportSize.height)
86
+ }
87
+
88
+ const fullPageScreenshot = await cdpPage!.screenshot({ fullPage: true })
89
+ expect(fullPageScreenshot).toBeDefined()
90
+
91
+ const fullPageDimensions = imageSize(fullPageScreenshot)
92
+ console.log('Full page screenshot dimensions:', fullPageDimensions)
93
+ expect(fullPageDimensions.width).toBeGreaterThan(0)
94
+ expect(fullPageDimensions.height).toBeGreaterThan(0)
95
+ expect(fullPageDimensions.width).toBeGreaterThanOrEqual(viewportDimensions.width!)
96
+
97
+ testCtx!.relayServer.off('cdp:command', commandHandler)
98
+
99
+ expect(capturedCommands.length).toBe(2)
100
+ expect(
101
+ capturedCommands.map((c) => ({
102
+ method: c.method,
103
+ params: c.params,
104
+ })),
105
+ ).toMatchInlineSnapshot(`
106
+ [
107
+ {
108
+ "method": "Page.captureScreenshot",
109
+ "params": {
110
+ "captureBeyondViewport": false,
111
+ "clip": {
112
+ "height": 720,
113
+ "scale": 1,
114
+ "width": 1280,
115
+ "x": 0,
116
+ "y": 0,
117
+ },
118
+ "format": "png",
119
+ },
120
+ },
121
+ {
122
+ "method": "Page.captureScreenshot",
123
+ "params": {
124
+ "captureBeyondViewport": false,
125
+ "clip": {
126
+ "height": 581,
127
+ "scale": 1,
128
+ "width": 1280,
129
+ "x": 0,
130
+ "y": 0,
131
+ },
132
+ "format": "png",
133
+ },
134
+ },
135
+ ]
136
+ `)
137
+
138
+ const screenshotPath = path.join(os.tmpdir(), 'tabwright-test-screenshot.png')
139
+ fs.writeFileSync(screenshotPath, viewportScreenshot)
140
+ console.log('Screenshot saved to:', screenshotPath)
141
+
142
+ await browser.close()
143
+ await page.close()
144
+ }, 60000)
145
+
146
+ it('should match window.innerWidth/Height without clip param', async () => {
147
+ // Proves that in connectOverCDP mode, Playwright already queries
148
+ // window.innerWidth/innerHeight for viewport screenshots, so passing
149
+ // a manual clip is redundant.
150
+ const browserContext = getBrowserContext()
151
+ const serviceWorker = await getExtensionServiceWorker(browserContext)
152
+
153
+ const page = await browserContext.newPage()
154
+ await page.goto('https://example.com/')
155
+ await page.bringToFront()
156
+
157
+ await serviceWorker.evaluate(async () => {
158
+ await globalThis.toggleExtensionForActiveTab()
159
+ })
160
+ await new Promise((r) => setTimeout(r, 100))
161
+
162
+ const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
163
+ const cdpPage = browser
164
+ .contexts()[0]
165
+ .pages()
166
+ .find((p) => p.url().includes('example.com'))
167
+ expect(cdpPage).toBeDefined()
168
+
169
+ // Get actual browser viewport via JS
170
+ const actualViewport = await cdpPage!.evaluate(() => ({
171
+ width: window.innerWidth,
172
+ height: window.innerHeight,
173
+ }))
174
+ console.log('Actual viewport (window.inner*):', actualViewport)
175
+
176
+ // Plain screenshot with scale:'css', NO clip
177
+ const screenshot = await cdpPage!.screenshot({ scale: 'css' })
178
+ const dimensions = imageSize(screenshot)
179
+ console.log('Screenshot dimensions (no clip):', dimensions)
180
+
181
+ expect(dimensions.width).toBe(actualViewport.width)
182
+ expect(dimensions.height).toBe(actualViewport.height)
183
+
184
+ await browser.close()
185
+ await page.close()
186
+ }, 60000)
187
+
188
+ it('should capture element screenshot with correct coordinates', async () => {
189
+ const browserContext = getBrowserContext()
190
+ const serviceWorker = await getExtensionServiceWorker(browserContext)
191
+
192
+ const target = { x: 200, y: 150, width: 300, height: 100 }
193
+ const scrolledTarget = { x: 100, y: 1500, width: 200, height: 80 }
194
+
195
+ const page = await browserContext.newPage()
196
+ await page.setContent(`
197
+ <html>
198
+ <head>
199
+ <style>
200
+ body { margin: 0; padding: 0; height: 2000px; }
201
+ #target {
202
+ position: absolute;
203
+ top: ${target.y}px;
204
+ left: ${target.x}px;
205
+ width: ${target.width}px;
206
+ height: ${target.height}px;
207
+ background: red;
208
+ }
209
+ #scrolled-target {
210
+ position: absolute;
211
+ top: ${scrolledTarget.y}px;
212
+ left: ${scrolledTarget.x}px;
213
+ width: ${scrolledTarget.width}px;
214
+ height: ${scrolledTarget.height}px;
215
+ background: blue;
216
+ }
217
+ </style>
218
+ </head>
219
+ <body>
220
+ <div id="target">Target Element</div>
221
+ <div id="scrolled-target">Scrolled Target</div>
222
+ </body>
223
+ </html>
224
+ `)
225
+ await page.bringToFront()
226
+
227
+ await serviceWorker.evaluate(async () => {
228
+ await globalThis.toggleExtensionForActiveTab()
229
+ })
230
+
231
+ await new Promise((r) => setTimeout(r, 400))
232
+
233
+ const capturedCommands: CDPCommand[] = []
234
+ const commandHandler = ({ command }: { clientId: string; command: CDPCommand }) => {
235
+ if (command.method === 'Page.captureScreenshot') {
236
+ capturedCommands.push(command)
237
+ }
238
+ }
239
+ testCtx!.relayServer.on('cdp:command', commandHandler)
240
+
241
+ const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
242
+ let cdpPage
243
+ for (const p of browser.contexts()[0].pages()) {
244
+ const html = await p.content()
245
+ if (html.includes('scrolled-target')) {
246
+ cdpPage = p
247
+ break
248
+ }
249
+ }
250
+ expect(cdpPage).toBeDefined()
251
+
252
+ await cdpPage!.locator('#target').screenshot()
253
+
254
+ await cdpPage!.locator('#scrolled-target').screenshot()
255
+
256
+ testCtx!.relayServer.off('cdp:command', commandHandler)
257
+
258
+ expect(capturedCommands.length).toBe(2)
259
+
260
+ const targetCmd = capturedCommands[0]
261
+ expect(targetCmd.method).toBe('Page.captureScreenshot')
262
+ const targetClip = (targetCmd.params as any).clip
263
+ expect(targetClip.x).toBe(target.x)
264
+ expect(targetClip.y).toBe(target.y)
265
+ expect(targetClip.width).toBe(target.width)
266
+ expect(targetClip.height).toBe(target.height)
267
+
268
+ const scrolledCmd = capturedCommands[1]
269
+ expect(scrolledCmd.method).toBe('Page.captureScreenshot')
270
+ const scrolledClip = (scrolledCmd.params as any).clip
271
+ expect(scrolledClip.x).toBe(scrolledTarget.x)
272
+ expect(scrolledClip.y).toBe(scrolledTarget.y)
273
+ expect(scrolledClip.width).toBe(scrolledTarget.width)
274
+ expect(scrolledClip.height).toBe(scrolledTarget.height)
275
+
276
+ await browser.close()
277
+ await page.close()
278
+ }, 60000)
279
+
280
+ it('should get locator string for element using getLocatorStringForElement', async () => {
281
+ const browserContext = getBrowserContext()
282
+ const serviceWorker = await getExtensionServiceWorker(browserContext)
283
+
284
+ const page = await browserContext.newPage()
285
+ await page.setContent(`
286
+ <html>
287
+ <body>
288
+ <button id="test-btn">Click Me</button>
289
+ <input type="text" placeholder="Enter name" />
290
+ </body>
291
+ </html>
292
+ `)
293
+ await page.bringToFront()
294
+
295
+ await serviceWorker.evaluate(async () => {
296
+ await globalThis.toggleExtensionForActiveTab()
297
+ })
298
+
299
+ await new Promise((r) => setTimeout(r, 400))
300
+
301
+ const result = await client.callTool({
302
+ name: 'execute',
303
+ arguments: {
304
+ code: js`
305
+ let testPage;
306
+ for (const p of context.pages()) {
307
+ const html = await p.content();
308
+ if (html.includes('test-btn')) { testPage = p; break; }
309
+ }
310
+ if (!testPage) throw new Error('Test page not found');
311
+ const btn = testPage.locator('#test-btn');
312
+ const locatorString = await getLocatorStringForElement(btn);
313
+ console.log('Locator string:', locatorString);
314
+ const locatorFromString = eval('testPage.' + locatorString);
315
+ const count = await locatorFromString.count();
316
+ console.log('Locator count:', count);
317
+ const text = await locatorFromString.textContent();
318
+ console.log('Locator text:', text);
319
+ `,
320
+ timeout: 30000,
321
+ },
322
+ })
323
+
324
+ expect(result.isError).toBeFalsy()
325
+ const text = (result.content as any)[0]?.text || ''
326
+ expect(text).toContain('Locator string:')
327
+ expect(text).toContain("getByRole('button', { name: 'Click Me' })")
328
+ expect(text).toContain('Locator count:')
329
+ expect(text).toContain('Locator text:')
330
+ expect(text).toContain('Click Me')
331
+
332
+ await page.close()
333
+ }, 60000)
334
+
335
+ it('should get styles for element using getStylesForLocator', async () => {
336
+ const browserContext = getBrowserContext()
337
+ const serviceWorker = await getExtensionServiceWorker(browserContext)
338
+
339
+ const page = await browserContext.newPage()
340
+ await page.setContent(`
341
+ <html>
342
+ <head>
343
+ <style>
344
+ body { font-family: Arial, sans-serif; color: #333; }
345
+ .container { padding: 20px; margin: 10px; }
346
+ #main-btn { background-color: blue; color: white; border-radius: 4px; }
347
+ .btn { padding: 8px 16px; }
348
+ </style>
349
+ </head>
350
+ <body>
351
+ <div class="container">
352
+ <button id="main-btn" class="btn" style="font-weight: bold;">Click Me</button>
353
+ </div>
354
+ </body>
355
+ </html>
356
+ `)
357
+ await page.bringToFront()
358
+
359
+ await serviceWorker.evaluate(async () => {
360
+ await globalThis.toggleExtensionForActiveTab()
361
+ })
362
+
363
+ await new Promise((r) => setTimeout(r, 400))
364
+
365
+ const stylesResult = await client.callTool({
366
+ name: 'execute',
367
+ arguments: {
368
+ code: js`
369
+ let testPage;
370
+ for (const p of context.pages()) {
371
+ const html = await p.content();
372
+ if (html.includes('main-btn')) { testPage = p; break; }
373
+ }
374
+ if (!testPage) throw new Error('Test page not found');
375
+ const btn = testPage.locator('#main-btn');
376
+ const styles = await getStylesForLocator({ locator: btn });
377
+ return styles;
378
+ `,
379
+ timeout: 30000,
380
+ },
381
+ })
382
+
383
+ expect(stylesResult.isError).toBeFalsy()
384
+ const stylesText = (stylesResult.content as any)[0]?.text || ''
385
+ expect(stylesText).toMatchInlineSnapshot(`
386
+ "[return value] {
387
+ element: 'button#main-btn.btn',
388
+ inlineStyle: { 'font-weight': 'bold' },
389
+ rules: [
390
+ {
391
+ selector: '.btn',
392
+ source: null,
393
+ origin: 'regular',
394
+ declarations: {
395
+ padding: '8px 16px',
396
+ 'padding-top': '8px',
397
+ 'padding-right': '16px',
398
+ 'padding-bottom': '8px',
399
+ 'padding-left': '16px'
400
+ },
401
+ inheritedFrom: null
402
+ },
403
+ {
404
+ selector: '#main-btn',
405
+ source: null,
406
+ origin: 'regular',
407
+ declarations: {
408
+ 'background-color': 'blue',
409
+ color: 'white',
410
+ 'border-radius': '4px',
411
+ 'border-top-left-radius': '4px',
412
+ 'border-top-right-radius': '4px',
413
+ 'border-bottom-right-radius': '4px',
414
+ 'border-bottom-left-radius': '4px'
415
+ },
416
+ inheritedFrom: null
417
+ },
418
+ {
419
+ selector: '.container',
420
+ source: null,
421
+ origin: 'regular',
422
+ declarations: {
423
+ padding: '20px',
424
+ margin: '10px',
425
+ 'padding-top': '20px',
426
+ 'padding-right': '20px',
427
+ 'padding-bottom': '20px',
428
+ 'padding-left': '20px',
429
+ 'margin-top': '10px',
430
+ 'margin-right': '10px',
431
+ 'margin-bottom': '10px',
432
+ 'margin-left': '10px'
433
+ },
434
+ inheritedFrom: 'ancestor[1]'
435
+ },
436
+ {
437
+ selector: 'body',
438
+ source: null,
439
+ origin: 'regular',
440
+ declarations: { 'font-family': 'Arial, sans-serif', color: 'rgb(51, 51, 51)' },
441
+ inheritedFrom: 'ancestor[2]'
442
+ }
443
+ ]
444
+ }"
445
+ `)
446
+
447
+ const formattedResult = await client.callTool({
448
+ name: 'execute',
449
+ arguments: {
450
+ code: js`
451
+ let testPage;
452
+ for (const p of context.pages()) {
453
+ const html = await p.content();
454
+ if (html.includes('main-btn')) { testPage = p; break; }
455
+ }
456
+ if (!testPage) throw new Error('Test page not found');
457
+ const btn = testPage.locator('#main-btn');
458
+ const styles = await getStylesForLocator({ locator: btn });
459
+ return formatStylesAsText(styles);
460
+ `,
461
+ timeout: 30000,
462
+ },
463
+ })
464
+
465
+ expect(formattedResult.isError).toBeFalsy()
466
+ const formattedText = (formattedResult.content as any)[0]?.text || ''
467
+ expect(formattedText).toMatchInlineSnapshot(`
468
+ "[return value] Element: button#main-btn.btn
469
+
470
+ Inline styles:
471
+ font-weight: bold
472
+
473
+ Matched rules:
474
+ .btn {
475
+ padding: 8px 16px;
476
+ padding-top: 8px;
477
+ padding-right: 16px;
478
+ padding-bottom: 8px;
479
+ padding-left: 16px;
480
+ }
481
+ #main-btn {
482
+ background-color: blue;
483
+ color: white;
484
+ border-radius: 4px;
485
+ border-top-left-radius: 4px;
486
+ border-top-right-radius: 4px;
487
+ border-bottom-right-radius: 4px;
488
+ border-bottom-left-radius: 4px;
489
+ }
490
+
491
+ Inherited from ancestor[1]:
492
+ .container {
493
+ padding: 20px;
494
+ margin: 10px;
495
+ padding-top: 20px;
496
+ padding-right: 20px;
497
+ padding-bottom: 20px;
498
+ padding-left: 20px;
499
+ margin-top: 10px;
500
+ margin-right: 10px;
501
+ margin-bottom: 10px;
502
+ margin-left: 10px;
503
+ }
504
+
505
+ Inherited from ancestor[2]:
506
+ body {
507
+ font-family: Arial, sans-serif;
508
+ color: rgb(51, 51, 51);
509
+ }"
510
+ `)
511
+
512
+ await page.close()
513
+ }, 60000)
514
+
515
+ it('should return correct layout metrics via CDP', async () => {
516
+ const browserContext = getBrowserContext()
517
+ const serviceWorker = await getExtensionServiceWorker(browserContext)
518
+
519
+ const page = await browserContext.newPage()
520
+ await page.goto('https://example.com/')
521
+ await page.bringToFront()
522
+
523
+ await serviceWorker.evaluate(async () => {
524
+ await globalThis.toggleExtensionForActiveTab()
525
+ })
526
+
527
+ await new Promise((r) => setTimeout(r, 100))
528
+
529
+ const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
530
+ const cdpPage = browser
531
+ .contexts()[0]
532
+ .pages()
533
+ .find((p) => p.url().includes('example.com'))
534
+ expect(cdpPage).toBeDefined()
535
+
536
+ const cdpSession = await getCDPSessionForPage({ page: cdpPage! })
537
+
538
+ const layoutMetrics = await cdpSession.send('Page.getLayoutMetrics')
539
+
540
+ const normalized = {
541
+ cssLayoutViewport: layoutMetrics.cssLayoutViewport,
542
+ cssVisualViewport: layoutMetrics.cssVisualViewport,
543
+ layoutViewport: layoutMetrics.layoutViewport,
544
+ visualViewport: layoutMetrics.visualViewport,
545
+ devicePixelRatio:
546
+ layoutMetrics.cssVisualViewport.clientWidth > 0
547
+ ? layoutMetrics.visualViewport.clientWidth / layoutMetrics.cssVisualViewport.clientWidth
548
+ : 1,
549
+ }
550
+
551
+ expect(normalized).toMatchInlineSnapshot(`
552
+ {
553
+ "cssLayoutViewport": {
554
+ "clientHeight": 720,
555
+ "clientWidth": 1280,
556
+ "pageX": 0,
557
+ "pageY": 0,
558
+ },
559
+ "cssVisualViewport": {
560
+ "clientHeight": 720,
561
+ "clientWidth": 1280,
562
+ "offsetX": 0,
563
+ "offsetY": 0,
564
+ "pageX": 0,
565
+ "pageY": 0,
566
+ "scale": 1,
567
+ "zoom": 1,
568
+ },
569
+ "devicePixelRatio": 1,
570
+ "layoutViewport": {
571
+ "clientHeight": 720,
572
+ "clientWidth": 1280,
573
+ "pageX": 0,
574
+ "pageY": 0,
575
+ },
576
+ "visualViewport": {
577
+ "clientHeight": 720,
578
+ "clientWidth": 1280,
579
+ "offsetX": 0,
580
+ "offsetY": 0,
581
+ "pageX": 0,
582
+ "pageY": 0,
583
+ "scale": 1,
584
+ "zoom": 1,
585
+ },
586
+ }
587
+ `)
588
+
589
+ const windowDpr = await cdpPage!.evaluate(() => (globalThis as any).devicePixelRatio)
590
+ console.log('window.devicePixelRatio:', windowDpr)
591
+ expect(windowDpr).toBe(1)
592
+
593
+ await cdpSession.detach()
594
+ await browser.close()
595
+ await page.close()
596
+ }, 60000)
597
+
598
+ it('should support getExistingCDPSession through the relay (reusing Playwright WS)', async () => {
599
+ const browserContext = getBrowserContext()
600
+ const serviceWorker = await getExtensionServiceWorker(browserContext)
601
+
602
+ const page = await browserContext.newPage()
603
+ await page.goto('https://example.com/')
604
+ await page.bringToFront()
605
+
606
+ await serviceWorker.evaluate(async () => {
607
+ await globalThis.toggleExtensionForActiveTab()
608
+ })
609
+
610
+ await new Promise((r) => setTimeout(r, 100))
611
+
612
+ const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
613
+ const cdpPage = browser
614
+ .contexts()[0]
615
+ .pages()
616
+ .find((p) => p.url().includes('example.com'))
617
+ expect(cdpPage).toBeDefined()
618
+
619
+ // Use the new getCDPSessionForPage which reuses Playwright's internal WS
620
+ const cdpClient = await getCDPSessionForPage({ page: cdpPage! })
621
+
622
+ // Should be able to send CDP commands just like the regular getCDPSessionForPage
623
+ const layoutMetrics = await cdpClient.send('Page.getLayoutMetrics')
624
+ expect(layoutMetrics).toBeDefined()
625
+ const metrics = layoutMetrics as { cssVisualViewport?: { clientWidth?: number } }
626
+ expect(metrics.cssVisualViewport).toBeDefined()
627
+ expect(metrics.cssVisualViewport!.clientWidth).toBeGreaterThan(0)
628
+
629
+ // Test DOM access
630
+ const document = await cdpClient.send('DOM.getDocument')
631
+ expect(document).toBeDefined()
632
+
633
+ await cdpClient.detach()
634
+ await browser.close()
635
+ await page.close()
636
+ }, 60000)
637
+
638
+ it('should get aria ref for locator using getAriaSnapshot', async () => {
639
+ const browserContext = getBrowserContext()
640
+ const serviceWorker = await getExtensionServiceWorker(browserContext)
641
+
642
+ const page = await browserContext.newPage()
643
+ // Use data-testid for stable refs, regular id for the button
644
+ await page.setContent(`
645
+ <html>
646
+ <body>
647
+ <button data-testid="submit-btn">Submit Form</button>
648
+ <a href="/about" data-testid="about-link">About Us</a>
649
+ <input type="text" placeholder="Enter your name" data-testid="name-input" />
650
+ </body>
651
+ </html>
652
+ `)
653
+ await page.bringToFront()
654
+
655
+ await serviceWorker.evaluate(async () => {
656
+ await globalThis.toggleExtensionForActiveTab()
657
+ })
658
+ await new Promise((r) => setTimeout(r, 400))
659
+
660
+ const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
661
+ let cdpPage
662
+ for (const p of browser.contexts()[0].pages()) {
663
+ const html = await p.content()
664
+ if (html.includes('submit-btn')) {
665
+ cdpPage = p
666
+ break
667
+ }
668
+ }
669
+ expect(cdpPage).toBeDefined()
670
+
671
+ const { getAriaSnapshot } = await import('./aria-snapshot.js')
672
+
673
+ const ariaResult = await getAriaSnapshot({
674
+ page: cdpPage!,
675
+ })
676
+
677
+ expect(ariaResult.snapshot).toBeDefined()
678
+ expect(ariaResult.snapshot.length).toBeGreaterThan(0)
679
+ expect(ariaResult.snapshot).toContain('Submit Form')
680
+ // Snapshot lines include Playwright locators for interactive elements
681
+ expect(ariaResult.snapshot).toContain('[data-testid="submit-btn"]')
682
+ expect(ariaResult.snapshot).toContain('[data-testid="about-link"]')
683
+ expect(ariaResult.snapshot).toContain('[data-testid="name-input"]')
684
+
685
+ const flattenNodes = (nodes: AriaSnapshotNode[]): AriaSnapshotNode[] => {
686
+ return nodes.flatMap((node) => {
687
+ return [node, ...flattenNodes(node.children)]
688
+ })
689
+ }
690
+
691
+ const allNodes = flattenNodes(ariaResult.tree)
692
+ const findByLocator = (locator: string) => {
693
+ return allNodes.find((node) => node.locator === locator)
694
+ }
695
+
696
+ const submitNode = findByLocator('[data-testid="submit-btn"]')
697
+ const aboutNode = findByLocator('[data-testid="about-link"]')
698
+ const nameNode = findByLocator('[data-testid="name-input"]')
699
+
700
+ expect(submitNode).toBeDefined()
701
+ expect(aboutNode).toBeDefined()
702
+ expect(nameNode).toBeDefined()
703
+
704
+ const submitLocator = cdpPage!.locator(submitNode!.locator!)
705
+ const aboutLocator = cdpPage!.locator(aboutNode!.locator!)
706
+ const nameLocator = cdpPage!.locator(nameNode!.locator!)
707
+
708
+ expect(await submitLocator.count()).toBe(1)
709
+ expect(await aboutLocator.count()).toBe(1)
710
+ expect(await nameLocator.count()).toBe(1)
711
+
712
+ expect(await submitLocator.textContent()).toBe('Submit Form')
713
+ expect(await aboutLocator.textContent()).toBe('About Us')
714
+ expect(await nameLocator.getAttribute('placeholder')).toBe('Enter your name')
715
+
716
+ expect(ariaResult.refToElement.size).toBeGreaterThan(0)
717
+ console.log('RefToElement map size:', ariaResult.refToElement.size)
718
+ console.log('RefToElement entries:', [...ariaResult.refToElement.entries()])
719
+
720
+ // Verify refs are stable test IDs
721
+ expect(ariaResult.refToElement.has('submit-btn')).toBe(true)
722
+ expect(ariaResult.refToElement.has('about-link')).toBe(true)
723
+ expect(ariaResult.refToElement.has('name-input')).toBe(true)
724
+
725
+ // Use getSelectorForRef to get CSS selector for a ref
726
+ const btnSelector = ariaResult.getSelectorForRef('submit-btn')
727
+ expect(btnSelector).toBeDefined()
728
+ console.log('Button selector:', btnSelector)
729
+
730
+ // Verify the selector works
731
+ const btnViaSelector = cdpPage!.locator(btnSelector!)
732
+ const btnTextViaRef = await btnViaSelector.textContent()
733
+ console.log('Button text via selector:', btnTextViaRef)
734
+ expect(btnTextViaRef).toBe('Submit Form')
735
+
736
+ // Test role and name
737
+ const btnInfo = ariaResult.refToElement.get('submit-btn')
738
+ expect(btnInfo?.role).toBe('button')
739
+ expect(btnInfo?.name).toBe('Submit Form')
740
+
741
+ const linkInfo = ariaResult.refToElement.get('about-link')
742
+ expect(linkInfo?.role).toBe('link')
743
+ expect(linkInfo?.name).toBe('About Us')
744
+
745
+ const inputInfo = ariaResult.refToElement.get('name-input')
746
+ expect(inputInfo?.role).toBe('textbox')
747
+
748
+ await browser.close()
749
+ await page.close()
750
+ }, 60000)
751
+
752
+ it('should show aria ref labels on real pages and save screenshots', async () => {
753
+ const browserContext = getBrowserContext()
754
+ const serviceWorker = await getExtensionServiceWorker(browserContext)
755
+
756
+ const { showAriaRefLabels, hideAriaRefLabels } = await import('./aria-snapshot.js')
757
+
758
+ // Create assets folder for screenshots
759
+ const assetsDir = path.join(path.dirname(new URL(import.meta.url).pathname), 'assets')
760
+ if (!fs.existsSync(assetsDir)) {
761
+ fs.mkdirSync(assetsDir, { recursive: true })
762
+ }
763
+
764
+ const testPages = [
765
+ { name: 'old-reddit', url: 'https://old.reddit.com/' },
766
+ { name: 'hacker-news', url: 'https://news.ycombinator.com/' },
767
+ ]
768
+
769
+ const loadPageWithRetries = async ({ name, url }: { name: string; url: string }) => {
770
+ const page = await browserContext.newPage()
771
+ page.setDefaultNavigationTimeout(60000)
772
+
773
+ const attempts = 2
774
+ let lastError: unknown = null
775
+ for (let attempt = 1; attempt <= attempts; attempt += 1) {
776
+ try {
777
+ console.log(`[labels] opening ${name}: ${url} (attempt ${attempt}/${attempts})`)
778
+ await page.goto(url, { waitUntil: 'load' })
779
+ console.log(`[labels] loaded ${name}: ${page.url()}`)
780
+ return { name, url, page }
781
+ } catch (error) {
782
+ lastError = error
783
+ }
784
+ }
785
+
786
+ await page.close()
787
+ throw new Error(`Failed to load ${name} after ${attempts} attempts`, {
788
+ cause: lastError instanceof Error ? lastError : undefined,
789
+ })
790
+ }
791
+
792
+ const pages = await Promise.all(
793
+ testPages.map((testPage) => {
794
+ return loadPageWithRetries(testPage)
795
+ }),
796
+ )
797
+
798
+ for (const { page } of pages) {
799
+ await page.bringToFront()
800
+ await serviceWorker.evaluate(async () => {
801
+ await globalThis.toggleExtensionForActiveTab()
802
+ })
803
+ }
804
+
805
+ const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
806
+
807
+ const withTimeout = async <T>(label: string, task: () => Promise<T>, timeoutMs: number): Promise<T> => {
808
+ let timeoutId: NodeJS.Timeout | null = null
809
+ const timeoutPromise = new Promise<never>((_, reject) => {
810
+ timeoutId = setTimeout(() => {
811
+ reject(new Error(`Timed out after ${timeoutMs}ms: ${label}`))
812
+ }, timeoutMs)
813
+ })
814
+
815
+ try {
816
+ return await Promise.race([task(), timeoutPromise])
817
+ } finally {
818
+ if (timeoutId) {
819
+ clearTimeout(timeoutId)
820
+ }
821
+ }
822
+ }
823
+
824
+ const wsUrl = getCdpUrl({ port: TEST_PORT })
825
+
826
+ for (const { name, url, page } of pages) {
827
+ console.log(`[labels] start ${name}`)
828
+ const cdpPage = browser
829
+ .contexts()[0]
830
+ .pages()
831
+ .find((p) => p.url().includes(new URL(url).hostname))
832
+
833
+ if (!cdpPage) {
834
+ throw new Error(`Could not find CDP page for ${name}`)
835
+ }
836
+
837
+ console.log(`[labels] show labels ${name}`)
838
+ const { labelCount } = await withTimeout(
839
+ `showAriaRefLabels(${name})`,
840
+ async () => {
841
+ return await showAriaRefLabels({ page: cdpPage })
842
+ },
843
+ 60000,
844
+ )
845
+ console.log(`${name}: ${labelCount} labels shown`)
846
+ expect(labelCount).toBeGreaterThan(0)
847
+
848
+ console.log(`[labels] screenshot ${name}`)
849
+ const screenshot = await withTimeout(
850
+ `screenshot(${name})`,
851
+ async () => {
852
+ return await cdpPage.screenshot({ type: 'png', fullPage: false })
853
+ },
854
+ 30000,
855
+ )
856
+ const screenshotPath = path.join(assetsDir, `aria-labels-${name}.png`)
857
+ fs.writeFileSync(screenshotPath, screenshot)
858
+ console.log(`Screenshot saved: ${screenshotPath}`)
859
+
860
+ console.log(`[labels] count dom labels ${name}`)
861
+ const labelElements = await withTimeout(
862
+ `countLabels(${name})`,
863
+ async () => {
864
+ return await cdpPage.evaluate(() => document.querySelectorAll('.__pw_label__').length)
865
+ },
866
+ 10000,
867
+ )
868
+ expect(labelElements).toBe(labelCount)
869
+
870
+ console.log(`[labels] hide labels ${name}`)
871
+ await withTimeout(
872
+ `hideAriaRefLabels(${name})`,
873
+ async () => {
874
+ await hideAriaRefLabels({ page: cdpPage })
875
+ },
876
+ 10000,
877
+ )
878
+
879
+ const labelsAfterHide = await withTimeout(
880
+ `verifyHide(${name})`,
881
+ async () => {
882
+ return await cdpPage.evaluate(() => document.getElementById('__tabwright_labels__'))
883
+ },
884
+ 10000,
885
+ )
886
+ expect(labelsAfterHide).toBeNull()
887
+
888
+ console.log(`[labels] close page ${name}`)
889
+ await page.close()
890
+ }
891
+
892
+ await browser.close()
893
+ console.log(`Screenshots saved to: ${assetsDir}`)
894
+ }, 180000)
895
+
896
+ it('should take screenshot with accessibility labels via MCP execute tool', async () => {
897
+ const browserContext = getBrowserContext()
898
+ const serviceWorker = await getExtensionServiceWorker(browserContext)
899
+
900
+ const page = await browserContext.newPage()
901
+ await page.setContent(`
902
+ <html>
903
+ <head>
904
+ <style>
905
+ body {
906
+ margin: 0;
907
+ background: #e8f4f8;
908
+ position: relative;
909
+ min-height: 100vh;
910
+ }
911
+ .controls {
912
+ padding: 20px;
913
+ position: relative;
914
+ z-index: 10;
915
+ }
916
+ .grid-marker {
917
+ position: absolute;
918
+ background: rgba(255, 100, 100, 0.3);
919
+ border: 1px solid #ff6464;
920
+ font-size: 10px;
921
+ color: #333;
922
+ display: flex;
923
+ align-items: center;
924
+ justify-content: center;
925
+ }
926
+ .h-marker {
927
+ left: 0;
928
+ width: 100%;
929
+ height: 20px;
930
+ }
931
+ .v-marker {
932
+ top: 0;
933
+ height: 100%;
934
+ width: 20px;
935
+ }
936
+ </style>
937
+ </head>
938
+ <body>
939
+ <div class="controls">
940
+ <button id="submit-btn">Submit Form</button>
941
+ <a href="/about">About Us</a>
942
+ <input type="text" placeholder="Enter your name" />
943
+ </div>
944
+ <!-- Horizontal markers every 200px -->
945
+ <div class="grid-marker h-marker" style="top: 200px;">200px</div>
946
+ <div class="grid-marker h-marker" style="top: 400px;">400px</div>
947
+ <div class="grid-marker h-marker" style="top: 600px;">600px</div>
948
+ <!-- Vertical markers every 200px -->
949
+ <div class="grid-marker v-marker" style="left: 200px;">200</div>
950
+ <div class="grid-marker v-marker" style="left: 400px;">400</div>
951
+ <div class="grid-marker v-marker" style="left: 600px;">600</div>
952
+ <div class="grid-marker v-marker" style="left: 800px;">800</div>
953
+ <div class="grid-marker v-marker" style="left: 1000px;">1000</div>
954
+ <div class="grid-marker v-marker" style="left: 1200px;">1200</div>
955
+ </body>
956
+ </html>
957
+ `)
958
+ await page.bringToFront()
959
+
960
+ await serviceWorker.evaluate(async () => {
961
+ await globalThis.toggleExtensionForActiveTab()
962
+ })
963
+ await new Promise((r) => setTimeout(r, 400))
964
+
965
+ const result = await client.callTool({
966
+ name: 'execute',
967
+ arguments: {
968
+ code: js`
969
+ let testPage;
970
+ for (const p of context.pages()) {
971
+ const html = await p.content();
972
+ if (html.includes('submit-btn')) { testPage = p; break; }
973
+ }
974
+ if (!testPage) throw new Error('Test page not found');
975
+ await screenshotWithAccessibilityLabels({ page: testPage });
976
+ `,
977
+ timeout: 15000,
978
+ },
979
+ })
980
+
981
+ expect(result.isError).toBeFalsy()
982
+
983
+ const content = result.content as any[]
984
+ expect(content.length).toBe(2)
985
+
986
+ const textContent = content.find((c) => c.type === 'text')
987
+ expect(textContent).toBeDefined()
988
+ expect(textContent.text).toContain('Screenshot saved to:')
989
+ expect(textContent.text).toContain('image included below')
990
+ expect(textContent.text).toContain('Accessibility snapshot:')
991
+ expect(textContent.text).toContain('Submit Form')
992
+
993
+ const imageContent = content.find((c) => c.type === 'image')
994
+ expect(imageContent).toBeDefined()
995
+ expect(imageContent.mimeType).toBe('image/png')
996
+ expect(imageContent.data).toBeDefined()
997
+ expect(imageContent.data.length).toBeGreaterThan(100)
998
+
999
+ const buffer = Buffer.from(imageContent.data, 'base64')
1000
+ const dimensions = imageSize(buffer)
1001
+
1002
+ const viewport = await page.evaluate(() => ({
1003
+ innerWidth: window.innerWidth,
1004
+ innerHeight: window.innerHeight,
1005
+ outerWidth: window.outerWidth,
1006
+ outerHeight: window.outerHeight,
1007
+ }))
1008
+ console.log('Screenshot dimensions:', dimensions.width, 'x', dimensions.height)
1009
+ console.log('Window viewport:', viewport)
1010
+
1011
+ expect(dimensions.type).toBe('png')
1012
+ expect(dimensions.width).toBeGreaterThan(0)
1013
+ expect(dimensions.height).toBeGreaterThan(0)
1014
+
1015
+ await page.close()
1016
+ }, 60000)
1017
+ })