agent-device 0.21.19 → 0.21.21

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 (369) hide show
  1. package/android/ime-helper/dist/{agent-device-android-ime-helper-0.21.19.apk → agent-device-android-ime-helper-0.21.21.apk} +0 -0
  2. package/android/ime-helper/dist/agent-device-android-ime-helper-0.21.21.apk.sha256 +1 -0
  3. package/android/ime-helper/dist/{agent-device-android-ime-helper-0.21.19.manifest.json → agent-device-android-ime-helper-0.21.21.manifest.json} +4 -4
  4. package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.21.21.apk +0 -0
  5. package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.21.21.apk.sha256 +1 -0
  6. package/android/snapshot-helper/dist/{agent-device-android-snapshot-helper-0.21.19.manifest.json → agent-device-android-snapshot-helper-0.21.21.manifest.json} +6 -6
  7. package/apple/macos-helper/Sources/AgentDeviceMacOSHelper/AppWindowScreenshot.swift +73 -0
  8. package/apple/macos-helper/Sources/AgentDeviceMacOSHelper/BackgroundInteraction.swift +557 -0
  9. package/apple/macos-helper/Sources/AgentDeviceMacOSHelper/GhostCursor.swift +141 -0
  10. package/apple/macos-helper/Sources/AgentDeviceMacOSHelper/SnapshotTraversal.swift +107 -3
  11. package/apple/macos-helper/Sources/AgentDeviceMacOSHelper/main.swift +136 -22
  12. package/apple/macos-helper/Tests/AgentDeviceMacOSHelperTests/BackgroundInteractionTests.swift +88 -0
  13. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunner/AgentDeviceRunnerApp.m +133 -4
  14. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerSynthesizedGesture.h +10 -2
  15. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerSynthesizedGesture.m +36 -9
  16. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Alert.swift +28 -0
  17. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandJournal.swift +1 -14
  18. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Interaction.swift +14 -16
  19. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Models.swift +67 -14
  20. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SynthesizedCommitDeadline.swift +6 -1
  21. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SynthesizedGesturePolicy.swift +7 -2
  22. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SynthesizedInteraction.swift +21 -7
  23. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SynthesizedTextEntry.swift +121 -17
  24. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextEntry.swift +179 -6
  25. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextEntryConfirmation.swift +119 -0
  26. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextEntryFocus.swift +8 -1
  27. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextEntryReadiness.swift +23 -11
  28. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextInputProbe.swift +59 -6
  29. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextTyping.swift +112 -23
  30. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TypeExecution.swift +25 -12
  31. package/dist/apple/runner/AgentDeviceRunner/RecordingScripts/RecordingExportSupport.swift +6 -1
  32. package/dist/apple/runner/AgentDeviceRunner/RecordingScripts/recording-overlay.swift +43 -13
  33. package/dist/apple/snapshot-presentation/Sources/AgentDeviceSnapshotPresentation/SnapshotModels.swift +20 -5
  34. package/dist/apple/snapshot-presentation/Sources/AgentDeviceSnapshotPresentation/SnapshotPresentationProjection.swift +2 -15
  35. package/dist/apple/snapshot-presentation/Sources/AgentDeviceSnapshotPresentation/SnapshotVisibilityFold.swift +10 -25
  36. package/dist/apple/snapshot-presentation/Sources/AgentDeviceSnapshotPresentation/SnapshotVisibilityFoldProjection.swift +1 -18
  37. package/dist/src/adapter.js +1 -1
  38. package/dist/src/adb-executor.js +2 -2
  39. package/dist/src/adb-failure.js +1 -1
  40. package/dist/src/adb-transport.js +1 -1
  41. package/dist/src/agent-browser-lifecycle.js +1 -1
  42. package/dist/src/agent-browser-process-record.js +1 -1
  43. package/dist/src/agent-browser-provider.js +1 -1
  44. package/dist/src/agent-browser-tool.js +1 -1
  45. package/dist/src/agent-device-client.js +2 -2
  46. package/dist/src/ai-sdk.js +1 -1
  47. package/dist/src/alert-runtime.js +1 -1
  48. package/dist/src/alert.js +1 -0
  49. package/dist/src/android-adb.d.ts +12 -0
  50. package/dist/src/android-input-ownership.js +1 -1
  51. package/dist/src/app-backend.js +1 -0
  52. package/dist/src/app-catalog.js +1 -1
  53. package/dist/src/app-deployment-resolution.js +1 -1
  54. package/dist/src/app-inventory.js +1 -1
  55. package/dist/src/app-launch.js +2 -2
  56. package/dist/src/app-lifecycle.js +2 -2
  57. package/dist/src/app-lifecycle2.js +1 -1
  58. package/dist/src/app-log-files.js +1 -1
  59. package/dist/src/app-log-reconnect.js +1 -1
  60. package/dist/src/app-log-runtime2.js +3 -3
  61. package/dist/src/app-log-session-resource.js +1 -0
  62. package/dist/src/app-resolution.js +2 -2
  63. package/dist/src/apple-runner-platform.js +1 -1
  64. package/dist/src/application-lifecycle-interaction.js +1 -1
  65. package/dist/src/archive.js +2 -2
  66. package/dist/src/artifact-download.js +1 -1
  67. package/dist/src/auth-session.js +1 -1
  68. package/dist/src/auth.js +1 -1
  69. package/dist/src/back-mode.js +1 -1
  70. package/dist/src/backend-snapshot-options.js +1 -1
  71. package/dist/src/boot-diagnostics.js +1 -1
  72. package/dist/src/browser-launch.js +1 -1
  73. package/dist/src/cache.js +1 -1
  74. package/dist/src/capabilities.js +1 -1
  75. package/dist/src/capture.js +1 -1
  76. package/dist/src/cli-config.js +1 -1
  77. package/dist/src/cli-help.js +317 -323
  78. package/dist/src/cli-runner.js +1 -1
  79. package/dist/src/cli.js +4 -4
  80. package/dist/src/click-button.js +1 -1
  81. package/dist/src/client-metro-companion.js +1 -1
  82. package/dist/src/client-metro.js +1 -1
  83. package/dist/src/client-options.js +3 -0
  84. package/dist/src/client-types.d.ts +101 -6
  85. package/dist/src/clipboard-shell-response.js +1 -1
  86. package/dist/src/{scroll-command.d.ts → cloud-artifacts.d.ts} +249 -240
  87. package/dist/src/code-signature-cache.js +1 -1
  88. package/dist/src/code-signature.js +1 -1
  89. package/dist/src/command-schema.js +3 -3
  90. package/dist/src/command-tools.js +2 -2
  91. package/dist/src/command.js +1 -1
  92. package/dist/src/config.js +1 -1
  93. package/dist/src/connection-runtime.js +1 -1
  94. package/dist/src/connection.js +5 -5
  95. package/dist/src/contracts.d.ts +1 -1
  96. package/dist/src/contracts.js +1 -1
  97. package/dist/src/daemon-artifacts.js +1 -0
  98. package/dist/src/daemon-client-lease-beat.js +1 -1
  99. package/dist/src/daemon-client-lifecycle.js +1 -1
  100. package/dist/src/daemon-client-liveness-probe.js +1 -0
  101. package/dist/src/daemon-client-transport.js +1 -1
  102. package/dist/src/daemon-http.js +1 -0
  103. package/dist/src/daemon-policy-file.js +1 -1
  104. package/dist/src/daemon-registration-owner.js +1 -0
  105. package/dist/src/daemon-registration.js +1 -1
  106. package/dist/src/daemon-resolution.js +1 -1
  107. package/dist/src/daemon-shutdown-report.js +1 -1
  108. package/dist/src/daemon.js +2 -2
  109. package/dist/src/debug-symbols-facade.js +3 -3
  110. package/dist/src/device-claim-inspection.js +1 -1
  111. package/dist/src/device-claim-rule.js +1 -1
  112. package/dist/src/device-inventory-context.js +1 -1
  113. package/dist/src/device-readiness-runtime.js +1 -1
  114. package/dist/src/device-release.js +1 -1
  115. package/dist/src/device-rotation.js +1 -1
  116. package/dist/src/device-selection-resolver.js +1 -1
  117. package/dist/src/device-session.js +1 -1
  118. package/dist/src/device-shell.js +1 -1
  119. package/dist/src/device.js +1 -1
  120. package/dist/src/device2.js +1 -3
  121. package/dist/src/device3.js +3 -0
  122. package/dist/src/devicectl.js +1 -1
  123. package/dist/src/diagnostics.js +3 -3
  124. package/dist/src/dispatch-resolve.js +4 -1
  125. package/dist/src/display-inventory.js +1 -1
  126. package/dist/src/doctor-facade.js +1 -1
  127. package/dist/src/doctor2.js +1 -1
  128. package/dist/src/durable-capture-resource.js +1 -0
  129. package/dist/src/engine-eval-script.js +1 -1
  130. package/dist/src/error.js +1 -1
  131. package/dist/src/exec.js +1 -1
  132. package/dist/src/file.js +1 -1
  133. package/dist/src/find.js +1 -1
  134. package/dist/src/generic-settle.js +1 -1
  135. package/dist/src/generic.js +1 -1
  136. package/dist/src/gesture-admission.js +1 -1
  137. package/dist/src/gesture-plan.js +1 -1
  138. package/dist/src/gesture-runtime.js +1 -1
  139. package/dist/src/harmonyos.js +1 -1
  140. package/dist/src/hdc.js +1 -1
  141. package/dist/src/helper.js +2 -2
  142. package/dist/src/host-file.js +1 -1
  143. package/dist/src/host.js +1 -1
  144. package/dist/src/human-control-contract.js +1 -1
  145. package/dist/src/human-control.js +1 -1
  146. package/dist/src/index.d.ts +2 -2
  147. package/dist/src/index.js +1 -1
  148. package/dist/src/input-actions.js +1 -1
  149. package/dist/src/input-actions2.js +2 -2
  150. package/dist/src/install-artifact-facade.js +1 -1
  151. package/dist/src/install-source-network-transport.js +1 -1
  152. package/dist/src/install-source.d.ts +5 -0
  153. package/dist/src/install-source2.js +1 -1
  154. package/dist/src/instance-client.js +1 -0
  155. package/dist/src/interaction-positionals.js +1 -1
  156. package/dist/src/interaction-ref-policy.js +1 -1
  157. package/dist/src/interaction.js +1 -1
  158. package/dist/src/interaction2.js +1 -1
  159. package/dist/src/interactor-operation-binding.js +1 -1
  160. package/dist/src/interactor.js +5 -5
  161. package/dist/src/interactor2.js +1 -1
  162. package/dist/src/interactors.js +1 -1
  163. package/dist/src/internal/bin.js +1 -1
  164. package/dist/src/internal/daemon.js +1 -2
  165. package/dist/src/internal/png-worker.js +1 -1
  166. package/dist/src/internal/run-script-http-child.js +1 -1
  167. package/dist/src/inventory.js +4 -4
  168. package/dist/src/inventory2.js +1 -1
  169. package/dist/src/inventory3.js +1 -1
  170. package/dist/src/inventory5.js +1 -1
  171. package/dist/src/ios-client.js +4 -30
  172. package/dist/src/ios-settings.js +1 -0
  173. package/dist/src/ios-snapshot-engine.js +1 -1
  174. package/dist/src/ios-snapshot-runtime.js +1 -1
  175. package/dist/src/launch-confirmation.js +1 -0
  176. package/dist/src/lease-context.js +1 -1
  177. package/dist/src/lease-registry-scope.js +1 -0
  178. package/dist/src/lease-scope.js +1 -1
  179. package/dist/src/lease.js +1 -1
  180. package/dist/src/legacy-xctest-device-set.js +1 -1
  181. package/dist/src/limrun-download-file.js +1 -1
  182. package/dist/src/limrun-runtime-dependencies.js +1 -1
  183. package/dist/src/limrun.d.ts +78 -44
  184. package/dist/src/limrun.js +1 -1
  185. package/dist/src/linux.js +1 -1
  186. package/dist/src/load.js +1 -0
  187. package/dist/src/location-coordinates.js +1 -1
  188. package/dist/src/macos-facade.js +1 -1
  189. package/dist/src/managed-device-scope.js +1 -1
  190. package/dist/src/mechanics.js +16 -16
  191. package/dist/src/observation.js +1 -1
  192. package/dist/src/observe-until.js +1 -1
  193. package/dist/src/overlay.js +2 -2
  194. package/dist/src/owner-identity.js +3 -3
  195. package/dist/src/path-resolution.js +1 -1
  196. package/dist/src/perf-facade.js +1 -1
  197. package/dist/src/perf-runtime-operation-builder.js +1 -1
  198. package/dist/src/perf-runtime-plan.js +1 -1
  199. package/dist/src/perf-target.js +1 -1
  200. package/dist/src/perf.js +1 -1
  201. package/dist/src/physical-device-control.js +1 -1
  202. package/dist/src/platform-request-scope.js +1 -1
  203. package/dist/src/platform-runtime-android-adb-host.js +1 -1
  204. package/dist/src/platform-runtime-app-log-process.js +1 -1
  205. package/dist/src/platform-runtime-host.js +1 -1
  206. package/dist/src/platform-runtime-managed-owner.js +1 -1
  207. package/dist/src/platform-runtime-open-target.js +1 -1
  208. package/dist/src/platform-runtime-operation-host.js +2 -2
  209. package/dist/src/platform-runtime-runtime-hints.js +2 -2
  210. package/dist/src/platform-runtime-screen-recording-android-host.js +1 -1
  211. package/dist/src/platform-runtime-screen-recording-apple-runner-transport.js +1 -1
  212. package/dist/src/platform-runtime-screen-recording-apple-simulator-host.js +1 -1
  213. package/dist/src/platform-runtime-screen-recording-finalizer-host.js +1 -1
  214. package/dist/src/platform-runtime-screen-recording-output-host.js +1 -1
  215. package/dist/src/platform-runtime-screen-recording-process-host.js +1 -1
  216. package/dist/src/platform-runtime-unavailable.js +1 -1
  217. package/dist/src/platform-runtime.js +1 -1
  218. package/dist/src/platform-runtime2.js +1 -1
  219. package/dist/src/plugins.d.ts +9 -0
  220. package/dist/src/plugins.js +1 -0
  221. package/dist/src/plugins2.js +2 -0
  222. package/dist/src/png-crop-bytes.js +1 -1
  223. package/dist/src/png-size.js +1 -1
  224. package/dist/src/png-worker-client.js +1 -1
  225. package/dist/src/png.js +1 -1
  226. package/dist/src/prepare-kind.js +1 -1
  227. package/dist/src/process.js +1 -1
  228. package/dist/src/project-runtime.js +1 -1
  229. package/dist/src/provider-credential-fingerprint.js +1 -0
  230. package/dist/src/provider-device-admission.js +1 -1
  231. package/dist/src/provider-device-runtimes.js +1 -0
  232. package/dist/src/provider-limrun-credentials.js +1 -0
  233. package/dist/src/provider-profile-fields.js +1 -0
  234. package/dist/src/provider-session-ownership.js +2 -2
  235. package/dist/src/provider-webdriver.js +2 -1
  236. package/dist/src/providers.js +1 -1
  237. package/dist/src/proxy.js +1 -1
  238. package/dist/src/quality-warnings.js +1 -1
  239. package/dist/src/react-native.js +1 -1
  240. package/dist/src/record-runtime.js +1 -1
  241. package/dist/src/record-trace.js +1 -1
  242. package/dist/src/recording.js +1 -1
  243. package/dist/src/rect-center.js +1 -1
  244. package/dist/src/register-builtins.js +1 -1
  245. package/dist/src/registry.js +52 -36
  246. package/dist/src/remote-config2.js +1 -1
  247. package/dist/src/replay-divergence.js +1 -1
  248. package/dist/src/replay.js +2 -2
  249. package/dist/src/reporting.js +5 -5
  250. package/dist/src/request-admission.js +1 -1
  251. package/dist/src/request-cancel.js +1 -1
  252. package/dist/src/request-cancellation.js +1 -1
  253. package/dist/src/request-runtime-binding.js +1 -1
  254. package/dist/src/result-serialization.js +1 -1
  255. package/dist/src/retry.js +1 -1
  256. package/dist/src/runner-artifact-manifest.js +1 -1
  257. package/dist/src/runner-cache-metadata.js +3 -3
  258. package/dist/src/runner-client.js +1 -1
  259. package/dist/src/runner-dev-tools-security.js +1 -1
  260. package/dist/src/runner-device-readiness.js +1 -1
  261. package/dist/src/runner-disposal.js +1 -1
  262. package/dist/src/runner-error-classification.js +2 -2
  263. package/dist/src/runner-exchange.js +1 -1
  264. package/dist/src/runner-operations-facade.js +1 -1
  265. package/dist/src/runner-session-types.js +1 -1
  266. package/dist/src/runner-start-budget.js +1 -1
  267. package/dist/src/runner.js +1 -1
  268. package/dist/src/runtime-admission.js +1 -1
  269. package/dist/src/runtime-transport-hints.js +1 -1
  270. package/dist/src/runtime.js +1 -1
  271. package/dist/src/runtime10.js +2 -2
  272. package/dist/src/runtime2.js +3 -3
  273. package/dist/src/runtime3.js +1 -1
  274. package/dist/src/runtime4.js +5 -5
  275. package/dist/src/runtime5.js +1 -1
  276. package/dist/src/runtime6.js +1 -1
  277. package/dist/src/runtime7.js +1 -1
  278. package/dist/src/runtime8.js +1 -1
  279. package/dist/src/runtime9.js +1 -1
  280. package/dist/src/screen-recording-admission-ledger.js +1 -1
  281. package/dist/src/screen-recording-completion.js +1 -1
  282. package/dist/src/screen-recording-options.js +1 -1
  283. package/dist/src/screen-recording-resource-recovery.js +1 -1
  284. package/dist/src/screen-recording-transport.js +1 -1
  285. package/dist/src/screenshot-density.js +1 -1
  286. package/dist/src/screenshot-image.js +1 -1
  287. package/dist/src/screenshot-result.js +1 -1
  288. package/dist/src/screenshot-runtime.js +1 -1
  289. package/dist/src/screenshot.js +1 -1
  290. package/dist/src/screenshot3.js +1 -1
  291. package/dist/src/script.js +1 -1
  292. package/dist/src/scroll-command.js +1 -1
  293. package/dist/src/scroll-gesture.js +1 -1
  294. package/dist/src/sdk-batch-runner.js +2 -2
  295. package/dist/src/sdk-batch.js +2 -2
  296. package/dist/src/sdk-contracts.d.ts +47 -2
  297. package/dist/src/sdk-contracts.js +2 -2
  298. package/dist/src/sdk-device.js +1 -1
  299. package/dist/src/sdk-io.js +1 -1
  300. package/dist/src/sdk-remote-config.js +1 -1
  301. package/dist/src/sdk-selectors.d.ts +37 -8
  302. package/dist/src/selection.js +0 -0
  303. package/dist/src/selector-runtime.js +1 -1
  304. package/dist/src/selectors.d.ts +2 -2
  305. package/dist/src/selectors.js +1 -1
  306. package/dist/src/server.js +1 -1
  307. package/dist/src/session-allocation.js +1 -1
  308. package/dist/src/session-artifact-paths.js +1 -1
  309. package/dist/src/session-repair-tombstone.js +1 -1
  310. package/dist/src/session-routing.js +1 -1
  311. package/dist/src/session-runtime-admission.js +1 -1
  312. package/dist/src/session-snapshot.js +1 -1
  313. package/dist/src/session-stream.js +27 -0
  314. package/dist/src/session.js +1 -1
  315. package/dist/src/session2.js +5 -5
  316. package/dist/src/settings.js +1 -1
  317. package/dist/src/settings2.js +1 -1
  318. package/dist/src/simctl-settings-facade.js +1 -0
  319. package/dist/src/simctl-settings.js +1 -0
  320. package/dist/src/simctl.js +1 -1
  321. package/dist/src/simulator-boot.js +1 -1
  322. package/dist/src/simulator.js +1 -1
  323. package/dist/src/snapshot-diagnostics.js +1 -1
  324. package/dist/src/snapshot-runtime-binding.js +1 -1
  325. package/dist/src/snapshot-runtime.js +1 -1
  326. package/dist/src/snapshot-state.js +1 -1
  327. package/dist/src/snapshot2.js +1 -1
  328. package/dist/src/snapshot3.js +1 -1
  329. package/dist/src/snapshot4.js +1 -1
  330. package/dist/src/src.js +1 -1
  331. package/dist/src/src7.js +1 -1
  332. package/dist/src/src8.js +2 -2
  333. package/dist/src/store.js +1 -0
  334. package/dist/src/string-enum.js +1 -1
  335. package/dist/src/surface-snapshot.js +1 -1
  336. package/dist/src/takeover.js +1 -1
  337. package/dist/src/target-evidence.js +1 -1
  338. package/dist/src/telemetry.js +1 -1
  339. package/dist/src/text-entry-focus.js +1 -1
  340. package/dist/src/tool-provider.js +3 -3
  341. package/dist/src/tool-provider2.js +1 -1
  342. package/dist/src/tool-provider3.js +1 -1
  343. package/dist/src/toolchain-probe.js +2 -2
  344. package/dist/src/touch-runtime.js +1 -1
  345. package/dist/src/touch-runtime2.js +1 -1
  346. package/dist/src/transport.js +2 -2
  347. package/dist/src/tv-remote.js +1 -1
  348. package/dist/src/type-text-runtime.js +1 -1
  349. package/dist/src/update-check.js +1 -1
  350. package/dist/src/vars.js +1 -1
  351. package/dist/src/verified-file.js +1 -1
  352. package/dist/src/version.js +1 -1
  353. package/dist/src/video.js +1 -1
  354. package/dist/src/viewport.js +1 -1
  355. package/dist/src/web.js +1 -1
  356. package/dist/src/web2.js +1 -1
  357. package/dist/src/webdriver-source.js +1 -1
  358. package/package.json +5 -1
  359. package/server.json +2 -2
  360. package/android/ime-helper/dist/agent-device-android-ime-helper-0.21.19.apk.sha256 +0 -1
  361. package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.21.19.apk +0 -0
  362. package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.21.19.apk.sha256 +0 -1
  363. package/dist/src/api.js +0 -3
  364. package/dist/src/daemon-process.js +0 -1
  365. package/dist/src/multitouch-support.js +0 -1
  366. package/dist/src/parameterized-recorded-fill.js +0 -1
  367. package/dist/src/runner-startup-transport.js +0 -1
  368. package/dist/src/runner-xctestrun.js +0 -1
  369. package/dist/src/session-store.js +0 -4
