@roxybrowser/playwright 2.0.2-beta.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 (424) hide show
  1. package/README.md +115 -0
  2. package/dist/apiRequestContext.d.ts +52 -0
  3. package/dist/apiRequestContext.d.ts.map +1 -0
  4. package/dist/apiRequestContext.js +632 -0
  5. package/dist/apiRequestContext.js.map +1 -0
  6. package/dist/ariaSnapshot.d.ts +38 -0
  7. package/dist/ariaSnapshot.d.ts.map +1 -0
  8. package/dist/ariaSnapshot.js +1137 -0
  9. package/dist/ariaSnapshot.js.map +1 -0
  10. package/dist/assertions.d.ts +2 -0
  11. package/dist/assertions.d.ts.map +1 -0
  12. package/dist/assertions.js +6 -0
  13. package/dist/assertions.js.map +1 -0
  14. package/dist/bin/roxybrowser-mcp.d.ts +3 -0
  15. package/dist/bin/roxybrowser-mcp.d.ts.map +1 -0
  16. package/dist/bin/roxybrowser-mcp.js +7 -0
  17. package/dist/bin/roxybrowser-mcp.js.map +1 -0
  18. package/dist/browser.d.ts +15 -0
  19. package/dist/browser.d.ts.map +1 -0
  20. package/dist/browser.js +67 -0
  21. package/dist/browser.js.map +1 -0
  22. package/dist/browserContext.d.ts +156 -0
  23. package/dist/browserContext.d.ts.map +1 -0
  24. package/dist/browserContext.js +1129 -0
  25. package/dist/browserContext.js.map +1 -0
  26. package/dist/browserContextClock.d.ts +54 -0
  27. package/dist/browserContextClock.d.ts.map +1 -0
  28. package/dist/browserContextClock.js +147 -0
  29. package/dist/browserContextClock.js.map +1 -0
  30. package/dist/browserType.d.ts +16 -0
  31. package/dist/browserType.d.ts.map +1 -0
  32. package/dist/browserType.js +68 -0
  33. package/dist/browserType.js.map +1 -0
  34. package/dist/bundle.d.ts +20 -0
  35. package/dist/bundle.d.ts.map +1 -0
  36. package/dist/bundle.js +20 -0
  37. package/dist/bundle.js.map +1 -0
  38. package/dist/clock.d.ts +43 -0
  39. package/dist/clock.d.ts.map +1 -0
  40. package/dist/clock.js +70 -0
  41. package/dist/clock.js.map +1 -0
  42. package/dist/elementHandle.d.ts +100 -0
  43. package/dist/elementHandle.d.ts.map +1 -0
  44. package/dist/elementHandle.js +423 -0
  45. package/dist/elementHandle.js.map +1 -0
  46. package/dist/errors.d.ts +10 -0
  47. package/dist/errors.d.ts.map +1 -0
  48. package/dist/errors.js +19 -0
  49. package/dist/errors.js.map +1 -0
  50. package/dist/evaluation.d.ts +4 -0
  51. package/dist/evaluation.d.ts.map +1 -0
  52. package/dist/evaluation.js +9 -0
  53. package/dist/evaluation.js.map +1 -0
  54. package/dist/frame.d.ts +139 -0
  55. package/dist/frame.d.ts.map +1 -0
  56. package/dist/frame.js +451 -0
  57. package/dist/frame.js.map +1 -0
  58. package/dist/httpHeaders.d.ts +6 -0
  59. package/dist/httpHeaders.d.ts.map +1 -0
  60. package/dist/httpHeaders.js +23 -0
  61. package/dist/httpHeaders.js.map +1 -0
  62. package/dist/human/controller.d.ts +15 -0
  63. package/dist/human/controller.d.ts.map +1 -0
  64. package/dist/human/controller.js +67 -0
  65. package/dist/human/controller.js.map +1 -0
  66. package/dist/human/profile.d.ts +5 -0
  67. package/dist/human/profile.d.ts.map +1 -0
  68. package/dist/human/profile.js +50 -0
  69. package/dist/human/profile.js.map +1 -0
  70. package/dist/human/types.d.ts +28 -0
  71. package/dist/human/types.d.ts.map +1 -0
  72. package/dist/human/types.js +2 -0
  73. package/dist/human/types.js.map +1 -0
  74. package/dist/index.d.ts +7 -0
  75. package/dist/index.d.ts.map +1 -0
  76. package/dist/index.js +3 -0
  77. package/dist/index.js.map +1 -0
  78. package/dist/inputFiles.d.ts +18 -0
  79. package/dist/inputFiles.d.ts.map +1 -0
  80. package/dist/inputFiles.js +192 -0
  81. package/dist/inputFiles.js.map +1 -0
  82. package/dist/jsHandle.d.ts +32 -0
  83. package/dist/jsHandle.d.ts.map +1 -0
  84. package/dist/jsHandle.js +148 -0
  85. package/dist/jsHandle.js.map +1 -0
  86. package/dist/locator.d.ts +190 -0
  87. package/dist/locator.d.ts.map +1 -0
  88. package/dist/locator.js +883 -0
  89. package/dist/locator.js.map +1 -0
  90. package/dist/locatorSelectors.d.ts +11 -0
  91. package/dist/locatorSelectors.d.ts.map +1 -0
  92. package/dist/locatorSelectors.js +50 -0
  93. package/dist/locatorSelectors.js.map +1 -0
  94. package/dist/mcp/backend/common.d.ts +4 -0
  95. package/dist/mcp/backend/common.d.ts.map +1 -0
  96. package/dist/mcp/backend/common.js +40 -0
  97. package/dist/mcp/backend/common.js.map +1 -0
  98. package/dist/mcp/backend/connect.d.ts +10 -0
  99. package/dist/mcp/backend/connect.d.ts.map +1 -0
  100. package/dist/mcp/backend/connect.js +30 -0
  101. package/dist/mcp/backend/connect.js.map +1 -0
  102. package/dist/mcp/backend/console.d.ts +13 -0
  103. package/dist/mcp/backend/console.d.ts.map +1 -0
  104. package/dist/mcp/backend/console.js +36 -0
  105. package/dist/mcp/backend/console.js.map +1 -0
  106. package/dist/mcp/backend/context.d.ts +27 -0
  107. package/dist/mcp/backend/context.d.ts.map +1 -0
  108. package/dist/mcp/backend/context.js +33 -0
  109. package/dist/mcp/backend/context.js.map +1 -0
  110. package/dist/mcp/backend/dialogs.d.ts +7 -0
  111. package/dist/mcp/backend/dialogs.d.ts.map +1 -0
  112. package/dist/mcp/backend/dialogs.js +24 -0
  113. package/dist/mcp/backend/dialogs.js.map +1 -0
  114. package/dist/mcp/backend/evaluate.d.ts +9 -0
  115. package/dist/mcp/backend/evaluate.d.ts.map +1 -0
  116. package/dist/mcp/backend/evaluate.js +31 -0
  117. package/dist/mcp/backend/evaluate.js.map +1 -0
  118. package/dist/mcp/backend/files.d.ts +9 -0
  119. package/dist/mcp/backend/files.d.ts.map +1 -0
  120. package/dist/mcp/backend/files.js +39 -0
  121. package/dist/mcp/backend/files.js.map +1 -0
  122. package/dist/mcp/backend/keyboard.d.ts +22 -0
  123. package/dist/mcp/backend/keyboard.d.ts.map +1 -0
  124. package/dist/mcp/backend/keyboard.js +67 -0
  125. package/dist/mcp/backend/keyboard.js.map +1 -0
  126. package/dist/mcp/backend/navigate.d.ts +4 -0
  127. package/dist/mcp/backend/navigate.d.ts.map +1 -0
  128. package/dist/mcp/backend/navigate.js +82 -0
  129. package/dist/mcp/backend/navigate.js.map +1 -0
  130. package/dist/mcp/backend/network.d.ts +17 -0
  131. package/dist/mcp/backend/network.d.ts.map +1 -0
  132. package/dist/mcp/backend/network.js +144 -0
  133. package/dist/mcp/backend/network.js.map +1 -0
  134. package/dist/mcp/backend/response.d.ts +26 -0
  135. package/dist/mcp/backend/response.d.ts.map +1 -0
  136. package/dist/mcp/backend/response.js +109 -0
  137. package/dist/mcp/backend/response.js.map +1 -0
  138. package/dist/mcp/backend/runCode.d.ts +6 -0
  139. package/dist/mcp/backend/runCode.d.ts.map +1 -0
  140. package/dist/mcp/backend/runCode.js +20 -0
  141. package/dist/mcp/backend/runCode.js.map +1 -0
  142. package/dist/mcp/backend/screenshot.d.ts +13 -0
  143. package/dist/mcp/backend/screenshot.d.ts.map +1 -0
  144. package/dist/mcp/backend/screenshot.js +37 -0
  145. package/dist/mcp/backend/screenshot.js.map +1 -0
  146. package/dist/mcp/backend/snapshot.d.ts +70 -0
  147. package/dist/mcp/backend/snapshot.d.ts.map +1 -0
  148. package/dist/mcp/backend/snapshot.js +185 -0
  149. package/dist/mcp/backend/snapshot.js.map +1 -0
  150. package/dist/mcp/backend/tab.d.ts +72 -0
  151. package/dist/mcp/backend/tab.d.ts.map +1 -0
  152. package/dist/mcp/backend/tab.js +99 -0
  153. package/dist/mcp/backend/tab.js.map +1 -0
  154. package/dist/mcp/backend/tabs.d.ts +13 -0
  155. package/dist/mcp/backend/tabs.d.ts.map +1 -0
  156. package/dist/mcp/backend/tabs.js +44 -0
  157. package/dist/mcp/backend/tabs.js.map +1 -0
  158. package/dist/mcp/backend/tool.d.ts +52 -0
  159. package/dist/mcp/backend/tool.d.ts.map +1 -0
  160. package/dist/mcp/backend/tool.js +28 -0
  161. package/dist/mcp/backend/tool.js.map +1 -0
  162. package/dist/mcp/backend/tools.d.ts +4 -0
  163. package/dist/mcp/backend/tools.d.ts.map +1 -0
  164. package/dist/mcp/backend/tools.js +30 -0
  165. package/dist/mcp/backend/tools.js.map +1 -0
  166. package/dist/mcp/connectedBrowser.d.ts +3 -0
  167. package/dist/mcp/connectedBrowser.d.ts.map +1 -0
  168. package/dist/mcp/connectedBrowser.js +2153 -0
  169. package/dist/mcp/connectedBrowser.js.map +1 -0
  170. package/dist/mcp/errors.d.ts +6 -0
  171. package/dist/mcp/errors.d.ts.map +1 -0
  172. package/dist/mcp/errors.js +12 -0
  173. package/dist/mcp/errors.js.map +1 -0
  174. package/dist/mcp/format.d.ts +12 -0
  175. package/dist/mcp/format.d.ts.map +1 -0
  176. package/dist/mcp/format.js +48 -0
  177. package/dist/mcp/format.js.map +1 -0
  178. package/dist/mcp/index.d.ts +6 -0
  179. package/dist/mcp/index.d.ts.map +1 -0
  180. package/dist/mcp/index.js +5 -0
  181. package/dist/mcp/index.js.map +1 -0
  182. package/dist/mcp/output.d.ts +7 -0
  183. package/dist/mcp/output.d.ts.map +1 -0
  184. package/dist/mcp/output.js +39 -0
  185. package/dist/mcp/output.js.map +1 -0
  186. package/dist/mcp/runtime.d.ts +122 -0
  187. package/dist/mcp/runtime.d.ts.map +1 -0
  188. package/dist/mcp/runtime.js +524 -0
  189. package/dist/mcp/runtime.js.map +1 -0
  190. package/dist/mcp/schemas.d.ts +61 -0
  191. package/dist/mcp/schemas.d.ts.map +1 -0
  192. package/dist/mcp/schemas.js +41 -0
  193. package/dist/mcp/schemas.js.map +1 -0
  194. package/dist/mcp/server.d.ts +3 -0
  195. package/dist/mcp/server.d.ts.map +1 -0
  196. package/dist/mcp/server.js +117 -0
  197. package/dist/mcp/server.js.map +1 -0
  198. package/dist/mcp/snapshot.d.ts +3 -0
  199. package/dist/mcp/snapshot.d.ts.map +1 -0
  200. package/dist/mcp/snapshot.js +3 -0
  201. package/dist/mcp/snapshot.js.map +1 -0
  202. package/dist/mcp/tool.d.ts +19 -0
  203. package/dist/mcp/tool.d.ts.map +1 -0
  204. package/dist/mcp/tool.js +10 -0
  205. package/dist/mcp/tool.js.map +1 -0
  206. package/dist/mcp/tools/common.d.ts +6 -0
  207. package/dist/mcp/tools/common.d.ts.map +1 -0
  208. package/dist/mcp/tools/common.js +34 -0
  209. package/dist/mcp/tools/common.js.map +1 -0
  210. package/dist/mcp/tools/connect.d.ts +6 -0
  211. package/dist/mcp/tools/connect.d.ts.map +1 -0
  212. package/dist/mcp/tools/connect.js +28 -0
  213. package/dist/mcp/tools/connect.js.map +1 -0
  214. package/dist/mcp/tools/console.d.ts +6 -0
  215. package/dist/mcp/tools/console.d.ts.map +1 -0
  216. package/dist/mcp/tools/console.js +36 -0
  217. package/dist/mcp/tools/console.js.map +1 -0
  218. package/dist/mcp/tools/dialog.d.ts +6 -0
  219. package/dist/mcp/tools/dialog.d.ts.map +1 -0
  220. package/dist/mcp/tools/dialog.js +22 -0
  221. package/dist/mcp/tools/dialog.js.map +1 -0
  222. package/dist/mcp/tools/evaluate.d.ts +6 -0
  223. package/dist/mcp/tools/evaluate.d.ts.map +1 -0
  224. package/dist/mcp/tools/evaluate.js +31 -0
  225. package/dist/mcp/tools/evaluate.js.map +1 -0
  226. package/dist/mcp/tools/form.d.ts +6 -0
  227. package/dist/mcp/tools/form.d.ts.map +1 -0
  228. package/dist/mcp/tools/form.js +129 -0
  229. package/dist/mcp/tools/form.js.map +1 -0
  230. package/dist/mcp/tools/index.d.ts +3 -0
  231. package/dist/mcp/tools/index.d.ts.map +1 -0
  232. package/dist/mcp/tools/index.js +7 -0
  233. package/dist/mcp/tools/index.js.map +1 -0
  234. package/dist/mcp/tools/keyboard.d.ts +3 -0
  235. package/dist/mcp/tools/keyboard.d.ts.map +1 -0
  236. package/dist/mcp/tools/keyboard.js +44 -0
  237. package/dist/mcp/tools/keyboard.js.map +1 -0
  238. package/dist/mcp/tools/mouse.d.ts +3 -0
  239. package/dist/mcp/tools/mouse.d.ts.map +1 -0
  240. package/dist/mcp/tools/mouse.js +89 -0
  241. package/dist/mcp/tools/mouse.js.map +1 -0
  242. package/dist/mcp/tools/navigate.d.ts +6 -0
  243. package/dist/mcp/tools/navigate.d.ts.map +1 -0
  244. package/dist/mcp/tools/navigate.js +74 -0
  245. package/dist/mcp/tools/navigate.js.map +1 -0
  246. package/dist/mcp/tools/network.d.ts +6 -0
  247. package/dist/mcp/tools/network.d.ts.map +1 -0
  248. package/dist/mcp/tools/network.js +142 -0
  249. package/dist/mcp/tools/network.js.map +1 -0
  250. package/dist/mcp/tools/runCode.d.ts +6 -0
  251. package/dist/mcp/tools/runCode.d.ts.map +1 -0
  252. package/dist/mcp/tools/runCode.js +24 -0
  253. package/dist/mcp/tools/runCode.js.map +1 -0
  254. package/dist/mcp/tools/screenshot.d.ts +6 -0
  255. package/dist/mcp/tools/screenshot.d.ts.map +1 -0
  256. package/dist/mcp/tools/screenshot.js +46 -0
  257. package/dist/mcp/tools/screenshot.js.map +1 -0
  258. package/dist/mcp/tools/snapshot.d.ts +6 -0
  259. package/dist/mcp/tools/snapshot.d.ts.map +1 -0
  260. package/dist/mcp/tools/snapshot.js +56 -0
  261. package/dist/mcp/tools/snapshot.js.map +1 -0
  262. package/dist/mcp/tools/tabs.d.ts +6 -0
  263. package/dist/mcp/tools/tabs.d.ts.map +1 -0
  264. package/dist/mcp/tools/tabs.js +34 -0
  265. package/dist/mcp/tools/tabs.js.map +1 -0
  266. package/dist/mcp/transports/http.d.ts +3 -0
  267. package/dist/mcp/transports/http.d.ts.map +1 -0
  268. package/dist/mcp/transports/http.js +134 -0
  269. package/dist/mcp/transports/http.js.map +1 -0
  270. package/dist/mcp/transports/inMemory.d.ts +3 -0
  271. package/dist/mcp/transports/inMemory.d.ts.map +1 -0
  272. package/dist/mcp/transports/inMemory.js +18 -0
  273. package/dist/mcp/transports/inMemory.js.map +1 -0
  274. package/dist/mcp/transports/stdio.d.ts +3 -0
  275. package/dist/mcp/transports/stdio.d.ts.map +1 -0
  276. package/dist/mcp/transports/stdio.js +16 -0
  277. package/dist/mcp/transports/stdio.js.map +1 -0
  278. package/dist/mcp/types.d.ts +193 -0
  279. package/dist/mcp/types.d.ts.map +1 -0
  280. package/dist/mcp/types.js +2 -0
  281. package/dist/mcp/types.js.map +1 -0
  282. package/dist/navigationResult.d.ts +10 -0
  283. package/dist/navigationResult.d.ts.map +1 -0
  284. package/dist/navigationResult.js +15 -0
  285. package/dist/navigationResult.js.map +1 -0
  286. package/dist/page.d.ts +907 -0
  287. package/dist/page.d.ts.map +1 -0
  288. package/dist/page.js +6385 -0
  289. package/dist/page.js.map +1 -0
  290. package/dist/pageApiReport.d.ts +31 -0
  291. package/dist/pageApiReport.d.ts.map +1 -0
  292. package/dist/pageApiReport.js +218 -0
  293. package/dist/pageApiReport.js.map +1 -0
  294. package/dist/pageResponse.d.ts +18 -0
  295. package/dist/pageResponse.d.ts.map +1 -0
  296. package/dist/pageResponse.js +23 -0
  297. package/dist/pageResponse.js.map +1 -0
  298. package/dist/processCleanup.d.ts +8 -0
  299. package/dist/processCleanup.d.ts.map +1 -0
  300. package/dist/processCleanup.js +413 -0
  301. package/dist/processCleanup.js.map +1 -0
  302. package/dist/protocol/adapter.d.ts +406 -0
  303. package/dist/protocol/adapter.d.ts.map +1 -0
  304. package/dist/protocol/adapter.js +8 -0
  305. package/dist/protocol/adapter.js.map +1 -0
  306. package/dist/protocol/bidi/backend.d.ts +8 -0
  307. package/dist/protocol/bidi/backend.d.ts.map +1 -0
  308. package/dist/protocol/bidi/backend.js +3616 -0
  309. package/dist/protocol/bidi/backend.js.map +1 -0
  310. package/dist/protocol/bidi/client.d.ts +104 -0
  311. package/dist/protocol/bidi/client.d.ts.map +1 -0
  312. package/dist/protocol/bidi/client.js +228 -0
  313. package/dist/protocol/bidi/client.js.map +1 -0
  314. package/dist/protocol/capabilities.d.ts +11 -0
  315. package/dist/protocol/capabilities.d.ts.map +1 -0
  316. package/dist/protocol/capabilities.js +2 -0
  317. package/dist/protocol/capabilities.js.map +1 -0
  318. package/dist/protocol/cdp/backend.d.ts +24 -0
  319. package/dist/protocol/cdp/backend.d.ts.map +1 -0
  320. package/dist/protocol/cdp/backend.js +8428 -0
  321. package/dist/protocol/cdp/backend.js.map +1 -0
  322. package/dist/protocol/evaluate.d.ts +2 -0
  323. package/dist/protocol/evaluate.d.ts.map +1 -0
  324. package/dist/protocol/evaluate.js +14 -0
  325. package/dist/protocol/evaluate.js.map +1 -0
  326. package/dist/protocol/evaluationSerializer.d.ts +6 -0
  327. package/dist/protocol/evaluationSerializer.d.ts.map +1 -0
  328. package/dist/protocol/evaluationSerializer.js +234 -0
  329. package/dist/protocol/evaluationSerializer.js.map +1 -0
  330. package/dist/protocol/keyboardInput.d.ts +34 -0
  331. package/dist/protocol/keyboardInput.d.ts.map +1 -0
  332. package/dist/protocol/keyboardInput.js +246 -0
  333. package/dist/protocol/keyboardInput.js.map +1 -0
  334. package/dist/protocol/routing.d.ts +40 -0
  335. package/dist/protocol/routing.d.ts.map +1 -0
  336. package/dist/protocol/routing.js +2 -0
  337. package/dist/protocol/routing.js.map +1 -0
  338. package/dist/protocol/selectorRuntime.d.ts +30 -0
  339. package/dist/protocol/selectorRuntime.d.ts.map +1 -0
  340. package/dist/protocol/selectorRuntime.js +1814 -0
  341. package/dist/protocol/selectorRuntime.js.map +1 -0
  342. package/dist/protocol/webdriver-classic/backend.d.ts +6 -0
  343. package/dist/protocol/webdriver-classic/backend.d.ts.map +1 -0
  344. package/dist/protocol/webdriver-classic/backend.js +158 -0
  345. package/dist/protocol/webdriver-classic/backend.js.map +1 -0
  346. package/dist/routeHandler.d.ts +15 -0
  347. package/dist/routeHandler.d.ts.map +1 -0
  348. package/dist/routeHandler.js +2 -0
  349. package/dist/routeHandler.js.map +1 -0
  350. package/dist/roxybrowser.bundle.js +78236 -0
  351. package/dist/roxybrowser.bundle.js.map +1 -0
  352. package/dist/screencast.d.ts +57 -0
  353. package/dist/screencast.d.ts.map +1 -0
  354. package/dist/screencast.js +155 -0
  355. package/dist/screencast.js.map +1 -0
  356. package/dist/screencastActions.d.ts +2 -0
  357. package/dist/screencastActions.d.ts.map +1 -0
  358. package/dist/screencastActions.js +183 -0
  359. package/dist/screencastActions.js.map +1 -0
  360. package/dist/screencastOverlay.d.ts +3 -0
  361. package/dist/screencastOverlay.d.ts.map +1 -0
  362. package/dist/screencastOverlay.js +119 -0
  363. package/dist/screencastOverlay.js.map +1 -0
  364. package/dist/screenshotOptions.d.ts +26 -0
  365. package/dist/screenshotOptions.d.ts.map +1 -0
  366. package/dist/screenshotOptions.js +171 -0
  367. package/dist/screenshotOptions.js.map +1 -0
  368. package/dist/screenshotPreparation.d.ts +21 -0
  369. package/dist/screenshotPreparation.d.ts.map +1 -0
  370. package/dist/screenshotPreparation.js +199 -0
  371. package/dist/screenshotPreparation.js.map +1 -0
  372. package/dist/selectOptionValues.d.ts +10 -0
  373. package/dist/selectOptionValues.d.ts.map +1 -0
  374. package/dist/selectOptionValues.js +60 -0
  375. package/dist/selectOptionValues.js.map +1 -0
  376. package/dist/selectors.d.ts +3 -0
  377. package/dist/selectors.d.ts.map +1 -0
  378. package/dist/selectors.js +287 -0
  379. package/dist/selectors.js.map +1 -0
  380. package/dist/types/api.d.ts +2164 -0
  381. package/dist/types/api.d.ts.map +1 -0
  382. package/dist/types/api.js +2 -0
  383. package/dist/types/api.js.map +1 -0
  384. package/dist/types/events.d.ts +158 -0
  385. package/dist/types/events.d.ts.map +1 -0
  386. package/dist/types/events.js +2 -0
  387. package/dist/types/events.js.map +1 -0
  388. package/dist/types/options.d.ts +263 -0
  389. package/dist/types/options.d.ts.map +1 -0
  390. package/dist/types/options.js +2 -0
  391. package/dist/types/options.js.map +1 -0
  392. package/dist/urlMatch.d.ts +19 -0
  393. package/dist/urlMatch.d.ts.map +1 -0
  394. package/dist/urlMatch.js +183 -0
  395. package/dist/urlMatch.js.map +1 -0
  396. package/dist/utilityScriptSerializers.d.ts +61 -0
  397. package/dist/utilityScriptSerializers.d.ts.map +1 -0
  398. package/dist/utilityScriptSerializers.js +297 -0
  399. package/dist/utilityScriptSerializers.js.map +1 -0
  400. package/dist/vendor/playwright/ariaSnapshotEvaluate.d.ts +2 -0
  401. package/dist/vendor/playwright/ariaSnapshotEvaluate.d.ts.map +1 -0
  402. package/dist/vendor/playwright/ariaSnapshotEvaluate.js +207 -0
  403. package/dist/vendor/playwright/ariaSnapshotEvaluate.js.map +1 -0
  404. package/dist/vendor/playwright/generated/clockSource.d.ts +9 -0
  405. package/dist/vendor/playwright/generated/clockSource.d.ts.map +1 -0
  406. package/dist/vendor/playwright/generated/clockSource.js +9 -0
  407. package/dist/vendor/playwright/generated/clockSource.js.map +1 -0
  408. package/dist/vendor/playwright/generated/injectedScriptSource.d.ts +9 -0
  409. package/dist/vendor/playwright/generated/injectedScriptSource.d.ts.map +1 -0
  410. package/dist/vendor/playwright/generated/injectedScriptSource.js +9 -0
  411. package/dist/vendor/playwright/generated/injectedScriptSource.js.map +1 -0
  412. package/dist/video.d.ts +39 -0
  413. package/dist/video.d.ts.map +1 -0
  414. package/dist/video.js +235 -0
  415. package/dist/video.js.map +1 -0
  416. package/dist/waitForSelector.d.ts +7 -0
  417. package/dist/waitForSelector.d.ts.map +1 -0
  418. package/dist/waitForSelector.js +23 -0
  419. package/dist/waitForSelector.js.map +1 -0
  420. package/dist/worker.d.ts +45 -0
  421. package/dist/worker.d.ts.map +1 -0
  422. package/dist/worker.js +192 -0
  423. package/dist/worker.js.map +1 -0
  424. package/package.json +82 -0
