agent-device 0.21.13 → 0.21.15

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 (298) hide show
  1. package/android/ime-helper/dist/{agent-device-android-ime-helper-0.21.13.apk → agent-device-android-ime-helper-0.21.15.apk} +0 -0
  2. package/android/ime-helper/dist/agent-device-android-ime-helper-0.21.15.apk.sha256 +1 -0
  3. package/android/ime-helper/dist/{agent-device-android-ime-helper-0.21.13.manifest.json → agent-device-android-ime-helper-0.21.15.manifest.json} +4 -4
  4. package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.21.15.apk +0 -0
  5. package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.21.15.apk.sha256 +1 -0
  6. package/android/snapshot-helper/dist/{agent-device-android-snapshot-helper-0.21.13.manifest.json → agent-device-android-snapshot-helper-0.21.15.manifest.json} +6 -6
  7. package/apple/snapshot-bridge/SnapshotBridgeRuntime.m +11 -1
  8. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunner/AgentDeviceRunnerApp.m +110 -2
  9. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerAXSnapshotBridge.m +4 -2
  10. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerMainOwnedState.swift +27 -0
  11. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerSynthesizedTextEntry.h +8 -2
  12. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerSynthesizedTextEntry.m +26 -1
  13. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+AXSnapshotFallback.swift +12 -19
  14. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Alert.swift +59 -20
  15. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+AlertObservation.swift +2 -3
  16. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandDispatch.swift +179 -91
  17. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandExecution.swift +21 -24
  18. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandJournal.swift +1 -1
  19. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+GestureExecution.swift +9 -13
  20. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Lifecycle.swift +36 -49
  21. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+MainThreadWork.swift +11 -15
  22. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Models.swift +136 -29
  23. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Navigation.swift +20 -21
  24. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+PrivateAXPresentation.swift +3 -0
  25. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+ScreenRecorder.swift +6 -5
  26. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+ScrollDragExecution.swift +12 -24
  27. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SequenceExecution.swift +22 -42
  28. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Snapshot.swift +15 -18
  29. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SnapshotAcquisition.swift +73 -31
  30. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SnapshotCapturePlan.swift +20 -11
  31. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SnapshotCaptureTarget.swift +9 -7
  32. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SnapshotExecution.swift +5 -4
  33. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SnapshotTiming.swift +2 -2
  34. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SynthesizedGesturePolicy.swift +51 -19
  35. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SynthesizedInteraction.swift +5 -2
  36. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SynthesizedTextEntry.swift +78 -10
  37. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextEntry.swift +32 -2
  38. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextEntryFocus.swift +7 -4
  39. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextTyping.swift +23 -0
  40. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Transport.swift +17 -10
  41. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TvRemote.swift +8 -9
  42. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TypeExecution.swift +9 -15
  43. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests.swift +12 -11
  44. package/dist/apple/snapshot-presentation/Sources/AgentDeviceSnapshotPresentation/SnapshotCoordinateSpace.swift +16 -19
  45. package/dist/apple/snapshot-presentation/Sources/AgentDeviceSnapshotPresentation/SnapshotGeometry.swift +24 -9
  46. package/dist/apple/snapshot-presentation/Sources/AgentDeviceSnapshotPresentation/SnapshotModels.swift +66 -10
  47. package/dist/apple/snapshot-presentation/Sources/AgentDeviceSnapshotPresentation/SnapshotPresentationInvariant.swift +12 -10
  48. package/dist/apple/snapshot-presentation/Sources/AgentDeviceSnapshotPresentation/SnapshotPresentationProjection.swift +1 -0
  49. package/dist/apple/snapshot-presentation/Sources/AgentDeviceSnapshotPresentation/SnapshotVisibilityFold.swift +27 -5
  50. package/dist/apple/snapshot-presentation/Sources/AgentDeviceSnapshotPresentation/SnapshotVisibilityFoldProjection.swift +1 -0
  51. package/dist/src/adapter.js +1 -1
  52. package/dist/src/adb-executor.js +1 -1
  53. package/dist/src/adb-failure.js +1 -1
  54. package/dist/src/adb-transport.js +1 -1
  55. package/dist/src/agent-browser-provider.js +1 -1
  56. package/dist/src/agent-browser-tool.js +1 -1
  57. package/dist/src/agent-device-client.js +2 -2
  58. package/dist/src/ai-sdk.js +1 -1
  59. package/dist/src/alert-runtime.js +1 -1
  60. package/dist/src/android-adb.d.ts +1 -8
  61. package/dist/src/android-input-ownership.js +1 -1
  62. package/dist/src/android-snapshot-quality.js +1 -0
  63. package/dist/src/android-system-chrome.js +1 -1
  64. package/dist/src/android.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.js +1 -1
  68. package/dist/src/app-launch.js +1 -1
  69. package/dist/src/app-lifecycle.js +1 -1
  70. package/dist/src/app-lifecycle2.js +1 -1
  71. package/dist/src/app-log-files.js +1 -1
  72. package/dist/src/app-log-runtime2.js +1 -1
  73. package/dist/src/app-resolution.js +2 -2
  74. package/dist/src/apple-runner-platform.js +1 -1
  75. package/dist/src/application-lifecycle-interaction.js +1 -1
  76. package/dist/src/archive.js +1 -1
  77. package/dist/src/artifact-download.js +1 -1
  78. package/dist/src/auth-session.js +1 -1
  79. package/dist/src/auth.js +1 -1
  80. package/dist/src/backend-snapshot-options.js +1 -1
  81. package/dist/src/boot-diagnostics.js +1 -1
  82. package/dist/src/capture-disclosure.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 +2 -2
  86. package/dist/src/cli.js +4 -4
  87. package/dist/src/click-button.js +1 -1
  88. package/dist/src/client-metro.js +1 -1
  89. package/dist/src/client-types.d.ts +102 -95
  90. package/dist/src/clipboard-shell-response.js +1 -1
  91. package/dist/src/command-schema.js +1 -1
  92. package/dist/src/command-tools.js +3 -3
  93. package/dist/src/connection-runtime.js +1 -1
  94. package/dist/src/connection.js +1 -1
  95. package/dist/src/contracts.d.ts +1 -1
  96. package/dist/src/contracts.js +1 -1
  97. package/dist/src/daemon-client-lifecycle.js +1 -1
  98. package/dist/src/daemon.js +1 -1
  99. package/dist/src/debug-symbols-facade.js +1 -1
  100. package/dist/src/device-claim-rule.js +2 -2
  101. package/dist/src/device-input-state.d.ts +15 -2
  102. package/dist/src/device-inventory-context.js +1 -1
  103. package/dist/src/device-rotation.js +1 -1
  104. package/dist/src/device-selection-resolver.js +1 -1
  105. package/dist/src/device-session.js +1 -1
  106. package/dist/src/device-shell.js +1 -1
  107. package/dist/src/device2.js +1 -1
  108. package/dist/src/devicectl.js +1 -1
  109. package/dist/src/diagnostics.js +1 -1
  110. package/dist/src/dispatch-resolve.js +1 -1
  111. package/dist/src/doctor2.js +1 -1
  112. package/dist/src/engine-eval-script.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/format.js +1 -1
  118. package/dist/src/gesture-plan-types.d.ts +1 -1
  119. package/dist/src/gesture-plan.js +1 -1
  120. package/dist/src/gesture-runtime.js +1 -1
  121. package/dist/src/harmonyos.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 +7 -3
  126. package/dist/src/index.js +1 -1
  127. package/dist/src/input-actions.js +1 -1
  128. package/dist/src/input-actions2.js +1 -1
  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-operation-binding.js +1 -1
  136. package/dist/src/interactor.js +4 -4
  137. package/dist/src/interactor2.js +1 -1
  138. package/dist/src/interactors.js +1 -1
  139. package/dist/src/internal/bin.js +1 -1
  140. package/dist/src/internal/daemon.js +2 -2
  141. package/dist/src/internal/png-worker.js +1 -1
  142. package/dist/src/internal/run-script-http-child.js +1 -1
  143. package/dist/src/inventory.js +1 -1
  144. package/dist/src/inventory2.js +1 -1
  145. package/dist/src/inventory3.js +1 -1
  146. package/dist/src/inventory5.js +1 -1
  147. package/dist/src/ios-snapshot-acquisition.js +1 -1
  148. package/dist/src/ios-snapshot-engine.js +1 -0
  149. package/dist/src/ios-snapshot-runtime.js +1 -1
  150. package/dist/src/lease.js +1 -1
  151. package/dist/src/limrun-download-file.js +1 -1
  152. package/dist/src/limrun-runtime-dependencies.js +1 -1
  153. package/dist/src/limrun.d.ts +16 -3
  154. package/dist/src/linux.js +1 -1
  155. package/dist/src/location-coordinates.js +1 -1
  156. package/dist/src/managed-device-scope.js +1 -1
  157. package/dist/src/mechanics.js +17 -17
  158. package/dist/src/mobile-snapshot-semantics.js +1 -1
  159. package/dist/src/multitouch-support.js +1 -1
  160. package/dist/src/native-build-cache.js +1 -0
  161. package/dist/src/observation.js +1 -1
  162. package/dist/src/overlay.js +3 -0
  163. package/dist/src/parameterized-recorded-fill.js +1 -1
  164. package/dist/src/perf-facade.js +1 -1
  165. package/dist/src/perf-runtime-operation-builder.js +1 -1
  166. package/dist/src/perf-runtime-plan.js +1 -1
  167. package/dist/src/perf-target.js +2 -2
  168. package/dist/src/perf.js +1 -1
  169. package/dist/src/physical-device-control.js +1 -1
  170. package/dist/src/platform-runtime-host.js +1 -1
  171. package/dist/src/platform-runtime-managed-owner.js +1 -1
  172. package/dist/src/platform-runtime-open-target.js +1 -1
  173. package/dist/src/platform-runtime-operation-host.js +4 -4
  174. package/dist/src/platform-runtime-runtime-hints.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-screen-recording-process-host.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-crop-bytes.js +1 -1
  181. package/dist/src/png-size.js +1 -1
  182. package/dist/src/png-worker-client.js +1 -1
  183. package/dist/src/png.js +1 -1
  184. package/dist/src/prepare-kind.js +1 -1
  185. package/dist/src/provider-session-ownership.js +1 -1
  186. package/dist/src/provider-webdriver.js +1 -1
  187. package/dist/src/proxy.js +1 -1
  188. package/dist/src/quality-warnings.js +1 -1
  189. package/dist/src/react-native.js +1 -1
  190. package/dist/src/record-runtime.js +1 -1
  191. package/dist/src/recording.js +1 -1
  192. package/dist/src/rect-center.js +1 -1
  193. package/dist/src/register-builtins.js +1 -1
  194. package/dist/src/registry.js +36 -36
  195. package/dist/src/remote-config2.js +1 -1
  196. package/dist/src/replay-divergence.js +1 -1
  197. package/dist/src/replay.js +1 -1
  198. package/dist/src/reporting.js +1 -1
  199. package/dist/src/request-admission.js +1 -1
  200. package/dist/src/request-cancel.js +1 -1
  201. package/dist/src/request-runtime-binding.js +1 -1
  202. package/dist/src/retry.js +1 -1
  203. package/dist/src/runner-cache-metadata.js +1 -1
  204. package/dist/src/runner-client.js +1 -1
  205. package/dist/src/runner-dev-tools-security.js +1 -1
  206. package/dist/src/runner-device-readiness.js +1 -1
  207. package/dist/src/runner-disposal.js +1 -1
  208. package/dist/src/runner-error-classification.js +1 -1
  209. package/dist/src/runner.js +1 -1
  210. package/dist/src/runtime-transport-hints.js +1 -1
  211. package/dist/src/runtime.js +1 -3
  212. package/dist/src/runtime10.js +2 -2
  213. package/dist/src/runtime2.js +4 -4
  214. package/dist/src/runtime3.js +1 -1
  215. package/dist/src/runtime4.js +5 -5
  216. package/dist/src/runtime5.js +1 -1
  217. package/dist/src/runtime6.js +1 -1
  218. package/dist/src/runtime7.js +1 -1
  219. package/dist/src/runtime8.js +1 -1
  220. package/dist/src/runtime9.js +1 -1
  221. package/dist/src/screen-recording-admission-ledger.js +1 -1
  222. package/dist/src/screen-recording-completion.js +1 -1
  223. package/dist/src/screen-recording-options.js +1 -1
  224. package/dist/src/screen-recording-resource-recovery.js +1 -1
  225. package/dist/src/screenshot-density.js +1 -1
  226. package/dist/src/screenshot-image.js +1 -1
  227. package/dist/src/screenshot-overlay-draw.js +1 -1
  228. package/dist/src/screenshot-runtime.js +1 -1
  229. package/dist/src/screenshot.js +1 -1
  230. package/dist/src/screenshot3.js +1 -1
  231. package/dist/src/script.js +1 -1
  232. package/dist/src/scroll-command.d.ts +13 -6
  233. package/dist/src/scroll-command.js +1 -1
  234. package/dist/src/scroll-gesture.js +1 -1
  235. package/dist/src/sdk-batch-runner.js +2 -2
  236. package/dist/src/sdk-batch.js +2 -2
  237. package/dist/src/sdk-contracts.d.ts +59 -12
  238. package/dist/src/sdk-contracts.js +2 -2
  239. package/dist/src/sdk-device.js +1 -1
  240. package/dist/src/sdk-finders.d.ts +1 -1
  241. package/dist/src/sdk-io.js +1 -1
  242. package/dist/src/sdk-remote-config.js +1 -1
  243. package/dist/src/sdk-selectors.d.ts +1 -1
  244. package/dist/src/selection.js +0 -0
  245. package/dist/src/selector-runtime.js +1 -1
  246. package/dist/src/server.js +1 -1
  247. package/dist/src/session-allocation.js +1 -1
  248. package/dist/src/session-routing.js +1 -1
  249. package/dist/src/session-runtime-admission.js +1 -1
  250. package/dist/src/session-snapshot.js +1 -1
  251. package/dist/src/session-store.js +1 -1
  252. package/dist/src/session.js +1 -1
  253. package/dist/src/session2.js +4 -4
  254. package/dist/src/settings.js +1 -1
  255. package/dist/src/settings2.js +1 -0
  256. package/dist/src/simctl.js +1 -1
  257. package/dist/src/simulator-state.js +1 -1
  258. package/dist/src/simulator.js +1 -1
  259. package/dist/src/snapshot-capture-annotations.js +1 -1
  260. package/dist/src/snapshot-desktop-projection.js +1 -1
  261. package/dist/src/snapshot-runtime-binding.js +1 -1
  262. package/dist/src/snapshot-runtime.js +1 -1
  263. package/dist/src/snapshot-state.js +1 -1
  264. package/dist/src/snapshot2.js +1 -1
  265. package/dist/src/snapshot3.js +1 -1
  266. package/dist/src/snapshot4.js +1 -1
  267. package/dist/src/src.js +1 -1
  268. package/dist/src/src4.js +1 -1
  269. package/dist/src/src7.js +1 -1
  270. package/dist/src/src8.js +1 -1
  271. package/dist/src/string-enum.js +1 -1
  272. package/dist/src/surface-snapshot.js +1 -1
  273. package/dist/src/takeover.js +1 -1
  274. package/dist/src/text-entry-focus.js +1 -1
  275. package/dist/src/tool-provider-facade.js +1 -1
  276. package/dist/src/tool-provider.js +2 -2
  277. package/dist/src/tool-provider2.js +1 -1
  278. package/dist/src/touch-runtime.js +1 -1
  279. package/dist/src/transport.js +1 -1
  280. package/dist/src/tree.js +1 -1
  281. package/dist/src/tv-remote.js +1 -1
  282. package/dist/src/type-text-runtime.js +1 -1
  283. package/dist/src/verified-file.js +1 -1
  284. package/dist/src/video.js +1 -1
  285. package/dist/src/viewport.js +1 -1
  286. package/dist/src/web.js +1 -1
  287. package/dist/src/web2.js +1 -1
  288. package/dist/src/webdriver-source.js +1 -1
  289. package/package.json +1 -1
  290. package/server.json +2 -2
  291. package/android/ime-helper/dist/agent-device-android-ime-helper-0.21.13.apk.sha256 +0 -1
  292. package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.21.13.apk +0 -0
  293. package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.21.13.apk.sha256 +0 -1
  294. package/dist/src/absence-observation-errors.js +0 -1
  295. package/dist/src/runner-presentation.js +0 -1
  296. package/dist/src/semantic-index.js +0 -1
  297. package/dist/src/snapshot-process.js +0 -1
  298. package/dist/src/snapshot-scope.js +0 -1