@@ -66,6 +66,10 @@ int main(int argc, const char *argv[]) {
66
66
  @property(nonatomic, assign) NSUInteger firstAlertActions;
67
67
  @property(nonatomic, assign) NSUInteger replacementAlertActions;
68
68
  @property(nonatomic, strong) UILabel *textEntryWriteBackStatus;
69
+ @property(nonatomic, strong) UILabel *textEntryDigitSlots;
70
+ // Filled from the fixture field's own delegate callbacks so a test can tell that a gesture really
71
+ // took focus from an input that has stopped answering the accessibility query that found it.
72
+ @property(nonatomic, strong) UILabel *textEntryFocusWitness;
69
73
  @property(nonatomic, assign) NSUInteger textEntryWriteBacks;
70
74
  @property(nonatomic, copy, nullable) NSString *textEntryRenderedValue;
71
75
  @property(nonatomic, assign) NSTimeInterval textEntryLastEditTime;
@@ -73,6 +77,7 @@ int main(int argc, const char *argv[]) {
73
77
  @property(nonatomic, assign) NSUInteger textEntryBurstEdits;
74
78
  @property(nonatomic, assign) NSTimeInterval textEntryBurstMinGap;
75
79
  @property(nonatomic, assign) NSTimeInterval textEntryAcknowledgeWindowSeconds;
80
+ @property(nonatomic, strong) NSLayoutConstraint *textEntryFieldTop;
76
81
  @property(nonatomic, assign) BOOL alertFixtureStarted;
77
82
  @property(nonatomic, strong) NSTimer *alertActivationBusyBackstop;
78
83
  @property(nonatomic, strong) NSTimer *alertBannerRepost;
@@ -81,6 +86,17 @@ int main(int argc, const char *argv[]) {
81
86
  #if TARGET_OS_IOS
82
87
  @interface AgentDeviceRunnerViewController () <UNUserNotificationCenterDelegate>
83
88
  @end
89
+
90
+ // A one-time-code field that announces how many digits it holds instead of the digits, the way
91
+ // React Native OTP inputs set `accessibilityValue` on their hidden text input.
92
+ @interface AgentDeviceDigitCountTextField : UITextField
93
+ @end
94
+
95
+ @implementation AgentDeviceDigitCountTextField
96
+ - (NSString *)accessibilityValue {
97
+ return [NSString stringWithFormat:@"%lu of 6 digits", (unsigned long)self.text.length];
98
+ }
99
+ @end
84
100
  #endif
85
101
 
86
102
  @implementation AgentDeviceRunnerViewController
@@ -265,6 +281,11 @@ static NSTimeInterval AgentDeviceTextEntryAcknowledgeWindow(void) {
265
281
  // Edits further apart than this belong to different bursts: one runner command's characters arrive
266
282
  // well inside it, and two commands are separated by at least a commit-wait poll and a status read.
267
283
  static const NSTimeInterval AgentDeviceTextEntryBurstBreakSeconds = 1.0;
284
+ static const NSUInteger AgentDeviceTextEntryAutoSubmitLength = 6;
285
+
286
+ static const CGFloat AgentDeviceTextEntryFieldTopInset = 24;
287
+ static const CGFloat AgentDeviceTextEntryFieldHeight = 44;
288
+ static const CGFloat AgentDeviceTextEntryNeighbourGap = 16;
268
289
 
269
290
  - (void)agentDeviceTextEntryDidChange:(UITextField *)textField {
270
291
  // A field whose app owns its value, the way a controlled React Native `TextInput` does. A burst
@@ -293,10 +314,56 @@ static const NSTimeInterval AgentDeviceTextEntryBurstBreakSeconds = 1.0;
293
314
  }
294
315
  [self updateTextEntryWriteBackStatus];
295
316
  }
317
+ self.textEntryDigitSlots.text = textField.text;
296
318
  if ([NSProcessInfo.processInfo.arguments containsObject:@"--agent-device-text-entry-disappear-after-input"] &&
297
319
  textField.text.length > 0) {
298
320
  [textField removeFromSuperview];
299
321
  }
322
+ // An auto-submitting one-time-code field: the last digit navigates away, to a screen whose own
323
+ // input takes the code field's place or to one without an input. `replace-after-input` is the
324
+ // same navigation landing before the code is complete.
325
+ NSArray<NSString *> *arguments = NSProcessInfo.processInfo.arguments;
326
+ BOOL codeComplete = textField.text.length >= AgentDeviceTextEntryAutoSubmitLength;
327
+ if (textField.superview == nil) {
328
+ return;
329
+ }
330
+ if ((codeComplete && [arguments containsObject:@"--agent-device-text-entry-auto-submit"]) ||
331
+ (textField.text.length > 0 && [arguments containsObject:@"--agent-device-text-entry-replace-after-input"])) {
332
+ UITextField *nextScreenField = [[UITextField alloc] initWithFrame:textField.frame];
333
+ if (![arguments containsObject:@"--agent-device-text-entry-unnamed-input"]) {
334
+ nextScreenField.accessibilityIdentifier = @"agent-device-auto-submit-next-screen-input";
335
+ }
336
+ nextScreenField.borderStyle = UITextBorderStyleRoundedRect;
337
+ [textField.superview addSubview:nextScreenField];
338
+ [textField removeFromSuperview];
339
+ [nextScreenField becomeFirstResponder];
340
+ } else if (codeComplete &&
341
+ [arguments containsObject:@"--agent-device-text-entry-auto-submit-without-successor"]) {
342
+ [textField removeFromSuperview];
343
+ }
344
+ }
345
+
346
+ // Stops answering the element-type query that found it, the moment it takes focus, under
347
+ // `--agent-device-text-entry-unqueryable-on-focus`. #3060's Flutter password field reaches this shape
348
+ // because its two accessibility channels disagree once focused — `TextField` through the legacy
349
+ // attributes, `Other` through the modern ones — and what either channel leaves behind is the same:
350
+ // the tap's element answers the query that resolved it up to the gesture and refuses it after, so a
351
+ // read of it after dispatching records an XCTest failure instead of describing the tap that already
352
+ // landed. The field keeps first-responder, so a test still needs a witness that is not the
353
+ // accessibility tree to tell the gesture landed: `agent-device-text-entry-focus` is a label this
354
+ // method fills in from the field's own delegate callback.
355
+ - (void)agentDeviceTextEntryBecomesUnqueryable:(UITextField *)textField {
356
+ self.textEntryFocusWitness.text = @"focus";
357
+ textField.hidden = YES;
358
+ }
359
+
360
+ // Moves the field up by its own height plus the gap below it when it gains focus, the way keyboard
361
+ // avoidance or a bottom sheet extending above the keyboard does, so the neighbouring field slides
362
+ // into the point the focus tap hit.
363
+ - (void)agentDeviceTextEntryDidBeginEditing:(UITextField *)textField {
364
+ self.textEntryFieldTop.constant =
365
+ AgentDeviceTextEntryFieldTopInset - AgentDeviceTextEntryFieldHeight - AgentDeviceTextEntryNeighbourGap;
366
+ [self.view layoutIfNeeded];
300
367
  }
301
368
  #endif
302
369
 
@@ -339,8 +406,13 @@ static const NSTimeInterval AgentDeviceTextEntryBurstBreakSeconds = 1.0;
339
406
  }
340
407
 
341
408
  if ([NSProcessInfo.processInfo.arguments containsObject:@"--agent-device-text-entry-regression"]) {
342
- UITextField *textField = [[UITextField alloc] init];
343
- textField.accessibilityIdentifier = @"agent-device-hardware-keyboard-input";
409
+ BOOL digitCountValue =
410
+ [NSProcessInfo.processInfo.arguments containsObject:@"--agent-device-text-entry-digit-count-value"];
411
+ UITextField *textField =
412
+ digitCountValue ? [[AgentDeviceDigitCountTextField alloc] init] : [[UITextField alloc] init];
413
+ if (![NSProcessInfo.processInfo.arguments containsObject:@"--agent-device-text-entry-unnamed-input"]) {
414
+ textField.accessibilityIdentifier = @"agent-device-hardware-keyboard-input";
415
+ }
344
416
  textField.borderStyle = UITextBorderStyleRoundedRect;
345
417
  // An empty input view keeps the software keyboard down, which is the hardware-keyboard responder
346
418
  // these routes are addressed to. `--agent-device-text-entry-soft-keyboard` leaves the real input
@@ -354,12 +426,46 @@ static const NSTimeInterval AgentDeviceTextEntryBurstBreakSeconds = 1.0;
354
426
  forControlEvents:UIControlEventEditingChanged];
355
427
  textField.translatesAutoresizingMaskIntoConstraints = NO;
356
428
  [self.view addSubview:textField];
429
+ self.textEntryFieldTop = [textField.topAnchor constraintEqualToAnchor:label.bottomAnchor
430
+ constant:AgentDeviceTextEntryFieldTopInset];
357
431
  [NSLayoutConstraint activateConstraints:@[
358
432
  [textField.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor],
359
- [textField.topAnchor constraintEqualToAnchor:label.bottomAnchor constant:24],
433
+ self.textEntryFieldTop,
360
434
  [textField.widthAnchor constraintEqualToConstant:240],
361
- [textField.heightAnchor constraintEqualToConstant:44],
435
+ [textField.heightAnchor constraintEqualToConstant:AgentDeviceTextEntryFieldHeight],
362
436
  ]];
437
+ if ([NSProcessInfo.processInfo.arguments containsObject:@"--agent-device-text-entry-unqueryable-on-focus"]) {
438
+ self.textEntryFocusWitness = [[UILabel alloc] init];
439
+ self.textEntryFocusWitness.accessibilityIdentifier = @"agent-device-text-entry-focus";
440
+ self.textEntryFocusWitness.translatesAutoresizingMaskIntoConstraints = NO;
441
+ [self.view addSubview:self.textEntryFocusWitness];
442
+ [NSLayoutConstraint activateConstraints:@[
443
+ [self.textEntryFocusWitness.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor],
444
+ [self.textEntryFocusWitness.topAnchor constraintEqualToAnchor:textField.bottomAnchor constant:24],
445
+ ]];
446
+ [textField addTarget:self
447
+ action:@selector(agentDeviceTextEntryBecomesUnqueryable:)
448
+ forControlEvents:UIControlEventEditingDidBegin];
449
+ }
450
+ if ([NSProcessInfo.processInfo.arguments containsObject:@"--agent-device-text-entry-moves-on-focus"]) {
451
+ textField.text = @"stale";
452
+ [textField addTarget:self
453
+ action:@selector(agentDeviceTextEntryDidBeginEditing:)
454
+ forControlEvents:UIControlEventEditingDidBegin];
455
+ UITextField *neighbour = [[UITextField alloc] init];
456
+ neighbour.accessibilityIdentifier = @"agent-device-text-entry-neighbour";
457
+ neighbour.borderStyle = UITextBorderStyleRoundedRect;
458
+ neighbour.text = @"neighbour";
459
+ neighbour.translatesAutoresizingMaskIntoConstraints = NO;
460
+ [self.view addSubview:neighbour];
461
+ [NSLayoutConstraint activateConstraints:@[
462
+ [neighbour.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor],
463
+ [neighbour.topAnchor constraintEqualToAnchor:textField.bottomAnchor
464
+ constant:AgentDeviceTextEntryNeighbourGap],
465
+ [neighbour.widthAnchor constraintEqualToConstant:240],
466
+ [neighbour.heightAnchor constraintEqualToConstant:AgentDeviceTextEntryFieldHeight],
467
+ ]];
468
+ }
363
469
  if ([NSProcessInfo.processInfo.arguments containsObject:@"--agent-device-text-entry-app-owned-value"]) {
364
470
  self.textEntryAcknowledgeWindowSeconds = AgentDeviceTextEntryAcknowledgeWindow();
365
471
  // Reports how many edits this app rendered and how many writes it had to make because a
@@ -375,6 +481,17 @@ static const NSTimeInterval AgentDeviceTextEntryBurstBreakSeconds = 1.0;
375
481
  ]];
