devharness 0.8.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 (627) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +185 -0
  3. package/build/atomic-write.d.ts +28 -0
  4. package/build/atomic-write.d.ts.map +1 -0
  5. package/build/atomic-write.js +126 -0
  6. package/build/atomic-write.js.map +1 -0
  7. package/build/cdp-manager.d.ts +524 -0
  8. package/build/cdp-manager.d.ts.map +1 -0
  9. package/build/cdp-manager.js +1828 -0
  10. package/build/cdp-manager.js.map +1 -0
  11. package/build/cdp-manager.test.d.ts +6 -0
  12. package/build/cdp-manager.test.d.ts.map +1 -0
  13. package/build/cdp-manager.test.js +546 -0
  14. package/build/cdp-manager.test.js.map +1 -0
  15. package/build/chrome-launcher.d.ts +400 -0
  16. package/build/chrome-launcher.d.ts.map +1 -0
  17. package/build/chrome-launcher.js +1071 -0
  18. package/build/chrome-launcher.js.map +1 -0
  19. package/build/chrome-launcher.test.d.ts +11 -0
  20. package/build/chrome-launcher.test.d.ts.map +1 -0
  21. package/build/chrome-launcher.test.js +204 -0
  22. package/build/chrome-launcher.test.js.map +1 -0
  23. package/build/clickable-cache.d.ts +73 -0
  24. package/build/clickable-cache.d.ts.map +1 -0
  25. package/build/clickable-cache.js +90 -0
  26. package/build/clickable-cache.js.map +1 -0
  27. package/build/command-recorder.d.ts +401 -0
  28. package/build/command-recorder.d.ts.map +1 -0
  29. package/build/command-recorder.js +701 -0
  30. package/build/command-recorder.js.map +1 -0
  31. package/build/config.d.ts +390 -0
  32. package/build/config.d.ts.map +1 -0
  33. package/build/config.js +826 -0
  34. package/build/config.js.map +1 -0
  35. package/build/config.test.d.ts +2 -0
  36. package/build/config.test.d.ts.map +1 -0
  37. package/build/config.test.js +112 -0
  38. package/build/config.test.js.map +1 -0
  39. package/build/connection-manager.d.ts +159 -0
  40. package/build/connection-manager.d.ts.map +1 -0
  41. package/build/connection-manager.js +432 -0
  42. package/build/connection-manager.js.map +1 -0
  43. package/build/connection-manager.test.d.ts +11 -0
  44. package/build/connection-manager.test.d.ts.map +1 -0
  45. package/build/connection-manager.test.js +114 -0
  46. package/build/connection-manager.test.js.map +1 -0
  47. package/build/console-monitor.d.ts +150 -0
  48. package/build/console-monitor.d.ts.map +1 -0
  49. package/build/console-monitor.js +454 -0
  50. package/build/console-monitor.js.map +1 -0
  51. package/build/dashboard/bundle.js +1 -0
  52. package/build/dashboard/dashboard-client.d.ts +42 -0
  53. package/build/dashboard/dashboard-client.d.ts.map +1 -0
  54. package/build/dashboard/dashboard-client.js +184 -0
  55. package/build/dashboard/dashboard-client.js.map +1 -0
  56. package/build/dashboard/dashboard-hub.d.ts +50 -0
  57. package/build/dashboard/dashboard-hub.d.ts.map +1 -0
  58. package/build/dashboard/dashboard-hub.js +654 -0
  59. package/build/dashboard/dashboard-hub.js.map +1 -0
  60. package/build/dashboard/hub-lock.d.ts +16 -0
  61. package/build/dashboard/hub-lock.d.ts.map +1 -0
  62. package/build/dashboard/hub-lock.js +77 -0
  63. package/build/dashboard/hub-lock.js.map +1 -0
  64. package/build/dashboard/index.d.ts +25 -0
  65. package/build/dashboard/index.d.ts.map +1 -0
  66. package/build/dashboard/index.js +77 -0
  67. package/build/dashboard/index.js.map +1 -0
  68. package/build/dashboard/types.d.ts +83 -0
  69. package/build/dashboard/types.d.ts.map +1 -0
  70. package/build/dashboard/types.js +6 -0
  71. package/build/dashboard/types.js.map +1 -0
  72. package/build/debug-logger.d.ts +85 -0
  73. package/build/debug-logger.d.ts.map +1 -0
  74. package/build/debug-logger.js +195 -0
  75. package/build/debug-logger.js.map +1 -0
  76. package/build/debugger-aware-wrapper.d.ts +37 -0
  77. package/build/debugger-aware-wrapper.d.ts.map +1 -0
  78. package/build/debugger-aware-wrapper.js +128 -0
  79. package/build/debugger-aware-wrapper.js.map +1 -0
  80. package/build/dom-change-monitor.d.ts +92 -0
  81. package/build/dom-change-monitor.d.ts.map +1 -0
  82. package/build/dom-change-monitor.js +437 -0
  83. package/build/dom-change-monitor.js.map +1 -0
  84. package/build/element-collector.d.ts +16 -0
  85. package/build/element-collector.d.ts.map +1 -0
  86. package/build/element-collector.js +207 -0
  87. package/build/element-collector.js.map +1 -0
  88. package/build/error-helpers.d.ts +43 -0
  89. package/build/error-helpers.d.ts.map +1 -0
  90. package/build/error-helpers.js +143 -0
  91. package/build/error-helpers.js.map +1 -0
  92. package/build/formatters/console-formatter.d.ts +160 -0
  93. package/build/formatters/console-formatter.d.ts.map +1 -0
  94. package/build/formatters/console-formatter.js +338 -0
  95. package/build/formatters/console-formatter.js.map +1 -0
  96. package/build/helpers/parser-plugins.d.ts +44 -0
  97. package/build/helpers/parser-plugins.d.ts.map +1 -0
  98. package/build/helpers/parser-plugins.js +102 -0
  99. package/build/helpers/parser-plugins.js.map +1 -0
  100. package/build/helpers/paths.d.ts +79 -0
  101. package/build/helpers/paths.d.ts.map +1 -0
  102. package/build/helpers/paths.js +168 -0
  103. package/build/helpers/paths.js.map +1 -0
  104. package/build/helpers/process-liveness.d.ts +24 -0
  105. package/build/helpers/process-liveness.d.ts.map +1 -0
  106. package/build/helpers/process-liveness.js +36 -0
  107. package/build/helpers/process-liveness.js.map +1 -0
  108. package/build/helpers/process-liveness.test.d.ts +2 -0
  109. package/build/helpers/process-liveness.test.d.ts.map +1 -0
  110. package/build/helpers/process-liveness.test.js +40 -0
  111. package/build/helpers/process-liveness.test.js.map +1 -0
  112. package/build/helpers/sequence-tree.d.ts +27 -0
  113. package/build/helpers/sequence-tree.d.ts.map +1 -0
  114. package/build/helpers/sequence-tree.js +80 -0
  115. package/build/helpers/sequence-tree.js.map +1 -0
  116. package/build/helpers/sequence-tree.test.d.ts +2 -0
  117. package/build/helpers/sequence-tree.test.d.ts.map +1 -0
  118. package/build/helpers/sequence-tree.test.js +122 -0
  119. package/build/helpers/sequence-tree.test.js.map +1 -0
  120. package/build/index.d.ts +3 -0
  121. package/build/index.d.ts.map +1 -0
  122. package/build/index.js +2007 -0
  123. package/build/index.js.map +1 -0
  124. package/build/interaction-recorder.d.ts +214 -0
  125. package/build/interaction-recorder.d.ts.map +1 -0
  126. package/build/interaction-recorder.js +2007 -0
  127. package/build/interaction-recorder.js.map +1 -0
  128. package/build/issue-tracker.d.ts +107 -0
  129. package/build/issue-tracker.d.ts.map +1 -0
  130. package/build/issue-tracker.js +582 -0
  131. package/build/issue-tracker.js.map +1 -0
  132. package/build/issue-tracker.test.d.ts +2 -0
  133. package/build/issue-tracker.test.d.ts.map +1 -0
  134. package/build/issue-tracker.test.js +88 -0
  135. package/build/issue-tracker.test.js.map +1 -0
  136. package/build/launch-chrome-port.test.d.ts +14 -0
  137. package/build/launch-chrome-port.test.d.ts.map +1 -0
  138. package/build/launch-chrome-port.test.js +97 -0
  139. package/build/launch-chrome-port.test.js.map +1 -0
  140. package/build/log-processor/orchestrator.d.ts +32 -0
  141. package/build/log-processor/orchestrator.d.ts.map +1 -0
  142. package/build/log-processor/orchestrator.js +27 -0
  143. package/build/log-processor/orchestrator.js.map +1 -0
  144. package/build/logpoint-execution-tracker.d.ts +57 -0
  145. package/build/logpoint-execution-tracker.d.ts.map +1 -0
  146. package/build/logpoint-execution-tracker.js +112 -0
  147. package/build/logpoint-execution-tracker.js.map +1 -0
  148. package/build/mcp-supervisor.d.ts +3 -0
  149. package/build/mcp-supervisor.d.ts.map +1 -0
  150. package/build/mcp-supervisor.js +211 -0
  151. package/build/mcp-supervisor.js.map +1 -0
  152. package/build/messages.d.ts +165 -0
  153. package/build/messages.d.ts.map +1 -0
  154. package/build/messages.js +574 -0
  155. package/build/messages.js.map +1 -0
  156. package/build/network-monitor.d.ts +172 -0
  157. package/build/network-monitor.d.ts.map +1 -0
  158. package/build/network-monitor.js +350 -0
  159. package/build/network-monitor.js.map +1 -0
  160. package/build/overlays.d.ts +49 -0
  161. package/build/overlays.d.ts.map +1 -0
  162. package/build/overlays.js +409 -0
  163. package/build/overlays.js.map +1 -0
  164. package/build/persistent-profiles.test.d.ts +15 -0
  165. package/build/persistent-profiles.test.d.ts.map +1 -0
  166. package/build/persistent-profiles.test.js +470 -0
  167. package/build/persistent-profiles.test.js.map +1 -0
  168. package/build/port-reserver.d.ts +32 -0
  169. package/build/port-reserver.d.ts.map +1 -0
  170. package/build/port-reserver.js +118 -0
  171. package/build/port-reserver.js.map +1 -0
  172. package/build/puppeteer-manager.d.ts +47 -0
  173. package/build/puppeteer-manager.d.ts.map +1 -0
  174. package/build/puppeteer-manager.js +116 -0
  175. package/build/puppeteer-manager.js.map +1 -0
  176. package/build/recording-coverage.test.d.ts +12 -0
  177. package/build/recording-coverage.test.d.ts.map +1 -0
  178. package/build/recording-coverage.test.js +72 -0
  179. package/build/recording-coverage.test.js.map +1 -0
  180. package/build/reference-validator.d.ts +47 -0
  181. package/build/reference-validator.d.ts.map +1 -0
  182. package/build/reference-validator.js +86 -0
  183. package/build/reference-validator.js.map +1 -0
  184. package/build/reference-validator.test.d.ts +5 -0
  185. package/build/reference-validator.test.d.ts.map +1 -0
  186. package/build/reference-validator.test.js +243 -0
  187. package/build/reference-validator.test.js.map +1 -0
  188. package/build/replay-cursor.d.ts +27 -0
  189. package/build/replay-cursor.d.ts.map +1 -0
  190. package/build/replay-cursor.js +240 -0
  191. package/build/replay-cursor.js.map +1 -0
  192. package/build/runners/docker-compose-runner.d.ts +69 -0
  193. package/build/runners/docker-compose-runner.d.ts.map +1 -0
  194. package/build/runners/docker-compose-runner.js +438 -0
  195. package/build/runners/docker-compose-runner.js.map +1 -0
  196. package/build/runners/docker-runner.d.ts +65 -0
  197. package/build/runners/docker-runner.d.ts.map +1 -0
  198. package/build/runners/docker-runner.js +414 -0
  199. package/build/runners/docker-runner.js.map +1 -0
  200. package/build/runners/index.d.ts +19 -0
  201. package/build/runners/index.d.ts.map +1 -0
  202. package/build/runners/index.js +35 -0
  203. package/build/runners/index.js.map +1 -0
  204. package/build/runners/native-runner.d.ts +95 -0
  205. package/build/runners/native-runner.d.ts.map +1 -0
  206. package/build/runners/native-runner.js +370 -0
  207. package/build/runners/native-runner.js.map +1 -0
  208. package/build/runners/types.d.ts +147 -0
  209. package/build/runners/types.d.ts.map +1 -0
  210. package/build/runners/types.js +25 -0
  211. package/build/runners/types.js.map +1 -0
  212. package/build/self-restart.d.ts +12 -0
  213. package/build/self-restart.d.ts.map +1 -0
  214. package/build/self-restart.js +41 -0
  215. package/build/self-restart.js.map +1 -0
  216. package/build/self-restart.test.d.ts +2 -0
  217. package/build/self-restart.test.d.ts.map +1 -0
  218. package/build/self-restart.test.js +34 -0
  219. package/build/self-restart.test.js.map +1 -0
  220. package/build/server-claims.d.ts +161 -0
  221. package/build/server-claims.d.ts.map +1 -0
  222. package/build/server-claims.js +382 -0
  223. package/build/server-claims.js.map +1 -0
  224. package/build/server-claims.test.d.ts +2 -0
  225. package/build/server-claims.test.d.ts.map +1 -0
  226. package/build/server-claims.test.js +197 -0
  227. package/build/server-claims.test.js.map +1 -0
  228. package/build/server-manager.d.ts +438 -0
  229. package/build/server-manager.d.ts.map +1 -0
  230. package/build/server-manager.js +1674 -0
  231. package/build/server-manager.js.map +1 -0
  232. package/build/server-manager.test.d.ts +17 -0
  233. package/build/server-manager.test.d.ts.map +1 -0
  234. package/build/server-manager.test.js +122 -0
  235. package/build/server-manager.test.js.map +1 -0
  236. package/build/server-ownership.test.d.ts +2 -0
  237. package/build/server-ownership.test.d.ts.map +1 -0
  238. package/build/server-ownership.test.js +238 -0
  239. package/build/server-ownership.test.js.map +1 -0
  240. package/build/server-watcher.d.ts +21 -0
  241. package/build/server-watcher.d.ts.map +1 -0
  242. package/build/server-watcher.js +105 -0
  243. package/build/server-watcher.js.map +1 -0
  244. package/build/session-detector.d.ts +47 -0
  245. package/build/session-detector.d.ts.map +1 -0
  246. package/build/session-detector.js +229 -0
  247. package/build/session-detector.js.map +1 -0
  248. package/build/session-detector.test.d.ts +9 -0
  249. package/build/session-detector.test.d.ts.map +1 -0
  250. package/build/session-detector.test.js +45 -0
  251. package/build/session-detector.test.js.map +1 -0
  252. package/build/sourcemap-handler.d.ts +114 -0
  253. package/build/sourcemap-handler.d.ts.map +1 -0
  254. package/build/sourcemap-handler.js +479 -0
  255. package/build/sourcemap-handler.js.map +1 -0
  256. package/build/supervisor/child-manager.d.ts +43 -0
  257. package/build/supervisor/child-manager.d.ts.map +1 -0
  258. package/build/supervisor/child-manager.js +148 -0
  259. package/build/supervisor/child-manager.js.map +1 -0
  260. package/build/supervisor/child-manager.test.d.ts +2 -0
  261. package/build/supervisor/child-manager.test.d.ts.map +1 -0
  262. package/build/supervisor/child-manager.test.js +107 -0
  263. package/build/supervisor/child-manager.test.js.map +1 -0
  264. package/build/supervisor/client-watcher.d.ts +48 -0
  265. package/build/supervisor/client-watcher.d.ts.map +1 -0
  266. package/build/supervisor/client-watcher.js +186 -0
  267. package/build/supervisor/client-watcher.js.map +1 -0
  268. package/build/supervisor/client-watcher.test.d.ts +2 -0
  269. package/build/supervisor/client-watcher.test.d.ts.map +1 -0
  270. package/build/supervisor/client-watcher.test.js +121 -0
  271. package/build/supervisor/client-watcher.test.js.map +1 -0
  272. package/build/supervisor/idle-config.d.ts +30 -0
  273. package/build/supervisor/idle-config.d.ts.map +1 -0
  274. package/build/supervisor/idle-config.js +76 -0
  275. package/build/supervisor/idle-config.js.map +1 -0
  276. package/build/supervisor/idle-config.test.d.ts +2 -0
  277. package/build/supervisor/idle-config.test.d.ts.map +1 -0
  278. package/build/supervisor/idle-config.test.js +119 -0
  279. package/build/supervisor/idle-config.test.js.map +1 -0
  280. package/build/supervisor/ndjson-reader.d.ts +47 -0
  281. package/build/supervisor/ndjson-reader.d.ts.map +1 -0
  282. package/build/supervisor/ndjson-reader.js +75 -0
  283. package/build/supervisor/ndjson-reader.js.map +1 -0
  284. package/build/supervisor/ndjson-reader.test.d.ts +2 -0
  285. package/build/supervisor/ndjson-reader.test.d.ts.map +1 -0
  286. package/build/supervisor/ndjson-reader.test.js +92 -0
  287. package/build/supervisor/ndjson-reader.test.js.map +1 -0
  288. package/build/supervisor/pidfile.d.ts +11 -0
  289. package/build/supervisor/pidfile.d.ts.map +1 -0
  290. package/build/supervisor/pidfile.js +29 -0
  291. package/build/supervisor/pidfile.js.map +1 -0
  292. package/build/supervisor/pidfile.test.d.ts +8 -0
  293. package/build/supervisor/pidfile.test.d.ts.map +1 -0
  294. package/build/supervisor/pidfile.test.js +46 -0
  295. package/build/supervisor/pidfile.test.js.map +1 -0
  296. package/build/supervisor/restart-coordinator.d.ts +73 -0
  297. package/build/supervisor/restart-coordinator.d.ts.map +1 -0
  298. package/build/supervisor/restart-coordinator.js +300 -0
  299. package/build/supervisor/restart-coordinator.js.map +1 -0
  300. package/build/supervisor/restart-coordinator.test.d.ts +2 -0
  301. package/build/supervisor/restart-coordinator.test.d.ts.map +1 -0
  302. package/build/supervisor/restart-coordinator.test.js +434 -0
  303. package/build/supervisor/restart-coordinator.test.js.map +1 -0
  304. package/build/test-support/fake-execute-tool-call.d.ts +11 -0
  305. package/build/test-support/fake-execute-tool-call.d.ts.map +1 -0
  306. package/build/test-support/fake-execute-tool-call.js +19 -0
  307. package/build/test-support/fake-execute-tool-call.js.map +1 -0
  308. package/build/tool-error.d.ts +14 -0
  309. package/build/tool-error.d.ts.map +1 -0
  310. package/build/tool-error.js +18 -0
  311. package/build/tool-error.js.map +1 -0
  312. package/build/tool-response.d.ts +293 -0
  313. package/build/tool-response.d.ts.map +1 -0
  314. package/build/tool-response.js +365 -0
  315. package/build/tool-response.js.map +1 -0
  316. package/build/tools/assert-tools.d.ts +62 -0
  317. package/build/tools/assert-tools.d.ts.map +1 -0
  318. package/build/tools/assert-tools.js +330 -0
  319. package/build/tools/assert-tools.js.map +1 -0
  320. package/build/tools/breakpoint-await-abort.test.d.ts +2 -0
  321. package/build/tools/breakpoint-await-abort.test.d.ts.map +1 -0
  322. package/build/tools/breakpoint-await-abort.test.js +124 -0
  323. package/build/tools/breakpoint-await-abort.test.js.map +1 -0
  324. package/build/tools/breakpoint-tools.d.ts +99 -0
  325. package/build/tools/breakpoint-tools.d.ts.map +1 -0
  326. package/build/tools/breakpoint-tools.js +1197 -0
  327. package/build/tools/breakpoint-tools.js.map +1 -0
  328. package/build/tools/config-tools.d.ts +58 -0
  329. package/build/tools/config-tools.d.ts.map +1 -0
  330. package/build/tools/config-tools.js +180 -0
  331. package/build/tools/config-tools.js.map +1 -0
  332. package/build/tools/connection-routing.test.d.ts +12 -0
  333. package/build/tools/connection-routing.test.d.ts.map +1 -0
  334. package/build/tools/connection-routing.test.js +206 -0
  335. package/build/tools/connection-routing.test.js.map +1 -0
  336. package/build/tools/console-tools.d.ts +84 -0
  337. package/build/tools/console-tools.d.ts.map +1 -0
  338. package/build/tools/console-tools.js +241 -0
  339. package/build/tools/console-tools.js.map +1 -0
  340. package/build/tools/content-tools.d.ts +74 -0
  341. package/build/tools/content-tools.d.ts.map +1 -0
  342. package/build/tools/content-tools.js +582 -0
  343. package/build/tools/content-tools.js.map +1 -0
  344. package/build/tools/dashboard-tools.d.ts +38 -0
  345. package/build/tools/dashboard-tools.d.ts.map +1 -0
  346. package/build/tools/dashboard-tools.js +123 -0
  347. package/build/tools/dashboard-tools.js.map +1 -0
  348. package/build/tools/dom-tools.d.ts +41 -0
  349. package/build/tools/dom-tools.d.ts.map +1 -0
  350. package/build/tools/dom-tools.js +277 -0
  351. package/build/tools/dom-tools.js.map +1 -0
  352. package/build/tools/download-tools.d.ts +55 -0
  353. package/build/tools/download-tools.d.ts.map +1 -0
  354. package/build/tools/download-tools.js +280 -0
  355. package/build/tools/download-tools.js.map +1 -0
  356. package/build/tools/execution-tools.d.ts +40 -0
  357. package/build/tools/execution-tools.d.ts.map +1 -0
  358. package/build/tools/execution-tools.js +121 -0
  359. package/build/tools/execution-tools.js.map +1 -0
  360. package/build/tools/input-abort.test.d.ts +2 -0
  361. package/build/tools/input-abort.test.d.ts.map +1 -0
  362. package/build/tools/input-abort.test.js +227 -0
  363. package/build/tools/input-abort.test.js.map +1 -0
  364. package/build/tools/input-ambiguous-selector.test.d.ts +2 -0
  365. package/build/tools/input-ambiguous-selector.test.d.ts.map +1 -0
  366. package/build/tools/input-ambiguous-selector.test.js +34 -0
  367. package/build/tools/input-ambiguous-selector.test.js.map +1 -0
  368. package/build/tools/input-tools.d.ts +168 -0
  369. package/build/tools/input-tools.d.ts.map +1 -0
  370. package/build/tools/input-tools.js +1397 -0
  371. package/build/tools/input-tools.js.map +1 -0
  372. package/build/tools/inspection-tools.d.ts +112 -0
  373. package/build/tools/inspection-tools.d.ts.map +1 -0
  374. package/build/tools/inspection-tools.js +637 -0
  375. package/build/tools/inspection-tools.js.map +1 -0
  376. package/build/tools/interpolation.d.ts +30 -0
  377. package/build/tools/interpolation.d.ts.map +1 -0
  378. package/build/tools/interpolation.js +143 -0
  379. package/build/tools/interpolation.js.map +1 -0
  380. package/build/tools/issues-abort-hygiene.test.d.ts +2 -0
  381. package/build/tools/issues-abort-hygiene.test.d.ts.map +1 -0
  382. package/build/tools/issues-abort-hygiene.test.js +79 -0
  383. package/build/tools/issues-abort-hygiene.test.js.map +1 -0
  384. package/build/tools/issues-tools.d.ts +98 -0
  385. package/build/tools/issues-tools.d.ts.map +1 -0
  386. package/build/tools/issues-tools.js +821 -0
  387. package/build/tools/issues-tools.js.map +1 -0
  388. package/build/tools/issues-tools.test.d.ts +8 -0
  389. package/build/tools/issues-tools.test.d.ts.map +1 -0
  390. package/build/tools/issues-tools.test.js +184 -0
  391. package/build/tools/issues-tools.test.js.map +1 -0
  392. package/build/tools/modal-tools.d.ts +73 -0
  393. package/build/tools/modal-tools.d.ts.map +1 -0
  394. package/build/tools/modal-tools.js +161 -0
  395. package/build/tools/modal-tools.js.map +1 -0
  396. package/build/tools/network-tools.d.ts +72 -0
  397. package/build/tools/network-tools.d.ts.map +1 -0
  398. package/build/tools/network-tools.js +432 -0
  399. package/build/tools/network-tools.js.map +1 -0
  400. package/build/tools/page-tools.d.ts +78 -0
  401. package/build/tools/page-tools.d.ts.map +1 -0
  402. package/build/tools/page-tools.js +255 -0
  403. package/build/tools/page-tools.js.map +1 -0
  404. package/build/tools/plugin-tools.d.ts +16 -0
  405. package/build/tools/plugin-tools.d.ts.map +1 -0
  406. package/build/tools/plugin-tools.js +17 -0
  407. package/build/tools/plugin-tools.js.map +1 -0
  408. package/build/tools/replay-abort.test.d.ts +2 -0
  409. package/build/tools/replay-abort.test.d.ts.map +1 -0
  410. package/build/tools/replay-abort.test.js +259 -0
  411. package/build/tools/replay-abort.test.js.map +1 -0
  412. package/build/tools/replay-add-conditional.test.d.ts +2 -0
  413. package/build/tools/replay-add-conditional.test.d.ts.map +1 -0
  414. package/build/tools/replay-add-conditional.test.js +205 -0
  415. package/build/tools/replay-add-conditional.test.js.map +1 -0
  416. package/build/tools/replay-background-run.test.d.ts +2 -0
  417. package/build/tools/replay-background-run.test.d.ts.map +1 -0
  418. package/build/tools/replay-background-run.test.js +176 -0
  419. package/build/tools/replay-background-run.test.js.map +1 -0
  420. package/build/tools/replay-capture-variables.test.d.ts +2 -0
  421. package/build/tools/replay-capture-variables.test.d.ts.map +1 -0
  422. package/build/tools/replay-capture-variables.test.js +331 -0
  423. package/build/tools/replay-capture-variables.test.js.map +1 -0
  424. package/build/tools/replay-conditional-idb-and-launch.test.d.ts +11 -0
  425. package/build/tools/replay-conditional-idb-and-launch.test.d.ts.map +1 -0
  426. package/build/tools/replay-conditional-idb-and-launch.test.js +429 -0
  427. package/build/tools/replay-conditional-idb-and-launch.test.js.map +1 -0
  428. package/build/tools/replay-conditional-timeout.test.d.ts +14 -0
  429. package/build/tools/replay-conditional-timeout.test.d.ts.map +1 -0
  430. package/build/tools/replay-conditional-timeout.test.js +126 -0
  431. package/build/tools/replay-conditional-timeout.test.js.map +1 -0
  432. package/build/tools/replay-declare.test.d.ts +2 -0
  433. package/build/tools/replay-declare.test.d.ts.map +1 -0
  434. package/build/tools/replay-declare.test.js +106 -0
  435. package/build/tools/replay-declare.test.js.map +1 -0
  436. package/build/tools/replay-declared-browser-cleanup.test.d.ts +2 -0
  437. package/build/tools/replay-declared-browser-cleanup.test.d.ts.map +1 -0
  438. package/build/tools/replay-declared-browser-cleanup.test.js +103 -0
  439. package/build/tools/replay-declared-browser-cleanup.test.js.map +1 -0
  440. package/build/tools/replay-declared-profile.test.d.ts +2 -0
  441. package/build/tools/replay-declared-profile.test.d.ts.map +1 -0
  442. package/build/tools/replay-declared-profile.test.js +116 -0
  443. package/build/tools/replay-declared-profile.test.js.map +1 -0
  444. package/build/tools/replay-executor.d.ts +556 -0
  445. package/build/tools/replay-executor.d.ts.map +1 -0
  446. package/build/tools/replay-executor.js +2347 -0
  447. package/build/tools/replay-executor.js.map +1 -0
  448. package/build/tools/replay-folder-save.test.d.ts +2 -0
  449. package/build/tools/replay-folder-save.test.d.ts.map +1 -0
  450. package/build/tools/replay-folder-save.test.js +63 -0
  451. package/build/tools/replay-folder-save.test.js.map +1 -0
  452. package/build/tools/replay-formatters.d.ts +148 -0
  453. package/build/tools/replay-formatters.d.ts.map +1 -0
  454. package/build/tools/replay-formatters.js +856 -0
  455. package/build/tools/replay-formatters.js.map +1 -0
  456. package/build/tools/replay-kill-chrome-on-finish.test.d.ts +2 -0
  457. package/build/tools/replay-kill-chrome-on-finish.test.d.ts.map +1 -0
  458. package/build/tools/replay-kill-chrome-on-finish.test.js +175 -0
  459. package/build/tools/replay-kill-chrome-on-finish.test.js.map +1 -0
  460. package/build/tools/replay-multi-connection.test.d.ts +2 -0
  461. package/build/tools/replay-multi-connection.test.d.ts.map +1 -0
  462. package/build/tools/replay-multi-connection.test.js +547 -0
  463. package/build/tools/replay-multi-connection.test.js.map +1 -0
  464. package/build/tools/replay-output-and-record-options.test.d.ts +2 -0
  465. package/build/tools/replay-output-and-record-options.test.d.ts.map +1 -0
  466. package/build/tools/replay-output-and-record-options.test.js +240 -0
  467. package/build/tools/replay-output-and-record-options.test.js.map +1 -0
  468. package/build/tools/replay-production-error-shape.test.d.ts +7 -0
  469. package/build/tools/replay-production-error-shape.test.d.ts.map +1 -0
  470. package/build/tools/replay-production-error-shape.test.js +93 -0
  471. package/build/tools/replay-production-error-shape.test.js.map +1 -0
  472. package/build/tools/replay-rebase.test.d.ts +2 -0
  473. package/build/tools/replay-rebase.test.d.ts.map +1 -0
  474. package/build/tools/replay-rebase.test.js +58 -0
  475. package/build/tools/replay-rebase.test.js.map +1 -0
  476. package/build/tools/replay-retry-classification.test.d.ts +9 -0
  477. package/build/tools/replay-retry-classification.test.d.ts.map +1 -0
  478. package/build/tools/replay-retry-classification.test.js +57 -0
  479. package/build/tools/replay-retry-classification.test.js.map +1 -0
  480. package/build/tools/replay-run-registry.d.ts +55 -0
  481. package/build/tools/replay-run-registry.d.ts.map +1 -0
  482. package/build/tools/replay-run-registry.js +65 -0
  483. package/build/tools/replay-run-registry.js.map +1 -0
  484. package/build/tools/replay-sequence-reload.test.d.ts +2 -0
  485. package/build/tools/replay-sequence-reload.test.d.ts.map +1 -0
  486. package/build/tools/replay-sequence-reload.test.js +82 -0
  487. package/build/tools/replay-sequence-reload.test.js.map +1 -0
  488. package/build/tools/replay-socket-scope.test.d.ts +2 -0
  489. package/build/tools/replay-socket-scope.test.d.ts.map +1 -0
  490. package/build/tools/replay-socket-scope.test.js +75 -0
  491. package/build/tools/replay-socket-scope.test.js.map +1 -0
  492. package/build/tools/replay-step-connection.test.d.ts +2 -0
  493. package/build/tools/replay-step-connection.test.d.ts.map +1 -0
  494. package/build/tools/replay-step-connection.test.js +292 -0
  495. package/build/tools/replay-step-connection.test.js.map +1 -0
  496. package/build/tools/replay-step-timeout.test.d.ts +2 -0
  497. package/build/tools/replay-step-timeout.test.d.ts.map +1 -0
  498. package/build/tools/replay-step-timeout.test.js +204 -0
  499. package/build/tools/replay-step-timeout.test.js.map +1 -0
  500. package/build/tools/replay-tags.test.d.ts +2 -0
  501. package/build/tools/replay-tags.test.d.ts.map +1 -0
  502. package/build/tools/replay-tags.test.js +99 -0
  503. package/build/tools/replay-tags.test.js.map +1 -0
  504. package/build/tools/replay-teardown-and-foreach.test.d.ts +13 -0
  505. package/build/tools/replay-teardown-and-foreach.test.d.ts.map +1 -0
  506. package/build/tools/replay-teardown-and-foreach.test.js +318 -0
  507. package/build/tools/replay-teardown-and-foreach.test.js.map +1 -0
  508. package/build/tools/replay-tool-name-validation.test.d.ts +2 -0
  509. package/build/tools/replay-tool-name-validation.test.d.ts.map +1 -0
  510. package/build/tools/replay-tool-name-validation.test.js +280 -0
  511. package/build/tools/replay-tool-name-validation.test.js.map +1 -0
  512. package/build/tools/replay-tools.d.ts +288 -0
  513. package/build/tools/replay-tools.d.ts.map +1 -0
  514. package/build/tools/replay-tools.js +2994 -0
  515. package/build/tools/replay-tools.js.map +1 -0
  516. package/build/tools/request-abort.test.d.ts +2 -0
  517. package/build/tools/request-abort.test.d.ts.map +1 -0
  518. package/build/tools/request-abort.test.js +130 -0
  519. package/build/tools/request-abort.test.js.map +1 -0
  520. package/build/tools/request-tools.d.ts +56 -0
  521. package/build/tools/request-tools.d.ts.map +1 -0
  522. package/build/tools/request-tools.js +174 -0
  523. package/build/tools/request-tools.js.map +1 -0
  524. package/build/tools/screenshot-tools.d.ts +135 -0
  525. package/build/tools/screenshot-tools.d.ts.map +1 -0
  526. package/build/tools/screenshot-tools.js +636 -0
  527. package/build/tools/screenshot-tools.js.map +1 -0
  528. package/build/tools/server-tools.d.ts +88 -0
  529. package/build/tools/server-tools.d.ts.map +1 -0
  530. package/build/tools/server-tools.js +485 -0
  531. package/build/tools/server-tools.js.map +1 -0
  532. package/build/tools/source-tools.d.ts +46 -0
  533. package/build/tools/source-tools.d.ts.map +1 -0
  534. package/build/tools/source-tools.js +47 -0
  535. package/build/tools/source-tools.js.map +1 -0
  536. package/build/tools/storage-tools.d.ts +141 -0
  537. package/build/tools/storage-tools.d.ts.map +1 -0
  538. package/build/tools/storage-tools.js +894 -0
  539. package/build/tools/storage-tools.js.map +1 -0
  540. package/build/tools/storage-tools.test.d.ts +13 -0
  541. package/build/tools/storage-tools.test.d.ts.map +1 -0
  542. package/build/tools/storage-tools.test.js +707 -0
  543. package/build/tools/storage-tools.test.js.map +1 -0
  544. package/build/tools/tab-tools.d.ts +42 -0
  545. package/build/tools/tab-tools.d.ts.map +1 -0
  546. package/build/tools/tab-tools.js +296 -0
  547. package/build/tools/tab-tools.js.map +1 -0
  548. package/build/tools/wait-tools.d.ts +85 -0
  549. package/build/tools/wait-tools.d.ts.map +1 -0
  550. package/build/tools/wait-tools.js +236 -0
  551. package/build/tools/wait-tools.js.map +1 -0
  552. package/build/tools/wait-tools.test.d.ts +9 -0
  553. package/build/tools/wait-tools.test.d.ts.map +1 -0
  554. package/build/tools/wait-tools.test.js +193 -0
  555. package/build/tools/wait-tools.test.js.map +1 -0
  556. package/build/types.d.ts +133 -0
  557. package/build/types.d.ts.map +1 -0
  558. package/build/types.js +5 -0
  559. package/build/types.js.map +1 -0
  560. package/build/ui-verifier.d.ts +88 -0
  561. package/build/ui-verifier.d.ts.map +1 -0
  562. package/build/ui-verifier.js +540 -0
  563. package/build/ui-verifier.js.map +1 -0
  564. package/build/utils/abort.d.ts +68 -0
  565. package/build/utils/abort.d.ts.map +1 -0
  566. package/build/utils/abort.js +159 -0
  567. package/build/utils/abort.js.map +1 -0
  568. package/build/utils/abort.test.d.ts +2 -0
  569. package/build/utils/abort.test.d.ts.map +1 -0
  570. package/build/utils/abort.test.js +174 -0
  571. package/build/utils/abort.test.js.map +1 -0
  572. package/build/utils/modal-detection-core.d.ts +150 -0
  573. package/build/utils/modal-detection-core.d.ts.map +1 -0
  574. package/build/utils/modal-detection-core.js +332 -0
  575. package/build/utils/modal-detection-core.js.map +1 -0
  576. package/build/utils/modal-detection-core.test.d.ts +5 -0
  577. package/build/utils/modal-detection-core.test.d.ts.map +1 -0
  578. package/build/utils/modal-detection-core.test.js +492 -0
  579. package/build/utils/modal-detection-core.test.js.map +1 -0
  580. package/build/utils/modal-detector.d.ts +29 -0
  581. package/build/utils/modal-detector.d.ts.map +1 -0
  582. package/build/utils/modal-detector.js +281 -0
  583. package/build/utils/modal-detector.js.map +1 -0
  584. package/build/utils/modal-dismissal.d.ts +32 -0
  585. package/build/utils/modal-dismissal.d.ts.map +1 -0
  586. package/build/utils/modal-dismissal.js +190 -0
  587. package/build/utils/modal-dismissal.js.map +1 -0
  588. package/build/utils/port-check.d.ts +31 -0
  589. package/build/utils/port-check.d.ts.map +1 -0
  590. package/build/utils/port-check.js +87 -0
  591. package/build/utils/port-check.js.map +1 -0
  592. package/build/utils/port-check.test.d.ts +2 -0
  593. package/build/utils/port-check.test.d.ts.map +1 -0
  594. package/build/utils/port-check.test.js +66 -0
  595. package/build/utils/port-check.test.js.map +1 -0
  596. package/build/utils/selector-resolver.d.ts +66 -0
  597. package/build/utils/selector-resolver.d.ts.map +1 -0
  598. package/build/utils/selector-resolver.js +200 -0
  599. package/build/utils/selector-resolver.js.map +1 -0
  600. package/build/validation-helpers.d.ts +41 -0
  601. package/build/validation-helpers.d.ts.map +1 -0
  602. package/build/validation-helpers.js +236 -0
  603. package/build/validation-helpers.js.map +1 -0
  604. package/build/window-sizing.d.ts +26 -0
  605. package/build/window-sizing.d.ts.map +1 -0
  606. package/build/window-sizing.js +87 -0
  607. package/build/window-sizing.js.map +1 -0
  608. package/build/window-sizing.test.d.ts +8 -0
  609. package/build/window-sizing.test.d.ts.map +1 -0
  610. package/build/window-sizing.test.js +113 -0
  611. package/build/window-sizing.test.js.map +1 -0
  612. package/docs/README.md +74 -0
  613. package/docs/automation.md +562 -0
  614. package/docs/debugging.md +229 -0
  615. package/docs/installation.md +133 -0
  616. package/docs/instructions.md +227 -0
  617. package/docs/mcp-instructions.md +15 -0
  618. package/docs/message-system.md +327 -0
  619. package/docs/messages.md +3480 -0
  620. package/docs/parser-plugins.md +132 -0
  621. package/docs/replay.md +1411 -0
  622. package/docs/troubleshooting.md +272 -0
  623. package/docs/working_with_llm_notes.md +5 -0
  624. package/package.json +87 -0
  625. package/skills/devharness/SKILL.md +195 -0
  626. package/skills/devharness/references/sequences.md +613 -0
  627. package/skills/devharness/references/tool-categories.md +99 -0
