rnxsim 0.1.313 → 0.1.314

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 (297) hide show
  1. package/LICENSE +11 -10
  2. package/README.md +5 -0
  3. package/cli/app-config.ts +65 -0
  4. package/cli/app-fonts.ts +408 -0
  5. package/cli/app-project.ts +231 -0
  6. package/cli/app-splash.ts +185 -0
  7. package/cli/app-state-reset.ts +24 -0
  8. package/cli/auth.ts +155 -0
  9. package/cli/bin.ts +594 -0
  10. package/cli/bridge-diagnostics.ts +226 -0
  11. package/cli/bridge-flow-runner.ts +2830 -0
  12. package/cli/browser-evals.ts +96 -0
  13. package/cli/commands/agent-wrapper.ts +986 -0
  14. package/cli/commands/agent.ts +423 -0
  15. package/cli/commands/app-fonts.ts +98 -0
  16. package/cli/commands/assert.ts +541 -0
  17. package/cli/commands/auth.ts +59 -0
  18. package/cli/commands/camera.ts +266 -0
  19. package/cli/commands/cleanup.ts +169 -0
  20. package/cli/commands/compat.ts +87 -0
  21. package/cli/commands/config.ts +32 -0
  22. package/cli/commands/control.ts +2142 -0
  23. package/cli/commands/cpu-profile.ts +269 -0
  24. package/cli/commands/daemon-mac-app.ts +169 -0
  25. package/cli/commands/daemon.ts +874 -0
  26. package/cli/commands/debug.ts +719 -0
  27. package/cli/commands/desktop.ts +39 -0
  28. package/cli/commands/detect.ts +197 -0
  29. package/cli/commands/detox.ts +385 -0
  30. package/cli/commands/device.ts +133 -0
  31. package/cli/commands/diagnose.ts +589 -0
  32. package/cli/commands/electron.ts +95 -0
  33. package/cli/commands/film.ts +379 -0
  34. package/cli/commands/flow.ts +1124 -0
  35. package/cli/commands/inspect/actions.ts +622 -0
  36. package/cli/commands/inspect/core.ts +2405 -0
  37. package/cli/commands/inspect/count.ts +17 -0
  38. package/cli/commands/inspect/describe.ts +192 -0
  39. package/cli/commands/inspect/env.ts +23 -0
  40. package/cli/commands/inspect/find.ts +171 -0
  41. package/cli/commands/inspect/get-layout.ts +39 -0
  42. package/cli/commands/inspect/keyboard.ts +52 -0
  43. package/cli/commands/inspect/list.ts +58 -0
  44. package/cli/commands/inspect/memory.ts +215 -0
  45. package/cli/commands/inspect/redaction.ts +39 -0
  46. package/cli/commands/inspect/resolve-target.ts +82 -0
  47. package/cli/commands/inspect/screens.ts +78 -0
  48. package/cli/commands/inspect/settle.ts +22 -0
  49. package/cli/commands/inspect/settling.ts +158 -0
  50. package/cli/commands/inspect/shared.ts +353 -0
  51. package/cli/commands/inspect/sleep.ts +14 -0
  52. package/cli/commands/inspect/tree.ts +32 -0
  53. package/cli/commands/inspect/url.ts +17 -0
  54. package/cli/commands/inspect/wait-event.ts +210 -0
  55. package/cli/commands/inspect/wait-idle.ts +24 -0
  56. package/cli/commands/inspect/wait-ready.ts +74 -0
  57. package/cli/commands/inspect/wait-selector.ts +54 -0
  58. package/cli/commands/inspect/wait.ts +31 -0
  59. package/cli/commands/inspect.ts +4519 -0
  60. package/cli/commands/install-desktop.ts +351 -0
  61. package/cli/commands/login.ts +331 -0
  62. package/cli/commands/logout.ts +31 -0
  63. package/cli/commands/maestro-generate.ts +361 -0
  64. package/cli/commands/maestro.ts +453 -0
  65. package/cli/commands/mode.ts +57 -0
  66. package/cli/commands/no-bridge-hint.ts +80 -0
  67. package/cli/commands/perf.ts +66 -0
  68. package/cli/commands/permissions.ts +203 -0
  69. package/cli/commands/profile.ts +108 -0
  70. package/cli/commands/react.ts +353 -0
  71. package/cli/commands/record.ts +1434 -0
  72. package/cli/commands/report-issue.ts +305 -0
  73. package/cli/commands/reset.ts +85 -0
  74. package/cli/commands/runtime.ts +351 -0
  75. package/cli/commands/screenshot-command.ts +106 -0
  76. package/cli/commands/screenshot-layers.ts +143 -0
  77. package/cli/commands/screenshot-mode.ts +37 -0
  78. package/cli/commands/screenshot.ts +488 -0
  79. package/cli/commands/screenshots-capture.ts +607 -0
  80. package/cli/commands/screenshots.ts +127 -0
  81. package/cli/commands/serve.ts +168 -0
  82. package/cli/commands/setup.ts +545 -0
  83. package/cli/commands/shell-boolean-mode.ts +81 -0
  84. package/cli/commands/skills.ts +467 -0
  85. package/cli/commands/slides.ts +361 -0
  86. package/cli/commands/state.ts +87 -0
  87. package/cli/commands/storage.ts +58 -0
  88. package/cli/commands/telemetry.ts +54 -0
  89. package/cli/commands/three-mode.ts +763 -0
  90. package/cli/commands/timeline.ts +122 -0
  91. package/cli/commands/upgrade.ts +208 -0
  92. package/cli/commands/upload.ts +1225 -0
  93. package/cli/commands/version.ts +54 -0
  94. package/cli/commands/what-happened.ts +327 -0
  95. package/cli/current-sim.ts +204 -0
  96. package/cli/desktop-companion.ts +300 -0
  97. package/cli/drivers/electron.ts +70 -0
  98. package/cli/drivers/index.ts +20 -0
  99. package/cli/drivers/playwright-provisioning.ts +180 -0
  100. package/cli/drivers/playwright.ts +698 -0
  101. package/cli/drivers/registry.ts +65 -0
  102. package/cli/drivers/types.ts +102 -0
  103. package/cli/flow-file.ts +142 -0
  104. package/cli/flow-live-status.ts +120 -0
  105. package/cli/flow-session.ts +187 -0
  106. package/cli/help.ts +80 -0
  107. package/cli/hidden-runtime-alias.ts +19 -0
  108. package/cli/hints.ts +216 -0
  109. package/cli/inspect-notice-state.ts +114 -0
  110. package/cli/maestro-js.ts +334 -0
  111. package/cli/open-url.ts +8 -0
  112. package/cli/parent-pid.ts +204 -0
  113. package/cli/parse-args.ts +211 -0
  114. package/cli/prompt.ts +51 -0
  115. package/cli/recording-access.ts +107 -0
  116. package/cli/registry.ts +1 -0
  117. package/cli/resolve-assets.ts +63 -0
  118. package/cli/run-registry.ts +226 -0
  119. package/cli/runtime-notes.ts +66 -0
  120. package/cli/runtime-summary.ts +25 -0
  121. package/cli/setup-repository.ts +187 -0
  122. package/cli/telemetry.ts +187 -0
  123. package/cli/ws-bridge.ts +798 -0
  124. package/dist-cli/bin.js +5 -5
  125. package/dist-cli/chunks/{agent-XZ2KTPCU.js → agent-7YBDCYMA.js} +2 -2
  126. package/dist-cli/chunks/{agent-wrapper-JJYYW2WH.js → agent-wrapper-2GHFBHCR.js} +2 -2
  127. package/dist-cli/chunks/{app-fonts-IXRNQG6B.js → app-fonts-RSNVPQSU.js} +2 -2
  128. package/dist-cli/chunks/{assert-54T5SK5F.js → assert-XCMX3XJX.js} +2 -2
  129. package/dist-cli/chunks/{auth-FI5UDI45.js → auth-B442HRAX.js} +2 -2
  130. package/dist-cli/chunks/{beta-JV6UKADW.js → beta-XJ55JK3M.js} +2 -2
  131. package/dist-cli/chunks/camera-UGYSSLIK.js +33 -0
  132. package/dist-cli/chunks/{chunk-3NV2NCNX.js → chunk-277AEQZX.js} +2 -2
  133. package/dist-cli/chunks/{chunk-2YR5BGA5.js → chunk-2JNSK774.js} +2 -2
  134. package/dist-cli/chunks/{chunk-RSZWCKNT.js → chunk-32WOTSTR.js} +3 -3
  135. package/dist-cli/chunks/{chunk-WEXDAC74.js → chunk-3S753SNQ.js} +2 -2
  136. package/dist-cli/chunks/{chunk-IJO63TDP.js → chunk-46ZOLOYA.js} +2 -2
  137. package/dist-cli/chunks/{chunk-WUSWBCWA.js → chunk-5L7ELDQL.js} +8 -9
  138. package/dist-cli/chunks/{chunk-WF3T4SVI.js → chunk-7OOPFSQS.js} +2 -2
  139. package/dist-cli/chunks/{chunk-TZFFR3SD.js → chunk-7SV3RPRW.js} +2 -2
  140. package/dist-cli/chunks/{chunk-WWZIXIRD.js → chunk-7ZC35MOU.js} +1 -1
  141. package/dist-cli/chunks/chunk-AMG5E6CC.js +9 -0
  142. package/dist-cli/chunks/{chunk-WINYQ44O.js → chunk-APWNH3A4.js} +1 -1
  143. package/dist-cli/chunks/chunk-B57XUKY3.js +4 -0
  144. package/dist-cli/chunks/{chunk-YDGQTMQL.js → chunk-C2NL26TD.js} +1 -1
  145. package/dist-cli/chunks/{chunk-NMF2ZMZQ.js → chunk-D2FNUWAB.js} +4 -4
  146. package/dist-cli/chunks/{chunk-YIFT42WN.js → chunk-E5T4XSJ3.js} +2 -2
  147. package/dist-cli/chunks/{chunk-46EUUFJ5.js → chunk-EAC34EQS.js} +1 -1
  148. package/dist-cli/chunks/{chunk-OVFJFXUD.js → chunk-EG32ML36.js} +2 -2
  149. package/dist-cli/chunks/{chunk-5YJCOWCH.js → chunk-FXUAC6D5.js} +1 -1
  150. package/dist-cli/chunks/chunk-GQSL4USA.js +6 -0
  151. package/dist-cli/chunks/{chunk-VFCMSYZK.js → chunk-HAXW27SS.js} +2 -2
  152. package/dist-cli/chunks/{chunk-2D2UPBBR.js → chunk-IZAHPAN6.js} +1 -1
  153. package/dist-cli/chunks/{chunk-7GN3LVWB.js → chunk-J62KM5TB.js} +2 -2
  154. package/dist-cli/chunks/{chunk-BTWORNNG.js → chunk-JEMCD5E4.js} +1 -1
  155. package/dist-cli/chunks/{chunk-DCEMHR2Y.js → chunk-JZS3Q37N.js} +2 -2
  156. package/dist-cli/chunks/{chunk-D4FFVGI5.js → chunk-KR4JON7D.js} +1 -1
  157. package/dist-cli/chunks/chunk-KWCYKQIQ.js +4 -0
  158. package/dist-cli/chunks/{chunk-VNQEB4L7.js → chunk-MCWPL644.js} +2 -2
  159. package/dist-cli/chunks/chunk-MJYK3N2I.js +4 -0
  160. package/dist-cli/chunks/{chunk-5TEF3ET3.js → chunk-O6TRIZNS.js} +2 -2
  161. package/dist-cli/chunks/{chunk-QKDWYITG.js → chunk-P7XL2E73.js} +3 -3
  162. package/dist-cli/chunks/{chunk-BBULZ7CG.js → chunk-PFQTUKQ4.js} +62 -87
  163. package/dist-cli/chunks/{chunk-GGRX24GF.js → chunk-PG5RZCTN.js} +2 -2
  164. package/dist-cli/chunks/{chunk-W6K4EFPH.js → chunk-QGRI2Z4M.js} +2 -2
  165. package/dist-cli/chunks/{chunk-ZMJD5GEC.js → chunk-QLXXE7GE.js} +1 -1
  166. package/dist-cli/chunks/{chunk-VZXWHRUZ.js → chunk-QOJJJWJE.js} +89 -133
  167. package/dist-cli/chunks/{chunk-OZSSI4WN.js → chunk-RWZY5427.js} +2 -2
  168. package/dist-cli/chunks/{chunk-UC6U3MML.js → chunk-RZKU2K3J.js} +2 -2
  169. package/dist-cli/chunks/{chunk-IJ5CAZZC.js → chunk-SBV4IK4H.js} +1 -1
  170. package/dist-cli/chunks/{chunk-DZS6WPUI.js → chunk-SGMVFFMK.js} +1 -1
  171. package/dist-cli/chunks/{chunk-OHAZNXLK.js → chunk-TAX4UT2N.js} +1 -1
  172. package/dist-cli/chunks/{chunk-5DHC6KHQ.js → chunk-TUOFAWXT.js} +1 -1
  173. package/dist-cli/chunks/{chunk-GASE6UBA.js → chunk-UHZLOHGP.js} +1 -1
  174. package/dist-cli/chunks/{chunk-F5ZRSS3C.js → chunk-VC7V76U3.js} +1 -1
  175. package/dist-cli/chunks/{chunk-RTN5C5RL.js → chunk-VQVMLW4U.js} +1 -1
  176. package/dist-cli/chunks/{chunk-WMIIKMGK.js → chunk-VUKKYPZN.js} +2 -2
  177. package/dist-cli/chunks/{chunk-5TPRP5QT.js → chunk-WGGRJDRE.js} +1 -1
  178. package/dist-cli/chunks/chunk-X6H76EKP.js +15 -0
  179. package/dist-cli/chunks/chunk-XLZ5FNRT.js +27 -0
  180. package/dist-cli/chunks/{chunk-HI5TFJWN.js → chunk-XULEACM4.js} +2 -2
  181. package/dist-cli/chunks/{chunk-MJRLLB4R.js → chunk-YFSDM7AX.js} +4 -4
  182. package/dist-cli/chunks/chunk-YWI3UEVX.js +5 -0
  183. package/dist-cli/chunks/{chunk-XEVZYVIW.js → chunk-ZBSJO4NB.js} +10 -9
  184. package/dist-cli/chunks/{cleanup-P27PA6JI.js → cleanup-EYLGCA6Z.js} +2 -2
  185. package/dist-cli/chunks/cli-version-LL2UGIHE.js +4 -0
  186. package/dist-cli/chunks/{compat-ZD65FED3.js → compat-TLJYHB4E.js} +2 -2
  187. package/dist-cli/chunks/{config-XMJRNM2A.js → config-4JWUOEXK.js} +2 -2
  188. package/dist-cli/chunks/{control-KMIQT3QP.js → control-HPAOYF4N.js} +2 -2
  189. package/dist-cli/chunks/daemon-OYLASXLE.js +4 -0
  190. package/dist-cli/chunks/{debug-PT4HOP7N.js → debug-4BKXF6KI.js} +5 -5
  191. package/dist-cli/chunks/{desktop-S3FG72AK.js → desktop-ZQ6ZD2S6.js} +3 -3
  192. package/dist-cli/chunks/{detox-B3D4IFCN.js → detox-WNPASSS3.js} +2 -2
  193. package/dist-cli/chunks/{device-XBNDSB2R.js → device-BXLXVG3V.js} +2 -2
  194. package/dist-cli/chunks/{diagnose-HMQXJE5N.js → diagnose-3QX7W5RW.js} +2 -2
  195. package/dist-cli/chunks/{disk-cleanup-BLCZ5BSZ.js → disk-cleanup-KSWKI7WB.js} +2 -2
  196. package/dist-cli/chunks/drivers-EPIEFF7P.js +4 -0
  197. package/dist-cli/chunks/{film-BJGTBYZB.js → film-GDMR33VO.js} +3 -3
  198. package/dist-cli/chunks/flow-NKMPBYCJ.js +4 -0
  199. package/dist-cli/chunks/help-OGCBHOKA.js +4 -0
  200. package/dist-cli/chunks/{hidden-runtime-alias-ANOYADHM.js → hidden-runtime-alias-S2GTDX3T.js} +2 -2
  201. package/dist-cli/chunks/home-paths-QRCDLTTV.js +4 -0
  202. package/dist-cli/chunks/inspect-FUYMOZPY.js +4 -0
  203. package/dist-cli/chunks/install-desktop-FR6YKW7Y.js +4 -0
  204. package/dist-cli/chunks/{login-FJ737MWG.js → login-IJAPUZHI.js} +4 -4
  205. package/dist-cli/chunks/{logout-ZCNMMHMY.js → logout-QMXDFU2X.js} +2 -2
  206. package/dist-cli/chunks/{maestro-SZTNKLDF.js → maestro-ZXU3YCVX.js} +3 -3
  207. package/dist-cli/chunks/{maestro-generate-DCFAIZ4H.js → maestro-generate-PYB5QY7K.js} +3 -3
  208. package/dist-cli/chunks/{mode-GRMQCRXR.js → mode-WGUCL5FZ.js} +2 -2
  209. package/dist-cli/chunks/{optional-demo-registry-W36EWFFB.js → optional-demo-registry-WH2O6H36.js} +2 -2
  210. package/dist-cli/chunks/{perf-QYBAAUZG.js → perf-TOD3UFAH.js} +2 -2
  211. package/dist-cli/chunks/{permissions-3QCQ6VF4.js → permissions-I5BRJGTB.js} +2 -2
  212. package/dist-cli/chunks/{record-QPPC2S4E.js → record-ZYL2FYSK.js} +3 -3
  213. package/dist-cli/chunks/{report-issue-7NMFP4HK.js → report-issue-TAI6DYZO.js} +2 -2
  214. package/dist-cli/chunks/reset-7YGYKQPQ.js +4 -0
  215. package/dist-cli/chunks/runtime-B4JO6QRI.js +4 -0
  216. package/dist-cli/chunks/{screenshot-command-67AECJFB.js → screenshot-command-7ANLODZY.js} +7 -7
  217. package/dist-cli/chunks/{screenshot-layers-ASWBYPJL.js → screenshot-layers-A7FYXSVU.js} +3 -3
  218. package/dist-cli/chunks/{screenshots-capture-PXA3HFQK.js → screenshots-capture-WT2ZY6CB.js} +2 -2
  219. package/dist-cli/chunks/serve-TG5WKA4V.js +44 -0
  220. package/dist-cli/chunks/{setup-7DWPMRSB.js → setup-PCWLD22V.js} +2 -2
  221. package/dist-cli/chunks/{skills-S3Y22TUA.js → skills-27ZHWCQS.js} +2 -2
  222. package/dist-cli/chunks/state-ZQVY46ZO.js +14 -0
  223. package/dist-cli/chunks/{storage-XUIMJWAJ.js → storage-OYC57C4X.js} +6 -6
  224. package/dist-cli/chunks/store-32HSPZHI.js +4 -0
  225. package/dist-cli/chunks/telemetry-PBJR7XHR.js +4 -0
  226. package/dist-cli/chunks/{timeline-TMPLQPSP.js → timeline-RAJJFQT6.js} +2 -2
  227. package/dist-cli/chunks/{upgrade-7HDSIM7K.js → upgrade-OEFNZRIP.js} +2 -2
  228. package/dist-cli/chunks/upload-LQJITLKK.js +4 -0
  229. package/dist-cli/chunks/version-NIXTY6PL.js +6 -0
  230. package/dist-cli/chunks/{web-DG3WBYD3.js → web-XBUTBVGR.js} +2 -2
  231. package/dist-cli/chunks/{what-happened-XFVUTZR7.js → what-happened-YNHRFUDX.js} +3 -3
  232. package/dist-lib/agent-daemon-client.cjs +1 -1
  233. package/dist-lib/agent-events.cjs +1 -1
  234. package/dist-lib/agent-identity.cjs +1 -1
  235. package/dist-lib/agent-sessions.cjs +1 -1
  236. package/dist-lib/attached-projects.cjs +1 -1
  237. package/dist-lib/auth/shared-session.cjs +1 -1
  238. package/dist-lib/backend-origin.cjs +1 -1
  239. package/dist-lib/beta.cjs +1 -1
  240. package/dist-lib/beta.mjs +1 -1
  241. package/dist-lib/bridge-constants.cjs +1 -1
  242. package/dist-lib/bridge-contract.cjs +20 -0
  243. package/dist-lib/cli-constants.cjs +1 -1
  244. package/dist-lib/config.cjs +1 -1
  245. package/dist-lib/detox/index.cjs +1 -1
  246. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  247. package/dist-lib/home-paths.cjs +67 -28
  248. package/dist-lib/host/bridge-host.cjs +140 -12
  249. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  250. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  251. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  252. package/dist-lib/host/websocket-proxy.cjs +1 -1
  253. package/dist-lib/index.cjs +2815 -40
  254. package/dist-lib/jump-to-source-babel.cjs +1 -1
  255. package/dist-lib/menu.cjs +1 -1
  256. package/dist-lib/menu.mjs +1 -1
  257. package/dist-lib/metro.cjs +1 -1
  258. package/dist-lib/profiles.cjs +1 -1
  259. package/dist-lib/public-brand.cjs +1 -1
  260. package/dist-lib/render-mode.cjs +1 -1
  261. package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
  262. package/dist-lib/sdk.cjs +2549 -2061
  263. package/dist-lib/sdk.mjs +2543 -2061
  264. package/dist-lib/skills.cjs +480 -280
  265. package/dist-lib/vite.cjs +1 -1
  266. package/package.json +8 -2
  267. package/src/bridge-constants.ts +3 -4
  268. package/src/bridge-contract.ts +251 -0
  269. package/src/connect.ts +83 -0
  270. package/src/disk-cleanup.ts +30 -0
  271. package/src/home-paths.ts +81 -38
  272. package/src/host/bridge-host.ts +134 -6
  273. package/src/index.ts +27 -1
  274. package/src/sdk.ts +8 -0
  275. package/src/sim-client.ts +660 -0
  276. package/dist-cli/chunks/camera-VL73YIKP.js +0 -22
  277. package/dist-cli/chunks/chunk-4NPPOV2N.js +0 -5
  278. package/dist-cli/chunks/chunk-FSUYIVJ6.js +0 -9
  279. package/dist-cli/chunks/chunk-G2WW6L2C.js +0 -23
  280. package/dist-cli/chunks/chunk-KTHV3RUS.js +0 -26
  281. package/dist-cli/chunks/chunk-LF2ZVT7O.js +0 -6
  282. package/dist-cli/chunks/chunk-NFK7T35W.js +0 -4
  283. package/dist-cli/chunks/chunk-TIVZIMMW.js +0 -4
  284. package/dist-cli/chunks/cli-version-WWLPBDQ7.js +0 -4
  285. package/dist-cli/chunks/daemon-G2ME7NLB.js +0 -4
  286. package/dist-cli/chunks/drivers-LDECZGP2.js +0 -4
  287. package/dist-cli/chunks/flow-UEQNVTU7.js +0 -4
  288. package/dist-cli/chunks/help-T5FYSVGB.js +0 -4
  289. package/dist-cli/chunks/home-paths-GT3LFNOR.js +0 -4
  290. package/dist-cli/chunks/inspect-ZA6XF5LD.js +0 -4
  291. package/dist-cli/chunks/install-desktop-TIMUDHPL.js +0 -4
  292. package/dist-cli/chunks/runtime-XOAXMSTU.js +0 -4
  293. package/dist-cli/chunks/serve-BI2NBAXG.js +0 -44
  294. package/dist-cli/chunks/store-JTHEJLAZ.js +0 -4
  295. package/dist-cli/chunks/telemetry-ZYJGD2DB.js +0 -4
  296. package/dist-cli/chunks/upload-GMSZPWM6.js +0 -4
  297. package/dist-cli/chunks/version-HOCHZ37L.js +0 -6
