agent-device 0.21.11 → 0.21.13

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 (201) hide show
  1. package/android/ime-helper/dist/{agent-device-android-ime-helper-0.21.11.apk → agent-device-android-ime-helper-0.21.13.apk} +0 -0
  2. package/android/ime-helper/dist/agent-device-android-ime-helper-0.21.13.apk.sha256 +1 -0
  3. package/android/ime-helper/dist/{agent-device-android-ime-helper-0.21.11.manifest.json → agent-device-android-ime-helper-0.21.13.manifest.json} +4 -4
  4. package/android/snapshot-helper/dist/{agent-device-android-snapshot-helper-0.21.11.apk → agent-device-android-snapshot-helper-0.21.13.apk} +0 -0
  5. package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.21.13.apk.sha256 +1 -0
  6. package/android/snapshot-helper/dist/{agent-device-android-snapshot-helper-0.21.11.manifest.json → agent-device-android-snapshot-helper-0.21.13.manifest.json} +6 -6
  7. package/apple/fold-helper/Fold.m +97 -0
  8. package/apple/macos-helper/Package.swift +3 -6
  9. package/apple/macos-helper/Sources/AgentDeviceMacOSHelper/main.swift +3 -8
  10. package/apple/macos-helper/Tests/AgentDeviceMacOSHelperTests/ScreenshotResponseTests.swift +15 -0
  11. package/apple/snapshot-bridge/SnapshotBridgeRuntime.m +47 -1
  12. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunner/AgentDeviceRunnerApp.m +99 -42
  13. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunner/Info.plist +23 -0
  14. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunner.xcodeproj/project.pbxproj +17 -0
  15. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerAppScreenCapture.swift +21 -13
  16. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerScrollViewportPolicy.swift +1 -162
  17. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerSynthesizedGesture.h +0 -9
  18. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerSynthesizedGesture.m +0 -56
  19. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerSystemSurfaceHostPolicy.swift +0 -48
  20. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTapPointPolicy.swift +0 -50
  21. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+AXSnapshotFallback.swift +22 -568
  22. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Alert.swift +21 -9
  23. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+BlockingSystemModalResolution.swift +0 -36
  24. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandDispatch.swift +630 -0
  25. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandExecution.swift +18 -2236
  26. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandJournal.swift +1 -338
  27. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Environment.swift +0 -12
  28. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+GestureExecution.swift +165 -0
  29. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Interaction.swift +0 -24
  30. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Keyboard.swift +0 -142
  31. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Lifecycle.swift +90 -39
  32. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+MainThreadWork.swift +78 -106
  33. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Models.swift +23 -2
  34. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Navigation.swift +147 -95
  35. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+PrivateAXPresentation.swift +0 -209
  36. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+ScreenRecorder.swift +86 -10
  37. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+ScrollDragExecution.swift +287 -0
  38. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+ScrollGesture.swift +1 -200
  39. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SequenceExecution.swift +0 -185
  40. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Snapshot.swift +75 -285
  41. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SnapshotAcquisition.swift +23 -10
  42. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SnapshotBackendCapabilities.swift +0 -78
  43. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SnapshotCapturePlan.swift +83 -578
  44. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SnapshotCaptureTarget.swift +106 -0
  45. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SnapshotExecution.swift +142 -0
  46. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SnapshotTiming.swift +49 -78
  47. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SynthesizedCommitDeadline.swift +0 -44
  48. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SynthesizedGesturePolicy.swift +27 -97
  49. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SynthesizedInteraction.swift +1 -11
  50. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SynthesizedTextEntry.swift +11 -87
  51. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SystemModal.swift +2 -10
  52. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextEntry.swift +1 -1
  53. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextEntryFocus.swift +0 -35
  54. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextInputCandidatePolicy.swift +0 -31
  55. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextTyping.swift +2 -18
  56. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Transport.swift +34 -52
  57. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TvRemote.swift +0 -22
  58. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TypeExecution.swift +142 -0
  59. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests.swift +22 -6
  60. package/dist/apple/runner/AgentDeviceRunner/RecordingScripts/recording-overlay.swift +140 -20
  61. package/dist/src/absence-observation-errors.js +1 -1
  62. package/dist/src/adapter.js +1 -1
  63. package/dist/src/agent-device-client.js +1 -1
  64. package/dist/src/alert-runtime.js +1 -1
  65. package/dist/src/android-adb.d.ts +1 -1
  66. package/dist/src/app-launch.js +2 -2
  67. package/dist/src/app-log-runtime2.js +3 -3
  68. package/dist/src/app-resolution.js +2 -2
  69. package/dist/src/back-mode.js +1 -1
  70. package/dist/src/capture-disclosure.js +1 -1
  71. package/dist/src/capture.js +1 -1
  72. package/dist/src/cli-config.js +1 -1
  73. package/dist/src/cli-help.js +10 -7
  74. package/dist/src/cli-runner.js +1 -1
  75. package/dist/src/cli.js +1 -1
  76. package/dist/src/client-types.d.ts +5 -8
  77. package/dist/src/code-signature.js +1 -1
  78. package/dist/src/command-schema.js +3 -3
  79. package/dist/src/command-tools.js +3 -3
  80. package/dist/src/connection.js +1 -1
  81. package/dist/src/daemon-client-lifecycle.js +1 -1
  82. package/dist/src/daemon-resolution.js +1 -1
  83. package/dist/src/device-claim-rule.js +2 -2
  84. package/dist/src/device-input-state.d.ts +1 -1
  85. package/dist/src/device-rotation.js +1 -1
  86. package/dist/src/device-session.js +1 -1
  87. package/dist/src/devicectl.js +1 -1
  88. package/dist/src/display-inventory.js +1 -1
  89. package/dist/src/doctor-facade.js +1 -1
  90. package/dist/src/find.js +1 -1
  91. package/dist/src/gesture-plan-types.d.ts +38 -0
  92. package/dist/src/harmonyos.js +1 -1
  93. package/dist/src/helper.js +2 -2
  94. package/dist/src/host.js +1 -1
  95. package/dist/src/install-source-network-transport.js +1 -1
  96. package/dist/src/install-source2.js +1 -1
  97. package/dist/src/interaction-ref-policy.js +1 -1
  98. package/dist/src/interaction2.js +1 -1
  99. package/dist/src/interactor-operation-catalog.js +1 -1
  100. package/dist/src/interactor.js +4 -4
  101. package/dist/src/interactor2.js +1 -1
  102. package/dist/src/internal/bin.js +1 -1
  103. package/dist/src/internal/daemon.js +2 -2
  104. package/dist/src/inventory2.js +1 -1
  105. package/dist/src/ios-snapshot-acquisition.js +1 -1
  106. package/dist/src/ios-snapshot-runtime.js +1 -1
  107. package/dist/src/limrun-runtime-dependencies.js +1 -1
  108. package/dist/src/limrun.d.ts +435 -4
  109. package/dist/src/linux.js +1 -1
  110. package/dist/src/local-interactor-operation-set.js +1 -1
  111. package/dist/src/macos-facade.js +1 -1
  112. package/dist/src/managed-device-scope.js +1 -1
  113. package/dist/src/mechanics.js +4 -4
  114. package/dist/src/mobile-snapshot-semantics.js +1 -1
  115. package/dist/src/perf-facade.js +1 -2
  116. package/dist/src/perf-runtime-plan.js +1 -1
  117. package/dist/src/perf-target.js +2 -0
  118. package/dist/src/physical-device-apps.js +1 -0
  119. package/dist/src/physical-device-control.js +1 -1
  120. package/dist/src/platform-request-scope.js +1 -1
  121. package/dist/src/platform-runtime-operation-host.js +1 -1
  122. package/dist/src/platform-runtime-screen-recording-finalizer-host.js +1 -1
  123. package/dist/src/platform-runtime-unavailable.js +1 -1
  124. package/dist/src/platform-runtime.js +1 -1
  125. package/dist/src/platform-runtime2.js +1 -1
  126. package/dist/src/provider-webdriver.js +1 -1
  127. package/dist/src/proxy.js +1 -1
  128. package/dist/src/quality-warnings.js +1 -1
  129. package/dist/src/react-native.js +1 -1
  130. package/dist/src/record-runtime.js +1 -1
  131. package/dist/src/registry.js +35 -35
  132. package/dist/src/request-admission.js +1 -1
  133. package/dist/src/request-runtime-binding.js +1 -1
  134. package/dist/src/runner-client.js +1 -1
  135. package/dist/src/runner-dev-tools-security.js +1 -1
  136. package/dist/src/runner-device-readiness.js +1 -1
  137. package/dist/src/runner-disposal.js +1 -1
  138. package/dist/src/runner-error-classification.js +3 -0
  139. package/dist/src/runner-operations-facade.js +1 -1
  140. package/dist/src/runner-presentation.js +1 -1
  141. package/dist/src/runner.js +1 -1
  142. package/dist/src/runtime.js +3 -3
  143. package/dist/src/runtime10.js +1 -1
  144. package/dist/src/runtime2.js +2 -2
  145. package/dist/src/runtime4.js +5 -5
  146. package/dist/src/runtime5.js +1 -1
  147. package/dist/src/runtime6.js +1 -1
  148. package/dist/src/runtime7.js +1 -1
  149. package/dist/src/runtime8.js +1 -1
  150. package/dist/src/runtime9.js +1 -1
  151. package/dist/src/screen-recording-admission-ledger.js +1 -1
  152. package/dist/src/screen-recording-resource-recovery.js +1 -1
  153. package/dist/src/screen-recording-transport.js +1 -1
  154. package/dist/src/screenshot-runtime.js +1 -1
  155. package/dist/src/screenshot3.js +1 -1
  156. package/dist/src/script.js +1 -1
  157. package/dist/src/scroll-command.d.ts +377 -0
  158. package/dist/src/scroll-command.js +1 -1
  159. package/dist/src/sdk-batch.js +2 -2
  160. package/dist/src/sdk-contracts.d.ts +9 -0
  161. package/dist/src/sdk-device.js +1 -1
  162. package/dist/src/sdk-selectors.d.ts +23 -715
  163. package/dist/src/selection.js +0 -0
  164. package/dist/src/selector-runtime.js +1 -1
  165. package/dist/src/semantic-index.js +1 -1
  166. package/dist/src/server.js +1 -1
  167. package/dist/src/session-observation.js +1 -1
  168. package/dist/src/session-snapshot.js +1 -1
  169. package/dist/src/session2.js +2 -2
  170. package/dist/src/simctl-facade.js +1 -1
  171. package/dist/src/simctl.js +1 -1
  172. package/dist/src/simulator-facade.js +1 -1
  173. package/dist/src/simulator-state.js +1 -1
  174. package/dist/src/simulator.js +1 -1
  175. package/dist/src/snapshot-runtime-binding.js +1 -1
  176. package/dist/src/snapshot-runtime.js +1 -1
  177. package/dist/src/snapshot-state.js +1 -1
  178. package/dist/src/snapshot4.js +1 -1
  179. package/dist/src/src7.js +1 -1
  180. package/dist/src/text-entry-focus.js +1 -1
  181. package/dist/src/tool-provider.js +1 -1
  182. package/dist/src/touch-runtime.js +1 -1
  183. package/dist/src/tree.js +1 -1
  184. package/dist/src/update-check.js +1 -1
  185. package/dist/src/version.js +1 -1
  186. package/dist/src/web2.js +1 -1
  187. package/package.json +4 -2
  188. package/server.json +2 -2
  189. package/android/ime-helper/dist/agent-device-android-ime-helper-0.21.11.apk.sha256 +0 -1
  190. package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.21.11.apk.sha256 +0 -1
  191. package/apple/macos-helper/Sources/AgentDeviceMacOSDeviceHub/DeviceHubPose.swift +0 -47
  192. package/apple/macos-helper/Sources/AgentDeviceMacOSDeviceHub/DeviceHubWindowInventory.swift +0 -65
  193. package/apple/macos-helper/Sources/AgentDeviceMacOSHelper/DeviceHubPose.swift +0 -225
  194. package/apple/macos-helper/Sources/AgentDeviceMacOSHelper/DeviceHubSidebar.swift +0 -57
  195. package/apple/macos-helper/Sources/AgentDeviceMacOSHelper/DeviceHubWindowDiscovery.swift +0 -108
  196. package/apple/macos-helper/Tests/AgentDeviceMacOSDeviceHubTests/DeviceHubPoseTests.swift +0 -30
  197. package/apple/macos-helper/Tests/AgentDeviceMacOSDeviceHubTests/DeviceHubWindowInventoryTests.swift +0 -65
  198. package/dist/src/app-inventory.d.ts +0 -4
  199. package/dist/src/apps-simctl.js +0 -1
  200. package/dist/src/cloud-artifacts.d.ts +0 -61
  201. package/dist/src/runner-contract.js +0 -3
