agent-device 0.19.3 → 0.20.0

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 (168) hide show
  1. package/README.md +61 -70
  2. package/android/ime-helper/dist/agent-device-android-ime-helper-0.20.0.apk +0 -0
  3. package/android/ime-helper/dist/agent-device-android-ime-helper-0.20.0.apk.sha256 +1 -0
  4. package/android/ime-helper/dist/agent-device-android-ime-helper-0.20.0.manifest.json +10 -0
  5. package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.20.0.apk +0 -0
  6. package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.20.0.apk.sha256 +1 -0
  7. package/{android-snapshot-helper/dist/agent-device-android-snapshot-helper-0.19.3.manifest.json → android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.20.0.manifest.json} +6 -6
  8. package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerAXSnapshotBridge.h +2 -0
  9. package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerAXSnapshotBridge.m +5 -0
  10. package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerSynthesizedGesture.h +5 -10
  11. package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerSynthesizedGesture.m +74 -174
  12. package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Alert.swift +113 -19
  13. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+BlockingSystemModalResolution.swift +82 -0
  14. package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandExecution.swift +272 -118
  15. package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandJournal.swift +2 -2
  16. package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Interaction.swift +134 -101
  17. package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Lifecycle.swift +56 -2
  18. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+LifecycleCacheTests.swift +73 -0
  19. package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Models.swift +44 -24
  20. package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+ScrollGesture.swift +2 -2
  21. package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SequenceExecution.swift +2 -107
  22. package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Snapshot.swift +126 -11
  23. package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SnapshotCapturePlan.swift +51 -8
  24. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SnapshotTraversalIdentityTests.swift +24 -0
  25. package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SynthesizedGesturePolicy.swift +1 -1
  26. package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SystemModal.swift +43 -35
  27. package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests.swift +14 -0
  28. package/dist/src/agent-browser-tool.js +1 -1
  29. package/dist/src/agent-device-client.js +3 -0
  30. package/dist/src/android-adb.d.ts +1 -1
  31. package/dist/src/android-adb.js +1 -1
  32. package/dist/src/android-input-method-overlays.js +1 -0
  33. package/dist/src/android-input-ownership.js +1 -6
  34. package/dist/src/android-system-chrome.js +1 -0
  35. package/dist/src/android.js +1 -1
  36. package/dist/src/app-lifecycle.js +5 -5
  37. package/dist/src/app-log-request-scope.js +1 -0
  38. package/dist/src/app-resolution.js +1 -0
  39. package/dist/src/apple-multitouch-support.js +1 -0
  40. package/dist/src/apps.js +5 -5
  41. package/dist/src/boot-diagnostics.js +2 -0
  42. package/dist/src/cli-config.js +1 -0
  43. package/dist/src/cli-help.js +61 -27
  44. package/dist/src/cli-runner.js +1 -1
  45. package/dist/src/cli.js +15 -11
  46. package/dist/src/command-schema.js +3 -3
  47. package/dist/src/config.js +1 -0
  48. package/dist/src/context.js +1 -0
  49. package/dist/src/contracts.d.ts +1 -1
  50. package/dist/src/daemon-shutdown-report.js +1 -0
  51. package/dist/src/debug-symbols.js +1 -1
  52. package/dist/src/device-input-state.js +1 -1
  53. package/dist/src/devices2.js +1 -1
  54. package/dist/src/find.js +1 -1
  55. package/dist/src/finders.js +1 -1
  56. package/dist/src/generic.js +1 -1
  57. package/dist/src/helper.js +1 -1
  58. package/dist/src/index.d.ts +412 -293
  59. package/dist/src/index.js +1 -1
  60. package/dist/src/input-actions.js +4 -3
  61. package/dist/src/install-artifact.js +1 -0
  62. package/dist/src/install-source.d.ts +5 -1
  63. package/dist/src/install-source.js +1 -1
  64. package/dist/src/install-source2.js +1 -1
  65. package/dist/src/interaction-snapshot.js +1 -1
  66. package/dist/src/interaction.d.ts +230 -0
  67. package/dist/src/interaction.js +1 -1
  68. package/dist/src/interactor.js +2 -2
  69. package/dist/src/internal/bin.js +1 -1
  70. package/dist/src/internal/daemon.js +42 -20
  71. package/dist/src/internal/png-worker.d.ts +22 -0
  72. package/dist/src/internal/png-worker.js +1 -1
  73. package/dist/src/launch-console.js +1 -0
  74. package/dist/src/lease-context.js +1 -0
  75. package/dist/src/lease.js +1 -1
  76. package/dist/src/linux.js +1 -1
  77. package/dist/src/logcat.js +5 -0
  78. package/dist/src/metro.d.ts +2 -2
  79. package/dist/src/notifications.js +1 -1
  80. package/dist/src/png-worker-contract.js +1 -1
  81. package/dist/src/process-lock.js +1 -0
  82. package/dist/src/react-native.js +1 -1
  83. package/dist/src/record-trace.js +1 -1
  84. package/dist/src/register-builtins.js +1 -0
  85. package/dist/src/registry.js +24 -24
  86. package/dist/src/reporting.js +8 -7
  87. package/dist/src/runner-client.js +1 -1
  88. package/dist/src/runner-disposal.js +2 -0
  89. package/dist/src/runner-lease.js +1 -0
  90. package/dist/src/runner-sequence.js +1 -1
  91. package/dist/src/runtime.js +3 -3
  92. package/dist/src/runtime2.js +1 -0
  93. package/dist/src/sdk-android-adb.d.ts +119 -33
  94. package/dist/src/sdk-android-adb.js +2 -2
  95. package/dist/src/sdk-batch-runner.d.ts +2 -1
  96. package/dist/src/sdk-batch-runner.js +2 -2
  97. package/dist/src/sdk-batch.js +2 -2
  98. package/dist/src/sdk-contracts.d.ts +105 -98
  99. package/dist/src/sdk-contracts.js +1 -1
  100. package/dist/src/sdk-device.js +1 -1
  101. package/dist/src/sdk-finders.d.ts +3 -3
  102. package/dist/src/sdk-finders.js +1 -1
  103. package/dist/src/sdk-io.d.ts +1 -1
  104. package/dist/src/sdk-remote-config.d.ts +9 -1
  105. package/dist/src/sdk-remote-config.js +1 -1
  106. package/dist/src/sdk-selectors.js +1 -1
  107. package/dist/src/selector-runtime.js +1 -1
  108. package/dist/src/selector-vocabulary.js +1 -0
  109. package/dist/src/selectors.d.ts +12 -3
  110. package/dist/src/selectors.js +1 -1
  111. package/dist/src/server.js +3 -3
  112. package/dist/src/session-target-evidence.js +1 -0
  113. package/dist/src/session.js +8 -10
  114. package/dist/src/simulator.js +1 -1
  115. package/dist/src/snapshot-diagnostics.js +5 -2
  116. package/dist/src/snapshot.js +1 -1
  117. package/dist/src/snapshot2.js +1 -2
  118. package/dist/src/tv-remote.js +13 -12
  119. package/dist/src/url.js +1 -1
  120. package/dist/src/web.js +1 -1
  121. package/package.json +28 -23
  122. package/server.json +2 -2
  123. package/android-multitouch-helper/dist/agent-device-android-multitouch-helper-0.19.3.apk +0 -0
  124. package/android-multitouch-helper/dist/agent-device-android-multitouch-helper-0.19.3.apk.sha256 +0 -1
  125. package/android-multitouch-helper/dist/agent-device-android-multitouch-helper-0.19.3.manifest.json +0 -10
  126. package/android-snapshot-helper/dist/agent-device-android-snapshot-helper-0.19.3.apk +0 -0
  127. package/android-snapshot-helper/dist/agent-device-android-snapshot-helper-0.19.3.apk.sha256 +0 -1
  128. package/dist/src/apple-runner-options.js +0 -1
  129. package/dist/src/cli-output.js +0 -1
  130. package/dist/src/client.js +0 -3
  131. package/dist/src/handler-utils.js +0 -1
  132. package/dist/src/output-path.js +0 -1
  133. package/dist/src/points.js +0 -4
  134. package/dist/src/provider.js +0 -1
  135. package/dist/src/providers.js +0 -1
  136. package/dist/src/record-trace-recording.js +0 -23
  137. package/dist/src/recording-provider.js +0 -1
  138. package/dist/src/selectors2.js +0 -1
  139. package/dist/src/simctl.js +0 -1
  140. package/dist/src/ttl-memo.js +0 -1
  141. package/dist/src/xml.js +0 -2
  142. /package/{macos-helper → apple/macos-helper}/Package.swift +0 -0
  143. /package/{macos-helper → apple/macos-helper}/Sources/AgentDeviceMacOSHelper/AudioProbe.swift +0 -0
  144. /package/{macos-helper → apple/macos-helper}/Sources/AgentDeviceMacOSHelper/SnapshotTraversal.swift +0 -0
  145. /package/{macos-helper → apple/macos-helper}/Sources/AgentDeviceMacOSHelper/main.swift +0 -0
  146. /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunner/AgentDeviceRunnerApp.m +0 -0
  147. /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunner.xcodeproj/project.pbxproj +0 -0
  148. /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -0
  149. /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunner.xcodeproj/xcshareddata/xcschemes/AgentDeviceRunner.xcscheme +0 -0
  150. /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/AgentDeviceRunnerUITests-Bridging-Header.h +0 -0
  151. /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/AgentDeviceRunnerUITests.entitlements +0 -0
  152. /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerObjCExceptionCatcher.h +0 -0
  153. /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerObjCExceptionCatcher.m +0 -0
  154. /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTapPointPolicy.swift +0 -0
  155. /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+AXSnapshotFallback.swift +0 -0
  156. /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Environment.swift +0 -0
  157. /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Exceptions.swift +0 -0
  158. /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+FlatSnapshotFiltering.swift +0 -0
  159. /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Navigation.swift +0 -0
  160. /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+ScreenRecorder.swift +0 -0
  161. /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextEntry.swift +0 -0
  162. /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Transport.swift +0 -0
  163. /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TvRemote.swift +0 -0
  164. /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests.xctestplan +0 -0
  165. /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/RecordingScripts/recording-overlay.swift +0 -0
  166. /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/RecordingScripts/recording-resize.swift +0 -0
  167. /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/RecordingScripts/recording-trim.swift +0 -0
  168. /package/{src/platforms/linux → linux}/atspi-dump.py +0 -0