@@ -0,0 +1,622 @@
1
+ import { waitForSootsimIdle } from './settling'
2
+ import type { InspectBridge } from './core'
3
+
4
+ export type TapFailure = 'not-found' | 'missed' | 'special'
5
+
6
+ export interface TapTargetSummary {
7
+ nodeId: number | null
8
+ id: string | null
9
+ testID: string | null
10
+ text: string | null
11
+ type: string | null
12
+ }
13
+
14
+ export interface TapOutcome {
15
+ payload: any
16
+ result: any
17
+ attempts: number
18
+ failure?: TapFailure
19
+ }
20
+
21
+ export interface TapTextOptions {
22
+ exact?: boolean
23
+ role?: string | null
24
+ within?: string | null
25
+ minX?: number | null
26
+ maxX?: number | null
27
+ minY?: number | null
28
+ maxY?: number | null
29
+ near?: { x: number; y: number } | null
30
+ nth?: number | null
31
+ first?: boolean
32
+ }
33
+
34
+ export interface TapTimingOptions {
35
+ initialWaitMs?: number
36
+ deadlineMs?: number
37
+ retryWaitMs?: number
38
+ }
39
+
40
+ // the device screen a finger can actually reach, evaluated in the page. the
41
+ // compositor keeps one canvas per surface and parks the inactive ones at zero
42
+ // size, so measuring `canvas[data-surface-id]` reads whichever element happens
43
+ // to come first in the DOM — `home`, sized 0x0 whenever an app is foreground —
44
+ // which left the offscreen guard below silently disabled inside every app. the
45
+ // engine publishes the real device spec on this snapshot instead.
46
+ const SCREEN_EVAL = `(() => {
47
+ const spec = window.SootSim?.state?.engineSnapshot?.windowState?.deviceSpec
48
+ return spec && spec.width > 0 && spec.height > 0
49
+ ? { width: spec.width, height: spec.height }
50
+ : null
51
+ })()`
52
+
53
+ const DEFAULT_AGENT_TIMING: Required<TapTimingOptions> = {
54
+ initialWaitMs: 3000,
55
+ deadlineMs: 5000,
56
+ retryWaitMs: 700,
57
+ }
58
+
59
+ const DEFAULT_INTERACTIVE_TIMING: Required<TapTimingOptions> = {
60
+ initialWaitMs: 1200,
61
+ deadlineMs: 2500,
62
+ retryWaitMs: 700,
63
+ }
64
+
65
+ function tapTiming(agent: boolean, opts: TapTimingOptions = {}) {
66
+ return {
67
+ ...(agent ? DEFAULT_AGENT_TIMING : DEFAULT_INTERACTIVE_TIMING),
68
+ ...opts,
69
+ }
70
+ }
71
+
72
+ export function isTapSuccess(result: any): boolean {
73
+ if (!result || result.hit === false || result.ok === false) return false
74
+ if (result.requestedTargetMatched === false) return false
75
+ if (
76
+ result.pointerTapHandled === false &&
77
+ !result.keyboardOpened &&
78
+ !result.isTextInput
79
+ ) {
80
+ return false
81
+ }
82
+ return true
83
+ }
84
+
85
+ export function tapTargetFromPayload(
86
+ payload: any,
87
+ textFallback?: string,
88
+ ): TapTargetSummary {
89
+ return {
90
+ nodeId:
91
+ payload.target?.nodeId ?? payload.match?.nodeId ?? payload.node?.nodeId ?? null,
92
+ id: payload.target?.id ?? payload.match?.id ?? payload.node?.id ?? null,
93
+ testID:
94
+ payload.target?.testID ?? payload.match?.testID ?? payload.node?.testID ?? null,
95
+ text:
96
+ payload.target?.text ??
97
+ payload.target?.accessibilityLabel ??
98
+ payload.match?.text ??
99
+ payload.match?.accessibilityLabel ??
100
+ payload.node?.text ??
101
+ textFallback ??
102
+ null,
103
+ type: payload.target?.type ?? payload.match?.type ?? payload.node?.type ?? null,
104
+ }
105
+ }
106
+
107
+ export async function tapResolvedTarget(
108
+ bridge: InspectBridge,
109
+ args: {
110
+ textFallback?: string
111
+ resolve: () => Promise<any>
112
+ agent?: boolean
113
+ timing?: TapTimingOptions
114
+ },
115
+ ): Promise<TapOutcome> {
116
+ const timing = tapTiming(!!args.agent, args.timing)
117
+ let attempts = 0
118
+ let lastPayload: any = null
119
+ let lastResult: any = null
120
+
121
+ try {
122
+ await waitForSootsimIdle({
123
+ bridge,
124
+ maxMs: timing.initialWaitMs,
125
+ pollMs: 32,
126
+ stablePolls: 2,
127
+ })
128
+ } catch {
129
+ // best-effort readiness gate; the retry loop decides success.
130
+ }
131
+
132
+ const deadline = Date.now() + timing.deadlineMs
133
+ let lastOffscreenAt: string | null = null
134
+ while (Date.now() <= deadline || attempts === 0) {
135
+ attempts++
136
+ const payload = await args.resolve()
137
+ lastPayload = payload
138
+
139
+ if (
140
+ payload?.error === 'bridge-not-ready' ||
141
+ payload?.ambiguous ||
142
+ payload?.nthOutOfRange
143
+ ) {
144
+ return { payload, result: null, attempts, failure: 'special' }
145
+ }
146
+
147
+ if (payload && typeof payload.cx === 'number' && typeof payload.cy === 'number') {
148
+ // a real finger can only land within the device screen. when the
149
+ // resolved center sits outside it (a virtualized row scrolled away),
150
+ // don't dispatch — the engine would "hit" tree coordinates no touch
151
+ // could reach and the press would do nothing visible. report an honest
152
+ // offscreen miss instead so drivers scroll first rather than getting
153
+ // stuck trusting a phantom hit.
154
+ if (payload.offscreen) {
155
+ lastResult = {
156
+ hit: false,
157
+ reason: 'offscreen',
158
+ x: payload.cx,
159
+ y: payload.cy,
160
+ screen: payload.screen,
161
+ }
162
+ // stable offscreen position twice in a row is deterministic — the
163
+ // node is scrolled out of view, not settling in. bail early instead
164
+ // of retrying to the deadline.
165
+ const at = `${Math.round(payload.cx)},${Math.round(payload.cy)}`
166
+ if (lastOffscreenAt === at) {
167
+ return { payload, result: lastResult, attempts, failure: 'missed' }
168
+ }
169
+ lastOffscreenAt = at
170
+ } else {
171
+ lastOffscreenAt = null
172
+ const requestedTarget = tapTargetFromPayload(payload, args.textFallback)
173
+ const verification = await bridge.send({
174
+ type: 'evaluate',
175
+ code: `(async () => {
176
+ const t = window.__sootsimTest
177
+ if (
178
+ !t ||
179
+ typeof t.inspectAt !== 'function' ||
180
+ typeof t.resolveTapTarget !== 'function'
181
+ ) {
182
+ return { error: 'tap-target-inspection-unavailable' }
183
+ }
184
+ const requestedTarget = ${JSON.stringify(requestedTarget)}
185
+ const inspected = await t.inspectAt(${payload.cx}, ${payload.cy})
186
+ const resolved =
187
+ inspected && typeof inspected.nodeId === 'number'
188
+ ? await t.resolveTapTarget(inspected.nodeId)
189
+ : null
190
+ const node = (resolved && resolved.target) || inspected
191
+ const coordinateTarget = node
192
+ ? {
193
+ nodeId: node.nodeId ?? null,
194
+ id: node.id ?? null,
195
+ testID: node.testID ?? null,
196
+ text: node.text ?? node.accessibilityLabel ?? null,
197
+ type: node.type ?? null,
198
+ }
199
+ : null
200
+ const coordinateAncestors = Array.isArray(inspected?.ancestors)
201
+ ? inspected.ancestors
202
+ : []
203
+ const ancestorMatchesRequested = coordinateAncestors.some(
204
+ (ancestor) =>
205
+ (typeof requestedTarget.nodeId === 'number' &&
206
+ ancestor?.nodeId === requestedTarget.nodeId) ||
207
+ (requestedTarget.testID &&
208
+ ancestor?.testID === requestedTarget.testID) ||
209
+ (requestedTarget.id && ancestor?.id === requestedTarget.id),
210
+ )
211
+ const requestedTargetMatched =
212
+ coordinateTarget === null
213
+ ? false
214
+ : typeof requestedTarget.nodeId === 'number' &&
215
+ typeof coordinateTarget.nodeId === 'number'
216
+ ? requestedTarget.nodeId === coordinateTarget.nodeId ||
217
+ ancestorMatchesRequested
218
+ : requestedTarget.testID && coordinateTarget.testID
219
+ ? requestedTarget.testID === coordinateTarget.testID
220
+ || ancestorMatchesRequested
221
+ : requestedTarget.id && coordinateTarget.id
222
+ ? requestedTarget.id === coordinateTarget.id
223
+ || ancestorMatchesRequested
224
+ : ancestorMatchesRequested
225
+ if (!requestedTargetMatched) {
226
+ return {
227
+ requestedTargetMatched,
228
+ requestedTarget,
229
+ coordinateTarget,
230
+ }
231
+ }
232
+ if (typeof t.activatePressAt !== 'function') {
233
+ return { error: 'tap-target-activation-unavailable' }
234
+ }
235
+ const activation = await t.activatePressAt(
236
+ ${payload.cx},
237
+ ${payload.cy},
238
+ requestedTarget.nodeId,
239
+ )
240
+ if (activation?.ok) {
241
+ window.dispatchEvent(
242
+ new CustomEvent('sootsim:agentAction', {
243
+ detail: {
244
+ type: 'tap',
245
+ x: ${payload.cx},
246
+ y: ${payload.cy},
247
+ target: requestedTarget,
248
+ },
249
+ }),
250
+ )
251
+ }
252
+ return {
253
+ activation,
254
+ requestedTargetMatched,
255
+ requestedTarget,
256
+ coordinateTarget,
257
+ }
258
+ })()`,
259
+ })
260
+ if (verification?.error) {
261
+ return {
262
+ payload,
263
+ result: {
264
+ hit: false,
265
+ reason: verification.error,
266
+ requestedTarget,
267
+ },
268
+ attempts,
269
+ failure: 'special',
270
+ }
271
+ }
272
+ if (verification?.requestedTargetMatched === false) {
273
+ return {
274
+ payload,
275
+ result: {
276
+ hit: false,
277
+ reason: 'target-covered',
278
+ ...verification,
279
+ },
280
+ attempts,
281
+ failure: 'missed',
282
+ }
283
+ }
284
+ const dispatched = verification?.activation?.tap ?? verification?.activation
285
+ const result =
286
+ dispatched && typeof dispatched === 'object'
287
+ ? { ...dispatched, ...verification?.activation, ...verification }
288
+ : { hit: !!dispatched, ...verification }
289
+ lastResult = result
290
+ if (isTapSuccess(result)) return { payload, result, attempts }
291
+ }
292
+ }
293
+
294
+ const remaining = deadline - Date.now()
295
+ if (remaining <= 0) break
296
+ try {
297
+ await waitForSootsimIdle({
298
+ bridge,
299
+ maxMs: Math.min(remaining, timing.retryWaitMs),
300
+ pollMs: 32,
301
+ stablePolls: 2,
302
+ })
303
+ } catch {
304
+ await new Promise((resolve) => setTimeout(resolve, Math.min(120, remaining)))
305
+ }
306
+ }
307
+
308
+ return {
309
+ payload: lastPayload,
310
+ result: lastResult,
311
+ attempts,
312
+ failure: lastPayload && typeof lastPayload.cx === 'number' ? 'missed' : 'not-found',
313
+ }
314
+ }
315
+
316
+ export async function tapCoordinates(
317
+ bridge: InspectBridge,
318
+ x: number,
319
+ y: number,
320
+ target?: TapTargetSummary,
321
+ ): Promise<any> {
322
+ return bridge.send({
323
+ type: 'tap',
324
+ x,
325
+ y,
326
+ ...(target ? { target } : {}),
327
+ })
328
+ }
329
+
330
+ export async function tapById(
331
+ bridge: InspectBridge,
332
+ query: string,
333
+ opts: { agent?: boolean; timing?: TapTimingOptions } = {},
334
+ ): Promise<TapOutcome> {
335
+ const arg = JSON.stringify(query)
336
+ return tapResolvedTarget(bridge, {
337
+ agent: opts.agent,
338
+ timing: opts.timing,
339
+ resolve: () =>
340
+ bridge.send({
341
+ type: 'evaluate',
342
+ code: `(async () => {
343
+ const t = window.__sootsimTest
344
+ if (!t) return null
345
+ const n = (await t.findByTestId(${arg})) || (await t.findById(${arg}))
346
+ if (!n || !n.absolutePosition || !n.layout) return { cx: null }
347
+ const resolved =
348
+ typeof n.nodeId === 'number' && typeof t.resolveTapTarget === 'function'
349
+ ? await t.resolveTapTarget(n.nodeId)
350
+ : null
351
+ const target = (resolved && resolved.target) || n
352
+ const cx =
353
+ resolved && typeof resolved.cx === 'number'
354
+ ? resolved.cx
355
+ : n.absolutePosition.x + (n.layout.width || 0) / 2
356
+ const cy =
357
+ resolved && typeof resolved.cy === 'number'
358
+ ? resolved.cy
359
+ : n.absolutePosition.y + (n.layout.height || 0) / 2
360
+ const scr = ${SCREEN_EVAL}
361
+ const offscreen =
362
+ !!scr && (cx < 0 || cy < 0 || cx > scr.width || cy > scr.height)
363
+ return {
364
+ cx,
365
+ cy,
366
+ ...(offscreen ? { offscreen: true, screen: scr } : {}),
367
+ match: {
368
+ nodeId: n.nodeId ?? null,
369
+ id: n.id,
370
+ testID: n.testID,
371
+ text: n.text ?? n.accessibilityLabel ?? null,
372
+ type: n.type,
373
+ },
374
+ target: {
375
+ nodeId: target.nodeId ?? null,
376
+ id: target.id,
377
+ testID: target.testID,
378
+ text:
379
+ target.text ??
380
+ target.accessibilityLabel ??
381
+ n.text ??
382
+ n.accessibilityLabel ??
383
+ null,
384
+ type: target.type,
385
+ },
386
+ strategy: (resolved && resolved.strategy) || 'matched-node',
387
+ }
388
+ })()`,
389
+ }),
390
+ })
391
+ }
392
+
393
+ export async function tapByText(
394
+ bridge: InspectBridge,
395
+ query: string,
396
+ options: TapTextOptions = {},
397
+ opts: { agent?: boolean; timing?: TapTimingOptions } = {},
398
+ ): Promise<TapOutcome> {
399
+ const filterArg = JSON.stringify({
400
+ query,
401
+ exact: !!options.exact,
402
+ role: options.role ?? null,
403
+ within: options.within ?? null,
404
+ minX: options.minX ?? null,
405
+ maxX: options.maxX ?? null,
406
+ minY: options.minY ?? null,
407
+ maxY: options.maxY ?? null,
408
+ near: options.near ?? null,
409
+ nth: options.nth ?? null,
410
+ first: !!options.first,
411
+ })
412
+
413
+ return tapResolvedTarget(bridge, {
414
+ agent: opts.agent,
415
+ timing: opts.timing,
416
+ textFallback: query,
417
+ resolve: () =>
418
+ bridge.send({
419
+ type: 'evaluate',
420
+ code: `(async () => {
421
+ const t = window.__sootsimTest
422
+ if (!t) return { error: 'bridge-not-ready' }
423
+ const F = ${filterArg}
424
+
425
+ const res = await t.queryTextCandidates({
426
+ query: F.query,
427
+ exact: !!F.exact,
428
+ ...(F.role ? { role: F.role } : {}),
429
+ })
430
+
431
+ let candidates = res.candidates || []
432
+
433
+ candidates = candidates.filter((c) => {
434
+ const ax = c.info.absolutePosition && c.info.absolutePosition.x
435
+ const ay = c.info.absolutePosition && c.info.absolutePosition.y
436
+ if (F.minX !== null && !(ax >= F.minX)) return false
437
+ if (F.maxX !== null && !(ax <= F.maxX)) return false
438
+ if (F.minY !== null && !(ay >= F.minY)) return false
439
+ if (F.maxY !== null && !(ay <= F.maxY)) return false
440
+ if (F.within && !c.ancestorTestIDs.includes(F.within)) return false
441
+ return true
442
+ })
443
+
444
+ if (F.near) {
445
+ candidates.sort((a, b) => {
446
+ const ax = (a.info.absolutePosition && a.info.absolutePosition.x) || 0
447
+ const ay = (a.info.absolutePosition && a.info.absolutePosition.y) || 0
448
+ const bx = (b.info.absolutePosition && b.info.absolutePosition.x) || 0
449
+ const by = (b.info.absolutePosition && b.info.absolutePosition.y) || 0
450
+ return (
451
+ Math.hypot(ax - F.near.x, ay - F.near.y) -
452
+ Math.hypot(bx - F.near.x, by - F.near.y)
453
+ )
454
+ })
455
+ } else {
456
+ candidates.sort((a, b) => {
457
+ const ay = (a.info.absolutePosition && a.info.absolutePosition.y) || 0
458
+ const by = (b.info.absolutePosition && b.info.absolutePosition.y) || 0
459
+ if (Math.abs(ay - by) > 2) return ay - by
460
+ const ax = (a.info.absolutePosition && a.info.absolutePosition.x) || 0
461
+ const bx = (b.info.absolutePosition && b.info.absolutePosition.x) || 0
462
+ return ax - bx
463
+ })
464
+ }
465
+
466
+ const total = candidates.length
467
+ if (total === 0) return { matched: 0, total: 0 }
468
+
469
+ let idx = 0
470
+ if (F.nth !== null) {
471
+ idx = F.nth < 0 ? total + F.nth : F.nth
472
+ if (idx < 0 || idx >= total) {
473
+ return { matched: 0, total, nthOutOfRange: true, nth: F.nth }
474
+ }
475
+ } else if (total > 1 && !F.first && !F.near) {
476
+ return {
477
+ ambiguous: true,
478
+ total,
479
+ candidates: candidates.slice(0, 10).map((c, i) => ({
480
+ idx: i,
481
+ nodeId: c.info.nodeId,
482
+ type: c.info.type,
483
+ testID: c.info.testID,
484
+ text: (c.info.text || '').slice(0, 60),
485
+ abs: c.info.absolutePosition,
486
+ layout: c.info.layout
487
+ ? {
488
+ width: Math.round(c.info.layout.width || 0),
489
+ height: Math.round(c.info.layout.height || 0),
490
+ }
491
+ : null,
492
+ ancestorTestIDs: (c.ancestorTestIDs || []).slice(0, 5),
493
+ })),
494
+ }
495
+ }
496
+
497
+ const picked = candidates[idx]
498
+ const n = picked.info
499
+ if (!n.absolutePosition || !n.layout) return { matched: 0, total }
500
+
501
+ const resolved =
502
+ typeof n.nodeId === 'number' &&
503
+ typeof t.resolveTapTarget === 'function'
504
+ ? await t.resolveTapTarget(n.nodeId)
505
+ : null
506
+ const target = (resolved && resolved.target) || n
507
+ const cx =
508
+ resolved && typeof resolved.cx === 'number'
509
+ ? resolved.cx
510
+ : n.absolutePosition.x + (n.layout.width || 0) / 2
511
+ const cy =
512
+ resolved && typeof resolved.cy === 'number'
513
+ ? resolved.cy
514
+ : n.absolutePosition.y + (n.layout.height || 0) / 2
515
+ const scr = ${SCREEN_EVAL}
516
+ const offscreen =
517
+ !!scr && (cx < 0 || cy < 0 || cx > scr.width || cy > scr.height)
518
+ return {
519
+ cx,
520
+ cy,
521
+ ...(offscreen ? { offscreen: true, screen: scr } : {}),
522
+ match: {
523
+ nodeId: n.nodeId ?? null,
524
+ id: n.id,
525
+ testID: n.testID,
526
+ type: n.type,
527
+ },
528
+ target: {
529
+ nodeId: target.nodeId ?? null,
530
+ id: target.id,
531
+ testID: target.testID,
532
+ text:
533
+ target.text ??
534
+ target.accessibilityLabel ??
535
+ n.text ??
536
+ n.accessibilityLabel ??
537
+ null,
538
+ type: target.type,
539
+ },
540
+ strategy: (resolved && resolved.strategy) || 'matched-node',
541
+ total,
542
+ idx,
543
+ }
544
+ })()`,
545
+ }),
546
+ })
547
+ }
548
+
549
+ export async function tapBest(
550
+ bridge: InspectBridge,
551
+ query: string,
552
+ opts: { agent?: boolean; timing?: TapTimingOptions } = {},
553
+ ): Promise<TapOutcome> {
554
+ const arg = JSON.stringify(query)
555
+ return tapResolvedTarget(bridge, {
556
+ agent: opts.agent,
557
+ timing: opts.timing,
558
+ textFallback: query,
559
+ resolve: async () => {
560
+ const payload = await bridge.send({
561
+ type: 'evaluate',
562
+ code: `(async () => {
563
+ const t = window.__sootsimTest
564
+ if (!t) return { error: 'bridge-not-ready' }
565
+ const byTestId =
566
+ (await t.findByTestId(${arg})) || (await t.findById(${arg}))
567
+ if (byTestId && byTestId.absolutePosition && byTestId.layout) {
568
+ return {
569
+ strategy: 'testid',
570
+ node: {
571
+ nodeId: byTestId.nodeId ?? null,
572
+ id: byTestId.id,
573
+ testID: byTestId.testID,
574
+ type: byTestId.type,
575
+ text: byTestId.text,
576
+ absolutePosition: byTestId.absolutePosition,
577
+ layout: byTestId.layout,
578
+ },
579
+ screen: ${SCREEN_EVAL},
580
+ }
581
+ }
582
+ const byText = await t.findByText(${arg})
583
+ if (byText && byText.absolutePosition && byText.layout) {
584
+ return {
585
+ strategy: 'text',
586
+ node: {
587
+ nodeId: byText.nodeId ?? null,
588
+ id: byText.id,
589
+ testID: byText.testID,
590
+ type: byText.type,
591
+ text: byText.text,
592
+ absolutePosition: byText.absolutePosition,
593
+ layout: byText.layout,
594
+ },
595
+ screen: ${SCREEN_EVAL},
596
+ }
597
+ }
598
+ return { strategy: 'none' }
599
+ })()`,
600
+ })
601
+ if (!payload || !('node' in payload)) return payload
602
+ const node = payload.node
603
+ const cx = node.absolutePosition.x + node.layout.width / 2
604
+ const cy = node.absolutePosition.y + node.layout.height / 2
605
+ const screen = payload.screen ?? null
606
+ const offscreen =
607
+ !!screen && (cx < 0 || cy < 0 || cx > screen.width || cy > screen.height)
608
+ return {
609
+ ...payload,
610
+ cx,
611
+ cy,
612
+ ...(offscreen ? { offscreen: true, screen } : {}),
613
+ target: {
614
+ id: node.id,
615
+ testID: node.testID,
616
+ text: payload.strategy === 'text' ? query : node.text,
617
+ type: node.type,
618
+ },
619
+ }
620
+ },
621
+ })
622
+ }