@agentrhq/webcmd 0.1.1 → 0.1.2

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 (415) hide show
  1. package/README.md +317 -24
  2. package/dist/src/adapter-shadow.d.ts +11 -0
  3. package/dist/src/adapter-shadow.js +72 -0
  4. package/dist/src/adapter-shadow.test.d.ts +1 -0
  5. package/dist/src/adapter-shadow.test.js +49 -0
  6. package/dist/src/adapter-source.d.ts +11 -0
  7. package/dist/src/adapter-source.js +24 -0
  8. package/dist/src/adapter-source.test.d.ts +1 -0
  9. package/dist/src/adapter-source.test.js +29 -0
  10. package/dist/src/brand.d.ts +9 -0
  11. package/dist/src/brand.js +9 -0
  12. package/dist/src/brand.test.d.ts +1 -0
  13. package/dist/src/brand.test.js +15 -0
  14. package/dist/src/browser/analyze.d.ts +118 -0
  15. package/dist/src/browser/analyze.js +405 -0
  16. package/dist/src/browser/analyze.test.d.ts +1 -0
  17. package/dist/src/browser/analyze.test.js +209 -0
  18. package/dist/src/browser/article-extract.d.ts +57 -0
  19. package/dist/src/browser/article-extract.e2e.test.d.ts +1 -0
  20. package/dist/src/browser/article-extract.e2e.test.js +105 -0
  21. package/dist/src/browser/article-extract.js +169 -0
  22. package/dist/src/browser/article-extract.test.d.ts +1 -0
  23. package/dist/src/browser/article-extract.test.js +94 -0
  24. package/dist/src/browser/ax-snapshot.d.ts +37 -0
  25. package/dist/src/browser/ax-snapshot.js +217 -0
  26. package/dist/src/browser/ax-snapshot.test.d.ts +1 -0
  27. package/dist/src/browser/ax-snapshot.test.js +91 -0
  28. package/dist/src/browser/base-page.d.ts +151 -0
  29. package/dist/src/browser/base-page.js +1060 -0
  30. package/dist/src/browser/base-page.test.d.ts +1 -0
  31. package/dist/src/browser/base-page.test.js +803 -0
  32. package/dist/src/browser/bridge-readiness.d.ts +14 -0
  33. package/dist/src/browser/bridge-readiness.js +24 -0
  34. package/dist/src/browser/bridge-readiness.test.d.ts +1 -0
  35. package/dist/src/browser/bridge-readiness.test.js +58 -0
  36. package/dist/src/browser/bridge.d.ts +26 -0
  37. package/dist/src/browser/bridge.js +58 -0
  38. package/dist/src/browser/cdp-click-fixture.test.d.ts +1 -0
  39. package/dist/src/browser/cdp-click-fixture.test.js +87 -0
  40. package/dist/src/browser/cdp.d.ts +46 -0
  41. package/dist/src/browser/cdp.js +536 -0
  42. package/dist/src/browser/cdp.test.d.ts +1 -0
  43. package/dist/src/browser/cdp.test.js +79 -0
  44. package/dist/src/browser/compound.d.ts +59 -0
  45. package/dist/src/browser/compound.js +112 -0
  46. package/dist/src/browser/compound.test.d.ts +1 -0
  47. package/dist/src/browser/compound.test.js +175 -0
  48. package/dist/src/browser/config.d.ts +2 -0
  49. package/dist/src/browser/config.js +14 -0
  50. package/dist/src/browser/daemon-client.d.ts +32 -0
  51. package/dist/src/browser/daemon-client.js +135 -0
  52. package/dist/src/browser/daemon-client.test.d.ts +1 -0
  53. package/dist/src/browser/daemon-client.test.js +326 -0
  54. package/dist/src/browser/daemon-lifecycle.d.ts +37 -0
  55. package/dist/src/browser/daemon-lifecycle.js +147 -0
  56. package/dist/src/browser/daemon-transport.d.ts +54 -0
  57. package/dist/src/browser/daemon-transport.js +68 -0
  58. package/dist/src/browser/daemon-version.d.ts +4 -0
  59. package/dist/src/browser/daemon-version.js +12 -0
  60. package/dist/src/browser/dom-helpers.d.ts +41 -0
  61. package/dist/src/browser/dom-helpers.js +239 -0
  62. package/dist/src/browser/dom-helpers.test.d.ts +1 -0
  63. package/dist/src/browser/dom-helpers.test.js +106 -0
  64. package/dist/src/browser/dom-snapshot.d.ts +93 -0
  65. package/dist/src/browser/dom-snapshot.js +894 -0
  66. package/dist/src/browser/dom-snapshot.test.d.ts +11 -0
  67. package/dist/src/browser/dom-snapshot.test.js +313 -0
  68. package/dist/src/browser/errors.d.ts +38 -0
  69. package/dist/src/browser/errors.js +79 -0
  70. package/dist/src/browser/errors.test.d.ts +1 -0
  71. package/dist/src/browser/errors.test.js +60 -0
  72. package/dist/src/browser/extract.d.ts +69 -0
  73. package/dist/src/browser/extract.js +132 -0
  74. package/dist/src/browser/extract.test.d.ts +1 -0
  75. package/dist/src/browser/extract.test.js +129 -0
  76. package/dist/src/browser/find.d.ts +84 -0
  77. package/dist/src/browser/find.js +398 -0
  78. package/dist/src/browser/find.test.d.ts +1 -0
  79. package/dist/src/browser/find.test.js +180 -0
  80. package/dist/src/browser/html-tree.d.ts +75 -0
  81. package/dist/src/browser/html-tree.js +112 -0
  82. package/dist/src/browser/html-tree.test.d.ts +1 -0
  83. package/dist/src/browser/html-tree.test.js +181 -0
  84. package/dist/src/browser/index.d.ts +14 -0
  85. package/dist/src/browser/index.js +12 -0
  86. package/dist/src/browser/network-cache.d.ts +49 -0
  87. package/dist/src/browser/network-cache.js +78 -0
  88. package/dist/src/browser/network-cache.test.d.ts +1 -0
  89. package/dist/src/browser/network-cache.test.js +75 -0
  90. package/dist/src/browser/network-interceptor.d.ts +11 -0
  91. package/dist/src/browser/network-interceptor.js +11 -0
  92. package/dist/src/browser/network-key.d.ts +22 -0
  93. package/dist/src/browser/network-key.js +66 -0
  94. package/dist/src/browser/network-key.test.d.ts +1 -0
  95. package/dist/src/browser/network-key.test.js +49 -0
  96. package/dist/src/browser/page.d.ts +83 -0
  97. package/dist/src/browser/page.js +453 -0
  98. package/dist/src/browser/page.test.d.ts +1 -0
  99. package/dist/src/browser/page.test.js +406 -0
  100. package/dist/src/browser/profile.d.ts +14 -0
  101. package/dist/src/browser/profile.js +86 -0
  102. package/dist/src/browser/protocol.d.ts +63 -0
  103. package/dist/src/browser/protocol.js +1 -0
  104. package/dist/src/browser/runtime/local-cloak/actions.d.ts +3 -0
  105. package/dist/src/browser/runtime/local-cloak/actions.js +240 -0
  106. package/dist/src/browser/runtime/local-cloak/downloads.d.ts +3 -0
  107. package/dist/src/browser/runtime/local-cloak/downloads.js +28 -0
  108. package/dist/src/browser/runtime/local-cloak/network.d.ts +28 -0
  109. package/dist/src/browser/runtime/local-cloak/network.js +120 -0
  110. package/dist/src/browser/runtime/local-cloak/network.test.d.ts +1 -0
  111. package/dist/src/browser/runtime/local-cloak/network.test.js +124 -0
  112. package/dist/src/browser/runtime/local-cloak/profiles.d.ts +6 -0
  113. package/dist/src/browser/runtime/local-cloak/profiles.js +17 -0
  114. package/dist/src/browser/runtime/local-cloak/profiles.test.d.ts +1 -0
  115. package/dist/src/browser/runtime/local-cloak/profiles.test.js +19 -0
  116. package/dist/src/browser/runtime/local-cloak/provider.d.ts +15 -0
  117. package/dist/src/browser/runtime/local-cloak/provider.js +27 -0
  118. package/dist/src/browser/runtime/local-cloak/provider.test.d.ts +1 -0
  119. package/dist/src/browser/runtime/local-cloak/provider.test.js +244 -0
  120. package/dist/src/browser/runtime/local-cloak/session-manager.d.ts +77 -0
  121. package/dist/src/browser/runtime/local-cloak/session-manager.js +280 -0
  122. package/dist/src/browser/runtime/local-cloak/session-manager.test.d.ts +1 -0
  123. package/dist/src/browser/runtime/local-cloak/session-manager.test.js +92 -0
  124. package/dist/src/browser/runtime/provider.d.ts +9 -0
  125. package/dist/src/browser/runtime/provider.js +1 -0
  126. package/dist/src/browser/runtime/provider.test.d.ts +2 -0
  127. package/dist/src/browser/runtime/provider.test.js +49 -0
  128. package/dist/src/browser/shape-filter.d.ts +52 -0
  129. package/dist/src/browser/shape-filter.js +101 -0
  130. package/dist/src/browser/shape-filter.test.d.ts +1 -0
  131. package/dist/src/browser/shape-filter.test.js +101 -0
  132. package/dist/src/browser/shape.d.ts +23 -0
  133. package/dist/src/browser/shape.js +95 -0
  134. package/dist/src/browser/shape.test.d.ts +1 -0
  135. package/dist/src/browser/shape.test.js +82 -0
  136. package/dist/src/browser/stealth.d.ts +11 -0
  137. package/dist/src/browser/stealth.js +359 -0
  138. package/dist/src/browser/stealth.test.d.ts +1 -0
  139. package/dist/src/browser/stealth.test.js +134 -0
  140. package/dist/src/browser/tabs.d.ts +13 -0
  141. package/dist/src/browser/tabs.js +70 -0
  142. package/dist/src/browser/target-errors.d.ts +39 -0
  143. package/dist/src/browser/target-errors.js +45 -0
  144. package/dist/src/browser/target-errors.test.d.ts +1 -0
  145. package/dist/src/browser/target-errors.test.js +94 -0
  146. package/dist/src/browser/target-resolver.d.ts +158 -0
  147. package/dist/src/browser/target-resolver.js +570 -0
  148. package/dist/src/browser/target-resolver.test.d.ts +1 -0
  149. package/dist/src/browser/target-resolver.test.js +118 -0
  150. package/dist/src/browser/utils.d.ts +20 -0
  151. package/dist/src/browser/utils.js +64 -0
  152. package/dist/src/browser/utils.test.d.ts +1 -0
  153. package/dist/src/browser/utils.test.js +29 -0
  154. package/dist/src/browser/verify-fixture.d.ts +65 -0
  155. package/dist/src/browser/verify-fixture.js +318 -0
  156. package/dist/src/browser/verify-fixture.test.d.ts +1 -0
  157. package/dist/src/browser/verify-fixture.test.js +219 -0
  158. package/dist/src/browser/visual-refs.d.ts +11 -0
  159. package/dist/src/browser/visual-refs.js +108 -0
  160. package/dist/src/browser.test.d.ts +1 -0
  161. package/dist/src/browser.test.js +297 -0
  162. package/dist/src/build-manifest.d.ts +103 -0
  163. package/dist/src/build-manifest.js +363 -0
  164. package/dist/src/build-manifest.test.d.ts +1 -0
  165. package/dist/src/build-manifest.test.js +341 -0
  166. package/dist/src/capabilityRouting.d.ts +19 -0
  167. package/dist/src/capabilityRouting.js +56 -0
  168. package/dist/src/capabilityRouting.test.d.ts +1 -0
  169. package/dist/src/capabilityRouting.test.js +63 -0
  170. package/dist/src/cli-argv-preprocess.d.ts +63 -0
  171. package/dist/src/cli-argv-preprocess.js +303 -0
  172. package/dist/src/cli-argv-preprocess.test.d.ts +1 -0
  173. package/dist/src/cli-argv-preprocess.test.js +304 -0
  174. package/dist/src/cli.d.ts +79 -0
  175. package/dist/src/cli.js +3350 -0
  176. package/dist/src/cli.test.d.ts +1 -0
  177. package/dist/src/cli.test.js +3041 -0
  178. package/dist/src/commanderAdapter.d.ts +21 -0
  179. package/dist/src/commanderAdapter.js +208 -0
  180. package/dist/src/commanderAdapter.test.d.ts +1 -0
  181. package/dist/src/commanderAdapter.test.js +352 -0
  182. package/dist/src/commands/auth.d.ts +40 -0
  183. package/dist/src/commands/auth.js +434 -0
  184. package/dist/src/commands/auth.test.d.ts +1 -0
  185. package/dist/src/commands/auth.test.js +252 -0
  186. package/dist/src/commands/daemon.d.ts +9 -0
  187. package/dist/src/commands/daemon.js +91 -0
  188. package/dist/src/commands/daemon.test.d.ts +1 -0
  189. package/dist/src/commands/daemon.test.js +293 -0
  190. package/dist/src/completion-fast.d.ts +22 -0
  191. package/dist/src/completion-fast.js +88 -0
  192. package/dist/src/completion-shared.d.ts +13 -0
  193. package/dist/src/completion-shared.js +61 -0
  194. package/dist/src/completion.d.ts +21 -0
  195. package/dist/src/completion.js +68 -0
  196. package/dist/src/completion.test.d.ts +1 -0
  197. package/dist/src/completion.test.js +24 -0
  198. package/dist/src/constants.d.ts +18 -0
  199. package/dist/src/constants.js +41 -0
  200. package/dist/src/convention-audit.d.ts +50 -0
  201. package/dist/src/convention-audit.js +553 -0
  202. package/dist/src/convention-audit.test.d.ts +1 -0
  203. package/dist/src/convention-audit.test.js +255 -0
  204. package/dist/src/daemon/server.d.ts +13 -0
  205. package/dist/src/daemon/server.js +185 -0
  206. package/dist/src/daemon/server.test.d.ts +1 -0
  207. package/dist/src/daemon/server.test.js +132 -0
  208. package/dist/src/daemon-utils.d.ts +18 -0
  209. package/dist/src/daemon-utils.js +37 -0
  210. package/dist/src/daemon.d.ts +1 -0
  211. package/dist/src/daemon.js +27 -0
  212. package/dist/src/daemon.test.d.ts +1 -0
  213. package/dist/src/daemon.test.js +60 -0
  214. package/dist/src/discovery.d.ts +46 -0
  215. package/dist/src/discovery.js +341 -0
  216. package/dist/src/doctor.d.ts +39 -0
  217. package/dist/src/doctor.js +168 -0
  218. package/dist/src/doctor.test.d.ts +1 -0
  219. package/dist/src/doctor.test.js +302 -0
  220. package/dist/src/download/article-download.d.ts +72 -0
  221. package/dist/src/download/article-download.js +305 -0
  222. package/dist/src/download/article-download.test.d.ts +1 -0
  223. package/dist/src/download/article-download.test.js +235 -0
  224. package/dist/src/download/index.d.ts +71 -0
  225. package/dist/src/download/index.js +363 -0
  226. package/dist/src/download/index.test.d.ts +1 -0
  227. package/dist/src/download/index.test.js +174 -0
  228. package/dist/src/download/media-download.d.ts +49 -0
  229. package/dist/src/download/media-download.js +127 -0
  230. package/dist/src/download/media-download.test.d.ts +1 -0
  231. package/dist/src/download/media-download.test.js +112 -0
  232. package/dist/src/download/progress.d.ts +36 -0
  233. package/dist/src/download/progress.js +120 -0
  234. package/dist/src/download/progress.test.d.ts +1 -0
  235. package/dist/src/download/progress.test.js +36 -0
  236. package/dist/src/electron-apps.d.ts +31 -0
  237. package/dist/src/electron-apps.js +91 -0
  238. package/dist/src/electron-apps.test.d.ts +1 -0
  239. package/dist/src/electron-apps.test.js +76 -0
  240. package/dist/src/engine.test.d.ts +1 -0
  241. package/dist/src/engine.test.js +312 -0
  242. package/dist/src/errors.d.ts +114 -0
  243. package/dist/src/errors.js +174 -0
  244. package/dist/src/errors.test.d.ts +1 -0
  245. package/dist/src/errors.test.js +109 -0
  246. package/dist/src/execution.d.ts +24 -0
  247. package/dist/src/execution.js +509 -0
  248. package/dist/src/execution.test.d.ts +1 -0
  249. package/dist/src/execution.test.js +645 -0
  250. package/dist/src/external.d.ts +49 -0
  251. package/dist/src/external.js +211 -0
  252. package/dist/src/external.test.d.ts +1 -0
  253. package/dist/src/external.test.js +110 -0
  254. package/dist/src/help.d.ts +88 -0
  255. package/dist/src/help.js +556 -0
  256. package/dist/src/help.test.d.ts +1 -0
  257. package/dist/src/help.test.js +58 -0
  258. package/dist/src/hooks.d.ts +46 -0
  259. package/dist/src/hooks.js +58 -0
  260. package/dist/src/hooks.test.d.ts +4 -0
  261. package/dist/src/hooks.test.js +92 -0
  262. package/dist/src/interceptor.d.ts +44 -0
  263. package/dist/src/interceptor.js +183 -0
  264. package/dist/src/interceptor.test.d.ts +4 -0
  265. package/dist/src/interceptor.test.js +81 -0
  266. package/dist/src/launcher.d.ts +41 -0
  267. package/dist/src/launcher.js +226 -0
  268. package/dist/src/launcher.test.d.ts +1 -0
  269. package/dist/src/launcher.test.js +153 -0
  270. package/dist/src/logger.d.ts +26 -0
  271. package/dist/src/logger.js +49 -0
  272. package/dist/src/main.d.ts +5 -0
  273. package/dist/src/main.js +177 -0
  274. package/dist/src/manifest-types.d.ts +43 -0
  275. package/dist/src/manifest-types.js +9 -0
  276. package/dist/src/node-network.d.ts +10 -0
  277. package/dist/src/node-network.js +174 -0
  278. package/dist/src/node-network.test.d.ts +1 -0
  279. package/dist/src/node-network.test.js +55 -0
  280. package/dist/src/observation/artifact.d.ts +16 -0
  281. package/dist/src/observation/artifact.js +260 -0
  282. package/dist/src/observation/artifact.test.d.ts +1 -0
  283. package/dist/src/observation/artifact.test.js +121 -0
  284. package/dist/src/observation/events.d.ts +89 -0
  285. package/dist/src/observation/events.js +1 -0
  286. package/dist/src/observation/index.d.ts +7 -0
  287. package/dist/src/observation/index.js +7 -0
  288. package/dist/src/observation/manager.d.ts +9 -0
  289. package/dist/src/observation/manager.js +27 -0
  290. package/dist/src/observation/manager.test.d.ts +1 -0
  291. package/dist/src/observation/manager.test.js +13 -0
  292. package/dist/src/observation/redaction.d.ts +11 -0
  293. package/dist/src/observation/redaction.js +81 -0
  294. package/dist/src/observation/redaction.test.d.ts +1 -0
  295. package/dist/src/observation/redaction.test.js +32 -0
  296. package/dist/src/observation/retention.d.ts +32 -0
  297. package/dist/src/observation/retention.js +160 -0
  298. package/dist/src/observation/retention.test.d.ts +1 -0
  299. package/dist/src/observation/retention.test.js +118 -0
  300. package/dist/src/observation/ring-buffer.d.ts +22 -0
  301. package/dist/src/observation/ring-buffer.js +45 -0
  302. package/dist/src/observation/ring-buffer.test.d.ts +1 -0
  303. package/dist/src/observation/ring-buffer.test.js +22 -0
  304. package/dist/src/observation/session.d.ts +25 -0
  305. package/dist/src/observation/session.js +50 -0
  306. package/dist/src/output.d.ts +14 -0
  307. package/dist/src/output.js +152 -0
  308. package/dist/src/output.test.d.ts +1 -0
  309. package/dist/src/output.test.js +53 -0
  310. package/dist/src/package-exports.test.d.ts +1 -0
  311. package/dist/src/package-exports.test.js +112 -0
  312. package/dist/src/package-paths.d.ts +8 -0
  313. package/dist/src/package-paths.js +41 -0
  314. package/dist/src/pipeline/executor.d.ts +11 -0
  315. package/dist/src/pipeline/executor.js +93 -0
  316. package/dist/src/pipeline/executor.test.d.ts +4 -0
  317. package/dist/src/pipeline/executor.test.js +182 -0
  318. package/dist/src/pipeline/index.d.ts +5 -0
  319. package/dist/src/pipeline/index.js +5 -0
  320. package/dist/src/pipeline/registry.d.ts +28 -0
  321. package/dist/src/pipeline/registry.js +55 -0
  322. package/dist/src/pipeline/steps/browser.d.ts +13 -0
  323. package/dist/src/pipeline/steps/browser.js +86 -0
  324. package/dist/src/pipeline/steps/download.d.ts +18 -0
  325. package/dist/src/pipeline/steps/download.js +252 -0
  326. package/dist/src/pipeline/steps/download.test.d.ts +1 -0
  327. package/dist/src/pipeline/steps/download.test.js +102 -0
  328. package/dist/src/pipeline/steps/fetch.d.ts +5 -0
  329. package/dist/src/pipeline/steps/fetch.js +122 -0
  330. package/dist/src/pipeline/steps/fetch.test.d.ts +1 -0
  331. package/dist/src/pipeline/steps/fetch.test.js +117 -0
  332. package/dist/src/pipeline/steps/intercept.d.ts +5 -0
  333. package/dist/src/pipeline/steps/intercept.js +50 -0
  334. package/dist/src/pipeline/steps/tap.d.ts +12 -0
  335. package/dist/src/pipeline/steps/tap.js +93 -0
  336. package/dist/src/pipeline/steps/transform.d.ts +9 -0
  337. package/dist/src/pipeline/steps/transform.js +70 -0
  338. package/dist/src/pipeline/template.d.ts +17 -0
  339. package/dist/src/pipeline/template.js +344 -0
  340. package/dist/src/pipeline/template.test.d.ts +4 -0
  341. package/dist/src/pipeline/template.test.js +197 -0
  342. package/dist/src/pipeline/transform.test.d.ts +4 -0
  343. package/dist/src/pipeline/transform.test.js +133 -0
  344. package/dist/src/plugin-manifest.d.ts +70 -0
  345. package/dist/src/plugin-manifest.js +160 -0
  346. package/dist/src/plugin-manifest.test.d.ts +4 -0
  347. package/dist/src/plugin-manifest.test.js +179 -0
  348. package/dist/src/plugin-scaffold.d.ts +28 -0
  349. package/dist/src/plugin-scaffold.js +143 -0
  350. package/dist/src/plugin-scaffold.test.d.ts +4 -0
  351. package/dist/src/plugin-scaffold.test.js +83 -0
  352. package/dist/src/plugin.d.ts +146 -0
  353. package/dist/src/plugin.js +1303 -0
  354. package/dist/src/plugin.test.d.ts +4 -0
  355. package/dist/src/plugin.test.js +1363 -0
  356. package/dist/src/registry-api.d.ts +13 -0
  357. package/dist/src/registry-api.js +10 -0
  358. package/dist/src/registry.d.ts +123 -0
  359. package/dist/src/registry.js +125 -0
  360. package/dist/src/registry.test.d.ts +4 -0
  361. package/dist/src/registry.test.js +209 -0
  362. package/dist/src/runtime-copy.test.d.ts +1 -0
  363. package/dist/src/runtime-copy.test.js +29 -0
  364. package/dist/src/runtime-detect.d.ts +31 -0
  365. package/dist/src/runtime-detect.js +47 -0
  366. package/dist/src/runtime-detect.test.d.ts +1 -0
  367. package/dist/src/runtime-detect.test.js +39 -0
  368. package/dist/src/runtime-identity.test.d.ts +1 -0
  369. package/dist/src/runtime-identity.test.js +18 -0
  370. package/dist/src/runtime.d.ts +47 -0
  371. package/dist/src/runtime.js +54 -0
  372. package/dist/src/scripts/framework.d.ts +7 -0
  373. package/dist/src/scripts/framework.js +25 -0
  374. package/dist/src/scripts/interact.d.ts +4 -0
  375. package/dist/src/scripts/interact.js +20 -0
  376. package/dist/src/scripts/store.d.ts +13 -0
  377. package/dist/src/scripts/store.js +48 -0
  378. package/dist/src/serialization.d.ts +42 -0
  379. package/dist/src/serialization.js +103 -0
  380. package/dist/src/serialization.test.d.ts +1 -0
  381. package/dist/src/serialization.test.js +78 -0
  382. package/dist/src/skills.d.ts +14 -0
  383. package/dist/src/skills.js +127 -0
  384. package/dist/src/skills.test.d.ts +1 -0
  385. package/dist/src/skills.test.js +71 -0
  386. package/dist/src/snapshotFormatter.d.ts +11 -0
  387. package/dist/src/snapshotFormatter.js +338 -0
  388. package/dist/src/snapshotFormatter.test.d.ts +7 -0
  389. package/dist/src/snapshotFormatter.test.js +521 -0
  390. package/dist/src/tui.d.ts +28 -0
  391. package/dist/src/tui.js +179 -0
  392. package/dist/src/tui.test.d.ts +1 -0
  393. package/dist/src/tui.test.js +19 -0
  394. package/dist/src/types.d.ts +236 -0
  395. package/dist/src/types.js +7 -0
  396. package/dist/src/update-check.d.ts +66 -0
  397. package/dist/src/update-check.js +206 -0
  398. package/dist/src/update-check.test.d.ts +1 -0
  399. package/dist/src/update-check.test.js +117 -0
  400. package/dist/src/utils.d.ts +57 -0
  401. package/dist/src/utils.js +152 -0
  402. package/dist/src/utils.test.d.ts +1 -0
  403. package/dist/src/utils.test.js +155 -0
  404. package/dist/src/validate.d.ts +21 -0
  405. package/dist/src/validate.js +130 -0
  406. package/dist/src/validate.test.d.ts +9 -0
  407. package/dist/src/validate.test.js +90 -0
  408. package/dist/src/verify.d.ts +26 -0
  409. package/dist/src/verify.js +67 -0
  410. package/dist/src/version.d.ts +4 -0
  411. package/dist/src/version.js +22 -0
  412. package/dist/src/weixin-download.test.d.ts +1 -0
  413. package/dist/src/weixin-download.test.js +45 -0
  414. package/package.json +4 -3
  415. package/scripts/check-package-bin.mjs +95 -0
