agent-device 0.20.10 → 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 (510) hide show
  1. package/README.md +2 -2
  2. package/android/ime-helper/dist/{agent-device-android-ime-helper-0.20.10.apk → agent-device-android-ime-helper-0.21.1.apk} +0 -0
  3. package/android/ime-helper/dist/agent-device-android-ime-helper-0.21.1.apk.sha256 +1 -0
  4. package/android/ime-helper/dist/{agent-device-android-ime-helper-0.20.10.manifest.json → agent-device-android-ime-helper-0.21.1.manifest.json} +4 -4
  5. package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.21.1.apk +0 -0
  6. package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.21.1.apk.sha256 +1 -0
  7. package/android/snapshot-helper/dist/{agent-device-android-snapshot-helper-0.20.10.manifest.json → agent-device-android-snapshot-helper-0.21.1.manifest.json} +6 -6
  8. package/apple/macos-helper/Sources/AgentDeviceMacOSHelper/AudioProbe.swift +35 -33
  9. package/apple/macos-helper/Sources/AgentDeviceMacOSHelper/main.swift +6 -1
  10. package/apple/snapshot-bridge/LICENSE.idb +23 -0
  11. package/apple/snapshot-bridge/README.md +106 -0
  12. package/apple/snapshot-bridge/SnapshotBridge.m +286 -0
  13. package/apple/snapshot-bridge/SnapshotBridgeCapture.h +25 -0
  14. package/apple/snapshot-bridge/SnapshotBridgeCapture.m +138 -0
  15. package/apple/snapshot-bridge/SnapshotBridgeRuntime.h +34 -0
  16. package/apple/snapshot-bridge/SnapshotBridgeRuntime.m +411 -0
  17. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunner/AgentDeviceRunnerApp.m +58 -0
  18. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunner.xcodeproj/project.pbxproj +19 -0
  19. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerAXSnapshotBridge.h +12 -0
  20. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerAXSnapshotBridge.m +20 -0
  21. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerAlertVerification.swift +36 -0
  22. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerSelectorMatchPolicy.swift +15 -15
  23. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerStringNormalization.swift +8 -0
  24. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerSynthesizedGesture.h +12 -1
  25. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerSynthesizedGesture.m +100 -6
  26. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerSystemSurfaceHostPolicy.swift +72 -0
  27. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTapPointPolicy.swift +63 -14
  28. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+AXSnapshotFallback.swift +677 -83
  29. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Alert.swift +35 -92
  30. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+AlertObservation.swift +124 -0
  31. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+BlockingSystemModalResolution.swift +39 -4
  32. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandExecution.swift +1058 -213
  33. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandJournal.swift +296 -14
  34. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Environment.swift +1 -1
  35. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Exceptions.swift +10 -10
  36. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Interaction.swift +309 -149
  37. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Keyboard.swift +101 -42
  38. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Lifecycle.swift +24 -17
  39. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Models.swift +124 -199
  40. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Navigation.swift +71 -4
  41. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+PrivateAXElementTypes.swift +12 -0
  42. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+PrivateAXPresentation.swift +211 -78
  43. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+ScreenRecorder.swift +30 -10
  44. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+ScrollGesture.swift +254 -24
  45. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SequenceExecution.swift +206 -22
  46. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Snapshot.swift +465 -494
  47. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SnapshotBackendCapabilities.swift +168 -0
  48. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SnapshotCapturePlan.swift +802 -247
  49. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SnapshotPresentationInvariant.swift +19 -0
  50. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SnapshotTiming.swift +185 -0
  51. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SynthesizedCommitDeadline.swift +206 -0
  52. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SynthesizedGesturePolicy.swift +83 -5
  53. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SynthesizedTextEntry.swift +156 -34
  54. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SystemModal.swift +5 -4
  55. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextEntry.swift +35 -318
  56. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextEntryFocus.swift +206 -0
  57. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextEntryReadiness.swift +158 -0
  58. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextInputCandidatePolicy.swift +28 -0
  59. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextInputProbe.swift +101 -0
  60. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextTyping.swift +56 -23
  61. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Transport.swift +56 -27
  62. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TvRemote.swift +30 -24
  63. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests.swift +126 -80
  64. package/dist/apple/runner/AgentDeviceRunner/RecordingScripts/RecordingExportSupport.swift +111 -0
  65. package/dist/apple/runner/AgentDeviceRunner/RecordingScripts/recording-overlay.swift +47 -86
  66. package/dist/apple/runner/AgentDeviceRunner/RecordingScripts/recording-trim.swift +32 -79
  67. package/dist/apple/snapshot-presentation/Package.swift +21 -0
  68. package/dist/apple/snapshot-presentation/Sources/AgentDeviceSnapshotPresentation/SnapshotGeometry.swift +56 -0
  69. package/dist/apple/snapshot-presentation/Sources/AgentDeviceSnapshotPresentation/SnapshotModels.swift +259 -0
  70. package/dist/apple/snapshot-presentation/Sources/AgentDeviceSnapshotPresentation/SnapshotPresentation.swift +153 -0
  71. package/dist/apple/snapshot-presentation/Sources/AgentDeviceSnapshotPresentation/SnapshotPresentationFailure.swift +27 -0
  72. package/dist/apple/snapshot-presentation/Sources/AgentDeviceSnapshotPresentation/SnapshotPresentationInvariant.swift +91 -0
  73. package/dist/apple/snapshot-presentation/Sources/AgentDeviceSnapshotPresentation/SnapshotPresentationProjection.swift +161 -0
  74. package/dist/apple/snapshot-presentation/Sources/AgentDeviceSnapshotPresentation/SnapshotScopePolicy.swift +53 -0
  75. package/dist/apple/snapshot-presentation/Sources/AgentDeviceSnapshotPresentation/SnapshotVisibilityFold.swift +242 -0
  76. package/dist/apple/snapshot-presentation/Sources/AgentDeviceSnapshotPresentation/SnapshotVisibilityFoldProjection.swift +97 -0
  77. package/dist/src/adapter.js +1 -0
  78. package/dist/src/adb-executor.js +2 -0
  79. package/dist/src/adb-failure.js +1 -0
  80. package/dist/src/adb-host.js +1 -0
  81. package/dist/src/adb.js +1 -0
  82. package/dist/src/agent-browser-lifecycle.js +1 -0
  83. package/dist/src/agent-browser-process-record.js +1 -0
  84. package/dist/src/agent-browser-provider.js +1 -0
  85. package/dist/src/agent-browser-tool.js +1 -1
  86. package/dist/src/agent-device-client.js +2 -2
  87. package/dist/src/ai-sdk.d.ts +2 -2
  88. package/dist/src/ai-sdk.js +1 -1
  89. package/dist/src/alert-contract.js +1 -0
  90. package/dist/src/alert-runtime.js +1 -0
  91. package/dist/src/android-adb.d.ts +27 -19
  92. package/dist/src/android-adb.js +1 -1
  93. package/dist/src/android-input-ownership.js +1 -0
  94. package/dist/src/android-system-chrome.js +1 -0
  95. package/dist/src/android.js +1 -1
  96. package/dist/src/api.js +11 -0
  97. package/dist/src/app-catalog.js +1 -0
  98. package/dist/src/app-deployment-resolution.js +2 -0
  99. package/dist/src/app-helpers.js +1 -1
  100. package/dist/src/app-inventory-contract.js +1 -0
  101. package/dist/src/app-inventory.js +1 -0
  102. package/dist/src/app-launch.js +2 -0
  103. package/dist/src/app-lifecycle-facade.js +1 -0
  104. package/dist/src/app-lifecycle.js +3 -6
  105. package/dist/src/app-lifecycle2.js +2 -2
  106. package/dist/src/app-lifecycle3.js +1 -0
  107. package/dist/src/app-log-files.js +1 -1
  108. package/dist/src/app-log-reconnect.js +1 -1
  109. package/dist/src/app-log-runtime.js +1 -4
  110. package/dist/src/app-log-runtime2.js +4 -0
  111. package/dist/src/app-parsers.js +4 -0
  112. package/dist/src/app-resolution-cache.js +1 -0
  113. package/dist/src/app-resolution-facade.js +1 -0
  114. package/dist/src/app-resolution.js +2 -2
  115. package/dist/src/app-state.js +1 -2
  116. package/dist/src/apple-runner-platform.js +1 -0
  117. package/dist/src/application-lifecycle-interaction.js +1 -0
  118. package/dist/src/application-lifecycle-runtime.js +1 -0
  119. package/dist/src/apps-simctl.js +1 -0
  120. package/dist/src/archive.js +4 -0
  121. package/dist/src/artifact-download.js +1 -0
  122. package/dist/src/artifacts.d.ts +6 -2
  123. package/dist/src/artifacts.js +1 -1
  124. package/dist/src/async-lifecycle.js +1 -0
  125. package/dist/src/audio-probe-result.js +1 -0
  126. package/dist/src/audio-probe-runtime.js +1 -0
  127. package/dist/src/audio-probe-support.js +1 -0
  128. package/dist/src/auth-session.js +1 -1
  129. package/dist/src/auth.js +1 -1
  130. package/dist/src/backend-snapshot-options.js +1 -1
  131. package/dist/src/{url.js → base-url.js} +1 -1
  132. package/dist/src/boot-diagnostics.js +2 -2
  133. package/dist/src/byte-limit-stream.js +1 -1
  134. package/dist/src/capabilities.js +1 -0
  135. package/dist/src/capture.js +1 -0
  136. package/dist/src/catalog.js +1 -0
  137. package/dist/src/cli-command-aliases.js +1 -0
  138. package/dist/src/cli-config.js +1 -1
  139. package/dist/src/cli-help.js +74 -51
  140. package/dist/src/cli-output.js +1 -1
  141. package/dist/src/cli-runner.js +1 -1
  142. package/dist/src/cli.js +4 -6
  143. package/dist/src/click-button.js +1 -0
  144. package/dist/src/client-metro-companion.js +1 -0
  145. package/dist/src/client-metro.js +1 -1
  146. package/dist/src/client-types.d.ts +280 -242
  147. package/dist/src/clipboard.js +1 -0
  148. package/dist/src/code-signature-cache.js +1 -0
  149. package/dist/src/code-signature.js +1 -0
  150. package/dist/src/color.js +1 -0
  151. package/dist/src/command-metadata.js +1 -1
  152. package/dist/src/command-schema.js +3 -3
  153. package/dist/src/command-surface.js +1 -1
  154. package/dist/src/command-tools.js +2 -2
  155. package/dist/src/command.js +1 -0
  156. package/dist/src/compatibility-policy.js +1 -0
  157. package/dist/src/config.js +1 -1
  158. package/dist/src/config2.js +1 -0
  159. package/dist/src/connection-runtime.js +1 -1
  160. package/dist/src/connection.js +5 -5
  161. package/dist/src/contracts.d.ts +1 -1
  162. package/dist/src/contracts.js +1 -1
  163. package/dist/src/daemon-client-lifecycle.js +1 -0
  164. package/dist/src/daemon-process.js +1 -0
  165. package/dist/src/daemon-registration.js +1 -0
  166. package/dist/src/daemon-shutdown-report.js +1 -1
  167. package/dist/src/daemon.js +2 -2
  168. package/dist/src/debug-symbols-facade.js +5 -0
  169. package/dist/src/deploy-result-target.js +1 -0
  170. package/dist/src/device-claim-inspection.js +1 -1
  171. package/dist/src/device-claim-rule.js +1 -0
  172. package/dist/src/device-input-state.d.ts +190 -3
  173. package/dist/src/device-inventory-context.js +1 -0
  174. package/dist/src/device-isolation.js +1 -0
  175. package/dist/src/device-release.js +1 -0
  176. package/dist/src/device-rotation.js +1 -0
  177. package/dist/src/device-selection-resolver.js +1 -0
  178. package/dist/src/device-session.js +1 -0
  179. package/dist/src/device.js +1 -1
  180. package/dist/src/device2.js +3 -2
  181. package/dist/src/diagnostics.js +3 -0
  182. package/dist/src/diagnostics2.js +1 -0
  183. package/dist/src/dispatch-resolve.js +1 -0
  184. package/dist/src/doctor-facade.js +1 -0
  185. package/dist/src/doctor-output.js +1 -0
  186. package/dist/src/doctor.js +1 -0
  187. package/dist/src/doctor2.js +1 -0
  188. package/dist/src/doctor3.js +1 -0
  189. package/dist/src/durable-resource.js +1 -0
  190. package/dist/src/entities.js +1 -1
  191. package/dist/src/error.js +2 -0
  192. package/dist/src/exec.js +1 -0
  193. package/dist/src/file.js +1 -0
  194. package/dist/src/find.js +1 -1
  195. package/dist/src/finders.js +1 -1
  196. package/dist/src/generic-settle.js +1 -1
  197. package/dist/src/generic.js +1 -1
  198. package/dist/src/gesture-admission.js +1 -0
  199. package/dist/src/gesture-plan-types.js +1 -0
  200. package/dist/src/gesture-plan.js +1 -0
  201. package/dist/src/gesture-runtime.js +1 -0
  202. package/dist/src/harmonyos.js +1 -1
  203. package/dist/src/hdc.js +1 -1
  204. package/dist/src/helper.js +2 -2
  205. package/dist/src/host-file.js +1 -0
  206. package/dist/src/human-control-contract.js +1 -0
  207. package/dist/src/human-control.js +1 -0
  208. package/dist/src/index.d.ts +1 -1
  209. package/dist/src/index.js +1 -1
  210. package/dist/src/input-actions.js +1 -4
  211. package/dist/src/input-actions2.js +2 -1
  212. package/dist/src/install-artifact-facade.js +1 -0
  213. package/dist/src/install-source-network-transport.js +1 -0
  214. package/dist/src/install-source.d.ts +3 -10
  215. package/dist/src/install-source2.js +1 -1
  216. package/dist/src/interaction-positionals.js +1 -0
  217. package/dist/src/interaction-ref-policy.js +1 -0
  218. package/dist/src/interaction.js +1 -1
  219. package/dist/src/interaction2.js +1 -1
  220. package/dist/src/interactor-operation-catalog.js +1 -0
  221. package/dist/src/interactor-types.js +1 -0
  222. package/dist/src/interactor.js +6 -2
  223. package/dist/src/interactor2.js +1 -1
  224. package/dist/src/interactors.js +1 -1
  225. package/dist/src/internal/bin.d.ts +1 -1
  226. package/dist/src/internal/bin.js +1 -2
  227. package/dist/src/internal/companion-tunnel.d.ts +1 -1
  228. package/dist/src/internal/companion-tunnel.js +1 -1
  229. package/dist/src/internal/daemon.d.ts +1 -1
  230. package/dist/src/internal/daemon.js +2 -4
  231. package/dist/src/internal/png-worker.d.ts +4 -4
  232. package/dist/src/internal/png-worker.js +1 -1
  233. package/dist/src/internal/run-script-http-child.d.ts +4 -0
  234. package/dist/src/internal/run-script-http-child.js +1 -0
  235. package/dist/src/internal/update-check-entry.d.ts +1 -1
  236. package/dist/src/inventory.js +4 -4
  237. package/dist/src/inventory2.js +1 -1
  238. package/dist/src/inventory3.js +1 -1
  239. package/dist/src/inventory5.js +1 -1
  240. package/dist/src/inventory6.js +1 -1
  241. package/dist/src/ios-client.js +16 -0
  242. package/dist/src/ios-snapshot-acquisition.js +1 -0
  243. package/dist/src/ios-snapshot-adapter.js +1 -0
  244. package/dist/src/ios-snapshot-planning.js +1 -0
  245. package/dist/src/ios-snapshot-runtime.js +1 -0
  246. package/dist/src/ios-system-surface.js +1 -0
  247. package/dist/src/json.js +1 -0
  248. package/dist/src/keyboard-actions.js +1 -0
  249. package/dist/src/launch-console.js +1 -0
  250. package/dist/src/lease-context.js +1 -1
  251. package/dist/src/lease-scope.js +1 -0
  252. package/dist/src/lease.js +1 -1
  253. package/dist/src/limrun-runtime-dependencies.js +1 -1
  254. package/dist/src/limrun.d.ts +1610 -7
  255. package/dist/src/limrun.js +1 -1
  256. package/dist/src/linux.js +1 -1
  257. package/dist/src/local-interactor-operation-set.js +1 -0
  258. package/dist/src/location-coordinates.js +1 -1
  259. package/dist/src/logcat.js +1 -1
  260. package/dist/src/macos-facade.js +1 -0
  261. package/dist/src/managed-device-scope.js +1 -0
  262. package/dist/src/mechanics.js +19 -0
  263. package/dist/src/metro.d.ts +4 -2
  264. package/dist/src/metro.js +1 -1
  265. package/dist/src/metro2.js +1 -1
  266. package/dist/src/mobile-snapshot-semantics.js +1 -1
  267. package/dist/src/multitouch-support.js +1 -0
  268. package/dist/src/numeric.js +1 -0
  269. package/dist/src/observability.js +1 -1
  270. package/dist/src/observation.js +2 -0
  271. package/dist/src/open-target-resolution.js +1 -0
  272. package/dist/src/output-path.js +1 -1
  273. package/dist/src/owner-identity.js +3 -1
  274. package/dist/src/parameterized-recorded-fill.js +1 -0
  275. package/dist/src/parser.js +1 -1
  276. package/dist/src/perf-facade.js +2 -0
  277. package/dist/src/perf-process-identity.js +1 -0
  278. package/dist/src/perf-runtime-operation-builder.js +1 -0
  279. package/dist/src/perf-runtime-plan.js +1 -0
  280. package/dist/src/perf-runtime.js +1 -0
  281. package/dist/src/perf.js +1 -4
  282. package/dist/src/physical-device-control.js +1 -1
  283. package/dist/src/physical-device-facade.js +1 -0
  284. package/dist/src/platform-request-scope.js +1 -0
  285. package/dist/src/platform-runtime-android-adb-host.js +1 -0
  286. package/dist/src/platform-runtime-android-mechanics.js +1 -0
  287. package/dist/src/platform-runtime-app-log-android-transport.js +1 -1
  288. package/dist/src/platform-runtime-app-log-process.js +1 -1
  289. package/dist/src/platform-runtime-host.js +1 -1
  290. package/dist/src/platform-runtime-managed-owner.js +1 -0
  291. package/dist/src/platform-runtime-network-web-transport.js +1 -1
  292. package/dist/src/platform-runtime-open-target.js +1 -1
  293. package/dist/src/platform-runtime-operation-host.js +4 -4
  294. package/dist/src/platform-runtime-runtime-hints.js +2 -2
  295. package/dist/src/platform-runtime-screen-recording-android-host.js +1 -1
  296. package/dist/src/platform-runtime-screen-recording-apple-runner-host.js +1 -1
  297. package/dist/src/platform-runtime-screen-recording-apple-runner-transport.js +1 -1
  298. package/dist/src/platform-runtime-screen-recording-apple-simulator-host.js +1 -1
  299. package/dist/src/platform-runtime-screen-recording-apple-transport.js +1 -1
  300. package/dist/src/platform-runtime-screen-recording-finalizer-host.js +1 -1
  301. package/dist/src/platform-runtime-screen-recording-harmony-host.js +1 -1
  302. package/dist/src/platform-runtime-screen-recording-process-host.js +1 -0
  303. package/dist/src/platform-runtime-screen-recording-web-host.js +1 -1
  304. package/dist/src/platform-runtime-unavailable.js +1 -0
  305. package/dist/src/platform-runtime.js +1 -1
  306. package/dist/src/platform-runtime2.js +1 -1
  307. package/dist/src/plist.js +1 -1
  308. package/dist/src/png-worker-contract.js +1 -1
  309. package/dist/src/prepare-kind.js +1 -0
  310. package/dist/src/process-exit.js +1 -1
  311. package/dist/src/process.js +1 -0
  312. package/dist/src/provider-device-runtime.js +1 -0
  313. package/dist/src/provider-session-ownership.js +2 -0
  314. package/dist/src/provider-webdriver.js +1 -1
  315. package/dist/src/provider.js +1 -1
  316. package/dist/src/providers.js +1 -0
  317. package/dist/src/proxy.js +2 -2
  318. package/dist/src/react-native-overlay.js +1 -0
  319. package/dist/src/react-native.js +1 -1
  320. package/dist/src/record-runtime.js +1 -1
  321. package/dist/src/record-trace.js +1 -1
  322. package/dist/src/rect-center.js +1 -0
  323. package/dist/src/register-builtins.js +1 -0
  324. package/dist/src/registry.js +40 -28
  325. package/dist/src/remote-config2.js +1 -1
  326. package/dist/src/replay-divergence.js +2 -0
  327. package/dist/src/replay.js +2 -2
  328. package/dist/src/reporting.js +8 -7
  329. package/dist/src/request-admission.js +1 -0
  330. package/dist/src/request-cancel.js +1 -0
  331. package/dist/src/request-cancellation.js +1 -0
  332. package/dist/src/request-progress-protocol.js +1 -0
  333. package/dist/src/request-providers.js +1 -0
  334. package/dist/src/request-runtime-binding.js +1 -0
  335. package/dist/src/request.js +1 -0
  336. package/dist/src/result-serialization.js +1 -0
  337. package/dist/src/retry.js +1 -0
  338. package/dist/src/rolldown-runtime.js +1 -1
  339. package/dist/src/runner-cache-metadata.js +2 -0
  340. package/dist/src/runner-client.js +1 -1
  341. package/dist/src/runner-disposal.js +1 -1
  342. package/dist/src/runner-operations-facade.js +1 -0
  343. package/dist/src/runner-owner-facade.js +1 -0
  344. package/dist/src/runner-owner-state.js +1 -0
  345. package/dist/src/runner-presentation.js +1 -0
  346. package/dist/src/runner.js +1 -0
  347. package/dist/src/{runtime-transport.js → runtime-transport-hints.js} +1 -1
  348. package/dist/src/runtime.js +3 -3
  349. package/dist/src/runtime2.js +4 -4
  350. package/dist/src/runtime3.js +1 -1
  351. package/dist/src/runtime4.js +5 -3
  352. package/dist/src/runtime5.js +1 -1
  353. package/dist/src/runtime6.js +1 -1
  354. package/dist/src/runtime7.js +1 -1
  355. package/dist/src/runtime8.js +1 -1
  356. package/dist/src/runtime9.js +1 -1
  357. package/dist/src/scoped-provider.js +1 -1
  358. package/dist/src/screen-recording-completion.js +1 -0
  359. package/dist/src/screen-recording-live-handle.js +1 -0
  360. package/dist/src/screen-recording-options.js +1 -0
  361. package/dist/src/screen-recording-resource-recovery.js +1 -0
  362. package/dist/src/screen-recording-runtime.js +1 -0
  363. package/dist/src/screenshot-density.js +1 -1
  364. package/dist/src/screenshot-result.js +1 -6
  365. package/dist/src/screenshot-runtime.js +1 -0
  366. package/dist/src/screenshot.js +1 -1
  367. package/dist/src/screenshot2.js +1 -0
  368. package/dist/src/screenshot3.js +5 -0
  369. package/dist/src/script.js +1 -0
  370. package/dist/src/scroll-command.js +1 -0
  371. package/dist/src/scroll-gesture.js +1 -0
  372. package/dist/src/scroll-indicator.js +1 -0
  373. package/dist/src/sdk-batch-runner.d.ts +23 -6
  374. package/dist/src/sdk-batch-runner.js +2 -3
  375. package/dist/src/sdk-batch.js +2 -0
  376. package/dist/src/sdk-contracts.d.ts +170 -41
  377. package/dist/src/sdk-contracts.js +2 -2
  378. package/dist/src/sdk-device.js +1 -1
  379. package/dist/src/sdk-finders.d.ts +1 -1
  380. package/dist/src/sdk-finders.js +1 -1
  381. package/dist/src/sdk-io.js +1 -1
  382. package/dist/src/sdk-remote-config.d.ts +1 -1
  383. package/dist/src/sdk-remote-config.js +1 -1
  384. package/dist/src/sdk-selectors.d.ts +524 -140
  385. package/dist/src/sdk-selectors.js +1 -1
  386. package/dist/src/selection.js +1 -0
  387. package/dist/src/selector-runtime.js +1 -1
  388. package/dist/src/selectors.js +1 -1
  389. package/dist/src/semantic-index.js +1 -0
  390. package/dist/src/server.js +4 -4
  391. package/dist/src/session-allocation.js +1 -0
  392. package/dist/src/session-artifact-paths.js +1 -0
  393. package/dist/src/session-observation.js +1 -0
  394. package/dist/src/session-paths.js +1 -0
  395. package/dist/src/session-repair-tombstone.js +1 -0
  396. package/dist/src/session-routing.js +1 -0
  397. package/dist/src/session-runtime-admission.js +1 -0
  398. package/dist/src/session-snapshot.js +1 -0
  399. package/dist/src/session-store.js +4 -0
  400. package/dist/src/session-target-evidence.js +1 -1
  401. package/dist/src/session2.js +5 -27
  402. package/dist/src/settings.js +1 -0
  403. package/dist/src/shared.js +2 -2
  404. package/dist/src/simctl-facade.js +1 -0
  405. package/dist/src/simctl.js +1 -1
  406. package/dist/src/simulator-facade.js +1 -0
  407. package/dist/src/simulator.js +1 -1
  408. package/dist/src/snapshot-capture-annotations.js +1 -0
  409. package/dist/src/snapshot-desktop-projection.js +1 -0
  410. package/dist/src/snapshot-diagnostics.js +1 -0
  411. package/dist/src/snapshot-process.js +1 -0
  412. package/dist/src/snapshot-quality-backend-capabilities.js +1 -0
  413. package/dist/src/snapshot-quality-verdict.js +1 -0
  414. package/dist/src/snapshot-runtime-binding.js +1 -0
  415. package/dist/src/snapshot-runtime.js +1 -1
  416. package/dist/src/snapshot-scope.js +1 -1
  417. package/dist/src/snapshot-state.js +1 -0
  418. package/dist/src/snapshot.js +1 -8
  419. package/dist/src/snapshot2.js +1 -1
  420. package/dist/src/snapshot3.js +1 -1
  421. package/dist/src/snapshot4.js +1 -0
  422. package/dist/src/src.js +1 -1
  423. package/dist/src/src2.js +1 -1
  424. package/dist/src/src3.js +1 -2
  425. package/dist/src/src4.js +1 -1
  426. package/dist/src/src5.js +1 -0
  427. package/dist/src/src6.js +1 -0
  428. package/dist/src/src7.js +2 -0
  429. package/dist/src/src8.js +115 -0
  430. package/dist/src/startup-recovery-fence.js +1 -0
  431. package/dist/src/status-markers.js +1 -0
  432. package/dist/src/string-enum.js +1 -1
  433. package/dist/src/success-text.js +1 -0
  434. package/dist/src/surface-snapshot.js +1 -0
  435. package/dist/src/surface-snapshot2.js +1 -0
  436. package/dist/src/takeover.js +5 -0
  437. package/dist/src/tool-provider-facade.js +1 -0
  438. package/dist/src/tool-provider.js +4 -3
  439. package/dist/src/tool-provider2.js +1 -1
  440. package/dist/src/tool-provider3.js +1 -1
  441. package/dist/src/toolchain-probe.js +2 -0
  442. package/dist/src/touch-reference-frame.js +1 -0
  443. package/dist/src/touch-runtime.js +1 -0
  444. package/dist/src/transport.js +3 -0
  445. package/dist/src/tree.js +1 -0
  446. package/dist/src/ttl-memo.js +1 -0
  447. package/dist/src/tv-remote.js +1 -0
  448. package/dist/src/type-text-runtime.js +1 -0
  449. package/dist/src/undici.js +99 -0
  450. package/dist/src/update-check.js +1 -1
  451. package/dist/src/verified-file.js +1 -1
  452. package/dist/src/version.js +1 -1
  453. package/dist/src/video.js +2 -2
  454. package/dist/src/web.js +2 -1
  455. package/dist/src/web2.js +1 -0
  456. package/dist/src/webdriver-ios-snapshot.js +1 -0
  457. package/dist/src/webdriver-source.js +1 -0
  458. package/dist/src/window-state.js +1 -0
  459. package/linux/atspi-dump.py +54 -9
  460. package/package.json +58 -35
  461. package/server.json +9 -3
  462. package/android/ime-helper/dist/agent-device-android-ime-helper-0.20.10.apk.sha256 +0 -1
  463. package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.20.10.apk +0 -0
  464. package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.20.10.apk.sha256 +0 -1
  465. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CoordinateTextEntryTests.swift +0 -4
  466. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+FlatSnapshotFiltering.swift +0 -168
  467. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+LifecycleCacheTests.swift +0 -6
  468. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+RecordingTests.swift +0 -4
  469. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SelectorMatchPolicyTests.swift +0 -4
  470. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SnapshotPresentation.swift +0 -183
  471. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SnapshotPresentationTests.swift +0 -0
  472. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SnapshotTraversalIdentityTests.swift +0 -4
  473. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextEntryPolicyTests.swift +0 -4
  474. package/dist/src/appearance.js +0 -1
  475. package/dist/src/apps.js +0 -5
  476. package/dist/src/audio-probe.js +0 -1
  477. package/dist/src/cli-grammar.js +0 -1
  478. package/dist/src/command-catalog.js +0 -1
  479. package/dist/src/debug-symbols.js +0 -5
  480. package/dist/src/device-input-state.js +0 -2
  481. package/dist/src/device-ready.js +0 -1
  482. package/dist/src/dispatch.js +0 -1
  483. package/dist/src/handler-utils.js +0 -1
  484. package/dist/src/helper-package-install.js +0 -1
  485. package/dist/src/host-process.js +0 -3
  486. package/dist/src/ime-lifecycle.js +0 -2
  487. package/dist/src/ime-recovery-marker.js +0 -1
  488. package/dist/src/input-actions3.js +0 -1
  489. package/dist/src/install-artifact.js +0 -1
  490. package/dist/src/install-artifact2.js +0 -1
  491. package/dist/src/interaction-common.js +0 -1
  492. package/dist/src/interaction-runtime.js +0 -1
  493. package/dist/src/interaction-snapshot.js +0 -1
  494. package/dist/src/manifest.js +0 -1
  495. package/dist/src/method-scope.js +0 -1
  496. package/dist/src/png-resize.js +0 -1
  497. package/dist/src/process-lock.js +0 -1
  498. package/dist/src/runner-lease.js +0 -1
  499. package/dist/src/runner-sequence.js +0 -1
  500. package/dist/src/script-source-bundle.js +0 -16
  501. package/dist/src/sdk-android-adb.d.ts +0 -245
  502. package/dist/src/sdk-android-adb.js +0 -4
  503. package/dist/src/sdk.js +0 -1
  504. package/dist/src/setting-state.js +0 -1
  505. package/dist/src/shell-quote.js +0 -1
  506. package/dist/src/snapshot-session.js +0 -1
  507. package/dist/src/timing-safe-equal.js +0 -1
  508. package/dist/src/unsupported-interactor.js +0 -1
  509. package/dist/src/verdict.js +0 -1
  510. /package/dist/src/{recording-telemetry.js → telemetry.js} +0 -0
