agent-device 0.21.8 → 0.21.9

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 (171) hide show
  1. package/android/ime-helper/dist/{agent-device-android-ime-helper-0.21.8.apk → agent-device-android-ime-helper-0.21.9.apk} +0 -0
  2. package/android/ime-helper/dist/agent-device-android-ime-helper-0.21.9.apk.sha256 +1 -0
  3. package/android/ime-helper/dist/{agent-device-android-ime-helper-0.21.8.manifest.json → agent-device-android-ime-helper-0.21.9.manifest.json} +4 -4
  4. package/android/snapshot-helper/dist/{agent-device-android-snapshot-helper-0.21.8.apk → agent-device-android-snapshot-helper-0.21.9.apk} +0 -0
  5. package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.21.9.apk.sha256 +1 -0
  6. package/android/snapshot-helper/dist/{agent-device-android-snapshot-helper-0.21.8.manifest.json → agent-device-android-snapshot-helper-0.21.9.manifest.json} +6 -6
  7. package/apple/macos-helper/Sources/AgentDeviceMacOSDeviceHub/DeviceHubWindowInventory.swift +65 -0
  8. package/apple/macos-helper/Sources/AgentDeviceMacOSHelper/DeviceHubPose.swift +45 -140
  9. package/apple/macos-helper/Sources/AgentDeviceMacOSHelper/DeviceHubSidebar.swift +57 -0
  10. package/apple/macos-helper/Sources/AgentDeviceMacOSHelper/DeviceHubWindowDiscovery.swift +108 -0
  11. package/apple/macos-helper/Tests/AgentDeviceMacOSDeviceHubTests/DeviceHubWindowInventoryTests.swift +65 -0
  12. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/AgentDeviceRunnerUITests-Bridging-Header.h +1 -0
  13. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerAppScreenCapture.swift +175 -0
  14. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerSynthesizedGesture.h +10 -2
  15. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerSynthesizedGesture.m +42 -15
  16. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Alert.swift +8 -0
  17. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandExecution.swift +45 -22
  18. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Interaction.swift +39 -734
  19. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Keyboard.swift +20 -4
  20. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Lifecycle.swift +63 -1
  21. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Models.swift +13 -0
  22. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Navigation.swift +25 -10
  23. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Snapshot.swift +5 -562
  24. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SnapshotAcquisition.swift +566 -0
  25. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SynthesizedGesturePolicy.swift +5 -1
  26. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SynthesizedInteraction.swift +594 -0
  27. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerXCTestEventBridge.h +35 -1
  28. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerXCTestEventBridge.m +160 -0
  29. package/dist/apple/runner/AgentDeviceRunner/RecordingScripts/RecordingExportSupport.swift +3 -0
  30. package/dist/apple/runner/AgentDeviceRunner/RecordingScripts/recording-frames.swift +203 -0
  31. package/dist/apple/snapshot-presentation/Sources/AgentDeviceSnapshotPresentation/SnapshotCoordinateSpace.swift +5 -0
  32. package/dist/src/absence-observation-errors.js +1 -1
  33. package/dist/src/adapter.js +1 -1
  34. package/dist/src/agent-browser-lifecycle.js +1 -1
  35. package/dist/src/agent-browser-process-record.js +1 -1
  36. package/dist/src/agent-device-client.js +1 -1
  37. package/dist/src/alert-runtime.js +1 -1
  38. package/dist/src/android-adb.js +1 -1
  39. package/dist/src/android-input-ownership.js +1 -1
  40. package/dist/src/android.js +1 -1
  41. package/dist/src/app-launch.js +1 -1
  42. package/dist/src/app-log-runtime2.js +3 -3
  43. package/dist/src/app-resolution.js +2 -2
  44. package/dist/src/apple-runner-platform.js +1 -1
  45. package/dist/src/application-lifecycle-interaction.js +1 -1
  46. package/dist/src/artifact-paths.js +1 -0
  47. package/dist/src/back-mode.js +1 -1
  48. package/dist/src/cli-config.js +1 -1
  49. package/dist/src/cli-help.js +8 -7
  50. package/dist/src/cli.js +4 -4
  51. package/dist/src/client-types.d.ts +23 -5
  52. package/dist/src/command-tools.js +3 -3
  53. package/dist/src/connection-runtime.js +1 -1
  54. package/dist/src/device-claim-inspection.js +1 -1
  55. package/dist/src/device-claim-rule.js +2 -2
  56. package/dist/src/device-input-state.d.ts +1 -1
  57. package/dist/src/device-selection-resolver.js +1 -1
  58. package/dist/src/device.js +1 -1
  59. package/dist/src/device2.js +1 -1
  60. package/dist/src/dispatch-resolve.js +1 -1
  61. package/dist/src/doctor-facade.js +1 -1
  62. package/dist/src/durable-resource.js +1 -1
  63. package/dist/src/find.js +1 -1
  64. package/dist/src/finders.js +1 -1
  65. package/dist/src/fold-runtime.js +1 -0
  66. package/dist/src/gesture-admission.js +1 -1
  67. package/dist/src/interaction-positionals.js +1 -1
  68. package/dist/src/interaction-ref-policy.js +1 -1
  69. package/dist/src/interaction.js +1 -1
  70. package/dist/src/interaction2.js +1 -1
  71. package/dist/src/interactor-operation-catalog.js +1 -1
  72. package/dist/src/interactor.js +4 -4
  73. package/dist/src/interactors.js +1 -1
  74. package/dist/src/internal/bin.js +1 -1
  75. package/dist/src/internal/daemon.js +2 -2
  76. package/dist/src/inventory2.js +1 -1
  77. package/dist/src/ios-snapshot-acquisition.js +1 -1
  78. package/dist/src/ios-snapshot-runtime.js +1 -1
  79. package/dist/src/lease.js +1 -1
  80. package/dist/src/local-interactor-operation-set.js +1 -1
  81. package/dist/src/location-coordinates.js +1 -1
  82. package/dist/src/managed-device-scope.js +1 -1
  83. package/dist/src/mechanics.js +17 -17
  84. package/dist/src/mobile-snapshot-semantics.js +1 -1
  85. package/dist/src/mp4-duration.js +1 -0
  86. package/dist/src/multitouch-support.js +1 -1
  87. package/dist/src/owner-identity.js +1 -1
  88. package/dist/src/parameterized-recorded-fill.js +1 -1
  89. package/dist/src/perf-facade.js +2 -2
  90. package/dist/src/perf-runtime-plan.js +1 -1
  91. package/dist/src/platform-request-scope.js +1 -1
  92. package/dist/src/platform-runtime-managed-owner.js +1 -1
  93. package/dist/src/platform-runtime-open-target.js +1 -1
  94. package/dist/src/platform-runtime-operation-host.js +2 -2
  95. package/dist/src/platform-runtime-runtime-hints.js +1 -1
  96. package/dist/src/platform-runtime-screen-recording-finalizer-host.js +1 -1
  97. package/dist/src/platform-runtime-unavailable.js +1 -1
  98. package/dist/src/platform-runtime.js +1 -1
  99. package/dist/src/platform-runtime2.js +1 -1
  100. package/dist/src/png-crop-bytes.js +1 -1
  101. package/dist/src/png-encode.js +1 -0
  102. package/dist/src/png-size.js +1 -0
  103. package/dist/src/provider-webdriver.js +1 -1
  104. package/dist/src/quality-warnings.js +1 -1
  105. package/dist/src/react-native.js +1 -1
  106. package/dist/src/record-runtime.js +1 -1
  107. package/dist/src/recording-export-quality.js +1 -1
  108. package/dist/src/recording-scope.js +1 -0
  109. package/dist/src/recording.js +3 -0
  110. package/dist/src/registry.js +35 -35
  111. package/dist/src/replay.js +2 -2
  112. package/dist/src/request-admission.js +1 -1
  113. package/dist/src/request-runtime-binding.js +1 -1
  114. package/dist/src/result-serialization.js +1 -1
  115. package/dist/src/runner-cache-metadata.js +2 -2
  116. package/dist/src/runner-client.js +1 -1
  117. package/dist/src/runner-contract.js +1 -1
  118. package/dist/src/runner-dev-tools-security.js +1 -1
  119. package/dist/src/runner-device-readiness.js +1 -1
  120. package/dist/src/runner-disposal.js +1 -1
  121. package/dist/src/runner-operations-facade.js +1 -1
  122. package/dist/src/runner-presentation.js +1 -1
  123. package/dist/src/runner.js +1 -1
  124. package/dist/src/runtime.js +2 -2
  125. package/dist/src/runtime10.js +2 -1
  126. package/dist/src/runtime2.js +4 -4
  127. package/dist/src/runtime4.js +5 -5
  128. package/dist/src/runtime5.js +1 -1
  129. package/dist/src/runtime6.js +1 -1
  130. package/dist/src/runtime7.js +1 -1
  131. package/dist/src/runtime8.js +1 -1
  132. package/dist/src/runtime9.js +1 -1
  133. package/dist/src/screen-recording-admission-ledger.js +1 -1
  134. package/dist/src/screen-recording-resource-recovery.js +1 -1
  135. package/dist/src/screen-recording-transport.js +1 -1
  136. package/dist/src/screenshot-density.js +1 -1
  137. package/dist/src/screenshot-overlay-draw.js +1 -0
  138. package/dist/src/screenshot-runtime.js +1 -1
  139. package/dist/src/script.js +1 -1
  140. package/dist/src/scroll-gesture.js +1 -1
  141. package/dist/src/sdk-batch.js +2 -2
  142. package/dist/src/sdk-contracts.d.ts +7 -5
  143. package/dist/src/sdk-device.js +1 -1
  144. package/dist/src/sdk-finders.js +1 -1
  145. package/dist/src/sdk-selectors.d.ts +44 -1
  146. package/dist/src/sdk-selectors.js +1 -1
  147. package/dist/src/selection.js +0 -0
  148. package/dist/src/selector-runtime.js +1 -1
  149. package/dist/src/selectors.js +1 -1
  150. package/dist/src/semantic-index.js +1 -1
  151. package/dist/src/session-routing.js +1 -1
  152. package/dist/src/session-snapshot.js +1 -1
  153. package/dist/src/session-store.js +3 -3
  154. package/dist/src/session2.js +3 -3
  155. package/dist/src/simctl.js +1 -1
  156. package/dist/src/simulator-boot.js +1 -1
  157. package/dist/src/snapshot-runtime-binding.js +1 -1
  158. package/dist/src/snapshot-runtime.js +1 -1
  159. package/dist/src/snapshot-state.js +1 -1
  160. package/dist/src/snapshot4.js +1 -1
  161. package/dist/src/src.js +1 -1
  162. package/dist/src/src7.js +1 -1
  163. package/dist/src/stop-sequence.js +1 -1
  164. package/dist/src/text-entry-focus.js +1 -1
  165. package/dist/src/tree.js +1 -1
  166. package/dist/src/type-text-runtime.js +1 -1
  167. package/dist/src/video.js +2 -2
  168. package/package.json +1 -1
  169. package/server.json +2 -2
  170. package/android/ime-helper/dist/agent-device-android-ime-helper-0.21.8.apk.sha256 +0 -1
  171. package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.21.8.apk.sha256 +0 -1
