agent-device 0.21.0 → 0.21.1

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 (282) hide show
  1. package/android/ime-helper/dist/{agent-device-android-ime-helper-0.21.0.apk → agent-device-android-ime-helper-0.21.1.apk} +0 -0
  2. package/android/ime-helper/dist/agent-device-android-ime-helper-0.21.1.apk.sha256 +1 -0
  3. package/android/ime-helper/dist/{agent-device-android-ime-helper-0.21.0.manifest.json → agent-device-android-ime-helper-0.21.1.manifest.json} +4 -4
  4. package/android/snapshot-helper/dist/{agent-device-android-snapshot-helper-0.21.0.apk → agent-device-android-snapshot-helper-0.21.1.apk} +0 -0
  5. package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.21.1.apk.sha256 +1 -0
  6. package/android/snapshot-helper/dist/{agent-device-android-snapshot-helper-0.21.0.manifest.json → agent-device-android-snapshot-helper-0.21.1.manifest.json} +6 -6
  7. package/apple/snapshot-bridge/README.md +58 -1
  8. package/apple/snapshot-bridge/SnapshotBridge.m +6 -0
  9. package/apple/snapshot-bridge/SnapshotBridgeCapture.h +25 -0
  10. package/apple/snapshot-bridge/SnapshotBridgeCapture.m +138 -0
  11. package/apple/snapshot-bridge/SnapshotBridgeRuntime.h +1 -0
  12. package/apple/snapshot-bridge/SnapshotBridgeRuntime.m +32 -3
  13. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerAXSnapshotBridge.h +12 -0
  14. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerAXSnapshotBridge.m +20 -0
  15. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerSelectorMatchPolicy.swift +15 -15
  16. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerSystemSurfaceHostPolicy.swift +72 -0
  17. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTapPointPolicy.swift +63 -14
  18. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+AXSnapshotFallback.swift +668 -82
  19. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Alert.swift +14 -4
  20. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+BlockingSystemModalResolution.swift +39 -4
  21. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandExecution.swift +1065 -223
  22. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandJournal.swift +290 -0
  23. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Environment.swift +1 -1
  24. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Exceptions.swift +10 -10
  25. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Interaction.swift +249 -61
  26. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Keyboard.swift +101 -42
  27. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Lifecycle.swift +13 -13
  28. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Models.swift +51 -43
  29. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Navigation.swift +71 -4
  30. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+PrivateAXPresentation.swift +144 -7
  31. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+ScreenRecorder.swift +13 -1
  32. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+ScrollGesture.swift +216 -17
  33. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SequenceExecution.swift +206 -22
  34. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Snapshot.swift +289 -42
  35. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SnapshotBackendCapabilities.swift +92 -22
  36. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SnapshotCapturePlan.swift +617 -122
  37. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SnapshotTiming.swift +79 -4
  38. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SynthesizedCommitDeadline.swift +71 -71
  39. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SynthesizedGesturePolicy.swift +83 -5
  40. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SynthesizedTextEntry.swift +67 -67
  41. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SystemModal.swift +4 -4
  42. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextEntry.swift +17 -17
  43. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextEntryFocus.swift +30 -30
  44. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextEntryReadiness.swift +31 -31
  45. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextInputCandidatePolicy.swift +28 -0
  46. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextInputProbe.swift +101 -0
  47. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextTyping.swift +19 -19
  48. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Transport.swift +55 -17
  49. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TvRemote.swift +26 -5
  50. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests.swift +125 -75
  51. package/dist/apple/runner/AgentDeviceRunner/RecordingScripts/RecordingExportSupport.swift +9 -9
  52. package/dist/apple/runner/AgentDeviceRunner/RecordingScripts/recording-overlay.swift +9 -9
  53. package/dist/apple/runner/AgentDeviceRunner/RecordingScripts/recording-trim.swift +4 -4
  54. package/dist/src/adapter.js +1 -1
  55. package/dist/src/adb-executor.js +1 -1
  56. package/dist/src/adb-failure.js +1 -1
  57. package/dist/src/agent-browser-process-record.js +1 -1
  58. package/dist/src/agent-browser-provider.js +1 -1
  59. package/dist/src/agent-browser-tool.js +1 -1
  60. package/dist/src/agent-device-client.js +2 -2
  61. package/dist/src/ai-sdk.d.ts +1 -1
  62. package/dist/src/ai-sdk.js +1 -1
  63. package/dist/src/alert-runtime.js +1 -1
  64. package/dist/src/android-input-ownership.js +1 -1
  65. package/dist/src/app-catalog.js +1 -1
  66. package/dist/src/app-deployment-resolution.js +1 -1
  67. package/dist/src/app-inventory-contract.js +1 -1
  68. package/dist/src/app-inventory.js +1 -1
  69. package/dist/src/app-launch.js +2 -2
  70. package/dist/src/app-lifecycle.js +3 -3
  71. package/dist/src/app-lifecycle2.js +1 -1
  72. package/dist/src/app-log-files.js +1 -1
  73. package/dist/src/app-log-runtime2.js +3 -3
  74. package/dist/src/app-resolution.js +1 -1
  75. package/dist/src/apple-runner-platform.js +1 -0
  76. package/dist/src/application-lifecycle-interaction.js +1 -1
  77. package/dist/src/archive.js +1 -1
  78. package/dist/src/artifact-download.js +1 -1
  79. package/dist/src/auth-session.js +1 -1
  80. package/dist/src/auth.js +1 -1
  81. package/dist/src/backend-snapshot-options.js +1 -1
  82. package/dist/src/boot-diagnostics.js +1 -1
  83. package/dist/src/capture.js +1 -1
  84. package/dist/src/cli-config.js +1 -1
  85. package/dist/src/cli-help.js +25 -21
  86. package/dist/src/cli-runner.js +1 -1
  87. package/dist/src/cli.js +4 -4
  88. package/dist/src/click-button.js +1 -1
  89. package/dist/src/client-metro-companion.js +1 -1
  90. package/dist/src/client-metro.js +1 -1
  91. package/dist/src/client-types.d.ts +27 -13
  92. package/dist/src/code-signature-cache.js +1 -1
  93. package/dist/src/code-signature.js +1 -1
  94. package/dist/src/command-schema.js +3 -3
  95. package/dist/src/command-tools.js +2 -2
  96. package/dist/src/command.js +1 -1
  97. package/dist/src/connection-runtime.js +1 -1
  98. package/dist/src/connection.js +3 -3
  99. package/dist/src/contracts.d.ts +1 -1
  100. package/dist/src/contracts.js +1 -1
  101. package/dist/src/daemon-client-lifecycle.js +1 -1
  102. package/dist/src/daemon.js +1 -1
  103. package/dist/src/debug-symbols-facade.js +1 -1
  104. package/dist/src/device-claim-rule.js +1 -1
  105. package/dist/src/device-inventory-context.js +1 -1
  106. package/dist/src/device-rotation.js +1 -1
  107. package/dist/src/device-selection-resolver.js +1 -1
  108. package/dist/src/device-session.js +1 -1
  109. package/dist/src/device2.js +1 -1
  110. package/dist/src/diagnostics.js +1 -1
  111. package/dist/src/dispatch-resolve.js +1 -1
  112. package/dist/src/doctor2.js +1 -1
  113. package/dist/src/error.js +1 -1
  114. package/dist/src/exec.js +1 -1
  115. package/dist/src/file.js +1 -1
  116. package/dist/src/find.js +1 -1
  117. package/dist/src/generic-settle.js +1 -1
  118. package/dist/src/gesture-plan.js +1 -1
  119. package/dist/src/gesture-runtime.js +1 -1
  120. package/dist/src/harmonyos.js +1 -1
  121. package/dist/src/hdc.js +1 -1
  122. package/dist/src/helper.js +2 -2
  123. package/dist/src/human-control-contract.js +1 -1
  124. package/dist/src/human-control.js +1 -1
  125. package/dist/src/index.d.ts +1 -1
  126. package/dist/src/index.js +1 -1
  127. package/dist/src/input-actions.js +1 -1
  128. package/dist/src/input-actions2.js +2 -2
  129. package/dist/src/install-artifact-facade.js +1 -1
  130. package/dist/src/install-source-network-transport.js +1 -1
  131. package/dist/src/install-source2.js +1 -1
  132. package/dist/src/interaction-positionals.js +1 -1
  133. package/dist/src/interaction-ref-policy.js +1 -1
  134. package/dist/src/interaction2.js +1 -1
  135. package/dist/src/interactor.js +5 -5
  136. package/dist/src/interactor2.js +1 -1
  137. package/dist/src/interactors.js +1 -1
  138. package/dist/src/internal/bin.js +1 -1
  139. package/dist/src/internal/daemon.js +2 -2
  140. package/dist/src/internal/png-worker.js +1 -1
  141. package/dist/src/internal/run-script-http-child.js +1 -1
  142. package/dist/src/inventory.js +2 -2
  143. package/dist/src/inventory2.js +1 -1
  144. package/dist/src/inventory3.js +1 -1
  145. package/dist/src/inventory5.js +1 -1
  146. package/dist/src/ios-snapshot-runtime.js +1 -1
  147. package/dist/src/ios-system-surface.js +1 -0
  148. package/dist/src/lease-scope.js +1 -1
  149. package/dist/src/lease.js +1 -1
  150. package/dist/src/limrun-runtime-dependencies.js +1 -1
  151. package/dist/src/limrun.d.ts +1 -1
  152. package/dist/src/linux.js +1 -1
  153. package/dist/src/location-coordinates.js +1 -1
  154. package/dist/src/managed-device-scope.js +1 -1
  155. package/dist/src/mechanics.js +16 -16
  156. package/dist/src/multitouch-support.js +1 -1
  157. package/dist/src/observation.js +1 -1
  158. package/dist/src/open-target-resolution.js +1 -0
  159. package/dist/src/owner-identity.js +3 -3
  160. package/dist/src/parameterized-recorded-fill.js +1 -1
  161. package/dist/src/perf-facade.js +2 -2
  162. package/dist/src/perf-process-identity.js +1 -0
  163. package/dist/src/perf-runtime-operation-builder.js +1 -1
  164. package/dist/src/perf-runtime-plan.js +1 -1
  165. package/dist/src/perf.js +1 -1
  166. package/dist/src/physical-device-control.js +1 -1
  167. package/dist/src/platform-runtime-android-adb-host.js +1 -1
  168. package/dist/src/platform-runtime-app-log-process.js +1 -1
  169. package/dist/src/platform-runtime-host.js +1 -1
  170. package/dist/src/platform-runtime-managed-owner.js +1 -1
  171. package/dist/src/platform-runtime-open-target.js +1 -1
  172. package/dist/src/platform-runtime-operation-host.js +2 -2
  173. package/dist/src/platform-runtime-runtime-hints.js +2 -2
  174. package/dist/src/platform-runtime-screen-recording-android-host.js +1 -1
  175. package/dist/src/platform-runtime-screen-recording-apple-simulator-host.js +1 -1
  176. package/dist/src/platform-runtime-screen-recording-finalizer-host.js +1 -1
  177. package/dist/src/platform-runtime-unavailable.js +1 -1
  178. package/dist/src/platform-runtime.js +1 -1
  179. package/dist/src/platform-runtime2.js +1 -1
  180. package/dist/src/png-worker-contract.js +1 -1
  181. package/dist/src/prepare-kind.js +1 -1
  182. package/dist/src/provider-device-runtime.js +1 -1
  183. package/dist/src/provider-session-ownership.js +1 -1
  184. package/dist/src/provider-webdriver.js +1 -1
  185. package/dist/src/proxy.js +1 -1
  186. package/dist/src/react-native.js +1 -1
  187. package/dist/src/record-runtime.js +1 -1
  188. package/dist/src/rect-center.js +1 -1
  189. package/dist/src/register-builtins.js +1 -1
  190. package/dist/src/registry.js +37 -36
  191. package/dist/src/remote-config2.js +1 -1
  192. package/dist/src/replay-divergence.js +1 -1
  193. package/dist/src/replay.js +2 -2
  194. package/dist/src/reporting.js +9 -8
  195. package/dist/src/request-admission.js +1 -1
  196. package/dist/src/request-cancel.js +1 -1
  197. package/dist/src/request-runtime-binding.js +1 -1
  198. package/dist/src/retry.js +1 -1
  199. package/dist/src/runner-cache-metadata.js +2 -2
  200. package/dist/src/runner-client.js +1 -1
  201. package/dist/src/runner-disposal.js +1 -1
  202. package/dist/src/runner-operations-facade.js +1 -1
  203. package/dist/src/runner.js +1 -1
  204. package/dist/src/runtime-transport-hints.js +1 -1
  205. package/dist/src/runtime.js +3 -3
  206. package/dist/src/runtime2.js +4 -4
  207. package/dist/src/runtime3.js +1 -1
  208. package/dist/src/runtime4.js +5 -4
  209. package/dist/src/runtime5.js +1 -1
  210. package/dist/src/runtime6.js +1 -1
  211. package/dist/src/runtime7.js +1 -1
  212. package/dist/src/runtime8.js +1 -1
  213. package/dist/src/runtime9.js +1 -1
  214. package/dist/src/screen-recording-live-handle.js +1 -1
  215. package/dist/src/screen-recording-options.js +1 -1
  216. package/dist/src/screen-recording-resource-recovery.js +1 -1
  217. package/dist/src/screenshot-density.js +1 -1
  218. package/dist/src/screenshot-runtime.js +1 -1
  219. package/dist/src/screenshot.js +1 -1
  220. package/dist/src/screenshot3.js +3 -3
  221. package/dist/src/script.js +1 -1
  222. package/dist/src/scroll-command.js +1 -1
  223. package/dist/src/scroll-gesture.js +1 -1
  224. package/dist/src/sdk-batch-runner.js +2 -2
  225. package/dist/src/sdk-batch.js +2 -2
  226. package/dist/src/sdk-contracts.d.ts +44 -1
  227. package/dist/src/sdk-contracts.js +2 -2
  228. package/dist/src/sdk-device.js +1 -1
  229. package/dist/src/sdk-finders.d.ts +1 -1
  230. package/dist/src/sdk-io.js +1 -1
  231. package/dist/src/sdk-remote-config.d.ts +1 -1
  232. package/dist/src/sdk-remote-config.js +1 -1
  233. package/dist/src/sdk-selectors.d.ts +69 -13
  234. package/dist/src/selector-runtime.js +1 -1
  235. package/dist/src/server.js +3 -3
  236. package/dist/src/session-allocation.js +1 -1
  237. package/dist/src/session-observation.js +1 -0
  238. package/dist/src/session-routing.js +1 -1
  239. package/dist/src/session-runtime-admission.js +1 -1
  240. package/dist/src/session-snapshot.js +1 -1
  241. package/dist/src/session-store.js +3 -3
  242. package/dist/src/session-target-evidence.js +1 -1
  243. package/dist/src/session2.js +5 -5
  244. package/dist/src/settings.js +1 -1
  245. package/dist/src/simulator.js +1 -1
  246. package/dist/src/snapshot-quality-backend-capabilities.js +1 -1
  247. package/dist/src/snapshot-quality-verdict.js +1 -1
  248. package/dist/src/snapshot-runtime-binding.js +1 -1
  249. package/dist/src/snapshot-runtime.js +1 -1
  250. package/dist/src/snapshot-state.js +1 -1
  251. package/dist/src/snapshot2.js +1 -1
  252. package/dist/src/snapshot3.js +1 -1
  253. package/dist/src/snapshot4.js +1 -1
  254. package/dist/src/src.js +1 -1
  255. package/dist/src/src7.js +2 -2
  256. package/dist/src/src8.js +2 -2
  257. package/dist/src/string-enum.js +1 -1
  258. package/dist/src/takeover.js +1 -1
  259. package/dist/src/tool-provider.js +2 -2
  260. package/dist/src/tool-provider2.js +1 -1
  261. package/dist/src/tool-provider3.js +1 -1
  262. package/dist/src/toolchain-probe.js +1 -1
  263. package/dist/src/touch-reference-frame.js +1 -0
  264. package/dist/src/touch-runtime.js +1 -1
  265. package/dist/src/transport.js +1 -1
  266. package/dist/src/tv-remote.js +1 -1
  267. package/dist/src/type-text-runtime.js +1 -1
  268. package/dist/src/update-check.js +1 -1
  269. package/dist/src/verified-file.js +1 -1
  270. package/dist/src/video.js +1 -1
  271. package/dist/src/web.js +1 -1
  272. package/dist/src/web2.js +1 -1
  273. package/dist/src/webdriver-source.js +1 -1
  274. package/dist/src/window-state.js +1 -0
  275. package/package.json +3 -2
  276. package/server.json +2 -2
  277. package/android/ime-helper/dist/agent-device-android-ime-helper-0.21.0.apk.sha256 +0 -1
  278. package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.21.0.apk.sha256 +0 -1
  279. package/dist/src/back-mode.js +0 -1
  280. package/dist/src/format.js +0 -2
  281. package/dist/src/platform-runtime-apple-resources.js +0 -1
  282. package/dist/src/runner-selector-query.js +0 -1