@@ -1,48 +1,89 @@
1
1
  import XCTest
2
2
 
3
3
  extension RunnerTests {
4
+ enum RunnerAlertSource {
5
+ case blockingSystemModal
6
+ case appAlert
7
+ case dismissPopup
8
+ }
9
+
4
10
  struct RunnerAlert {
5
11
  let root: XCUIElement
6
12
  let ownerApp: XCUIApplication
7
13
  let buttons: [XCUIElement]
14
+ let source: RunnerAlertSource
15
+ }
16
+
17
+ static let defaultAlertCommandTimeout: TimeInterval = 10
18
+
19
+ static func alertCommandTimeout(timeoutMs: Double?) -> TimeInterval {
20
+ guard let timeoutMs, timeoutMs.isFinite else { return defaultAlertCommandTimeout }
21
+ return max(0.001, timeoutMs / 1000)
8
22
  }
9
23
 
10
- func resolveAlert(app activeApp: XCUIApplication) -> RunnerAlert? {
24
+ func resolveAlert(app activeApp: XCUIApplication, deadline: Date) -> RunnerAlert? {
11
25
  #if !os(macOS)
12
- if let systemModal = firstBlockingSystemModal(in: springboard) {
13
- return runnerAlert(root: systemModal, ownerApp: springboard)
26
+ switch resolveBlockingSystemModal(deadline: deadline) {
27
+ case .resolved(let modal):
28
+ return runnerAlert(modal)
29
+ case .unresolved:
30
+ return nil
31
+ case .absent:
32
+ break
14
33
  }
15
34
  #endif
16
- if let alert = firstExistingElement(in: activeApp.alerts.allElementsBoundByIndex) {
17
- return runnerAlert(root: alert, ownerApp: activeApp)
35
+ // Guard the query: when a remote-hosted modal (e.g. the AccessorySetupKit
36
+ // picker) was just dismissed, the dismissal re-check re-enters here with the
37
+ // now-gone host as `activeApp`, and its `alerts` query raises
38
+ // kAXErrorServerNotFound. safeElementsQuery absorbs it and reports no alert.
39
+ if let alert = firstExistingElement(in: safeElementsQuery { activeApp.alerts.allElementsBoundByIndex }) {
40
+ return runnerAlert(root: alert, ownerApp: activeApp, source: .appAlert)
18
41
  }
19
42
  if let popup = firstDismissPopupWindow(in: activeApp) {
20
- return runnerAlert(root: popup, ownerApp: activeApp)
43
+ return runnerAlert(root: popup, ownerApp: activeApp, source: .dismissPopup)
21
44
  }
22
45
  return nil
23
46
  }
24
47
 
25
- func handleAlert(_ alert: RunnerAlert, action: String) -> Response {
48
+ func handleAlert(_ alert: RunnerAlert, action: String, deadline: Date) -> Response {
26
49
  if action == "accept" || action == "dismiss" {
27
50
  guard let button = chooseAlertButton(alert.buttons, action: action) else {
28
51
  return Response(ok: false, error: ErrorPayload(message: "alert \(action) button not found"))
29
52
  }
53
+ let previousTitle = preferredAlertTitle(alert.root, buttons: alert.buttons)
54
+ let actionButtonLabel = button.label.trimmingCharacters(in: .whitespacesAndNewlines)
55
+ let actionButtonFrame = button.frame
30
56
  let outcome = activateElement(app: alert.ownerApp, element: button, action: "alert \(action)")
31
57
  if let response = unsupportedResponse(for: outcome) {
32
58
  return response
33
59
  }
34
60
  sleepFor(0.2)
35
- if alertStillVisible(alert, actionButtonLabel: button.label) {
36
- let frame = button.frame
37
- if !frame.isNull && !frame.isEmpty {
38
- let coordinateOutcome = tapAt(app: alert.ownerApp, x: frame.midX, y: frame.midY)
61
+ if alertStillVisible(
62
+ in: alert.ownerApp,
63
+ source: alert.source,
64
+ previousTitle: previousTitle,
65
+ actionButtonLabel: actionButtonLabel,
66
+ deadline: deadline
67
+ ) {
68
+ if !actionButtonFrame.isNull && !actionButtonFrame.isEmpty {
69
+ let coordinateOutcome = tapAt(
70
+ app: alert.ownerApp,
71
+ x: actionButtonFrame.midX,
72
+ y: actionButtonFrame.midY
73
+ )
39
74
  if let response = unsupportedResponse(for: coordinateOutcome) {
40
75
  return response
41
76
  }
42
77
  sleepFor(0.2)
43
78
  }
44
79
  }
45
- if alertStillVisible(alert, actionButtonLabel: button.label) {
80
+ if alertStillVisible(
81
+ in: alert.ownerApp,
82
+ source: alert.source,
83
+ previousTitle: previousTitle,
84
+ actionButtonLabel: actionButtonLabel,
85
+ deadline: deadline
86
+ ) {
46
87
  return Response(
47
88
  ok: false,
48
89
  error: ErrorPayload(
@@ -64,26 +105,79 @@ extension RunnerTests {
64
105
  )
65
106
  }
66
107
 
67
- private func runnerAlert(root: XCUIElement, ownerApp: XCUIApplication) -> RunnerAlert? {
108
+ private func runnerAlert(_ modal: ResolvedBlockingSystemModal) -> RunnerAlert? {
109
+ let buttons = modal.actions.filter { isEnabledElement($0) }
110
+ guard !buttons.isEmpty else {
111
+ return nil
112
+ }
113
+ return RunnerAlert(
114
+ root: modal.root,
115
+ ownerApp: modal.ownerApp,
116
+ buttons: buttons,
117
+ source: .blockingSystemModal
118
+ )
119
+ }
120
+
121
+ private func runnerAlert(
122
+ root: XCUIElement,
123
+ ownerApp: XCUIApplication,
124
+ source: RunnerAlertSource
125
+ ) -> RunnerAlert? {
68
126
  let buttons = actionableElements(in: root).filter { isEnabledElement($0) }
69
127
  guard !buttons.isEmpty else {
70
128
  return nil
71
129
  }
72
- return RunnerAlert(root: root, ownerApp: ownerApp, buttons: buttons)
130
+ return RunnerAlert(root: root, ownerApp: ownerApp, buttons: buttons, source: source)
73
131
  }
74
132
 
75
- private func alertStillVisible(_ alert: RunnerAlert, actionButtonLabel: String) -> Bool {
76
- guard let current = resolveAlert(app: alert.ownerApp) else {
133
+ private func alertStillVisible(
134
+ in ownerApp: XCUIApplication,
135
+ source: RunnerAlertSource,
136
+ previousTitle: String,
137
+ actionButtonLabel: String,
138
+ deadline: Date
139
+ ) -> Bool {
140
+ guard Date() < deadline,
141
+ let current = resolveAlert(source: source, app: ownerApp, deadline: deadline)
142
+ else {
77
143
  return false
78
144
  }
79
- let previousTitle = preferredAlertTitle(alert.root, buttons: alert.buttons)
80
145
  let currentTitle = preferredAlertTitle(current.root, buttons: current.buttons)
81
146
  if previousTitle == currentTitle {
82
147
  return true
83
148
  }
84
- let normalizedActionLabel = actionButtonLabel.trimmingCharacters(in: .whitespacesAndNewlines)
85
149
  return current.buttons.contains { button in
86
- button.label.trimmingCharacters(in: .whitespacesAndNewlines) == normalizedActionLabel
150
+ button.label.trimmingCharacters(in: .whitespacesAndNewlines) == actionButtonLabel
151
+ }
152
+ }
153
+
154
+ private func resolveAlert(
155
+ source: RunnerAlertSource,
156
+ app: XCUIApplication,
157
+ deadline: Date
158
+ ) -> RunnerAlert? {
159
+ switch source {
160
+ case .blockingSystemModal:
161
+ #if os(macOS)
162
+ return nil
163
+ #else
164
+ guard case .resolved(let modal) = resolveBlockingSystemModal(deadline: deadline) else {
165
+ return nil
166
+ }
167
+ return runnerAlert(modal)
168
+ #endif
169
+ case .appAlert:
170
+ guard let alert = firstExistingElement(
171
+ in: safeElementsQuery { app.alerts.allElementsBoundByIndex }
172
+ ) else {
173
+ return nil
174
+ }
175
+ return runnerAlert(root: alert, ownerApp: app, source: .appAlert)
176
+ case .dismissPopup:
177
+ guard let popup = firstDismissPopupWindow(in: app) else {
178
+ return nil
179
+ }
180
+ return runnerAlert(root: popup, ownerApp: app, source: .dismissPopup)
87
181
  }
88
182
  }
89
183
 
@@ -0,0 +1,82 @@
1
+ import XCTest
2
+
3
+ extension RunnerTests {
4
+ struct ResolvedBlockingSystemModal {
5
+ let root: XCUIElement
6
+ let ownerApp: XCUIApplication
7
+ let actions: [XCUIElement]
8
+ }
9
+
10
+ enum BlockingSystemModalResolution {
11
+ case absent
12
+ case unresolved
13
+ case resolved(ResolvedBlockingSystemModal)
14
+ }
15
+
16
+ func resolveBlockingSystemModal(
17
+ deadline: Date
18
+ ) -> BlockingSystemModalResolution {
19
+ guard Self.hasSpringBoardSystemModalHost else {
20
+ return .absent
21
+ }
22
+
23
+ guard let springboardModal = firstBlockingSystemModal(
24
+ in: springboard,
25
+ deadline: deadline
26
+ ) else {
27
+ return .absent
28
+ }
29
+
30
+ let springboardActions = actionableElements(in: springboardModal)
31
+ if !RemoteHostedSystemModalPolicy.shouldProbeRemoteHost(
32
+ springboardActionCount: springboardActions.count
33
+ ) {
34
+ return .resolved(
35
+ ResolvedBlockingSystemModal(
36
+ root: springboardModal,
37
+ ownerApp: springboard,
38
+ actions: springboardActions
39
+ )
40
+ )
41
+ }
42
+
43
+ guard Date() < deadline, let remoteModal = remoteHostedSystemModal(deadline: deadline) else {
44
+ return .unresolved
45
+ }
46
+ return .resolved(remoteModal)
47
+ }
48
+
49
+ // AccessorySetupUI mirrors into SpringBoard, but its mirrored elements are not
50
+ // reliably hittable. Query the host directly; activating it breaks the picker.
51
+ private static let remoteSystemModalHostBundleIds = [
52
+ "com.apple.AccessorySetupUI"
53
+ ]
54
+
55
+ private func remoteHostedSystemModal(deadline: Date) -> ResolvedBlockingSystemModal? {
56
+ for bundleId in Self.remoteSystemModalHostBundleIds {
57
+ guard Date() < deadline else { return nil }
58
+ let host = XCUIApplication(bundleIdentifier: bundleId)
59
+ let state = safely("REMOTE_MODAL_STATE", XCUIApplication.State.unknown) { host.state }
60
+ guard RemoteHostedSystemModalPolicy.isEligibleHostState(state) else { continue }
61
+ guard Date() < deadline else { return nil }
62
+
63
+ // A dismissed remote host can raise kAXErrorServerNotFound. The safe queries
64
+ // inside actionableElements turn that disappearance into an empty result.
65
+ let actions = actionableElements(in: host)
66
+ guard !actions.isEmpty else { continue }
67
+ return ResolvedBlockingSystemModal(root: host, ownerApp: host, actions: actions)
68
+ }
69
+ return nil
70
+ }
71
+ }
72
+
73
+ enum RemoteHostedSystemModalPolicy {
74
+ static func shouldProbeRemoteHost(springboardActionCount: Int) -> Bool {
75
+ springboardActionCount == 0
76
+ }
77
+
78
+ static func isEligibleHostState(_ state: XCUIApplication.State) -> Bool {
79
+ state == .runningForeground
80
+ }
81
+ }
82
+