agent-device 0.21.20 → 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 (357) hide show
  1. package/android/ime-helper/dist/{agent-device-android-ime-helper-0.21.20.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.20.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.20.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 +121 -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+Models.swift +27 -0
  18. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SynthesizedCommitDeadline.swift +6 -1
  19. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SynthesizedGesturePolicy.swift +7 -2
  20. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SynthesizedInteraction.swift +21 -7
  21. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SynthesizedTextEntry.swift +121 -17
  22. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextEntry.swift +179 -6
  23. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextEntryConfirmation.swift +119 -0
  24. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextEntryFocus.swift +8 -1
  25. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextEntryReadiness.swift +23 -11
  26. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextInputProbe.swift +59 -6
  27. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextTyping.swift +112 -23
  28. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TypeExecution.swift +25 -12
  29. package/dist/apple/runner/AgentDeviceRunner/RecordingScripts/RecordingExportSupport.swift +6 -1
  30. package/dist/apple/runner/AgentDeviceRunner/RecordingScripts/recording-overlay.swift +43 -13
  31. package/dist/src/adapter.js +1 -1
  32. package/dist/src/adb-executor.js +2 -2
  33. package/dist/src/adb-failure.js +1 -1
  34. package/dist/src/adb-transport.js +1 -1
  35. package/dist/src/agent-browser-lifecycle.js +1 -1
  36. package/dist/src/agent-browser-process-record.js +1 -1
  37. package/dist/src/agent-browser-provider.js +1 -1
  38. package/dist/src/agent-browser-tool.js +1 -1
  39. package/dist/src/agent-device-client.js +2 -2
  40. package/dist/src/ai-sdk.js +1 -1
  41. package/dist/src/alert-runtime.js +1 -1
  42. package/dist/src/alert.js +1 -1
  43. package/dist/src/android-adb.d.ts +12 -0
  44. package/dist/src/android-input-ownership.js +1 -1
  45. package/dist/src/app-backend.js +1 -0
  46. package/dist/src/app-catalog.js +1 -1
  47. package/dist/src/app-deployment-resolution.js +1 -1
  48. package/dist/src/app-inventory.js +1 -1
  49. package/dist/src/app-launch.js +1 -1
  50. package/dist/src/app-lifecycle.js +2 -2
  51. package/dist/src/app-lifecycle2.js +1 -1
  52. package/dist/src/app-log-files.js +1 -1
  53. package/dist/src/app-log-reconnect.js +1 -1
  54. package/dist/src/app-log-runtime2.js +3 -3
  55. package/dist/src/app-log-session-resource.js +1 -0
  56. package/dist/src/app-resolution.js +2 -2
  57. package/dist/src/apple-runner-platform.js +1 -1
  58. package/dist/src/application-lifecycle-interaction.js +1 -1
  59. package/dist/src/archive.js +2 -2
  60. package/dist/src/artifact-download.js +1 -1
  61. package/dist/src/auth-session.js +1 -1
  62. package/dist/src/auth.js +1 -1
  63. package/dist/src/back-mode.js +1 -1
  64. package/dist/src/backend-snapshot-options.js +1 -1
  65. package/dist/src/boot-diagnostics.js +1 -1
  66. package/dist/src/cache.js +1 -1
  67. package/dist/src/capabilities.js +1 -1
  68. package/dist/src/capture.js +1 -1
  69. package/dist/src/cli-config.js +1 -1
  70. package/dist/src/cli-help.js +13 -5
  71. package/dist/src/cli.js +4 -4
  72. package/dist/src/click-button.js +1 -1
  73. package/dist/src/client-metro-companion.js +1 -1
  74. package/dist/src/client-metro.js +1 -1
  75. package/dist/src/client-options.js +3 -0
  76. package/dist/src/client-types.d.ts +88 -5
  77. package/dist/src/clipboard-shell-response.js +1 -1
  78. package/dist/src/cloud-artifacts.d.ts +7 -2
  79. package/dist/src/code-signature-cache.js +1 -1
  80. package/dist/src/code-signature.js +1 -1
  81. package/dist/src/command-schema.js +1 -1
  82. package/dist/src/command-tools.js +2 -2
  83. package/dist/src/connection-runtime.js +1 -1
  84. package/dist/src/connection.js +5 -5
  85. package/dist/src/contracts.d.ts +1 -1
  86. package/dist/src/contracts.js +1 -1
  87. package/dist/src/daemon-artifacts.js +1 -0
  88. package/dist/src/daemon-client-lease-beat.js +1 -1
  89. package/dist/src/daemon-client-lifecycle.js +1 -1
  90. package/dist/src/daemon-client-liveness-probe.js +1 -0
  91. package/dist/src/daemon-client-transport.js +1 -1
  92. package/dist/src/daemon-http.js +1 -0
  93. package/dist/src/daemon-policy-file.js +1 -1
  94. package/dist/src/daemon-registration-owner.js +1 -0
  95. package/dist/src/daemon-registration.js +1 -1
  96. package/dist/src/daemon-resolution.js +1 -1
  97. package/dist/src/daemon-shutdown-report.js +1 -1
  98. package/dist/src/daemon.js +2 -2
  99. package/dist/src/debug-symbols-facade.js +1 -1
  100. package/dist/src/device-claim-inspection.js +1 -1
  101. package/dist/src/device-claim-rule.js +1 -1
  102. package/dist/src/device-inventory-context.js +1 -1
  103. package/dist/src/device-readiness-runtime.js +1 -1
  104. package/dist/src/device-release.js +1 -1
  105. package/dist/src/device-rotation.js +1 -1
  106. package/dist/src/device-selection-resolver.js +1 -1
  107. package/dist/src/device-session.js +1 -1
  108. package/dist/src/device-shell.js +1 -1
  109. package/dist/src/device.js +1 -1
  110. package/dist/src/device2.js +1 -3
  111. package/dist/src/device3.js +3 -0
  112. package/dist/src/devicectl.js +1 -1
  113. package/dist/src/diagnostics.js +3 -3
  114. package/dist/src/dispatch-resolve.js +4 -1
  115. package/dist/src/display-inventory.js +1 -1
  116. package/dist/src/doctor-facade.js +1 -1
  117. package/dist/src/doctor2.js +1 -1
  118. package/dist/src/durable-capture-resource.js +1 -0
  119. package/dist/src/engine-eval-script.js +1 -1
  120. package/dist/src/error.js +1 -1
  121. package/dist/src/exec.js +1 -1
  122. package/dist/src/file.js +1 -1
  123. package/dist/src/find.js +1 -1
  124. package/dist/src/generic-settle.js +1 -1
  125. package/dist/src/generic.js +1 -1
  126. package/dist/src/gesture-admission.js +1 -1
  127. package/dist/src/gesture-plan.js +1 -1
  128. package/dist/src/gesture-runtime.js +1 -1
  129. package/dist/src/harmonyos.js +1 -1
  130. package/dist/src/hdc.js +1 -1
  131. package/dist/src/helper.js +2 -2
  132. package/dist/src/host-file.js +1 -1
  133. package/dist/src/host.js +1 -1
  134. package/dist/src/human-control-contract.js +1 -1
  135. package/dist/src/human-control.js +1 -1
  136. package/dist/src/index.d.ts +1 -1
  137. package/dist/src/index.js +1 -1
  138. package/dist/src/input-actions.js +1 -1
  139. package/dist/src/input-actions2.js +2 -2
  140. package/dist/src/install-artifact-facade.js +1 -1
  141. package/dist/src/install-source-network-transport.js +1 -1
  142. package/dist/src/install-source.d.ts +5 -0
  143. package/dist/src/install-source2.js +1 -1
  144. package/dist/src/instance-client.js +1 -0
  145. package/dist/src/interaction-positionals.js +1 -1
  146. package/dist/src/interaction-ref-policy.js +1 -1
  147. package/dist/src/interaction.js +1 -1
  148. package/dist/src/interaction2.js +1 -1
  149. package/dist/src/interactor-operation-binding.js +1 -1
  150. package/dist/src/interactor.js +5 -5
  151. package/dist/src/interactor2.js +1 -1
  152. package/dist/src/interactors.js +1 -1
  153. package/dist/src/internal/bin.js +1 -1
  154. package/dist/src/internal/daemon.js +1 -2
  155. package/dist/src/internal/png-worker.js +1 -1
  156. package/dist/src/internal/run-script-http-child.js +1 -1
  157. package/dist/src/inventory.js +4 -4
  158. package/dist/src/inventory2.js +1 -1
  159. package/dist/src/inventory3.js +1 -1
  160. package/dist/src/inventory5.js +1 -1
  161. package/dist/src/ios-client.js +4 -30
  162. package/dist/src/ios-settings.js +1 -0
  163. package/dist/src/ios-snapshot-engine.js +1 -1
  164. package/dist/src/ios-snapshot-runtime.js +1 -1
  165. package/dist/src/launch-confirmation.js +1 -1
  166. package/dist/src/lease-context.js +1 -1
  167. package/dist/src/lease-registry-scope.js +1 -0
  168. package/dist/src/lease-scope.js +1 -1
  169. package/dist/src/lease.js +1 -1
  170. package/dist/src/legacy-xctest-device-set.js +1 -1
  171. package/dist/src/limrun-download-file.js +1 -1
  172. package/dist/src/limrun-runtime-dependencies.js +1 -1
  173. package/dist/src/limrun.d.ts +28 -2
  174. package/dist/src/limrun.js +1 -1
  175. package/dist/src/linux.js +1 -1
  176. package/dist/src/load.js +1 -0
  177. package/dist/src/location-coordinates.js +1 -1
  178. package/dist/src/macos-facade.js +1 -1
  179. package/dist/src/managed-device-scope.js +1 -1
  180. package/dist/src/mechanics.js +16 -16
  181. package/dist/src/observation.js +1 -1
  182. package/dist/src/observe-until.js +1 -1
  183. package/dist/src/overlay.js +2 -2
  184. package/dist/src/owner-identity.js +3 -3
  185. package/dist/src/path-resolution.js +1 -1
  186. package/dist/src/perf-facade.js +1 -1
  187. package/dist/src/perf-runtime-operation-builder.js +1 -1
  188. package/dist/src/perf-runtime-plan.js +1 -1
  189. package/dist/src/perf-target.js +1 -1
  190. package/dist/src/perf.js +1 -1
  191. package/dist/src/physical-device-control.js +1 -1
  192. package/dist/src/platform-request-scope.js +1 -1
  193. package/dist/src/platform-runtime-android-adb-host.js +1 -1
  194. package/dist/src/platform-runtime-app-log-process.js +1 -1
  195. package/dist/src/platform-runtime-host.js +1 -1
  196. package/dist/src/platform-runtime-managed-owner.js +1 -1
  197. package/dist/src/platform-runtime-open-target.js +1 -1
  198. package/dist/src/platform-runtime-operation-host.js +2 -2
  199. package/dist/src/platform-runtime-runtime-hints.js +2 -2
  200. package/dist/src/platform-runtime-screen-recording-android-host.js +1 -1
  201. package/dist/src/platform-runtime-screen-recording-apple-runner-transport.js +1 -1
  202. package/dist/src/platform-runtime-screen-recording-apple-simulator-host.js +1 -1
  203. package/dist/src/platform-runtime-screen-recording-finalizer-host.js +1 -1
  204. package/dist/src/platform-runtime-screen-recording-output-host.js +1 -1
  205. package/dist/src/platform-runtime-screen-recording-process-host.js +1 -1
  206. package/dist/src/platform-runtime-unavailable.js +1 -1
  207. package/dist/src/platform-runtime.js +1 -1
  208. package/dist/src/platform-runtime2.js +1 -1
  209. package/dist/src/plugins.d.ts +9 -0
  210. package/dist/src/plugins.js +1 -0
  211. package/dist/src/plugins2.js +2 -0
  212. package/dist/src/png-crop-bytes.js +1 -1
  213. package/dist/src/png-size.js +1 -1
  214. package/dist/src/png-worker-client.js +1 -1
  215. package/dist/src/png.js +1 -1
  216. package/dist/src/prepare-kind.js +1 -1
  217. package/dist/src/process.js +1 -1
  218. package/dist/src/project-runtime.js +1 -1
  219. package/dist/src/provider-credential-fingerprint.js +1 -0
  220. package/dist/src/provider-device-admission.js +1 -1
  221. package/dist/src/provider-device-runtimes.js +1 -0
  222. package/dist/src/provider-limrun-credentials.js +1 -0
  223. package/dist/src/provider-profile-fields.js +1 -0
  224. package/dist/src/provider-session-ownership.js +2 -2
  225. package/dist/src/provider-webdriver.js +2 -1
  226. package/dist/src/providers.js +1 -1
  227. package/dist/src/proxy.js +1 -1
  228. package/dist/src/quality-warnings.js +1 -1
  229. package/dist/src/react-native.js +1 -1
  230. package/dist/src/record-runtime.js +1 -1
  231. package/dist/src/record-trace.js +1 -1
  232. package/dist/src/recording.js +1 -1
  233. package/dist/src/rect-center.js +1 -1
  234. package/dist/src/register-builtins.js +1 -1
  235. package/dist/src/registry.js +39 -39
  236. package/dist/src/remote-config2.js +1 -1
  237. package/dist/src/replay-divergence.js +1 -1
  238. package/dist/src/replay.js +2 -2
  239. package/dist/src/reporting.js +5 -5
  240. package/dist/src/request-admission.js +1 -1
  241. package/dist/src/request-cancel.js +1 -1
  242. package/dist/src/request-cancellation.js +1 -1
  243. package/dist/src/request-runtime-binding.js +1 -1
  244. package/dist/src/result-serialization.js +1 -1
  245. package/dist/src/retry.js +1 -1
  246. package/dist/src/runner-artifact-manifest.js +1 -1
  247. package/dist/src/runner-cache-metadata.js +3 -3
  248. package/dist/src/runner-client.js +1 -1
  249. package/dist/src/runner-dev-tools-security.js +1 -1
  250. package/dist/src/runner-device-readiness.js +1 -1
  251. package/dist/src/runner-disposal.js +1 -1
  252. package/dist/src/runner-error-classification.js +2 -2
  253. package/dist/src/runner-exchange.js +1 -1
  254. package/dist/src/runner-operations-facade.js +1 -1
  255. package/dist/src/runner-session-types.js +1 -1
  256. package/dist/src/runner-start-budget.js +1 -1
  257. package/dist/src/runner.js +1 -1
  258. package/dist/src/runtime-admission.js +1 -1
  259. package/dist/src/runtime-transport-hints.js +1 -1
  260. package/dist/src/runtime.js +1 -1
  261. package/dist/src/runtime10.js +2 -2
  262. package/dist/src/runtime2.js +3 -3
  263. package/dist/src/runtime3.js +1 -1
  264. package/dist/src/runtime4.js +5 -5
  265. package/dist/src/runtime5.js +1 -1
  266. package/dist/src/runtime6.js +1 -1
  267. package/dist/src/runtime7.js +1 -1
  268. package/dist/src/runtime8.js +1 -1
  269. package/dist/src/runtime9.js +1 -1
  270. package/dist/src/screen-recording-admission-ledger.js +1 -1
  271. package/dist/src/screen-recording-completion.js +1 -1
  272. package/dist/src/screen-recording-options.js +1 -1
  273. package/dist/src/screen-recording-resource-recovery.js +1 -1
  274. package/dist/src/screen-recording-transport.js +1 -1
  275. package/dist/src/screenshot-density.js +1 -1
  276. package/dist/src/screenshot-image.js +1 -1
  277. package/dist/src/screenshot-result.js +1 -1
  278. package/dist/src/screenshot-runtime.js +1 -1
  279. package/dist/src/screenshot.js +1 -1
  280. package/dist/src/screenshot3.js +1 -1
  281. package/dist/src/script.js +1 -1
  282. package/dist/src/scroll-command.js +1 -1
  283. package/dist/src/scroll-gesture.js +1 -1
  284. package/dist/src/sdk-batch-runner.js +2 -2
  285. package/dist/src/sdk-batch.js +2 -2
  286. package/dist/src/sdk-contracts.d.ts +45 -2
  287. package/dist/src/sdk-contracts.js +2 -2
  288. package/dist/src/sdk-device.js +1 -1
  289. package/dist/src/sdk-io.js +1 -1
  290. package/dist/src/sdk-remote-config.js +1 -1
  291. package/dist/src/sdk-selectors.d.ts +33 -4
  292. package/dist/src/selection.js +0 -0
  293. package/dist/src/selector-runtime.js +1 -1
  294. package/dist/src/selectors.d.ts +2 -2
  295. package/dist/src/selectors.js +1 -1
  296. package/dist/src/server.js +1 -1
  297. package/dist/src/session-allocation.js +1 -1
  298. package/dist/src/session-artifact-paths.js +1 -1
  299. package/dist/src/session-repair-tombstone.js +1 -1
  300. package/dist/src/session-routing.js +1 -1
  301. package/dist/src/session-runtime-admission.js +1 -1
  302. package/dist/src/session-snapshot.js +1 -1
  303. package/dist/src/session-stream.js +27 -0
  304. package/dist/src/session.js +1 -1
  305. package/dist/src/session2.js +5 -5
  306. package/dist/src/settings.js +1 -1
  307. package/dist/src/settings2.js +1 -1
  308. package/dist/src/simctl-settings-facade.js +1 -0
  309. package/dist/src/simctl-settings.js +1 -0
  310. package/dist/src/simctl.js +1 -1
  311. package/dist/src/simulator.js +1 -1
  312. package/dist/src/snapshot-diagnostics.js +1 -1
  313. package/dist/src/snapshot-runtime-binding.js +1 -1
  314. package/dist/src/snapshot-runtime.js +1 -1
  315. package/dist/src/snapshot-state.js +1 -1
  316. package/dist/src/snapshot2.js +1 -1
  317. package/dist/src/snapshot3.js +1 -1
  318. package/dist/src/snapshot4.js +1 -1
  319. package/dist/src/src.js +1 -1
  320. package/dist/src/src7.js +1 -1
  321. package/dist/src/src8.js +2 -2
  322. package/dist/src/store.js +1 -0
  323. package/dist/src/string-enum.js +1 -1
  324. package/dist/src/surface-snapshot.js +1 -1
  325. package/dist/src/takeover.js +1 -1
  326. package/dist/src/target-evidence.js +1 -1
  327. package/dist/src/telemetry.js +1 -1
  328. package/dist/src/text-entry-focus.js +1 -1
  329. package/dist/src/tool-provider.js +1 -1
  330. package/dist/src/tool-provider2.js +1 -1
  331. package/dist/src/toolchain-probe.js +2 -2
  332. package/dist/src/touch-runtime.js +1 -1
  333. package/dist/src/touch-runtime2.js +1 -1
  334. package/dist/src/transport.js +2 -2
  335. package/dist/src/tv-remote.js +1 -1
  336. package/dist/src/type-text-runtime.js +1 -1
  337. package/dist/src/update-check.js +1 -1
  338. package/dist/src/vars.js +1 -1
  339. package/dist/src/verified-file.js +1 -1
  340. package/dist/src/version.js +1 -1
  341. package/dist/src/video.js +1 -1
  342. package/dist/src/viewport.js +1 -1
  343. package/dist/src/web.js +1 -1
  344. package/dist/src/web2.js +1 -1
  345. package/dist/src/webdriver-source.js +1 -1
  346. package/package.json +5 -1
  347. package/server.json +2 -2
  348. package/android/ime-helper/dist/agent-device-android-ime-helper-0.21.20.apk.sha256 +0 -1
  349. package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.21.20.apk +0 -0
  350. package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.21.20.apk.sha256 +0 -1
  351. package/dist/src/api.js +0 -3
  352. package/dist/src/daemon-process.js +0 -1
  353. package/dist/src/multitouch-support.js +0 -1
  354. package/dist/src/parameterized-recorded-fill.js +0 -1
  355. package/dist/src/runner-startup-transport.js +0 -1
  356. package/dist/src/runner-xctestrun.js +0 -1
  357. package/dist/src/session-store.js +0 -4
@@ -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
 
@@ -5,7 +5,7 @@ final class TextInputProbeIssues {
5
5
  var count = 0
6
6
  }
7
7
 
8
- enum TextInputProbeFailure: String {
8
+ enum TextInputProbeFailure: String, Error {
9
9
  case recordedIssue = "text_input_probe_recorded_issue"
10
10
  case exception = "text_input_probe_exception"
11
11
  }
@@ -38,7 +38,60 @@ extension RunnerTests {
38
38
  }
39
39
  }
40
40
 
41
+
42
+
43
+ func coordinateTapTextInputIdentityAt(app: XCUIApplication, x: Double, y: Double) -> TextInputAtPoint? {
44
+ let probed = containingTextInputProbeIssues(fallback: nil) { shouldStop -> TextInputAtPoint? in
45
+ guard let element = queryTextInputs(app: app, point: CGPoint(x: x, y: y), shouldStop: shouldStop).first,
46
+ case .input(let identity) = probeTextEntryInput(element)
47
+ else {
48
+ return nil
49
+ }
50
+ return TextInputAtPoint(element: element, identity: identity)
51
+ }
52
+ guard case .success(let input) = probed else { return nil }
53
+ return input
54
+ }
55
+
56
+
57
+
58
+ func textInputStillResolves(_ input: TextInputAtPoint) -> Bool {
59
+ let probed = containingTextInputProbeIssues(fallback: false) { _ in
60
+ input.element.exists && probeTextEntryInput(input.element) == .input(input.identity)
61
+ }
62
+ guard case .success(let resolves) = probed else { return false }
63
+ return resolves
64
+ }
65
+
66
+
67
+
68
+ func clearedFieldRead(app: XCUIApplication, target: TextEntryTarget) -> ClearedFieldRead {
69
+ let probed = containingTextInputProbeIssues(fallback: ClearedFieldRead.unavailable) { _ in
70
+ guard let input = resolveTextEntryElement(app: app, target: target) else { return .unavailable }
71
+ if input.elementType == .secureTextField { return .unreadable }
72
+ return editableTextValue(for: input, treatingPlaceholderAsEmpty: true).map { .text($0) } ?? .unavailable
73
+ }
74
+ guard case .success(let read) = probed else { return .unavailable }
75
+ return read
76
+ }
77
+
41
78
  func probeTextInputs(app: XCUIApplication, point: CGPoint) -> TextInputProbeOutcome {
79
+ switch containingTextInputProbeIssues(fallback: [], { shouldStop in
80
+ queryTextInputs(app: app, point: point, shouldStop: shouldStop)
81
+ }) {
82
+ case .success(let elements):
83
+ return elements.isEmpty ? .absent : .matches(elements)
84
+ case .failure(let failure):
85
+ return .unavailable(failure)
86
+ }
87
+ }
88
+
89
+
90
+
91
+ private func containingTextInputProbeIssues<T>(
92
+ fallback: T,
93
+ _ probe: (_ shouldStop: () -> Bool) -> T
94
+ ) -> Result<T, TextInputProbeFailure> {
42
95
  precondition(Thread.isMainThread)
43
96
  let issues = TextInputProbeIssues()
44
97
  suppressedIssueLock.lock()
@@ -50,12 +103,12 @@ extension RunnerTests {
50
103
  textInputProbeIssues = previous
51
104
  suppressedIssueLock.unlock()
52
105
  }
53
- let (elements, exception) = catchingObjCException(fallback: []) {
54
- queryTextInputs(app: app, point: point, shouldStop: { self.hasTextInputProbeIssues(issues) })
106
+ let (value, exception) = catchingObjCException(fallback: fallback) {
107
+ probe({ self.hasTextInputProbeIssues(issues) })
55
108
  }
56
- if hasTextInputProbeIssues(issues) { return .unavailable(.recordedIssue) }
57
- if exception != nil { return .unavailable(.exception) }
58
- return elements.isEmpty ? .absent : .matches(elements)
109
+ if hasTextInputProbeIssues(issues) { return .failure(.recordedIssue) }
110
+ if exception != nil { return .failure(.exception) }
111
+ return .success(value)
59
112
  }
60
113
 
61
114
  private func hasTextInputProbeIssues(_ scope: TextInputProbeIssues) -> Bool {