@@ -0,0 +1 @@
1
+ 2f8d38e6705542b327057b059c77cf3a6358fa2fc9a53e4bf5ecfb6279c441bf agent-device-android-ime-helper-0.21.15.apk
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "android-ime-helper",
3
- "version": "0.21.13",
4
- "assetName": "agent-device-android-ime-helper-0.21.13.apk",
5
- "sha256": "8388dc7a3c3f18aca7322cc50bc4f3ff769d426733746a764f45b56d50a0f267",
3
+ "version": "0.21.15",
4
+ "assetName": "agent-device-android-ime-helper-0.21.15.apk",
5
+ "sha256": "2f8d38e6705542b327057b059c77cf3a6358fa2fc9a53e4bf5ecfb6279c441bf",
6
6
  "packageName": "com.callstack.agentdevice.imehelper",
7
- "versionCode": 21013,
7
+ "versionCode": 21015,
8
8
  "serviceComponent": "com.callstack.agentdevice.imehelper/.TestInputMethodService",
9
9
  "broadcastProtocol": "android-ime-helper-v1"
10
10
  }
@@ -0,0 +1 @@
1
+ af733a540d518ede88f0336564d6d4d03b05191732130b65a3094228fa1bed54 agent-device-android-snapshot-helper-0.21.15.apk
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "android-snapshot-helper",
3
- "version": "0.21.13",
4
- "releaseTag": "v0.21.13",
5
- "assetName": "agent-device-android-snapshot-helper-0.21.13.apk",
3
+ "version": "0.21.15",
4
+ "releaseTag": "v0.21.15",
5
+ "assetName": "agent-device-android-snapshot-helper-0.21.15.apk",
6
6
  "apkUrl": null,