@@ -4,27 +4,66 @@ extension RunnerTests {
4
4
  static let navigationBackKeywords = ["back", "close", "cancel"]
5
5
  static let navigationFallbackVerificationDelay: TimeInterval = 0.25
6
6
 
7
- func tapInAppBackControl(app: XCUIApplication) -> Bool {
7
+
8
+
9
+
10
+ enum InAppBackOutcome {
11
+ case performed
12
+ case unavailable
13
+ case unverified(ErrorPayload)
14
+ }
15
+
16
+
17
+
18
+ enum NavigationVisualObservation: Equatable {
19
+ case changed
20
+ case unchanged
21
+ case unobserved
22
+
23
+ var logToken: String {
24
+ switch self {
25
+ case .changed: return "yes"
26
+ case .unchanged: return "no"
27
+ case .unobserved: return "unknown"
28
+ }
29
+ }
30
+ }
31
+
32
+
33
+
34
+ struct NavigationVisualSample {
35
+ let data: Data?
36
+ let refusalCode: String?
37
+ let refusalHint: String?
38
+
39
+ init(data: Data?, refusalCode: String? = nil, refusalHint: String? = nil) {
40
+ self.data = data
41
+ self.refusalCode = refusalCode
42
+ self.refusalHint = refusalHint
43
+ }
44
+ }
45
+
46
+ func tapInAppBackControl(app: XCUIApplication) -> InAppBackOutcome {
8
47
  #if os(macOS)
9
48
  if let back = macOSNavigationBackElement(app: app) {
10
49
  tapElementCenter(app: app, element: back)
11
- return true
50
+ return .performed
12
51
  }
13
- return false
52
+ return .unavailable
14
53
  #elseif os(tvOS)
15
54
  _ = pressTvRemote(.menu)
16
- return true
55
+ return .performed
17
56
  #else
18
57
  let buttons = app.navigationBars.buttons.allElementsBoundByIndex
19
58
  if let back = buttons.first(where: { $0.isHittable }) {
20
59
  back.tap()
21
- return true
60
+ return .performed
22
61
  }
23
62
  if isSnapshotXCTestChannelPenalized(bundleId: currentBundleId) {
24
63
  NSLog("AGENT_DEVICE_RUNNER_IN_APP_BACK_SKIPPED_XCTEST_ENUMERATION bundle=%@", currentBundleId ?? "")
25
64
  } else if let back = topNavigationBackElement(app: app) {
26
65
  tapElementCenter(app: app, element: back)
27
- return true
66
+ return .performed
28
67
  }
29
68
  return tapTopLeadingNavigationFallback(app: app)
30
69
  #endif
@@ -109,11 +148,11 @@ extension RunnerTests {
109
148
  return CGPoint(x: frame.minX + xOffset, y: frame.minY + yOffset)
110
149
  }
111
150
 
112
- private func tapTopLeadingNavigationFallback(app: XCUIApplication) -> Bool {
151
+ private func tapTopLeadingNavigationFallback(app: XCUIApplication) -> InAppBackOutcome {
113
152
  #if os(iOS)
114
153
  let frame = onScreenWindowFrame(app: app)
115
154
  guard let point = Self.topLeadingNavigationFallbackPoint(in: frame) else {
116
- return false
155
+ return .unavailable
117
156
  }
118
157
  let before = captureNavigationFallbackVisualState(app: app)
119
158
  let context = synthesizedCoordinateContext(
@@ -124,107 +163,30 @@ extension RunnerTests {
124
163
  synthesizedTapAt(app: app, x: point.x, y: point.y, context: context)
125
164
  }
126
165
  if case .performed = synthesized.outcome {
127
- return didNavigationFallbackChangeVisualState(app: app, before: before)
166
+ return verifyNavigationFallbackOutcome(app: app, before: before)
128
167
  }
129
168
  let fallback = performGesture(app) {
130
169
  tapAt(app: app, x: point.x, y: point.y)
131
170
  }
132
171
  if case .performed = fallback.outcome {
133
- return didNavigationFallbackChangeVisualState(app: app, before: before)
172
+ return verifyNavigationFallbackOutcome(app: app, before: before)
134
173
  }
135
174
  #endif
136
- return false
175
+ return .unavailable
137
176
  }
138
177
 
139
- private func captureNavigationFallbackVisualState(app: XCUIApplication) -> Data? {
178
+ private func captureNavigationFallbackVisualState(app: XCUIApplication) -> NavigationVisualSample {
140
179
  #if os(iOS)
141
-
142
-
143
-
144
-
145
- guard case .success(let captured) = captureObservedScreen(app: app) else {
146
- return nil
147
- }
148
- return runnerPngData(for: captured.image)
180
+ return Self.navigationFallbackSample(
181
+ resolvingApp: { self.captureResolvedAppScreen(app: app) },
182
+ systemSurface: { self.captureResolvedAppScreen(app: self.springboard) },
183
+ encoding: { runnerPngData(for: $0.image) }
184
+ )
149
185
  #else
150
- return nil
186
+ return NavigationVisualSample(data: nil)
151
187
  #endif
152
188
  }
153
189
 
154
- private func didNavigationFallbackChangeVisualState(
155
- app: XCUIApplication,
156
- before: Data?
157
- ) -> Bool {
158
- sleepFor(Self.navigationFallbackVerificationDelay)
159
- let after = captureNavigationFallbackVisualState(app: app)
160
- let changed = Self.didNavigationFallbackChangeVisualState(before: before, after: after)
161
-
162
-
163
- NSLog(
164
- "AGENT_DEVICE_RUNNER_IN_APP_BACK_VISUAL_VERIFICATION beforeBytes=%ld afterBytes=%ld changed=%@",
165
- before?.count ?? -1,
166
- after?.count ?? -1,
167
- changed ? "yes" : "no"
168
- )
169
- return changed
170
- }
171
-
172
- static func didNavigationFallbackChangeVisualState(before: Data?, after: Data?) -> Bool {
173
- guard let before, let after else { return false }
174
- return before != after
175
- }
176
-
177
- private func macOSNavigationBackElement(app: XCUIApplication) -> XCUIElement? {
178
- let predicate = NSPredicate(
179
- format: "identifier == %@ OR label == %@",
180
- "go back",
181
- "Back"
182
- )
183
- let element = app.descendants(matching: .any).matching(predicate).firstMatch
184
- return element.exists ? element : nil
185
- }
186
-
187
-
188
-
189
-
190
-
191
-
192
-
193
-
194
-
195
-
196
-
197
-
198
-
199
-
200
-
201
-
202
-
203
-
204
-
205
-
206
-
207
-
208
-
209
-
210
-
211
-
212
-
213
-
214
-
215
-
216
-
217
-
218
-
219
-
220
-
221
-
222
-
223
-
224
-
225
-
226
-
227
-
228
190
 
229
191
 
230
192
 
@@ -232,23 +194,113 @@ extension RunnerTests {
232
194
 
233
195
 
234
196
 
197
+ static func navigationFallbackSample(
198
+ resolvingApp: () -> Result<CapturedAppScreen, RunnerAppScreenCaptureFailure>,
199
+ systemSurface: () -> Result<CapturedAppScreen, RunnerAppScreenCaptureFailure>,
200
+ encoding: (CapturedAppScreen) -> Data?
201
+ ) -> NavigationVisualSample {
202
+ _ = systemSurface
203
+ return navigationVisualSample(from: resolvingApp(), encoding: encoding)
204
+ }
235
205
 
236
206
 
237
207
 
238
208
 
239
209
 
210
+ static func navigationVisualSample(
211
+ from outcome: Result<CapturedAppScreen, RunnerAppScreenCaptureFailure>,
212
+ encoding: (CapturedAppScreen) -> Data?
213
+ ) -> NavigationVisualSample {
214
+ switch outcome {
215
+ case .success(let captured):
216
+ guard let png = encoding(captured) else {
217
+ let refusal = RunnerAppScreenCaptureFailure.unrenderableImage
218
+ return NavigationVisualSample(
219
+ data: nil,
220
+ refusalCode: refusal.rawValue,
221
+ refusalHint: refusal.hint
222
+ )
223
+ }
224
+ return NavigationVisualSample(data: png)
225
+ case .failure(let failure):
226
+ return NavigationVisualSample(
227
+ data: nil,
228
+ refusalCode: failure.rawValue,
229
+ refusalHint: failure.hint
230
+ )
231
+ }
232
+ }
240
233
 
234
+ private func verifyNavigationFallbackOutcome(
235
+ app: XCUIApplication,
236
+ before: NavigationVisualSample
237
+ ) -> InAppBackOutcome {
238
+ sleepFor(Self.navigationFallbackVerificationDelay)
239
+ let after = captureNavigationFallbackVisualState(app: app)
240
+ let observation = Self.navigationVisualObservation(before: before.data, after: after.data)
241
241
 
242
242
 
243
+ NSLog(
244
+ "AGENT_DEVICE_RUNNER_IN_APP_BACK_VISUAL_VERIFICATION beforeBytes=%ld afterBytes=%ld changed=%@",
245
+ before.data?.count ?? -1,
246
+ after.data?.count ?? -1,
247
+ observation.logToken
248
+ )
249
+ return Self.inAppBackOutcome(observation: observation, before: before, after: after)
250
+ }
243
251
 
244
252
 
245
253
 
254
+ static func inAppBackOutcome(
255
+ observation: NavigationVisualObservation,
256
+ before: NavigationVisualSample,
257
+ after: NavigationVisualSample
258
+ ) -> InAppBackOutcome {
259
+ switch observation {
260
+ case .changed:
261
+ return .performed
262
+ case .unchanged:
263
+ return .unavailable
264
+ case .unobserved:
246
265
 
247
266
 
267
+ return .unverified(Self.navigationFallbackErrorPayload(after: after, before: before))
268
+ }
269
+ }
248
270
 
271
+ static func navigationVisualObservation(
272
+ before: Data?,
273
+ after: Data?
274
+ ) -> NavigationVisualObservation {
275
+ guard let before, let after else { return .unobserved }
276
+ return before != after ? .changed : .unchanged
277
+ }
249
278
 
250
279
 
251
280
 
252
281
 
282
+ static func navigationFallbackErrorPayload(
283
+ after: NavigationVisualSample,
284
+ before: NavigationVisualSample
285
+ ) -> ErrorPayload {
286
+ ErrorPayload(
287
+ code:
288
+ after.refusalCode
289
+ ?? before.refusalCode
290
+ ?? RunnerAppScreenCaptureFailure.unresolvedScreen.rawValue,
291
+ message:
292
+ "The in-app back fallback was dispatched, but no display could be sampled to confirm the result. This is an unknown outcome, not evidence that a back control is absent.",
293
+ hint: after.refusalHint ?? before.refusalHint
294
+ )
295
+ }
253
296
 
297
+ private func macOSNavigationBackElement(app: XCUIApplication) -> XCUIElement? {
298
+ let predicate = NSPredicate(
299
+ format: "identifier == %@ OR label == %@",
300
+ "go back",
301
+ "Back"
302
+ )
303
+ let element = app.descendants(matching: .any).matching(predicate).firstMatch
304
+ return element.exists ? element : nil
305
+ }
254
306
  }
@@ -25,12 +25,20 @@ extension RunnerTests {
25
25
  private var startedSession = false
26
26
  private var startError: Error?
27
27
 
28
+
29
+
30
+
28
31
  init(outputPath: String, fps: Int32?) {
29
32
  self.outputPath = outputPath
30
33
  self.fps = fps
31
34
  }
32
35
 
33
- func start(captureFrame: @escaping () -> RunnerImage?) throws {
36
+
37
+
38
+ func start(
39
+ bootstrap: @escaping () -> Result<CapturedAppScreen, RunnerAppScreenCaptureFailure>,
40
+ frame: @escaping () -> RunnerImage?
41
+ ) throws {
34
42
  let url = URL(fileURLWithPath: outputPath)
35
43
  let directory = url.deletingLastPathComponent()
36
44
  try FileManager.default.createDirectory(
@@ -44,21 +52,34 @@ extension RunnerTests {
44
52
 
45
53
  var dimensions: CGSize = .zero
46
54
  var bootstrapImage: RunnerImage?
55
+ var lastFailure: RunnerAppScreenCaptureFailure?
47
56
  let bootstrapDeadline = Date().addingTimeInterval(2.0)
48
57
  while Date() < bootstrapDeadline {
49
- if let image = captureFrame(), let cgImage = runnerCGImage(from: image) {
50
- bootstrapImage = image
51
- dimensions = CGSize(width: cgImage.width, height: cgImage.height)
58
+ switch bootstrap() {
59
+ case .success(let captured):
60
+ bootstrapImage = captured.image
61
+ dimensions = CGSize(width: captured.pixelWidth, height: captured.pixelHeight)
62
+ case .failure(let failure):
63
+ lastFailure = failure
64
+ }
65
+ if dimensions.width > 0, dimensions.height > 0 {
52
66
  break
53
67
  }
54
68
  Thread.sleep(forTimeInterval: 0.05)
55
69
  }
56
70
  guard dimensions.width > 0, dimensions.height > 0 else {
57
- throw NSError(
58
- domain: "AgentDeviceRunner.Record",
59
- code: 1,
60
- userInfo: [NSLocalizedDescriptionKey: "failed to capture initial frame"]
61
- )
71
+
72
+
73
+
74
+
75
+ #if os(iOS)
76
+ throw RunnerTests.recordingBootstrapError(from: lastFailure)
77
+ #else
78
+
79
+
80
+ _ = lastFailure
81
+ throw RunnerTests.recordingBootstrapError(from: nil)
82
+ #endif
62
83
  }
63
84
 
64
85
  let writer = try AVAssetWriter(outputURL: url, fileType: .mp4)
@@ -114,7 +135,7 @@ extension RunnerTests {
114
135
  timer.setEventHandler { [weak self] in
115
136
  guard let self else { return }
116
137
  if self.shouldStop() { return }
117
- guard let image = captureFrame() else { return }
138
+ guard let image = frame() else { return }
118
139
  self.append(image: image)
119
140
  }
120
141
  self.timer = timer
@@ -212,6 +233,9 @@ extension RunnerTests {
212
233
  return
213
234
  }
214
235
  lastTimestampValue = timestampValue
236
+
237
+
238
+
215
239
  }
216
240
 
217
241
  private func timestampCandidateValue(for nowUptime: TimeInterval) -> Int64 {
@@ -269,6 +293,58 @@ extension RunnerTests {
269
293
  }
270
294
  }
271
295
 
296
+ extension RunnerTests {
297
+
298
+
299
+
300
+
301
+
302
+ func startRecording(
303
+ _ recorder: ScreenRecorder,
304
+ capture: @escaping () -> Result<CapturedAppScreen, RunnerAppScreenCaptureFailure>
305
+ ) throws {
306
+ try recorder.start(bootstrap: capture) { [weak self] in
307
+ guard let self else { return nil }
308
+ return try? self.runMainThreadWorkIfIdle(
309
+ "recording_frame",
310
+ timeout: self.recordingFrameCaptureTimeout,
311
+ timeoutError: self.mainThreadExecutionTimeoutError
312
+ ) {
313
+ try capture().get().image
314
+ }
315
+ }
316
+ }
317
+
318
+
319
+
320
+
321
+
322
+ static func recordingBootstrapError(from lastFailure: RunnerAppScreenCaptureFailure?) -> Error {
323
+ lastFailure
324
+ ?? NSError(
325
+ domain: "AgentDeviceRunner.Record",
326
+ code: 1,
327
+ userInfo: [NSLocalizedDescriptionKey: "failed to capture initial frame"]
328
+ )
329
+ }
330
+
331
+
332
+
333
+
334
+ static func recordingStartErrorPayload(for error: Error) -> ErrorPayload {
335
+ if let failure = error as? RunnerAppScreenCaptureFailure {
336
+ return ErrorPayload(code: failure.rawValue, message: failure.message, hint: failure.hint)
337
+ }
338
+ return ErrorPayload(message: "failed to start recording: \(error.localizedDescription)")
339
+ }
340
+ }
341
+
342
+
343
+
344
+
345
+
346
+
347
+
272
348
 
273
349
 
274
350