@@ -0,0 +1,175 @@
1
+ import XCTest
2
+ #if canImport(UIKit)
3
+ import UIKit
4
+ #endif
5
+
6
+
7
+
8
+
9
+ struct CapturedAppScreen {
10
+ let image: RunnerImage
11
+ let displayID: UInt
12
+ let pixelWidth: Int
13
+ let pixelHeight: Int
14
+ let pixelsPerPoint: Double
15
+ }
16
+
17
+
18
+
19
+
20
+ enum RunnerAppScreenCaptureFailure: String, Error {
21
+ case unresolvedWindow = "APP_SCREEN_WINDOW_UNRESOLVED"
22
+ case unresolvedScreen = "APP_SCREEN_UNRESOLVED"
23
+ case unrenderableImage = "APP_SCREEN_CAPTURE_UNRENDERABLE"
24
+
25
+ init(_ failure: RunnerApplicationScreenFailure) {
26
+ switch failure {
27
+ case .unresolvedWindow:
28
+ self = .unresolvedWindow
29
+ default:
30
+ self = .unresolvedScreen
31
+ }
32
+ }
33
+
34
+ var message: String {
35
+ switch self {
36
+ case .unresolvedWindow:
37
+ return "Neither the target app nor the system surface resolved a window, so no display could be named."
38
+ case .unresolvedScreen:
39
+ return "The resolved app window did not report the display it is shown on."
40
+ case .unrenderableImage:
41
+ return "The resolved display handed back an image that could not be drawn upright."
42
+ }
43
+ }
44
+
45
+ var hint: String {
46
+ switch self {
47
+ case .unresolvedWindow:
48
+ return "The capture follows the display that owns a window, so this is a runtime that is answering no window for any process; runner.log names the window frame and the query answer it refused."
49
+ case .unresolvedScreen:
50
+ return "Retry the capture. If it persists, the XCTest runtime is not reporting the window's display; runner.log names the window frame it resolved."
51
+ case .unrenderableImage:
52
+ return "Retry the capture. The display was resolved, so this is the capture itself failing, not a panel question."
53
+ }
54
+ }
55
+ }
56
+
57
+ #if canImport(UIKit) && os(iOS)
58
+ extension RunnerTests {
59
+
60
+
61
+
62
+
63
+
64
+
65
+
66
+
67
+
68
+
69
+
70
+ func captureResolvedAppScreen(
71
+ app: XCUIApplication
72
+ ) -> Result<CapturedAppScreen, RunnerAppScreenCaptureFailure> {
73
+ var outcome: Result<CapturedAppScreen, RunnerAppScreenCaptureFailure> = .failure(
74
+ .unresolvedScreen
75
+ )
76
+ let capture = {
77
+ outcome = Self.resolveCapturedAppScreen(app: app)
78
+ }
79
+ if Thread.isMainThread {
80
+ capture()
81
+ } else {
82
+ DispatchQueue.main.sync(execute: capture)
83
+ }
84
+ return outcome
85
+ }
86
+
87
+
88
+
89
+
90
+
91
+
92
+
93
+
94
+
95
+
96
+
97
+ func captureObservedScreen(app: XCUIApplication) -> Result<
98
+ CapturedAppScreen, RunnerAppScreenCaptureFailure
99
+ > {
100
+ selectObservedScreenCapture(
101
+ resolving: { captureResolvedAppScreen(app: app) },
102
+ fallingBack: { captureResolvedAppScreen(app: springboard) }
103
+ )
104
+ }
105
+
106
+
107
+
108
+ func selectObservedScreenCapture(
109
+ resolving: () -> Result<CapturedAppScreen, RunnerAppScreenCaptureFailure>,
110
+ fallingBack: () -> Result<CapturedAppScreen, RunnerAppScreenCaptureFailure>
111
+ ) -> Result<CapturedAppScreen, RunnerAppScreenCaptureFailure> {
112
+ let outcome = resolving()
113
+ if case .failure(.unresolvedWindow) = outcome {
114
+ return fallingBack()
115
+ }
116
+ return outcome
117
+ }
118
+
119
+ private static func resolveCapturedAppScreen(
120
+ app: XCUIApplication
121
+ ) -> Result<CapturedAppScreen, RunnerAppScreenCaptureFailure> {
122
+ var screen: AnyObject?
123
+ var displayID: UInt = 0
124
+ var resolution: RunnerApplicationScreenFailure = .unresolvedScreen
125
+ guard RunnerResolveApplicationScreen(app, &screen, &displayID, &resolution),
126
+ let captureScreen = screen as? XCUIScreen
127
+ else {
128
+ return .failure(RunnerAppScreenCaptureFailure(resolution))
129
+ }
130
+ let image = captureScreen.screenshot().image
131
+ guard let upright = runnerUprightCaptureImage(image) else {
132
+ return .failure(.unrenderableImage)
133
+ }
134
+
135
+
136
+
137
+ guard upright.scale.isFinite, upright.scale > 0 else {
138
+ return .failure(.unrenderableImage)
139
+ }
140
+ guard let cgImage = runnerCGImage(from: upright) else {
141
+ return .failure(.unrenderableImage)
142
+ }
143
+ return .success(
144
+ CapturedAppScreen(
145
+ image: upright,
146
+ displayID: displayID,
147
+ pixelWidth: cgImage.width,
148
+ pixelHeight: cgImage.height,
149
+ pixelsPerPoint: Double(upright.scale)
150
+ )
151
+ )
152
+ }
153
+
154
+
155
+
156
+
157
+
158
+
159
+
160
+ private static func runnerUprightCaptureImage(_ image: UIImage) -> UIImage? {
161
+ if image.imageOrientation == .up {
162
+ return image
163
+ }
164
+ guard image.size.width > 0, image.size.height > 0, image.scale.isFinite, image.scale > 0 else {
165
+ return nil
166
+ }
167
+ let format = UIGraphicsImageRendererFormat()
168
+ format.scale = image.scale
169
+ let renderer = UIGraphicsImageRenderer(size: image.size, format: format)
170
+ return renderer.image { _ in
171
+ image.draw(in: CGRect(origin: .zero, size: image.size))
172
+ }
173
+ }
174
+ }
175
+ #endif
@@ -8,7 +8,11 @@ FOUNDATION_EXPORT double RunnerControlledScrollProgress(double t);
8
8
 