7
- "sha256": "e07b556384093a04be99af068b6493e0b0a66e7e7cf09264da4fe60f75f2d164",
8
- "checksumName": "agent-device-android-snapshot-helper-0.21.13.apk.sha256",
7
+ "sha256": "af733a540d518ede88f0336564d6d4d03b05191732130b65a3094228fa1bed54",
8
+ "checksumName": "agent-device-android-snapshot-helper-0.21.15.apk.sha256",
9
9
  "packageName": "com.callstack.agentdevice.snapshothelper",
10
- "versionCode": 21013,
10
+ "versionCode": 21015,
11
11
  "instrumentationRunner": "com.callstack.agentdevice.snapshothelper/.SnapshotInstrumentation",
12
12
  "minSdk": 23,
13
13
  "targetSdk": 36,
@@ -18,7 +18,7 @@
18
18
  NSString *const kProtocolVersionKey = @"protocolVersion";
19
19
  NSString *const kSourceVersionKey = @"sourceVersion";
20
20
  NSString *const kRequestIdKey = @"requestId";
21
- NSString *const kSourceVersion = @"agent-device-simulator-ax-v1.7.0";
21
+ NSString *const kSourceVersion = @"agent-device-simulator-ax-v1.8.0";
22
22
  const NSUInteger kProtocolVersion = 1;