376
482
  [self updateTextEntryWriteBackStatus];
377
483
  }
484
+ if (digitCountValue) {
485
+ // The slots an OTP screen renders next to its input: the digits the field really holds.
486
+ self.textEntryDigitSlots = [[UILabel alloc] init];
487
+ self.textEntryDigitSlots.accessibilityIdentifier = @"agent-device-text-entry-digit-slots";
488
+ self.textEntryDigitSlots.translatesAutoresizingMaskIntoConstraints = NO;
489
+ [self.view addSubview:self.textEntryDigitSlots];
490
+ [NSLayoutConstraint activateConstraints:@[
491
+ [self.textEntryDigitSlots.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor],
492
+ [self.textEntryDigitSlots.topAnchor constraintEqualToAnchor:textField.bottomAnchor constant:12],
493
+ ]];
494
+ }
378
495
  }
379
496
 
380
497
  if ([NSProcessInfo.processInfo.arguments containsObject:@"--agent-device-crowded-screen"]) {
@@ -422,6 +539,8 @@ static const NSTimeInterval AgentDeviceTextEntryBurstBreakSeconds = 1.0;
422
539
  // UIApplicationSceneManifest in Info.plist names this class; a rename must update the manifest.
423
540
  @interface AgentDeviceRunnerSceneDelegate : UIResponder <UIWindowSceneDelegate>
424
541
  @property(nonatomic, strong) UIWindow *window;
542
+ // Held for the app's lifetime: a window nobody retained would be gone before the test queried it.
543
+ @property(nonatomic, strong) UIWindow *secondWindow;
425
544
  @end
426
545
 
427
546
  @implementation AgentDeviceRunnerSceneDelegate
@@ -442,6 +561,16 @@ static const NSTimeInterval AgentDeviceTextEntryBurstBreakSeconds = 1.0;
442
561
  if ([NSProcessInfo.processInfo.arguments containsObject:@"--agent-device-dismiss-popup-window"]) {
443
562
  self.window.accessibilityIdentifier = @"Dismiss popup";
444
563
  }
564
+ // A second window the app keeps visible, so `app.windows` reports the main window and this one.
565
+ // Window order is what the resolver walks, and a candidate behind the answer must never be read.
566
+ if ([NSProcessInfo.processInfo.arguments containsObject:@"--agent-device-second-window"]) {
567
+ UIWindow *second = [[UIWindow alloc] initWithWindowScene:(UIWindowScene *)scene];
568
+ second.frame = ((UIWindowScene *)scene).coordinateSpace.bounds;
569
+ second.accessibilityIdentifier = @"agent-device-second-window";
570
+ second.rootViewController = [UIViewController new];
571
+ self.secondWindow = second;
572
+ [second makeKeyAndVisible];
573
+ }
445
574
  #endif
446
575
  [self.window makeKeyAndVisible];
447
576
  }