@@ -1,4 +1,5 @@
1
1
  import XCTest
2
+ import AgentDeviceSnapshotPresentation
2
3
 
3
4
  extension RunnerTests {
4
5
  private static let axSnapshotErrorCode = "IOS_AX_SNAPSHOT_FAILED"
@@ -15,78 +16,76 @@ extension RunnerTests {
15
16
  let queryRoot: XCUIElement
16
17
  let rootSnapshot: XCUIElementSnapshot
17
18
  let viewport: CGRect
18
- let flatSnapshots: [XCUIElementSnapshot]
19
- let snapshotRanges: [ObjectIdentifier: (Int, Int)]
20
- let maxDepth: Int
21
19
  }
22
20
 
23
21
  private struct SnapshotEvaluation {
24
22
  let label: String
25
23
  let identifier: String
26
24
  let valueText: String?
27
- let hittable: Bool
28
25
  let focused: Bool
29
26
  let selected: Bool
30
- let visible: Bool
31
27
  }
32
28
 
33
29
  private struct SnapshotTraversalEntry {
34
30
  let snapshot: XCUIElementSnapshot
35
31
  let depth: Int
36
- let visibleDepth: Int
37
32
  let parentIndex: Int?
38
- let nearestScrollAnchor: (index: Int, rect: CGRect)?
39
- let projectionCursor: FlatSnapshotProjectionCursor
33
+ let parentPresentedDepth: Int
34
+ let parentTraversal: SnapshotVisibilityFold.TraversalState
40
35
  }
41
36
 
42
37
  struct SnapshotCaptureFailure: Error {
43
38
  let code: String
44
39
  let message: String
45
40
  let hint: String
41
+ let qualityReasonCode: String?
42
+
43
+ init(code: String, message: String, hint: String, qualityReasonCode: String? = nil) {
44
+ self.code = code
45
+ self.message = message
46
+ self.hint = hint
47
+ self.qualityReasonCode = qualityReasonCode
48
+ }
46
49
  }
47
50
 
48
- // MARK: - Snapshot Entry
51
+
52
+
53
+
54
+ static let elementTypeNamesByRawValue = [
55
+ XCUIElement.ElementType.application.rawValue: "Application",
56
+ XCUIElement.ElementType.window.rawValue: "Window",
57
+ XCUIElement.ElementType.button.rawValue: "Button",
58
+ XCUIElement.ElementType.cell.rawValue: "Cell",
59
+ XCUIElement.ElementType.staticText.rawValue: "StaticText",
60
+ XCUIElement.ElementType.textField.rawValue: "TextField",
61
+ XCUIElement.ElementType.textView.rawValue: "TextView",
62
+ XCUIElement.ElementType.secureTextField.rawValue: "SecureTextField",
63
+ XCUIElement.ElementType.switch.rawValue: "Switch",
64
+ XCUIElement.ElementType.slider.rawValue: "Slider",
65
+ XCUIElement.ElementType.link.rawValue: "Link",
66
+ XCUIElement.ElementType.image.rawValue: "Image",
67
+ XCUIElement.ElementType.navigationBar.rawValue: "NavigationBar",
68
+ XCUIElement.ElementType.tabBar.rawValue: "TabBar",
69
+ XCUIElement.ElementType.collectionView.rawValue: "CollectionView",
70
+ XCUIElement.ElementType.table.rawValue: "Table",
71
+ XCUIElement.ElementType.scrollView.rawValue: "ScrollView",
72
+ XCUIElement.ElementType.toolbar.rawValue: "Toolbar",
73
+ XCUIElement.ElementType.searchField.rawValue: "SearchField",
74
+ XCUIElement.ElementType.segmentedControl.rawValue: "SegmentedControl",
75
+ XCUIElement.ElementType.stepper.rawValue: "Stepper",
76
+ XCUIElement.ElementType.picker.rawValue: "Picker",
77
+ XCUIElement.ElementType.activityIndicator.rawValue: "ActivityIndicator",
78
+ XCUIElement.ElementType.progressIndicator.rawValue: "ProgressIndicator",
79
+ XCUIElement.ElementType.checkBox.rawValue: "CheckBox",
80
+ XCUIElement.ElementType.menuItem.rawValue: "MenuItem",
81
+ XCUIElement.ElementType.webView.rawValue: "WebView",
82
+ XCUIElement.ElementType.other.rawValue: "Other",
83
+ 19: "Keyboard",
84
+ 20: "Key"
85
+ ]
49
86
 
50
87
  func elementTypeName(_ type: XCUIElement.ElementType) -> String {
51
- switch type {
52
- case .application: return "Application"
53
- case .window: return "Window"
54
- case .button: return "Button"
55
- case .cell: return "Cell"
56
- case .staticText: return "StaticText"
57
- case .textField: return "TextField"
58
- case .textView: return "TextView"
59
- case .secureTextField: return "SecureTextField"
60
- case .switch: return "Switch"
61
- case .slider: return "Slider"
62
- case .link: return "Link"
63
- case .image: return "Image"
64
- case .navigationBar: return "NavigationBar"
65
- case .tabBar: return "TabBar"
66
- case .collectionView: return "CollectionView"
67
- case .table: return "Table"
68
- case .scrollView: return "ScrollView"
69
- case .toolbar: return "Toolbar"
70
- case .searchField: return "SearchField"
71
- case .segmentedControl: return "SegmentedControl"
72
- case .stepper: return "Stepper"
73
- case .picker: return "Picker"
74
- case .activityIndicator: return "ActivityIndicator"
75
- case .progressIndicator: return "ProgressIndicator"
76
- case .checkBox: return "CheckBox"
77
- case .menuItem: return "MenuItem"
78
- case .webView: return "WebView"
79
- case .other: return "Other"
80
- default:
81
- switch type.rawValue {
82
- case 19:
83
- return "Keyboard"
84
- case 20:
85
- return "Key"
86
- default:
87
- return "Element(\(type.rawValue))"
88
- }
89
- }
88
+ Self.elementTypeNamesByRawValue[type.rawValue] ?? "Element(\(type.rawValue))"
90
89
  }
91
90
 
92
91
  static let structuralOnlyNodeTypes: Set<String> = [
@@ -112,11 +111,11 @@ extension RunnerTests {
112
111
 
113
112
  static let flatInteractiveFallbackBudget: TimeInterval = 1.0
114
113
 
115
- // The single production entry point -- always compiled, no unit-test overload. A unit test
116
- // exercises this exact function; the only injectable seam lives inside
117
- // `boundedBlockingSystemAlertSnapshot`'s probe closure (see `systemModalProbeOverrideForTesting`
118
- // in RunnerTests.swift), so reverting this entry point to bypass the bounded probe fails the
119
- // regression test.
114
+
115
+
116
+
117
+
118
+
120
119
  func snapshotFast(app: XCUIApplication, options: PresentationOptions) throws -> DataPayload {
121
120
  let deadline = Date().addingTimeInterval(Self.snapshotPlanBudget)
122
121
  if let blocking = boundedBlockingSystemAlertSnapshot(deadline: deadline) {
@@ -133,7 +132,7 @@ extension RunnerTests {
133
132
 
134
133
  func recursiveTreeSnapshotAcquisition(
135
134
  context: SnapshotTraversalContext,
136
- options: PresentationOptions
135
+ hint: CaptureHint
137
136
  ) -> SnapshotAcquisition {
138
137
  var cachedDescendantElements: [XCUIElement]?
139
138
  func collapsedTabDescendants() -> [XCUIElement] {
@@ -147,110 +146,68 @@ extension RunnerTests {
147
146
  return result.elements
148
147
  }
149
148
 
149
+
150
+
151
+
152
+
153
+
150
154
  var nodes: [RawAXNode] = []
151
- var hiddenContentHintsByNodeIndex: [Int: (above: Bool, below: Bool)] = [:]
152
- let rootEvaluation = evaluateSnapshot(context.rootSnapshot, in: context)
155
+ let rootEvaluation = evaluateSnapshot(context.rootSnapshot)
153
156
  nodes.append(
154
157
  makeSnapshotNode(
155
158
  snapshot: context.rootSnapshot,
156
159
  evaluation: rootEvaluation,
157
160
  depth: 0,
158
161
  index: 0,
159
- parentIndex: nil
162
+ parentIndex: nil,
163
+ viewport: context.viewport
160
164
  )
161
165
  )
162
- if context.maxDepth > 0 {
166
+ let shouldVisitRootChildren = SnapshotPresentation.shouldAcquireChildren(
167
+ for: hint,
168
+ rawDepth: 0,
169
+ regularPresentedDepth: 0
170
+ )
171
+ if shouldVisitRootChildren {
163
172
  appendCollapsedTabFallbackNodes(
164
173
  to: &nodes,
165
174
  containerSnapshot: context.rootSnapshot,
166
175
  resolveElements: collapsedTabDescendants,
167
176
  depth: 1,
168
- parentIndex: 0
177
+ parentIndex: 0,
178
+ viewport: context.viewport
169
179
  )
170
180
  }
171
181
 
172
182
  var seen = Set<String>()
173
- let rootScrollAnchor = scrollContainerAnchor(
174
- for: context.rootSnapshot,
175
- visible: rootEvaluation.visible,
176
- nodeIndex: 0
177
- )
178
- #if os(iOS)
179
- let rootProjection = flatSnapshotProjectionTransition(
180
- frame: context.rootSnapshot.frame,
181
- intersectsViewportAndScrollClip: rootEvaluation.visible,
182
- elementType: context.rootSnapshot.elementType,
183
- hasChildren: !context.rootSnapshot.children.isEmpty,
184
- cursor: .root
185
- )
186
- let rootDescendantProjectionCursor = rootProjection.decision.descendants
187
- #else
188
- let rootDescendantProjectionCursor = FlatSnapshotProjectionCursor.root
189
- #endif
190
- var stack: [SnapshotTraversalEntry] = context.rootSnapshot.children.map {
191
- SnapshotTraversalEntry(
192
- snapshot: $0,
193
- depth: 1,
194
- visibleDepth: 1,
195
- parentIndex: 0,
196
- nearestScrollAnchor: rootScrollAnchor,
197
- projectionCursor: rootDescendantProjectionCursor
198
- )
183
+ var stack: [SnapshotTraversalEntry] = []
184
+ if shouldVisitRootChildren {
185
+ stack = context.rootSnapshot.children.map {
186
+ SnapshotTraversalEntry(
187
+ snapshot: $0,
188
+ depth: 1,
189
+ parentIndex: 0,
190
+ parentPresentedDepth: 0,
191
+ parentTraversal: .root
192
+ )
193
+ }
199
194
  }
200
195
 
201
196
  while let entry = stack.popLast() {
202
197
  let snapshot = entry.snapshot
203
198
  let depth = entry.depth
204
- let visibleDepth = entry.visibleDepth
205
199
  let parentIndex = entry.parentIndex
206
- let nearestScrollAnchor = entry.nearestScrollAnchor
207
- let projectionCursor = entry.projectionCursor
208
- if let limit = options.depth, depth > limit { continue }
200
+ if let limit = hint.rawTraversalDepth, depth > limit { continue }
209
201
 
210
- let evaluation = evaluateSnapshot(snapshot, in: context)
211
- let intersectsViewportAndScrollClip = isVisibleInRegularSnapshot(
212
- snapshot.frame,
213
- viewport: context.viewport,
214
- scrollContainerAnchor: nearestScrollAnchor
215
- )
216
- #if os(iOS)
217
- let projectionTransition = flatSnapshotProjectionTransition(
218
- frame: snapshot.frame,
219
- intersectsViewportAndScrollClip: intersectsViewportAndScrollClip,
220
- elementType: snapshot.elementType,
221
- hasChildren: !snapshot.children.isEmpty,
222
- cursor: projectionCursor
223
- )
224
- let projection = projectionTransition.decision
225
- #else
226
- let projectionTransition = FlatSnapshotProjectionTransition(
227
- decision: FlatSnapshotProjectionDecision(
228
- presentationVisible: intersectsViewportAndScrollClip,
229
- descendants: .root
230
- ),
231
- hiddenContentFrame: !intersectsViewportAndScrollClip ? snapshot.frame : nil
232
- )
233
- let projection = projectionTransition.decision
234
- #endif
235
- if let hiddenFrame = projectionTransition.hiddenContentFrame, let nearestScrollAnchor
236
- {
237
- rememberHiddenContentHint(
238
- for: hiddenFrame,
239
- relativeTo: nearestScrollAnchor,
240
- hints: &hiddenContentHintsByNodeIndex
241
- )
242
- }
243
- let include = shouldAcquireSnapshotNode(
202
+ let evaluation = evaluateSnapshot(snapshot)
203
+ let node = makeSnapshotNode(
244
204
  snapshot: snapshot,
245
- label: evaluation.label,
246
- identifier: evaluation.identifier,
247
- valueText: evaluation.valueText,
248
- options: options,
249
- hittable: evaluation.hittable,
250
- visible: projection.presentationVisible,
251
- regularSnapshot: true
205
+ evaluation: evaluation,
206
+ depth: depth,
207
+ index: nodes.count,
208
+ parentIndex: parentIndex,
209
+ viewport: context.viewport
252
210
  )
253
-
254
211
  let key = Self.snapshotTraversalIdentity(
255
212
  elementType: snapshot.elementType,
256
213
  label: evaluation.label,
@@ -262,67 +219,57 @@ extension RunnerTests {
262
219
  seen.insert(key)
263
220
  }
264
221
 
265
- let currentIndex = include && !isDuplicate ? nodes.count : parentIndex
266
- if depth < context.maxDepth {
267
- let nextVisibleDepth = include && !isDuplicate ? visibleDepth + 1 : visibleDepth
268
- let nextScrollContainerAnchor: (index: Int, rect: CGRect)?
269
- if include && !isDuplicate {
270
- nextScrollContainerAnchor =
271
- scrollContainerAnchor(
272
- for: snapshot,
273
- visible: intersectsViewportAndScrollClip,
274
- nodeIndex: currentIndex
275
- )
276
- ?? nearestScrollAnchor
277
- } else {
278
- nextScrollContainerAnchor = nearestScrollAnchor
279
- }
222
+ let currentIndex = !isDuplicate ? nodes.count : parentIndex
223
+ let transition = SnapshotPresentation.regularTraversalTransition(
224
+ for: node,
225
+ parentPresentedDepth: entry.parentPresentedDepth,
226
+ parentTraversal: entry.parentTraversal,
227
+ hint: hint,
228
+ rawDepth: depth,
229
+ viewport: context.viewport,
230
+ hasChildren: !snapshot.children.isEmpty,
231
+ isDuplicate: isDuplicate,
232
+ policy: .platformDefault
233
+ )
234
+ if transition.shouldVisitChildren {
280
235
  for child in snapshot.children.reversed() {
281
236
  stack.append(
282
237
  SnapshotTraversalEntry(
283
238
  snapshot: child,
284
239
  depth: depth + 1,
285
- visibleDepth: nextVisibleDepth,
286
240
  parentIndex: currentIndex,
287
- nearestScrollAnchor: nextScrollContainerAnchor,
288
- projectionCursor: projection.descendants
241
+ parentPresentedDepth: transition.presentedDepth,
242
+ parentTraversal: transition.traversal
289
243
  )
290
244
  )
291
245
  }
292
246
  }
293
247
 
294
- if !include || isDuplicate { continue }
248
+ if isDuplicate { continue }
295
249
 
296
- let index = nodes.count
297
- nodes.append(
298
- makeSnapshotNode(
299
- snapshot: snapshot,
300
- evaluation: evaluation,
301
- depth: min(context.maxDepth, visibleDepth),
302
- index: index,
303
- parentIndex: parentIndex
304
- )
305
- )
306
- if visibleDepth < context.maxDepth {
250
+ nodes.append(node)
251
+ if transition.shouldVisitChildren {
307
252
  appendCollapsedTabFallbackNodes(
308
253
  to: &nodes,
309
254
  containerSnapshot: snapshot,
310
255
  resolveElements: collapsedTabDescendants,
311
- depth: visibleDepth + 1,
312
- parentIndex: index
256
+ depth: depth + 1,
257
+ parentIndex: node.index,
258
+ viewport: context.viewport
313
259
  )
314
260
  }
315
-
316
261
  }
317
262
 
318
263
  return SnapshotAcquisition(
319
- nodes: applyHiddenContentHints(hiddenContentHintsByNodeIndex, to: nodes),
264
+ hint: hint,
265
+ nodes: nodes,
320
266
  truncated: false,
321
- effectiveDepth: nil
267
+ effectiveDepth: nil,
268
+ viewport: context.viewport
322
269
  )
323
270
  }
324
271
 
325
- // See `snapshotFast` above: the single production entry point, no unit-test overload.
272
+
326
273
  func snapshotRaw(app: XCUIApplication, options: PresentationOptions) throws -> DataPayload {
327
274
  let deadline = Date().addingTimeInterval(Self.snapshotPlanBudget)
328
275
  if let blocking = boundedBlockingSystemAlertSnapshot(deadline: deadline) {
@@ -337,19 +284,24 @@ extension RunnerTests {
337
284
  )
338
285
  }
339
286
 
340
- /// Runs the pre-plan SpringBoard system-modal probe as a bounded capture tier sharing the plan
341
- /// deadline, so a slow alert enumeration cannot bypass the snapshot timeout and stall (#1244).
287
+
288
+
342
289
  func boundedBlockingSystemAlertSnapshot(deadline: Date) -> DataPayload? {
343
290
  boundedBlockingSystemAlertSnapshotBody(deadline: deadline) { probeDeadline in
291
+
292
+
293
+
294
+
295
+
344
296
  return self.blockingSystemAlertSnapshot(deadline: probeDeadline)
345
297
  }
346
298
  }
347
299
 
348
- /// The real bounding/hook machinery used by `boundedBlockingSystemAlertSnapshot` above: the
349
- /// probe closure it's given always calls `self.blockingSystemAlertSnapshot` in production, and
350
- /// in unit-test builds may first consult `systemModalProbeOverrideForTesting`. Keeping this in
351
- /// one place means the `runMainThreadWork` wrap and the `onAbandoned`/`onDrained` hooks can
352
- /// never drift between what production runs and what the unit tests exercise.
300
+
301
+
302
+
303
+
304
+
353
305
  private func boundedBlockingSystemAlertSnapshotBody(
354
306
  deadline: Date,
355
307
  probe: @escaping (Date) -> DataPayload?
@@ -369,7 +321,6 @@ extension RunnerTests {
369
321
  let startedAt = Date()
370
322
  do {
371
323
  return try runMainThreadWork(
372
- command: nil,
373
324
  timeout: slice,
374
325
  timeoutError: {
375
326
  SnapshotCaptureFailure(
@@ -404,8 +355,8 @@ extension RunnerTests {
404
355
  #endif
405
356
  }
406
357
 
407
- /// The probe gets its own budget, clamped by whatever remains of the shared plan deadline, and
408
- /// 0 (skip entirely) once that deadline is already spent.
358
+
359
+
409
360
  static func systemModalProbeSlice(
410
361
  budget: TimeInterval,
411
362
  deadlineRemaining: TimeInterval
@@ -416,38 +367,28 @@ extension RunnerTests {
416
367
 
417
368
  func rawTreeSnapshotAcquisition(
418
369
  context: SnapshotTraversalContext,
419
- options: PresentationOptions
370
+ hint: CaptureHint
420
371
  ) throws -> SnapshotAcquisition {
421
372
  var nodes: [RawAXNode] = []
422
373
 
423
374
  func walk(_ snapshot: XCUIElementSnapshot, depth: Int, parentIndex: Int?) throws {
424
- if let limit = options.depth, depth > limit { return }
375
+ if let limit = hint.rawTraversalDepth, depth > limit { return }
425
376
 
426
- let evaluation = evaluateSnapshot(snapshot, in: context)
427
- let include = shouldAcquireSnapshotNode(
428
- snapshot: snapshot,
429
- label: evaluation.label,
430
- identifier: evaluation.identifier,
431
- valueText: evaluation.valueText,
432
- options: options,
433
- hittable: evaluation.hittable,
434
- visible: evaluation.visible
435
- )
436
- let currentIndex = include ? nodes.count : parentIndex
437
- if include {
438
- if nodes.count >= Self.rawSnapshotMaxNodes {
439
- throw rawSnapshotTooLargeFailure(nodeCount: nodes.count + 1)
440
- }
441
- nodes.append(
442
- makeSnapshotNode(
443
- snapshot: snapshot,
444
- evaluation: evaluation,
445
- depth: depth,
446
- index: nodes.count,
447
- parentIndex: parentIndex
448
- )
449
- )
377
+ let evaluation = evaluateSnapshot(snapshot)
378
+ if nodes.count >= Self.rawSnapshotMaxNodes {
379
+ throw rawSnapshotTooLargeFailure(nodeCount: nodes.count + 1)
450
380
  }
381
+ let currentIndex = nodes.count
382
+ nodes.append(
383
+ makeSnapshotNode(
384
+ snapshot: snapshot,
385
+ evaluation: evaluation,
386
+ depth: depth,
387
+ index: currentIndex,
388
+ parentIndex: parentIndex,
389
+ viewport: context.viewport
390
+ )
391
+ )
451
392
 
452
393
  let children = snapshot.children
453
394
  for child in children {
@@ -456,24 +397,36 @@ extension RunnerTests {
456
397
  }
457
398
 
458
399
  try walk(context.rootSnapshot, depth: 0, parentIndex: nil)
459
- return SnapshotAcquisition(nodes: nodes, truncated: false, effectiveDepth: nil)
400
+ return SnapshotAcquisition(
401
+ hint: hint,
402
+ nodes: nodes,
403
+ truncated: false,
404
+ effectiveDepth: nil,
405
+ viewport: context.viewport
406
+ )
460
407
  }
461
408
 
462
409
  func querySweepSnapshotAcquisition(
463
410
  app: XCUIApplication,
464
- options: PresentationOptions,
411
+ hint: CaptureHint,
465
412
  planDeadline: Date = .distantFuture
466
413
  ) -> SnapshotAcquisition {
467
414
  var nodes: [RawAXNode] = [
468
415
  interactiveRootNode(rect: .zero)
469
416
  ]
470
- if options.depth == 0 {
471
- return SnapshotAcquisition(nodes: nodes, truncated: false, effectiveDepth: nil)
417
+ if hint.rawTraversalDepth == 0 || hint.regularPresentedDepth == 0 {
418
+ return SnapshotAcquisition(
419
+ hint: hint,
420
+ nodes: nodes,
421
+ truncated: false,
422
+ effectiveDepth: nil,
423
+ viewport: .infinite
424
+ )
472
425
  }
473
426
 
474
- // Bounded by both its own sweep budget and the umbrella capture-plan deadline, so a
475
- // chained recovery tier can never push the plan past the main-thread watchdog (#1105).
476
- let sweepDeadline = options.interactiveOnly
427
+
428
+
429
+ let sweepDeadline = hint.interactiveOnly
477
430
  ? Date().addingTimeInterval(Self.flatInteractiveFallbackBudget)
478
431
  : Date.distantFuture
479
432
  let deadline = min(sweepDeadline, planDeadline)
@@ -492,8 +445,7 @@ extension RunnerTests {
492
445
  element: element,
493
446
  index: 0,
494
447
  parentIndex: 0,
495
- viewport: viewport,
496
- options: options
448
+ viewport: viewport
497
449
  ) else {
498
450
  continue
499
451
  }
@@ -512,9 +464,9 @@ extension RunnerTests {
512
464
  return left.type < right.type
513
465
  }
514
466
 
515
- // The synthetic root doubles as the daemon's viewport (find.ts prefers on-screen matches
516
- // inside nodes[0].rect): use the real screen viewport when capture produced a finite one,
517
- // so off-screen candidates can never inflate the root and masquerade as on-screen.
467
+
468
+
469
+
518
470
  let rootRect = viewport.isInfinite || viewport.isNull || viewport.isEmpty
519
471
  ? interactiveRootFrame(for: candidates)
520
472
  : viewport
@@ -539,15 +491,21 @@ extension RunnerTests {
539
491
  )
540
492
  )
541
493
  }
542
- return SnapshotAcquisition(nodes: nodes, truncated: truncated, effectiveDepth: nil)
494
+ return SnapshotAcquisition(
495
+ hint: hint,
496
+ nodes: nodes,
497
+ truncated: truncated,
498
+ effectiveDepth: nil,
499
+ viewport: viewport
500
+ )
543
501
  }
544
502
 
545
503
  func snapshotAccessibilityUnavailable(failure: SnapshotCaptureFailure) -> DataPayload {
546
504
  NSLog("AGENT_DEVICE_RUNNER_SNAPSHOT_AX_UNAVAILABLE=%@", failure.message)
547
505
  runnerAccessibilityHealth = .unavailable
548
506
  invalidateCachedTarget(reason: Self.axSnapshotUnavailableReason)
549
- // This is a planned terminal result, so it carries the structured verdict like every other
550
- // planned snapshot — downstream sparse handling keys off the verdict, not node shapes.
507
+
508
+
551
509
  return sparseTruncatedSnapshotPayload(
552
510
  message: recoveredSnapshotMessage(failure),
553
511
  snapshotQuality: SnapshotQuality(
@@ -593,6 +551,248 @@ extension RunnerTests {
593
551
  }
594
552
 
595
553
 
554
+
555
+
556
+
557
+
558
+
559
+
560
+
561
+
562
+
563
+
564
+
565
+
566
+
567
+
568
+
569
+
570
+
571
+
572
+
573
+
574
+
575
+
576
+
577
+
578
+
579
+
580
+
581
+
582
+
583
+
584
+
585
+
586
+
587
+
588
+
589
+
590
+
591
+
592
+
593
+
594
+
595
+
596
+
597
+
598
+
599
+
600
+
601
+
602
+
603
+
604
+
605
+
606
+
607
+
608
+
609
+
610
+
611
+
612
+
613
+
614
+
615
+
616
+
617
+
618
+
619
+
620
+
621
+
622
+
623
+
624
+
625
+
626
+
627
+
628
+
629
+
630
+
631
+
632
+
633
+
634
+
635
+
636
+
637
+
638
+
639
+
640
+
641
+
642
+
643
+
644
+
645
+
646
+
647
+
648
+
649
+
650
+
651
+
652
+
653
+
654
+
655
+
656
+
657
+
658
+
659
+
660
+
661
+
662
+
663
+
664
+
665
+
666
+
667
+
668
+
669
+
670
+
671
+
672
+
673
+
674
+
675
+
676
+
677
+
678
+
679
+
680
+
681
+
682
+
683
+
684
+
685
+
686
+
687
+
688
+
689
+
690
+
691
+
692
+
693
+
694
+
695
+
696
+
697
+
698
+
699
+
700
+
701
+
702
+
703
+
704
+
705
+
706
+
707
+
708
+
709
+
710
+
711
+
712
+
713
+
714
+
715
+
716
+
717
+
718
+
719
+
720
+
721
+
722
+
723
+
724
+
725
+
726
+
727
+
728
+
729
+
730
+
731
+
732
+
733
+
734
+
735
+
736
+
737
+
738
+
739
+
740
+
741
+
742
+
743
+
744
+
745
+
746
+
747
+
748
+
749
+
750
+
751
+
752
+
753
+
754
+
755
+
756
+
757
+
758
+
759
+
760
+
761
+
762
+
763
+
764
+
765
+
766
+
767
+
768
+
769
+
770
+
771
+
772
+
773
+
774
+
775
+
776
+
777
+
778
+
779
+
780
+
781
+
782
+
783
+
784
+
785
+
786
+
787
+
788
+
789
+
790
+
791
+
792
+
793
+
794
+
795
+
596
796
  private func interactiveRootNode(rect: CGRect) -> RawAXNode {
597
797
  RawAXNode(
598
798
  index: 0,
@@ -630,96 +830,31 @@ extension RunnerTests {
630
830
  )
631
831
  }
632
832
 
633
- // MARK: - Snapshot Filtering
634
-
635
- /// Conservative acquisition gate. Regular capture keeps every node its visibility policy can
636
- /// supply; `SnapshotPresentation` alone decides eligibility after the backend returns.
637
- private func shouldAcquireSnapshotNode(
638
- snapshot: XCUIElementSnapshot,
639
- label: String,
640
- identifier: String,
641
- valueText: String?,
642
- options: PresentationOptions,
643
- hittable: Bool,
644
- visible: Bool,
645
- regularSnapshot: Bool = false
646
- ) -> Bool {
647
- let type = snapshot.elementType
648
- let hasContent = !label.isEmpty || !identifier.isEmpty || (valueText != nil)
649
- #if os(iOS)
650
- if regularSnapshot && !visible && type != .application && type != .window {
651
- return false
652
- }
653
- #endif
654
- if regularSnapshot {
655
- #if os(macOS)
656
- if options.interactiveOnly && !visible && type != .application {
657
- return false
658
- }
659
- #endif
660
- if type == .application || type == .window { return true }
661
- return visible
662
- }
663
- if options.interactiveOnly {
664
- if isScrollableContainer(snapshot, visible: visible) { return true }
665
- if interactiveTypes.contains(type) { return true }
666
- if hittable && type != .other { return true }
667
- if hasContent { return true }
668
- return false
669
- }
670
- return true
671
- }
672
833
 
673
- private func computedSnapshotHittable(
674
- _ snapshot: XCUIElementSnapshot,
675
- viewport: CGRect,
676
- laterNodes: ArraySlice<XCUIElementSnapshot>
677
- ) -> Bool {
678
- guard snapshot.isEnabled else { return false }
679
- let frame = snapshot.frame
680
- if frame.isNull || frame.isEmpty { return false }
681
- let center = CGPoint(x: frame.midX, y: frame.midY)
682
- if !viewport.contains(center) { return false }
683
- for node in laterNodes {
684
- if !isOccludingType(node.elementType) { continue }
685
- let nodeFrame = node.frame
686
- if nodeFrame.isNull || nodeFrame.isEmpty { continue }
687
- if nodeFrame.contains(center) { return false }
688
- }
689
- return true
690
- }
691
834
 
692
835
  func makeSnapshotTraversalContext(
693
836
  app: XCUIApplication,
694
- options: PresentationOptions,
837
+ hint: CaptureHint,
695
838
  captureDeadline: Date = .distantFuture,
696
839
  treeCaptureSliceBudgetOverride: TimeInterval? = nil
697
840
  ) throws -> SnapshotTraversalContext? {
698
- let (viewport, queryRoot) = try runMainThreadWork(
699
- command: nil,
841
+ let viewport = try runMainThreadWork(
700
842
  timeout: min(1.0, max(0.1, captureDeadline.timeIntervalSinceNow)),
701
843
  timeoutError: snapshotMainThreadTimeoutError("preparing tree snapshot")
702
844
  ) {
703
- (
704
- self.safeSnapshotViewport(app: app),
705
- options.scope.flatMap { self.findScopeElement(app: app, scope: $0) } ?? app
706
- )
845
+ self.safeSnapshotViewport(app: app)
707
846
  }
708
847
 
709
848
  let treeSliceBudget = treeCaptureSliceBudgetOverride ?? treeCaptureSliceBudget
710
849
  let slice = min(treeSliceBudget, max(0.5, captureDeadline.timeIntervalSinceNow))
711
- guard let rootSnapshot = try captureSnapshotRootBounded(queryRoot, sliceSeconds: slice) else {
850
+ guard let rootSnapshot = try captureSnapshotRootBounded(app, sliceSeconds: slice) else {
712
851
  return nil
713
852
  }
714
853
 
715
- let (flatSnapshots, snapshotRanges) = flattenedSnapshots(rootSnapshot)
716
854
  return SnapshotTraversalContext(
717
- queryRoot: queryRoot,
855
+ queryRoot: app,
718
856
  rootSnapshot: rootSnapshot,
719
- viewport: viewport,
720
- flatSnapshots: flatSnapshots,
721
- snapshotRanges: snapshotRanges,
722
- maxDepth: options.depth ?? Int.max
857
+ viewport: viewport
723
858
  )
724
859
  }
725
860
 
@@ -731,8 +866,8 @@ extension RunnerTests {
731
866
  return abandonedTreeCaptureCount > 0
732
867
  }
733
868
 
734
- /// The watchdog abandoned one unit of XCTest main-thread capture work that is still draining on
735
- /// main; XCTest-backed snapshot tiers skip (`hasAbandonedTreeCapture`) until a matching release.
869
+
870
+
736
871
  func retainAbandonedXCTestChannelWork() {
737
872
  treeCaptureLock.lock()
738
873
  abandonedTreeCaptureCount += 1
@@ -745,11 +880,11 @@ extension RunnerTests {
745
880
  treeCaptureLock.unlock()
746
881
  }
747
882
 
748
- /// Runs the blocking tree-snapshot XPC on the main thread bounded by `sliceSeconds`. On
749
- /// timeout the XPC keeps running on main (it cannot be cancelled); the capture is marked
750
- /// abandoned so plans avoid XCTest-backed tiers until it drains, the tree backend is penalized
751
- /// for this bundle, and the plan moves to the platform's independent recovery tier when one
752
- /// exists (#1105/#1122).
883
+
884
+
885
+
886
+
887
+
753
888
  private func captureSnapshotRootBounded(
754
889
  _ element: XCUIElement,
755
890
  sliceSeconds: TimeInterval
@@ -758,7 +893,6 @@ extension RunnerTests {
758
893
  return try captureSnapshotRoot(element)
759
894
  }
760
895
  return try runMainThreadWork(
761
- command: nil,
762
896
  timeout: sliceSeconds,
763
897
  timeoutError: treeCaptureTimeoutError(sliceSeconds: sliceSeconds),
764
898
  onAbandoned: {
@@ -856,26 +990,16 @@ extension RunnerTests {
856
990
  failure.code == Self.axSnapshotErrorCode || isAxIllegalArgument(failure.message)
857
991
  }
858
992
 
859
- private func evaluateSnapshot(
860
- _ snapshot: XCUIElementSnapshot,
861
- in context: SnapshotTraversalContext
862
- ) -> SnapshotEvaluation {
993
+ private func evaluateSnapshot(_ snapshot: XCUIElementSnapshot) -> SnapshotEvaluation {
863
994
  let label = aggregatedLabel(for: snapshot) ?? snapshot.label.trimmingCharacters(in: .whitespacesAndNewlines)
864
995
  let identifier = snapshot.identifier.trimmingCharacters(in: .whitespacesAndNewlines)
865
996
  let valueText = snapshotValueText(snapshot)
866
- let laterNodes = laterSnapshots(
867
- for: snapshot,
868
- in: context.flatSnapshots,
869
- ranges: context.snapshotRanges
870
- )
871
997
  return SnapshotEvaluation(
872
998
  label: label,
873
999
  identifier: identifier,
874
1000
  valueText: valueText,
875
- hittable: computedSnapshotHittable(snapshot, viewport: context.viewport, laterNodes: laterNodes),
876
1001
  focused: snapshotHasFocus(snapshot),
877
- selected: snapshotIsSelected(snapshot),
878
- visible: isVisibleInViewport(snapshot.frame, context.viewport)
1002
+ selected: snapshotIsSelected(snapshot)
879
1003
  )
880
1004
  }
881
1005
 
@@ -884,7 +1008,8 @@ extension RunnerTests {
884
1008
  evaluation: SnapshotEvaluation,
885
1009
  depth: Int,
886
1010
  index: Int,
887
- parentIndex: Int?
1011
+ parentIndex: Int?,
1012
+ viewport: CGRect
888
1013
  ) -> RawAXNode {
889
1014
  return RawAXNode(
890
1015
  index: index,
@@ -896,7 +1021,11 @@ extension RunnerTests {
896
1021
  enabled: snapshot.isEnabled,
897
1022
  focused: evaluation.focused ? true : nil,
898
1023
  selected: evaluation.selected ? true : nil,
899
- hittable: evaluation.hittable,
1024
+ hittable: parentIndex != nil && SnapshotGeometry.isGeometricallyActionable(
1025
+ enabled: snapshot.isEnabled,
1026
+ frame: snapshot.frame,
1027
+ viewport: viewport
1028
+ ),
900
1029
  depth: depth,
901
1030
  parentIndex: parentIndex,
902
1031
  hiddenContentAbove: nil,
@@ -904,52 +1033,6 @@ extension RunnerTests {
904
1033
  )
905
1034
  }
906
1035
 
907
- private func isOccludingType(_ type: XCUIElement.ElementType) -> Bool {
908
- switch type {
909
- case .application, .window:
910
- return false
911
- default:
912
- return true
913
- }
914
- }
915
-
916
- private func flattenedSnapshots(
917
- _ root: XCUIElementSnapshot
918
- ) -> ([XCUIElementSnapshot], [ObjectIdentifier: (Int, Int)]) {
919
- var ordered: [XCUIElementSnapshot] = []
920
- var ranges: [ObjectIdentifier: (Int, Int)] = [:]
921
-
922
- @discardableResult
923
- func visit(_ snapshot: XCUIElementSnapshot) -> Int {
924
- let start = ordered.count
925
- ordered.append(snapshot)
926
- var end = start
927
- for child in snapshot.children {
928
- end = max(end, visit(child))
929
- }
930
- ranges[ObjectIdentifier(snapshot)] = (start, end)
931
- return end
932
- }
933
-
934
- _ = visit(root)
935
- return (ordered, ranges)
936
- }
937
-
938
- private func laterSnapshots(
939
- for snapshot: XCUIElementSnapshot,
940
- in ordered: [XCUIElementSnapshot],
941
- ranges: [ObjectIdentifier: (Int, Int)]
942
- ) -> ArraySlice<XCUIElementSnapshot> {
943
- guard let (_, subtreeEnd) = ranges[ObjectIdentifier(snapshot)] else {
944
- return ordered.suffix(from: ordered.count)
945
- }
946
- let nextIndex = subtreeEnd + 1
947
- if nextIndex >= ordered.count {
948
- return ordered.suffix(from: ordered.count)
949
- }
950
- return ordered.suffix(from: nextIndex)
951
- }
952
-
953
1036
  private func snapshotValueText(_ snapshot: XCUIElementSnapshot) -> String? {
954
1037
  guard let value = snapshot.value else { return nil }
955
1038
  let text = String(describing: value).trimmingCharacters(in: .whitespacesAndNewlines)
@@ -990,134 +1073,47 @@ extension RunnerTests {
990
1073
  return nil
991
1074
  }
992
1075
 
993
- func isVisibleInViewport(_ rect: CGRect, _ viewport: CGRect) -> Bool {
994
- if rect.isNull || rect.isEmpty { return false }
995
- return rect.intersects(viewport)
996
- }
997
-
998
- func isVisibleInRegularSnapshot(
999
- _ rect: CGRect,
1000
- viewport: CGRect,
1001
- scrollContainerAnchor: (index: Int, rect: CGRect)?
1002
- ) -> Bool {
1003
- if !isVisibleInViewport(rect, viewport) { return false }
1004
- guard let scrollContainerAnchor else { return true }
1005
- return isVisibleInViewport(rect, scrollContainerAnchor.rect)
1006
- }
1007
-
1008
1076
  private func appendCollapsedTabFallbackNodes(
1009
1077
  to nodes: inout [RawAXNode],
1010
1078
  containerSnapshot: XCUIElementSnapshot,
1011
1079
  resolveElements: () -> [XCUIElement],
1012
1080
  depth: Int,
1013
- parentIndex: Int
1081
+ parentIndex: Int,
1082
+ viewport: CGRect
1014
1083
  ) {
1015
1084
  let fallbackNodes = collapsedTabFallbackNodes(
1016
1085
  for: containerSnapshot,
1017
1086
  resolveElements: resolveElements,
1018
1087
  startingIndex: nodes.count,
1019
1088
  depth: depth,
1020
- parentIndex: parentIndex
1089
+ parentIndex: parentIndex,
1090
+ viewport: viewport
1021
1091
  )
1022
1092
  nodes.append(contentsOf: fallbackNodes)
1023
1093
  }
1024
1094
 
1025
- private func scrollContainerAnchor(
1026
- for snapshot: XCUIElementSnapshot,
1027
- visible: Bool,
1028
- nodeIndex: Int?
1029
- ) -> (index: Int, rect: CGRect)? {
1030
- return scrollContainerAnchor(
1031
- for: snapshot.elementType,
1032
- hasChildren: !snapshot.children.isEmpty,
1033
- visible: visible,
1034
- frame: snapshot.frame,
1035
- nodeIndex: nodeIndex
1036
- )
1037
- }
1038
-
1039
- func scrollContainerAnchor(
1040
- for elementType: XCUIElement.ElementType,
1041
- hasChildren: Bool,
1042
- visible: Bool,
1043
- frame: CGRect,
1044
- nodeIndex: Int?
1045
- ) -> (index: Int, rect: CGRect)? {
1046
- guard let nodeIndex else { return nil }
1047
- if !isScrollableContainer(
1048
- elementType: elementType,
1049
- hasChildren: hasChildren,
1050
- visible: visible
1051
- ) { return nil }
1052
- return (nodeIndex, frame)
1053
- }
1054
-
1055
- func rememberHiddenContentHint(
1056
- for frame: CGRect,
1057
- relativeTo scrollContainerAnchor: (index: Int, rect: CGRect),
1058
- hints: inout [Int: (above: Bool, below: Bool)]
1059
- ) {
1060
- if frame.isNull || frame.isEmpty { return }
1061
- var hint = hints[scrollContainerAnchor.index] ?? (above: false, below: false)
1062
- if frame.maxY <= scrollContainerAnchor.rect.minY {
1063
- hint.above = true
1064
- } else if frame.minY >= scrollContainerAnchor.rect.maxY {
1065
- hint.below = true
1066
- } else {
1067
- return
1068
- }
1069
- hints[scrollContainerAnchor.index] = hint
1070
- }
1071
-
1072
- func applyHiddenContentHints(
1073
- _ hints: [Int: (above: Bool, below: Bool)],
1074
- to nodes: [RawAXNode]
1075
- ) -> [RawAXNode] {
1076
- if hints.isEmpty { return nodes }
1077
- return nodes.map { node in
1078
- guard let hint = hints[node.index] else { return node }
1079
- let hiddenContentAbove: Bool? = (node.hiddenContentAbove == true || hint.above) ? true : nil
1080
- let hiddenContentBelow: Bool? = (node.hiddenContentBelow == true || hint.below) ? true : nil
1081
- return RawAXNode(
1082
- index: node.index,
1083
- type: node.type,
1084
- label: node.label,
1085
- identifier: node.identifier,
1086
- value: node.value,
1087
- rect: node.rect,
1088
- enabled: node.enabled,
1089
- focused: node.focused,
1090
- selected: node.selected,
1091
- hittable: node.hittable,
1092
- depth: node.depth,
1093
- parentIndex: node.parentIndex,
1094
- hiddenContentAbove: hiddenContentAbove,
1095
- hiddenContentBelow: hiddenContentBelow,
1096
- actions: node.actions
1097
- )
1098
- }
1099
- }
1100
-
1101
1095
  private func collapsedTabFallbackNodes(
1102
1096
  for containerSnapshot: XCUIElementSnapshot,
1103
1097
  resolveElements: () -> [XCUIElement],
1104
1098
  startingIndex: Int,
1105
1099
  depth: Int,
1106
- parentIndex: Int
1100
+ parentIndex: Int,
1101
+ viewport: CGRect
1107
1102
  ) -> [RawAXNode] {
1108
1103
  if !containerSnapshot.children.isEmpty { return [] }
1109
1104
  guard shouldExpandCollapsedTabContainer(containerSnapshot) else { return [] }
1110
1105
  let containerFrame = containerSnapshot.frame
1111
1106
  if containerFrame.isNull || containerFrame.isEmpty { return [] }
1112
1107
 
1113
- // Collapsed tab containers should be rare, so a full descendant scan is acceptable once per
1114
- // snapshot as a fallback for XCTest omitting the tab children from the snapshot tree.
1108
+
1109
+
1115
1110
  let elements = resolveElements()
1116
1111
  let candidates = elements.compactMap { element in
1117
1112
  collapsedTabCandidateNode(
1118
1113
  element: element,
1119
1114
  containerSnapshot: containerSnapshot,
1120
- containerFrame: containerFrame
1115
+ containerFrame: containerFrame,
1116
+ viewport: viewport
1121
1117
  )
1122
1118
  }
1123
1119
  .sorted { left, right in
@@ -1130,7 +1126,7 @@ extension RunnerTests {
1130
1126
  if candidates.count < 2 { return [] }
1131
1127
  let rowMidpoints = candidates.map { $0.rect.y + ($0.rect.height / 2) }
1132
1128
  let rowSpread = (rowMidpoints.max() ?? 0) - (rowMidpoints.min() ?? 0)
1133
- // Allow modest vertical jitter and short two-row wraps while still rejecting unrelated controls.
1129
+
1134
1130
  if rowSpread > max(24.0, Double(containerFrame.height) * 0.6) { return [] }
1135
1131
 
1136
1132
  var seen = Set<String>()
@@ -1165,7 +1161,8 @@ extension RunnerTests {
1165
1161
  private func collapsedTabCandidateNode(
1166
1162
  element: XCUIElement,
1167
1163
  containerSnapshot: XCUIElementSnapshot,
1168
- containerFrame: CGRect
1164
+ containerFrame: CGRect,
1165
+ viewport: CGRect
1169
1166
  ) -> RawAXNode? {
1170
1167
  var node: RawAXNode?
1171
1168
  let exceptionMessage = RunnerObjCExceptionCatcher.catchException({
@@ -1205,7 +1202,11 @@ extension RunnerTests {
1205
1202
  enabled: element.isEnabled,
1206
1203
  focused: elementHasFocus(element) ? true : nil,
1207
1204
  selected: element.isSelected ? true : nil,
1208
- hittable: element.isHittable,
1205
+ hittable: SnapshotGeometry.isGeometricallyActionable(
1206
+ enabled: element.isEnabled,
1207
+ frame: frame,
1208
+ viewport: viewport
1209
+ ),
1209
1210
  depth: 0,
1210
1211
  parentIndex: nil,
1211
1212
  hiddenContentAbove: nil,
@@ -1331,40 +1332,25 @@ extension RunnerTests {
1331
1332
  element: XCUIElement,
1332
1333
  index: Int,
1333
1334
  parentIndex: Int?,
1334
- viewport: CGRect,
1335
- options: PresentationOptions
1335
+ viewport: CGRect
1336
1336
  ) -> RawAXNode? {
1337
1337
  var node: RawAXNode?
1338
1338
  let exceptionMessage = RunnerObjCExceptionCatcher.catchException({
1339
1339
  if !element.exists { return }
1340
+
1341
+
1340
1342
  let frame = element.frame
1341
1343
  if frame.isNull || frame.isEmpty { return }
1342
- let visible = isVisibleInViewport(frame, viewport)
1343
- if options.interactiveOnly && !visible { return }
1344
- #if os(macOS)
1345
- if !visible { return }
1346
- #endif
1347
1344
  let label = element.label.trimmingCharacters(in: .whitespacesAndNewlines)
1348
1345
  let identifier = element.identifier.trimmingCharacters(in: .whitespacesAndNewlines)
1349
1346
  let valueText = snapshotValueText(element)
1350
1347
  let elementType = element.elementType
1351
1348
  let enabled = element.isEnabled
1352
- let hittable = visible && enabled && element.isHittable
1353
- let filterNode = FlatSnapshotFilterNode(
1354
- isRoot: false,
1355
- label: label,
1356
- identifier: identifier,
1357
- valueText: valueText,
1358
- visible: visible
1349
+ let hittable = SnapshotGeometry.isGeometricallyActionable(
1350
+ enabled: enabled,
1351
+ frame: frame,
1352
+ viewport: viewport
1359
1353
  )
1360
- if !flatSnapshotFilterDecision(
1361
- filterNode,
1362
- options: options,
1363
- visibilityPolicy: .interactiveOnly,
1364
- insideMatchedScope: false
1365
- ).include {
1366
- return
1367
- }
1368
1354
 
1369
1355
  node = RawAXNode(
1370
1356
  index: index,
@@ -1390,19 +1376,4 @@ extension RunnerTests {
1390
1376
  return node
1391
1377
  }
1392
1378
 
1393
- private func isScrollableContainer(_ snapshot: XCUIElementSnapshot, visible: Bool) -> Bool {
1394
- return isScrollableContainer(
1395
- elementType: snapshot.elementType,
1396
- hasChildren: !snapshot.children.isEmpty,
1397
- visible: visible
1398
- )
1399
- }
1400
-
1401
- private func isScrollableContainer(
1402
- elementType: XCUIElement.ElementType,
1403
- hasChildren: Bool,
1404
- visible: Bool
1405
- ) -> Bool {
1406
- return visible && hasChildren && Self.scrollContainerTypes.contains(elementType)
1407
- }
1408
1379
  }