23
23
  const uint32_t kMaximumFrameBytes = 16 * 1024 * 1024;
24
24
  const NSUInteger kMaximumDepth = 128;
@@ -29,6 +29,7 @@ static NSString *const kAttributeElementType = @"XC_kAXXCAttributeElementType";
29
29
  static NSString *const kAttributeElementBaseType = @"XC_kAXXCAttributeElementBaseType";
30
30
  static NSString *const kAttributeLabel = @"XC_kAXXCAttributeLabel";
31
31
  static NSString *const kAttributeValue = @"XC_kAXXCAttributeValue";
32
+ static NSString *const kAttributePlaceholderValue = @"XC_kAXXCAttributePlaceholderValue";
32
33
  static NSString *const kAttributeIdentifier = @"XC_kAXXCAttributeIdentifier";
33
34
  static NSString *const kAttributeFrame = @"XC_kAXXCAttributeFrame";
34
35
  static NSString *const kAttributeAutomationType = @"XC_kAXXCAttributeAutomationType";
@@ -330,6 +331,7 @@ static void finishRequestWatchdog(dispatch_source_t watchdog, SnapshotWatchdogSt
330
331
  kAttributeElementBaseType,
331
332
  kAttributeLabel,
332
333
  kAttributeValue,
334
+ kAttributePlaceholderValue,
333
335
  kAttributeIdentifier,
334
336
  kAttributeFrame,
335
337
  kAttributeAutomationType,
@@ -337,6 +339,14 @@ static void finishRequestWatchdog(dispatch_source_t watchdog, SnapshotWatchdogSt
337
339
  kAttributeChildren,
338
340
  ];
339
341
  NSArray<NSNumber *> *numbers = _attributeNumbersForNames(names);
342
+ if (![numbers isKindOfClass:NSArray.class] || numbers.count != names.count) {
343
+ // The placeholder attribute is optional: a runtime whose vocabulary lacks it serves the capture
344
+ // without placeholders rather than failing every capture over a fact no consumer depends on.
345
+ NSMutableArray<NSString *> *required = [names mutableCopy];
346
+ [required removeObject:kAttributePlaceholderValue];
347
+ names = required;
348
+ numbers = _attributeNumbersForNames(names);
349
+ }
340
350
  if (![numbers isKindOfClass:NSArray.class] || numbers.count != names.count) {
341
351
  if (error) *error = failureResponse(requestId, @"reader_unavailable", @"attribute-vocabulary-mismatch", @"AX attribute vocabulary is incompatible");
342
352
  finishRequestWatchdog(watchdog, watchdogState);
@@ -65,6 +65,15 @@ int main(int argc, const char *argv[]) {
65
65
  @property(nonatomic, strong) UILabel *alertActivationBusyAnswer;
66
66
  @property(nonatomic, assign) NSUInteger firstAlertActions;
67
67
  @property(nonatomic, assign) NSUInteger replacementAlertActions;
68
+ @property(nonatomic, strong) UILabel *textEntryWriteBackStatus;
69
+ @property(nonatomic, assign) NSUInteger textEntryRenderedEdits;
70
+ @property(nonatomic, assign) NSUInteger textEntryWriteBacks;
71
+ @property(nonatomic, copy, nullable) NSString *textEntryRenderedValue;
72
+ @property(nonatomic, assign) NSTimeInterval textEntryLastEditTime;
73
+ @property(nonatomic, assign) NSTimeInterval textEntryBurstStartTime;
74
+ @property(nonatomic, assign) NSUInteger textEntryBurstEdits;
75
+ @property(nonatomic, assign) NSTimeInterval textEntryBurstMinGap;
76
+ @property(nonatomic, assign) NSTimeInterval textEntryAcknowledgeWindowSeconds;
68
77
  @property(nonatomic, assign) BOOL alertFixtureStarted;
69
78
  @property(nonatomic, strong) NSTimer *alertActivationBusyBackstop;
70
79
  @property(nonatomic, strong) NSTimer *alertBannerRepost;
@@ -175,6 +184,17 @@ static NSTimeInterval AgentDeviceAlertActivationBusyWindow(void) {
175
184
  (unsigned long)self.replacementAlertActions];
176
185
  }
177
186
 
187
+ - (void)updateTextEntryWriteBackStatus {
188
+ NSTimeInterval burstSpan = self.textEntryLastEditTime - self.textEntryBurstStartTime;
189
+ self.textEntryWriteBackStatus.text = [NSString
190
+ stringWithFormat:@"edits=%lu write-backs=%lu burst-edits=%lu burst-ms=%lu min-gap-ms=%lu",
191
+ (unsigned long)self.textEntryRenderedEdits,
192
+ (unsigned long)self.textEntryWriteBacks,
193
+ (unsigned long)self.textEntryBurstEdits,
194
+ (unsigned long)llround(burstSpan * 1000),
195
+ (unsigned long)llround(self.textEntryBurstMinGap * 1000)];
196
+ }
197
+
178
198
  - (void)presentAlertFixtureReplacement:(BOOL)replacement {
179
199
  NSArray<NSString *> *arguments = NSProcessInfo.processInfo.arguments;
180
200
  BOOL sameTitle = [arguments containsObject:@"--agent-device-alert-same-title"];
@@ -230,14 +250,58 @@ static NSTimeInterval AgentDeviceAlertActivationBusyWindow(void) {
230
250
  }
231
251
  }
232
252
  }
233
- #endif
253
+
254
+ // How fast an app that owns this field's value can acknowledge edits: one render per window, passed
255
+ // by the test as `--agent-device-text-entry-acknowledge-window <seconds>`. An edit that arrives
256
+ // inside that window overtook the render still in flight, so the value that render commits predates
257
+ // it and writing it erases the characters that got ahead of the app. The app then reads its own
258
+ // erasure back into its model, which is why the field stays wrong instead of healing when the burst
259
+ // finishes. The window is decided at the edit rather than scheduled, so a loaded host, which
260
+ // stretches the gaps between characters, can only make this app keep up better.
261
+ static NSTimeInterval AgentDeviceTextEntryAcknowledgeWindow(void) {
262
+ NSArray<NSString *> *arguments = NSProcessInfo.processInfo.arguments;
263
+ NSUInteger index = [arguments indexOfObject:@"--agent-device-text-entry-acknowledge-window"];
264
+ return index == NSNotFound || index + 1 >= arguments.count ? 0 : [arguments[index + 1] doubleValue];
265
+ }
266
+
267
+ // Edits further apart than this belong to different bursts: one runner command's characters arrive
268
+ // well inside it, and two commands are separated by at least a commit-wait poll and a status read.
269
+ static const NSTimeInterval AgentDeviceTextEntryBurstBreakSeconds = 1.0;
234
270
 
235
271
  - (void)agentDeviceTextEntryDidChange:(UITextField *)textField {
272
+ // A field whose app owns its value, the way a controlled React Native `TextInput` does. A burst
273
+ // typed faster than the app renders loses the characters that arrived while a render was in
274
+ // flight, and the field settles stable short of the request.
275
+ if ([NSProcessInfo.processInfo.arguments containsObject:@"--agent-device-text-entry-app-owned-value"]) {
276
+ NSTimeInterval now = NSProcessInfo.processInfo.systemUptime;
277
+ NSTimeInterval gap = now - self.textEntryLastEditTime;
278
+ BOOL overtookARender = self.textEntryRenderedValue != nil && gap < self.textEntryAcknowledgeWindowSeconds;
279
+ if (self.textEntryBurstEdits == 0 || gap > AgentDeviceTextEntryBurstBreakSeconds) {
280
+ self.textEntryBurstStartTime = now;
281
+ self.textEntryBurstEdits = 0;
282
+ self.textEntryBurstMinGap = 0;
283
+ } else if (self.textEntryBurstEdits == 1 || gap < self.textEntryBurstMinGap) {
284
+ self.textEntryBurstMinGap = gap;
285
+ }
286
+ self.textEntryBurstEdits += 1;
287
+ self.textEntryLastEditTime = now;
288
+ if (overtookARender) {
289
+ if (![textField.text isEqualToString:self.textEntryRenderedValue]) {
290
+ textField.text = self.textEntryRenderedValue;
291
+ self.textEntryWriteBacks += 1;
292
+ }
293
+ } else {
294
+ self.textEntryRenderedValue = [textField.text copy];
295
+ self.textEntryRenderedEdits += 1;
296
+ }
297
+ [self updateTextEntryWriteBackStatus];
298
+ }
236
299
  if ([NSProcessInfo.processInfo.arguments containsObject:@"--agent-device-text-entry-disappear-after-input"] &&
237
300
  textField.text.length > 0) {
238
301
  [textField removeFromSuperview];
239
302
  }
240
303
  }
304
+ #endif
241
305
 
242
306
  - (void)viewDidLoad {
243
307
  [super viewDidLoad];
@@ -281,7 +345,13 @@ static NSTimeInterval AgentDeviceAlertActivationBusyWindow(void) {
281
345
  UITextField *textField = [[UITextField alloc] init];
282
346
  textField.accessibilityIdentifier = @"agent-device-hardware-keyboard-input";
283
347
  textField.borderStyle = UITextBorderStyleRoundedRect;
284
- textField.inputView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 1, 1)];
348
+ // An empty input view keeps the software keyboard down, which is the hardware-keyboard responder
349
+ // these routes are addressed to. `--agent-device-text-entry-soft-keyboard` leaves the real input
350
+ // view in place, so a lane test can reach the branch that requires a visible keyboard.
351
+ if (![NSProcessInfo.processInfo.arguments
352
+ containsObject:@"--agent-device-text-entry-soft-keyboard"]) {
353
+ textField.inputView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 1, 1)];
354
+ }
285
355
  [textField addTarget:self
286
356
  action:@selector(agentDeviceTextEntryDidChange:)
287
357
  forControlEvents:UIControlEventEditingChanged];