@@ -5,6 +5,12 @@ NS_ASSUME_NONNULL_BEGIN
5
5
  FOUNDATION_EXPORT NSInteger RunnerControlledScrollFrameCount(double durationMs);
6
6
  FOUNDATION_EXPORT double RunnerControlledScrollProgress(double t);
7
7
 
8
+ typedef NS_ENUM(NSInteger, RunnerTapSynthesisStatus) {
9
+ RunnerTapSynthesisStatusSucceeded,
10
+ RunnerTapSynthesisStatusDeadlineExceeded,
11
+ RunnerTapSynthesisStatusFailed,
12
+ };
13
+
8
14
  @interface RunnerSynthesizedGesture : NSObject
9
15
 
10
16
  // `resolvedWindow` is the app window the caller already resolved for the gesture's reference
@@ -26,10 +32,12 @@ FOUNDATION_EXPORT double RunnerControlledScrollProgress(double t);
26
32
  y2:(double)y2
27
33
  durationMs:(double)durationMs;
28
34
 
29
- + (NSString * _Nullable)synthesizeTapWithApplication:(id)application
35
+ + (RunnerTapSynthesisStatus)synthesizeTapWithApplication:(id)application
30
36
  resolvedWindow:(id _Nullable)resolvedWindow
31
37
  x:(double)x
