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
@@ -0,0 +1 @@
1
+ 65454a4663596adfc7fccd9a30e42f02b5ecde17eb1514fa115ef3036171b995 agent-device-android-ime-helper-0.21.1.apk
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "android-ime-helper",
3
- "version": "0.21.0",
4
- "assetName": "agent-device-android-ime-helper-0.21.0.apk",
5
- "sha256": "2f486edc5788d34f3ba8afd551f33e59a4823a6f358d2a3e883e93b3f2a0d7e8",
3
+ "version": "0.21.1",
4
+ "assetName": "agent-device-android-ime-helper-0.21.1.apk",
5
+ "sha256": "65454a4663596adfc7fccd9a30e42f02b5ecde17eb1514fa115ef3036171b995",
6
6
  "packageName": "com.callstack.agentdevice.imehelper",
7
- "versionCode": 21000,
7
+ "versionCode": 21001,
8
8
  "serviceComponent": "com.callstack.agentdevice.imehelper/.TestInputMethodService",
9
9
  "broadcastProtocol": "android-ime-helper-v1"
10
10
  }
@@ -0,0 +1 @@
1
+ 6ee75909ac499eca439d3663717d8fc1928ada3fec71bf2ae85fb567967a3793 agent-device-android-snapshot-helper-0.21.1.apk
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "android-snapshot-helper",
3
- "version": "0.21.0",
4
- "releaseTag": "v0.21.0",
5
- "assetName": "agent-device-android-snapshot-helper-0.21.0.apk",
3
+ "version": "0.21.1",
4
+ "releaseTag": "v0.21.1",
5
+ "assetName": "agent-device-android-snapshot-helper-0.21.1.apk",
6
6
  "apkUrl": null,
7
- "sha256": "536b4096b0bfd7e224a6ebbeeff1daaca0a8df4dc56abd3e410541ad1733048f",
8
- "checksumName": "agent-device-android-snapshot-helper-0.21.0.apk.sha256",
7
+ "sha256": "6ee75909ac499eca439d3663717d8fc1928ada3fec71bf2ae85fb567967a3793",
8
+ "checksumName": "agent-device-android-snapshot-helper-0.21.1.apk.sha256",
9
9
  "packageName": "com.callstack.agentdevice.snapshothelper",
10
- "versionCode": 21000,
10
+ "versionCode": 21001,
11
11
  "instrumentationRunner": "com.callstack.agentdevice.snapshothelper/.SnapshotInstrumentation",
12
12
  "minSdk": 23,
13
13
  "targetSdk": 36,
@@ -8,7 +8,7 @@ and is never downloaded, pre-signed, or built by npm installation.
8
8
 
9
9
  The guest process uses the `XCTAccessibilityFramework` remote-access client
10
10
  from the simulator runtime and the `userTestingSnapshotForElement:options:error:`
11
- single-fetch API. Requests and responses are length-prefixed JSON frames:
11
+ snapshot API. Requests and responses are length-prefixed JSON frames:
12
12
 
