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
@@ -95,8 +95,9 @@ extension RunnerTests {
95
95
  app: XCUIApplication,
96
96
  x: Double,
97
97
  y: Double,
98
- context: SynthesizedCoordinateContext? = nil
99
- ) -> RunnerInteractionOutcome {
98
+ context: SynthesizedCoordinateContext? = nil,
99
+ deadline: Date? = nil
100
+ ) -> RunnerInteractionOutcome? {
100
101
  #if os(iOS)
101
102
  guard x.isFinite, y.isFinite else {
102
103
  return .unsupported(
@@ -126,18 +127,31 @@ extension RunnerTests {
126
127
  referenceFrame: context.referenceFrame,
127
128
  orientation: orientation
128
129
  )
129
- if let message = RunnerSynthesizedGesture.synthesizeTap(
130
+ var message: NSString?
131
+ let status = RunnerSynthesizedGesture.synthesizeTap(
130
132
  withApplication: app,
131
133
  resolvedWindow: context.resolvedWindow,
132
134
  x: Double(point.x),
133
- y: Double(point.y)
134
- ) {
135
+ y: Double(point.y),
136
+ deadline: deadline,
137
+ errorMessage: &message
138
+ )
139
+ switch status {
140
+ case .succeeded:
141
+ return .performed
142
+ case .deadlineExceeded:
143
+ return nil
144
+ case .failed:
135
145
  return .unsupported(
136
- message: message,
146
+ message: message as String? ?? "private XCTest event synthesis failed",
137
147
  hint: "Falling back to XCTest coordinate tap may be slower and can still need a healthy accessibility tree."
138
148
  )
149
+ @unknown default:
150
+ return .unsupported(
151
+ message: "private XCTest event synthesis returned an unknown status",
152
+ hint: "Inspect the current app before deciding whether to act again."
153
+ )
139
154
  }
140
- return .performed
141
155
  #elseif os(tvOS)
142
156
  return .unsupported(
143
157
  message: "coordinate tap is not supported on tvOS; move focus with swipe or scroll, then select the focused element",
@@ -101,8 +101,27 @@ extension RunnerTests {
101
101
  var warmsUpField = false
102
102
 
103
103
 
104
+
105
+
106
+
107
+
108
+ static let maxClearPassCount = 4
109
+
110
+ static let unreadableClearPassCount = 2
111
+
112
+
104
113
  var synthesizeCallCount: Int {
105
- replacesExistingText ? 2 : 1
114
+ replacesExistingText ? 2 * Self.maxClearPassCount + 1 : 1
115
+ }
116
+
117
+
118
+ var keystrokeCount: Int {
119
+ characterCount + clearReadCount
120
+ }
121
+
122
+
123
+ var clearReadCount: Int {
124
+ replacesExistingText ? Self.maxClearPassCount : 0
106
125
  }
107
126
  }
108
127
 
@@ -129,8 +148,9 @@ extension RunnerTests {
129
148
  var seconds: TimeInterval {
130
149
  steps.reduce(0) { total, step in
131
150
  total
132
- + Double(step.characterCount) * TextEntryTiming.synthesizedCharacterInterval
151
+ + Double(step.keystrokeCount) * TextEntryTiming.synthesizedCharacterInterval
133
152
  + Double(step.synthesizeCallCount) * TextEntryTiming.synthesizeCallOverhead
153
+ + Double(step.clearReadCount) * TextEntryTiming.synthesizedClearReadAllowance
134
154
  + step.pauseAfterSeconds
135
155
  }
136
156
  }
@@ -226,6 +246,48 @@ extension RunnerTests {
226
246
  case stop
227
247
  }
228
248
 
249
+
250
+ enum ClearedFieldRead: Equatable {
251
+ case text(String)
252
+
253
+ case unreadable
254
+
255
+ case unavailable
256
+ }
257
+
258
+
259
+ enum SynthesizedClearOutcome: Equatable {
260
+ case cleared
261
+
262
+ case unverified
263
+
264
+ case notCleared
265
+
266
+ case stopped
267
+ }
268
+
269
+
270
+
271
+ static func clearForSynthesizedReplacement(
272
+ clearOnce: () -> SynthesizedStepDispatch,
273
+ read: () -> ClearedFieldRead
274
+ ) -> SynthesizedClearOutcome {
275
+ for pass in 1...SynthesizedTextPlan.Step.maxClearPassCount {
276
+ guard clearOnce() == .posted else { return .stopped }
277
+ switch read() {
278
+ case .text(let value) where value.isEmpty:
279
+ return .cleared
280
+ case .text:
281
+ continue
282
+ case .unreadable:
283
+ if pass >= SynthesizedTextPlan.Step.unreadableClearPassCount { return .unverified }
284
+ case .unavailable:
285
+ return .notCleared
286
+ }
287
+ }
288
+ return .notCleared
289
+ }
290
+
229
291
  struct SynthesizedPlanRun {
230
292
  let postedCharacterCount: Int
231
293
 
@@ -298,32 +360,68 @@ extension RunnerTests {
298
360
  )
299
361
  )
300
362
  }
363
+ var clearOutcome: SynthesizedClearOutcome?
301
364
 
302
365
 
303
366
  let synthesisAvailable = runSynthesizedTextPlan(
304
367
  plan,
305
368
  text: request.text,
306
369
  post: { slice, step in
307
- switch request.synthesizer.enterText(
308
- app: request.app,
309
- text: slice,
310
- replacingExistingText: step.replacesExistingText
311
- ) {
312
- case .continueTyping:
313
- return .posted
314
- case .fallback:
315
- return .stop
316
- case .raise(let message):
317
- NSException(
318
- name: NSExceptionName.internalInconsistencyException,
319
- reason: message ?? "private XCTest text synthesis failed"
320
- ).raise()
321
- return .stop
370
+ func enter(_ text: String, replacingExistingText: Bool) -> SynthesizedStepDispatch {
371
+ switch request.synthesizer.enterText(
372
+ app: request.app,
373
+ text: text,
374
+ replacingExistingText: replacingExistingText
375
+ ) {
376
+ case .continueTyping:
377
+ return .posted
378
+ case .fallback:
379
+ return .stop
380
+ case .raise(let message):
381
+ NSException(
382
+ name: NSExceptionName.internalInconsistencyException,
383
+ reason: message ?? "private XCTest text synthesis failed"
384
+ ).raise()
385
+ return .stop
386
+ }
387
+ }
388
+ if step.replacesExistingText {
389
+ var passes = 0
390
+ let outcome = Self.clearForSynthesizedReplacement(
391
+ clearOnce: {
392
+ passes += 1
393
+ return enter(XCUIKeyboardKey.delete.rawValue, replacingExistingText: true)
394
+ },
395
+ read: { self.clearedFieldRead(app: request.app, target: request.target) }
396
+ )
397
+ clearOutcome = outcome
398
+ NSLog("AGENT_DEVICE_RUNNER_TEXT_ENTRY_CLEAR passes=%d outcome=%@", passes, String(describing: outcome))
399
+ guard outcome == .cleared || outcome == .unverified else { return .stop }
322
400
  }
401
+ return enter(slice, replacingExistingText: false)
323
402
  },
324
403
 
325
404
  waitAfterWarmupCharacter: { _ in }
326
405
  )
406
+ if clearOutcome == .notCleared {
407
+ logTextEntryPhase(
408
+ commandId: request.commandId,
409
+ phase: "total",
410
+ startedAt: request.startedAt,
411
+ chars: request.text.count,
412
+ mode: .replacement
413
+ )
414
+ return .completed(
415
+ TextEntryResult(
416
+ verified: nil,
417
+ repaired: false,
418
+ expectedText: request.text,
419
+ observedText: nil,
420
+ textEntryRoute: "synthesized-first-responder-replacement",
421
+ failure: .clearNotObserved
422
+ )
423
+ )
424
+ }
327
425
  if synthesisAvailable.stoppedEarly {
328
426
  NSLog("AGENT_DEVICE_RUNNER_TEXT_ENTRY_ROUTE route=verified-fallback reason=synthesis-unavailable")
329
427
  guard let point = request.target.refreshPoint else { return .notApplicable }
@@ -432,6 +530,7 @@ extension RunnerTests {
432
530
  static func awaitSynthesizedReplacementCommitOutcome(
433
531
  expectedText: String,
434
532
  placeholder: String?,
533
+ fieldIsSecure: Bool = false,
435
534
  stallBudget: TimeInterval = TextEntryTiming.synthesizedCommitStallTimeout,
436
535
  ceiling: TimeInterval = TextEntryTiming.synthesizedCommitCeiling,
437
536
  now: () -> Date = { Date() },
@@ -440,6 +539,11 @@ extension RunnerTests {
440
539
  ) -> SynthesizedTextCommitOutcome {
441
540
 
442
541
 
542
+ if fieldIsSecure {
543
+ return .unobservable
544
+ }
545
+
546
+
443
547
 
444
548
  if Self.textMatchesPlaceholder(expectedText, placeholder: placeholder) {
445
549
  return .notObserved
@@ -8,6 +8,7 @@ extension RunnerTests {
8
8
  case notFocused = "TEXT_INPUT_NOT_FOCUSED"
9
9
  case synthesisUnavailable = "TEXT_INPUT_SYNTHESIS_UNAVAILABLE"
10
10
  case commitNotObserved = "TEXT_INPUT_COMMIT_NOT_OBSERVED"
11
+ case clearNotObserved = "TEXT_INPUT_CLEAR_NOT_OBSERVED"
11
12
  case synthesisBudgetExceeded = "TEXT_INPUT_SYNTHESIS_BUDGET_EXCEEDED"
12
13
 
13
14
  var message: String {
@@ -18,6 +19,8 @@ extension RunnerTests {
18
19
  return "Reliable text synthesis is unavailable while the software keyboard is hidden."
19
20
  case .commitNotObserved:
20
21
  return "The runner could not confirm the typed text reached the field."
22
+ case .clearNotObserved:
23
+ return "The runner could not clear the field before typing, so it typed nothing."
21
24
  case .synthesisBudgetExceeded:
22
25
  return "The text is longer than one runner command can type at this pace."
23
26
  }
@@ -31,6 +34,8 @@ extension RunnerTests {
31
34
  return "Show the software keyboard, then retry type."
32
35
  case .commitNotObserved:
33
36
  return "The field may hold none, part, or all of the text. Run snapshot -i and inspect the field: if it already matches, continue; otherwise retry fill with the full text quoted and --delay-ms \(TextEntryTiming.recoveryDelayMilliseconds). Do not use type, which appends to whatever committed."
37
+ case .clearNotObserved:
38
+ return "The field may still hold some or all of its old text. Run snapshot -i and inspect the field, then retry fill. Do not use type, which appends to whatever the field holds."
34
39
  case .synthesisBudgetExceeded:
35
40
  let recoveryDelay = TextEntryTiming.recoveryDelayMilliseconds
36
41
  let recoveryBudget = SynthesizedDeliveryBudget.maxTextLength(
@@ -61,13 +66,24 @@ extension RunnerTests {
61
66
 
62
67
 
63
68
 
69
+ static let unconfirmedSettleCeiling: TimeInterval = 1.0
70
+
71
+
72
+
64
73
  static let synthesizedCommitStallTimeout: TimeInterval = 3.0
65
74
 
66
75
 
67
76
  static let synthesizedCommitCeiling: TimeInterval = 10.0
68
77
 
69
78
 
70
- static let synthesizedReplacementFocusAllowance: TimeInterval = 2.0
79
+
80
+
81
+ static let synthesizedReplacementFocusAllowance: TimeInterval = 4.0
82
+
83
+
84
+
85
+
86
+ static let synthesizedClearReadAllowance: TimeInterval = 0.9
71
87
 
72
88
 
73
89
 
@@ -108,6 +124,27 @@ extension RunnerTests {
108
124
  let observedText: String?
109
125
  var textEntryRoute: String? = nil
110
126
  var failure: TextEntryFailure? = nil
127
+ var unconfirmed: TextEntryUnconfirmedEvidence? = nil
128
+ }
129
+
130
+
131
+
132
+
133
+
134
+
135
+ struct TextEntryInputIdentity: Equatable {
136
+ let elementType: XCUIElement.ElementType
137
+ let identifier: String
138
+
139
+ var isDistinguishable: Bool { !identifier.isEmpty }
140
+ }
141
+
142
+
143
+
144
+
145
+ struct TextInputAtPoint {
146
+ let element: XCUIElement
147
+ let identity: TextEntryInputIdentity
111
148
  }
112
149
 
113
150
  struct TextEntryTarget {
@@ -116,33 +153,81 @@ extension RunnerTests {
116
153
  let prefersFocusedElement: Bool
117
154
  let fromTapWitness: Bool
118
155
 
156
+ let boundIdentity: TextEntryInputIdentity?
157
+
158
+
159
+
160
+
161
+
162
+ let inputAtRefreshPoint: TextInputAtPoint?
163
+
119
164
  init(
120
165
  element: XCUIElement?,
121
166
  refreshPoint: CGPoint?,
122
167
  prefersFocusedElement: Bool,
123
- fromTapWitness: Bool = false
168
+ fromTapWitness: Bool = false,
169
+ boundIdentity: TextEntryInputIdentity? = nil,
170
+ inputAtRefreshPoint: TextInputAtPoint? = nil
124
171
  ) {
125
172
  self.element = element
126
173
  self.refreshPoint = refreshPoint
127
174
  self.prefersFocusedElement = prefersFocusedElement
128
175
  self.fromTapWitness = fromTapWitness
176
+ self.boundIdentity = boundIdentity ?? inputAtRefreshPoint?.identity
177
+ self.inputAtRefreshPoint = inputAtRefreshPoint
129
178
  }
130
179
 
131
180
  func withElement(_ nextElement: XCUIElement?) -> TextEntryTarget {
132
181
  guard let nextElement else {
133
182
  return self
134
183
  }
135
- let frame = nextElement.frame
184
+
185
+
186
+
187
+ let snapshot = nextElement.exists ? try? nextElement.snapshot() : nil
188
+ let frame = snapshot?.frame ?? .zero
136
189
  let point = frame.isEmpty ? refreshPoint : CGPoint(x: frame.midX, y: frame.midY)
137
190
  return TextEntryTarget(
138
191
  element: nextElement,
139
192
  refreshPoint: point,
140
193
  prefersFocusedElement: prefersFocusedElement,
141
- fromTapWitness: fromTapWitness
194
+ fromTapWitness: fromTapWitness,
195
+ boundIdentity: boundIdentity ?? snapshot.map {
196
+ TextEntryInputIdentity(elementType: $0.elementType, identifier: $0.identifier)
197
+ },
198
+ inputAtRefreshPoint: inputAtRefreshPoint
142
199
  )
143
200
  }
144
201
  }
145
202
 
203
+
204
+ enum TextEntryInputProbe: Equatable {
205
+ case input(TextEntryInputIdentity)
206
+
207
+ case noMatch
208
+
209
+ case unavailable
210
+ }
211
+
212
+ static let xCTestUITestingErrorDomain = "com.apple.dt.xctest.ui-testing.error"
213
+
214
+
215
+ static let xCTestNoMatchesErrorCode = 10008
216
+
217
+
218
+ static func textEntryInputProbe(snapshotError error: Error) -> TextEntryInputProbe {
219
+ let error = error as NSError
220
+ return error.domain == xCTestUITestingErrorDomain && error.code == xCTestNoMatchesErrorCode
221
+ ? .noMatch
222
+ : .unavailable
223
+ }
224
+
225
+
226
+
227
+ static func textEntryRepairCanTarget(boundIdentity: TextEntryInputIdentity?) -> Bool {
228
+ boundIdentity?.isDistinguishable ?? true
229
+ }
230
+
146
231
  struct TextEntryStabilization {
147
232
  let element: XCUIElement?
148
233
  let focusConfirmed: Bool
@@ -199,7 +284,95 @@ extension RunnerTests {
199
284
  }
200
285
  }
201
286
 
287
+
288
+
289
+
202
290
  func resolveTextEntryElement(app: XCUIApplication, target: TextEntryTarget) -> XCUIElement? {
291
+ guard let boundIdentity = target.boundIdentity else {
292
+ return resolveUnboundTextEntryElement(app: app, target: target)
293
+ }
294
+ func isBoundInput(_ candidate: XCUIElement) -> Bool {
295
+ candidate.exists && probeTextEntryInput(candidate) == .input(boundIdentity)
296
+ }
297
+ if target.prefersFocusedElement, let focused = focusedTextInput(app: app), isBoundInput(focused) {
298
+ return focused
299
+ }
300
+ if let element = target.element, isBoundInput(element) {
301
+ return element
302
+ }
303
+ if let input = target.inputAtRefreshPoint, textInputStillResolves(input) {
304
+ return input.element
305
+ }
306
+ if target.inputAtRefreshPoint == nil,
307
+ let refreshPoint = target.refreshPoint,
308
+ case .matches(let candidates) = probeTextInputs(app: app, point: refreshPoint),
309
+ let match = candidates.first(where: isBoundInput) {
310
+ return match
311
+ }
312
+ if let focused = focusedTextInput(app: app), isBoundInput(focused) {
313
+ return focused
314
+ }
315
+ if let byIdentifier = boundTextEntryInputQuery(app: app, identity: boundIdentity), isBoundInput(byIdentifier) {
316
+ return byIdentifier
317
+ }
318
+ return nil
319
+ }
320
+
321
+
322
+
323
+
324
+
325
+ func boundTextEntryInputIsGone(app: XCUIApplication, target: TextEntryTarget) -> Bool {
326
+ guard let boundIdentity = target.boundIdentity,
327
+ let query = boundTextEntryInputQuery(app: app, identity: boundIdentity) ?? target.element
328
+ else {
329
+ return false
330
+ }
331
+ switch probeTextEntryInput(query) {
332
+ case .input(let identity) where identity != boundIdentity:
333
+ break
334
+ case .noMatch:
335
+ break
336
+ case .input, .unavailable:
337
+ return false
338
+ }
339
+ guard let refreshPoint = target.refreshPoint else {
340
+ return true
341
+ }
342
+ switch probeTextInputs(app: app, point: refreshPoint) {
343
+ case .absent:
344
+ return true
345
+ case .matches(let candidates):
346
+ return candidates.allSatisfy { probeTextEntryInput($0) != .input(boundIdentity) }
347
+ case .unavailable:
348
+ return false
349
+ }
350
+ }
351
+
352
+
353
+ private func boundTextEntryInputQuery(app: XCUIApplication, identity: TextEntryInputIdentity) -> XCUIElement? {
354
+ guard identity.isDistinguishable else {
355
+ return nil
356
+ }
357
+ return app.descendants(matching: identity.elementType).matching(identifier: identity.identifier).element
358
+ }
359
+
360
+
361
+ func probeTextEntryInput(_ element: XCUIElement) -> TextEntryInputProbe {
362
+ var probe = TextEntryInputProbe.unavailable
363
+ let (_, exception) = catchingObjCException(fallback: ()) {
364
+ do {
365
+ let snapshot = try element.snapshot()
366
+ probe = .input(TextEntryInputIdentity(elementType: snapshot.elementType, identifier: snapshot.identifier))
367
+ } catch {
368
+ probe = Self.textEntryInputProbe(snapshotError: error)
369
+ }
370
+ }
371
+ return exception == nil ? probe : .unavailable
372
+ }
373
+
374
+
375
+ private func resolveUnboundTextEntryElement(app: XCUIApplication, target: TextEntryTarget) -> XCUIElement? {
203
376
  if target.prefersFocusedElement {
204
377
  if let focused = focusedTextInput(app: app) {
205
378
  return focused
@@ -251,7 +424,7 @@ extension RunnerTests {
251
424
  }
252
425
 
253
426
  func editableTextValue(
254
- for element: XCUIElement?,
427
+ for element: (any XCUIElementAttributes)?,
255
428
  treatingPlaceholderAsEmpty: Bool = false
256
429
  ) -> String? {
257
430
  guard let element else {
@@ -271,7 +444,7 @@ extension RunnerTests {
271
444
  }
272
445
  }
273
446
 
274
- private func isPlaceholderValue(_ value: String, for element: XCUIElement) -> Bool {
447
+ private func isPlaceholderValue(_ value: String, for element: any XCUIElementAttributes) -> Bool {
275
448
  if Self.textMatchesPlaceholder(value, placeholder: element.placeholderValue) {
276
449
  return true
277
450
  }
@@ -0,0 +1,119 @@
1
+ import XCTest
2
+
3
+
4
+
5
+
6
+ extension RunnerTests {
7
+
8
+ struct TextEntryElementIdentity: Equatable {
9
+ let identifier: String?
10
+ let elementType: String
11
+ let frame: CGRect
12
+
13
+
14
+
15
+ func isSameElement(as other: TextEntryElementIdentity) -> Bool {
16
+ if identifier != nil || other.identifier != nil {
17
+ return identifier == other.identifier
18
+ }
19
+ return elementType == other.elementType && frame == other.frame
20
+ }
21
+ }
22
+
23
+
24
+ struct TextEntryObservation: Equatable {
25
+ let value: String
26
+ let identity: TextEntryElementIdentity
27
+
28
+
29
+ func isSettled(with other: TextEntryObservation) -> Bool {
30
+ value == other.value && identity.isSameElement(as: other.identity)
31
+ }
32
+ }
33
+
34
+
35
+ struct TextEntryUnconfirmedEvidence: Equatable {
36
+ let requested: String
37
+ let before: String
38
+ let after: String
39
+ let target: TextEntryElementIdentity
40
+ }
41
+
42
+
43
+
44
+
45
+
46
+
47
+ static func textEntryValueEchoes(observed: String, expected: String, baseline: String) -> Bool {
48
+ let request = textEntryRequestWithoutSubmitKeys(expected)
49
+ let residualAndRequest = baseline + request
50
+ return isOrderedSubsequence(observed, of: residualAndRequest)
51
+ || (isOrderedSubsequence(request, of: observed) && !isOrderedSubsequence(request, of: baseline))
52
+ || isOrderedSubsequence(residualAndRequest, of: observed)
53
+ }
54
+
55
+
56
+
57
+
58
+ static func unconfirmedTextEntryEvidence(
59
+ requested: String,
60
+ baseline: TextEntryObservation?,
61
+ observed: TextEntryObservation?
62
+ ) -> TextEntryUnconfirmedEvidence? {
63
+ guard !textEntryRequestWithoutSubmitKeys(requested).isEmpty,
64
+ let baseline,
65
+ let observed,
66
+ baseline.identity.isSameElement(as: observed.identity),
67
+ observed.value != baseline.value,
68
+ !textEntryValueEchoes(observed: observed.value, expected: requested, baseline: baseline.value)
69
+ else {
70
+ return nil
71
+ }
72
+ return TextEntryUnconfirmedEvidence(
73
+ requested: requested,
74
+ before: baseline.value,
75
+ after: observed.value,
76
+ target: observed.identity
77
+ )
78
+ }
79
+
80
+
81
+
82
+ func textEntryObservation(for element: XCUIElement?) -> TextEntryObservation? {
83
+ guard let element,
84
+ let snapshot = try? element.snapshot(),
85
+ let value = editableTextValue(for: snapshot, treatingPlaceholderAsEmpty: true)
86
+ else {
87
+ return nil
88
+ }
89
+ return TextEntryObservation(
90
+ value: value,
91
+ identity: TextEntryElementIdentity(
92
+ identifier: snapshot.identifier.isEmpty ? nil : snapshot.identifier,
93
+ elementType: elementTypeName(snapshot.elementType),
94
+ frame: snapshot.frame
95
+ )
96
+ )
97
+ }
98
+
99
+
100
+ static func textEntryRequestWithoutSubmitKeys(_ text: String) -> String {
101
+ var request = text
102
+ while request.hasSuffix("\n") || request.hasSuffix("\r") {
103
+ request.removeLast()
104
+ }
105
+ return request
106
+ }
107
+
108
+
109
+ static func isOrderedSubsequence(_ candidate: String, of text: String) -> Bool {
110
+ var remaining = text[...]
111
+ for character in candidate {
112
+ guard let match = remaining.firstIndex(of: character) else {
113
+ return false
114
+ }
115
+ remaining = remaining[remaining.index(after: match)...]
116
+ }
117
+ return true
118
+ }
119
+ }
@@ -6,9 +6,16 @@ import XCTest
6
6
 
7
7
 
8
8
  extension RunnerTests {
9
+
10
+
11
+
12
+
13
+
14
+
15
+
9
16
  @MainActor
10
17
  func rememberTextEntryTap(_ element: XCUIElement?) {
11
- guard let element, isTextEntryElement(element) else {
18
+ guard let element else {
12
19
  clearRememberedTextEntryTap()
13
20
  return
14
21
  }
@@ -80,21 +80,33 @@ extension RunnerTests {
80
80
  #endif
81
81
  }
82
82
 
83
+
84
+
85
+
86
+
87
+
88
+
89
+
90
+
91
+
92
+
93
+
94
+
95
+
96
+
83
97
  func waitForTextEntryReadinessAfterTap(app: XCUIApplication, element: XCUIElement) {
84
98
  #if os(iOS)
85
- switch element.elementType {
86
- case .textField, .secureTextField, .searchField, .textView:
87
- if waitForFocusedTextInput(app: app, timeout: TextEntryTiming.readinessTimeout) != nil {
88
- return
89
- }
90
- let frame = element.frame
91
- if !frame.isEmpty {
92
- _ = tapAt(app: app, x: frame.midX, y: frame.midY)
93
- _ = waitForFocusedTextInput(app: app, timeout: TextEntryTiming.readinessTimeout)
94
- }
95
- default:
99
+ if waitForFocusedTextInput(app: app, timeout: TextEntryTiming.readinessTimeout) != nil {
96
100
  return
97
101
  }
102
+ guard let settled = safely("TEXT_ENTRY_READINESS_FRAME", { try? element.snapshot() }) else {
103
+ return
104
+ }
105
+ let frame = settled.frame
106
+ if !frame.isEmpty {
107
+ _ = tapAt(app: app, x: frame.midX, y: frame.midY)
108
+ _ = waitForFocusedTextInput(app: app, timeout: TextEntryTiming.readinessTimeout)
109
+ }
98
110
  #endif
99
111
  }
100
112