9
9
  @interface RunnerSynthesizedGesture : NSObject
10
10
 
11
+ // `resolvedWindow` is the app window the caller already resolved for the gesture's reference
12
+ // frame. The record's display ID is read from that same window so geometry and routing can never
13
+ // name different windows; pass nil to fall back to the application's first window.
11
14
  + (NSString * _Nullable)synthesizeSwipeWithApplication:(id)application
15
+ resolvedWindow:(id _Nullable)resolvedWindow
12
16
  x:(double)x
13
17
  y:(double)y
14
18
  x2:(double)x2
@@ -16,26 +20,30 @@ FOUNDATION_EXPORT double RunnerControlledScrollProgress(double t);
16
20
  durationMs:(double)durationMs;
17
21
 
18
22
  + (NSString * _Nullable)synthesizeContinuousDragWithApplication:(id)application
23
+ resolvedWindow:(id _Nullable)resolvedWindow
19
24
  x:(double)x
20
25
  y:(double)y
21
26
  x2:(double)x2
22
- y2:(double)y2
27
+ y2:(double)y2
23
28
  durationMs:(double)durationMs;
24
29
 
25
30
  + (NSString * _Nullable)synthesizeControlledScrollWithApplication:(id)application
31
+ resolvedWindow:(id _Nullable)resolvedWindow
26
32
  x:(double)x