@@ -0,0 +1,803 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import { CliError } from '../errors.js';
3
+ import { BasePage } from './base-page.js';
4
+ import { TargetError } from './target-errors.js';
5
+ class TestPage extends BasePage {
6
+ result;
7
+ args;
8
+ async goto() { }
9
+ async evaluate() { return null; }
10
+ async evaluateWithArgs(_js, args) {
11
+ this.args = args;
12
+ return this.result;
13
+ }
14
+ async getCookies() { return []; }
15
+ async screenshot() { return ''; }
16
+ async tabs() { return []; }
17
+ async selectTab() { }
18
+ }
19
+ class ActionPage extends BasePage {
20
+ results = [];
21
+ withArgsResults = [];
22
+ scripts = [];
23
+ withArgs = [];
24
+ screenshotCalls = [];
25
+ nativeType;
26
+ insertText;
27
+ nativeKeyPress;
28
+ nativeClick;
29
+ setFileInput;
30
+ cdp;
31
+ async goto() { }
32
+ async evaluate(input) {
33
+ this.scripts.push(typeof input === 'string' ? input : input.toString());
34
+ return this.results.shift() ?? null;
35
+ }
36
+ async evaluateWithArgs(js, args) {
37
+ this.scripts.push(js);
38
+ this.withArgs.push(args);
39
+ return this.withArgsResults.shift() ?? null;
40
+ }
41
+ async getCookies() { return []; }
42
+ async screenshot(options = {}) {
43
+ this.screenshotCalls.push(options);
44
+ return 'shot';
45
+ }
46
+ async tabs() { return []; }
47
+ async selectTab() { }
48
+ }
49
+ const resolveOk = { ok: true, matches_n: 1, match_level: 'exact' };
50
+ describe('BasePage.fetchJson', () => {
51
+ it('passes a narrow browser-context JSON request and parses the response in Node', async () => {
52
+ const page = new TestPage();
53
+ page.result = {
54
+ ok: true,
55
+ status: 200,
56
+ url: 'https://api.example.com/items',
57
+ contentType: 'application/json',
58
+ text: '{"items":[1]}',
59
+ };
60
+ await expect(page.fetchJson('https://api.example.com/items', {
61
+ method: 'POST',
62
+ headers: { 'X-Test': '1' },
63
+ body: { q: 'webcmd' },
64
+ timeoutMs: 1234,
65
+ })).resolves.toEqual({ items: [1] });
66
+ expect(page.args).toEqual({
67
+ request: {
68
+ url: 'https://api.example.com/items',
69
+ method: 'POST',
70
+ headers: { 'X-Test': '1' },
71
+ body: { q: 'webcmd' },
72
+ hasBody: true,
73
+ timeoutMs: 1234,
74
+ },
75
+ });
76
+ });
77
+ it('throws a CliError for non-JSON responses', async () => {
78
+ const page = new TestPage();
79
+ page.result = {
80
+ ok: true,
81
+ status: 200,
82
+ url: 'https://api.example.com/items',
83
+ contentType: 'text/html',
84
+ text: '<html>blocked</html>',
85
+ };
86
+ const err = await page.fetchJson('https://api.example.com/items').catch((error) => error);
87
+ expect(err).toBeInstanceOf(CliError);
88
+ expect(err.code).toBe('FETCH_ERROR');
89
+ expect(err.message).toContain('Expected JSON');
90
+ expect(err.hint).toContain('blocked');
91
+ });
92
+ it('throws a CliError for browser fetch transport errors', async () => {
93
+ const page = new TestPage();
94
+ page.result = {
95
+ ok: false,
96
+ status: 0,
97
+ url: 'https://api.example.com/items',
98
+ text: '',
99
+ error: 'The operation was aborted.',
100
+ };
101
+ await expect(page.fetchJson('https://api.example.com/items')).rejects.toMatchObject({
102
+ code: 'FETCH_ERROR',
103
+ message: expect.stringContaining('The operation was aborted.'),
104
+ });
105
+ });
106
+ });
107
+ describe('BasePage annotatedScreenshot', () => {
108
+ it('refreshes DOM refs, captures with a temporary visual overlay, and cleans up', async () => {
109
+ const page = new ActionPage();
110
+ page.results = [
111
+ 'snapshot',
112
+ '["hash"]',
113
+ { annotated: 1, truncated: false },
114
+ true,
115
+ ];
116
+ await expect(page.annotatedScreenshot({ path: '/tmp/webcmd.png', annotate: true })).resolves.toBe('shot');
117
+ expect(page.scripts[0]).toContain('const VIEWPORT_EXPAND = 0');
118
+ expect(page.scripts[2]).toContain('__webcmd_visual_ref_overlay');
119
+ expect(page.scripts[2]).toContain('[data-webcmd-ref]');
120
+ expect(page.scripts[3]).toContain('__webcmd_visual_ref_overlay');
121
+ expect(page.screenshotCalls).toEqual([{ path: '/tmp/webcmd.png', annotate: false }]);
122
+ });
123
+ });
124
+ describe('BasePage native input routing', () => {
125
+ it('types rich-editor text via native Input.insertText when available', async () => {
126
+ const page = new ActionPage();
127
+ page.nativeType = vi.fn().mockResolvedValue(undefined);
128
+ page.results = [resolveOk, { ok: true, mode: 'contenteditable' }];
129
+ await expect(page.typeText('#editor', 'hello')).resolves.toEqual({ matches_n: 1, match_level: 'exact' });
130
+ expect(page.nativeType).toHaveBeenCalledWith('hello');
131
+ expect(page.scripts).toHaveLength(2);
132
+ expect(page.scripts[1]).toContain('nearestContentEditableHost');
133
+ expect(page.scripts.join('\n')).not.toContain("return 'typed'");
134
+ });
135
+ it('uses CDP DOM focus and scroll before native text insertion when available', async () => {
136
+ const page = new ActionPage();
137
+ page.nativeType = vi.fn().mockResolvedValue(undefined);
138
+ page.cdp = vi.fn()
139
+ .mockResolvedValueOnce({})
140
+ .mockResolvedValueOnce({ root: { nodeId: 1 } })
141
+ .mockResolvedValueOnce({ nodeId: 7 })
142
+ .mockResolvedValueOnce({})
143
+ .mockResolvedValueOnce({})
144
+ .mockResolvedValueOnce({ root: { nodeId: 1 } })
145
+ .mockResolvedValueOnce({ nodeId: 7 })
146
+ .mockResolvedValueOnce({});
147
+ page.results = [resolveOk, { ok: true, mode: 'input' }];
148
+ page.withArgsResults = [{ ok: true }, undefined, { ok: true }, undefined];
149
+ await page.typeText('#q', 'hello');
150
+ expect(page.cdp).toHaveBeenCalledWith('DOM.scrollIntoViewIfNeeded', { nodeId: 7 });
151
+ expect(page.cdp).toHaveBeenCalledWith('DOM.focus', { nodeId: 7 });
152
+ expect(page.nativeType).toHaveBeenCalledWith('hello');
153
+ expect(page.scripts.at(-1)).toContain('if (false) el.scrollIntoView');
154
+ expect(page.scripts.at(-1)).toContain('if (false) {');
155
+ });
156
+ it('keeps the DOM setter fallback when native text insertion is unavailable', async () => {
157
+ const page = new ActionPage();
158
+ page.results = [resolveOk, 'typed'];
159
+ await page.typeText('#q', 'hello');
160
+ expect(page.scripts).toHaveLength(2);
161
+ expect(page.scripts[1]).toContain('document.execCommand');
162
+ expect(page.scripts[1]).toContain("return 'typed'");
163
+ });
164
+ it('falls back to DOM typing if native text insertion fails', async () => {
165
+ const page = new ActionPage();
166
+ page.nativeType = vi.fn().mockRejectedValue(new Error('native failed'));
167
+ page.results = [resolveOk, { ok: true, mode: 'input' }, 'typed'];
168
+ await page.typeText('#q', 'hello');
169
+ expect(page.nativeType).toHaveBeenCalledWith('hello');
170
+ expect(page.scripts).toHaveLength(3);
171
+ expect(page.scripts[2]).toContain("return 'typed'");
172
+ });
173
+ it('fills text through the native input path and verifies the exact value', async () => {
174
+ const page = new ActionPage();
175
+ page.nativeType = vi.fn().mockResolvedValue(undefined);
176
+ page.results = [
177
+ resolveOk,
178
+ { ok: true, mode: 'textarea' },
179
+ { ok: true, actual: 'line1\\n/ / raw', expected: 'line1\\n/ / raw', length: 14, mode: 'textarea' },
180
+ ];
181
+ await expect(page.fillText('#message', 'line1\\n/ / raw')).resolves.toEqual({
182
+ filled: true,
183
+ verified: true,
184
+ expected: 'line1\\n/ / raw',
185
+ actual: 'line1\\n/ / raw',
186
+ length: 14,
187
+ matches_n: 1,
188
+ match_level: 'exact',
189
+ mode: 'textarea',
190
+ });
191
+ expect(page.nativeType).toHaveBeenCalledWith('line1\\n/ / raw');
192
+ expect(page.scripts).toHaveLength(3);
193
+ expect(page.scripts[2]).toContain('actual ===');
194
+ });
195
+ it('falls back to the DOM setter when native fill insertion is unavailable', async () => {
196
+ const page = new ActionPage();
197
+ page.results = [
198
+ resolveOk,
199
+ { ok: true, mode: 'input' },
200
+ 'typed',
201
+ { ok: true, actual: 'hello', expected: 'hello', length: 5, mode: 'input' },
202
+ ];
203
+ await expect(page.fillText('#q', 'hello')).resolves.toEqual(expect.objectContaining({
204
+ filled: true,
205
+ verified: true,
206
+ actual: 'hello',
207
+ mode: 'input',
208
+ }));
209
+ expect(page.scripts).toHaveLength(4);
210
+ expect(page.scripts[2]).toContain("return 'typed'");
211
+ });
212
+ it('falls back to DOM fill if native insertion does not verify', async () => {
213
+ const page = new ActionPage();
214
+ page.nativeType = vi.fn().mockResolvedValue(undefined);
215
+ page.results = [
216
+ resolveOk,
217
+ { ok: true, mode: 'input' },
218
+ { ok: false, actual: '', expected: 'hello', length: 0, mode: 'input' },
219
+ 'typed',
220
+ { ok: true, actual: 'hello', expected: 'hello', length: 5, mode: 'input' },
221
+ ];
222
+ await expect(page.fillText('#q', 'hello')).resolves.toEqual(expect.objectContaining({
223
+ filled: true,
224
+ verified: true,
225
+ actual: 'hello',
226
+ }));
227
+ expect(page.nativeType).toHaveBeenCalledWith('hello');
228
+ expect(page.scripts).toHaveLength(5);
229
+ expect(page.scripts[3]).toContain("return 'typed'");
230
+ });
231
+ it('throws a structured not_editable error for non-fillable targets', async () => {
232
+ const page = new ActionPage();
233
+ page.results = [resolveOk, { ok: false, reason: 'not_editable', tag: 'button' }];
234
+ const err = await page.fillText('button', 'hello').catch((error) => error);
235
+ expect(err).toBeInstanceOf(TargetError);
236
+ expect(err.code).toBe('not_editable');
237
+ });
238
+ it('uses CDP DOM scrollIntoViewIfNeeded before measuring rect for click', async () => {
239
+ const page = new ActionPage();
240
+ page.nativeClick = vi.fn().mockResolvedValue(undefined);
241
+ page.cdp = vi.fn()
242
+ .mockResolvedValueOnce({})
243
+ .mockResolvedValueOnce({ root: { nodeId: 1 } })
244
+ .mockResolvedValueOnce({ nodeId: 9 })
245
+ .mockResolvedValueOnce({});
246
+ page.results = [resolveOk, { x: 50, y: 100, w: 200, h: 32, visible: true }];
247
+ page.withArgsResults = [{ ok: true, multiple: false, accept: 'application/pdf' }, undefined];
248
+ await page.click('#save');
249
+ expect(page.cdp).toHaveBeenCalledWith('DOM.scrollIntoViewIfNeeded', { nodeId: 9 });
250
+ // After CDP scroll, boundingRectResolvedJs runs with skipScroll=true.
251
+ expect(page.scripts.at(-1)).toContain('if (false) el.scrollIntoView');
252
+ });
253
+ it('clicks via CDP Input.dispatchMouseEvent when rect is visible', async () => {
254
+ const page = new ActionPage();
255
+ page.nativeClick = vi.fn().mockResolvedValue(undefined);
256
+ page.results = [resolveOk, { x: 50, y: 100, w: 200, h: 32, visible: true }];
257
+ await page.click('#category');
258
+ expect(page.nativeClick).toHaveBeenCalledWith(50, 100);
259
+ expect(page.nativeClick).toHaveBeenCalledTimes(1);
260
+ expect(page.scripts).toHaveLength(2);
261
+ expect(page.scripts[1]).toContain('getBoundingClientRect');
262
+ expect(page.scripts.join('\n')).not.toContain('el.click()');
263
+ });
264
+ it('clicks AX snapshot refs through backend node coordinates without DOM resolver', async () => {
265
+ const page = new ActionPage();
266
+ page.nativeClick = vi.fn().mockResolvedValue(undefined);
267
+ page.cdp = vi.fn(async (method) => {
268
+ if (method === 'Accessibility.getFullAXTree') {
269
+ return {
270
+ nodes: [
271
+ { nodeId: '1', role: { value: 'RootWebArea' }, name: { value: 'Demo' }, childIds: ['2'] },
272
+ { nodeId: '2', role: { value: 'button' }, name: { value: 'Submit' }, backendDOMNodeId: 10 },
273
+ ],
274
+ };
275
+ }
276
+ if (method === 'Page.getFrameTree') {
277
+ return {
278
+ frameTree: { frame: { id: 'root', url: 'https://app.example/' } },
279
+ };
280
+ }
281
+ if (method === 'DOM.getBoxModel') {
282
+ return { model: { content: [10, 20, 50, 20, 50, 40, 10, 40] } };
283
+ }
284
+ return {};
285
+ });
286
+ await expect(page.snapshot({ source: 'ax' })).resolves.toContain('[1]button "Submit"');
287
+ await expect(page.click('1')).resolves.toEqual({ matches_n: 1, match_level: 'exact' });
288
+ expect(page.cdp).toHaveBeenNthCalledWith(1, 'Accessibility.enable', {});
289
+ expect(page.cdp).toHaveBeenNthCalledWith(2, 'Accessibility.getFullAXTree', {});
290
+ expect(page.cdp).toHaveBeenCalledWith('Accessibility.getFullAXTree', {});
291
+ expect(page.cdp).toHaveBeenCalledWith('Page.getFrameTree', {});
292
+ expect(page.cdp).toHaveBeenCalledWith('DOM.getBoxModel', { backendNodeId: 10 });
293
+ expect(page.nativeClick).toHaveBeenCalledWith(30, 30);
294
+ expect(page.scripts).toHaveLength(0);
295
+ });
296
+ it('adds same-origin iframe AX refs and clicks them by frame-scoped backend node', async () => {
297
+ const page = new ActionPage();
298
+ page.nativeClick = vi.fn().mockResolvedValue(undefined);
299
+ page.cdp = vi.fn(async (method, params) => {
300
+ if (method === 'Accessibility.getFullAXTree' && params?.frameId === 'same-frame') {
301
+ return {
302
+ nodes: [
303
+ { nodeId: '1', role: { value: 'RootWebArea' }, name: { value: 'Embedded' }, childIds: ['2'] },
304
+ { nodeId: '2', role: { value: 'button' }, name: { value: 'Frame Save' }, backendDOMNodeId: 20 },
305
+ ],
306
+ };
307
+ }
308
+ if (method === 'Accessibility.getFullAXTree' && params?.frameId === 'cross-frame') {
309
+ return {
310
+ nodes: [
311
+ { nodeId: '1', role: { value: 'RootWebArea' }, name: { value: 'Cross' }, childIds: ['2'] },
312
+ { nodeId: '2', role: { value: 'button' }, name: { value: 'Cross Save' }, backendDOMNodeId: 30 },
313
+ ],
314
+ };
315
+ }
316
+ if (method === 'Accessibility.getFullAXTree') {
317
+ return {
318
+ nodes: [
319
+ { nodeId: '1', role: { value: 'RootWebArea' }, name: { value: 'Demo' }, childIds: ['2'] },
320
+ { nodeId: '2', role: { value: 'button' }, name: { value: 'Main Save' }, backendDOMNodeId: 10 },
321
+ ],
322
+ };
323
+ }
324
+ if (method === 'Page.getFrameTree') {
325
+ return {
326
+ frameTree: {
327
+ frame: { id: 'root', url: 'https://app.example/' },
328
+ childFrames: [
329
+ { frame: { id: 'same-frame', url: 'https://app.example/embed' } },
330
+ { frame: { id: 'cross-frame', url: 'https://other.example/embed' } },
331
+ ],
332
+ },
333
+ };
334
+ }
335
+ if (method === 'DOM.getBoxModel') {
336
+ return { model: { content: [100, 200, 140, 200, 140, 220, 100, 220] } };
337
+ }
338
+ return {};
339
+ });
340
+ const snapshot = await page.snapshot({ source: 'ax' });
341
+ expect(snapshot).toContain('[1]button "Main Save"');
342
+ expect(snapshot).toContain('frame "https://app.example/embed":');
343
+ expect(snapshot).toContain('[2]button "Frame Save"');
344
+ expect(snapshot).toContain('frame "https://other.example/embed":');
345
+ expect(snapshot).toContain('[3]button "Cross Save"');
346
+ await expect(page.click('2')).resolves.toEqual({ matches_n: 1, match_level: 'exact' });
347
+ expect(page.cdp).toHaveBeenCalledWith('Accessibility.getFullAXTree', { frameId: 'same-frame' });
348
+ expect(page.cdp).toHaveBeenCalledWith('Accessibility.enable', { frameId: 'cross-frame', sessionId: 'target', targetUrl: 'https://other.example/embed' });
349
+ expect(page.cdp).toHaveBeenCalledWith('Accessibility.getFullAXTree', { frameId: 'cross-frame', sessionId: 'target', targetUrl: 'https://other.example/embed' });
350
+ expect(page.cdp).toHaveBeenCalledWith('DOM.getBoxModel', { backendNodeId: 20 });
351
+ expect(page.nativeClick).toHaveBeenCalledWith(120, 210);
352
+ });
353
+ it('clicks cross-origin AX refs through a frame-target CDP route', async () => {
354
+ const page = new ActionPage();
355
+ page.nativeClick = vi.fn().mockResolvedValue(undefined);
356
+ page.cdp = vi.fn(async (method, params) => {
357
+ if (method === 'Page.getFrameTree') {
358
+ return {
359
+ frameTree: {
360
+ frame: { id: 'root', url: 'https://app.example/' },
361
+ childFrames: [{ frame: { id: 'cross-frame', url: 'https://other.example/embed' } }],
362
+ },
363
+ };
364
+ }
365
+ if (method === 'Accessibility.getFullAXTree' && params?.sessionId === 'target') {
366
+ return {
367
+ nodes: [
368
+ { nodeId: '1', role: { value: 'RootWebArea' }, childIds: ['2'] },
369
+ { nodeId: '2', role: { value: 'button' }, name: { value: 'Cross Save' }, backendDOMNodeId: 99 },
370
+ ],
371
+ };
372
+ }
373
+ if (method === 'Accessibility.getFullAXTree') {
374
+ return { nodes: [{ nodeId: '1', role: { value: 'RootWebArea' } }] };
375
+ }
376
+ if (method === 'DOM.getBoxModel') {
377
+ return { model: { content: [300, 400, 340, 400, 340, 420, 300, 420] } };
378
+ }
379
+ return {};
380
+ });
381
+ const snapshot = await page.snapshot({ source: 'ax' });
382
+ expect(snapshot).toContain('[1]button "Cross Save"');
383
+ await expect(page.click('1')).resolves.toEqual({ matches_n: 1, match_level: 'exact' });
384
+ expect(page.cdp).toHaveBeenCalledWith('Accessibility.enable', { frameId: 'cross-frame', sessionId: 'target', targetUrl: 'https://other.example/embed' });
385
+ expect(page.cdp).toHaveBeenCalledWith('Accessibility.getFullAXTree', { frameId: 'cross-frame', sessionId: 'target', targetUrl: 'https://other.example/embed' });
386
+ expect(page.cdp).toHaveBeenCalledWith('DOM.getBoxModel', { backendNodeId: 99, frameId: 'cross-frame', sessionId: 'target', targetUrl: 'https://other.example/embed' });
387
+ expect(page.nativeClick).toHaveBeenCalledWith(320, 410);
388
+ });
389
+ it('enables Accessibility in cross-origin frame target sessions before stale AX recovery', async () => {
390
+ const page = new ActionPage();
391
+ page.nativeClick = vi.fn().mockResolvedValue(undefined);
392
+ let crossAxCalls = 0;
393
+ page.cdp = vi.fn(async (method, params) => {
394
+ if (method === 'Page.getFrameTree') {
395
+ return {
396
+ frameTree: {
397
+ frame: { id: 'root', url: 'https://app.example/' },
398
+ childFrames: [{ frame: { id: 'cross-frame', url: 'https://other.example/embed' } }],
399
+ },
400
+ };
401
+ }
402
+ if (method === 'Accessibility.getFullAXTree' && params?.sessionId === 'target') {
403
+ crossAxCalls++;
404
+ const backendDOMNodeId = crossAxCalls === 1 ? 99 : 100;
405
+ return {
406
+ nodes: [
407
+ { nodeId: '1', role: { value: 'RootWebArea' }, childIds: ['2'] },
408
+ { nodeId: '2', role: { value: 'button' }, name: { value: 'Cross Save' }, backendDOMNodeId },
409
+ ],
410
+ };
411
+ }
412
+ if (method === 'Accessibility.getFullAXTree') {
413
+ return { nodes: [{ nodeId: '1', role: { value: 'RootWebArea' } }] };
414
+ }
415
+ if (method === 'DOM.getBoxModel') {
416
+ if (params?.backendNodeId === 99)
417
+ throw new Error('No node with given id found');
418
+ return { model: { content: [300, 400, 340, 400, 340, 420, 300, 420] } };
419
+ }
420
+ return {};
421
+ });
422
+ await page.snapshot({ source: 'ax' });
423
+ await expect(page.click('1')).resolves.toEqual({ matches_n: 1, match_level: 'reidentified' });
424
+ expect(page.cdp).toHaveBeenCalledWith('Accessibility.enable', { frameId: 'cross-frame', sessionId: 'target', targetUrl: 'https://other.example/embed' });
425
+ expect(page.cdp).toHaveBeenCalledWith('Accessibility.getFullAXTree', { frameId: 'cross-frame', sessionId: 'target', targetUrl: 'https://other.example/embed' });
426
+ expect(page.cdp).toHaveBeenCalledWith('DOM.getBoxModel', { backendNodeId: 99, frameId: 'cross-frame', sessionId: 'target', targetUrl: 'https://other.example/embed' });
427
+ expect(page.cdp).toHaveBeenCalledWith('DOM.getBoxModel', { backendNodeId: 100, frameId: 'cross-frame', sessionId: 'target', targetUrl: 'https://other.example/embed' });
428
+ expect(page.nativeClick).toHaveBeenCalledWith(320, 410);
429
+ });
430
+ it('recovers stale iframe AX refs inside the original frame', async () => {
431
+ const page = new ActionPage();
432
+ page.nativeClick = vi.fn().mockResolvedValue(undefined);
433
+ let iframeAxCalls = 0;
434
+ page.cdp = vi.fn(async (method, params) => {
435
+ if (method === 'Page.getFrameTree') {
436
+ return {
437
+ frameTree: {
438
+ frame: { id: 'root', url: 'https://app.example/' },
439
+ childFrames: [{ frame: { id: 'same-frame', url: 'https://app.example/embed' } }],
440
+ },
441
+ };
442
+ }
443
+ if (method === 'Accessibility.getFullAXTree' && params?.frameId === 'same-frame') {
444
+ iframeAxCalls++;
445
+ return {
446
+ nodes: [
447
+ { nodeId: '1', role: { value: 'RootWebArea' }, childIds: ['2'] },
448
+ { nodeId: '2', role: { value: 'button' }, name: { value: 'Frame Save' }, backendDOMNodeId: iframeAxCalls === 1 ? 20 : 42 },
449
+ ],
450
+ };
451
+ }
452
+ if (method === 'Accessibility.getFullAXTree') {
453
+ return {
454
+ nodes: [
455
+ { nodeId: '1', role: { value: 'RootWebArea' }, childIds: ['2'] },
456
+ { nodeId: '2', role: { value: 'button' }, name: { value: 'Main Save' }, backendDOMNodeId: 10 },
457
+ ],
458
+ };
459
+ }
460
+ if (method === 'DOM.getBoxModel') {
461
+ if (params?.backendNodeId === 20)
462
+ throw new Error('No node with given id found');
463
+ return { model: { content: [200, 300, 240, 300, 240, 320, 200, 320] } };
464
+ }
465
+ return {};
466
+ });
467
+ await page.snapshot({ source: 'ax' });
468
+ await expect(page.click('2')).resolves.toEqual({ matches_n: 1, match_level: 'reidentified' });
469
+ expect(page.cdp).toHaveBeenCalledWith('Accessibility.enable', {});
470
+ expect(page.cdp).toHaveBeenCalledWith('Accessibility.getFullAXTree', { frameId: 'same-frame' });
471
+ expect(page.cdp).toHaveBeenCalledWith('DOM.getBoxModel', { backendNodeId: 20 });
472
+ expect(page.cdp).toHaveBeenCalledWith('DOM.getBoxModel', { backendNodeId: 42 });
473
+ expect(page.nativeClick).toHaveBeenCalledWith(220, 310);
474
+ });
475
+ it('recovers stale AX refs by role/name/nth before clicking', async () => {
476
+ const page = new ActionPage();
477
+ page.nativeClick = vi.fn().mockResolvedValue(undefined);
478
+ let axCalls = 0;
479
+ page.cdp = vi.fn(async (method, params) => {
480
+ if (method === 'Accessibility.getFullAXTree') {
481
+ axCalls++;
482
+ return {
483
+ nodes: [
484
+ { nodeId: '1', role: { value: 'RootWebArea' }, name: { value: 'Demo' }, childIds: ['2'] },
485
+ { nodeId: '2', role: { value: 'button' }, name: { value: 'Submit' }, backendDOMNodeId: axCalls === 1 ? 10 : 42 },
486
+ ],
487
+ };
488
+ }
489
+ if (method === 'DOM.getBoxModel') {
490
+ if (params?.backendNodeId === 10)
491
+ throw new Error('No node with given id found');
492
+ return { model: { content: [100, 200, 140, 200, 140, 220, 100, 220] } };
493
+ }
494
+ return {};
495
+ });
496
+ await page.snapshot({ source: 'ax' });
497
+ await expect(page.click('1')).resolves.toEqual({ matches_n: 1, match_level: 'reidentified' });
498
+ expect(page.cdp).toHaveBeenCalledWith('DOM.getBoxModel', { backendNodeId: 10 });
499
+ expect(page.cdp).toHaveBeenCalledWith('DOM.getBoxModel', { backendNodeId: 42 });
500
+ expect(page.nativeClick).toHaveBeenCalledWith(120, 210);
501
+ });
502
+ it('recovers AX refs across 10 repeated React-style rerenders', async () => {
503
+ const page = new ActionPage();
504
+ page.nativeClick = vi.fn().mockResolvedValue(undefined);
505
+ let currentBackendId = 100;
506
+ const staleBackendIds = new Set();
507
+ page.cdp = vi.fn(async (method, params) => {
508
+ if (method === 'Accessibility.getFullAXTree') {
509
+ return {
510
+ nodes: [
511
+ { nodeId: '1', role: { value: 'RootWebArea' }, name: { value: 'Demo' }, childIds: ['2'] },
512
+ { nodeId: '2', role: { value: 'button' }, name: { value: 'Submit' }, backendDOMNodeId: currentBackendId },
513
+ ],
514
+ };
515
+ }
516
+ if (method === 'DOM.getBoxModel') {
517
+ const id = params?.backendNodeId;
518
+ if (staleBackendIds.has(id))
519
+ throw new Error('No node with given id found');
520
+ return { model: { content: [id, id, id + 20, id, id + 20, id + 10, id, id + 10] } };
521
+ }
522
+ return {};
523
+ });
524
+ await page.snapshot({ source: 'ax' });
525
+ for (let i = 0; i < 10; i++) {
526
+ staleBackendIds.add(currentBackendId);
527
+ currentBackendId += 1;
528
+ await expect(page.click('1')).resolves.toEqual({ matches_n: 1, match_level: 'reidentified' });
529
+ }
530
+ expect(page.nativeClick).toHaveBeenCalledTimes(10);
531
+ });
532
+ it('falls back to JS el.click() when nativeClick is unavailable', async () => {
533
+ const page = new ActionPage();
534
+ page.results = [
535
+ resolveOk,
536
+ { x: 50, y: 100, w: 200, h: 32, visible: true },
537
+ { status: 'clicked', x: 50, y: 100 },
538
+ ];
539
+ await page.click('#save');
540
+ expect(page.scripts).toHaveLength(3);
541
+ expect(page.scripts[1]).toContain('getBoundingClientRect');
542
+ expect(page.scripts[2]).toContain('el.click()');
543
+ });
544
+ it('falls back to JS el.click() when rect is zero-area', async () => {
545
+ const page = new ActionPage();
546
+ page.nativeClick = vi.fn().mockResolvedValue(undefined);
547
+ page.results = [
548
+ resolveOk,
549
+ { x: 0, y: 0, w: 0, h: 0, visible: false },
550
+ { status: 'clicked', x: 0, y: 0 },
551
+ ];
552
+ await page.click('#hidden');
553
+ expect(page.nativeClick).not.toHaveBeenCalled();
554
+ expect(page.scripts).toHaveLength(3);
555
+ expect(page.scripts[2]).toContain('el.click()');
556
+ });
557
+ it('retries CDP click when JS path throws but yields coordinates', async () => {
558
+ const page = new ActionPage();
559
+ const nativeClick = vi.fn()
560
+ .mockRejectedValueOnce(new Error('cdp transient'))
561
+ .mockResolvedValueOnce(undefined);
562
+ page.nativeClick = nativeClick;
563
+ page.results = [
564
+ resolveOk,
565
+ { x: 10, y: 20, w: 100, h: 30, visible: true },
566
+ { status: 'js_failed', x: 10, y: 20, error: 'click intercepted' },
567
+ ];
568
+ await page.click('#flaky');
569
+ expect(nativeClick).toHaveBeenCalledTimes(2);
570
+ expect(nativeClick).toHaveBeenNthCalledWith(1, 10, 20);
571
+ expect(nativeClick).toHaveBeenNthCalledWith(2, 10, 20);
572
+ });
573
+ it('hovers via native CDP mouseMoved when coordinates are available', async () => {
574
+ const page = new ActionPage();
575
+ page.cdp = vi.fn().mockResolvedValue({});
576
+ page.results = [resolveOk, { x: 70, y: 80, w: 100, h: 20, visible: true }];
577
+ await expect(page.hover('#menu')).resolves.toEqual({ matches_n: 1, match_level: 'exact' });
578
+ expect(page.cdp).toHaveBeenCalledWith('Input.dispatchMouseEvent', { type: 'mouseMoved', x: 70, y: 80 });
579
+ expect(page.scripts.at(-1)).toContain('getBoundingClientRect');
580
+ });
581
+ it('focuses through CDP DOM.focus when available', async () => {
582
+ const page = new ActionPage();
583
+ page.cdp = vi.fn(async (method) => {
584
+ if (method === 'DOM.getDocument')
585
+ return { root: { nodeId: 1 } };
586
+ if (method === 'DOM.querySelector')
587
+ return { nodeId: 9 };
588
+ return {};
589
+ });
590
+ page.results = [resolveOk, true];
591
+ page.withArgsResults = [{ ok: true }, undefined];
592
+ await expect(page.focus('#email')).resolves.toEqual({ focused: true, matches_n: 1, match_level: 'exact' });
593
+ expect(page.cdp).toHaveBeenCalledWith('DOM.focus', { nodeId: 9 });
594
+ });
595
+ it('verifies CDP focus and falls back to DOM focus when focus did not stick', async () => {
596
+ const page = new ActionPage();
597
+ page.cdp = vi.fn(async (method) => {
598
+ if (method === 'DOM.getDocument')
599
+ return { root: { nodeId: 1 } };
600
+ if (method === 'DOM.querySelector')
601
+ return { nodeId: 9 };
602
+ return {};
603
+ });
604
+ page.results = [resolveOk, false, true];
605
+ page.withArgsResults = [{ ok: true }, undefined];
606
+ await expect(page.focus('#email')).resolves.toEqual({ focused: true, matches_n: 1, match_level: 'exact' });
607
+ expect(page.cdp).toHaveBeenCalledWith('DOM.focus', { nodeId: 9 });
608
+ expect(page.scripts.at(-2)).toContain('document.activeElement === el');
609
+ expect(page.scripts.at(-1)).toContain('el.focus');
610
+ });
611
+ it('double-clicks via native CDP mouse events', async () => {
612
+ const page = new ActionPage();
613
+ page.cdp = vi.fn().mockResolvedValue({});
614
+ page.results = [resolveOk, { x: 20, y: 30, w: 100, h: 20, visible: true }];
615
+ await expect(page.dblClick('#row')).resolves.toEqual({ matches_n: 1, match_level: 'exact' });
616
+ expect(page.cdp).toHaveBeenCalledWith('Input.dispatchMouseEvent', { type: 'mouseMoved', x: 20, y: 30 });
617
+ expect(page.cdp).toHaveBeenCalledWith('Input.dispatchMouseEvent', { type: 'mousePressed', x: 20, y: 30, button: 'left', clickCount: 2 });
618
+ expect(page.cdp).toHaveBeenCalledWith('Input.dispatchMouseEvent', { type: 'mouseReleased', x: 20, y: 30, button: 'left', clickCount: 2 });
619
+ });
620
+ it('checks a checkbox only when its current state differs', async () => {
621
+ const page = new ActionPage();
622
+ page.nativeClick = vi.fn().mockResolvedValue(undefined);
623
+ page.results = [
624
+ resolveOk,
625
+ { ok: true, checked: false, disabled: false, kind: 'checkbox' },
626
+ resolveOk,
627
+ { x: 20, y: 30, w: 40, h: 20, visible: true },
628
+ { ok: true, checked: true, disabled: false, kind: 'checkbox' },
629
+ ];
630
+ await expect(page.setChecked('#agree', true)).resolves.toEqual({
631
+ checked: true,
632
+ changed: true,
633
+ matches_n: 1,
634
+ match_level: 'exact',
635
+ kind: 'checkbox',
636
+ });
637
+ expect(page.nativeClick).toHaveBeenCalledWith(20, 30);
638
+ });
639
+ it('does not click a checkbox that already has the requested state', async () => {
640
+ const page = new ActionPage();
641
+ page.nativeClick = vi.fn().mockResolvedValue(undefined);
642
+ page.results = [resolveOk, { ok: true, checked: false, disabled: false, kind: 'checkbox' }];
643
+ await expect(page.setChecked('#agree', false)).resolves.toEqual({
644
+ checked: false,
645
+ changed: false,
646
+ matches_n: 1,
647
+ match_level: 'exact',
648
+ kind: 'checkbox',
649
+ });
650
+ expect(page.nativeClick).not.toHaveBeenCalled();
651
+ });
652
+ it('rejects non-checkable targets with a structured error', async () => {
653
+ const page = new ActionPage();
654
+ page.results = [resolveOk, { ok: false, reason: 'not_checkable', tag: 'button' }];
655
+ const err = await page.setChecked('button', true).catch((error) => error);
656
+ expect(err).toBeInstanceOf(TargetError);
657
+ expect(err.code).toBe('not_checkable');
658
+ });
659
+ it('rejects attempts to uncheck a radio button directly', async () => {
660
+ const page = new ActionPage();
661
+ page.results = [resolveOk, { ok: true, checked: true, disabled: false, kind: 'radio' }];
662
+ const err = await page.setChecked('#radio', false).catch((error) => error);
663
+ expect(err).toBeInstanceOf(TargetError);
664
+ expect(err.code).toBe('not_checkable');
665
+ expect(err.hint).toContain('Select another radio');
666
+ });
667
+ it('treats ARIA radio controls like radio buttons', async () => {
668
+ const page = new ActionPage();
669
+ page.results = [resolveOk, { ok: true, checked: true, disabled: false, kind: 'menuitemradio' }];
670
+ const err = await page.setChecked('[role="menuitemradio"]', false).catch((error) => error);
671
+ expect(err).toBeInstanceOf(TargetError);
672
+ expect(err.code).toBe('not_checkable');
673
+ expect(err.hint).toContain('Select another radio');
674
+ });
675
+ it('uploads files through setFileInput using a temporary marker selector', async () => {
676
+ const page = new ActionPage();
677
+ page.setFileInput = vi.fn().mockResolvedValue(undefined);
678
+ page.results = [
679
+ resolveOk,
680
+ ['receipt.pdf'],
681
+ ];
682
+ page.withArgsResults = [{ ok: true, multiple: false, accept: 'application/pdf' }, undefined];
683
+ await expect(page.uploadFiles('#file', ['/tmp/receipt.pdf'])).resolves.toEqual({
684
+ uploaded: true,
685
+ files: 1,
686
+ file_names: ['receipt.pdf'],
687
+ target: '#file',
688
+ matches_n: 1,
689
+ match_level: 'exact',
690
+ multiple: false,
691
+ accept: 'application/pdf',
692
+ });
693
+ expect(page.setFileInput).toHaveBeenCalledWith(['/tmp/receipt.pdf'], expect.stringMatching(/data-webcmd-upload-target/));
694
+ expect(page.withArgs.at(0)).toMatchObject({ markerAttr: 'data-webcmd-upload-target' });
695
+ expect(page.withArgs.at(-1)).toMatchObject({ markerAttr: 'data-webcmd-upload-target' });
696
+ });
697
+ it('rejects non-file-input upload targets with a structured error', async () => {
698
+ const page = new ActionPage();
699
+ page.setFileInput = vi.fn().mockResolvedValue(undefined);
700
+ page.results = [resolveOk];
701
+ page.withArgsResults = [{ ok: false, tag: 'button', type: '' }];
702
+ const err = await page.uploadFiles('button', ['/tmp/receipt.pdf']).catch((error) => error);
703
+ expect(err).toBeInstanceOf(TargetError);
704
+ expect(err.code).toBe('not_file_input');
705
+ expect(page.setFileInput).not.toHaveBeenCalled();
706
+ });
707
+ it('rejects multiple files for a single-file input before mutating files', async () => {
708
+ const page = new ActionPage();
709
+ page.setFileInput = vi.fn().mockResolvedValue(undefined);
710
+ page.results = [resolveOk];
711
+ page.withArgsResults = [{ ok: true, multiple: false, accept: '' }, undefined];
712
+ const err = await page.uploadFiles('#file', ['/tmp/a.pdf', '/tmp/b.pdf']).catch((error) => error);
713
+ expect(err).toBeInstanceOf(TargetError);
714
+ expect(err.code).toBe('not_file_input');
715
+ expect(page.setFileInput).not.toHaveBeenCalled();
716
+ });
717
+ it('drags between two resolved element centers via native CDP mouse events', async () => {
718
+ const page = new ActionPage();
719
+ page.cdp = vi.fn().mockResolvedValue({});
720
+ page.results = [
721
+ resolveOk,
722
+ { x: 10, y: 20, w: 30, h: 20, visible: true },
723
+ { ok: true, matches_n: 2, match_level: 'stable' },
724
+ {
725
+ source: { x: 10, y: 20, w: 30, h: 20, visible: true },
726
+ target: { x: 110, y: 120, w: 40, h: 30, visible: true },
727
+ },
728
+ ];
729
+ await expect(page.drag('#card', '.lane', { to: { nth: 1 } })).resolves.toEqual({
730
+ dragged: true,
731
+ source: '#card',
732
+ target: '.lane',
733
+ source_matches_n: 1,
734
+ target_matches_n: 2,
735
+ source_match_level: 'exact',
736
+ target_match_level: 'stable',
737
+ });
738
+ expect(page.cdp).toHaveBeenCalledWith('Input.dispatchMouseEvent', { type: 'mouseMoved', x: 10, y: 20 });
739
+ expect(page.cdp).toHaveBeenCalledWith('Input.dispatchMouseEvent', { type: 'mousePressed', x: 10, y: 20, button: 'left', clickCount: 1 });
740
+ expect(page.cdp).toHaveBeenCalledWith('Input.dispatchMouseEvent', { type: 'mouseMoved', x: 60, y: 70, button: 'left', buttons: 1 });
741
+ expect(page.cdp).toHaveBeenCalledWith('Input.dispatchMouseEvent', { type: 'mouseMoved', x: 110, y: 120, button: 'left', buttons: 1 });
742
+ expect(page.cdp).toHaveBeenCalledWith('Input.dispatchMouseEvent', { type: 'mouseReleased', x: 110, y: 120, button: 'left', clickCount: 1 });
743
+ });
744
+ it('presses key chords through native CDP key events when available', async () => {
745
+ const page = new ActionPage();
746
+ page.nativeKeyPress = vi.fn().mockResolvedValue(undefined);
747
+ await page.pressKey('Control+a');
748
+ expect(page.nativeKeyPress).toHaveBeenCalledWith('a', ['Ctrl']);
749
+ expect(page.scripts).toHaveLength(0);
750
+ });
751
+ it('falls back to synthetic keyboard events with parsed modifiers', async () => {
752
+ const page = new ActionPage();
753
+ await page.pressKey('Meta+N');
754
+ expect(page.scripts).toHaveLength(1);
755
+ expect(page.scripts[0]).toContain('key: "N"');
756
+ expect(page.scripts[0]).toContain('metaKey: true');
757
+ });
758
+ });
759
+ describe('BasePage.evaluateWithArgs scoping', () => {
760
+ class EvalCapturePage extends BasePage {
761
+ scripts = [];
762
+ async goto() { }
763
+ async evaluate(input) {
764
+ const code = typeof input === 'string' ? input : input.toString();
765
+ this.scripts.push(code);
766
+ if (typeof input !== 'string')
767
+ return null;
768
+ return globalThis.eval(input);
769
+ }
770
+ async getCookies() { return []; }
771
+ async screenshot() { return ''; }
772
+ async tabs() { return []; }
773
+ async selectTab() { }
774
+ }
775
+ it('wraps declarations in a lexical block to avoid global const redeclaration', async () => {
776
+ const page = new EvalCapturePage();
777
+ const result = await page.evaluateWithArgs('(() => markerAttr)()', { markerAttr: 'test-value' });
778
+ expect(result).toBe('test-value');
779
+ expect(page.scripts).toHaveLength(1);
780
+ const code = page.scripts[0];
781
+ // Must be wrapped in a lexical block, not bare top-level const.
782
+ expect(code).toMatch(/^\{\n/);
783
+ expect(code).toContain('const markerAttr = "test-value"');
784
+ });
785
+ it('allows same arg name across multiple calls without conflict', async () => {
786
+ const page = new EvalCapturePage();
787
+ await expect(page.evaluateWithArgs('(() => x)()', { x: 'first' })).resolves.toBe('first');
788
+ await expect(page.evaluateWithArgs('(() => x)()', { x: 'second' })).resolves.toBe('second');
789
+ expect(page.scripts).toHaveLength(2);
790
+ // Both should be self-contained blocks, not polluting global scope.
791
+ expect(page.scripts[0]).toContain('const x = "first"');
792
+ expect(page.scripts[1]).toContain('const x = "second"');
793
+ expect(page.scripts[0]).toMatch(/^\{\n/);
794
+ expect(page.scripts[1]).toMatch(/^\{\n/);
795
+ });
796
+ it('preserves completion value semantics for statement snippets', async () => {
797
+ const page = new EvalCapturePage();
798
+ await expect(page.evaluateWithArgs(`
799
+ const local = value + 1;
800
+ local * 2
801
+ `, { value: 20 })).resolves.toBe(42);
802
+ });
803
+ });