@@ -0,0 +1,1397 @@
1
+ /**
2
+ * Input Automation Tools
3
+ */
4
+ import { z } from 'zod';
5
+ import { executeWithPauseDetection } from '../debugger-aware-wrapper.js';
6
+ import { checkBrowserAutomation } from '../error-helpers.js';
7
+ import { createTool } from '../validation-helpers.js';
8
+ import { configManager } from '../config.js';
9
+ import { createSuccessResponse, createErrorResponse } from '../messages.js';
10
+ import { isElementBlocked, detectModals } from '../utils/modal-detector.js';
11
+ import { dismissModalByStrategy, selectDismissalStrategy } from '../utils/modal-dismissal.js';
12
+ import { resolveSelector, isExtendedSelector, cleanupResolvedSelector } from '../utils/selector-resolver.js';
13
+ import { domChangeMonitor, formatDOMChanges } from '../dom-change-monitor.js';
14
+ import { abortErrorFor, abortableSleep, isAbortError, throwIfAborted } from '../utils/abort.js';
15
+ // Coordinate schema for mouse actions
16
+ const coordinateSchema = z.object({
17
+ x: z.number(),
18
+ y: z.number(),
19
+ });
20
+ // Consolidated input tool schema
21
+ const inputToolSchema = z.object({
22
+ action: z.enum(['click', 'type', 'press', 'hover', 'focus', 'focusNext', 'focusPrevious', 'drag', 'scroll', 'mousemove', 'pinch', 'tap', 'swipe']),
23
+ connectionReason: z.string(),
24
+ // Selector-based actions
25
+ selector: z.string().optional().describe('CSS selector. Supports :has-text("x"), :text("x")'),
26
+ handleModals: z.boolean().optional(),
27
+ dismissStrategy: z.enum(['accept', 'reject', 'close', 'remove', 'auto']).optional(),
28
+ // click
29
+ clickCount: z.number().optional(),
30
+ // type
31
+ text: z.string().optional(),
32
+ delay: z.number().optional().describe('Keystroke delay ms'),
33
+ append: z.boolean().optional().describe('Append text instead of replacing (default: false)'),
34
+ // press
35
+ key: z.string().optional(),
36
+ // focusNext/focusPrevious
37
+ count: z.number().optional().describe('Tab count'),
38
+ // drag
39
+ from: coordinateSchema.optional(),
40
+ to: coordinateSchema.optional(),
41
+ steps: z.number().optional().describe('Drag smoothness'),
42
+ // swipe: gesture on an element, instead of from/to pixels
43
+ direction: z.enum(['left', 'right', 'up', 'down']).optional()
44
+ .describe("swipe: gesture direction across `selector`'s box, resolved at run time. Use this rather than from/to for anything in a list - a row's coordinates move with the rows above it"),
45
+ distance: z.number().optional()
46
+ .describe('swipe: travel in px. Default is a short, reveal-sized nudge (60% of the element, capped at 96px) because a long fast swipe commits the destructive action on rows that have ranges - pass a larger distance deliberately to over-drag'),
47
+ durationMs: z.number().optional()
48
+ .describe('swipe: how long the gesture takes, spread across `steps` (default 300 - a deliberate drag). Velocity is a real input to gesture handling: unpaced moves go out as fast as the transport allows, which reads as a flick, and a flick is a DIFFERENT gesture - on a swipe-action row it commits the destructive action instead of revealing it. Pass a small value deliberately to test the flick'),
49
+ // scroll
50
+ deltaX: z.number().optional().describe('Horizontal scroll px'),
51
+ deltaY: z.number().optional().describe('Vertical scroll px'),
52
+ x: z.number().optional(),
53
+ y: z.number().optional(),
54
+ // pinch
55
+ scaleFactor: z.number().optional().describe('>1 zoom in, <1 zoom out'),
56
+ // Change detection
57
+ detectChanges: z.boolean().optional(),
58
+ settleTimeout: z.number().optional().describe('DOM settle timeout ms'),
59
+ }).strict();
60
+ /**
61
+ * Wrapper to execute input actions while bypassing the replay blocker overlay.
62
+ * Sets __cdpReplayClickInProgress flag before the action and clears it after.
63
+ * This allows CDP-dispatched events to pass through the overlay's event listeners.
64
+ */
65
+ async function withReplayBypass(page, action) {
66
+ await page.evaluate(() => { globalThis.__cdpReplayClickInProgress = true; });
67
+ try {
68
+ return await action();
69
+ }
70
+ finally {
71
+ // Best-effort: never let a failure clearing the flag mask the action's
72
+ // own outcome (in particular an AbortError thrown mid-action).
73
+ try {
74
+ await page.evaluate(() => { globalThis.__cdpReplayClickInProgress = false; });
75
+ }
76
+ catch { /* page may have navigated/closed */ }
77
+ }
78
+ }
79
+ /**
80
+ * Warning text when a selector matches more than one element.
81
+ *
82
+ * The action takes the first match. On a list UI that silently drifts onto the
83
+ * wrong row as the list grows, and the step still reports success - so the
84
+ * count has to be said out loud even though the action succeeded.
85
+ *
86
+ * Advisory only: a selector the browser rejects is the action's error to
87
+ * report, not this helper's.
88
+ */
89
+ export async function ambiguousSelectorWarning(page, selector, raw) {
90
+ try {
91
+ const count = await page.evaluate((sel) => globalThis.document.querySelectorAll(sel).length, selector);
92
+ if (count > 1) {
93
+ return `\`${raw}\` matched ${count} elements - acted on the first. Narrow it if you meant a specific one.`;
94
+ }
95
+ }
96
+ catch {
97
+ // Counting is best-effort.
98
+ }
99
+ return undefined;
100
+ }
101
+ export function createInputTools(puppeteerManager, cdpManager, connectionManager, resolveConnectionFromReason) {
102
+ return {
103
+ input: createTool('Perform browser input actions. Actions: click (click element), type (type text into element), press (press keyboard key), hover (hover over element), focus (focus element by selector), focusNext (Tab to next focusable element), focusPrevious (Shift+Tab to previous focusable element), drag (drag from one point to another), scroll (scroll wheel at position), mousemove (move mouse to position), pinch (pinch zoom gesture), tap (real touch tap - selector or x/y), swipe (real touch drag - selector + direction, or raw from/to - for touch-only gestures the mouse cannot drive)', inputToolSchema,
104
+ // abortSignal (#110): input events cannot be recalled once dispatched -
105
+ // Input.dispatchMouseEvent on the wire WILL be processed by Chrome. What
106
+ // cancellation buys here is *not dispatching* events that have not gone
107
+ // out yet: checkpoints sit after connection/selector resolution and
108
+ // immediately before every dispatch, and multi-dispatch paths (Tab
109
+ // loops, drag stepping, clear-and-retype) abort between events. On
110
+ // abort the handler THROWS an abort-shaped error; already-issued
111
+ // dispatches are NOT undone.
112
+ async (args, abortSignal) => {
113
+ const { action, connectionReason } = args;
114
+ throwIfAborted(abortSignal);
115
+ // Resolve connection from reason
116
+ const resolved = await resolveConnectionFromReason(connectionReason);
117
+ if (!resolved) {
118
+ return createErrorResponse('CONNECTION_NOT_FOUND', {
119
+ message: 'No Chrome browser available. Use `launchChrome` first to start a browser.'
120
+ });
121
+ }
122
+ const targetPuppeteerManager = resolved.puppeteerManager || puppeteerManager;
123
+ const targetCdpManager = resolved.cdpManager;
124
+ const error = checkBrowserAutomation(targetCdpManager, targetPuppeteerManager, action, resolved.connection.port);
125
+ if (error) {
126
+ return error;
127
+ }
128
+ const page = targetPuppeteerManager.getPage();
129
+ // Determine if change detection is enabled
130
+ const changeConfig = configManager.getChangeDetectionConfig();
131
+ const shouldDetectChanges = args.detectChanges ?? changeConfig.enabled;
132
+ const settleTimeout = args.settleTimeout ?? changeConfig.settleTimeout;
133
+ // Start observing DOM changes before interaction
134
+ if (shouldDetectChanges && ['click', 'type', 'hover'].includes(action)) {
135
+ await domChangeMonitor.startObserving(connectionReason, page);
136
+ }
137
+ // Abort checkpoint used at every point where nothing (further) has
138
+ // been dispatched yet: tears down the DOM-change observer
139
+ // (best-effort, no settle wait) so a cancelled action does not leave
140
+ // a MutationObserver running in the page, then throws abort-shaped.
141
+ const checkAborted = async () => {
142
+ if (!abortSignal?.aborted)
143
+ return;
144
+ if (shouldDetectChanges && domChangeMonitor.isObserving(connectionReason)) {
145
+ try {
146
+ await domChangeMonitor.stopObserving(connectionReason, { settleTimeout: 0 });
147
+ }
148
+ catch { /* cleanup is best-effort */ }
149
+ }
150
+ throw abortErrorFor(abortSignal);
151
+ };
152
+ await checkAborted();
153
+ switch (action) {
154
+ case 'click': {
155
+ const { selector: rawSelector, clickCount = 1, handleModals = false, dismissStrategy = 'auto', x, y } = args;
156
+ // Coordinate-based click (for canvas/3D apps)
157
+ if (typeof x === 'number' && typeof y === 'number') {
158
+ await checkAborted(); // last exit before the click goes on the wire
159
+ await withReplayBypass(page, () => page.mouse.click(x, y, { clickCount }));
160
+ return {
161
+ content: [{
162
+ type: 'text',
163
+ text: `Clicked at coordinates (${x}, ${y})`
164
+ }]
165
+ };
166
+ }
167
+ if (!rawSelector) {
168
+ return {
169
+ content: [
170
+ {
171
+ type: 'text',
172
+ text: `## Error\n\nMissing required parameter: \`selector\` or coordinates (\`x\`, \`y\`)\n\n**Action:** click\n\n**Suggestion:** Provide a CSS selector for the element to click, or x/y coordinates for coordinate-based clicking.`,
173
+ },
174
+ ],
175
+ isError: true,
176
+ };
177
+ }
178
+ // Capture pre-click URL for validation metadata
179
+ const preClickUrl = page.url();
180
+ // Resolve extended selectors (like :has-text())
181
+ let selector = rawSelector;
182
+ let selectorWarning;
183
+ if (isExtendedSelector(rawSelector)) {
184
+ const resolved = await resolveSelector(page, rawSelector);
185
+ if ('error' in resolved) {
186
+ return createErrorResponse('ELEMENT_NOT_FOUND', {
187
+ selector: rawSelector,
188
+ suggestion: resolved.suggestion,
189
+ });
190
+ }
191
+ selector = resolved.selector;
192
+ selectorWarning = resolved.warning;
193
+ }
194
+ {
195
+ const ambiguous = await ambiguousSelectorWarning(page, selector, rawSelector);
196
+ if (ambiguous)
197
+ selectorWarning = selectorWarning ? `${selectorWarning} ${ambiguous}` : ambiguous;
198
+ }
199
+ await checkAborted(); // after selector resolution, before any dispatch
200
+ const result = await executeWithPauseDetection(targetCdpManager, async () => {
201
+ // Check if element exists and is clickable
202
+ const element = await page.$(selector);
203
+ if (!element) {
204
+ return {
205
+ error: `Element not found: ${selector}`,
206
+ };
207
+ }
208
+ // Check if element is blocked by modal
209
+ const blockingCheck = await isElementBlocked(page, selector);
210
+ if (blockingCheck.blocked && blockingCheck.blockingModal) {
211
+ if (handleModals) {
212
+ // Auto-dismiss modal. Dismissal DISPATCHES too (an Escape
213
+ // key or a click on the dismiss control), so it needs its
214
+ // own pre-dispatch checkpoint - not just the one before the
215
+ // real action below.
216
+ await checkAborted();
217
+ const dismissResult = await dismissModalHelper(page, blockingCheck.blockingModal.selector, dismissStrategy);
218
+ // Check if dismissal was successful
219
+ if (!dismissResult.success) {
220
+ return {
221
+ error: `Failed to dismiss blocking modal: ${dismissResult.error || 'Unknown error'}`,
222
+ blockingModal: blockingCheck.blockingModal,
223
+ };
224
+ }
225
+ // Re-check if element is still blocked
226
+ const recheckBlocking = await isElementBlocked(page, selector);
227
+ if (recheckBlocking.blocked) {
228
+ return {
229
+ error: `Element still blocked after dismissing modal`,
230
+ blockingModal: recheckBlocking.blockingModal,
231
+ };
232
+ }
233
+ }
234
+ else {
235
+ // Return error with modal information
236
+ return {
237
+ error: `Element is blocked by modal`,
238
+ blockingModal: blockingCheck.blockingModal,
239
+ suggestion: `Enable handleModals parameter or call dismissModal tool first`,
240
+ };
241
+ }
242
+ }
243
+ // Check if element has click handlers
244
+ const hasClickHandler = await page.evaluate((sel) => {
245
+ const el = globalThis.document.querySelector(sel);
246
+ if (!el)
247
+ return false;
248
+ // Check for onclick attribute
249
+ if (el.onclick)
250
+ return true;
251
+ // Check for addEventListener listeners (limited - can't detect all)
252
+ // Check if element or ancestors have event listeners by testing common patterns
253
+ let current = el;
254
+ while (current) {
255
+ // Check for common click-related attributes
256
+ if (current.hasAttribute('onclick'))
257
+ return true;
258
+ if (current.hasAttribute('data-action'))
259
+ return true;
260
+ // Check for interactive elements that typically have handlers
261
+ const tag = current.tagName.toLowerCase();
262
+ if (tag === 'button' || tag === 'a' || tag === 'input')
263
+ return true;
264
+ // Check for cursor pointer (often indicates clickable)
265
+ const style = globalThis.window.getComputedStyle(current);
266
+ if (style.cursor === 'pointer')
267
+ return true;
268
+ current = current.parentElement;
269
+ }
270
+ return false;
271
+ }, selector);
272
+ // Perform the click - use wrapper to bypass replay blocker overlay
273
+ await checkAborted(); // last exit before the click goes on the wire
274
+ await withReplayBypass(page, () => page.click(selector, { clickCount }));
275
+ // Check if breakpoint was hit during click - if so, skip post-click evaluation
276
+ // which would hang because page JS is paused
277
+ if (targetCdpManager.isPaused()) {
278
+ return {
279
+ selector,
280
+ clickCount,
281
+ hasClickHandler,
282
+ postClickState: null,
283
+ pausedDuringClick: true,
284
+ };
285
+ }
286
+ // Get post-click state
287
+ const postClickState = await page.evaluate((clickedSelector) => {
288
+ const focused = globalThis.document.activeElement;
289
+ let focusInfo = null;
290
+ if (focused && focused !== globalThis.document.body) {
291
+ const tag = focused.tagName.toLowerCase();
292
+ const text = focused.textContent?.trim().substring(0, 30) || '';
293
+ const ariaLabel = focused.getAttribute('aria-label') || '';
294
+ const placeholder = focused.getAttribute('placeholder') || '';
295
+ const type = focused.getAttribute('type') || '';
296
+ focusInfo = {
297
+ tag,
298
+ type: type || undefined,
299
+ text: text || ariaLabel || placeholder || undefined,
300
+ isInput: ['input', 'textarea', 'select'].includes(tag),
301
+ };
302
+ }
303
+ // Get tabbable elements
304
+ const tabbable = Array.from(globalThis.document.querySelectorAll('a[href], button, input, select, textarea, [tabindex]:not([tabindex="-1"])')).filter((el) => {
305
+ const style = globalThis.window.getComputedStyle(el);
306
+ return style.display !== 'none' && style.visibility !== 'hidden' && !el.disabled;
307
+ });
308
+ // Helper to format element for display
309
+ const formatEl = (el) => {
310
+ const tag = el.tagName.toLowerCase();
311
+ const text = el.textContent?.trim().substring(0, 20) || el.getAttribute('aria-label') || el.getAttribute('placeholder') || '';
312
+ const type = el.getAttribute('type');
313
+ if (tag === 'input') {
314
+ return `${tag}[${type || 'text'}]${text ? ` "${text}"` : ''}`;
315
+ }
316
+ return `${tag}${text ? ` "${text}"` : ''}`;
317
+ };
318
+ let prevTabbable = [];
319
+ let nextTabbable = [];
320
+ if (focused) {
321
+ const currentIndex = tabbable.indexOf(focused);
322
+ if (currentIndex !== -1) {
323
+ // Previous 5 tabbable elements
324
+ prevTabbable = tabbable.slice(Math.max(0, currentIndex - 5), currentIndex).map(formatEl);
325
+ // Next 5 tabbable elements
326
+ nextTabbable = tabbable.slice(currentIndex + 1, currentIndex + 6).map(formatEl);
327
+ }
328
+ }
329
+ // Get child interactive elements of clicked element
330
+ let childInteractive = [];
331
+ try {
332
+ const clickedEl = globalThis.document.querySelector(clickedSelector);
333
+ if (clickedEl) {
334
+ const children = clickedEl.querySelectorAll('a[href], button, input, select, textarea');
335
+ childInteractive = Array.from(children).slice(0, 10).map((el) => formatEl(el));
336
+ }
337
+ }
338
+ catch (e) {
339
+ // Selector may have been cleaned up, ignore
340
+ }
341
+ return {
342
+ focusInfo,
343
+ prevTabbable,
344
+ nextTabbable,
345
+ childInteractive,
346
+ url: globalThis.window.location.href,
347
+ };
348
+ }, selector);
349
+ return {
350
+ selector,
351
+ clickCount,
352
+ hasClickHandler,
353
+ postClickState,
354
+ warning: !hasClickHandler ? 'Element may not have a click handler attached. Click was performed but may not trigger any action.' : undefined,
355
+ };
356
+ }, 'click');
357
+ // If paused at breakpoint, return immediately - don't try any more page interactions
358
+ if (result.pausedAtBreakpoint || result.result?.pausedDuringClick) {
359
+ // Stop observing without waiting
360
+ if (shouldDetectChanges) {
361
+ await domChangeMonitor.stopObserving(connectionReason, { settleTimeout: 0 });
362
+ }
363
+ // Get pause info if we detected pause inside the action
364
+ const pauseInfo = result.pauseInfo || (result.result?.pausedDuringClick ? (() => {
365
+ const info = targetCdpManager.getPausedInfo();
366
+ return info.location ? {
367
+ url: info.location.url,
368
+ lineNumber: info.location.lineNumber,
369
+ } : undefined;
370
+ })() : undefined);
371
+ return createSuccessResponse('ACTION_PAUSED_AT_BREAKPOINT', {
372
+ action: 'click',
373
+ selector: rawSelector,
374
+ ...pauseInfo,
375
+ });
376
+ }
377
+ // Collect DOM changes
378
+ let changesText = '';
379
+ let changes = null;
380
+ if (shouldDetectChanges) {
381
+ changes = await domChangeMonitor.stopObserving(connectionReason, { settleTimeout, signal: abortSignal });
382
+ changesText = formatDOMChanges(changes);
383
+ }
384
+ // Clean up temporary selector attribute
385
+ await cleanupResolvedSelector(page, selector);
386
+ // Check if element was not found
387
+ if (!result.result || result.result.error) {
388
+ // Check if error is due to blocking modal
389
+ if (result.result?.blockingModal) {
390
+ return createErrorResponse('ELEMENT_BLOCKED_BY_MODAL', {
391
+ selector: rawSelector,
392
+ modalType: result.result.blockingModal.type,
393
+ modalDescription: result.result.blockingModal.description,
394
+ modalSelector: result.result.blockingModal.selector,
395
+ suggestion: result.result.suggestion,
396
+ availableStrategies: result.result.blockingModal.dismissStrategies,
397
+ });
398
+ }
399
+ return createErrorResponse('ELEMENT_NOT_FOUND', { selector: rawSelector });
400
+ }
401
+ // Build post-click info string
402
+ const postClick = result.result.postClickState;
403
+ let postClickInfo = '';
404
+ if (postClick?.focusInfo) {
405
+ const f = postClick.focusInfo;
406
+ if (f.isInput) {
407
+ postClickInfo = `\n**Focus:** ${f.tag}${f.type ? `[type="${f.type}"]` : ''} ${f.text ? `"${f.text}"` : ''}`;
408
+ }
409
+ else if (f.tag !== 'body') {
410
+ postClickInfo = `\n**Focus:** ${f.tag}${f.text ? ` "${f.text}"` : ''}`;
411
+ }
412
+ }
413
+ if (postClick?.prevTabbable?.length > 0) {
414
+ postClickInfo += `\n**Prev tab:** ${postClick.prevTabbable.join(' ← ')}`;
415
+ }
416
+ if (postClick?.nextTabbable?.length > 0) {
417
+ postClickInfo += `\n**Next tab:** ${postClick.nextTabbable.join(' → ')}`;
418
+ }
419
+ if (postClick?.childInteractive?.length > 0) {
420
+ postClickInfo += `\n**Contains:** ${postClick.childInteractive.join(', ')}`;
421
+ }
422
+ // Build _meta for click validation
423
+ const postClickUrl = postClick?.url || page.url();
424
+ const clickMeta = {
425
+ tool: 'input',
426
+ action: 'click',
427
+ timestamp: Date.now(),
428
+ click: {
429
+ selector: rawSelector,
430
+ preClickUrl,
431
+ postClickUrl,
432
+ navigationOccurred: preClickUrl !== postClickUrl,
433
+ hasClickHandler: result.result.hasClickHandler ?? false,
434
+ domChanges: changes ? {
435
+ mutationCount: changes.mutationCount,
436
+ added: changes.added?.length || 0,
437
+ removed: changes.removed?.length || 0,
438
+ shown: changes.shown?.length || 0,
439
+ hidden: changes.hidden?.length || 0,
440
+ } : null,
441
+ },
442
+ };
443
+ // Return success with warning if no click handler detected
444
+ if (result.result.warning) {
445
+ const response = createSuccessResponse('ELEMENT_CLICK_WARNING', { selector: rawSelector, warning: selectorWarning });
446
+ response._meta = clickMeta;
447
+ return response;
448
+ }
449
+ // Include warning about multiple matches if applicable
450
+ if (selectorWarning) {
451
+ return {
452
+ content: [
453
+ {
454
+ type: 'text',
455
+ text: `Clicked element \`${rawSelector}\`${changesText}${postClickInfo}\n\n**Warning:** ${selectorWarning}`,
456
+ },
457
+ ],
458
+ _meta: clickMeta,
459
+ };
460
+ }
461
+ // Default success response with post-click info and changes
462
+ return {
463
+ content: [
464
+ {
465
+ type: 'text',
466
+ text: `Clicked element: \`${rawSelector}\`${changesText}${postClickInfo}`,
467
+ },
468
+ ],
469
+ _meta: clickMeta,
470
+ };
471
+ }
472
+ case 'type': {
473
+ const { selector: rawSelector, text, delay = 0, handleModals = false, dismissStrategy = 'auto', append = false } = args;
474
+ if (!rawSelector) {
475
+ return {
476
+ content: [
477
+ {
478
+ type: 'text',
479
+ text: `## Error\n\nMissing required parameter: \`selector\`\n\n**Action:** type\n\n**Suggestion:** Provide a CSS selector for the input element.`,
480
+ },
481
+ ],
482
+ isError: true,
483
+ };
484
+ }
485
+ if (!text) {
486
+ return {
487
+ content: [
488
+ {
489
+ type: 'text',
490
+ text: `## Error\n\nMissing required parameter: \`text\`\n\n**Action:** type\n\n**Suggestion:** Provide text to type into the element.`,
491
+ },
492
+ ],
493
+ isError: true,
494
+ };
495
+ }
496
+ // Resolve extended selectors (like :has-text())
497
+ let selector = rawSelector;
498
+ let selectorWarning;
499
+ if (isExtendedSelector(rawSelector)) {
500
+ const resolved = await resolveSelector(page, rawSelector);
501
+ if ('error' in resolved) {
502
+ return createErrorResponse('ELEMENT_NOT_FOUND', {
503
+ selector: rawSelector,
504
+ suggestion: resolved.suggestion,
505
+ });
506
+ }
507
+ selector = resolved.selector;
508
+ selectorWarning = resolved.warning;
509
+ }
510
+ {
511
+ const ambiguous = await ambiguousSelectorWarning(page, selector, rawSelector);
512
+ if (ambiguous)
513
+ selectorWarning = selectorWarning ? `${selectorWarning} ${ambiguous}` : ambiguous;
514
+ }
515
+ await checkAborted(); // after selector resolution, before any dispatch
516
+ const result = await executeWithPauseDetection(targetCdpManager, async () => {
517
+ // Check if element exists first
518
+ const element = await page.$(selector);
519
+ if (!element) {
520
+ return { error: `Element not found: ${selector}` };
521
+ }
522
+ // Check if element is blocked by modal
523
+ const blockingCheck = await isElementBlocked(page, selector);
524
+ if (blockingCheck.blocked && blockingCheck.blockingModal) {
525
+ if (handleModals) {
526
+ // Auto-dismiss modal. Dismissal DISPATCHES too (an Escape
527
+ // key or a click on the dismiss control), so it needs its
528
+ // own pre-dispatch checkpoint - not just the one before the
529
+ // real action below.
530
+ await checkAborted();
531
+ const dismissResult = await dismissModalHelper(page, blockingCheck.blockingModal.selector, dismissStrategy);
532
+ // Check if dismissal was successful
533
+ if (!dismissResult.success) {
534
+ return {
535
+ error: `Failed to dismiss blocking modal: ${dismissResult.error || 'Unknown error'}`,
536
+ blockingModal: blockingCheck.blockingModal,
537
+ };
538
+ }
539
+ // Re-check if element is still blocked
540
+ const recheckBlocking = await isElementBlocked(page, selector);
541
+ if (recheckBlocking.blocked) {
542
+ return {
543
+ error: `Element still blocked after dismissing modal`,
544
+ blockingModal: recheckBlocking.blockingModal,
545
+ };
546
+ }
547
+ }
548
+ else {
549
+ // Return error with modal information
550
+ return {
551
+ error: `Element is blocked by modal`,
552
+ blockingModal: blockingCheck.blockingModal,
553
+ suggestion: `Enable handleModals parameter or call dismissModal tool first`,
554
+ };
555
+ }
556
+ }
557
+ // Clear existing text first (unless append mode)
558
+ await checkAborted(); // last exit before keystrokes go on the wire
559
+ if (!append) {
560
+ await withReplayBypass(page, async () => {
561
+ await page.click(selector, { clickCount: 3 });
562
+ await page.keyboard.press('Backspace');
563
+ });
564
+ // Abortable between clear and retype: the clear that went
565
+ // out stays out, but the new text is not dispatched.
566
+ await checkAborted();
567
+ }
568
+ // Type new text - use wrapper to bypass replay blocker overlay
569
+ await withReplayBypass(page, () => page.type(selector, text, { delay }));
570
+ // Get the actual value after typing
571
+ const currentValue = await page.$eval(selector, (el) => {
572
+ const element = el;
573
+ if ('value' in element && element.value !== undefined) {
574
+ return element.value;
575
+ }
576
+ return element.textContent || '';
577
+ });
578
+ return { selector, text, currentValue };
579
+ }, 'typeText');
580
+ // If paused at breakpoint, return immediately - don't try any more page interactions
581
+ if (result.pausedAtBreakpoint) {
582
+ if (shouldDetectChanges) {
583
+ await domChangeMonitor.stopObserving(connectionReason, { settleTimeout: 0 });
584
+ }
585
+ return createSuccessResponse('ACTION_PAUSED_AT_BREAKPOINT', {
586
+ action: 'type',
587
+ selector: rawSelector,
588
+ ...result.pauseInfo,
589
+ });
590
+ }
591
+ // Collect DOM changes
592
+ let changesText = '';
593
+ if (shouldDetectChanges) {
594
+ const changes = await domChangeMonitor.stopObserving(connectionReason, { settleTimeout, signal: abortSignal });
595
+ changesText = formatDOMChanges(changes);
596
+ }
597
+ // Clean up temporary selector attribute
598
+ await cleanupResolvedSelector(page, selector);
599
+ // Check if element was not found
600
+ if (result.result?.error) {
601
+ // Check if error is due to blocking modal
602
+ if (result.result?.blockingModal) {
603
+ return createErrorResponse('ELEMENT_BLOCKED_BY_MODAL', {
604
+ selector: rawSelector,
605
+ modalType: result.result.blockingModal.type,
606
+ modalDescription: result.result.blockingModal.description,
607
+ modalSelector: result.result.blockingModal.selector,
608
+ suggestion: result.result.suggestion,
609
+ availableStrategies: result.result.blockingModal.dismissStrategies,
610
+ });
611
+ }
612
+ return createErrorResponse('ELEMENT_NOT_FOUND', { selector: rawSelector });
613
+ }
614
+ // Include warning about multiple matches if applicable
615
+ if (selectorWarning) {
616
+ return {
617
+ content: [
618
+ {
619
+ type: 'text',
620
+ text: `Typed into element \`${rawSelector}\`${changesText}\n\nCurrent value: ${result.result?.currentValue}\n\n**Warning:** ${selectorWarning}`,
621
+ },
622
+ ],
623
+ };
624
+ }
625
+ // Default success response with changes
626
+ return {
627
+ content: [
628
+ {
629
+ type: 'text',
630
+ text: `Text typed into \`${rawSelector}\`: "${text}"${changesText}`,
631
+ },
632
+ ],
633
+ };
634
+ }
635
+ case 'press': {
636
+ const { key } = args;
637
+ if (!key) {
638
+ return {
639
+ content: [
640
+ {
641
+ type: 'text',
642
+ text: `## Error\n\nMissing required parameter: \`key\`\n\n**Action:** press\n\n**Suggestion:** Provide a key name to press (e.g., "Enter", "Tab", "Escape").`,
643
+ },
644
+ ],
645
+ isError: true,
646
+ };
647
+ }
648
+ // Use wrapper to bypass replay blocker overlay for key press
649
+ await checkAborted(); // last exit before the key press goes on the wire
650
+ await withReplayBypass(page, () => executeWithPauseDetection(targetCdpManager, () => page.keyboard.press(key), 'pressKey'));
651
+ return createSuccessResponse('KEY_PRESS_SUCCESS', {
652
+ key
653
+ });
654
+ }
655
+ case 'hover': {
656
+ const { selector: rawSelector, handleModals = false, dismissStrategy = 'auto' } = args;
657
+ if (!rawSelector) {
658
+ return {
659
+ content: [
660
+ {
661
+ type: 'text',
662
+ text: `## Error\n\nMissing required parameter: \`selector\`\n\n**Action:** hover\n\n**Suggestion:** Provide a CSS selector for the element to hover over.`,
663
+ },
664
+ ],
665
+ isError: true,
666
+ };
667
+ }
668
+ // Resolve extended selectors (like :has-text())
669
+ let selector = rawSelector;
670
+ let selectorWarning;
671
+ if (isExtendedSelector(rawSelector)) {
672
+ const resolved = await resolveSelector(page, rawSelector);
673
+ if ('error' in resolved) {
674
+ return createErrorResponse('ELEMENT_NOT_FOUND', {
675
+ selector: rawSelector,
676
+ suggestion: resolved.suggestion,
677
+ });
678
+ }
679
+ selector = resolved.selector;
680
+ selectorWarning = resolved.warning;
681
+ }
682
+ {
683
+ const ambiguous = await ambiguousSelectorWarning(page, selector, rawSelector);
684
+ if (ambiguous)
685
+ selectorWarning = selectorWarning ? `${selectorWarning} ${ambiguous}` : ambiguous;
686
+ }
687
+ await checkAborted(); // after selector resolution, before any dispatch
688
+ const result = await executeWithPauseDetection(targetCdpManager, async () => {
689
+ // Check if element exists first
690
+ const element = await page.$(selector);
691
+ if (!element) {
692
+ return { error: `Element not found: ${selector}` };
693
+ }
694
+ // Check if element is blocked by modal
695
+ const blockingCheck = await isElementBlocked(page, selector);
696
+ if (blockingCheck.blocked && blockingCheck.blockingModal) {
697
+ if (handleModals) {
698
+ // Auto-dismiss modal. Dismissal DISPATCHES too (an Escape
699
+ // key or a click on the dismiss control), so it needs its
700
+ // own pre-dispatch checkpoint - not just the one before the
701
+ // real action below.
702
+ await checkAborted();
703
+ const dismissResult = await dismissModalHelper(page, blockingCheck.blockingModal.selector, dismissStrategy);
704
+ // Check if dismissal was successful
705
+ if (!dismissResult.success) {
706
+ return {
707
+ error: `Failed to dismiss blocking modal: ${dismissResult.error || 'Unknown error'}`,
708
+ blockingModal: blockingCheck.blockingModal,
709
+ };
710
+ }
711
+ // Re-check if element is still blocked
712
+ const recheckBlocking = await isElementBlocked(page, selector);
713
+ if (recheckBlocking.blocked) {
714
+ return {
715
+ error: `Element still blocked after dismissing modal`,
716
+ blockingModal: recheckBlocking.blockingModal,
717
+ };
718
+ }
719
+ }
720
+ else {
721
+ // Return error with modal information
722
+ return {
723
+ error: `Element is blocked by modal`,
724
+ blockingModal: blockingCheck.blockingModal,
725
+ suggestion: `Enable handleModals parameter or call dismissModal tool first`,
726
+ };
727
+ }
728
+ }
729
+ await checkAborted(); // last exit before the hover goes on the wire
730
+ await withReplayBypass(page, () => page.hover(selector));
731
+ return { selector };
732
+ }, 'hoverElement');
733
+ // If paused at breakpoint, return immediately - don't try any more page interactions
734
+ if (result.pausedAtBreakpoint) {
735
+ if (shouldDetectChanges) {
736
+ await domChangeMonitor.stopObserving(connectionReason, { settleTimeout: 0 });
737
+ }
738
+ return createSuccessResponse('ACTION_PAUSED_AT_BREAKPOINT', {
739
+ action: 'hover',
740
+ selector: rawSelector,
741
+ ...result.pauseInfo,
742
+ });
743
+ }
744
+ // Collect DOM changes
745
+ let changesText = '';
746
+ if (shouldDetectChanges) {
747
+ const changes = await domChangeMonitor.stopObserving(connectionReason, { settleTimeout, signal: abortSignal });
748
+ changesText = formatDOMChanges(changes);
749
+ }
750
+ // Clean up temporary selector attribute
751
+ await cleanupResolvedSelector(page, selector);
752
+ // Check if element was not found
753
+ if (result.result?.error) {
754
+ // Check if error is due to blocking modal
755
+ if (result.result?.blockingModal) {
756
+ return createErrorResponse('ELEMENT_BLOCKED_BY_MODAL', {
757
+ selector: rawSelector,
758
+ modalType: result.result.blockingModal.type,
759
+ modalDescription: result.result.blockingModal.description,
760
+ modalSelector: result.result.blockingModal.selector,
761
+ suggestion: result.result.suggestion,
762
+ availableStrategies: result.result.blockingModal.dismissStrategies,
763
+ });
764
+ }
765
+ return createErrorResponse('ELEMENT_NOT_FOUND', { selector: rawSelector });
766
+ }
767
+ // Include warning about multiple matches if applicable
768
+ if (selectorWarning) {
769
+ return {
770
+ content: [
771
+ {
772
+ type: 'text',
773
+ text: `Hovered over element \`${rawSelector}\`${changesText}\n\n**Warning:** ${selectorWarning}`,
774
+ },
775
+ ],
776
+ };
777
+ }
778
+ // Default success response with changes
779
+ return {
780
+ content: [
781
+ {
782
+ type: 'text',
783
+ text: `Hovered over element: \`${rawSelector}\`${changesText}`,
784
+ },
785
+ ],
786
+ };
787
+ }
788
+ case 'focus': {
789
+ const { selector: rawSelector } = args;
790
+ if (!rawSelector) {
791
+ return {
792
+ content: [
793
+ {
794
+ type: 'text',
795
+ text: `## Error\n\nMissing required parameter: \`selector\`\n\n**Action:** focus\n\n**Suggestion:** Provide a CSS selector for the element to focus.`,
796
+ },
797
+ ],
798
+ isError: true,
799
+ };
800
+ }
801
+ // Resolve extended selectors (like :has-text())
802
+ let selector = rawSelector;
803
+ let selectorWarning;
804
+ if (isExtendedSelector(rawSelector)) {
805
+ const resolved = await resolveSelector(page, rawSelector);
806
+ if ('error' in resolved) {
807
+ return createErrorResponse('ELEMENT_NOT_FOUND', {
808
+ selector: rawSelector,
809
+ suggestion: resolved.suggestion,
810
+ });
811
+ }
812
+ selector = resolved.selector;
813
+ selectorWarning = resolved.warning;
814
+ }
815
+ {
816
+ const ambiguous = await ambiguousSelectorWarning(page, selector, rawSelector);
817
+ if (ambiguous)
818
+ selectorWarning = selectorWarning ? `${selectorWarning} ${ambiguous}` : ambiguous;
819
+ }
820
+ const result = await executeWithPauseDetection(targetCdpManager, async () => {
821
+ // Check if element exists first
822
+ const element = await page.$(selector);
823
+ if (!element) {
824
+ return { error: `Element not found: ${selector}` };
825
+ }
826
+ // Focus the element
827
+ await checkAborted(); // last exit before the focus is dispatched
828
+ await withReplayBypass(page, () => page.focus(selector));
829
+ // Get focused element info
830
+ const focusInfo = await getFocusedElementInfo(page);
831
+ return { selector, focusInfo };
832
+ }, 'focus');
833
+ // Clean up temporary selector attribute
834
+ await cleanupResolvedSelector(page, selector);
835
+ if (result.result?.error) {
836
+ return createErrorResponse('ELEMENT_NOT_FOUND', { selector: rawSelector });
837
+ }
838
+ const focusInfo = result.result?.focusInfo;
839
+ return createSuccessResponse('ELEMENT_FOCUS_SUCCESS', {
840
+ description: focusInfo?.description || 'Unknown element',
841
+ selector: focusInfo?.selector || rawSelector,
842
+ nextTabbable: focusInfo?.nextTabbable?.length ? focusInfo.nextTabbable.join(' → ') : undefined,
843
+ warning: selectorWarning || undefined
844
+ });
845
+ }
846
+ case 'focusNext': {
847
+ const { count = 1 } = args;
848
+ const result = await executeWithPauseDetection(targetCdpManager, async () => {
849
+ // Press Tab count times - use wrapper to bypass replay blocker overlay
850
+ await withReplayBypass(page, async () => {
851
+ for (let i = 0; i < count; i++) {
852
+ // Abortable between Tabs: dispatched presses stay
853
+ // dispatched, but no further ones go out.
854
+ throwIfAborted(abortSignal);
855
+ await page.keyboard.press('Tab');
856
+ // Small delay between tabs for stability
857
+ if (i < count - 1) {
858
+ await abortableSleep(50, abortSignal);
859
+ }
860
+ }
861
+ });
862
+ // Get focused element info
863
+ const focusInfo = await getFocusedElementInfo(page);
864
+ return { focusInfo, tabCount: count };
865
+ }, 'focusNext');
866
+ const focusInfo = result.result?.focusInfo;
867
+ return createSuccessResponse('FOCUS_NEXT_SUCCESS', {
868
+ count: count > 1 ? count : undefined,
869
+ description: focusInfo?.description || 'No element focused (may have reached end of page)',
870
+ selector: focusInfo?.selector || 'none',
871
+ nextTabbable: focusInfo?.nextTabbable?.length ? focusInfo.nextTabbable.join(' → ') : undefined
872
+ });
873
+ }
874
+ case 'focusPrevious': {
875
+ const { count = 1 } = args;
876
+ const result = await executeWithPauseDetection(targetCdpManager, async () => {
877
+ // Press Shift+Tab count times - use wrapper to bypass replay blocker overlay
878
+ await withReplayBypass(page, async () => {
879
+ for (let i = 0; i < count; i++) {
880
+ // Abortable between Tabs (not mid-chord: Shift down/Tab/
881
+ // Shift up always complete together so no modifier is
882
+ // left held down).
883
+ throwIfAborted(abortSignal);
884
+ await page.keyboard.down('Shift');
885
+ await page.keyboard.press('Tab');
886
+ await page.keyboard.up('Shift');
887
+ // Small delay between tabs for stability
888
+ if (i < count - 1) {
889
+ await abortableSleep(50, abortSignal);
890
+ }
891
+ }
892
+ });
893
+ // Get focused element info
894
+ const focusInfo = await getFocusedElementInfo(page);
895
+ return { focusInfo, tabCount: count };
896
+ }, 'focusPrevious');
897
+ const focusInfo = result.result?.focusInfo;
898
+ return createSuccessResponse('FOCUS_PREVIOUS_SUCCESS', {
899
+ count: count > 1 ? count : undefined,
900
+ description: focusInfo?.description || 'No element focused (may have reached start of page)',
901
+ selector: focusInfo?.selector || 'none',
902
+ nextTabbable: focusInfo?.nextTabbable?.length ? focusInfo.nextTabbable.join(' → ') : undefined
903
+ });
904
+ }
905
+ case 'drag': {
906
+ const { from, to, steps = 10 } = args;
907
+ if (!from || !to) {
908
+ return {
909
+ content: [
910
+ {
911
+ type: 'text',
912
+ text: `## Error\n\nMissing required parameters: \`from\` and \`to\`\n\n**Action:** drag\n\n**Suggestion:** Provide starting and ending coordinates. Example: from: {x: 100, y: 100}, to: {x: 200, y: 200}`,
913
+ },
914
+ ],
915
+ isError: true,
916
+ };
917
+ }
918
+ const result = await executeWithPauseDetection(targetCdpManager, async () => {
919
+ // Use wrapper to bypass replay blocker overlay for entire drag operation
920
+ return await withReplayBypass(page, async () => {
921
+ const mouse = page.mouse;
922
+ // Abortable before anything is dispatched.
923
+ throwIfAborted(abortSignal);
924
+ // Move to start position
925
+ await mouse.move(from.x, from.y);
926
+ // Press mouse button
927
+ await mouse.down();
928
+ // Calculate intermediate steps for smooth drag
929
+ const deltaX = (to.x - from.x) / steps;
930
+ const deltaY = (to.y - from.y) / steps;
931
+ try {
932
+ for (let i = 1; i <= steps; i++) {
933
+ // Abortable between steps: movement already dispatched
934
+ // stays dispatched, the rest of the drag does not go out.
935
+ throwIfAborted(abortSignal);
936
+ const currentX = from.x + deltaX * i;
937
+ const currentY = from.y + deltaY * i;
938
+ await mouse.move(currentX, currentY);
939
+ // Small delay for smoother drag
940
+ await abortableSleep(10, abortSignal);
941
+ }
942
+ }
943
+ catch (err) {
944
+ // A cancel mid-drag must not leave the button held down.
945
+ if (isAbortError(err)) {
946
+ try {
947
+ await mouse.up();
948
+ }
949
+ catch { /* best-effort */ }
950
+ }
951
+ throw err;
952
+ }
953
+ // Release mouse button
954
+ await mouse.up();
955
+ return {
956
+ from,
957
+ to,
958
+ steps,
959
+ distance: Math.sqrt(Math.pow(to.x - from.x, 2) + Math.pow(to.y - from.y, 2)),
960
+ };
961
+ });
962
+ }, 'drag');
963
+ if (result.pausedAtBreakpoint) {
964
+ return createSuccessResponse('ACTION_PAUSED_AT_BREAKPOINT', {
965
+ action: 'drag',
966
+ ...result.pauseInfo,
967
+ });
968
+ }
969
+ const dragResult = result.result;
970
+ return {
971
+ content: [
972
+ {
973
+ type: 'text',
974
+ text: `Dragged from (${dragResult?.from.x}, ${dragResult?.from.y}) to (${dragResult?.to.x}, ${dragResult?.to.y})\n**Distance:** ${dragResult?.distance.toFixed(1)}px over ${dragResult?.steps} steps`,
975
+ },
976
+ ],
977
+ };
978
+ }
979
+ case 'scroll': {
980
+ const { deltaX = 0, deltaY = 0, x, y } = args;
981
+ if (deltaX === 0 && deltaY === 0) {
982
+ return {
983
+ content: [
984
+ {
985
+ type: 'text',
986
+ text: `## Error\n\nAt least one of \`deltaX\` or \`deltaY\` must be non-zero\n\n**Action:** scroll\n\n**Suggestion:** Provide scroll amounts. Example: deltaY: 300 (scroll down), deltaY: -300 (scroll up)`,
987
+ },
988
+ ],
989
+ isError: true,
990
+ };
991
+ }
992
+ const result = await executeWithPauseDetection(targetCdpManager, async () => {
993
+ // Use wrapper to bypass replay blocker overlay for scroll operation
994
+ return await withReplayBypass(page, async () => {
995
+ const mouse = page.mouse;
996
+ // Abortable before anything is dispatched.
997
+ throwIfAborted(abortSignal);
998
+ // If coordinates provided, move to that position first
999
+ if (x !== undefined && y !== undefined) {
1000
+ await mouse.move(x, y);
1001
+ }
1002
+ // Perform scroll
1003
+ await mouse.wheel({ deltaX, deltaY });
1004
+ // Get current scroll position
1005
+ const scrollPosition = await page.evaluate(() => ({
1006
+ scrollX: globalThis.window.scrollX,
1007
+ scrollY: globalThis.window.scrollY,
1008
+ maxScrollX: globalThis.document.documentElement.scrollWidth - globalThis.window.innerWidth,
1009
+ maxScrollY: globalThis.document.documentElement.scrollHeight - globalThis.window.innerHeight,
1010
+ }));
1011
+ return {
1012
+ deltaX,
1013
+ deltaY,
1014
+ position: x !== undefined && y !== undefined ? { x, y } : undefined,
1015
+ scrollPosition,
1016
+ };
1017
+ });
1018
+ }, 'scroll');
1019
+ if (result.pausedAtBreakpoint) {
1020
+ return createSuccessResponse('ACTION_PAUSED_AT_BREAKPOINT', {
1021
+ action: 'scroll',
1022
+ ...result.pauseInfo,
1023
+ });
1024
+ }
1025
+ const scrollResult = result.result;
1026
+ const directionParts = [];
1027
+ if (deltaY > 0)
1028
+ directionParts.push(`down ${deltaY}px`);
1029
+ if (deltaY < 0)
1030
+ directionParts.push(`up ${Math.abs(deltaY)}px`);
1031
+ if (deltaX > 0)
1032
+ directionParts.push(`right ${deltaX}px`);
1033
+ if (deltaX < 0)
1034
+ directionParts.push(`left ${Math.abs(deltaX)}px`);
1035
+ const positionInfo = scrollResult?.position
1036
+ ? ` at (${scrollResult.position.x}, ${scrollResult.position.y})`
1037
+ : '';
1038
+ return {
1039
+ content: [
1040
+ {
1041
+ type: 'text',
1042
+ text: `Scrolled ${directionParts.join(' and ')}${positionInfo}\n**Page position:** (${scrollResult?.scrollPosition.scrollX}, ${scrollResult?.scrollPosition.scrollY}) of (${scrollResult?.scrollPosition.maxScrollX}, ${scrollResult?.scrollPosition.maxScrollY})`,
1043
+ },
1044
+ ],
1045
+ };
1046
+ }
1047
+ case 'mousemove': {
1048
+ const { x, y } = args;
1049
+ if (x === undefined || y === undefined) {
1050
+ return {
1051
+ content: [
1052
+ {
1053
+ type: 'text',
1054
+ text: `## Error\n\nMissing required parameters: \`x\` and \`y\`\n\n**Action:** mousemove\n\n**Suggestion:** Provide coordinates. Example: x: 100, y: 200`,
1055
+ },
1056
+ ],
1057
+ isError: true,
1058
+ };
1059
+ }
1060
+ const result = await executeWithPauseDetection(targetCdpManager, async () => {
1061
+ // Use wrapper to bypass replay blocker overlay for mousemove
1062
+ throwIfAborted(abortSignal); // last exit before dispatch
1063
+ await withReplayBypass(page, () => page.mouse.move(x, y));
1064
+ // Get element at the mouse position
1065
+ const elementInfo = await page.evaluate((mouseX, mouseY) => {
1066
+ const el = globalThis.document.elementFromPoint(mouseX, mouseY);
1067
+ if (!el)
1068
+ return null;
1069
+ const tag = el.tagName.toLowerCase();
1070
+ const text = el.textContent?.trim().substring(0, 30) || '';
1071
+ const id = el.id ? `#${el.id}` : '';
1072
+ const className = el.className && typeof el.className === 'string'
1073
+ ? `.${el.className.trim().split(/\s+/).slice(0, 2).join('.')}`
1074
+ : '';
1075
+ return {
1076
+ tag,
1077
+ text: text || undefined,
1078
+ selector: id || className || tag,
1079
+ };
1080
+ }, x, y);
1081
+ return { x, y, elementInfo };
1082
+ }, 'mousemove');
1083
+ if (result.pausedAtBreakpoint) {
1084
+ return createSuccessResponse('ACTION_PAUSED_AT_BREAKPOINT', {
1085
+ action: 'mousemove',
1086
+ ...result.pauseInfo,
1087
+ });
1088
+ }
1089
+ const moveResult = result.result;
1090
+ const elementDesc = moveResult?.elementInfo
1091
+ ? `\n**Element at position:** \`${moveResult.elementInfo.selector}\`${moveResult.elementInfo.text ? ` "${moveResult.elementInfo.text}"` : ''}`
1092
+ : '';
1093
+ return {
1094
+ content: [
1095
+ {
1096
+ type: 'text',
1097
+ text: `Mouse moved to (${moveResult?.x}, ${moveResult?.y})${elementDesc}`,
1098
+ },
1099
+ ],
1100
+ };
1101
+ }
1102
+ case 'tap':
1103
+ case 'swipe': {
1104
+ // Real touch events. Mouse actions do not produce touchstart/
1105
+ // touchmove, so a component that listens only for touch cannot be
1106
+ // driven by click or drag at all.
1107
+ const isSwipe = args.action === 'swipe';
1108
+ let start = isSwipe ? args.from : (typeof args.x === 'number' && typeof args.y === 'number' ? { x: args.x, y: args.y } : undefined);
1109
+ // A gesture across an element: its geometry is read now, because a
1110
+ // row's coordinates move with whatever is above it and a sequence
1111
+ // cannot know them in advance. Without this, gesture-revealed
1112
+ // actions get faked with a programmatic click in page JS, which
1113
+ // proves nothing about the gesture.
1114
+ let selectorEnd;
1115
+ if ((!isSwipe || args.direction) && !start && args.selector) {
1116
+ const rawSelector = args.selector;
1117
+ let selector = rawSelector;
1118
+ if (isExtendedSelector(selector)) {
1119
+ const resolved = await resolveSelector(page, selector);
1120
+ if ('error' in resolved) {
1121
+ return createErrorResponse('ELEMENT_NOT_FOUND', { selector: rawSelector, suggestion: resolved.suggestion });
1122
+ }
1123
+ selector = resolved.selector;
1124
+ }
1125
+ const box = await page.evaluate((sel) => {
1126
+ const el = globalThis.document.querySelector(sel);
1127
+ if (!el)
1128
+ return null;
1129
+ el.scrollIntoView({ block: 'center', inline: 'center' });
1130
+ const r = el.getBoundingClientRect();
1131
+ const doc = globalThis.document.documentElement;
1132
+ return {
1133
+ x: r.x + r.width / 2, y: r.y + r.height / 2,
1134
+ width: r.width, height: r.height,
1135
+ viewW: doc.clientWidth, viewH: doc.clientHeight,
1136
+ };
1137
+ }, selector);
1138
+ await cleanupResolvedSelector(page, selector);
1139
+ if (!box)
1140
+ return createErrorResponse('ELEMENT_NOT_FOUND', { selector: rawSelector });
1141
+ start = { x: box.x, y: box.y };
1142
+ if (args.direction) {
1143
+ const along = (args.direction === 'left' || args.direction === 'right') ? box.width : box.height;
1144
+ // Deliberately short. A swipe-action row has ranges - a nudge
1145
+ // peeks, further opens, further still commits the destructive
1146
+ // action - and 60% of a wide row lands in the last one. Travel
1147
+ // far enough to reveal, and let a caller that WANTS the
1148
+ // over-drag ask for it.
1149
+ const travel = args.distance ?? Math.min(Math.round(along * 0.6), 96);
1150
+ const dx = args.direction === 'left' ? -travel : args.direction === 'right' ? travel : 0;
1151
+ const dy = args.direction === 'up' ? -travel : args.direction === 'down' ? travel : 0;
1152
+ // Stay on-screen: a gesture ending outside the viewport lands
1153
+ // nowhere and reads as an unresponsive component.
1154
+ selectorEnd = {
1155
+ x: Math.max(1, Math.min(box.viewW - 1, box.x + dx)),
1156
+ y: Math.max(1, Math.min(box.viewH - 1, box.y + dy)),
1157
+ };
1158
+ }
1159
+ }
1160
+ if (!start) {
1161
+ return createErrorResponse('INVALID_PARAMETER', {
1162
+ parameter: isSwipe ? 'from' : 'selector/x,y',
1163
+ value: 'missing',
1164
+ message: isSwipe
1165
+ ? 'swipe needs from:{x,y} and to:{x,y}, or selector + direction.'
1166
+ : 'tap needs either a selector or x and y.'
1167
+ });
1168
+ }
1169
+ if (isSwipe && !args.to && !selectorEnd) {
1170
+ return createErrorResponse('INVALID_PARAMETER', {
1171
+ parameter: 'to',
1172
+ value: 'missing',
1173
+ message: 'swipe needs to:{x,y}, or direction alongside selector.'
1174
+ });
1175
+ }
1176
+ const end = isSwipe ? (args.to ?? selectorEnd) : start;
1177
+ const steps = Math.max(1, args.steps ?? 10);
1178
+ const result = await executeWithPauseDetection(targetCdpManager, async () => {
1179
+ const client = await page.createCDPSession();
1180
+ const point = (x, y) => ({ x: Math.round(x), y: Math.round(y) });
1181
+ try {
1182
+ return await withReplayBypass(page, async () => {
1183
+ throwIfAborted(abortSignal);
1184
+ await client.send('Input.dispatchTouchEvent', {
1185
+ type: 'touchStart',
1186
+ touchPoints: [point(start.x, start.y)],
1187
+ });
1188
+ if (isSwipe) {
1189
+ // Paced by default. Unpaced, the moves arrive within a few
1190
+ // ms of each other - velocity of several px/ms, which
1191
+ // gesture handlers read as a flick. On a row whose flick
1192
+ // means "commit the destructive action", the safe-looking
1193
+ // call (no timing given, short travel) would fire it.
1194
+ // Distance does not protect against this; only pacing
1195
+ // does.
1196
+ const perStep = (args.durationMs ?? 300) / steps;
1197
+ for (let i = 1; i <= steps; i++) {
1198
+ throwIfAborted(abortSignal);
1199
+ await client.send('Input.dispatchTouchEvent', {
1200
+ type: 'touchMove',
1201
+ touchPoints: [point(start.x + ((end.x - start.x) * i) / steps, start.y + ((end.y - start.y) * i) / steps)],
1202
+ });
1203
+ if (perStep > 0 && i < steps)
1204
+ await abortableSleep(perStep, abortSignal);
1205
+ }
1206
+ }
1207
+ // touchEnd carries no points: the contact has lifted.
1208
+ await client.send('Input.dispatchTouchEvent', { type: 'touchEnd', touchPoints: [] });
1209
+ return { from: point(start.x, start.y), to: point(end.x, end.y), steps: isSwipe ? steps : 0 };
1210
+ });
1211
+ }
1212
+ finally {
1213
+ await client.detach();
1214
+ }
1215
+ }, args.action);
1216
+ const r = result.result;
1217
+ if (!r) {
1218
+ return createErrorResponse('INVALID_PARAMETER', {
1219
+ parameter: args.action,
1220
+ value: 'no result',
1221
+ message: `${args.action} dispatched but returned no result — the page may have navigated mid-gesture.`
1222
+ });
1223
+ }
1224
+ return {
1225
+ content: [{
1226
+ type: 'text',
1227
+ text: isSwipe
1228
+ ? `Swiped (touch) from (${r.from.x},${r.from.y}) to (${r.to.x},${r.to.y}) in ${r.steps} steps`
1229
+ : `Tapped (touch) at (${r.from.x},${r.from.y})`,
1230
+ }],
1231
+ };
1232
+ }
1233
+ case 'pinch': {
1234
+ const { x, y, scaleFactor } = args;
1235
+ if (scaleFactor === undefined) {
1236
+ return {
1237
+ content: [
1238
+ {
1239
+ type: 'text',
1240
+ text: `## Error\n\nMissing required parameter: \`scaleFactor\`\n\n**Action:** pinch\n\n**Suggestion:** Provide a scale factor. Example: scaleFactor: 2.0 (zoom in 2x), scaleFactor: 0.5 (zoom out 50%)`,
1241
+ },
1242
+ ],
1243
+ isError: true,
1244
+ };
1245
+ }
1246
+ const result = await executeWithPauseDetection(targetCdpManager, async () => {
1247
+ // Get viewport center if coordinates not provided
1248
+ const viewport = page.viewport();
1249
+ const centerX = x ?? (viewport?.width ?? 800) / 2;
1250
+ const centerY = y ?? (viewport?.height ?? 600) / 2;
1251
+ // Create CDP session for synthesizePinchGesture
1252
+ const client = await page.createCDPSession();
1253
+ try {
1254
+ // Use wrapper to bypass replay blocker overlay for pinch gesture
1255
+ return await withReplayBypass(page, async () => {
1256
+ // Last exit before the gesture goes on the wire (once
1257
+ // dispatched, the whole synthesized pinch runs in Chrome).
1258
+ throwIfAborted(abortSignal);
1259
+ await client.send('Input.synthesizePinchGesture', {
1260
+ x: centerX,
1261
+ y: centerY,
1262
+ scaleFactor,
1263
+ relativeSpeed: 300, // pixels per second
1264
+ gestureSourceType: 'touch',
1265
+ });
1266
+ return {
1267
+ x: centerX,
1268
+ y: centerY,
1269
+ scaleFactor,
1270
+ action: scaleFactor > 1 ? 'zoom in' : 'zoom out',
1271
+ };
1272
+ });
1273
+ }
1274
+ finally {
1275
+ await client.detach();
1276
+ }
1277
+ }, 'pinch');
1278
+ if (result.pausedAtBreakpoint) {
1279
+ return createSuccessResponse('ACTION_PAUSED_AT_BREAKPOINT', {
1280
+ action: 'pinch',
1281
+ ...result.pauseInfo,
1282
+ });
1283
+ }
1284
+ const pinchResult = result.result;
1285
+ return {
1286
+ content: [
1287
+ {
1288
+ type: 'text',
1289
+ text: `Pinch ${pinchResult?.action} at (${pinchResult?.x}, ${pinchResult?.y}) with scale factor ${pinchResult?.scaleFactor}`,
1290
+ },
1291
+ ],
1292
+ };
1293
+ }
1294
+ default:
1295
+ return createErrorResponse('INVALID_ACTION', {
1296
+ action,
1297
+ validActions: 'click, type, press, hover, focus, focusNext, focusPrevious, drag, scroll, mousemove, pinch'
1298
+ });
1299
+ }
1300
+ }),
1301
+ };
1302
+ }
1303
+ /**
1304
+ * Helper function to get information about the currently focused element
1305
+ */
1306
+ async function getFocusedElementInfo(page) {
1307
+ return await page.evaluate(() => {
1308
+ const focused = globalThis.document.activeElement;
1309
+ if (!focused || focused === globalThis.document.body) {
1310
+ return null;
1311
+ }
1312
+ const tag = focused.tagName.toLowerCase();
1313
+ const type = focused.getAttribute('type') || undefined;
1314
+ const text = focused.textContent?.trim().substring(0, 50) ||
1315
+ focused.getAttribute('aria-label') ||
1316
+ focused.getAttribute('placeholder') ||
1317
+ focused.getAttribute('name') ||
1318
+ focused.getAttribute('id') ||
1319
+ '';
1320
+ // Build a description
1321
+ let description = tag;
1322
+ if (type)
1323
+ description += `[type="${type}"]`;
1324
+ if (text)
1325
+ description += ` "${text}"`;
1326
+ // Try to build a useful selector
1327
+ let selector;
1328
+ if (focused.id) {
1329
+ selector = `#${focused.id}`;
1330
+ }
1331
+ else if (focused.name) {
1332
+ selector = `${tag}[name="${focused.name}"]`;
1333
+ }
1334
+ else if (focused.className && typeof focused.className === 'string') {
1335
+ const classes = focused.className.trim().split(/\s+/).slice(0, 2).join('.');
1336
+ if (classes)
1337
+ selector = `${tag}.${classes}`;
1338
+ }
1339
+ // Helper to format element for display
1340
+ const formatEl = (el) => {
1341
+ const elTag = el.tagName.toLowerCase();
1342
+ const elText = el.textContent?.trim().substring(0, 20) ||
1343
+ el.getAttribute('aria-label') ||
1344
+ el.getAttribute('placeholder') ||
1345
+ '';
1346
+ const elType = el.getAttribute('type');
1347
+ if (elTag === 'input') {
1348
+ return `${elTag}[${elType || 'text'}]${elText ? ` "${elText}"` : ''}`;
1349
+ }
1350
+ return `${elTag}${elText ? ` "${elText}"` : ''}`;
1351
+ };
1352
+ // Get tabbable elements
1353
+ const tabbable = Array.from(globalThis.document.querySelectorAll('a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])')).filter((el) => {
1354
+ const style = globalThis.window.getComputedStyle(el);
1355
+ return style.display !== 'none' && style.visibility !== 'hidden';
1356
+ });
1357
+ const currentIndex = tabbable.indexOf(focused);
1358
+ let prevTabbable = [];
1359
+ let nextTabbable = [];
1360
+ if (currentIndex !== -1) {
1361
+ // Previous 3 tabbable elements
1362
+ prevTabbable = tabbable.slice(Math.max(0, currentIndex - 3), currentIndex).map(formatEl);
1363
+ // Next 3 tabbable elements
1364
+ nextTabbable = tabbable.slice(currentIndex + 1, currentIndex + 4).map(formatEl);
1365
+ }
1366
+ return {
1367
+ description,
1368
+ tag,
1369
+ type,
1370
+ text: text || undefined,
1371
+ selector,
1372
+ prevTabbable,
1373
+ nextTabbable,
1374
+ };
1375
+ });
1376
+ }
1377
+ /**
1378
+ * Helper function to dismiss a modal
1379
+ *
1380
+ * This is a thin wrapper around the shared dismissal logic from modal-dismissal.ts
1381
+ */
1382
+ async function dismissModalHelper(page, modalSelector, strategy) {
1383
+ // Get modal info to determine strategy
1384
+ const modals = await detectModals(page);
1385
+ const modal = modals.find(m => m.selector === modalSelector);
1386
+ if (!modal) {
1387
+ return { success: false, error: 'Modal not found' };
1388
+ }
1389
+ // Use shared dismissal logic
1390
+ const effectiveStrategy = selectDismissalStrategy(modal, strategy);
1391
+ const result = await dismissModalByStrategy(page, modal, effectiveStrategy, 3);
1392
+ return {
1393
+ success: result.success,
1394
+ error: result.error,
1395
+ };
1396
+ }
1397
+ //# sourceMappingURL=input-tools.js.map