@@ -1,8 +1,8 @@
1
1
  import XCTest
2
2
 
3
- // Text entry & keyboard-readiness for the runner: the focus -> type -> verify -> repair
4
- // pipeline, readiness polling, and field clearing. Behavior-preserving extraction from
5
- // RunnerTests+Interaction.swift (no logic changes) to keep that file navigable.
3
+
4
+
5
+
6
6
  extension RunnerTests {
7
7
  enum TextEntryFailure: String {
8
8
  case notFocused = "TEXT_INPUT_NOT_FOCUSED"
@@ -45,13 +45,13 @@ extension RunnerTests {
45
45
  static let pollInterval: TimeInterval = 0.02
46
46
  static let warmupValueTimeout: TimeInterval = 0.4
47
47
  static let verificationStabilityWindow: TimeInterval = 0.2
48
- /// How long the commit wait tolerates seeing NO further progress toward the expected value.
49
- /// Numerically the flat deadline this replaced, so a pipeline that delivers nothing is
50
- /// condemned at exactly the same instant it always was (see `SynthesizedCommitDeadline`).
48
+
49
+
50
+
51
51
  static let synthesizedCommitStallTimeout: TimeInterval = 3.0
52
- /// The commit wait's absolute bound, however long characters keep arriving. Sits well inside
53
- /// the daemon's per-command budget (`RUNNER_COMMAND_TIMEOUT_MS`, 45s), which also has to cover
54
- /// focus, clear and verification around this wait.
52
+
53
+
54
+
55
55
  static let synthesizedCommitCeiling: TimeInterval = 10.0
56
56
  static let synthesizedCommitPollInterval: TimeInterval = 0.2
57
57
  }
@@ -114,14 +114,14 @@ extension RunnerTests {
114
114
  }
115
115
 
116
116
  func clearTextInput(_ element: XCUIElement) {
117
- // Skip the clear (delete burst + moveCaretToEnd edge-tap) ONLY when we can confirm the
118
- // field is empty. Why skip: the edge-tap computes a point from the element frame, which can
119
- // be stale after the field repositions on focus (e.g. the Settings search bar jumps
120
- // bottom->top and reveals a "Suggestions" list) — tapping there navigates away instead of
121
- // clearing; and replacing into an already-empty field is a no-op anyway.
122
- // editableTextValue returns nil for secure (and unknown) fields, where we CANNOT confirm
123
- // emptiness — those must still be cleared, or replace would concatenate stale + new text.
124
- // So distinguish nil (clear) from "" (skip).
117
+
118
+
119
+
120
+
121
+
122
+
123
+
124
+
125
125
  if let existing = editableTextValue(for: element, treatingPlaceholderAsEmpty: true),
126
126
  existing.isEmpty {
127
127
  return
@@ -1,10 +1,10 @@
1
1
  import XCTest
2
2
 
3
- // Text-entry target acquisition: choosing the element a `type`/`fill` will address and getting
4
- // focus onto it — the one-shot tap witness, the post-tap stabilization, and the two
5
- // `focusTextInputForTextEntry` entry points the command layer calls. Whether that target is ready
6
- // is RunnerTests+TextEntryReadiness.swift's question, and this file asks it rather than answering
7
- // it.
3
+
4
+
5
+
6
+
7
+
8
8
  extension RunnerTests {
9
9
  func rememberTextEntryTap(_ element: XCUIElement?) {
10
10
  guard let element, isTextEntryElement(element) else {
@@ -26,8 +26,8 @@ extension RunnerTests {
26
26
  guard let witness = textEntryTapWitness else {
27
27
  return nil
28
28
  }
29
- // The tap is proof for one immediately-following bare type only. Consume it before checking
30
- // the element so a failed or interrupted type cannot reuse stale focus evidence.
29
+
30
+
31
31
  clearRememberedTextEntryTap()
32
32
  guard witness.matches(
33
33
  bundleId: currentBundleId,
@@ -36,16 +36,16 @@ extension RunnerTests {
36
36
  return nil
37
37
  }
38
38
  let element = witness.element
39
- // XCUIElement is query-backed rather than a stable node identity. A same-identifier field
40
- // introduced by app-side navigation between tap and this immediate type can therefore
41
- // re-resolve here; keep the witness one-shot and fail closed on every observable identity
42
- // boundary instead of using frame equality, which would reject legitimate layout changes.
39
+
40
+
41
+
42
+
43
43
  guard safely("LAST_TAPPED_TEXT_INPUT_EXISTS", false, { element.exists }) else {
44
44
  return nil
45
45
  }
46
- // Keep the target scoped to the element that the preceding tap actually selected. Do not
47
- // attach a refresh point: if that element disappeared, bare type must fail closed rather
48
- // than rediscovering a different field or dispatching unscoped app.typeText.
46
+
47
+
48
+
49
49
  return TextEntryTarget(
50
50
  element: element,
51
51
  refreshPoint: nil,
@@ -69,8 +69,8 @@ extension RunnerTests {
69
69
  if let focused = focusedTextInput(app: app) {
70
70
  return TextEntryStabilization(element: focused, focusConfirmed: true)
71
71
  }
72
- // focusedTextInput is intentionally nil on iOS; treat the keyboard transitioning to
73
- // visible after our tap as the focus-moved signal. Don't fast-path when it was already up.
72
+
73
+
74
74
  if keyboardBecameVisible(app: app, wasVisibleAtEntry: keyboardVisibleAtEntry) {
75
75
  return TextEntryStabilization(element: latest, focusConfirmed: true)
76
76
  }
@@ -86,10 +86,10 @@ extension RunnerTests {
86
86
  if !softwareKeyboardVisible, let rememberedTarget = rememberedTextEntryTarget() {
87
87
  return rememberedTarget
88
88
  }
89
- // Bare `type` targets the current first responder. On iOS we intentionally do not trust
90
- // `hasKeyboardFocus`, but an already-visible software keyboard is sufficient evidence that
91
- // app.typeText has a receiver; waiting the full readiness timeout cannot prove a stronger
92
- // target because there is no selector/coordinate focus move to validate.
89
+
90
+
91
+
92
+
93
93
  if softwareKeyboardVisible {
94
94
  return TextEntryTarget(
95
95
  element: focusedTextInput(app: app),
@@ -121,9 +121,9 @@ extension RunnerTests {
121
121
  } else {
122
122
  _ = tapAt(app: app, x: x, y: y)
123
123
  }
124
- // A visible keyboard is not enough evidence for app.typeText, because focus may still
125
- // belong to a previous field. With a concrete target we type through XCUIElement.typeText,
126
- // so after tapping it the iOS readiness timeout cannot prove anything stronger.
124
+
125
+
126
+
127
127
  if keyboardVisibleBeforeTap, let target {
128
128
  return TextEntryTarget(
129
129
  element: target,
@@ -158,8 +158,8 @@ extension RunnerTests {
158
158
  if let point {
159
159
  _ = tapAt(app: app, x: point.x, y: point.y)
160
160
  }
161
- // See the coordinate-target path above: direct element typing keeps this scoped to the
162
- // tapped target, while the first-character warmup and final verify still catch dropped input.
161
+
162
+
163
163
  if keyboardVisibleBeforeTap {
164
164
  return TextEntryTarget(
165
165
  element: element,
@@ -198,9 +198,9 @@ extension RunnerTests {
198
198
  return CGPoint(x: frame.midX, y: frame.midY)
199
199
  }
200
200
 
201
- /// A focus-moved signal for iOS text entry, where `focusedTextInput` is intentionally nil.
202
- /// The software keyboard TRANSITIONING from hidden (at entry) to visible means the field we
203
- /// just tapped gained first-responder. If the keyboard was ALREADY up (e.g. back-to-back
204
- /// fills into different fields), its visibility is not evidence focus moved to the new field,
205
- /// so callers must keep waiting rather than typing into the previously-focused field.
201
+
202
+
203
+
204
+
205
+
206
206
  }
@@ -1,14 +1,14 @@
1
1
  import XCTest
2
2
 
3
- // Text-entry readiness: whether the target is actually able to receive text yet. The waits, the
4
- // keyboard signals they read, and the focus corroboration that ends the hardware-keyboard window.
5
- // RunnerTests+TextEntryFocus.swift is the only caller of the two non-private entries here; nothing
6
- // in this file reaches back into it.
3
+
4
+
5
+
6
+
7
7
  extension RunnerTests {
8
8
  func focusedTextInput(app: XCUIApplication) -> XCUIElement? {
9
9
  #if os(iOS)
10
- // iOS focus predicates can return stale or misleading text-input matches
11
- // under XCUITest, so text entry readiness is driven by tap/keyboard state.
10
+
11
+
12
12
  return nil
13
13
  #else
14
14
  return safely("FOCUSED_INPUT_QUERY") {
@@ -49,21 +49,21 @@ extension RunnerTests {
49
49
  return focused
50
50
  }
51
51
  }
52
- // Fast-path on a keyboard hidden->visible transition: our tapped field gained focus, so
53
- // return immediately instead of burning the full readinessTimeout (warmup-first-char echo
54
- // + post-type verify/repair remain as drop safety nets). When the keyboard was ALREADY up
55
- // (back-to-back fills), this isn't a focus signal — fall through to the settle/timeout so
56
- // text isn't sent to the previously-focused field.
52
+
53
+
54
+
55
+
56
+
57
57
  if keyboardBecameVisible(app: app, wasVisibleAtEntry: keyboardVisibleAtEntry) {
58
58
  return latest
59
59
  }
60
60
  sawSoftwareKeyboard = sawSoftwareKeyboard || keyboardElementExists(app: app)
61
- // A responder that takes no software keyboard (hardware keyboard connected, or a custom
62
- // `inputView`) would otherwise burn the whole readinessTimeout waiting for one that is never
63
- // coming. Leaving that window a bare wall-clock guess made readiness a function of ambient
64
- // simulator state (#1874): on a loaded host the keyboard is merely late, and returning here
65
- // handed the caller an element that had not taken focus yet. Ask the target itself instead,
66
- // and re-arm rather than re-asking every poll — the query is cheap, not free.
61
+
62
+
63
+
64
+
65
+
66
+
67
67
  if !sawSoftwareKeyboard, Date() >= hardwareKeyboardFallback, let candidate = latest {
68
68
  if keyboardFocusConfirmed(app: app, element: candidate) {
69
69
  return candidate
@@ -113,23 +113,23 @@ extension RunnerTests {
113
113
  return !wasVisibleAtEntry && isKeyboardVisible(app: app)
114
114
  }
115
115
 
116
- /// Positive evidence that this element — the one readiness is about to hand its caller — holds
117
- /// keyboard focus. Readable even with no software keyboard on screen, which is what lets the
118
- /// hardware-keyboard fallback stop guessing from a wall clock.
119
- ///
120
- /// This is the same app-wide predicate `focusedTextInput` refuses to trust on iOS, used the
121
- /// other way round. There, the query PICKS the target, so a stale or unrelated match becomes
122
- /// the field that gets typed into. Here the target is already chosen and the query only
123
- /// corroborates it: at most one element holds keyboard focus, so an answer that is not this
124
- /// element is a refusal, not a substitution. Every way of being wrong therefore ends as `false`
125
- /// and costs the remaining readiness timeout — exactly what the wait would spend with no
126
- /// fallback at all.
116
+
117
+
118
+
119
+
120
+
121
+
122
+
123
+
124
+
125
+
126
+
127
127
  func keyboardFocusConfirmed(app: XCUIApplication, element: XCUIElement) -> Bool {
128
128
  #if os(iOS)
129
129
  return safely("TEXT_ENTRY_FOCUS_CONFIRMED", false) {
130
- // An element that no longer resolves reads as identifier "" and frame `.zero`, which would
131
- // match any focused element that also reports an empty frame. Require a real frame first,
132
- // so a dead handle cannot corroborate anything.
130
+
131
+
132
+
133
133
  let frame = element.frame
134
134
  guard !frame.isEmpty else {
135
135
  return false
@@ -0,0 +1,101 @@
1
+ import XCTest
2
+
3
+ final class TextInputProbeIssues {
4
+ let thread = Thread.current
5
+ var count = 0
6
+ }
7
+
8
+ enum TextInputProbeFailure: String {
9
+ case recordedIssue = "text_input_probe_recorded_issue"
10
+ case exception = "text_input_probe_exception"
11
+ }
12
+
13
+ enum TextInputProbeOutcome {
14
+ case matches([XCUIElement])
15
+ case absent
16
+ case unavailable(TextInputProbeFailure)
17
+ }
18
+
19
+ extension RunnerTests {
20
+ func textInputAt(app: XCUIApplication, x: Double, y: Double) -> XCUIElement? {
21
+ textInputCandidatesAt(app: app, point: CGPoint(x: x, y: y)).first
22
+ }
23
+
24
+ func textInputCandidatesAt(app: XCUIApplication, point: CGPoint) -> [XCUIElement] {
25
+ safely("TEXT_INPUT_AT_POINT", []) {
26
+ queryTextInputs(app: app, point: point, shouldStop: { false })
27
+ }
28
+ }
29
+
30
+ func coordinateTapTextInputAt(app: XCUIApplication, x: Double, y: Double) -> XCUIElement? {
31
+ switch probeTextInputs(app: app, point: CGPoint(x: x, y: y)) {
32
+ case .matches(let elements):
33
+ return elements.first
34
+ case .absent:
35
+ return nil
36
+ case .unavailable:
37
+ return nil
38
+ }
39
+ }
40
+
41
+ func probeTextInputs(app: XCUIApplication, point: CGPoint) -> TextInputProbeOutcome {
42
+ precondition(Thread.isMainThread)
43
+ let issues = TextInputProbeIssues()
44
+ suppressedIssueLock.lock()
45
+ let previous = textInputProbeIssues
46
+ textInputProbeIssues = issues
47
+ suppressedIssueLock.unlock()
48
+ defer {
49
+ suppressedIssueLock.lock()
50
+ textInputProbeIssues = previous
51
+ suppressedIssueLock.unlock()
52
+ }
53
+ let (elements, exception) = catchingObjCException(fallback: []) {
54
+ queryTextInputs(app: app, point: point, shouldStop: { self.hasTextInputProbeIssues(issues) })
55
+ }
56
+ if hasTextInputProbeIssues(issues) { return .unavailable(.recordedIssue) }
57
+ if exception != nil { return .unavailable(.exception) }
58
+ return elements.isEmpty ? .absent : .matches(elements)
59
+ }
60
+
61
+ private func hasTextInputProbeIssues(_ scope: TextInputProbeIssues) -> Bool {
62
+ suppressedIssueLock.lock()
63
+ defer { suppressedIssueLock.unlock() }
64
+ return scope.count > 0
65
+ }
66
+
67
+ func containTextInputProbeIssue(_ issue: XCTIssue) -> Bool {
68
+ suppressedIssueLock.lock()
69
+ guard let scope = textInputProbeIssues, scope.thread === Thread.current else {
70
+ suppressedIssueLock.unlock()
71
+ return false
72
+ }
73
+ scope.count += 1
74
+ suppressedIssueLock.unlock()
75
+ NSLog("AGENT_DEVICE_RUNNER_TEXT_INPUT_PROBE_UNAVAILABLE issue=%@", issue.compactDescription)
76
+ return true
77
+ }
78
+
79
+ private func queryTextInputs(
80
+ app: XCUIApplication,
81
+ point: CGPoint,
82
+ shouldStop: () -> Bool
83
+ ) -> [XCUIElement] {
84
+ var candidates: [XCUIElement] = []
85
+ for query in [app.textFields, app.secureTextFields, app.searchFields, app.textViews] {
86
+ if shouldStop() { break }
87
+ candidates.append(contentsOf: query.allElementsBoundByIndex)
88
+
89
+
90
+
91
+
92
+
93
+
94
+ }
95
+ guard !shouldStop() else { return [] }
96
+ return candidates.filter { element in
97
+ guard !shouldStop(), element.exists else { return false }
98
+ return isCoordinateTextInputCandidate(enabled: element.isEnabled, frame: element.frame, point: point)
99
+ }.sorted(by: smallestElementFirst)
100
+ }
101
+ }
@@ -1,7 +1,7 @@
1
1
  import XCTest
2
2
 
3
- // Text typing, verification, and repair. Kept separate from focus/readiness so each
4
- // text-entry policy remains a bounded review surface.
3
+
4
+
5
5
  extension RunnerTests {
6
6
  func typeTextReliably(
7
7
  app: XCUIApplication,
@@ -15,15 +15,15 @@ extension RunnerTests {
15
15
  ) -> TextEntryResult {
16
16
  let totalStartedAt = Date()
17
17
  guard !text.isEmpty else {
18
- // Replacing a field WITH the empty string is the clear-field primitive (#2063): the typing
19
- // is vacuous but the clear is not. Returning here unconditionally left the old value in
20
- // place while the response reported "typed". Append (`type`) and unrepaired entry stay
21
- // no-ops, which is what an empty payload means for them.
18
+
19
+
20
+
21
+
22
22
  if repairMode == .replacement {
23
23
  guard let clearTarget = resolveTextEntryElement(app: app, target: target) else {
24
- // No resolvable input means no clear happened — including the synthesized
25
- // first-responder route, whose target carries no element. Success here would claim a
26
- // clear the runner never performed.
24
+
25
+
26
+
27
27
  logTextEntryPhase(commandId: commandId, phase: "total", startedAt: totalStartedAt, chars: 0, mode: repairMode)
28
28
  return TextEntryResult(
29
29
  verified: nil,
@@ -34,8 +34,8 @@ extension RunnerTests {
34
34
  )
35
35
  }
36
36
  clearTextInput(clearTarget)
37
- // nil means the value is unreadable (secure fields), not "not empty": leave `verified`
38
- // nil there rather than reporting a mismatch the runner cannot actually observe.
37
+
38
+
39
39
  let observed = editableTextValue(for: clearTarget, treatingPlaceholderAsEmpty: true)
40
40
  logTextEntryPhase(commandId: commandId, phase: "total", startedAt: totalStartedAt, chars: 0, mode: repairMode)
41
41
  return TextEntryResult(
@@ -95,9 +95,9 @@ extension RunnerTests {
95
95
  mode: repairMode
96
96
  )
97
97
 
98
- // Dispatching text through XCTest without a resolved target or evidence of a focused
99
- // responder records a test failure. That tears down the long-lived runner and turns a
100
- // single invalid request into a restart cascade, so fail before entering that channel.
98
+
99
+
100
+
101
101
  guard initialTarget != nil || (activeTarget.prefersFocusedElement && isKeyboardVisible(app: app)) else {
102
102
  logTextEntryPhase(
103
103
  commandId: commandId,
@@ -145,8 +145,8 @@ extension RunnerTests {
145
145
  let textBefore = editableTextValue(for: currentTarget, treatingPlaceholderAsEmpty: true)
146
146
  switch synthesizer.enterText(app: app, text: value, replacingExistingText: false) {
147
147
  case .continueTyping:
148
- // No refresh point: like the tap-witness target itself, the commit wait must observe
149
- // only the element the tap selected, never rediscover a different field.
148
+
149
+
150
150
  let commit = awaitSynthesizedFirstResponderCommit(
151
151
  app: app,
152
152
  target: TextEntryTarget(
@@ -158,9 +158,9 @@ extension RunnerTests {
158
158
  textBefore: textBefore,
159
159
  typedText: value
160
160
  )
161
- // The characters were posted, so the element stays on the tuple for the caller's
162
- // bookkeeping; the failure is what makes the command refuse. Reporting ok here is the
163
- // defect this route had — the deadline was indistinguishable from a commit.
161
+
162
+
163
+
164
164
  return (currentTarget, Self.textEntryFailure(forCommitOutcome: commit))
165
165
  case .fallback:
166
166
  return (nil, .synthesisUnavailable)
@@ -2,13 +2,13 @@ import XCTest
2
2
  import Network
3
3
 
4
4
  extension RunnerTests {
5
- // MARK: - Connection Lifecycle
5
+
6
6
 
7
7
  func handle(connection: NWConnection) {
8
8
  receiveRequest(connection: connection, buffer: Data())
9
9
  }
10
10
 
11
- // MARK: - Request Parsing
11
+
12
12
 
13
13
  private func receiveRequest(connection: NWConnection, buffer: Data) {
14
14
  connection.receive(minimumIncompleteLength: 1, maximumLength: 1024 * 1024) { [weak self] data, _, _, _ in
@@ -116,9 +116,9 @@ extension RunnerTests {
116
116
  completion((jsonResponse(status: 200, response: executeUptime()), false))
117
117
  return
118
118
  }
119
- // Re-sends of a still-executing commandId (the daemon's transport retry loop) attach to
120
- // the in-flight execution and receive its response instead of piling a second execution
121
- // onto the main queue behind it (#1105 capture pileup).
119
+
120
+
121
+
122
122
  if attachToInFlightCommandIfNeeded(command: command, completion: completion) {
123
123
  return
124
124
  }
@@ -181,11 +181,11 @@ extension RunnerTests {
181
181
  }
182
182
  }
183
183
 
184
- // MARK: - In-Flight Command Coalescing
185
184
 
186
- /// Returns true when this send duplicated a still-executing commandId and was attached as a
187
- /// waiter of the in-flight execution. Otherwise marks the commandId in flight and returns
188
- /// false so the caller enqueues the (single) execution.
185
+
186
+
187
+
188
+
189
189
  private func attachToInFlightCommandIfNeeded(
190
190
  command: Command,
191
191
  completion: @escaping ((data: Data, shouldFinish: Bool)) -> Void
@@ -226,14 +226,52 @@ extension RunnerTests {
226
226
  }
227
227
 
228
228
 
229
- // MARK: - Response Encoding
229
+
230
+
231
+
232
+
233
+
234
+
235
+
236
+
237
+
238
+
239
+
240
+
241
+
242
+
243
+
244
+
245
+
246
+
247
+
248
+
249
+
250
+
251
+
252
+
253
+
254
+
255
+
256
+
257
+
258
+
259
+
260
+
261
+
262
+
263
+
264
+
265
+
266
+
267
+
230
268
 
231
269
  private func jsonResponse(status: Int, response: Response) -> Data {
232
- // Stamp the gesture-clock uptime at the END of command handling, just before the HTTP
233
- // write, so the warm snapshot and recordStart responses carry the anchor for free. This
234
- // runs AFTER commandJournal.finish, so journal-stored lifecycleResponseJson stays
235
- // unstamped — recovered/status-replayed results carry no anchor and the daemon falls back
236
- // rather than pairing a stale uptime with a much-later receipt time.
270
+
271
+
272
+
273
+
274
+
237
275
  let stamped =
238
276
  response.ok
239
277
  ? response.stampingCurrentUptimeMs(ProcessInfo.processInfo.systemUptime * 1000)
@@ -262,8 +300,8 @@ extension RunnerTests {
262
300
  private func finish() {
263
301
  listener?.cancel()
264
302
  listener = nil
265
- // Guard against double-fulfill: coalesced shutdown sends deliver one result to
266
- // multiple waiters, each of which may ask to finish.
303
+
304
+
267
305
  doneExpectation?.fulfill()
268
306
  doneExpectation = nil
269
307
  }
@@ -2,8 +2,8 @@ import XCTest
2
2
 
3
3
  enum RunnerInteractionOutcome {
4
4
  case performed
5
- /// A capability/state gap, surfaced to the caller as an UNSUPPORTED_OPERATION error.
6
- /// `hint` is an optional actionable next step (mapped to ErrorPayload.hint).
5
+
6
+
7
7
  case unsupported(message: String, hint: String?)
8
8
  }
9
9
 
@@ -19,6 +19,27 @@ enum TvRemoteButton: String {
19
19
 
20
20
  extension RunnerTests {
21
21
 
22
+
23
+
24
+
25
+
26
+
27
+
28
+
29
+
30
+
31
+
32
+
33
+
34
+
35
+
36
+
37
+
38
+
39
+
40
+
41
+
42
+
22
43
  func resolveTvRemoteDoublePressDelay() -> TimeInterval {
23
44
  guard
24
45
  let raw = ProcessInfo.processInfo.environment["AGENT_DEVICE_TV_REMOTE_DOUBLE_PRESS_DELAY_MS"],
@@ -71,9 +92,9 @@ extension RunnerTests {
71
92
  #else
72
93
  let frame = element.frame
73
94
  if !frame.isEmpty {
74
- // XCUIElement.tap() can fail the whole XCTest after navigation because it
75
- // re-resolves the tapped element even after the app removed it. Keep the
76
- // selector target semantic, then activate its resolved stable screen point.
95
+
96
+
97
+
77
98
  return tapAt(app: app, x: frame.midX, y: frame.midY)
78
99
  }
79
100
  return performElementTap(element)