32
- y:(double)y;
38
+ y:(double)y
39
+ deadline:(NSDate * _Nullable)deadline
40
+ errorMessage:(NSString * _Nullable * _Nullable)errorMessage;
33
41
 
34
42
  // Each pointer is an ordered array of { x, y, offsetMs } samples. The first sample
35
43
  // starts contact; subsequent samples move it; all pointers lift at their final offset.
@@ -75,7 +75,13 @@ static NSString * _Nullable RunnerTrySynthesizeDrag(
75
75
  NSString *recordName,
76
76
  RunnerDragPointerPathFactory pathFactory
77
77
  );
78
- static NSString * _Nullable RunnerTrySynthesizeTap(id application, id _Nullable resolvedWindow, CGPoint point);
78
+ static RunnerTapSynthesisStatus RunnerTrySynthesizeTap(
79
+ id application,
80
+ id _Nullable resolvedWindow,
81
+ CGPoint point,
82
+ NSDate * _Nullable deadline,
83
+ NSString * _Nullable * _Nullable errorMessage
84
+ );
79
85
  // XCTest's proven swipe profile reaches the endpoint in 100 ms, then holds for the planned
80
86
  // fling duration. Fast movement is what lets UIKit distinguish a fling from a timed pan.
81
87
  static const NSTimeInterval RunnerSwipeMovementDurationSeconds = 0.1;