13
13
  ```text
14
14
  uint32 big-endian byte length
@@ -47,3 +47,60 @@ app tree. The existing route then uses XCTest, which owns system-modal
47
47
  resolution. Secondary owners such as the return-to-app status-bar control do
48
48
  not replace the native primary owner. The route's generation circuit remains
49
49
  disabled after fallback until that app relaunches.
50
+
51
+ ## Remote content
52
+
53
+ The reader snapshots one process. A WebKit page — Safari's or a `WKWebView`'s —
54
+ lives in a WebContent process and appears in that tree as an `AXRemoteElement`
55
+ leaf under the web view. The guest returns the leaf as delivered; the host
56
+ refuses a tree in which such a leaf sits under a web view and reaches the
57
+ viewport, or reports no frame (`remote-content-boundary`), and routes that app
58
+ generation to XCTest, which resolves remote elements (#2484). A zero-area or
59
+ off-screen leaf is published: it hosts nothing the capture can miss.
60
+
61
+ ## Bounded depth recovery
62
+
63
+ A healthy capture uses one native request. If native acquisition rejects it,
64
+ `SnapshotBridgeCapture.m` retries supported native failure codes at lower depths
65
+ and fetches withheld children from their accessibility elements. The completed
66
+ tree keeps the original depth and node limits; partial trees disclose truncation.
67
+ The traversal depth counts edges below the root; native requests count the root
68
+ as one level. Each acquisition allows two lower-depth retries, and recovery
69
+ allows at most 32 native requests within the existing capture deadline,
70
+ checks foreground ownership on every request, and returns a failure when it
71
+ cannot complete a continuation. Budget exhaustion and malformed continuations
72
+ use non-launch failure codes, so the route falls back without launch re-polling.
73
+ At each native fragment boundary, an absent or invalid child count means unknown
74
+ completeness and fails closed. Natural leaves above that boundary need no
75
+ continuation evidence. Unchanged native dictionaries and child arrays are reused.
76
+
77
+ ## Accepted-depth hints
78
+
79
+ The host source (`packages/platform-apple/src/snapshot-source/depth-hints.ts`)
80
+ remembers the native levels a finished recovery accepted, keyed by the resolved
81
+ target id, its app generation, and this producer. The next capture of that
82
+ generation sends `nativeLevelsHint`, so the guest's first request asks for the
83
+ accepted levels instead of re-paying the known rejection. A hint changes the
84
+ request strategy only: the delivered depth, node bounds, and completeness rules
85
+ are unchanged. Hints are learned only from a recovery that observed a rejection
86
+ and then finished (a tree bounded by the requested depth or node budget still
87
+ teaches), never cross apps, generations, or producers, and expire after eight
88
+ hinted captures so the next capture probes the full depth again; a capture that
89
+ merely succeeds at the hinted depth does not renew it. Explicit raw-depth
90
+ requests neither use nor teach hints. Every response carries `recovery`
91
+ (`requests`, `rejected`, `continuations`, `acceptedLevels`), which the host
92
+ emits as the `ios_snapshot_source_recovery` diagnostic.
93
+
94
+ ## Recovery conformance
95
+
96
+ `contracts/fixtures/ios-ax-recovery-conformance.json` is the shared, executable
97
+ recovery contract for this bridge and the XCTest runner's private AX bridge.
98
+ `packages/platform-apple/src/snapshot-source/fixtures/recovery-conformance.m`
99
+ replays each case through `captureSnapshotTree`; the runner replays the same
100
+ cases through its own bridge in
101
+ `RunnerTests+AXRecoveryConformanceTests.swift`. Each expectation names the
102
+ outcome, the native request accounting, and the delivered tree as a canonical
103
+ preorder signature with its retained node count, so a producer that drops,
104
+ duplicates, reorders, or re-parents nodes cannot pass as complete. The fixture
105
+ records each producer's expectation and documents the intentional differences
106
+ (depth vocabulary, ladders, completeness evidence, budgets, hint lifetime).
@@ -85,6 +85,11 @@ static NSDictionary *handleRequest(NSDictionary *request)
85
85
  !validBoundInteger(request[@"maxResponseBytes"], 1024, kMaximumFrameBytes, &maxResponseBytes)) {
86
86
  return failureResponse(requestId, @"bad_request", @"bounds-invalid", @"snapshot bridge request bounds are outside the bridge limits");
87
87
  }
88
+ NSUInteger nativeLevelsHint = 0;
89
+ if (request[@"nativeLevelsHint"] != nil &&
90
+ !validBoundInteger(request[@"nativeLevelsHint"], 1, kMaximumDepth + 1, &nativeLevelsHint)) {
91
+ return failureResponse(requestId, @"bad_request", @"bounds-invalid", @"nativeLevelsHint is outside the bridge depth limits");
92
+ }
88
93
 
89
94
  NSString *setupError = nil;
90
95
  BridgeRuntime *runtime = sharedRuntime(&setupError);
@@ -98,6 +103,7 @@ static NSDictionary *handleRequest(NSDictionary *request)
98
103
  NSDictionary *response = [runtime snapshotForProcess:pidValue.intValue
99
104
  maxDepth:maxDepth
100
105
  maxNodes:maxNodes
106
+ nativeLevelsHint:nativeLevelsHint
101
107
  requestId:requestId
102
108
  generation:generation
103
109
  maxDurationMs:maxDurationMs
@@ -0,0 +1,25 @@
1
+ #import <Foundation/Foundation.h>
2
+
3
+ NS_ASSUME_NONNULL_BEGIN
4
+
5
+ typedef id _Nullable (^SnapshotElementReader)(id element, NSUInteger depth, NSUInteger nodes, NSError **error);
6
+
7
+ /// Native request accounting for one acquisition: requests issued, requests the accessibility
8
+ /// server rejected at their depth, continuations (requests that reached the reader beyond the
9
+ /// root's accepted one), and the native levels of the last accepted request.
10
+ typedef struct {
11
+ NSUInteger requests;
12
+ NSUInteger rejected;
13
+ NSUInteger continuations;
14
+ NSUInteger acceptedLevels;
15
+ } SnapshotCaptureRecovery;
16
+
17
+ /// Materializes one bounded tree; retries bounded native acquisition failures and re-roots withheld
18
+ /// children. `nativeLevelsHint` (0 for none) caps only the first request's native levels; the
19
+ /// delivered depth, node bounds, and completeness rules are unchanged.
20
+ NSDictionary *_Nullable captureSnapshotTree(id element, NSUInteger maxDepth, NSUInteger maxNodes,
21
+ NSUInteger nativeLevelsHint, SnapshotElementReader reader,
22
+ BOOL *truncated, SnapshotCaptureRecovery *_Nullable recovery,
23
+ NSError **error);
24
+
25
+ NS_ASSUME_NONNULL_END
@@ -0,0 +1,138 @@
1
+ #import "SnapshotBridgeCapture.h"
2
+
3
+ static NSString *const attributesKey = @"UIAccessibilitySnapshotKeyAttributes";
4
+ static NSString *const childrenKey = @"UIAccessibilitySnapshotKeyChildren";
5
+ static NSString *const childCountKey = @"UIAccessibilitySnapshotKeyChildrenCount";
6
+ static NSString *const elementKey = @"UIAccessibilitySnapshotKeyElement";
7
+ static const NSUInteger maximumRequests = 32;
8
+
9
+ @interface SnapshotTreeCapture : NSObject
10
+ @property(nonatomic, copy) SnapshotElementReader reader;
11
+ @property(nonatomic) NSUInteger acceptedDepth;
12
+ @property(nonatomic) NSUInteger remainingNodes;
13
+ @property(nonatomic) NSUInteger maximumNodes;
14
+ @property(nonatomic) NSUInteger requests;
15
+ @property(nonatomic) NSUInteger rejected;
16
+ @property(nonatomic) BOOL truncated;
17
+ - (nullable NSDictionary *)read:(id)element depth:(NSUInteger)depth error:(NSError **)error;
18
+ - (nullable NSDictionary *)materialize:(NSDictionary *)tree depth:(NSUInteger)depth nativeLevels:(NSUInteger)nativeLevels error:(NSError **)error;
19
+ @end
20
+
21
+ @implementation SnapshotTreeCapture
22
+ - (nullable NSDictionary *)read:(id)element depth:(NSUInteger)depth error:(NSError **)error
23
+ {
24
+ NSUInteger attemptDepth = MIN(depth, self.acceptedDepth);
25
+ for (NSUInteger retries = 0;; retries++) {
26
+ if (self.requests >= maximumRequests) {
27
+ if (error) *error = [NSError errorWithDomain:@"agent-device.snapshot" code:1
28
+ userInfo:@{NSLocalizedDescriptionKey: @"snapshot continuation request budget exhausted"}];
29
+ return nil;
30
+ }
31
+ self.requests++;
32
+ NSError *failure = nil;
33
+ id tree = self.reader(element, attemptDepth, MIN(self.maximumNodes, self.remainingNodes + 1), &failure);
34
+ if (tree) return tree;
35
+ NSNumber *nativeCode = failure.userInfo[@"accessibility-error"];
36
+ BOOL rejected = ([nativeCode isKindOfClass:NSNumber.class] && nativeCode.integerValue == -25201) ||
37
+ ([failure.domain isEqualToString:@"com.apple.dt.xctest.automation-support.error"] && failure.code == 5);
38
+ if (rejected) self.rejected++;
39
+ if (!rejected || attemptDepth <= 1 || retries >= 2) {
40
+ if (error) *error = failure;
41
+ return nil;
42
+ }
43
+ attemptDepth = MAX(1, attemptDepth / 2);
44
+ self.acceptedDepth = attemptDepth;
45
+ }
46
+ }
47
+
48
+ - (nullable NSDictionary *)materialize:(NSDictionary *)tree depth:(NSUInteger)depth nativeLevels:(NSUInteger)nativeLevels error:(NSError **)error
49
+ {
50
+ if (![tree isKindOfClass:NSDictionary.class] ||
51
+ ![tree[attributesKey] isKindOfClass:NSDictionary.class] ||
52
+ ![tree[childrenKey] isKindOfClass:NSArray.class]) {
53
+ if (error) *error = [NSError errorWithDomain:@"agent-device.snapshot" code:2
54
+ userInfo:@{NSLocalizedDescriptionKey: @"malformed snapshot continuation"}];
55
+ return nil;
56
+ }
57
+ if (self.remainingNodes == 0) {
58
+ self.truncated = YES;
59
+ return nil;
60
+ }
61
+ self.remainingNodes--;
62
+ NSArray *children = tree[childrenKey];
63
+ NSNumber *childCount = tree[childCountKey];
64
+ BOOL knownChildCount = [childCount isKindOfClass:NSNumber.class] && childCount.doubleValue >= 0 &&
65
+ childCount.doubleValue == (double)childCount.unsignedIntegerValue;
66
+ if (nativeLevels <= 1 && !knownChildCount && depth <= 1) self.truncated = YES;
67
+ if (depth > 1 && nativeLevels <= 1 && !knownChildCount) {
68
+ if (error) *error = [NSError errorWithDomain:@"agent-device.snapshot" code:6
69
+ userInfo:@{NSLocalizedDescriptionKey: @"snapshot boundary child count unavailable"}];
70
+ return nil;
71
+ }
72
+ BOOL withheld = knownChildCount && childCount.unsignedIntegerValue > children.count;
73
+ if (depth <= 1 || self.remainingNodes == 0) {
74
+ self.truncated |= children.count > 0 || withheld;
75
+ if (children.count == 0) return tree;
76
+ NSMutableDictionary *bounded = [tree mutableCopy];
77
+ bounded[childrenKey] = @[];
78
+ return bounded;
79
+ }
80
+ if (withheld && children.count < self.remainingNodes) {
81
+ id element = tree[elementKey];
82
+ if (!element) {
83
+ if (error) *error = [NSError errorWithDomain:@"agent-device.snapshot" code:3
84
+ userInfo:@{NSLocalizedDescriptionKey: @"snapshot continuation element unavailable"}];
85
+ return nil;
86
+ }
87
+ NSDictionary *continuation = [self read:element depth:depth error:error];
88
+ if (!continuation) return nil;
89
+ nativeLevels = MIN(depth, self.acceptedDepth);
90
+ children = continuation[childrenKey];
91
+ if (![children isKindOfClass:NSArray.class] || children.count < MIN(childCount.unsignedIntegerValue, self.remainingNodes)) {
92
+ if (error) *error = [NSError errorWithDomain:@"agent-device.snapshot" code:4
93
+ userInfo:@{NSLocalizedDescriptionKey: @"snapshot continuation children unavailable"}];
94
+ return nil;
95
+ }
96
+ }
97
+ if (withheld && children.count < childCount.unsignedIntegerValue) self.truncated = YES;
98
+ NSMutableArray *materialized = nil;
99
+ NSUInteger index = 0;
100
+ for (NSDictionary *child in children) {
101
+ if (self.remainingNodes == 0) {
102
+ self.truncated = YES;
103
+ if (!materialized) materialized = [[children subarrayWithRange:NSMakeRange(0, index)] mutableCopy];
104
+ break;
105
+ }
106
+ NSDictionary *node = [self materialize:child depth:depth - 1 nativeLevels:(nativeLevels > 0 ? nativeLevels - 1 : 0) error:error];
107
+ if (!node) return nil;
108
+ if (node != child && !materialized) materialized = [[children subarrayWithRange:NSMakeRange(0, index)] mutableCopy];
109
+ [materialized addObject:node];
110
+ index++;
111
+ }
112
+ if (!materialized && children == tree[childrenKey]) return tree;
113
+ NSMutableDictionary *result = [tree mutableCopy];
114
+ result[childrenKey] = materialized ?: children;
115
+ return result;
116
+ }
117
+ @end
118
+
119
+ NSDictionary *captureSnapshotTree(id element, NSUInteger maxDepth, NSUInteger maxNodes,
120
+ NSUInteger nativeLevelsHint, SnapshotElementReader reader,
121
+ BOOL *truncated, SnapshotCaptureRecovery *recovery, NSError **error)
122
+ {
123
+ SnapshotTreeCapture *capture = [SnapshotTreeCapture new];
124
+ capture.reader = reader;
125
+ capture.acceptedDepth = nativeLevelsHint > 0 ? MIN(maxDepth + 1, nativeLevelsHint) : maxDepth + 1;
126
+ capture.remainingNodes = maxNodes;
127
+ capture.maximumNodes = maxNodes;
128
+ NSDictionary *tree = [capture read:element depth:maxDepth + 1 error:error];
129
+ NSDictionary *result = tree ? [capture materialize:tree depth:maxDepth + 1 nativeLevels:capture.acceptedDepth error:error] : nil;
130
+ *truncated = capture.truncated;
131
+ if (recovery) {
132
+ recovery->requests = capture.requests;
133
+ recovery->rejected = capture.rejected;
134
+ recovery->continuations = capture.requests - capture.rejected - (tree ? 1 : 0);
135
+ recovery->acceptedLevels = tree ? capture.acceptedDepth : 0;
136
+ }
137
+ return result;
138
+ }
@@ -22,6 +22,7 @@ NSDictionary *failureResponse(NSString *requestId,
22
22
  - (nullable NSDictionary *)snapshotForProcess:(pid_t)pid
23
23
  maxDepth:(NSUInteger)maxDepth
24
24
  maxNodes:(NSUInteger)maxNodes
25
+ nativeLevelsHint:(NSUInteger)nativeLevelsHint
25
26
  requestId:(NSString *)requestId
26
27
  generation:(NSString *)generation
27
28
  maxDurationMs:(NSUInteger)maxDurationMs
@@ -5,6 +5,7 @@
5
5
  */
6
6
 
7
7
  #import "SnapshotBridgeRuntime.h"
8
+ #import "SnapshotBridgeCapture.h"
8
9
 
9
10
  #import <CoreGraphics/CoreGraphics.h>
10
11
  #import <objc/message.h>
@@ -17,7 +18,7 @@
17
18
  NSString *const kProtocolVersionKey = @"protocolVersion";
18
19
  NSString *const kSourceVersionKey = @"sourceVersion";
19
20
  NSString *const kRequestIdKey = @"requestId";
20
- NSString *const kSourceVersion = @"agent-device-simulator-ax-v1.5.3";
21
+ NSString *const kSourceVersion = @"agent-device-simulator-ax-v1.5.5";
21
22
  const NSUInteger kProtocolVersion = 1;
22
23
  const uint32_t kMaximumFrameBytes = 16 * 1024 * 1024;
23
24
  const NSUInteger kMaximumDepth = 128;
@@ -261,6 +262,7 @@ static void finishRequestWatchdog(dispatch_source_t watchdog, SnapshotWatchdogSt
261
262
  - (nullable NSDictionary *)snapshotForProcess:(pid_t)pid
262
263
  maxDepth:(NSUInteger)maxDepth
263
264
  maxNodes:(NSUInteger)maxNodes
265
+ nativeLevelsHint:(NSUInteger)nativeLevelsHint
264
266
  requestId:(NSString *)requestId
265
267
  generation:(NSString *)generation
266
268
  maxDurationMs:(NSUInteger)maxDurationMs
@@ -311,13 +313,27 @@ static void finishRequestWatchdog(dispatch_source_t watchdog, SnapshotWatchdogSt
311
313
  BOOL automationEnabled = [self assertAutomationMode:YES];
312
314
  NSError *runtimeError = nil;
313
315
  id snapshot = nil;
316
+ BOOL acquisitionTruncated = NO;
317
+ SnapshotCaptureRecovery recovery = {0, 0, 0, 0};
314
318
  @try {
315
319
  if (![self isPrimaryForegroundProcess:pid]) {
316
320
  if (error) *error = failureResponse(requestId, @"unsupported", @"foreground-owner-unverified", @"target app is not the primary foreground accessibility owner");
317
321
  finishRequestWatchdog(watchdog, watchdogState);
318
322
  return nil;
319
323
  }
320
- snapshot = [_framework userTestingSnapshotForElement:(__bridge id)raw options:options error:&runtimeError];
324
+ snapshot = captureSnapshotTree((__bridge id)raw, maxDepth, maxNodes, nativeLevelsHint,
325
+ ^id(id element, NSUInteger depth, NSUInteger nodes, NSError **captureError) {
326
+ if (![self isPrimaryForegroundProcess:pid]) {
327
+ if (captureError) *captureError = [NSError errorWithDomain:@"agent-device.snapshot" code:5
328
+ userInfo:@{NSLocalizedDescriptionKey: @"foreground owner changed during continuation"}];
329
+ return nil;
330
+ }
331
+ NSMutableDictionary *bounded = [options mutableCopy];
332
+ bounded[@"maxDepth"] = @(depth);
333
+ bounded[@"maxChildren"] = @(nodes);
334
+ bounded[@"maxArrayCount"] = @(nodes);
335
+ return [_framework userTestingSnapshotForElement:element options:bounded error:captureError];
336
+ }, &acquisitionTruncated, &recovery, &runtimeError);
321
337
  if (![self isPrimaryForegroundProcess:pid]) {
322
338
  if (error) *error = failureResponse(requestId, @"unsupported", @"foreground-owner-changed", @"foreground accessibility ownership changed during acquisition");
323
339
  finishRequestWatchdog(watchdog, watchdogState);
@@ -328,6 +344,13 @@ static void finishRequestWatchdog(dispatch_source_t watchdog, SnapshotWatchdogSt
328
344
  finishRequestWatchdog(watchdog, watchdogState);
329
345
  return nil;
330
346
  }
347
+ if (!snapshot && [runtimeError.domain isEqualToString:@"agent-device.snapshot"]) {
348
+ BOOL exhausted = runtimeError.code == 1;
349
+ if (error) *error = failureResponse(requestId, exhausted ? @"reader_unavailable" : @"malformed_tree",
350
+ exhausted ? @"continuation-budget-exhausted" : @"snapshot-tree-malformed", runtimeError.localizedDescription);
351
+ finishRequestWatchdog(watchdog, watchdogState);
352
+ return nil;
353
+ }
331
354
  if (!snapshot) {
332
355
  NSNumber *axError = runtimeError.userInfo[kAccessibilityErrorKey];
333
356
  NSInteger code = [axError respondsToSelector:@selector(integerValue)] ? axError.integerValue : runtimeError.code;
@@ -362,8 +385,14 @@ static void finishRequestWatchdog(dispatch_source_t watchdog, SnapshotWatchdogSt
362
385
  @"ok" : @YES,
363
386
  @"pid" : @(pid),
364
387
  @"tree" : tree,
365
- @"truncated" : @(truncated),
388
+ @"truncated" : @((BOOL)(truncated || acquisitionTruncated)),
366
389
  @"automationEnabled" : @(automationEnabled),
390
+ @"recovery" : @{
391
+ @"requests" : @(recovery.requests),
392
+ @"rejected" : @(recovery.rejected),
393
+ @"continuations" : @(recovery.continuations),
394
+ @"acceptedLevels" : @(recovery.acceptedLevels),
395
+ },
367
396
  };
368
397
  }
369
398
  @end
@@ -49,6 +49,18 @@ FOUNDATION_EXPORT NSString *const RunnerAXSnapshotCustomActionsBlockedKey;
49
49
  customActionLimit:(NSInteger)customActionLimit
50
50
  deadline:(nullable NSDate *)deadline;
51
51
 
52
+ /// The capture behind `snapshotTreeForApplication:`, for an AX client and target
53
+ /// application element that are already resolved. Every request, extension, and
54
+ /// custom-action decision happens here; the application entry point only resolves
55
+ /// the two identities.
56
+ + (NSDictionary<NSString *, id> *)snapshotTreeWithClient:(id)axClient
57
+ target:(id)target
58
+ maxDepth:(NSInteger)maxDepth
59
+ maxNodes:(NSInteger)maxNodes
60
+ deepExtensionCallLimit:(NSInteger)deepExtensionCallLimit
61
+ customActionLimit:(NSInteger)customActionLimit
62
+ deadline:(nullable NSDate *)deadline;
63
+
52
64
  /// Names of the element's UIAccessibilityCustomActions, or nil when it has
53
65
  /// none.
54
66
  ///
@@ -104,7 +104,27 @@ typedef id (*RunnerAXSnapshotMsgSend)(id, SEL, id, id, id, NSError **);
104
104
  if (nil == target) {
105
105
  return [self failure:@"Could not match active AX application for XCTest application"];
106
106
  }
107
+ return [self snapshotTreeWithClient:axClient
108
+ target:target
109
+ maxDepth:maxDepth
110
+ maxNodes:maxNodes
111
+ deepExtensionCallLimit:deepExtensionCallLimit
112
+ customActionLimit:customActionLimit
113
+ deadline:deadline];
114
+ } @catch (NSException *exception) {
115
+ return [self failure:exception.reason ?: exception.name ?: @"AX snapshot bridge exception"];
116
+ }
117
+ }
107
118
 
119
+ + (NSDictionary<NSString *, id> *)snapshotTreeWithClient:(id)axClient
120
+ target:(id)target
121
+ maxDepth:(NSInteger)maxDepth
122
+ maxNodes:(NSInteger)maxNodes
123
+ deepExtensionCallLimit:(NSInteger)deepExtensionCallLimit
124
+ customActionLimit:(NSInteger)customActionLimit
125
+ deadline:(nullable NSDate *)deadline
126
+ {
127
+ @try {
108
128
  NSArray *attributes = [self snapshotAttributes];
109
129
  NSError *error = nil;
110
130
  id root = [self requestSnapshotFromClient:axClient
@@ -20,26 +20,26 @@ enum DirectSelectorCandidateDecision: Equatable {
20
20
  case ambiguous
21
21
  }
22
22
 
23
- /// How many raw exact matches a dispatch may discard before hittability is
24
- /// allowed to pick a winner. The two rows differ because the cost of guessing
25
- /// wrong differs, not because the matching differs.
23
+
24
+
25
+
26
26
  enum DirectSelectorRawMatchPolicy: Equatable {
27
- /// Mutations fail closed: every raw exact match counts, so a hittable
28
- /// element can never silently win over a same-selector duplicate the caller
29
- /// never saw and act on the wrong one.
27
+
28
+
29
+
30
30
  case rejectDistinctMatches
31
- /// Reads prefer the single hittable match and ignore non-hittable
32
- /// same-selector duplicates. A read has no side effect to guard, and
33
- /// `querySelector` backs `get`/`is`/`wait` — failing those closed turns a
34
- /// decorative duplicate into an error where the reader previously got its
35
- /// answer.
31
+
32
+
33
+
34
+
35
+
36
36
  case preferHittableMatch
37
37
  }
38
38
 
39
- /// Normal direct selector mutations count every raw exact match before
40
- /// hittability can choose a winner. Reads keep the hittable-preference rule,
41
- /// and Maestro's explicitly requested coordinate fallback keeps its
42
- /// point-filtered compatibility behavior.
39
+
40
+
41
+
42
+
43
43
  func classifyDirectSelectorCandidates(
44
44
  _ candidates: [SelectorCandidateFacts],
45
45
  allowNonHittableFallback: Bool,
@@ -0,0 +1,72 @@
1
+ import Foundation
2
+
3
+
4
+
5
+
6
+
7
+ enum SystemSurfaceHostKind: String {
8
+ case webAuth = "web-auth"
9
+ }
10
+
11
+ struct SystemSurfaceHost: Equatable {
12
+ let bundleId: String
13
+ let kind: SystemSurfaceHostKind
14
+ }
15
+
16
+ enum SystemSurfaceHostRegistry {
17
+ static let hosts: [SystemSurfaceHost] = [
18
+ SystemSurfaceHost(bundleId: "com.apple.SafariViewService", kind: .webAuth)
19
+ ]
20
+
21
+ static func host(forBundleId bundleId: String?) -> SystemSurfaceHost? {
22
+ guard let bundleId else { return nil }
23
+ return hosts.first { $0.bundleId == bundleId }
24
+ }
25
+
26
+ static func isSystemSurfaceHost(_ bundleId: String?) -> Bool {
27
+ host(forBundleId: bundleId) != nil
28
+ }
29
+ }
30
+
31
+
32
+
33
+
34
+
35
+
36
+
37
+
38
+
39
+
40
+
41
+
42
+
43
+
44
+
45
+
46
+
47
+
48
+
49
+
50
+
51
+
52
+
53
+
54
+
55
+
56
+
57
+
58
+
59
+
60
+
61
+
62
+
63
+
64
+
65
+
66
+
67
+
68
+
69
+
70
+
71
+
72
+
@@ -1,19 +1,19 @@
1
1
  import XCTest
2
2
 
3
- // The tap-point rule the runner shares with the TS runtime (ADR 0011 Layer 2).
4
- //
5
- // RULE: the tap point is the element frame's CENTER; a tap is allowed iff that
6
- // center lies inside the window frame, edges inclusive. An empty or invalid
7
- // window frame fails open (allowed): resolving the best available frame is the
8
- // impure caller's job (onScreenWindowFrame / app.frame), and the policy must
9
- // not turn a missing frame into a refusal.
10
- //
11
- // This is pure geometry on purpose — no XCUIElement — so the exact decision
12
- // can be proven against the golden fixture table shared with the TS twin:
13
- // table: contracts/fixtures/tap-point-policy.json
14
- // TS twin: packages/contracts/src/snapshot-visibility.ts#isTapPointInsideViewport
15
- // TS test: packages/contracts/src/snapshot-tap-point-policy.test.ts
16
- // Drift on either side turns CI red without needing a simulator.
3
+
4
+
5
+
6
+
7
+
8
+
9
+
10
+
11
+
12
+
13
+
14
+
15
+
16
+
17
17
  enum TapPointPolicy {
18
18
  static func isAllowed(elementFrame: CGRect, windowFrame: CGRect) -> Bool {
19
19
  if windowFrame.isNull || windowFrame.isEmpty || windowFrame.isInfinite {
@@ -26,3 +26,52 @@ enum TapPointPolicy {
26
26
  }
27
27
  }
28
28
 
29
+
30
+
31
+
32
+
33
+
34
+
35
+
36
+
37
+
38
+
39
+
40
+
41
+
42
+
43
+
44
+
45
+
46
+
47
+
48
+
49
+
50
+
51
+
52
+
53
+
54
+
55
+
56
+
57
+
58
+
59
+
60
+
61
+
62
+
63
+
64
+
65
+
66
+
67
+
68
+
69
+
70
+
71
+
72
+
73
+
74
+
75
+
76
+
77
+