@@ -293,6 +363,39 @@ static NSTimeInterval AgentDeviceAlertActivationBusyWindow(void) {
293
363
  [textField.widthAnchor constraintEqualToConstant:240],
294
364
  [textField.heightAnchor constraintEqualToConstant:44],
295
365
  ]];
366
+ if ([NSProcessInfo.processInfo.arguments containsObject:@"--agent-device-text-entry-app-owned-value"]) {
367
+ self.textEntryAcknowledgeWindowSeconds = AgentDeviceTextEntryAcknowledgeWindow();
368
+ // Reports how many edits this app rendered and how many writes it had to make because a
369
+ // character overtook one, so a lane test can tell a burst the app kept up with from an inert
370
+ // fixture. Counts only: no field content crosses into the test.
371
+ self.textEntryWriteBackStatus = [[UILabel alloc] init];
372
+ self.textEntryWriteBackStatus.accessibilityIdentifier = @"agent-device-text-entry-write-backs";
373
+ self.textEntryWriteBackStatus.translatesAutoresizingMaskIntoConstraints = NO;
374
+ [self.view addSubview:self.textEntryWriteBackStatus];
375
+ [NSLayoutConstraint activateConstraints:@[
376
+ [self.textEntryWriteBackStatus.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor],
377
+ [self.textEntryWriteBackStatus.topAnchor constraintEqualToAnchor:textField.bottomAnchor constant:12],
378
+ ]];
379
+ [self updateTextEntryWriteBackStatus];
380
+ }
381
+ }
382
+
383
+ if ([NSProcessInfo.processInfo.arguments containsObject:@"--agent-device-crowded-screen"]) {
384
+ for (NSUInteger row = 0; row < 500; row++) {
385
+ UILabel *rowLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 60 + (row % 30) * 24, 400, 16)];
386
+ rowLabel.text = [NSString stringWithFormat:@"Crowded row %lu", (unsigned long)row];
387
+ rowLabel.accessibilityIdentifier = [NSString stringWithFormat:@"agent-device-crowded-row-%lu", (unsigned long)row];
388
+ [self.view addSubview:rowLabel];
389
+ }
390
+ }
391
+
392
+ BOOL markedButton = [NSProcessInfo.processInfo.arguments containsObject:@"--agent-device-dismiss-popup"];
393
+ if (markedButton || [NSProcessInfo.processInfo.arguments containsObject:@"--agent-device-dismiss-popup-window"]) {
394
+ UIButton *dismissRegion = [UIButton buttonWithType:UIButtonTypeSystem];
395
+ dismissRegion.accessibilityIdentifier = markedButton ? @" Dismiss Popup " : @"agent-device-close-popover";
396
+ [dismissRegion setTitle:@"Close popover" forState:UIControlStateNormal];
397
+ dismissRegion.frame = CGRectMake(40, 40, 200, 44);
398
+ [self.view addSubview:dismissRegion];
296
399
  }