27
33
  y:(double)y
28
34
  x2:(double)x2
29
- y2:(double)y2
35
+ y2:(double)y2
30
36
  durationMs:(double)durationMs;
31
37
 
32
38
  + (NSString * _Nullable)synthesizeTapWithApplication:(id)application
39
+ resolvedWindow:(id _Nullable)resolvedWindow
33
40
  x:(double)x
34
41
  y:(double)y;
35
42
 
36
43
  // Each pointer is an ordered array of { x, y, offsetMs } samples. The first sample
37
44
  // starts contact; subsequent samples move it; all pointers lift at their final offset.
38
45
  + (NSString * _Nullable)synthesizeGestureWithApplication:(id)application
46
+ resolvedWindow:(id _Nullable)resolvedWindow
39
47
  pointerSamples:(NSArray<NSArray<NSDictionary<NSString *, NSNumber *> *> *> *)pointerSamples;
40
48
 
41
49
  // UIInterfaceOrientation of the app (1 portrait, 2 upsideDown, 3 landscapeRight,
@@ -6,13 +6,13 @@
6
6
 
7
7
  static NSString *const RunnerGestureSynthesisSurface = @"event";
8
8
 
9
- typedef id (*RunnerMsgSendInitRecord)(id, SEL, NSString *, NSInteger);
9
+ typedef id (*RunnerMsgSendInitRecord)(id, SEL, NSString *, NSUInteger, NSInteger);
10
10
  typedef id (*RunnerMsgSendInitPath)(id, SEL, CGPoint, NSTimeInterval);