@@ -132,14 +138,20 @@ static id RunnerTapPointerPath(
132
138
  }
133
139
  }
134
140
 
135
- + (NSString * _Nullable)synthesizeTapWithApplication:(id)application
141
+ + (RunnerTapSynthesisStatus)synthesizeTapWithApplication:(id)application
136
142
  resolvedWindow:(id _Nullable)resolvedWindow
137
143
  x:(double)x
138
- y:(double)y {
144
+ y:(double)y
145
+ deadline:(NSDate * _Nullable)deadline
146
+ errorMessage:(NSString * _Nullable * _Nullable)errorMessage {
147
+ if (errorMessage != NULL) *errorMessage = nil;
139
148
  @try {
140
- return RunnerTrySynthesizeTap(application, resolvedWindow, CGPointMake(x, y));
149
+ return RunnerTrySynthesizeTap(application, resolvedWindow, CGPointMake(x, y), deadline, errorMessage);
141
150
  } @catch (NSException *exception) {
142
- return RunnerFormatXCTestException(exception, @"private XCTest event synthesis failed");
151
+ if (errorMessage != NULL) {
152
+ *errorMessage = RunnerFormatXCTestException(exception, @"private XCTest event synthesis failed");
153
+ }
154
+ return RunnerTapSynthesisStatusFailed;
143
155
  }
144
156
  }
145
157
 
@@ -227,7 +239,13 @@ static NSString * _Nullable RunnerTrySynthesizeDrag(
227
239
  return RunnerSynthesizeEventRecord(&bridge, record);
228
240
  }
229
241
 
230
- static NSString * _Nullable RunnerTrySynthesizeTap(id application, id _Nullable resolvedWindow, CGPoint point) {
242
+ static RunnerTapSynthesisStatus RunnerTrySynthesizeTap(
243
+ id application,
244
+ id _Nullable resolvedWindow,
245
+ CGPoint point,
246
+ NSDate * _Nullable deadline,
247
+ NSString * _Nullable * _Nullable errorMessage
248
+ ) {
231
249
  RunnerGestureEventBridge bridge;
232
250
  id record = nil;
233
251
  NSString *error = RunnerCreateEventRecord(
@@ -237,14 +255,23 @@ static NSString * _Nullable RunnerTrySynthesizeTap(id application, id _Nullable
237
255
  &bridge,
238
256
  &record
239
257
  );
240
- if (error != nil) return error;
258
+ if (error != nil) {
259
+ if (errorMessage != NULL) *errorMessage = error;
260
+ return RunnerTapSynthesisStatusFailed;
261
+ }
241
262
 
242
263
  id path = RunnerTapPointerPath(&bridge, point);
243
264
  if (path == nil) {
244
- return @"private XCTest event synthesis failed: could not create pointer path";
265
+ if (errorMessage != NULL) *errorMessage = @"private XCTest event synthesis failed: could not create pointer path";
266
+ return RunnerTapSynthesisStatusFailed;
245
267
  }
246
268
  ((RunnerMsgSendAddPath)objc_msgSend)(record, bridge.core.addPathSelector, path);
247
- return RunnerSynthesizeEventRecord(&bridge, record);
269
+ if (deadline != nil && deadline.timeIntervalSinceNow <= 0) {
270
+ return RunnerTapSynthesisStatusDeadlineExceeded;
271
+ }
272
+ error = RunnerSynthesizeEventRecord(&bridge, record);
273
+ if (errorMessage != NULL) *errorMessage = error;
274
+ return error == nil ? RunnerTapSynthesisStatusSucceeded : RunnerTapSynthesisStatusFailed;
248
275
  }
249
276
 
250
277
  static NSString * _Nullable RunnerResolveGestureEventBridge(
@@ -138,12 +138,40 @@ extension RunnerTests {
138
138
  #if !os(tvOS)
139
139
  guard !frame.isEmpty else { return }
140
140
  #endif
141
+ #if os(iOS)
142
+ let postEventWaitSelector = NSSelectorFromString("_waitForQuiescenceAsPreEvent:")
143
+ guard alert.ownerApp.responds(to: postEventWaitSelector) else {
144
+ outcome = .unsupported(
145
+ message: "alert activation could not wait for post-event quiescence",
146
+ hint: "Inspect the current alert before deciding whether to act again."
147
+ )
148
+ return
149
+ }
150
+ switch synthesizedTapAt(app: alert.ownerApp, x: frame.midX, y: frame.midY, deadline: deadline) {
151
+ case .performed?:
152
+ typealias WaitForQuiescence = @convention(c) (NSObject, Selector, Bool) -> Void
153
+ let waitForQuiescence = unsafeBitCast(
154
+ alert.ownerApp.method(for: postEventWaitSelector),
155
+ to: WaitForQuiescence.self
156
+ )
157
+ waitForQuiescence(alert.ownerApp, postEventWaitSelector, false)
158
+ outcome = .performed
159
+ case .unsupported(let message, _)?:
160
+ outcome = .unsupported(
161
+ message: message,
162
+ hint: "Inspect the current alert before deciding whether to act again."
163
+ )
164
+ case nil:
165
+ break
166
+ }
167
+ #else
141
168
  outcome = activateElement(
142
169
  app: alert.ownerApp,
143
170
  element: button,
144
171
  action: "alert \(action)",
145
172
  resolvedFrame: frame
146
173
  )
174
+ #endif
147
175
  }
148
176
  }
149
177
  return outcome
@@ -125,22 +125,9 @@ final class RunnerCommandJournal {
125
125
  }
126
126
 
127
127
  private func encodeResponseJson(command: Command, response: Response) -> String? {
128
- guard shouldRetainResponseJson(command: command) else { return nil }
128
+ guard command.traits.retainsJournalResponseJson else { return nil }
129
129
  guard let data = try? JSONEncoder().encode(response) else { return nil }
130
130
  guard data.count <= maxResponseJsonBytes else { return nil }
131
131
  return String(data: data, encoding: .utf8)
132
132
  }
133
-
134
- private func shouldRetainResponseJson(command: Command) -> Bool {
135
- switch command.command {
136
- case .snapshot, .screenshot:
137
- return false
138
- case .tap, .mouseClick, .longPress, .drag,
139
- .remotePress, .type, .swipe, .scroll, .desktopScroll, .findText, .querySelector, .readText,
140
- .backInApp, .backSystem, .home, .rotate, .appSwitcher, .actionButton, .keyboardDismiss, .keyboardReturn,
141
- .alert, .sequence, .gesture, .gestureViewport, .recordStart, .recordStop,
142
- .status, .uptime, .appState, .pasteboardWrite, .activate, .terminate, .targetReset, .shutdown:
143
- return true
144
- }
145
- }
146
133
  }
@@ -224,25 +224,23 @@ extension RunnerTests {
224
224
 
225
225
 
226
226
 
227
- func resolveRunnerWindow(app: XCUIApplication) -> (window: XCUIElement?, frame: CGRect) {
228
- let windows = app.windows.allElementsBoundByIndex
229
- var frames: [CGRect?] = []
230
- for window in windows {
231
- frames.append(window.exists ? window.frame : nil)
232
- }
233
- if let index = Self.firstUsableWindowIndex(frames: frames) {
234
- return (windows[index], frames[index] ?? .zero)
235
- }
236
- return (nil, app.frame)
237
- }
238
227
 
239
- static func firstUsableWindowIndex(frames: [CGRect?]) -> Int? {
240
- for (index, frame) in frames.enumerated() {
241
- if let frame, !frame.isEmpty {
242
- return index
228
+
229
+
230
+
231
+
232
+
233
+
234
+
235
+
236
+ func resolveRunnerWindow(app: XCUIApplication) -> (window: XCUIElement?, frame: CGRect) {
237
+ for window in app.windows.allElementsBoundByIndex where window.exists {
238
+ let frame = window.frame
239
+ if !frame.isEmpty {
240
+ return (window, frame)
243
241
  }
244
242
  }
245
- return nil
243
+ return (nil, app.frame)
246
244
  }
247
245
 
248
246
  func onScreenWindowFrame(app: XCUIApplication) -> CGRect {
@@ -102,16 +102,20 @@ struct CommandTraits {
102
102
 
103
103
  let convertsRecordedFailure: Bool
104
104
 
105
+ let retainsJournalResponseJson: Bool
106
+
105
107
  init(
106
108
  isInteraction: Bool = false,
107
109
  retryOnSessionLoss: Bool = false,
108
110
  launchPolicy: CommandLaunchPolicy,
109
- convertsRecordedFailure: Bool = false
111
+ convertsRecordedFailure: Bool = false,
112
+ retainsJournalResponseJson: Bool
110
113
  ) {
111
114
  self.isInteraction = isInteraction
112
115
  self.retryOnSessionLoss = retryOnSessionLoss
113
116
  self.launchPolicy = launchPolicy
114
117
  self.convertsRecordedFailure = convertsRecordedFailure
118
+ self.retainsJournalResponseJson = retainsJournalResponseJson
115
119
  }
116
120
  }
117
121
 
@@ -121,18 +125,26 @@ fileprivate extension CommandTraits {
121
125
  static let interaction = CommandTraits(
122
126
  isInteraction: true,
123
127
  launchPolicy: .mayLaunch,
124
- convertsRecordedFailure: true
128
+ convertsRecordedFailure: true,
129
+ retainsJournalResponseJson: true
125
130
  )
126
131
 
127
132
 
128
133
 
129
134
 
130
135
 
131
- static let appMutation = CommandTraits(launchPolicy: .mayLaunch, convertsRecordedFailure: true)
136
+ static let appMutation = CommandTraits(
137
+ launchPolicy: .mayLaunch, convertsRecordedFailure: true, retainsJournalResponseJson: true
138
+ )
132
139
 
133
140
 
134
141
 
135
- static let appRead = CommandTraits(retryOnSessionLoss: true, launchPolicy: .existingApp)
142
+ static func appRead(retainsJournalResponseJson: Bool) -> CommandTraits {
143
+ CommandTraits(
144
+ retryOnSessionLoss: true, launchPolicy: .existingApp,
145
+ retainsJournalResponseJson: retainsJournalResponseJson
146
+ )
147
+ }
136
148
 
137
149
 
138
150
 
@@ -140,33 +152,41 @@ fileprivate extension CommandTraits {
140
152
 
141
153
  static let selectorResolution = CommandTraits(
142
154
  launchPolicy: .existingApp,
143
- convertsRecordedFailure: true
155
+ convertsRecordedFailure: true,
156
+ retainsJournalResponseJson: true
144
157
  )
145
158
 
146
159
 
147
160
 
148
- static let runnerCaptureRead = CommandTraits(retryOnSessionLoss: true, launchPolicy: .noApp)
161
+ static func runnerCaptureRead(retainsJournalResponseJson: Bool) -> CommandTraits {
162
+ CommandTraits(
163
+ retryOnSessionLoss: true, launchPolicy: .noApp,
164
+ retainsJournalResponseJson: retainsJournalResponseJson
165
+ )
166
+ }
149
167
 
150
168
 
151
- static let runnerLifecycle = CommandTraits(launchPolicy: .noApp)
169
+ static let runnerLifecycle = CommandTraits(launchPolicy: .noApp, retainsJournalResponseJson: true)
152
170
 
153
171
 
154
172
 
155
173
 
156
- static let deviceState = CommandTraits(launchPolicy: .noApp)
174
+ static let deviceState = CommandTraits(launchPolicy: .noApp, retainsJournalResponseJson: true)
157
175
 
158
176
 
159
177
 
160
178
 
161
179
  static let presentedSurfaceMutation = CommandTraits(
162
180
  launchPolicy: .presentedSurface,
163
- convertsRecordedFailure: true
181
+ convertsRecordedFailure: true,
182
+ retainsJournalResponseJson: true
164
183
  )
165
184
 
166
185
 
167
186
  static let presentedSurfaceQuery = CommandTraits(
168
187
  retryOnSessionLoss: true,
169
- launchPolicy: .presentedSurface
188
+ launchPolicy: .presentedSurface,
189
+ retainsJournalResponseJson: true
170
190
  )
171
191
  }
172
192
 
@@ -243,13 +263,19 @@ extension Command {
243
263
  .keyboardDismiss, .keyboardReturn, .sequence, .gesture:
244
264
  return .interaction
245
265
 
246
- case .findText, .readText, .snapshot, .gestureViewport:
247
- return .appRead
266
+ case .findText, .readText, .gestureViewport:
267
+ return .appRead(retainsJournalResponseJson: true)
268
+
269
+ case .snapshot:
270
+ return .appRead(retainsJournalResponseJson: false)
248
271
 
249
272
 
250
273
 
251
- case .screenshot, .status, .appState:
252
- return .runnerCaptureRead
274
+ case .status, .appState:
275
+ return .runnerCaptureRead(retainsJournalResponseJson: true)
276
+
277
+ case .screenshot:
278
+ return .runnerCaptureRead(retainsJournalResponseJson: false)
253
279
 
254
280
  case .alert:
255
281
  return (action ?? "get").lowercased() == "get"
@@ -444,6 +470,13 @@ struct DataPayload: Codable {
444
470
  var keyboardMinY: Double?
445
471
  var maestroNonHittableCoordinateFallbackUsed: Bool?
446
472
  var textEntryRoute: String?
473
+
474
+
475
+ var verification: String?
476
+ var requested: String?
477
+ var before: String?
478
+ var after: String?
479
+ var target: TextEntryVerificationTargetPayload?
447
480
  var runnerFatal: Bool?
448
481
  var runnerFatalReason: String?
449
482
 
@@ -461,6 +494,26 @@ struct DataPayload: Codable {
461
494
  }
462
495
 
463
496
 
497
+ struct TextEntryVerificationTargetPayload: Codable, Equatable {
498
+ let resourceId: String?
499
+ let className: String?
500
+ let packageName: String?
501
+ let rect: SnapshotRect
502
+
503
+ enum CodingKeys: String, CodingKey {
504
+ case resourceId, className, packageName, rect
505
+ }
506
+
507
+ func encode(to encoder: Encoder) throws {
508
+ var container = encoder.container(keyedBy: CodingKeys.self)
509
+ try container.encode(resourceId, forKey: .resourceId)
510
+ try container.encode(className, forKey: .className)
511
+ try container.encode(packageName, forKey: .packageName)
512
+ try container.encode(rect, forKey: .rect)
513
+ }
514
+ }
515
+
516
+
464
517
 
465
518
 
466
519
 
@@ -68,6 +68,7 @@ extension RunnerTests {
68
68
 
69
69
 
70
70
 
71
+
71
72
  func awaitSynthesizedReplacementCommit(
72
73
  app: XCUIApplication,
73
74
  target: TextEntryTarget,
@@ -82,6 +83,7 @@ extension RunnerTests {
82
83
  let outcome = Self.awaitSynthesizedReplacementCommitOutcome(
83
84
  expectedText: expectedText,
84
85
  placeholder: ingredients.placeholder,
86
+ fieldIsSecure: ingredients.fieldIsSecure,
85
87
  now: { Date() },
86
88
  observe: ingredients.observe,
87
89
  waitForNextObservation: ingredients.waitForNextObservation
@@ -132,13 +134,16 @@ extension RunnerTests {
132
134
  expectedText: String
133
135
  ) -> (
134
136
  placeholder: String?,
137
+ fieldIsSecure: Bool,
135
138
  observe: () -> String?,
136
139
  waitForNextObservation: () -> Void
137
140
  ) {
138
- let placeholder = resolveTextEntryElement(app: app, target: target)?.placeholderValue
141
+ let field = resolveTextEntryElement(app: app, target: target)
142
+ let placeholder = field?.placeholderValue
139
143
  let waitStartedAt = Date()
140
144
  return (
141
145
  placeholder: placeholder,
146
+ fieldIsSecure: field?.elementType == .secureTextField,
142
147
  observe: {
143
148
  let observedText = self.editableTextValue(
144
149
  for: self.resolveTextEntryElement(app: app, target: target),
@@ -143,9 +143,14 @@ extension RunnerTests {
143
143
  _ app: XCUIApplication,
144
144
  kind: SynthesizedGesturePolicyKind,
145
145
  context: SynthesizedCoordinateContext?,
146
- synthesize: () -> RunnerInteractionOutcome
146
+ synthesize: () -> RunnerInteractionOutcome?
147
147
  ) -> SynthesizedGestureAttempt {
148
- let (timing, outcome) = performGesture(app, idleTimeout: false, synthesize)
148
+ var outcome: RunnerInteractionOutcome?
149
+ let timing = measureGesture { outcome = synthesize() }
150
+ guard let outcome else {
151
+ logSynthesizedGesturePolicyDecision(kind: kind, context: context, fallbackAttempted: false)
152
+ return .refused(timing: timing, message: "synthesized gesture deadline expired before dispatch", hint: nil)
153
+ }
149
154
  guard case .unsupported(let message, let hint) = outcome else {
150
155
  logSynthesizedGesturePolicyDecision(kind: kind, context: context, fallbackAttempted: false)
151
156
  return .performed(timing: timing)