297
400
 
298
401
  if ([NSProcessInfo.processInfo.arguments containsObject:@"--agent-device-selector-read-regression"]) {
@@ -338,6 +441,11 @@ static NSTimeInterval AgentDeviceAlertActivationBusyWindow(void) {
338
441
 
339
442
  self.window = [[UIWindow alloc] initWithWindowScene:(UIWindowScene *)scene];
340
443
  self.window.rootViewController = [[AgentDeviceRunnerViewController alloc] init];
444
+ #if TARGET_OS_IOS
445
+ if ([NSProcessInfo.processInfo.arguments containsObject:@"--agent-device-dismiss-popup-window"]) {
446
+ self.window.accessibilityIdentifier = @"Dismiss popup";
447
+ }
448
+ #endif
341
449
  [self.window makeKeyAndVisible];
342
450
  }
343
451
 
@@ -365,6 +365,7 @@ typedef id (*RunnerAXSnapshotMsgSend)(id, SEL, id, id, id, NSError **);
365
365
  @"identifier",
366
366
  @"label",
367
367
  @"value",
368
+ @"placeholderValue",
368
369
  @"frame",
369
370
  @"enabled",
370
371
  @"selected",
@@ -388,8 +389,8 @@ typedef id (*RunnerAXSnapshotMsgSend)(id, SEL, id, id, id, NSError **);
388
389
  // The mapper expands keypaths with extra attributes (automation type, window display
389
390
  // id, base type) that are disproportionately expensive for the AX server to compute
390
391
  // on large React Native trees. Keep only the attributes we actually consume.
391
- NSArray *needed = @[ @"ElementType", @"Identifier", @"Label", @"Value", @"Frame",
392
- @"Enabled", @"Selected", @"Focus" ];
392
+ NSArray *needed = @[ @"ElementType", @"Identifier", @"Label", @"Value", @"PlaceholderValue",
393
+ @"Frame", @"Enabled", @"Selected", @"Focus" ];
393
394
  NSMutableArray *filtered = [NSMutableArray array];
394
395
  for (id attribute in (NSArray *)mapped) {
395
396
  NSString *name = [attribute description];
@@ -764,6 +765,7 @@ typedef id (*RunnerAXSnapshotMsgSend)(id, SEL, id, id, id, NSError **);
764
765
  result[@"identifier"] = [self stringValueForKey:@"identifier" snapshot:snapshot] ?: @"";
765
766
  result[@"label"] = [self stringValueForKey:@"label" snapshot:snapshot] ?: @"";
766
767
  result[@"value"] = [self stringValueForKey:@"value" snapshot:snapshot] ?: @"";
768
+ result[@"placeholder"] = [self stringValueForKey:@"placeholderValue" snapshot:snapshot] ?: @"";
767
769
  result[@"frame"] = [self frameValueForSnapshot:snapshot];
768
770
  result[@"enabled"] = [self boolNumberForKey:@"enabled" snapshot:snapshot defaultValue:YES];
769
771
  result[@"selected"] = [self boolNumberForKey:@"selected" snapshot:snapshot defaultValue:NO];
@@ -0,0 +1,27 @@
1
+ import XCTest
2
+
3
+
4
+
5
+ @MainActor
6
+ final class RunnerMainOwnedState {
7
+ var app: XCUIApplication?
8
+ var bundleId: String?
9
+ var processIdentifier: Int?
10
+ var accessibilityHealth: RunnerAccessibilityHealth = .unknown
11
+ var needsPostSnapshotInteractionDelay = false
12
+
13
+ nonisolated init() {}
14
+ }
15
+
16
+
17
+
18
+
19
+
20
+
21
+ func runOnMainActor<T>(_ work: @MainActor () throws -> T) -> Result<T, Error> {
22
+ var result: Result<T, Error>?
23
+ MainActor.assumeIsolated {
24
+ result = Result { try work() }
25
+ }
26
+ return result!
27
+ }
@@ -17,13 +17,19 @@ typedef NS_ENUM(NSInteger, RunnerSynthesizedTextEntryStatus) {
17
17
 
18
18
  @interface RunnerSynthesizedTextEntry : NSObject
19
19
 
20
+ // Characters per second the synthesized text-input records are typed at. Declared here, where the
21
+ // typing happens, so the delivery budget that bounds a burst is charged the same pace the app sees.
22
+ // The edit-acknowledge window that pace is sized for is a separate assumption about the app
23
+ // (TextEntryTiming.synthesizedAcknowledgeWindowSeconds), not a value derived from this one.
24
+ + (NSUInteger)typingSpeedCharactersPerSecond;
25
+
20
26
  // Synthesizes keyboard input for the current first responder without resolving an
21
27
  // XCUIElement or serializing the application's accessibility tree.
22
28
  + (RunnerSynthesizedTextEntryResult *)synthesizeTextWithApplication:(id)application
23
29
  text:(NSString *)text;
24
30
 
25
- // Replaces the current first responder's contents using one synthesized
26
- // Command-A, Delete, and text-input event sequence.
31
+ // Replaces the current first responder's contents with one synthesized Command-A record
32
+ // followed by a text-input record, typed at the bounded pace declared in the implementation.
27
33
  + (RunnerSynthesizedTextEntryResult *)replaceTextWithApplication:(id)application
28
34
  text:(NSString *)text;
29
35
 
@@ -5,6 +5,20 @@
5
5
 
6
6
  static NSString *const RunnerTextSynthesisSurface = @"text";
7
7
 
8
+ // XCTest's `typingSpeed:` argument is characters per second. At 60 the 11 characters of a `fill`
9
+ // arrived at a fixture field across 131 ms (~13 ms per gap), which is faster than an app that owns
10
+ // its field's value and re-applies it after the edit (a controlled React Native `TextInput`, an
11
+ // async validator) can acknowledge: such a write lands between two characters of the burst and
12
+ // erases what was typed while it was in flight, leaving a value that is stable short of the
13
+ // request. 12 characters/second spaces them ~83 ms apart on average, which reduces that loss but
14
+ // does not remove it: XCTest does not space the characters evenly, and two of them can reach the
15
+ // app a few milliseconds apart. Against a fixture app that acknowledges each edit within 40 ms, 60
16
+ // characters/second left 1 of 11 characters in 20 of 20 bursts, and this pace left 10 or 11. The
17
+ // command refuses a field left short; back-pressure from the field (#2906) is what would prevent
18
+ // it. The app-owned-value lane test pins the average spacing the app sees, and the delivery budget
19
+ // in TextEntryTiming bounds what the pace costs a long text.
20
+ static const NSUInteger RunnerTextEntryTypingSpeedCharactersPerSecond = 12;
21
+
8
22
  typedef id (*RunnerTextMsgSendInit)(id, SEL, NSString *);
9
23
  typedef id (*RunnerTextMsgSendInitPath)(id, SEL);
10
24
  typedef void (*RunnerTextMsgSendType)(id, SEL, NSString *, NSTimeInterval, NSUInteger, BOOL);
@@ -51,6 +65,10 @@ static RunnerSynthesizedTextEntryResult *RunnerSynthesizeTextWithMode(
51
65
 
52
66
  @implementation RunnerSynthesizedTextEntry
53
67
 
68
+ + (NSUInteger)typingSpeedCharactersPerSecond {
69
+ return RunnerTextEntryTypingSpeedCharactersPerSecond;
70
+ }
71
+
54
72
  + (RunnerSynthesizedTextEntryResult *)synthesizeTextWithApplication:(id)application
55
73
  text:(NSString *)text {
56
74
  return RunnerSynthesizeTextWithMode(application, text, NO);
@@ -128,7 +146,14 @@ static RunnerSynthesizedTextEntryResult *RunnerSynthesizeTextWithMode(
128
146
  );
129
147
  }
130
148
  ((RunnerMsgSendSetInteger)objc_msgSend)(record, bridge.core.setTargetProcessIDSelector, targetProcessID);
131
- ((RunnerTextMsgSendType)objc_msgSend)(path, bridge.typeTextSelector, text, 0.0, 60, YES);
149
+ ((RunnerTextMsgSendType)objc_msgSend)(
150
+ path,
151
+ bridge.typeTextSelector,
152
+ text,
153
+ 0.0,
154
+ RunnerTextEntryTypingSpeedCharactersPerSecond,
155
+ YES
156
+ );
132
157
  ((RunnerMsgSendAddPath)objc_msgSend)(record, bridge.core.addPathSelector, path);
133
158
 
134
159
  NSError *error = nil;
@@ -217,12 +217,11 @@ extension RunnerTests {
217
217
  }
218
218
 
219
219
  let rootFrame = privateAXRect(root["frame"])
220
- let geometry = privateAXSnapshotGeometry(
220
+ let viewport = privateAXSnapshotViewport(
221
221
  app: app,
222
222
  bundleId: target.bundleId,
223
223
  rootFrame: rootFrame
224
224
  )
225
- let viewport = geometry.viewport
226
225
  let nodes = privateAXAcquisition(
227
226
  rawRoot: root,
228
227
  hint: hint
@@ -259,8 +258,7 @@ extension RunnerTests {
259
258
  customActions: Self.privateAXCustomActionCoverage(
260
259
  response[RunnerAXSnapshotCustomActionsKey]
261
260
  ),
262
- viewport: viewport,
263
- interfaceOrientation: geometry.interfaceOrientation
261
+ viewport: viewport
264
262
  )
265
263
  #else
266
264
  return nil
@@ -277,35 +275,30 @@ extension RunnerTests {
277
275
 
278
276
 
279
277
 
280
-
281
-
282
- private func privateAXSnapshotGeometry(
278
+ private func privateAXSnapshotViewport(
283
279
  app: XCUIApplication,
284
280
  bundleId: String?,
285
281
  rootFrame: CGRect
286
- ) -> (viewport: CGRect, interfaceOrientation: Int) {
287
- let fallback = rootFrame.isEmpty ? CGRect.infinite : rootFrame
282
+ ) -> SnapshotViewport {
283
+ let fallback = SnapshotViewport.derived(box: rootFrame)
288
284
  guard shouldReadPrivateAXViewportViaXCTest(bundleId: bundleId) else {
289
- return (fallback, RunnerInterfaceOrientation.unknown)
285
+ return fallback
290
286
  }
291
287
  do {
292
- let anchor = try runMainThreadWork(
288
+ let reported = try runMainThreadWork(
293
289
  "private_ax_viewport",
294
290
  timeout: 1,
295
291
  timeoutError: snapshotMainThreadTimeoutError("reading private AX viewport")
296
292
  ) {
297
- (
298
- viewport: self.safeSnapshotViewport(app: app),
299
- interfaceOrientation: self.capturedInterfaceOrientation(app: app)
300
- )
293
+ self.safeSnapshotViewport(app: app, readingOrientation: true)
301
294
  }
302
- if anchor.viewport.isInfinite || anchor.viewport.isNull || anchor.viewport.isEmpty {
303
- return (fallback, RunnerInterfaceOrientation.unknown)
295
+ if case .missing = reported {
296
+ return fallback
304
297
  }
305
- return (anchor.viewport, anchor.interfaceOrientation)
298
+ return reported
306
299
  } catch {
307
300
  NSLog("AGENT_DEVICE_RUNNER_PRIVATE_AX_VIEWPORT_FALLBACK=%@", String(describing: error))
308
- return (fallback, RunnerInterfaceOrientation.unknown)
301
+ return fallback
309
302
  }
310
303
  }
311
304
 
@@ -21,6 +21,7 @@ extension RunnerTests {
21
21
  return max(0.001, timeoutMs / 1000)
22
22
  }
23
23
 
24
+ @MainActor
24
25
  func resolveAlert(app activeApp: XCUIApplication, deadline: Date) -> RunnerAlert? {
25
26
 
26
27
 
@@ -50,6 +51,7 @@ extension RunnerTests {
50
51
  return nil
51
52
  }
52
53
 
54
+ @MainActor
53
55
  func handleAlert(_ alert: RunnerAlert, action: String, deadline: Date) -> Response {
54
56
  if action == "accept" || action == "dismiss" {
55
57
  guard let button = chooseAlertButton(alert.buttons, action: action) else {
@@ -69,6 +71,14 @@ extension RunnerTests {
69
71
  return alertVerificationResponse(.timedOut, action: action, activated: false)
70
72
  }
71
73
  let buttonFrame = button.frame
74
+ guard Date() < deadline else {
75
+ return alertVerificationResponse(.timedOut, action: action, activated: false)
76
+ }
77
+ #if !os(tvOS)
78
+ guard !buttonFrame.isEmpty else {
79
+ return alertVerificationResponse(.unconfirmed, action: action, activated: false)
80
+ }
81
+ #endif
72
82
  NSLog(
73
83
  "AGENT_DEVICE_RUNNER_ALERT_ACTIVATION action=%@ label=%@ frame=(%.1f,%.1f,%.1f,%.1f) point=(%.1f,%.1f)",
74
84
  action,
@@ -82,11 +92,8 @@ extension RunnerTests {
82
92
 
83
93
 
84
94
 
85
- var outcome = RunnerInteractionOutcome.performed
86
- withUIInterruptionHandlingDisabledIfSupported(alert.ownerApp) {
87
- withBoundedInteractionIdleTimeoutIfSupported(alert.ownerApp, waits: .preEventSkipped) {
88
- outcome = activateElement(app: alert.ownerApp, element: button, action: "alert \(action)")
89
- }
95
+ guard let outcome = activateAlertButton(alert, button: button, action: action, frame: buttonFrame, deadline: deadline) else {
96
+ return alertVerificationResponse(.timedOut, action: action, activated: false)
90
97
  }
91
98
  if let response = unsupportedResponse(for: outcome) {
92
99
  return response
@@ -116,6 +123,32 @@ extension RunnerTests {
116
123
  )
117
124
  }
118
125
 
126
+ @MainActor
127
+ func activateAlertButton(
128
+ _ alert: RunnerAlert,
129
+ button: XCUIElement,
130
+ action: String,
131
+ frame: CGRect,
132
+ deadline: Date
133
+ ) -> RunnerInteractionOutcome? {
134
+ var outcome: RunnerInteractionOutcome?
135
+ withUIInterruptionHandlingDisabledIfSupported(alert.ownerApp) {
136
+ withBoundedInteractionIdleTimeoutIfSupported(alert.ownerApp, waits: .preEventSkipped) {
137
+ guard Date() < deadline else { return }
138
+ #if !os(tvOS)
139
+ guard !frame.isEmpty else { return }
140
+ #endif
141
+ outcome = activateElement(
142
+ app: alert.ownerApp,
143
+ element: button,
144
+ action: "alert \(action)",
145
+ resolvedFrame: frame
146
+ )
147
+ }
148
+ }
149
+ return outcome
150
+ }
151
+
119
152
 
120
153
 
121
154
 
@@ -161,22 +194,26 @@ extension RunnerTests {
161
194
  elements.first { isVisibleElement($0) }
162
195
  }
163
196
 
197
+
198
+
199
+
200
+
164
201
  private func firstDismissPopupWindow(in app: XCUIApplication) -> XCUIElement? {
165
- safeElementsQuery {
166
- app.windows.allElementsBoundByIndex
167
- }.first { window in
168
- if !isVisibleElement(window) { return false }
169
- if isDismissPopupMarker(window.label) || isDismissPopupMarker(window.identifier) {
170
- return true
171
- }
172
- return safeElementsQuery {
173
- window.descendants(matching: .any).allElementsBoundByIndex
174
- }.contains { descendant in
175
- isDismissPopupMarker(descendant.label) || isDismissPopupMarker(descendant.identifier)
176
- }
177
- }
202
+ firstExistingElement(in: safeElementsQuery {
203
+ app.windows.containing(Self.dismissPopupMarker).allElementsBoundByIndex
204
+ })
178
205
  }
179
206
 
207
+
208
+
209
+ private static let dismissPopupMarkerPattern = #"\s*dismiss popup\s*"#
210
+ private static let dismissPopupMarker = NSPredicate(
211
+ format: "label MATCHES[c] %@ OR identifier MATCHES[c] %@",
212
+ dismissPopupMarkerPattern,
213
+ dismissPopupMarkerPattern
214
+ )
215
+ private static let dismissPopupMarkerText = NSPredicate(format: "SELF MATCHES[c] %@", dismissPopupMarkerPattern)
216
+
180
217
  private func chooseAlertButton(_ buttons: [XCUIElement], action: String) -> XCUIElement? {
181
218
  if action == "accept" {
182
219
  if let accept = buttons.first(where: { isAcceptButton($0.label) }) {
@@ -262,6 +299,7 @@ extension RunnerTests {
262
299
 
263
300
 
264
301
 
302
+ @MainActor
265
303
  private func waitUntilAlertButtonHittable(_ button: XCUIElement, deadline: Date) -> Bool {
266
304
  while Date() < deadline {
267
305
  if probeAlertButtonHittable(button, deadline: deadline) {
@@ -272,6 +310,7 @@ extension RunnerTests {
272
310
  return false
273
311
  }
274
312
 
313
+ @MainActor
275
314
  private func probeAlertButtonHittable(_ button: XCUIElement, deadline: Date) -> Bool {
276
315
 
277
316
 
@@ -285,7 +324,7 @@ extension RunnerTests {
285
324
  return hittable
286
325
  }
287
326
 
288
- private func isDismissPopupMarker(_ label: String) -> Bool {
289
- label.trimmingCharacters(in: .whitespacesAndNewlines).caseInsensitiveCompare("dismiss popup") == .orderedSame
327
+ func isDismissPopupMarker(_ text: String) -> Bool {
328
+ Self.dismissPopupMarkerText.evaluate(with: text)
290
329
  }
291
330
  }
@@ -68,9 +68,8 @@ extension RunnerTests {
68
68
  }
69
69
 
70
70
  private func containsDismissPopupMarker(_ snapshot: XCUIElementSnapshot) -> Bool {
71
- [snapshot.label, snapshot.identifier].contains {
72
- $0.trimmingCharacters(in: .whitespacesAndNewlines).caseInsensitiveCompare("dismiss popup") == .orderedSame
73
- } || snapshot.children.contains { containsDismissPopupMarker($0) }
71
+ [snapshot.label, snapshot.identifier].contains { isDismissPopupMarker($0) } ||
72
+ snapshot.children.contains { containsDismissPopupMarker($0) }
74
73
  }
75
74
 
76
75
  private func alertPresentation(_ snapshot: XCUIElementSnapshot) -> RunnerAlertPresentation {