11
11
  typedef void (*RunnerMsgSendPathMove)(id, SEL, CGPoint, NSTimeInterval);
12
12
  typedef void (*RunnerMsgSendPathOffset)(id, SEL, NSTimeInterval);
13
13
 
14
- // Gesture-specific extension of the shared bridge: the 2-arg
15
- // `initWithName:interfaceOrientation:` record initializer and the touch-path
14
+ // Gesture-specific extension of the shared bridge: the display-aware
15
+ // `initWithName:displayID:interfaceOrientation:` record initializer and the touch-path
16
16
  // factory/mutator selectors, none of which text-entry synthesis needs.
17
17
  typedef struct {
18
18
  RunnerXCTestEventBridge core;
@@ -37,6 +37,7 @@ static NSString * _Nullable RunnerResolveGestureEventBridge(
37
37
  );
38
38
  static NSString * _Nullable RunnerCreateEventRecord(
39
39
  id application,
40
+ id _Nullable resolvedWindow,
40
41
  NSString *recordName,
41
42
  RunnerGestureEventBridge *bridge,
42
43
  id *record
@@ -74,13 +75,14 @@ static id RunnerSampledDragPointerPath(
74
75
  static double RunnerSmoothstepProgress(double t);
75
76
  static NSString * _Nullable RunnerTrySynthesizeDrag(
76
77
  id application,
78
+ id _Nullable resolvedWindow,
77
79
  CGPoint start,
78
80
  CGPoint end,
79
81
  double durationMs,
80
82
  NSString *recordName,
81
83
  RunnerDragPointerPathFactory pathFactory
82
84
  );
83
- static NSString * _Nullable RunnerTrySynthesizeTap(id application, CGPoint point);
85
+ static NSString * _Nullable RunnerTrySynthesizeTap(id application, id _Nullable resolvedWindow, CGPoint point);
84
86
  // XCTest's proven swipe profile reaches the endpoint in 100 ms, then holds for the planned
85
87
  // fling duration. Fast movement is what lets UIKit distinguish a fling from a timed pan.
86
88
  static const NSTimeInterval RunnerSwipeMovementDurationSeconds = 0.1;
@@ -94,14 +96,16 @@ static id RunnerTapPointerPath(
94
96
  @implementation RunnerSynthesizedGesture
95
97
 
96
98
  + (NSString * _Nullable)synthesizeSwipeWithApplication:(id)application
97
- x:(double)x
98
- y:(double)y
99
- x2:(double)x2
100
- y2:(double)y2
99
+ resolvedWindow:(id _Nullable)resolvedWindow
100
+ x:(double)x
101
+ y:(double)y
102
+ x2:(double)x2
103
+ y2:(double)y2
101
104
  durationMs:(double)durationMs {
102
105
  @try {
103
106
  return RunnerTrySynthesizeDrag(
104
107
  application,
108
+ resolvedWindow,
105
109
  CGPointMake(x, y),
106
110
  CGPointMake(x2, y2),
107
111
  durationMs,
@@ -114,6 +118,7 @@ static id RunnerTapPointerPath(
114
118
  }
115
119
 
116
120
  + (NSString * _Nullable)synthesizeContinuousDragWithApplication:(id)application
121
+ resolvedWindow:(id _Nullable)resolvedWindow
117
122
  x:(double)x
118
123
  y:(double)y
119
124
  x2:(double)x2
@@ -122,6 +127,7 @@ static id RunnerTapPointerPath(
122
127
  @try {
123
128
  return RunnerTrySynthesizeDrag(
124
129
  application,
130
+ resolvedWindow,
125
131
  CGPointMake(x, y),
126
132
  CGPointMake(x2, y2),
127
133
  durationMs,
@@ -134,14 +140,16 @@ static id RunnerTapPointerPath(
134
140
  }
135
141
 
136
142
  + (NSString * _Nullable)synthesizeControlledScrollWithApplication:(id)application
143
+ resolvedWindow:(id _Nullable)resolvedWindow
137
144
  x:(double)x
138
145
  y:(double)y
139
146
  x2:(double)x2
140
- y2:(double)y2
141
- durationMs:(double)durationMs {
147
+ y2:(double)y2
148
+ durationMs:(double)durationMs {
142
149
  @try {
143
150
  return RunnerTrySynthesizeDrag(
144
151
  application,
152
+ resolvedWindow,
145
153
  CGPointMake(x, y),
146
154
  CGPointMake(x2, y2),
147
155
  durationMs,
@@ -154,22 +162,25 @@ static id RunnerTapPointerPath(
154
162
  }
155
163
 
156
164
  + (NSString * _Nullable)synthesizeTapWithApplication:(id)application
165
+ resolvedWindow:(id _Nullable)resolvedWindow
157
166
  x:(double)x
158
167
  y:(double)y {
159
168
  @try {
160
- return RunnerTrySynthesizeTap(application, CGPointMake(x, y));
169
+ return RunnerTrySynthesizeTap(application, resolvedWindow, CGPointMake(x, y));
161
170
  } @catch (NSException *exception) {
162
171
  return RunnerFormatXCTestException(exception, @"private XCTest event synthesis failed");
163
172
  }
164
173
  }
165
174
 
166
175
  + (NSString * _Nullable)synthesizeGestureWithApplication:(id)application
176
+ resolvedWindow:(id _Nullable)resolvedWindow
167
177
  pointerSamples:(NSArray<NSArray<NSDictionary<NSString *, NSNumber *> *> *> *)pointerSamples {
168
178
  @try {
169
179
  RunnerGestureEventBridge bridge;
170
180
  id record = nil;
171
181
  NSString *error = RunnerCreateEventRecord(
172
182
  application,
183
+ resolvedWindow,
173
184
  @"agent-device-gesture-plan",
174
185
  &bridge,
175
186
  &record
@@ -224,6 +235,7 @@ static id RunnerTapPointerPath(
224
235
 
225
236
  static NSString * _Nullable RunnerTrySynthesizeDrag(
226
237
  id application,
238
+ id _Nullable resolvedWindow,
227
239
  CGPoint start,
228
240
  CGPoint end,
229
241
  double durationMs,
@@ -232,7 +244,7 @@ static NSString * _Nullable RunnerTrySynthesizeDrag(
232
244
  ) {
233
245
  RunnerGestureEventBridge bridge;
234
246
  id record = nil;
235
- NSString *error = RunnerCreateEventRecord(application, recordName, &bridge, &record);
247
+ NSString *error = RunnerCreateEventRecord(application, resolvedWindow, recordName, &bridge, &record);
236
248
  if (error != nil) return error;
237
249
 
238
250
  id path = pathFactory(&bridge, start, end, durationMs);
@@ -244,11 +256,12 @@ static NSString * _Nullable RunnerTrySynthesizeDrag(
244
256
  return RunnerSynthesizeEventRecord(&bridge, record);
245
257
  }
246
258
 
247
- static NSString * _Nullable RunnerTrySynthesizeTap(id application, CGPoint point) {
259
+ static NSString * _Nullable RunnerTrySynthesizeTap(id application, id _Nullable resolvedWindow, CGPoint point) {
248
260
  RunnerGestureEventBridge bridge;
249
261
  id record = nil;
250
262
  NSString *error = RunnerCreateEventRecord(
251
263
  application,
264
+ resolvedWindow,
252
265
  @"agent-device-tap",
253
266
  &bridge,
254
267
  &record
@@ -271,14 +284,14 @@ static NSString * _Nullable RunnerResolveGestureEventBridge(
271
284
  NSString *missing = RunnerResolveXCTestEventBridge(application, RunnerGestureSynthesisSurface, &core);
272
285
  if (missing != nil) return missing;
273
286
 
274
- SEL initRecordSelector = NSSelectorFromString(@"initWithName:interfaceOrientation:");
287
+ SEL initRecordSelector = NSSelectorFromString(@"initWithName:displayID:interfaceOrientation:");
275
288
  SEL interfaceOrientationSelector = NSSelectorFromString(@"interfaceOrientation");
276
289
  SEL initPathSelector = NSSelectorFromString(@"initForTouchAtPoint:offset:");
277
290
  SEL moveSelector = NSSelectorFromString(@"moveToPoint:atOffset:");
278
291
  SEL liftSelector = NSSelectorFromString(@"liftUpAtOffset:");
279
292
 
280
293
  missing = RunnerRequireSelector(
281
- core.recordClass, initRecordSelector, @"initWithName:interfaceOrientation:", RunnerGestureSynthesisSurface
294
+ core.recordClass, initRecordSelector, @"initWithName:displayID:interfaceOrientation:", RunnerGestureSynthesisSurface
282
295
  );
283
296
  if (missing != nil) return missing;
284
297
  missing = RunnerRequireSelector(
@@ -311,6 +324,7 @@ static NSString * _Nullable RunnerResolveGestureEventBridge(
311
324
 
312
325
  static NSString * _Nullable RunnerCreateEventRecord(
313
326
  id application,
327
+ id _Nullable resolvedWindow,
314
328
  NSString *recordName,
315
329
  RunnerGestureEventBridge *bridge,
316
330
  id *record
@@ -318,6 +332,12 @@ static NSString * _Nullable RunnerCreateEventRecord(
318
332
  NSString *missing = RunnerResolveGestureEventBridge(application, bridge);
319
333
  if (missing != nil) return missing;
320
334
 
335
+ NSUInteger displayID = 0;
336
+ if (resolvedWindow == nil) {
337
+ return @"private XCTest event synthesis unavailable: no resolved application window";
338
+ }
339
+ NSString *displayError = RunnerResolveWindowDisplayID(resolvedWindow, &displayID);
340
+ if (displayError != nil) return displayError;
321
341
  NSInteger interfaceOrientation =
322
342
  ((RunnerMsgSendInteger)objc_msgSend)(application, bridge->interfaceOrientationSelector);
323
343
  NSInteger targetProcessID =
@@ -330,11 +350,18 @@ static NSString * _Nullable RunnerCreateEventRecord(
330
350
  [bridge->core.recordClass alloc],
331
351
  bridge->initRecordSelector,
332
352
  recordName,
353
+ displayID,
333
354
  interfaceOrientation
334
355
  );
335
356
  if (eventRecord == nil) {
336
357
  return @"private XCTest event synthesis failed: could not create event record";
337
358
  }
359
+ NSLog(
360
+ @"AGENT_DEVICE_RUNNER_SYNTHESIZED_RECORD name=%@ displayID=%lu interfaceOrientation=%ld",
361
+ recordName,
362
+ (unsigned long)displayID,
363
+ (long)interfaceOrientation
364
+ );
338
365
  ((RunnerMsgSendSetInteger)objc_msgSend)(
339
366
  eventRecord,
340
367
  bridge->core.setTargetProcessIDSelector,
@@ -68,6 +68,14 @@ extension RunnerTests {
68
68
  guard waitUntilAlertButtonHittable(button, deadline: deadline) else {
69
69
  return alertVerificationResponse(.timedOut, action: action, activated: false)
70
70
  }
71
+ let buttonFrame = button.frame
72
+ NSLog(
73
+ "AGENT_DEVICE_RUNNER_ALERT_ACTIVATION action=%@ label=%@ frame=(%.1f,%.1f,%.1f,%.1f) point=(%.1f,%.1f)",
74
+ action,
75
+ button.label,
76
+ buttonFrame.origin.x, buttonFrame.origin.y, buttonFrame.size.width, buttonFrame.size.height,
77
+ buttonFrame.midX, buttonFrame.midY
78
+ )
71
79
 
72
80
 
73
81
 
@@ -24,6 +24,7 @@ import AgentDeviceSnapshotPresentation
24
24
 
25
25
 
26
26
 
27
+
27
28
 
28
29
 
29
30
  extension RunnerTests {
@@ -1465,7 +1466,7 @@ extension RunnerTests {
1465
1466
  fps: command.fps.map { Int32($0) }
1466
1467
  )
1467
1468
  try recorder.start { [weak self] in
1468
- return self?.captureRunnerFrame()
1469
+ return self?.captureRunnerFrame(app: activeApp)
1469
1470
  }
1470
1471
  activeRecording = recorder
1471
1472
  return Response(ok: true, data: DataPayload(message: "recording started"))
@@ -2054,7 +2055,6 @@ extension RunnerTests {
2054
2055
  case .snapshot:
2055
2056
  return try executeSnapshotPrepared(command: command, activeApp: activeApp)
2056
2057
  case .screenshot:
2057
- let screenshot: XCUIScreenshot
2058
2058
  #if os(macOS)
2059
2059
 
2060
2060
  if let bundleId = command.appBundleId, !bundleId.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
@@ -2064,6 +2064,7 @@ extension RunnerTests {
2064
2064
 
2065
2065
  sleepFor(0.5)
2066
2066
  }
2067
+ let screenshot: XCUIScreenshot
2067
2068
  if command.fullscreen == true {
2068
2069
  screenshot = XCUIScreen.main.screenshot()
2069
2070
  } else if let bundleId = command.appBundleId, !bundleId.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
@@ -2071,30 +2072,52 @@ extension RunnerTests {
2071
2072
  } else {
2072
2073
  screenshot = XCUIScreen.main.screenshot()
2073
2074
  }
2074
- #else
2075
- screenshot = XCUIScreen.main.screenshot()
2076
- #endif
2077
- guard let pngData = runnerPngData(for: screenshot.image) else {
2078
- return Response(ok: false, error: ErrorPayload(message: "Failed to encode screenshot as PNG"))
2079
- }
2080
- if command.inlineScreenshot == true {
2075
+ return screenshotResponse(
2076
+ image: screenshot.image,
2077
+ inlineScreenshot: command.inlineScreenshot == true
2078
+ )
2079
+ #elseif os(iOS)
2080
+
2081
+
2082
+
2083
+
2084
+
2085
+
2086
+
2087
+
2088
+
2089
+ switch captureObservedScreen(app: resolveAppWithoutActivation(command: command)) {
2090
+ case .failure(let failure):
2081
2091
  return Response(
2082
- ok: true,
2083
- data: DataPayload(imageBase64: pngData.base64EncodedString())
2092
+ ok: false,
2093
+ error: ErrorPayload(code: failure.rawValue, message: failure.message, hint: failure.hint)
2094
+ )
2095
+ case .success(let captured):
2096
+
2097
+
2098
+ NSLog(
2099
+ "AGENT_DEVICE_RUNNER_SCREEN_CAPTURE display=%lu pixels=%ldx%ld pixelsPerPoint=%g",
2100
+ captured.displayID,
2101
+ captured.pixelWidth,
2102
+ captured.pixelHeight,
2103
+ captured.pixelsPerPoint
2104
+ )
2105
+ return screenshotResponse(
2106
+ image: captured.image,
2107
+ inlineScreenshot: command.inlineScreenshot == true,
2108
+ metadata: ScreenshotMetadataPayload(
2109
+ displayID: captured.displayID,
2110
+ pixelWidth: captured.pixelWidth,
2111
+ pixelHeight: captured.pixelHeight,
2112
+ pixelsPerPoint: captured.pixelsPerPoint
2113
+ )
2084
2114
  )
2085
2115
  }
2086
- let fileName = "screenshot-\(Int(Date().timeIntervalSince1970 * 1000)).png"
2087
- let filePath = (NSTemporaryDirectory() as NSString).appendingPathComponent(fileName)
2088
- do {
2089
- try pngData.write(to: URL(fileURLWithPath: filePath))
2090
- } catch {
2091
- return Response(ok: false, error: ErrorPayload(message: "Failed to write screenshot: \(error.localizedDescription)"))
2092
- }
2093
- #if os(macOS)
2094
- return Response(ok: true, data: DataPayload(message: filePath))
2095
2116
  #else
2096
-
2097
- return Response(ok: true, data: DataPayload(message: "tmp/\(fileName)"))
2117
+ return screenshotResponse(
2118
+ image: XCUIScreen.main.screenshot().image,
2119
+ inlineScreenshot: command.inlineScreenshot == true
2120
+ )
2098
2121
  #endif
2099
2122
  case .back, .backInApp:
2100
2123
  if tapInAppBackControl(app: activeApp) {