@@ -0,0 +1,2153 @@
1
+ import { appendFile, mkdir, readFile, stat } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import * as cdpModule from "chrome-remote-interface";
4
+ import { normalizeAriaSnapshotOptions, retryUntilReady } from "../ariaSnapshot.js";
5
+ import { PLAYWRIGHT_ARIA_SNAPSHOT_EVALUATE_SOURCE as ARIA_SNAPSHOT_EVALUATE_SOURCE } from "../vendor/playwright/ariaSnapshotEvaluate.js";
6
+ import { getBidiClientFactory } from "../protocol/bidi/client.js";
7
+ import { McpToolError } from "./errors.js";
8
+ import { ACTION_POINT_EVALUATE_SOURCE, ACTION_POINT_BY_SELECTOR_SOURCE } from "./snapshot.js";
9
+ function delay(ms) {
10
+ return new Promise((resolve) => setTimeout(resolve, ms));
11
+ }
12
+ const chromeRemoteInterface = ("default" in cdpModule
13
+ ? cdpModule.default
14
+ : cdpModule);
15
+ function buildConnectionFromWsEndpoint(browserWsEndpoint) {
16
+ const parsed = new URL(browserWsEndpoint);
17
+ return {
18
+ browserWsEndpoint,
19
+ host: parsed.hostname,
20
+ port: Number(parsed.port)
21
+ };
22
+ }
23
+ async function resolveCdpConnection(endpoint) {
24
+ const parsed = new URL(endpoint);
25
+ if (parsed.protocol === "ws:" || parsed.protocol === "wss:") {
26
+ return buildConnectionFromWsEndpoint(endpoint);
27
+ }
28
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
29
+ throw new McpToolError("unsupported_protocol_input", `CDP endpoint must use http(s) discovery or ws(s) browser websocket. Received "${parsed.protocol}".`);
30
+ }
31
+ const versionUrl = parsed.pathname.endsWith("/json/version")
32
+ ? parsed
33
+ : new URL("/json/version", parsed);
34
+ const response = await fetch(versionUrl);
35
+ if (!response.ok) {
36
+ throw new Error(`Unable to resolve CDP discovery endpoint: ${response.status} ${response.statusText}`);
37
+ }
38
+ const payload = (await response.json());
39
+ if (!payload.webSocketDebuggerUrl) {
40
+ throw new Error("CDP discovery response did not include webSocketDebuggerUrl.");
41
+ }
42
+ return buildConnectionFromWsEndpoint(payload.webSocketDebuggerUrl);
43
+ }
44
+ async function evaluateCdp(client, functionSource, arg, contextId) {
45
+ const expression = arg === undefined
46
+ ? `(${functionSource})()`
47
+ : `(${functionSource})(${JSON.stringify(arg)})`;
48
+ const response = await client.Runtime.evaluate({
49
+ expression,
50
+ returnByValue: true,
51
+ awaitPromise: true,
52
+ ...(contextId !== undefined ? { contextId } : {})
53
+ });
54
+ if (response.exceptionDetails) {
55
+ const description = response.exceptionDetails.exception?.description;
56
+ const value = response.exceptionDetails.exception?.value;
57
+ throw new Error(description
58
+ || (value !== undefined ? String(value) : undefined)
59
+ || response.exceptionDetails.text
60
+ || "CDP runtime evaluation failed.");
61
+ }
62
+ return response.result.value;
63
+ }
64
+ async function evaluateCdpRef(client, functionSource, arg, contextId) {
65
+ const expression = arg === undefined
66
+ ? `(${functionSource})()`
67
+ : `(${functionSource})(${JSON.stringify(arg)})`;
68
+ const response = await client.Runtime.evaluate({
69
+ expression,
70
+ returnByValue: false,
71
+ awaitPromise: true,
72
+ ...(contextId !== undefined ? { contextId } : {})
73
+ });
74
+ if (response.exceptionDetails) {
75
+ const description = response.exceptionDetails.exception?.description;
76
+ const value = response.exceptionDetails.exception?.value;
77
+ throw new Error(description
78
+ || (value !== undefined ? String(value) : undefined)
79
+ || response.exceptionDetails.text
80
+ || "CDP runtime evaluation failed.");
81
+ }
82
+ const objectId = response.result.objectId;
83
+ return objectId !== undefined ? { objectId } : {};
84
+ }
85
+ const FOCUS_AND_GET_ELEMENT_SOURCE = String.raw `(payload) => {
86
+ const state = globalThis.__roxyMcpState;
87
+ const el = payload.nodeToken
88
+ ? (state?.elements?.get(payload.nodeToken) ?? null)
89
+ : document.querySelector(payload.selector);
90
+ if (!el || !el.isConnected) return null;
91
+ el.scrollIntoView({ block: 'nearest', inline: 'nearest' });
92
+ el.focus();
93
+ return el;
94
+ }`;
95
+ const TYPE_INTO_ELEMENT_SOURCE = String.raw `(payload) => {
96
+ const state = globalThis.__roxyMcpState;
97
+ const el = payload.nodeToken
98
+ ? (state?.elements?.get(payload.nodeToken) ?? null)
99
+ : document.querySelector(payload.selector);
100
+ if (!el || !el.isConnected) return { ok: false, reason: 'not_found' };
101
+ el.scrollIntoView({ block: 'nearest', inline: 'nearest' });
102
+ el.focus();
103
+ const tag = el.tagName.toLowerCase();
104
+ if (tag === 'input' || tag === 'textarea') {
105
+ const proto = Object.getPrototypeOf(el);
106
+ const setter = Object.getOwnPropertyDescriptor(proto, 'value')?.set
107
+ ?? Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value')?.set;
108
+ if (setter) setter.call(el, payload.text);
109
+ else el.value = payload.text;
110
+ } else if (el.isContentEditable) {
111
+ el.textContent = payload.text;
112
+ } else {
113
+ return { ok: false, reason: 'not_input' };
114
+ }
115
+ el.dispatchEvent(new Event('input', { bubbles: true }));
116
+ el.dispatchEvent(new Event('change', { bubbles: true }));
117
+ if (payload.submit) {
118
+ el.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', keyCode: 13, bubbles: true }));
119
+ el.dispatchEvent(new KeyboardEvent('keyup', { key: 'Enter', keyCode: 13, bubbles: true }));
120
+ if (el.form && typeof el.form.submit === 'function') el.form.submit();
121
+ else if (el.form) {
122
+ el.form.dispatchEvent(new Event('submit', { bubbles: true, cancelable: true }));
123
+ }
124
+ }
125
+ return { ok: true };
126
+ }`;
127
+ const SELECT_OPTION_SOURCE = String.raw `(payload) => {
128
+ const state = globalThis.__roxyMcpState;
129
+ const el = payload.nodeToken
130
+ ? (state?.elements?.get(payload.nodeToken) ?? null)
131
+ : document.querySelector(payload.selector);
132
+ if (!el || !el.isConnected) return { ok: false, reason: 'not_found', selected: [] };
133
+ if (el.tagName.toLowerCase() !== 'select') return { ok: false, reason: 'not_select', selected: [] };
134
+ const selectEl = el;
135
+ const values = payload.values;
136
+ let matched = false;
137
+ for (const option of selectEl.options) {
138
+ const shouldSelect = values.includes(option.value) || values.includes(option.text);
139
+ if (shouldSelect) {
140
+ option.selected = true;
141
+ matched = true;
142
+ } else if (!selectEl.multiple) {
143
+ option.selected = false;
144
+ }
145
+ }
146
+ if (!matched) {
147
+ // Try partial match
148
+ for (const option of selectEl.options) {
149
+ const shouldSelect = values.some(v => option.value.includes(v) || option.text.includes(v));
150
+ if (shouldSelect) { option.selected = true; matched = true; }
151
+ }
152
+ }
153
+ selectEl.dispatchEvent(new Event('input', { bubbles: true }));
154
+ selectEl.dispatchEvent(new Event('change', { bubbles: true }));
155
+ const selected = Array.from(selectEl.selectedOptions).map(o => o.value);
156
+ return { ok: matched, selected };
157
+ }`;
158
+ const CHECK_ELEMENT_SOURCE = String.raw `(payload) => {
159
+ const state = globalThis.__roxyMcpState;
160
+ const el = payload.nodeToken
161
+ ? (state?.elements?.get(payload.nodeToken) ?? null)
162
+ : document.querySelector(payload.selector);
163
+ if (!el || !el.isConnected) return { ok: false, reason: 'not_found' };
164
+ const tag = el.tagName.toLowerCase();
165
+ const type = el.getAttribute('type')?.toLowerCase();
166
+ if (tag === 'input' && (type === 'checkbox' || type === 'radio')) {
167
+ if (el.checked !== payload.checked) {
168
+ el.click();
169
+ }
170
+ return { ok: true };
171
+ }
172
+ const role = el.getAttribute('role');
173
+ if (role === 'checkbox' || role === 'switch') {
174
+ const current = el.getAttribute('aria-checked') === 'true';
175
+ if (current !== payload.checked) {
176
+ el.click();
177
+ }
178
+ return { ok: true };
179
+ }
180
+ return { ok: false, reason: 'not_checkable' };
181
+ }`;
182
+ const IS_FILE_INPUT_SOURCE = String.raw `(payload) => {
183
+ const state = globalThis.__roxyMcpState;
184
+ const element = payload.nodeToken
185
+ ? state?.elements?.get(payload.nodeToken)
186
+ : document.querySelector(payload.selector);
187
+ return !!element
188
+ && element instanceof HTMLInputElement
189
+ && element.type === 'file';
190
+ }`;
191
+ const SCROLL_ELEMENT_SOURCE = String.raw `(payload) => {
192
+ const state = globalThis.__roxyMcpState;
193
+ const el = payload.nodeToken
194
+ ? (state?.elements?.get(payload.nodeToken) ?? null)
195
+ : payload.selector ? document.querySelector(payload.selector) : null;
196
+ const target = el ?? document.documentElement;
197
+ target.scrollBy({ left: payload.deltaX, top: payload.deltaY, behavior: 'instant' });
198
+ return { ok: true };
199
+ }`;
200
+ const GET_ELEMENT_OBJECT_SOURCE = String.raw `(payload) => {
201
+ const state = globalThis.__roxyMcpState;
202
+ return payload.nodeToken
203
+ ? (state?.elements?.get(payload.nodeToken) ?? null)
204
+ : document.querySelector(payload.selector);
205
+ }`;
206
+ const SET_FORM_FIELD_SOURCE = String.raw `(payload) => {
207
+ const state = globalThis.__roxyMcpState;
208
+ const el = payload.nodeToken
209
+ ? (state?.elements?.get(payload.nodeToken) ?? null)
210
+ : document.querySelector(payload.selector);
211
+ if (!el || !el.isConnected) return { ok: false, reason: 'not_found' };
212
+ const tag = el.tagName.toLowerCase();
213
+ const type = el.getAttribute('type')?.toLowerCase();
214
+ if (payload.fieldType === 'textbox' || payload.fieldType === 'slider') {
215
+ if (tag !== 'input' && tag !== 'textarea' && !el.isContentEditable) return { ok: false, reason: 'not_input' };
216
+ el.focus();
217
+ if (el.isContentEditable) el.textContent = payload.value;
218
+ else {
219
+ const proto = Object.getPrototypeOf(el);
220
+ const setter = Object.getOwnPropertyDescriptor(proto, 'value')?.set
221
+ ?? Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value')?.set;
222
+ if (setter) setter.call(el, payload.value);
223
+ else el.value = payload.value;
224
+ }
225
+ el.dispatchEvent(new Event('input', { bubbles: true }));
226
+ el.dispatchEvent(new Event('change', { bubbles: true }));
227
+ return { ok: true };
228
+ }
229
+ if (payload.fieldType === 'checkbox' || payload.fieldType === 'radio') {
230
+ if (tag !== 'input' || (type !== 'checkbox' && type !== 'radio')) return { ok: false, reason: 'not_checkable' };
231
+ const checked = payload.value === 'true';
232
+ if (el.checked !== checked) el.click();
233
+ return { ok: true };
234
+ }
235
+ if (payload.fieldType === 'combobox') {
236
+ if (tag !== 'select') return { ok: false, reason: 'not_select' };
237
+ let matched = false;
238
+ for (const option of el.options) {
239
+ const shouldSelect = option.value === payload.value || option.text === payload.value;
240
+ option.selected = shouldSelect;
241
+ matched ||= shouldSelect;
242
+ }
243
+ el.dispatchEvent(new Event('input', { bubbles: true }));
244
+ el.dispatchEvent(new Event('change', { bubbles: true }));
245
+ return { ok: matched, reason: matched ? undefined : 'not_found' };
246
+ }
247
+ return { ok: false, reason: 'unsupported' };
248
+ }`;
249
+ const DROP_ON_ELEMENT_SOURCE = String.raw `async (payload) => {
250
+ const state = globalThis.__roxyMcpState;
251
+ const el = payload.nodeToken
252
+ ? (state?.elements?.get(payload.nodeToken) ?? null)
253
+ : document.querySelector(payload.selector);
254
+ if (!el || !el.isConnected) return { ok: false, reason: 'not_found' };
255
+ const dataTransfer = new DataTransfer();
256
+ for (const file of payload.files || []) {
257
+ const bytes = Uint8Array.from(atob(file.buffer), c => c.charCodeAt(0));
258
+ dataTransfer.items.add(new File([bytes], file.name, {
259
+ type: file.mimeType || 'application/octet-stream',
260
+ lastModified: file.lastModifiedMs
261
+ }));
262
+ }
263
+ for (const [type, value] of Object.entries(payload.data || {}))
264
+ dataTransfer.setData(type, value);
265
+ const rect = el.getBoundingClientRect();
266
+ const makeEvent = (type) => new DragEvent(type, {
267
+ bubbles: true,
268
+ cancelable: true,
269
+ composed: true,
270
+ dataTransfer,
271
+ clientX: rect.left + rect.width / 2,
272
+ clientY: rect.top + rect.height / 2
273
+ });
274
+ el.dispatchEvent(makeEvent('dragenter'));
275
+ const dragover = makeEvent('dragover');
276
+ el.dispatchEvent(dragover);
277
+ if (!dragover.defaultPrevented) {
278
+ el.dispatchEvent(makeEvent('dragleave'));
279
+ return { ok: false, reason: 'not_accepted' };
280
+ }
281
+ el.dispatchEvent(makeEvent('drop'));
282
+ return { ok: true };
283
+ }`;
284
+ const ELEMENT_BOX_SOURCE = String.raw `(payload) => {
285
+ const state = globalThis.__roxyMcpState;
286
+ const el = payload.nodeToken
287
+ ? (state?.elements?.get(payload.nodeToken) ?? null)
288
+ : document.querySelector(payload.selector);
289
+ if (!el || !el.isConnected || !(el instanceof Element) || el.getClientRects().length === 0)
290
+ return { ok: false };
291
+ const rect = el.getBoundingClientRect();
292
+ return {
293
+ ok: true,
294
+ x: rect.x,
295
+ y: rect.y,
296
+ width: Math.max(1, rect.width),
297
+ height: Math.max(1, rect.height)
298
+ };
299
+ }`;
300
+ const DOCUMENT_BOX_SOURCE = String.raw `() => {
301
+ const body = document.body;
302
+ const documentElement = document.documentElement;
303
+ const width = Math.max(
304
+ body?.scrollWidth ?? 0,
305
+ body?.offsetWidth ?? 0,
306
+ documentElement?.clientWidth ?? 0,
307
+ documentElement?.scrollWidth ?? 0,
308
+ documentElement?.offsetWidth ?? 0
309
+ );
310
+ const height = Math.max(
311
+ body?.scrollHeight ?? 0,
312
+ body?.offsetHeight ?? 0,
313
+ documentElement?.clientHeight ?? 0,
314
+ documentElement?.scrollHeight ?? 0,
315
+ documentElement?.offsetHeight ?? 0
316
+ );
317
+ return { x: 0, y: 0, width: Math.max(1, width), height: Math.max(1, height) };
318
+ }`;
319
+ const CDP_KEY_MAP = {
320
+ Enter: { key: "Enter", code: "Enter", keyCode: 13, text: "\r" },
321
+ Return: { key: "Enter", code: "Enter", keyCode: 13, text: "\r" },
322
+ Escape: { key: "Escape", code: "Escape", keyCode: 27 },
323
+ Tab: { key: "Tab", code: "Tab", keyCode: 9 },
324
+ Backspace: { key: "Backspace", code: "Backspace", keyCode: 8 },
325
+ Delete: { key: "Delete", code: "Delete", keyCode: 46 },
326
+ ArrowLeft: { key: "ArrowLeft", code: "ArrowLeft", keyCode: 37 },
327
+ ArrowRight: { key: "ArrowRight", code: "ArrowRight", keyCode: 39 },
328
+ ArrowUp: { key: "ArrowUp", code: "ArrowUp", keyCode: 38 },
329
+ ArrowDown: { key: "ArrowDown", code: "ArrowDown", keyCode: 40 },
330
+ Home: { key: "Home", code: "Home", keyCode: 36 },
331
+ End: { key: "End", code: "End", keyCode: 35 },
332
+ PageUp: { key: "PageUp", code: "PageUp", keyCode: 33 },
333
+ PageDown: { key: "PageDown", code: "PageDown", keyCode: 34 },
334
+ Insert: { key: "Insert", code: "Insert", keyCode: 45 },
335
+ Space: { key: " ", code: "Space", keyCode: 32, text: " " },
336
+ F1: { key: "F1", code: "F1", keyCode: 112 },
337
+ F2: { key: "F2", code: "F2", keyCode: 113 },
338
+ F3: { key: "F3", code: "F3", keyCode: 114 },
339
+ F4: { key: "F4", code: "F4", keyCode: 115 },
340
+ F5: { key: "F5", code: "F5", keyCode: 116 },
341
+ F6: { key: "F6", code: "F6", keyCode: 117 },
342
+ F7: { key: "F7", code: "F7", keyCode: 118 },
343
+ F8: { key: "F8", code: "F8", keyCode: 119 },
344
+ F9: { key: "F9", code: "F9", keyCode: 120 },
345
+ F10: { key: "F10", code: "F10", keyCode: 121 },
346
+ F11: { key: "F11", code: "F11", keyCode: 122 },
347
+ F12: { key: "F12", code: "F12", keyCode: 123 },
348
+ };
349
+ async function evaluateBiDi(client, contextId, functionSource, arg) {
350
+ const expression = arg === undefined
351
+ ? `(${functionSource})()`
352
+ : `(${functionSource})(${JSON.stringify(arg)})`;
353
+ const response = (await client.scriptEvaluate({
354
+ expression,
355
+ target: {
356
+ context: contextId
357
+ },
358
+ awaitPromise: true,
359
+ resultOwnership: "none"
360
+ }));
361
+ if (response.type === "exception") {
362
+ throw new Error(response.exceptionDetails?.text || "BiDi runtime evaluation failed.");
363
+ }
364
+ return response.result?.value;
365
+ }
366
+ async function evaluateBiDiRef(client, contextId, functionSource, arg) {
367
+ const expression = arg === undefined
368
+ ? `(${functionSource})()`
369
+ : `(${functionSource})(${JSON.stringify(arg)})`;
370
+ const response = (await client.scriptEvaluate({
371
+ expression,
372
+ target: {
373
+ context: contextId
374
+ },
375
+ awaitPromise: true,
376
+ resultOwnership: "root",
377
+ serializationOptions: {
378
+ maxObjectDepth: 0,
379
+ maxDomDepth: 0
380
+ }
381
+ }));
382
+ if (response.type === "exception") {
383
+ throw new Error(response.exceptionDetails?.text || "BiDi runtime evaluation failed.");
384
+ }
385
+ return {
386
+ ...(response.result?.sharedId !== undefined ? { sharedId: response.result.sharedId } : {}),
387
+ ...(response.result?.handle !== undefined ? { handle: response.result.handle } : {}),
388
+ ...(response.result?.value?.sharedId !== undefined ? { sharedId: response.result.value.sharedId } : {}),
389
+ ...(response.result?.value?.handle !== undefined ? { handle: response.result.value.handle } : {})
390
+ };
391
+ }
392
+ function toAriaSnapshotPayload(request = {}) {
393
+ return {
394
+ options: normalizeAriaSnapshotOptions({
395
+ mode: "ai",
396
+ ...(request.depth !== undefined ? { depth: request.depth } : {}),
397
+ ...(request.boxes !== undefined ? { boxes: request.boxes } : {})
398
+ }),
399
+ ...(request.target ? { target: request.target } : {})
400
+ };
401
+ }
402
+ function toBrowserSnapshot(result, request, extras = {}) {
403
+ if (result.error) {
404
+ const targetLabel = request.target?.raw ?? "target";
405
+ const detailedMessage = result.error.message;
406
+ if (result.error.code === "stale") {
407
+ throw new McpToolError("stale_ref", detailedMessage || `Target "${targetLabel}" is no longer valid. Call "browser_snapshot" again.`);
408
+ }
409
+ if (result.error.code === "invalid_selector" || result.error.code === "strict") {
410
+ throw new McpToolError("invalid_target", detailedMessage || `Target "${targetLabel}" is not a valid selector.`);
411
+ }
412
+ throw new McpToolError("invalid_target", detailedMessage || `Target "${targetLabel}" could not be found in the active tab.`);
413
+ }
414
+ return {
415
+ refs: result.refs,
416
+ text: result.text,
417
+ title: result.title,
418
+ url: result.url,
419
+ ...(extras.console ? { console: extras.console } : {}),
420
+ ...(extras.consoleLink ? { consoleLink: extras.consoleLink } : {})
421
+ };
422
+ }
423
+ function chooseInitialTab(tabs) {
424
+ return tabs.find((tab) => tab.url && tab.url !== "about:blank")?.id ?? tabs[0]?.id;
425
+ }
426
+ class CdpConnectedBrowserSession {
427
+ browserClient;
428
+ connection;
429
+ protocol = "cdp";
430
+ browserName = "chromium";
431
+ pageClients = new Map();
432
+ pageConsoleStates = new Map();
433
+ pageNetworkStates = new Map();
434
+ pageDialogStates = new Map();
435
+ activeTabId;
436
+ versionString = "Chromium/unknown";
437
+ constructor(browserClient, connection) {
438
+ this.browserClient = browserClient;
439
+ this.connection = connection;
440
+ }
441
+ static async connect(args) {
442
+ if (args.browser && args.browser !== "chromium") {
443
+ throw new McpToolError("unsupported_protocol_input", 'CDP attach only supports browser "chromium".');
444
+ }
445
+ const connection = await resolveCdpConnection(args.endpoint);
446
+ const version = await chromeRemoteInterface.Version({
447
+ host: connection.host,
448
+ port: connection.port
449
+ });
450
+ const browserClient = await chromeRemoteInterface({
451
+ target: connection.browserWsEndpoint
452
+ });
453
+ const session = new CdpConnectedBrowserSession(browserClient, connection);
454
+ session.versionString = version.Browser;
455
+ const tabs = await session.refreshTabs();
456
+ if (tabs.length === 0) {
457
+ await session.newTab();
458
+ }
459
+ await session.getActivePageClient().catch(() => undefined);
460
+ return session;
461
+ }
462
+ async version() {
463
+ return this.versionString;
464
+ }
465
+ async listTabs() {
466
+ return this.refreshTabs();
467
+ }
468
+ async newTab(url = "about:blank") {
469
+ const response = await this.browserClient.Target.createTarget({ url });
470
+ this.activeTabId = response.targetId;
471
+ await this.browserClient.Target.activateTarget({
472
+ targetId: response.targetId
473
+ });
474
+ return this.refreshTabs();
475
+ }
476
+ async selectTab(tabId) {
477
+ this.activeTabId = tabId;
478
+ await this.browserClient.Target.activateTarget({
479
+ targetId: tabId
480
+ });
481
+ return this.refreshTabs();
482
+ }
483
+ async closeTab(tabId) {
484
+ const tabsBeforeClose = await this.refreshTabs();
485
+ const index = tabsBeforeClose.findIndex((tab) => tab.id === tabId);
486
+ await this.browserClient.Target.closeTarget({
487
+ targetId: tabId
488
+ });
489
+ const pageClient = this.pageClients.get(tabId);
490
+ if (pageClient) {
491
+ this.pageClients.delete(tabId);
492
+ this.pageConsoleStates.delete(tabId);
493
+ await pageClient.close().catch(() => { });
494
+ }
495
+ const tabsAfterClose = await this.refreshTabs();
496
+ if (tabsAfterClose.length === 0) {
497
+ this.activeTabId = undefined;
498
+ return tabsAfterClose;
499
+ }
500
+ const fallbackIndex = index >= 0 ? Math.min(index, tabsAfterClose.length - 1) : 0;
501
+ this.activeTabId = tabsAfterClose[fallbackIndex]?.id;
502
+ if (this.activeTabId) {
503
+ await this.browserClient.Target.activateTarget({
504
+ targetId: this.activeTabId
505
+ });
506
+ }
507
+ return this.refreshTabs();
508
+ }
509
+ async snapshot(request = {}) {
510
+ const activeTabId = await this.getActiveTabId();
511
+ const pageClient = await this.getActivePageClient();
512
+ const contextId = await this.getActiveUtilityContextId(pageClient);
513
+ const result = await retryUntilReady(() => evaluateCdp(pageClient, ARIA_SNAPSHOT_EVALUATE_SOURCE, toAriaSnapshotPayload(request), contextId));
514
+ return toBrowserSnapshot(result, request, {
515
+ console: this.consoleSummary(activeTabId),
516
+ consoleLink: await this.takeConsoleLink(activeTabId)
517
+ });
518
+ }
519
+ async click(target, options) {
520
+ const pageClient = await this.getActivePageClient();
521
+ const contextId = await this.getActiveUtilityContextId(pageClient);
522
+ const source = "nodeToken" in target ? ACTION_POINT_EVALUATE_SOURCE : ACTION_POINT_BY_SELECTOR_SOURCE;
523
+ const arg = "nodeToken" in target ? { nodeToken: target.nodeToken } : { selector: target.selector };
524
+ const point = await evaluateCdp(pageClient, source, arg, contextId);
525
+ if (!point.ok || point.x === undefined || point.y === undefined) {
526
+ const isSelector = "selector" in target;
527
+ throw new McpToolError(isSelector ? "invalid_target" : "stale_ref", isSelector
528
+ ? `Element "${target.selector}" could not be found or is not visible.`
529
+ : 'The referenced element is no longer valid. Call "browser_snapshot" again.');
530
+ }
531
+ const MODIFIER_BITS = {
532
+ Alt: 1, Control: 2, ControlOrMeta: 2, Meta: 4, Shift: 8
533
+ };
534
+ const modifiersMask = (options.modifiers ?? []).reduce((acc, m) => acc | (MODIFIER_BITS[m] ?? 0), 0);
535
+ const cdpButton = (options.button ?? "left");
536
+ const clickCount = options.doubleClick ? 2 : 1;
537
+ const cycles = options.doubleClick ? 2 : 1;
538
+ await pageClient.Input.dispatchMouseEvent({
539
+ type: "mouseMoved",
540
+ x: point.x,
541
+ y: point.y,
542
+ button: "none",
543
+ modifiers: modifiersMask
544
+ });
545
+ for (let i = 0; i < cycles; i++) {
546
+ await pageClient.Input.dispatchMouseEvent({
547
+ type: "mousePressed",
548
+ x: point.x,
549
+ y: point.y,
550
+ button: cdpButton,
551
+ clickCount,
552
+ modifiers: modifiersMask
553
+ });
554
+ await delay(options.clickHoldMs);
555
+ await pageClient.Input.dispatchMouseEvent({
556
+ type: "mouseReleased",
557
+ x: point.x,
558
+ y: point.y,
559
+ button: cdpButton,
560
+ clickCount,
561
+ modifiers: modifiersMask
562
+ });
563
+ }
564
+ }
565
+ async drag(start, end, options) {
566
+ const pageClient = await this.getActivePageClient();
567
+ const contextId = await this.getActiveUtilityContextId(pageClient);
568
+ const startPoint = await this.actionPoint(pageClient, contextId, start);
569
+ const endPoint = await this.actionPoint(pageClient, contextId, end);
570
+ await pageClient.Input.dispatchMouseEvent({
571
+ type: "mouseMoved",
572
+ x: startPoint.x,
573
+ y: startPoint.y,
574
+ button: "none"
575
+ });
576
+ await delay(options.moveDelayMs);
577
+ await pageClient.Input.dispatchMouseEvent({
578
+ type: "mousePressed",
579
+ x: startPoint.x,
580
+ y: startPoint.y,
581
+ button: "left",
582
+ clickCount: 1
583
+ });
584
+ await delay(options.holdDelayMs);
585
+ await pageClient.Input.dispatchMouseEvent({
586
+ type: "mouseMoved",
587
+ x: endPoint.x,
588
+ y: endPoint.y,
589
+ button: "left"
590
+ });
591
+ await delay(options.moveDelayMs);
592
+ await pageClient.Input.dispatchMouseEvent({
593
+ type: "mouseReleased",
594
+ x: endPoint.x,
595
+ y: endPoint.y,
596
+ button: "left",
597
+ clickCount: 1
598
+ });
599
+ }
600
+ async drop(target, payload) {
601
+ const files = await prepareDropFiles(payload.paths);
602
+ const pageClient = await this.getActivePageClient();
603
+ const contextId = await this.getActiveUtilityContextId(pageClient);
604
+ const result = await evaluateCdp(pageClient, DROP_ON_ELEMENT_SOURCE, { ...this.targetArg(target), data: payload.data ?? {}, files }, contextId);
605
+ if (!result.ok) {
606
+ throw new McpToolError(result.reason === "not_accepted" ? "action_failed" : "invalid_target", result.reason === "not_accepted"
607
+ ? "Drop target did not accept the drop; its dragover handler did not call preventDefault()."
608
+ : "Drop target could not be found.");
609
+ }
610
+ }
611
+ async hover(target) {
612
+ const pageClient = await this.getActivePageClient();
613
+ const contextId = await this.getActiveUtilityContextId(pageClient);
614
+ const source = "nodeToken" in target ? ACTION_POINT_EVALUATE_SOURCE : ACTION_POINT_BY_SELECTOR_SOURCE;
615
+ const arg = "nodeToken" in target ? { nodeToken: target.nodeToken } : { selector: target.selector };
616
+ const point = await evaluateCdp(pageClient, source, arg, contextId);
617
+ if (!point.ok || point.x === undefined || point.y === undefined) {
618
+ const isSelector = "selector" in target;
619
+ throw new McpToolError(isSelector ? "invalid_target" : "stale_ref", isSelector
620
+ ? `Element "${target.selector}" could not be found or is not visible.`
621
+ : 'The referenced element is no longer valid. Call "browser_snapshot" again.');
622
+ }
623
+ await pageClient.Input.dispatchMouseEvent({
624
+ type: "mouseMoved",
625
+ x: point.x,
626
+ y: point.y,
627
+ button: "none"
628
+ });
629
+ }
630
+ async close() {
631
+ await Promise.all(Array.from(this.pageClients.values()).map(async (client) => {
632
+ await client.close().catch(() => { });
633
+ }));
634
+ this.pageClients.clear();
635
+ await this.browserClient.close().catch(() => { });
636
+ }
637
+ async navigate(url) {
638
+ const pageClient = await this.getActivePageClient();
639
+ const tabId = await this.getActiveTabId();
640
+ this.resetConsole(tabId);
641
+ await pageClient.Page.navigate({ url });
642
+ await waitForCdpDocumentReady(pageClient, 5_000);
643
+ }
644
+ async type(target, text, options) {
645
+ const pageClient = await this.getActivePageClient();
646
+ const contextId = await this.getActiveUtilityContextId(pageClient);
647
+ const arg = this.targetArg(target);
648
+ if (options?.slowly || options?.delayMs) {
649
+ const refResult = await evaluateCdpRef(pageClient, FOCUS_AND_GET_ELEMENT_SOURCE, arg, contextId);
650
+ if (!refResult.objectId) {
651
+ const isSelector = "selector" in target;
652
+ throw new McpToolError(isSelector ? "invalid_target" : "stale_ref", isSelector ? `Element "${target.selector}" could not be found.` : "The referenced element is no longer valid.");
653
+ }
654
+ for (const char of text) {
655
+ await pageClient.Input.insertText({ text: char });
656
+ if (options.delayMs) {
657
+ await delay(options.delayMs);
658
+ }
659
+ }
660
+ if (options.submit) {
661
+ await this.pressKey("Enter");
662
+ }
663
+ return;
664
+ }
665
+ const result = await evaluateCdp(pageClient, TYPE_INTO_ELEMENT_SOURCE, { ...arg, text, submit: options?.submit ?? false }, contextId);
666
+ if (!result.ok) {
667
+ const isSelector = "selector" in target;
668
+ throw new McpToolError(isSelector ? "invalid_target" : "stale_ref", result.reason === "not_found"
669
+ ? (isSelector ? `Element "${target.selector}" could not be found.` : 'The referenced element is no longer valid. Call "browser_snapshot" again.')
670
+ : `Element is not a typeable input.`);
671
+ }
672
+ }
673
+ async pressKey(key, modifiers) {
674
+ const pageClient = await this.getActivePageClient();
675
+ const MODIFIER_BITS = {
676
+ Alt: 1, Control: 2, ControlOrMeta: 2, Meta: 4, Shift: 8
677
+ };
678
+ const modifiersMask = (modifiers ?? []).reduce((acc, m) => acc | (MODIFIER_BITS[m] ?? 0), 0);
679
+ const mapped = CDP_KEY_MAP[key];
680
+ if (mapped) {
681
+ await pageClient.Input.dispatchKeyEvent({
682
+ type: "keyDown",
683
+ key: mapped.key,
684
+ code: mapped.code,
685
+ windowsVirtualKeyCode: mapped.keyCode,
686
+ nativeVirtualKeyCode: mapped.keyCode,
687
+ ...(mapped.text ? { text: mapped.text } : {}),
688
+ modifiers: modifiersMask
689
+ });
690
+ await pageClient.Input.dispatchKeyEvent({
691
+ type: "keyUp",
692
+ key: mapped.key,
693
+ code: mapped.code,
694
+ windowsVirtualKeyCode: mapped.keyCode,
695
+ nativeVirtualKeyCode: mapped.keyCode,
696
+ modifiers: modifiersMask
697
+ });
698
+ }
699
+ else {
700
+ // Printable single character
701
+ await pageClient.Input.insertText({ text: key });
702
+ }
703
+ }
704
+ async selectOption(target, values) {
705
+ const pageClient = await this.getActivePageClient();
706
+ const contextId = await this.getActiveUtilityContextId(pageClient);
707
+ const arg = this.targetArg(target);
708
+ const result = await evaluateCdp(pageClient, SELECT_OPTION_SOURCE, { ...arg, values }, contextId);
709
+ if (!result.ok) {
710
+ const isSelector = "selector" in target;
711
+ throw new McpToolError(isSelector ? "invalid_target" : "stale_ref", result.reason === "not_found"
712
+ ? (isSelector ? `Element "${target.selector}" could not be found.` : 'The referenced element is no longer valid.')
713
+ : `Element is not a <select> element.`);
714
+ }
715
+ return result.selected;
716
+ }
717
+ async check(target, checked) {
718
+ const pageClient = await this.getActivePageClient();
719
+ const contextId = await this.getActiveUtilityContextId(pageClient);
720
+ const arg = this.targetArg(target);
721
+ const result = await evaluateCdp(pageClient, CHECK_ELEMENT_SOURCE, { ...arg, checked }, contextId);
722
+ if (!result.ok) {
723
+ const isSelector = "selector" in target;
724
+ throw new McpToolError(isSelector ? "invalid_target" : "stale_ref", result.reason === "not_found"
725
+ ? (isSelector ? `Element "${target.selector}" could not be found.` : 'The referenced element is no longer valid.')
726
+ : `Element is not a checkbox or radio button.`);
727
+ }
728
+ }
729
+ async goBack() {
730
+ const pageClient = await this.getActivePageClient();
731
+ await pageClient.Page.goBack().catch(() => { });
732
+ }
733
+ async goForward() {
734
+ const pageClient = await this.getActivePageClient();
735
+ await pageClient.Page.goForward().catch(() => { });
736
+ }
737
+ async resize(width, height) {
738
+ const pageClient = await this.getActivePageClient();
739
+ await pageClient.Emulation?.setDeviceMetricsOverride({
740
+ mobile: false,
741
+ width,
742
+ height,
743
+ screenWidth: width,
744
+ screenHeight: height,
745
+ deviceScaleFactor: 1
746
+ });
747
+ }
748
+ async scroll(target, deltaX, deltaY) {
749
+ const pageClient = await this.getActivePageClient();
750
+ const contextId = await this.getActiveUtilityContextId(pageClient);
751
+ const arg = target ? this.targetArg(target) : {};
752
+ await evaluateCdp(pageClient, SCROLL_ELEMENT_SOURCE, { ...arg, deltaX, deltaY }, contextId);
753
+ }
754
+ async screenshot(options = {}) {
755
+ const pageClient = await this.getActivePageClient();
756
+ const format = options.type ?? "png";
757
+ const screenshotOptions = { format };
758
+ if (options.target) {
759
+ const refResult = await evaluateCdpRef(pageClient, GET_ELEMENT_OBJECT_SOURCE, this.targetArg(options.target), await this.getActiveUtilityContextId(pageClient));
760
+ if (!refResult.objectId) {
761
+ throw new McpToolError("invalid_target", "Screenshot target could not be found.");
762
+ }
763
+ const model = await pageClient.DOM.getBoxModel({ objectId: refResult.objectId });
764
+ const border = model.model.border;
765
+ const xs = [border[0], border[2], border[4], border[6]].filter((value) => value !== undefined);
766
+ const ys = [border[1], border[3], border[5], border[7]].filter((value) => value !== undefined);
767
+ const x = Math.min(...xs);
768
+ const y = Math.min(...ys);
769
+ screenshotOptions.clip = {
770
+ x,
771
+ y,
772
+ width: Math.max(1, Math.max(...xs) - x),
773
+ height: Math.max(1, Math.max(...ys) - y),
774
+ scale: 1
775
+ };
776
+ }
777
+ else if (options.fullPage) {
778
+ const metrics = await pageClient.send("Page.getLayoutMetrics");
779
+ const contentSize = metrics.cssContentSize ?? metrics.contentSize;
780
+ if (contentSize) {
781
+ screenshotOptions.clip = {
782
+ x: contentSize.x,
783
+ y: contentSize.y,
784
+ width: contentSize.width,
785
+ height: contentSize.height,
786
+ scale: 1
787
+ };
788
+ }
789
+ }
790
+ const result = await pageClient.Page.captureScreenshot(screenshotOptions);
791
+ return { data: result.data, mimeType: format === "png" ? "image/png" : "image/jpeg" };
792
+ }
793
+ async uploadFile(target, filePaths) {
794
+ const pageClient = await this.getActivePageClient();
795
+ const contextId = await this.getActiveUtilityContextId(pageClient);
796
+ const arg = this.targetArg(target);
797
+ const refResult = await evaluateCdpRef(pageClient, GET_ELEMENT_OBJECT_SOURCE, arg, contextId);
798
+ if (!refResult.objectId) {
799
+ const isSelector = "selector" in target;
800
+ throw new McpToolError(isSelector ? "invalid_target" : "stale_ref", isSelector ? `Element "${target.selector}" could not be found.` : 'The referenced element is no longer valid.');
801
+ }
802
+ await pageClient.DOM.setFileInputFiles({ objectId: refResult.objectId, files: filePaths });
803
+ }
804
+ async fillForm(fields) {
805
+ const pageClient = await this.getActivePageClient();
806
+ const contextId = await this.getActiveUtilityContextId(pageClient);
807
+ for (const field of fields) {
808
+ const result = await evaluateCdp(pageClient, SET_FORM_FIELD_SOURCE, {
809
+ ...this.targetArg(field.target),
810
+ fieldType: field.type,
811
+ value: field.value
812
+ }, contextId);
813
+ if (!result.ok) {
814
+ throw new McpToolError("invalid_target", `Unable to fill form field: ${result.reason ?? "unknown error"}.`);
815
+ }
816
+ }
817
+ }
818
+ async handleDialog(accept, promptText) {
819
+ const tabId = await this.getActiveTabId();
820
+ if (!this.pageDialogStates.has(tabId)) {
821
+ throw new McpToolError("no_dialog", "No dialog visible.");
822
+ }
823
+ const pageClient = await this.getActivePageClient();
824
+ this.pageDialogStates.delete(tabId);
825
+ await pageClient.Page.handleJavaScriptDialog({
826
+ accept,
827
+ ...(promptText !== undefined ? { promptText } : {})
828
+ });
829
+ }
830
+ async networkRequests() {
831
+ const tabId = await this.getActiveTabId();
832
+ return this.ensureNetworkState(tabId).requests.map(cloneNetworkRequest);
833
+ }
834
+ async networkRequest(index) {
835
+ const tabId = await this.getActiveTabId();
836
+ const request = this.ensureNetworkState(tabId).requests[index - 1];
837
+ return request ? cloneNetworkRequest(request) : undefined;
838
+ }
839
+ async runCodeUnsafe(code) {
840
+ const pageClient = await this.getActivePageClient();
841
+ const contextId = await this.getActiveUtilityContextId(pageClient);
842
+ return evaluateCdp(pageClient, String.raw `async (payload) => {
843
+ const fn = eval('(' + payload.code + ')');
844
+ if (typeof fn !== 'function') throw new Error('Code must evaluate to a function.');
845
+ const page = {
846
+ evaluate: async (expression, arg) => {
847
+ const value = typeof expression === 'function' ? expression : eval('(' + expression + ')');
848
+ return typeof value === 'function' ? await value(arg) : value;
849
+ },
850
+ title: () => document.title,
851
+ url: () => location.href,
852
+ goto: (url) => { location.href = url; },
853
+ locator: (selector) => ({
854
+ click: () => document.querySelector(selector)?.click(),
855
+ fill: (value) => {
856
+ const el = document.querySelector(selector);
857
+ if (!el) throw new Error('Element not found: ' + selector);
858
+ el.value = value;
859
+ el.dispatchEvent(new Event('input', { bubbles: true }));
860
+ el.dispatchEvent(new Event('change', { bubbles: true }));
861
+ },
862
+ textContent: () => document.querySelector(selector)?.textContent ?? null
863
+ })
864
+ };
865
+ return await fn(page);
866
+ }`, { code }, contextId);
867
+ }
868
+ targetArg(target) {
869
+ return "nodeToken" in target
870
+ ? { nodeToken: target.nodeToken }
871
+ : { selector: target.selector };
872
+ }
873
+ async actionPoint(client, contextId, target) {
874
+ const source = "nodeToken" in target ? ACTION_POINT_EVALUATE_SOURCE : ACTION_POINT_BY_SELECTOR_SOURCE;
875
+ const point = await evaluateCdp(client, source, this.targetArg(target), contextId);
876
+ if (!point.ok || point.x === undefined || point.y === undefined) {
877
+ throw new McpToolError("invalid_target", "Element could not be found or is not visible.");
878
+ }
879
+ return { x: point.x, y: point.y };
880
+ }
881
+ async refreshTabs() {
882
+ const response = await this.browserClient.Target.getTargets();
883
+ const tabs = response.targetInfos
884
+ .filter((targetInfo) => {
885
+ return targetInfo.type === "page" && !targetInfo.url.startsWith("devtools://");
886
+ })
887
+ .map((targetInfo) => ({
888
+ id: targetInfo.targetId,
889
+ title: targetInfo.title,
890
+ url: targetInfo.url
891
+ }));
892
+ if (!this.activeTabId) {
893
+ this.activeTabId = chooseInitialTab(tabs);
894
+ }
895
+ return tabs.map((tab) => ({
896
+ ...tab,
897
+ active: tab.id === this.activeTabId
898
+ }));
899
+ }
900
+ async getActiveTabId() {
901
+ const tabs = await this.refreshTabs();
902
+ const activeTab = tabs.find((tab) => tab.active);
903
+ if (!activeTab) {
904
+ throw new McpToolError("no_active_tab", "No active tab is available.");
905
+ }
906
+ return activeTab.id;
907
+ }
908
+ async getActivePageClient() {
909
+ const tabs = await this.refreshTabs();
910
+ const activeTab = tabs.find((tab) => tab.active);
911
+ if (!activeTab) {
912
+ throw new McpToolError("no_active_tab", "No active tab is available.");
913
+ }
914
+ const existing = this.pageClients.get(activeTab.id);
915
+ if (existing) {
916
+ return existing;
917
+ }
918
+ const client = await chromeRemoteInterface({
919
+ host: this.connection.host,
920
+ port: this.connection.port,
921
+ target: activeTab.id
922
+ });
923
+ this.installConsoleCollection(activeTab.id, client);
924
+ await Promise.all([
925
+ client.Page.enable(),
926
+ client.Runtime.enable(),
927
+ client.DOM.enable({}),
928
+ client.Network?.enable({}).catch(() => undefined),
929
+ client.Log?.enable().catch(() => undefined)
930
+ ]);
931
+ this.pageClients.set(activeTab.id, client);
932
+ return client;
933
+ }
934
+ async getActiveUtilityContextId(client) {
935
+ const { frameTree } = await client.Page.getFrameTree();
936
+ const response = await client.Page.createIsolatedWorld({
937
+ frameId: frameTree.frame.id,
938
+ worldName: "__roxy_playwright_utility_world__",
939
+ grantUniversalAccess: true
940
+ });
941
+ return response.executionContextId;
942
+ }
943
+ installConsoleCollection(tabId, client) {
944
+ this.ensureConsoleState(tabId);
945
+ client.Runtime.consoleAPICalled((event) => {
946
+ if (event.executionContextId === 0) {
947
+ return;
948
+ }
949
+ const frame = event.stackTrace?.callFrames?.[0];
950
+ const text = formatConsoleText(event.args);
951
+ const locationUrl = frame?.url ?? "";
952
+ const lineNumber = frame?.lineNumber ?? 0;
953
+ this.addConsoleMessage(tabId, {
954
+ type: event.type,
955
+ timestamp: normalizeConsoleTimestamp(event.timestamp),
956
+ text,
957
+ locationUrl,
958
+ lineNumber,
959
+ formattedText: formatConsoleMessage(event.type, text, locationUrl, lineNumber)
960
+ });
961
+ });
962
+ client.Runtime.exceptionThrown((event) => {
963
+ const details = event.exceptionDetails;
964
+ const text = details?.exception?.description
965
+ || (details?.exception?.value !== undefined ? String(details.exception.value) : undefined)
966
+ || details?.text
967
+ || "Uncaught exception";
968
+ this.addConsoleMessage(tabId, {
969
+ type: "error",
970
+ timestamp: Date.now(),
971
+ text,
972
+ locationUrl: details?.url ?? "",
973
+ lineNumber: details?.lineNumber ?? 0,
974
+ formattedText: text
975
+ });
976
+ });
977
+ client.Log?.entryAdded((event) => {
978
+ const entry = event.entry;
979
+ if (entry.source === "worker") {
980
+ return;
981
+ }
982
+ const type = entry.level ?? "log";
983
+ const text = entry.text ?? "";
984
+ const locationUrl = entry.url ?? "";
985
+ const lineNumber = entry.lineNumber ?? 0;
986
+ this.addConsoleMessage(tabId, {
987
+ type,
988
+ timestamp: normalizeConsoleTimestamp(entry.timestamp),
989
+ text,
990
+ locationUrl,
991
+ lineNumber,
992
+ formattedText: formatConsoleMessage(type, text, locationUrl, lineNumber)
993
+ });
994
+ });
995
+ client.Page.javascriptDialogOpening((event) => {
996
+ this.pageDialogStates.set(tabId, {
997
+ message: event.message,
998
+ type: event.type,
999
+ ...(event.defaultPrompt !== undefined ? { defaultPrompt: event.defaultPrompt } : {}),
1000
+ ...(event.url !== undefined ? { url: event.url } : {})
1001
+ });
1002
+ });
1003
+ this.installNetworkCollection(tabId, client);
1004
+ }
1005
+ installNetworkCollection(tabId, client) {
1006
+ if (!client.Network) {
1007
+ return;
1008
+ }
1009
+ const state = this.ensureNetworkState(tabId);
1010
+ client.Network.requestWillBeSent((event) => {
1011
+ const existing = state.byRequestId.get(event.requestId);
1012
+ const request = existing ?? {
1013
+ index: state.requests.length + 1,
1014
+ requestId: event.requestId,
1015
+ method: event.request.method,
1016
+ url: event.request.url,
1017
+ resourceType: normalizeResourceType(event.type),
1018
+ requestHeaders: {},
1019
+ };
1020
+ request.method = event.request.method;
1021
+ request.url = event.request.url;
1022
+ request.resourceType = normalizeResourceType(event.type);
1023
+ request.requestHeaders = normalizeHeaders(event.request.headers ?? {});
1024
+ if (event.request.postData !== undefined) {
1025
+ request.requestBody = event.request.postData;
1026
+ }
1027
+ if (!existing) {
1028
+ state.requests.push(request);
1029
+ state.byRequestId.set(event.requestId, request);
1030
+ }
1031
+ if (event.timestamp !== undefined) {
1032
+ state.startedAt.set(event.requestId, event.timestamp * 1000);
1033
+ }
1034
+ });
1035
+ client.Network.responseReceived((event) => {
1036
+ const request = state.byRequestId.get(event.requestId);
1037
+ if (!request) {
1038
+ return;
1039
+ }
1040
+ request.status = event.response.status;
1041
+ request.statusText = event.response.statusText;
1042
+ request.responseHeaders = normalizeHeaders(event.response.headers ?? {});
1043
+ request.mimeType = event.response.mimeType;
1044
+ request.resourceType = normalizeResourceType(event.type) || request.resourceType;
1045
+ });
1046
+ client.Network.loadingFinished(async (event) => {
1047
+ const request = state.byRequestId.get(event.requestId);
1048
+ if (!request) {
1049
+ return;
1050
+ }
1051
+ const startedAt = state.startedAt.get(event.requestId);
1052
+ if (startedAt !== undefined && event.timestamp !== undefined) {
1053
+ request.durationMs = Math.round(event.timestamp * 1000 - startedAt);
1054
+ }
1055
+ if (canReadResponseBody(request)) {
1056
+ const clientNetwork = client.Network;
1057
+ const body = await clientNetwork?.getResponseBody({ requestId: event.requestId }).catch(() => undefined);
1058
+ if (body) {
1059
+ request.responseBody = body.base64Encoded
1060
+ ? Buffer.from(body.body, "base64").toString("utf8")
1061
+ : body.body;
1062
+ }
1063
+ }
1064
+ });
1065
+ client.Network.loadingFailed((event) => {
1066
+ const request = state.byRequestId.get(event.requestId);
1067
+ if (!request) {
1068
+ return;
1069
+ }
1070
+ request.failureText = event.errorText ?? "Unknown error";
1071
+ const startedAt = state.startedAt.get(event.requestId);
1072
+ if (startedAt !== undefined && event.timestamp !== undefined) {
1073
+ request.durationMs = Math.round(event.timestamp * 1000 - startedAt);
1074
+ }
1075
+ });
1076
+ }
1077
+ ensureConsoleState(tabId) {
1078
+ let state = this.pageConsoleStates.get(tabId);
1079
+ if (!state) {
1080
+ state = {
1081
+ messages: [],
1082
+ nextMessageIndex: 0,
1083
+ logStartTime: Date.now(),
1084
+ logLine: 0
1085
+ };
1086
+ this.pageConsoleStates.set(tabId, state);
1087
+ }
1088
+ return state;
1089
+ }
1090
+ ensureNetworkState(tabId) {
1091
+ let state = this.pageNetworkStates.get(tabId);
1092
+ if (!state) {
1093
+ state = {
1094
+ requests: [],
1095
+ byRequestId: new Map(),
1096
+ startedAt: new Map()
1097
+ };
1098
+ this.pageNetworkStates.set(tabId, state);
1099
+ }
1100
+ return state;
1101
+ }
1102
+ resetConsole(tabId) {
1103
+ this.pageConsoleStates.set(tabId, {
1104
+ messages: [],
1105
+ nextMessageIndex: 0,
1106
+ logStartTime: Date.now(),
1107
+ logLine: 0
1108
+ });
1109
+ this.pageNetworkStates.set(tabId, {
1110
+ requests: [],
1111
+ byRequestId: new Map(),
1112
+ startedAt: new Map()
1113
+ });
1114
+ this.pageDialogStates.delete(tabId);
1115
+ }
1116
+ addConsoleMessage(tabId, message) {
1117
+ const state = this.ensureConsoleState(tabId);
1118
+ if (!shouldIncludeConsoleMessage(message.type)) {
1119
+ return;
1120
+ }
1121
+ state.messages.push(message);
1122
+ }
1123
+ async consoleMessages(level = "info", all = false) {
1124
+ const activeTabId = await this.getActiveTabId();
1125
+ const state = this.ensureConsoleState(activeTabId);
1126
+ const startIndex = all ? 0 : state.nextMessageIndex;
1127
+ return state.messages
1128
+ .slice(startIndex)
1129
+ .filter((message) => consoleLevelForMessageType(message.type) <= consoleLevelForMessageType(level))
1130
+ .map((message) => ({
1131
+ type: message.type,
1132
+ text: message.text,
1133
+ timestamp: message.timestamp,
1134
+ locationUrl: message.locationUrl,
1135
+ lineNumber: message.lineNumber,
1136
+ formattedText: message.formattedText
1137
+ }));
1138
+ }
1139
+ async evaluate(expression, target) {
1140
+ const pageClient = await this.getActivePageClient();
1141
+ const contextId = await this.getActiveUtilityContextId(pageClient);
1142
+ const source = target
1143
+ ? String.raw `async (payload) => {
1144
+ const state = globalThis.__roxyMcpState;
1145
+ const element = payload.nodeToken
1146
+ ? (state?.elements?.get(payload.nodeToken) ?? null)
1147
+ : document.querySelector(payload.selector);
1148
+ if (!element) throw new Error('Element not found');
1149
+ const value = eval('(' + payload.expression + ')');
1150
+ return typeof value === 'function' ? await value(element) : value;
1151
+ }`
1152
+ : String.raw `async (payload) => {
1153
+ const value = eval('(' + payload.expression + ')');
1154
+ return typeof value === 'function' ? await value() : value;
1155
+ }`;
1156
+ const payload = target ? { ...this.targetArg(target), expression } : { expression };
1157
+ return evaluateCdp(pageClient, source, payload, contextId);
1158
+ }
1159
+ async isFileInput(target) {
1160
+ const pageClient = await this.getActivePageClient();
1161
+ const contextId = await this.getActiveUtilityContextId(pageClient);
1162
+ return evaluateCdp(pageClient, IS_FILE_INPUT_SOURCE, this.targetArg(target), contextId).catch(() => false);
1163
+ }
1164
+ consoleSummary(tabId) {
1165
+ const messages = this.ensureConsoleState(tabId).messages;
1166
+ let errors = 0;
1167
+ let warnings = 0;
1168
+ for (const message of messages) {
1169
+ if (message.type === "error" || message.type === "assert") {
1170
+ errors++;
1171
+ }
1172
+ else if (message.type === "warning") {
1173
+ warnings++;
1174
+ }
1175
+ }
1176
+ return { total: messages.length, errors, warnings };
1177
+ }
1178
+ async takeConsoleLink(tabId) {
1179
+ const state = this.ensureConsoleState(tabId);
1180
+ const messages = state.messages.slice(state.nextMessageIndex);
1181
+ if (messages.length === 0) {
1182
+ return undefined;
1183
+ }
1184
+ state.logFile ??= path.join(process.cwd(), ".playwright-mcp", `console-${new Date(state.logStartTime).toISOString().replace(/[:.]/g, "-")}.log`);
1185
+ await mkdir(path.dirname(state.logFile), { recursive: true });
1186
+ const fromLine = state.logLine + 1;
1187
+ for (const message of messages) {
1188
+ const relativeTime = Math.round(message.timestamp - state.logStartTime);
1189
+ const logLine = `[${String(relativeTime).padStart(8, " ")}ms] ${message.formattedText}\n`;
1190
+ await appendFile(state.logFile, logLine);
1191
+ state.logLine += logLine.split("\n").length - 1;
1192
+ }
1193
+ state.nextMessageIndex = state.messages.length;
1194
+ const relativePath = path.relative(process.cwd(), state.logFile);
1195
+ const lineRange = fromLine === state.logLine ? `#L${fromLine}` : `#L${fromLine}-L${state.logLine}`;
1196
+ return `${relativePath}${lineRange}`;
1197
+ }
1198
+ }
1199
+ async function waitForCdpDocumentReady(client, timeoutMs) {
1200
+ const deadline = Date.now() + timeoutMs;
1201
+ while (Date.now() < deadline) {
1202
+ const readyState = await evaluateCdp(client, String.raw `() => document.readyState`).catch(() => "loading");
1203
+ if (readyState === "complete") {
1204
+ return;
1205
+ }
1206
+ await delay(100);
1207
+ }
1208
+ }
1209
+ function normalizeConsoleTimestamp(timestamp) {
1210
+ if (timestamp === undefined) {
1211
+ return Date.now();
1212
+ }
1213
+ return timestamp < 100_000_000_000 ? timestamp * 1000 : timestamp;
1214
+ }
1215
+ function formatConsoleText(args) {
1216
+ return args
1217
+ .map((arg) => {
1218
+ if (typeof arg.value === "string") {
1219
+ return arg.value;
1220
+ }
1221
+ if (arg.value !== undefined) {
1222
+ return String(arg.value);
1223
+ }
1224
+ if (arg.unserializableValue) {
1225
+ return arg.unserializableValue;
1226
+ }
1227
+ if (arg.description) {
1228
+ return arg.description;
1229
+ }
1230
+ return arg.type ?? "";
1231
+ })
1232
+ .join(" ");
1233
+ }
1234
+ function formatConsoleMessage(type, text, locationUrl, lineNumber) {
1235
+ return `[${type.toUpperCase()}] ${text} @ ${locationUrl}:${lineNumber}`;
1236
+ }
1237
+ function shouldIncludeConsoleMessage(type) {
1238
+ return consoleLevelForMessageType(type) <= consoleLevelForMessageType("info");
1239
+ }
1240
+ function consoleLevelForMessageType(type) {
1241
+ switch (type) {
1242
+ case "assert":
1243
+ case "error":
1244
+ return 0;
1245
+ case "warning":
1246
+ return 1;
1247
+ case "count":
1248
+ case "dir":
1249
+ case "dirxml":
1250
+ case "info":
1251
+ case "log":
1252
+ case "table":
1253
+ case "time":
1254
+ case "timeEnd":
1255
+ return 2;
1256
+ case "clear":
1257
+ case "debug":
1258
+ case "endGroup":
1259
+ case "profile":
1260
+ case "profileEnd":
1261
+ case "startGroup":
1262
+ case "startGroupCollapsed":
1263
+ case "trace":
1264
+ return 3;
1265
+ default:
1266
+ return 2;
1267
+ }
1268
+ }
1269
+ function normalizeHeaders(headers) {
1270
+ const result = {};
1271
+ for (const [key, value] of Object.entries(headers)) {
1272
+ result[key.toLowerCase()] = String(value);
1273
+ }
1274
+ return result;
1275
+ }
1276
+ function normalizeResourceType(type) {
1277
+ return (type ?? "other").toLowerCase();
1278
+ }
1279
+ function canReadResponseBody(request) {
1280
+ if (request.failureText || request.status === undefined) {
1281
+ return false;
1282
+ }
1283
+ return request.status !== 204 && request.status !== 304 && !(request.status >= 100 && request.status < 200);
1284
+ }
1285
+ function cloneNetworkRequest(request) {
1286
+ return {
1287
+ ...request,
1288
+ requestHeaders: { ...request.requestHeaders },
1289
+ ...(request.responseHeaders ? { responseHeaders: { ...request.responseHeaders } } : {})
1290
+ };
1291
+ }
1292
+ async function prepareDropFiles(paths) {
1293
+ if (!paths?.length) {
1294
+ return [];
1295
+ }
1296
+ return Promise.all(paths.map(async (filePath) => {
1297
+ const [fileStat, buffer] = await Promise.all([
1298
+ stat(filePath),
1299
+ readFile(filePath)
1300
+ ]);
1301
+ if (!fileStat.isFile()) {
1302
+ throw new McpToolError("invalid_input", `Drop path is not a file: ${filePath}`);
1303
+ }
1304
+ return {
1305
+ name: path.basename(filePath),
1306
+ mimeType: mimeTypeForPath(filePath),
1307
+ buffer: buffer.toString("base64"),
1308
+ lastModifiedMs: fileStat.mtimeMs
1309
+ };
1310
+ }));
1311
+ }
1312
+ function mimeTypeForPath(filePath) {
1313
+ const ext = path.extname(filePath).toLowerCase();
1314
+ switch (ext) {
1315
+ case ".avif": return "image/avif";
1316
+ case ".bmp": return "image/bmp";
1317
+ case ".css": return "text/css";
1318
+ case ".csv": return "text/csv";
1319
+ case ".gif": return "image/gif";
1320
+ case ".htm":
1321
+ case ".html": return "text/html";
1322
+ case ".jpeg":
1323
+ case ".jpg": return "image/jpeg";
1324
+ case ".js":
1325
+ case ".mjs": return "text/javascript";
1326
+ case ".json": return "application/json";
1327
+ case ".md": return "text/markdown";
1328
+ case ".pdf": return "application/pdf";
1329
+ case ".png": return "image/png";
1330
+ case ".svg": return "image/svg+xml";
1331
+ case ".txt": return "text/plain";
1332
+ case ".webp": return "image/webp";
1333
+ case ".xml": return "application/xml";
1334
+ case ".zip": return "application/zip";
1335
+ default: return "application/octet-stream";
1336
+ }
1337
+ }
1338
+ function bidiBytesValueToString(value) {
1339
+ if (typeof value === "string") {
1340
+ return value;
1341
+ }
1342
+ if (value.type === "base64" && value.value !== undefined) {
1343
+ return Buffer.from(value.value, "base64").toString("utf8");
1344
+ }
1345
+ return value.value ?? "";
1346
+ }
1347
+ function bidiHeadersToRecord(headers) {
1348
+ const result = {};
1349
+ for (const header of headers ?? []) {
1350
+ result[header.name] = bidiBytesValueToString(header.value);
1351
+ }
1352
+ return result;
1353
+ }
1354
+ function parseBidiNetworkEvent(payload) {
1355
+ if (!payload || typeof payload !== "object") {
1356
+ return undefined;
1357
+ }
1358
+ const event = payload;
1359
+ if (!event.context || !event.request?.request || !event.request.method || !event.request.url) {
1360
+ return undefined;
1361
+ }
1362
+ return {
1363
+ context: event.context,
1364
+ request: event.request,
1365
+ ...(event.errorText !== undefined ? { errorText: event.errorText } : {}),
1366
+ ...(event.response !== undefined ? { response: event.response } : {}),
1367
+ ...(event.timestamp !== undefined ? { timestamp: event.timestamp } : {})
1368
+ };
1369
+ }
1370
+ function bidiLogContext(payload) {
1371
+ if (!payload || typeof payload !== "object" || !("source" in payload)) {
1372
+ return undefined;
1373
+ }
1374
+ const context = payload.source?.context;
1375
+ return context ?? undefined;
1376
+ }
1377
+ class BidiConnectedBrowserSession {
1378
+ client;
1379
+ protocol = "bidi";
1380
+ browserName = "firefox";
1381
+ pageConsoleStates = new Map();
1382
+ pageNetworkStates = new Map();
1383
+ pageDialogStates = new Map();
1384
+ bidiListeners = new Map();
1385
+ responseDataCollector;
1386
+ activeTabId;
1387
+ constructor(client) {
1388
+ this.client = client;
1389
+ }
1390
+ static async connect(args) {
1391
+ if (args.browser && args.browser !== "firefox") {
1392
+ throw new McpToolError("unsupported_protocol_input", 'BiDi attach only supports browser "firefox" in v1.');
1393
+ }
1394
+ const parsed = new URL(args.endpoint);
1395
+ if (parsed.protocol !== "ws:" && parsed.protocol !== "wss:") {
1396
+ throw new McpToolError("unsupported_protocol_input", `BiDi endpoint must be a ws(s) URL. Received "${parsed.protocol}".`);
1397
+ }
1398
+ const client = await getBidiClientFactory()({
1399
+ browserName: "firefox",
1400
+ webSocketUrl: args.endpoint
1401
+ });
1402
+ const session = new BidiConnectedBrowserSession(client);
1403
+ await session.initialize();
1404
+ const tabs = await session.refreshTabs();
1405
+ if (tabs.length === 0) {
1406
+ await session.newTab();
1407
+ }
1408
+ return session;
1409
+ }
1410
+ async version() {
1411
+ return `${this.browserName}/unknown`;
1412
+ }
1413
+ async listTabs() {
1414
+ return this.refreshTabs();
1415
+ }
1416
+ async newTab(url = "about:blank") {
1417
+ const response = await this.client.browsingContextCreate({
1418
+ type: "tab"
1419
+ });
1420
+ this.activeTabId = response.context;
1421
+ await this.client.browsingContextActivate({
1422
+ context: response.context
1423
+ });
1424
+ if (url && url !== "about:blank") {
1425
+ await this.client.browsingContextNavigate({
1426
+ context: response.context,
1427
+ url,
1428
+ wait: "complete"
1429
+ });
1430
+ }
1431
+ return this.refreshTabs();
1432
+ }
1433
+ async selectTab(tabId) {
1434
+ this.activeTabId = tabId;
1435
+ await this.client.browsingContextActivate({
1436
+ context: tabId
1437
+ });
1438
+ return this.refreshTabs();
1439
+ }
1440
+ async closeTab(tabId) {
1441
+ const tabsBeforeClose = await this.refreshTabs();
1442
+ const index = tabsBeforeClose.findIndex((tab) => tab.id === tabId);
1443
+ await this.client.browsingContextClose({
1444
+ context: tabId
1445
+ });
1446
+ const tabsAfterClose = await this.refreshTabs();
1447
+ if (tabsAfterClose.length === 0) {
1448
+ this.activeTabId = undefined;
1449
+ this.pageConsoleStates.delete(tabId);
1450
+ this.pageNetworkStates.delete(tabId);
1451
+ this.pageDialogStates.delete(tabId);
1452
+ return tabsAfterClose;
1453
+ }
1454
+ const fallbackIndex = index >= 0 ? Math.min(index, tabsAfterClose.length - 1) : 0;
1455
+ this.activeTabId = tabsAfterClose[fallbackIndex]?.id;
1456
+ if (this.activeTabId) {
1457
+ await this.client.browsingContextActivate({
1458
+ context: this.activeTabId
1459
+ });
1460
+ }
1461
+ return this.refreshTabs();
1462
+ }
1463
+ async snapshot(request = {}) {
1464
+ const tabId = await this.getActiveTabId();
1465
+ const result = await evaluateBiDi(this.client, tabId, ARIA_SNAPSHOT_EVALUATE_SOURCE, toAriaSnapshotPayload(request));
1466
+ return toBrowserSnapshot(result, request, {
1467
+ console: this.consoleSummary(tabId),
1468
+ consoleLink: await this.takeConsoleLink(tabId)
1469
+ });
1470
+ }
1471
+ async consoleMessages(level = "info", all = false) {
1472
+ const activeTabId = await this.getActiveTabId();
1473
+ const state = this.ensureConsoleState(activeTabId);
1474
+ const startIndex = all ? 0 : state.nextMessageIndex;
1475
+ return state.messages
1476
+ .slice(startIndex)
1477
+ .filter((message) => consoleLevelForMessageType(message.type) <= consoleLevelForMessageType(level))
1478
+ .map((message) => ({
1479
+ type: message.type,
1480
+ text: message.text,
1481
+ timestamp: message.timestamp,
1482
+ locationUrl: message.locationUrl,
1483
+ lineNumber: message.lineNumber,
1484
+ formattedText: message.formattedText
1485
+ }));
1486
+ }
1487
+ async evaluate(expression, target) {
1488
+ const tabId = await this.getActiveTabId();
1489
+ const source = target
1490
+ ? String.raw `async (payload) => {
1491
+ const state = globalThis.__roxyMcpState;
1492
+ const element = payload.nodeToken
1493
+ ? (state?.elements?.get(payload.nodeToken) ?? null)
1494
+ : document.querySelector(payload.selector);
1495
+ if (!element) throw new Error('Element not found');
1496
+ const value = eval('(' + payload.expression + ')');
1497
+ return typeof value === 'function' ? await value(element) : value;
1498
+ }`
1499
+ : String.raw `async (payload) => {
1500
+ const value = eval('(' + payload.expression + ')');
1501
+ return typeof value === 'function' ? await value() : value;
1502
+ }`;
1503
+ const payload = target ? { ...("nodeToken" in target ? { nodeToken: target.nodeToken } : { selector: target.selector }), expression } : { expression };
1504
+ return evaluateBiDi(this.client, tabId, source, payload);
1505
+ }
1506
+ async isFileInput(target) {
1507
+ const tabId = await this.getActiveTabId();
1508
+ return evaluateBiDi(this.client, tabId, IS_FILE_INPUT_SOURCE, this.targetArg(target)).catch(() => false);
1509
+ }
1510
+ async click(target, options) {
1511
+ const tabId = await this.getActiveTabId();
1512
+ const source = "nodeToken" in target ? ACTION_POINT_EVALUATE_SOURCE : ACTION_POINT_BY_SELECTOR_SOURCE;
1513
+ const arg = "nodeToken" in target ? { nodeToken: target.nodeToken } : { selector: target.selector };
1514
+ const point = await evaluateBiDi(this.client, tabId, source, arg);
1515
+ if (!point.ok || point.x === undefined || point.y === undefined) {
1516
+ const isSelector = "selector" in target;
1517
+ throw new McpToolError(isSelector ? "invalid_target" : "stale_ref", isSelector
1518
+ ? `Element "${target.selector}" could not be found or is not visible.`
1519
+ : 'The referenced element is no longer valid. Call "browser_snapshot" again.');
1520
+ }
1521
+ const BIDI_BUTTON = { left: 0, middle: 1, right: 2 };
1522
+ const buttonCode = BIDI_BUTTON[options.button ?? "left"] ?? 0;
1523
+ const cycles = options.doubleClick ? 2 : 1;
1524
+ const modifierKeys = (options.modifiers ?? []).map((m) => {
1525
+ const KEY_MAP = {
1526
+ Alt: "",
1527
+ Control: "",
1528
+ ControlOrMeta: "",
1529
+ Meta: "",
1530
+ Shift: ""
1531
+ };
1532
+ return KEY_MAP[m] ?? m;
1533
+ });
1534
+ const keyDownActions = modifierKeys.map((value) => ({ type: "keyDown", value }));
1535
+ const keyUpActions = modifierKeys.map((value) => ({ type: "keyUp", value }));
1536
+ const pointerActions = [
1537
+ {
1538
+ type: "pointerMove",
1539
+ x: Math.round(point.x),
1540
+ y: Math.round(point.y),
1541
+ origin: "viewport"
1542
+ }
1543
+ ];
1544
+ for (let i = 0; i < cycles; i++) {
1545
+ pointerActions.push({ type: "pointerDown", button: buttonCode });
1546
+ pointerActions.push({ type: "pause", duration: options.clickHoldMs });
1547
+ pointerActions.push({ type: "pointerUp", button: buttonCode });
1548
+ }
1549
+ const actions = [];
1550
+ if (modifierKeys.length > 0) {
1551
+ actions.push({ type: "key", id: "kbd", actions: [...keyDownActions, ...keyUpActions] });
1552
+ }
1553
+ actions.push({
1554
+ type: "pointer",
1555
+ id: "mouse",
1556
+ parameters: { pointerType: "mouse" },
1557
+ actions: pointerActions
1558
+ });
1559
+ await this.client.inputPerformActions({
1560
+ context: tabId,
1561
+ actions
1562
+ });
1563
+ await this.client.inputReleaseActions({ context: tabId }).catch(() => { });
1564
+ }
1565
+ async drag(start, end, options) {
1566
+ const tabId = await this.getActiveTabId();
1567
+ const startPoint = await this.actionPoint(tabId, start);
1568
+ const endPoint = await this.actionPoint(tabId, end);
1569
+ await this.client.inputPerformActions({
1570
+ context: tabId,
1571
+ actions: [
1572
+ {
1573
+ type: "pointer",
1574
+ id: "mouse",
1575
+ parameters: { pointerType: "mouse" },
1576
+ actions: [
1577
+ {
1578
+ type: "pointerMove",
1579
+ x: Math.round(startPoint.x),
1580
+ y: Math.round(startPoint.y),
1581
+ origin: "viewport"
1582
+ },
1583
+ { type: "pause", duration: options.moveDelayMs },
1584
+ { type: "pointerDown", button: 0 },
1585
+ { type: "pause", duration: options.holdDelayMs },
1586
+ {
1587
+ type: "pointerMove",
1588
+ x: Math.round(endPoint.x),
1589
+ y: Math.round(endPoint.y),
1590
+ origin: "viewport"
1591
+ },
1592
+ { type: "pause", duration: options.moveDelayMs },
1593
+ { type: "pointerUp", button: 0 }
1594
+ ]
1595
+ }
1596
+ ]
1597
+ });
1598
+ await this.client.inputReleaseActions({ context: tabId }).catch(() => { });
1599
+ }
1600
+ async drop(target, payload) {
1601
+ const files = await prepareDropFiles(payload.paths);
1602
+ const tabId = await this.getActiveTabId();
1603
+ const result = await evaluateBiDi(this.client, tabId, DROP_ON_ELEMENT_SOURCE, { ...this.targetArg(target), data: payload.data ?? {}, files });
1604
+ if (!result.ok) {
1605
+ throw new McpToolError(result.reason === "not_accepted" ? "action_failed" : "invalid_target", result.reason === "not_accepted"
1606
+ ? "Drop target did not accept the drop; its dragover handler did not call preventDefault()."
1607
+ : "Drop target could not be found.");
1608
+ }
1609
+ }
1610
+ async hover(target) {
1611
+ const tabId = await this.getActiveTabId();
1612
+ const source = "nodeToken" in target ? ACTION_POINT_EVALUATE_SOURCE : ACTION_POINT_BY_SELECTOR_SOURCE;
1613
+ const arg = "nodeToken" in target ? { nodeToken: target.nodeToken } : { selector: target.selector };
1614
+ const point = await evaluateBiDi(this.client, tabId, source, arg);
1615
+ if (!point.ok || point.x === undefined || point.y === undefined) {
1616
+ const isSelector = "selector" in target;
1617
+ throw new McpToolError(isSelector ? "invalid_target" : "stale_ref", isSelector
1618
+ ? `Element "${target.selector}" could not be found or is not visible.`
1619
+ : 'The referenced element is no longer valid. Call "browser_snapshot" again.');
1620
+ }
1621
+ await this.client.inputPerformActions({
1622
+ context: tabId,
1623
+ actions: [
1624
+ {
1625
+ type: "pointer",
1626
+ id: "mouse",
1627
+ parameters: { pointerType: "mouse" },
1628
+ actions: [
1629
+ {
1630
+ type: "pointerMove",
1631
+ x: Math.round(point.x),
1632
+ y: Math.round(point.y),
1633
+ origin: "viewport"
1634
+ }
1635
+ ]
1636
+ }
1637
+ ]
1638
+ });
1639
+ await this.client.inputReleaseActions({ context: tabId }).catch(() => { });
1640
+ }
1641
+ async close() {
1642
+ for (const [event, listener] of this.bidiListeners) {
1643
+ this.client.removeListener(event, listener);
1644
+ }
1645
+ this.bidiListeners.clear();
1646
+ if (this.responseDataCollector) {
1647
+ await this.client.networkRemoveDataCollector({ collector: this.responseDataCollector }).catch(() => { });
1648
+ this.responseDataCollector = undefined;
1649
+ }
1650
+ await this.client.sessionEnd({}).catch(() => { });
1651
+ this.client.close();
1652
+ }
1653
+ async navigate(url) {
1654
+ const tabId = await this.getActiveTabId();
1655
+ this.resetConsole(tabId);
1656
+ await this.client.browsingContextNavigate({ context: tabId, url, wait: "complete" });
1657
+ }
1658
+ async type(target, text, options) {
1659
+ const tabId = await this.getActiveTabId();
1660
+ const arg = "nodeToken" in target ? { nodeToken: target.nodeToken } : { selector: target.selector };
1661
+ const result = await evaluateBiDi(this.client, tabId, TYPE_INTO_ELEMENT_SOURCE, { ...arg, text, submit: options?.submit ?? false });
1662
+ if (!result.ok) {
1663
+ const isSelector = "selector" in target;
1664
+ throw new McpToolError(isSelector ? "invalid_target" : "stale_ref", result.reason === "not_found"
1665
+ ? (isSelector ? `Element "${target.selector}" could not be found.` : 'The referenced element is no longer valid.')
1666
+ : `Element is not a typeable input.`);
1667
+ }
1668
+ }
1669
+ async pressKey(key, modifiers) {
1670
+ const tabId = await this.getActiveTabId();
1671
+ // WebDriver BiDi uses Unicode Private Use Area for special keys
1672
+ const BIDI_SPECIAL_KEY = {
1673
+ Enter: "", Return: "", Escape: "",
1674
+ Tab: "", Backspace: "", Delete: "",
1675
+ ArrowLeft: "", ArrowRight: "", ArrowUp: "", ArrowDown: "",
1676
+ Home: "", End: "", PageUp: "", PageDown: "",
1677
+ Insert: "", Space: " ",
1678
+ F1: "", F2: "", F3: "", F4: "",
1679
+ F5: "", F6: "", F7: "", F8: "",
1680
+ F9: "", F10: "", F11: "", F12: "",
1681
+ };
1682
+ const BIDI_MODIFIER_KEY = {
1683
+ Alt: "", Control: "", ControlOrMeta: "",
1684
+ Meta: "", Shift: ""
1685
+ };
1686
+ const keyValue = BIDI_SPECIAL_KEY[key] ?? key;
1687
+ const modifierKeys = (modifiers ?? []).map((m) => BIDI_MODIFIER_KEY[m] ?? m);
1688
+ const keyDownActions = modifierKeys.map((value) => ({ type: "keyDown", value }));
1689
+ const keyUpActions = [...modifierKeys].reverse().map((value) => ({ type: "keyUp", value }));
1690
+ await this.client.inputPerformActions({
1691
+ context: tabId,
1692
+ actions: [
1693
+ {
1694
+ type: "key",
1695
+ id: "kbd",
1696
+ actions: [
1697
+ ...keyDownActions,
1698
+ { type: "keyDown", value: keyValue },
1699
+ { type: "keyUp", value: keyValue },
1700
+ ...keyUpActions
1701
+ ]
1702
+ }
1703
+ ]
1704
+ });
1705
+ await this.client.inputReleaseActions({ context: tabId }).catch(() => { });
1706
+ }
1707
+ async selectOption(target, values) {
1708
+ const tabId = await this.getActiveTabId();
1709
+ const arg = "nodeToken" in target ? { nodeToken: target.nodeToken } : { selector: target.selector };
1710
+ const result = await evaluateBiDi(this.client, tabId, SELECT_OPTION_SOURCE, { ...arg, values });
1711
+ if (!result.ok) {
1712
+ const isSelector = "selector" in target;
1713
+ throw new McpToolError(isSelector ? "invalid_target" : "stale_ref", result.reason === "not_found"
1714
+ ? (isSelector ? `Element "${target.selector}" could not be found.` : 'The referenced element is no longer valid.')
1715
+ : `Element is not a <select> element.`);
1716
+ }
1717
+ return result.selected;
1718
+ }
1719
+ async check(target, checked) {
1720
+ const tabId = await this.getActiveTabId();
1721
+ const arg = "nodeToken" in target ? { nodeToken: target.nodeToken } : { selector: target.selector };
1722
+ const result = await evaluateBiDi(this.client, tabId, CHECK_ELEMENT_SOURCE, { ...arg, checked });
1723
+ if (!result.ok) {
1724
+ const isSelector = "selector" in target;
1725
+ throw new McpToolError(isSelector ? "invalid_target" : "stale_ref", result.reason === "not_found"
1726
+ ? (isSelector ? `Element "${target.selector}" could not be found.` : 'The referenced element is no longer valid.')
1727
+ : `Element is not a checkbox or radio button.`);
1728
+ }
1729
+ }
1730
+ async goBack() {
1731
+ const tabId = await this.getActiveTabId();
1732
+ await this.client.browsingContextTraverseHistory({ context: tabId, delta: -1 }).catch(() => { });
1733
+ }
1734
+ async goForward() {
1735
+ const tabId = await this.getActiveTabId();
1736
+ await this.client.browsingContextTraverseHistory({ context: tabId, delta: 1 }).catch(() => { });
1737
+ }
1738
+ async resize(width, height) {
1739
+ const tabId = await this.getActiveTabId();
1740
+ await this.client.browsingContextSetViewport({
1741
+ context: tabId,
1742
+ viewport: { width, height }
1743
+ });
1744
+ }
1745
+ async scroll(target, deltaX, deltaY) {
1746
+ const tabId = await this.getActiveTabId();
1747
+ const arg = target ? ("nodeToken" in target ? { nodeToken: target.nodeToken } : { selector: target.selector }) : {};
1748
+ await evaluateBiDi(this.client, tabId, SCROLL_ELEMENT_SOURCE, { ...arg, deltaX, deltaY });
1749
+ }
1750
+ async screenshot(options = {}) {
1751
+ const tabId = await this.getActiveTabId();
1752
+ const format = options.type ?? "png";
1753
+ const screenshotOptions = {
1754
+ context: tabId,
1755
+ format: { type: format === "png" ? "image/png" : "image/jpeg" }
1756
+ };
1757
+ if (options.target) {
1758
+ const box = await evaluateBiDi(this.client, tabId, ELEMENT_BOX_SOURCE, this.targetArg(options.target));
1759
+ if (!box.ok || box.x === undefined || box.y === undefined || box.width === undefined || box.height === undefined) {
1760
+ throw new McpToolError("invalid_target", "Screenshot target could not be found.");
1761
+ }
1762
+ screenshotOptions.origin = "viewport";
1763
+ screenshotOptions.clip = {
1764
+ type: "box",
1765
+ x: box.x,
1766
+ y: box.y,
1767
+ width: box.width,
1768
+ height: box.height
1769
+ };
1770
+ }
1771
+ else if (options.fullPage) {
1772
+ const box = await evaluateBiDi(this.client, tabId, DOCUMENT_BOX_SOURCE);
1773
+ screenshotOptions.origin = "document";
1774
+ screenshotOptions.clip = {
1775
+ type: "box",
1776
+ x: box.x,
1777
+ y: box.y,
1778
+ width: box.width,
1779
+ height: box.height
1780
+ };
1781
+ }
1782
+ const result = await this.client.browsingContextCaptureScreenshot(screenshotOptions);
1783
+ return {
1784
+ data: result.data,
1785
+ mimeType: format === "jpeg" ? "image/jpeg" : "image/png"
1786
+ };
1787
+ }
1788
+ async uploadFile(target, filePaths) {
1789
+ const tabId = await this.getActiveTabId();
1790
+ const element = await this.sharedElementReference(tabId, target);
1791
+ await this.client.inputSetFiles({
1792
+ context: tabId,
1793
+ element,
1794
+ files: filePaths
1795
+ });
1796
+ }
1797
+ async fillForm(fields) {
1798
+ const tabId = await this.getActiveTabId();
1799
+ for (const field of fields) {
1800
+ const result = await evaluateBiDi(this.client, tabId, SET_FORM_FIELD_SOURCE, {
1801
+ ...this.targetArg(field.target),
1802
+ fieldType: field.type,
1803
+ value: field.value
1804
+ });
1805
+ if (!result.ok) {
1806
+ throw new McpToolError("invalid_target", `Unable to fill form field: ${result.reason ?? "unknown error"}.`);
1807
+ }
1808
+ }
1809
+ }
1810
+ async handleDialog(accept, promptText) {
1811
+ const tabId = await this.getActiveTabId();
1812
+ if (!this.pageDialogStates.has(tabId)) {
1813
+ throw new McpToolError("no_dialog", "No dialog visible.");
1814
+ }
1815
+ this.pageDialogStates.delete(tabId);
1816
+ await this.client.browsingContextHandleUserPrompt({
1817
+ context: tabId,
1818
+ accept,
1819
+ ...(promptText !== undefined ? { userText: promptText } : {})
1820
+ });
1821
+ }
1822
+ async networkRequests() {
1823
+ const tabId = await this.getActiveTabId();
1824
+ return this.ensureNetworkState(tabId).requests.map(cloneNetworkRequest);
1825
+ }
1826
+ async networkRequest(index) {
1827
+ const tabId = await this.getActiveTabId();
1828
+ const request = this.ensureNetworkState(tabId).requests[index - 1];
1829
+ return request ? cloneNetworkRequest(request) : undefined;
1830
+ }
1831
+ async runCodeUnsafe(code) {
1832
+ return this.evaluate(`async () => {
1833
+ const fn = eval(${JSON.stringify(`(${code})`)});
1834
+ if (typeof fn !== 'function') throw new Error('Code must evaluate to a function.');
1835
+ return await fn({
1836
+ evaluate: async expression => {
1837
+ const value = typeof expression === 'function' ? expression : eval('(' + expression + ')');
1838
+ return typeof value === 'function' ? await value() : value;
1839
+ },
1840
+ title: () => document.title,
1841
+ url: () => location.href
1842
+ });
1843
+ }`);
1844
+ }
1845
+ async initialize() {
1846
+ await this.client.sessionSubscribe({
1847
+ events: [
1848
+ "browsingContext.userPromptOpened",
1849
+ "log.entryAdded",
1850
+ "network.beforeRequestSent",
1851
+ "network.responseCompleted",
1852
+ "network.fetchError",
1853
+ "network.responseStarted"
1854
+ ]
1855
+ }).catch(() => undefined);
1856
+ const collectorResult = await this.client.networkAddDataCollector({
1857
+ dataTypes: ["response"],
1858
+ maxEncodedDataSize: 10_000_000
1859
+ }).catch(() => undefined);
1860
+ this.responseDataCollector = collectorResult?.collector;
1861
+ this.attachBiDiListeners();
1862
+ }
1863
+ attachBiDiListeners() {
1864
+ this.attachBiDiListener("log.entryAdded", (payload) => this.handleLogEntry(payload));
1865
+ this.attachBiDiListener("browsingContext.userPromptOpened", (payload) => this.handleUserPromptOpened(payload));
1866
+ this.attachBiDiListener("network.beforeRequestSent", (payload) => this.handleBeforeRequestSent(payload));
1867
+ this.attachBiDiListener("network.responseStarted", (payload) => this.handleResponseStarted(payload));
1868
+ this.attachBiDiListener("network.responseCompleted", (payload) => void this.handleResponseCompleted(payload));
1869
+ this.attachBiDiListener("network.fetchError", (payload) => this.handleFetchError(payload));
1870
+ }
1871
+ attachBiDiListener(event, listener) {
1872
+ this.bidiListeners.set(event, listener);
1873
+ this.client.on(event, listener);
1874
+ }
1875
+ async refreshTabs() {
1876
+ const response = await this.client.browsingContextGetTree({});
1877
+ const tabs = await Promise.all(response.contexts
1878
+ .filter((context) => context.parent === undefined || context.parent === null)
1879
+ .map(async (context) => ({
1880
+ id: context.context,
1881
+ title: await this.titleForContext(context.context),
1882
+ url: context.url
1883
+ })));
1884
+ if (!this.activeTabId) {
1885
+ this.activeTabId = chooseInitialTab(tabs);
1886
+ }
1887
+ return tabs.map((tab) => ({
1888
+ ...tab,
1889
+ active: tab.id === this.activeTabId
1890
+ }));
1891
+ }
1892
+ async getActiveTabId() {
1893
+ const tabs = await this.refreshTabs();
1894
+ const activeTab = tabs.find((tab) => tab.active);
1895
+ if (!activeTab) {
1896
+ throw new McpToolError("no_active_tab", "No active tab is available.");
1897
+ }
1898
+ return activeTab.id;
1899
+ }
1900
+ targetArg(target) {
1901
+ return "nodeToken" in target ? { nodeToken: target.nodeToken } : { selector: target.selector };
1902
+ }
1903
+ async actionPoint(tabId, target) {
1904
+ const source = "nodeToken" in target ? ACTION_POINT_EVALUATE_SOURCE : ACTION_POINT_BY_SELECTOR_SOURCE;
1905
+ const point = await evaluateBiDi(this.client, tabId, source, this.targetArg(target));
1906
+ if (!point.ok || point.x === undefined || point.y === undefined) {
1907
+ const isSelector = "selector" in target;
1908
+ throw new McpToolError(isSelector ? "invalid_target" : "stale_ref", isSelector
1909
+ ? `Element "${target.selector}" could not be found or is not visible.`
1910
+ : 'The referenced element is no longer valid. Call "browser_snapshot" again.');
1911
+ }
1912
+ return { x: point.x, y: point.y };
1913
+ }
1914
+ async sharedElementReference(tabId, target) {
1915
+ const reference = await evaluateBiDiRef(this.client, tabId, GET_ELEMENT_OBJECT_SOURCE, this.targetArg(target));
1916
+ if (!reference.sharedId) {
1917
+ const isSelector = "selector" in target;
1918
+ throw new McpToolError(isSelector ? "invalid_target" : "stale_ref", isSelector ? `Element "${target.selector}" could not be found.` : 'The referenced element is no longer valid.');
1919
+ }
1920
+ return {
1921
+ sharedId: reference.sharedId,
1922
+ ...(reference.handle !== undefined ? { handle: reference.handle } : {})
1923
+ };
1924
+ }
1925
+ async titleForContext(contextId) {
1926
+ try {
1927
+ return await evaluateBiDi(this.client, contextId, String.raw `() => document.title || ""`);
1928
+ }
1929
+ catch {
1930
+ return "";
1931
+ }
1932
+ }
1933
+ handleLogEntry(payload) {
1934
+ const context = bidiLogContext(payload);
1935
+ if (!context) {
1936
+ return;
1937
+ }
1938
+ const log = payload;
1939
+ const frame = log.stackTrace?.callFrames?.[0];
1940
+ const text = log.text ?? (log.args ? formatConsoleText(log.args) : "");
1941
+ const type = log.method ?? log.level ?? log.type ?? "log";
1942
+ const locationUrl = frame?.url ?? "";
1943
+ const lineNumber = frame?.lineNumber ?? 0;
1944
+ this.addConsoleMessage(context, {
1945
+ type,
1946
+ timestamp: normalizeConsoleTimestamp(log.timestamp),
1947
+ text,
1948
+ locationUrl,
1949
+ lineNumber,
1950
+ formattedText: formatConsoleMessage(type, text, locationUrl, lineNumber)
1951
+ });
1952
+ }
1953
+ handleUserPromptOpened(payload) {
1954
+ if (!payload || typeof payload !== "object" || !("context" in payload)) {
1955
+ return;
1956
+ }
1957
+ const event = payload;
1958
+ this.pageDialogStates.set(event.context, {
1959
+ message: event.message ?? "",
1960
+ type: event.type ?? "alert",
1961
+ ...(event.defaultValue !== undefined ? { defaultPrompt: event.defaultValue } : {})
1962
+ });
1963
+ }
1964
+ handleBeforeRequestSent(payload) {
1965
+ const event = parseBidiNetworkEvent(payload);
1966
+ if (!event) {
1967
+ return;
1968
+ }
1969
+ const state = this.ensureNetworkState(event.context);
1970
+ const existing = state.byRequestId.get(event.request.request);
1971
+ const request = existing ?? {
1972
+ index: state.requests.length + 1,
1973
+ requestId: event.request.request,
1974
+ method: event.request.method,
1975
+ url: event.request.url,
1976
+ resourceType: normalizeResourceType(event.request.destination),
1977
+ requestHeaders: {},
1978
+ };
1979
+ request.method = event.request.method;
1980
+ request.url = event.request.url;
1981
+ request.resourceType = normalizeResourceType(event.request.destination);
1982
+ request.requestHeaders = normalizeHeaders(bidiHeadersToRecord(event.request.headers));
1983
+ if (event.request.bodySize !== undefined && event.request.bodySize > 0) {
1984
+ request.requestBody ??= "";
1985
+ }
1986
+ if (!existing) {
1987
+ state.requests.push(request);
1988
+ state.byRequestId.set(event.request.request, request);
1989
+ }
1990
+ if (event.timestamp !== undefined) {
1991
+ state.startedAt.set(event.request.request, event.timestamp);
1992
+ }
1993
+ }
1994
+ handleResponseStarted(payload) {
1995
+ const event = parseBidiNetworkEvent(payload);
1996
+ if (!event?.response) {
1997
+ return;
1998
+ }
1999
+ const request = this.ensureNetworkRequest(event);
2000
+ request.status = event.response.status;
2001
+ request.statusText = event.response.statusText ?? "";
2002
+ request.responseHeaders = normalizeHeaders(bidiHeadersToRecord(event.response.headers));
2003
+ request.mimeType = event.response.mimeType;
2004
+ }
2005
+ async handleResponseCompleted(payload) {
2006
+ const event = parseBidiNetworkEvent(payload);
2007
+ if (!event?.response) {
2008
+ return;
2009
+ }
2010
+ const request = this.ensureNetworkRequest(event);
2011
+ request.status = event.response.status;
2012
+ request.statusText = event.response.statusText ?? "";
2013
+ request.responseHeaders = normalizeHeaders(bidiHeadersToRecord(event.response.headers));
2014
+ request.mimeType = event.response.mimeType;
2015
+ const startedAt = this.ensureNetworkState(event.context).startedAt.get(event.request.request);
2016
+ if (startedAt !== undefined && event.timestamp !== undefined) {
2017
+ request.durationMs = Math.round(event.timestamp - startedAt);
2018
+ }
2019
+ if (canReadResponseBody(request)) {
2020
+ const body = await this.getResponseBody(event.request.request).catch(() => undefined);
2021
+ if (body !== undefined) {
2022
+ request.responseBody = body;
2023
+ }
2024
+ }
2025
+ }
2026
+ handleFetchError(payload) {
2027
+ const event = parseBidiNetworkEvent(payload);
2028
+ if (!event) {
2029
+ return;
2030
+ }
2031
+ const request = this.ensureNetworkRequest(event);
2032
+ request.failureText = event.errorText ?? "Unknown error";
2033
+ const startedAt = this.ensureNetworkState(event.context).startedAt.get(event.request.request);
2034
+ if (startedAt !== undefined && event.timestamp !== undefined) {
2035
+ request.durationMs = Math.round(event.timestamp - startedAt);
2036
+ }
2037
+ }
2038
+ ensureNetworkRequest(event) {
2039
+ const state = this.ensureNetworkState(event.context);
2040
+ let request = state.byRequestId.get(event.request.request);
2041
+ if (!request) {
2042
+ request = {
2043
+ index: state.requests.length + 1,
2044
+ requestId: event.request.request,
2045
+ method: event.request.method,
2046
+ url: event.request.url,
2047
+ resourceType: normalizeResourceType(event.request.destination),
2048
+ requestHeaders: normalizeHeaders(bidiHeadersToRecord(event.request.headers)),
2049
+ };
2050
+ state.requests.push(request);
2051
+ state.byRequestId.set(event.request.request, request);
2052
+ }
2053
+ return request;
2054
+ }
2055
+ async getResponseBody(requestId) {
2056
+ if (!this.responseDataCollector) {
2057
+ return undefined;
2058
+ }
2059
+ const response = await this.client.networkGetData({
2060
+ collector: this.responseDataCollector,
2061
+ dataType: "response",
2062
+ request: requestId
2063
+ });
2064
+ return response.bytes ? bidiBytesValueToString(response.bytes) : undefined;
2065
+ }
2066
+ ensureConsoleState(tabId) {
2067
+ let state = this.pageConsoleStates.get(tabId);
2068
+ if (!state) {
2069
+ state = {
2070
+ messages: [],
2071
+ nextMessageIndex: 0,
2072
+ logStartTime: Date.now(),
2073
+ logLine: 0
2074
+ };
2075
+ this.pageConsoleStates.set(tabId, state);
2076
+ }
2077
+ return state;
2078
+ }
2079
+ ensureNetworkState(tabId) {
2080
+ let state = this.pageNetworkStates.get(tabId);
2081
+ if (!state) {
2082
+ state = {
2083
+ requests: [],
2084
+ byRequestId: new Map(),
2085
+ startedAt: new Map()
2086
+ };
2087
+ this.pageNetworkStates.set(tabId, state);
2088
+ }
2089
+ return state;
2090
+ }
2091
+ resetConsole(tabId) {
2092
+ this.pageConsoleStates.set(tabId, {
2093
+ messages: [],
2094
+ nextMessageIndex: 0,
2095
+ logStartTime: Date.now(),
2096
+ logLine: 0
2097
+ });
2098
+ this.pageNetworkStates.set(tabId, {
2099
+ requests: [],
2100
+ byRequestId: new Map(),
2101
+ startedAt: new Map()
2102
+ });
2103
+ this.pageDialogStates.delete(tabId);
2104
+ }
2105
+ addConsoleMessage(tabId, message) {
2106
+ const state = this.ensureConsoleState(tabId);
2107
+ if (!shouldIncludeConsoleMessage(message.type)) {
2108
+ return;
2109
+ }
2110
+ state.messages.push(message);
2111
+ }
2112
+ consoleSummary(tabId) {
2113
+ const messages = this.ensureConsoleState(tabId).messages;
2114
+ let errors = 0;
2115
+ let warnings = 0;
2116
+ for (const message of messages) {
2117
+ if (message.type === "error" || message.type === "assert") {
2118
+ errors++;
2119
+ }
2120
+ else if (message.type === "warning") {
2121
+ warnings++;
2122
+ }
2123
+ }
2124
+ return { total: messages.length, errors, warnings };
2125
+ }
2126
+ async takeConsoleLink(tabId) {
2127
+ const state = this.ensureConsoleState(tabId);
2128
+ const messages = state.messages.slice(state.nextMessageIndex);
2129
+ if (messages.length === 0) {
2130
+ return undefined;
2131
+ }
2132
+ state.logFile ??= path.join(process.cwd(), ".playwright-mcp", `console-${new Date(state.logStartTime).toISOString().replace(/[:.]/g, "-")}.log`);
2133
+ await mkdir(path.dirname(state.logFile), { recursive: true });
2134
+ const fromLine = state.logLine + 1;
2135
+ for (const message of messages) {
2136
+ const relativeTime = Math.round(message.timestamp - state.logStartTime);
2137
+ const logLine = `[${String(relativeTime).padStart(8, " ")}ms] ${message.formattedText}\n`;
2138
+ await appendFile(state.logFile, logLine);
2139
+ state.logLine += logLine.split("\n").length - 1;
2140
+ }
2141
+ state.nextMessageIndex = state.messages.length;
2142
+ const relativePath = path.relative(process.cwd(), state.logFile);
2143
+ const lineRange = fromLine === state.logLine ? `#L${fromLine}` : `#L${fromLine}-L${state.logLine}`;
2144
+ return `${relativePath}${lineRange}`;
2145
+ }
2146
+ }
2147
+ export async function connectBrowserSession(args) {
2148
+ if (args.protocol === "cdp") {
2149
+ return CdpConnectedBrowserSession.connect(args);
2150
+ }
2151
+ return BidiConnectedBrowserSession.connect(args);
2152
+ }
2153
+ //# sourceMappingURL=connectedBrowser.js.map