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
@@ -0,0 +1 @@
1
+ 8388dc7a3c3f18aca7322cc50bc4f3ff769d426733746a764f45b56d50a0f267 agent-device-android-ime-helper-0.21.13.apk
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "android-ime-helper",
3
- "version": "0.21.11",
4
- "assetName": "agent-device-android-ime-helper-0.21.11.apk",
5
- "sha256": "63beaf80513455a01906685716ad7a91169cb5632dc054e02d06047957490697",
3
+ "version": "0.21.13",
4
+ "assetName": "agent-device-android-ime-helper-0.21.13.apk",
5
+ "sha256": "8388dc7a3c3f18aca7322cc50bc4f3ff769d426733746a764f45b56d50a0f267",
6
6
  "packageName": "com.callstack.agentdevice.imehelper",
7
- "versionCode": 21011,
7
+ "versionCode": 21013,
8
8
  "serviceComponent": "com.callstack.agentdevice.imehelper/.TestInputMethodService",
9
9
  "broadcastProtocol": "android-ime-helper-v1"
10
10
  }
@@ -0,0 +1 @@
1
+ e07b556384093a04be99af068b6493e0b0a66e7e7cf09264da4fe60f75f2d164 agent-device-android-snapshot-helper-0.21.13.apk
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "android-snapshot-helper",
3
- "version": "0.21.11",
4
- "releaseTag": "v0.21.11",
5
- "assetName": "agent-device-android-snapshot-helper-0.21.11.apk",
3
+ "version": "0.21.13",
4
+ "releaseTag": "v0.21.13",
5
+ "assetName": "agent-device-android-snapshot-helper-0.21.13.apk",
6
6
  "apkUrl": null,
7
- "sha256": "9201208584b56e121818bcfcf8db066d8f0f63309af27c34edccc0f9b16e9cfb",
8
- "checksumName": "agent-device-android-snapshot-helper-0.21.11.apk.sha256",
7
+ "sha256": "e07b556384093a04be99af068b6493e0b0a66e7e7cf09264da4fe60f75f2d164",
8
+ "checksumName": "agent-device-android-snapshot-helper-0.21.13.apk.sha256",
9
9
  "packageName": "com.callstack.agentdevice.snapshothelper",
10
- "versionCode": 21011,
10
+ "versionCode": 21013,
11
11
  "instrumentationRunner": "com.callstack.agentdevice.snapshothelper/.SnapshotInstrumentation",
12
12
  "minSdk": 23,
13
13
  "targetSdk": 36,
@@ -0,0 +1,97 @@
1
+ #import <Foundation/Foundation.h>
2
+ #import <mach/mach_time.h>
3
+ #import <math.h>
4
+
5
+ // Private simulator HID entry points; the host verifies the resulting hinge independently.
6
+ extern CFDataRef IOCFSerialize(CFTypeRef object, CFOptionFlags options);
7
+ extern CFTypeRef IOHIDEventCreateVendorDefinedEvent(CFAllocatorRef allocator, uint64_t timestamp,
8
+ uint32_t usagePage, uint32_t usage, uint32_t version, const uint8_t *data,
9
+ CFIndex length, uint32_t options);
10
+ extern CFTypeRef IOHIDEventSystemClientCreate(CFAllocatorRef allocator);
11
+ extern void IOHIDEventSystemClientDispatchEvent(CFTypeRef client, CFTypeRef event);
12
+
13
+ static BOOL sendAngle(CFTypeRef client, double angle) {
14
+ NSDictionary *payload = @{
15
+ @"provider": @"com.apple.Virtualization.VirtualMachines",
16
+ @"source": @"hinge-slider-control", @"type": @"range", @"value": @(angle),
17
+ };
18
+ CFDataRef data = IOCFSerialize((__bridge CFTypeRef)payload, 1);
19
+ if (!data) return NO;
20
+ CFTypeRef event = IOHIDEventCreateVendorDefinedEvent(kCFAllocatorDefault,
21
+ mach_absolute_time(), 0xff61, 0x5b, 0, CFDataGetBytePtr(data), CFDataGetLength(data), 0);
22
+ if (event) IOHIDEventSystemClientDispatchEvent(client, event);
23
+ CFRelease(data);
24
+ if (!event) return NO;
25
+ CFRelease(event);
26
+ return YES;
27
+ }
28
+
29
+ static BOOL validNumber(id value) {
30
+ return [value isKindOfClass:NSNumber.class] &&
31
+ CFGetTypeID((__bridge CFTypeRef)value) != CFBooleanGetTypeID() && isfinite([value doubleValue]);
32
+ }
33
+
34
+ static NSArray *readFrames(NSString *argument) {
35
+ NSNumber *preset = (@{@"closed": @0, @"half-open": @130, @"open": @180})[argument];
36
+ if (preset) return @[@{@"atMs": @0, @"angle": preset}];
37
+ id frames = [NSJSONSerialization JSONObjectWithData:[argument dataUsingEncoding:NSUTF8StringEncoding]
38
+ options:0 error:NULL];
39
+ if (![frames isKindOfClass:NSArray.class] || [frames count] < 2 || [frames count] > 64) return nil;
40
+ double previous = -1;
41
+ for (id frame in frames) {
42
+ if (![frame isKindOfClass:NSDictionary.class] || [frame count] != 2 ||
43
+ !validNumber(frame[@"atMs"]) || !validNumber(frame[@"angle"])) return nil;
44
+ double time = [frame[@"atMs"] doubleValue], angle = [frame[@"angle"] doubleValue];
45
+ if (time < 0 || time > 60000 || floor(time) != time || time <= previous ||
46
+ (previous == -1 && time != 0) || angle < 0 || angle > 180) return nil;
47
+ previous = time;
48
+ }
49
+ return frames;
50
+ }
51
+
52
+ static double angleAtTime(NSArray *frames, double elapsed) {
53
+ NSUInteger segment = 0;
54
+ while (segment + 1 < frames.count && [frames[segment + 1][@"atMs"] doubleValue] <= elapsed) segment++;
55
+ double angle = [frames[segment][@"angle"] doubleValue];
56
+ if (segment + 1 < frames.count) {
57
+ double from = [frames[segment][@"atMs"] doubleValue];
58
+ double to = [frames[segment + 1][@"atMs"] doubleValue];
59
+ angle += ([frames[segment + 1][@"angle"] doubleValue] - angle) * (elapsed - from) / (to - from);
60
+ }
61
+ return angle;
62
+ }
63
+
64
+ static BOOL runFrames(CFTypeRef client, NSArray *frames) {
65
+ mach_timebase_info_data_t clock;
66
+ mach_timebase_info(&clock);
67
+ uint64_t start = mach_absolute_time();
68
+ double duration = [frames.lastObject[@"atMs"] doubleValue];
69
+ double elapsed = 0;
70
+ while (YES) {
71
+ @autoreleasepool {
72
+ if (!sendAngle(client, angleAtTime(frames, elapsed))) return NO;
73
+ }
74
+ if (elapsed >= duration) return YES;
75
+ double now = (mach_absolute_time() - start) * clock.numer / (double)clock.denom / 1e6;
76
+ // Absolute deadlines skip missed frames instead of extending the user's timeline.
77
+ double next = fmin(duration, (floor(now * 60 / 1000) + 1) * 1000 / 60);
78
+ mach_wait_until(start + (uint64_t)(next * 1e6 * clock.denom / clock.numer));
79
+ elapsed = fmin(duration, (mach_absolute_time() - start) * clock.numer / (double)clock.denom / 1e6);
80
+ }
81
+ }
82
+
83
+ int main(int argc, const char *argv[]) {
84
+ @autoreleasepool {
85
+ NSArray *frames = argc == 2 ? readFrames(@(argv[1])) : nil;
86
+ if (!frames) {
87
+ fprintf(stderr, "Expected a pose or 2-64 {atMs,angle} keyframes spanning 0 to at most 60000ms\n");
88
+ return 2;
89
+ }
90
+ CFTypeRef client = IOHIDEventSystemClientCreate(kCFAllocatorDefault);
91
+ if (!client) return 1;
92
+ BOOL dispatched = runFrames(client, frames);
93
+ CFRelease(client);
94
+ if (!dispatched) fprintf(stderr, "Unable to create simulator HID event\n");
95
+ return dispatched ? 0 : 1;
96
+ }
97
+ }
@@ -14,20 +14,17 @@ let package = Package(
14
14
  .target(
15
15
  name: "AgentDeviceMacOSInput"
16
16
  ),
17
- .target(
18
- name: "AgentDeviceMacOSDeviceHub"
19
- ),
20
17
  .executableTarget(
21
18
  name: "AgentDeviceMacOSHelper",
22
- dependencies: ["AgentDeviceMacOSInput", "AgentDeviceMacOSDeviceHub"]
19
+ dependencies: ["AgentDeviceMacOSInput"]
23
20
  ),
24
21
  .testTarget(
25
22
  name: "AgentDeviceMacOSInputTests",
26
23
  dependencies: ["AgentDeviceMacOSInput"]
27
24
  ),
28
25
  .testTarget(
29
- name: "AgentDeviceMacOSDeviceHubTests",
30
- dependencies: ["AgentDeviceMacOSDeviceHub"]
26
+ name: "AgentDeviceMacOSHelperTests",
27
+ dependencies: ["AgentDeviceMacOSHelper"]
31
28
  ),
32
29
  ]
33
30
  )
@@ -75,7 +75,6 @@ struct PressResponse: Encodable {
75
75
  struct ScreenshotResponse: Encodable {
76
76
  let path: String
77
77
  let surface: String?
78
- let fullscreen: Bool
79
78
  }
80
79
 
81
80
  struct AgentDeviceMacOSHelper {
@@ -127,8 +126,6 @@ struct AgentDeviceMacOSHelper {
127
126
  return try handleScreenshot(arguments: Array(arguments.dropFirst()))
128
127
  case "audio-probe":
129
128
  return try handleAudioProbe(arguments: Array(arguments.dropFirst()))
130
- case "device-hub":
131
- return try handleDeviceHub(arguments: Array(arguments.dropFirst()))
132
129
  default:
133
130
  throw HelperError.invalidArgs("unknown command: \(command)")
134
131
  }
@@ -438,9 +435,8 @@ struct AgentDeviceMacOSHelper {
438
435
  }
439
436
 
440
437
  let surface = optionValue(arguments: arguments, name: "--surface")
441
- let fullscreen = arguments.contains("--fullscreen")
442
- try captureSurfaceScreenshot(surface: surface, outPath: outPath, fullscreen: fullscreen)
443
- return SuccessEnvelope(data: ScreenshotResponse(path: outPath, surface: surface, fullscreen: fullscreen))
438
+ try captureSurfaceScreenshot(surface: surface, outPath: outPath)
439
+ return SuccessEnvelope(data: ScreenshotResponse(path: outPath, surface: surface))
444
440
  }
445
441
 
446
442
  static func handleAudioProbe(arguments: [String]) throws -> any Encodable {
@@ -549,8 +545,7 @@ private func pressAtPosition(_ request: MouseClickRequest) throws {
549
545
  }
550
546
  }
551
547
 
552
- private func captureSurfaceScreenshot(surface: String?, outPath: String, fullscreen: Bool) throws {
553
- _ = fullscreen
548
+ private func captureSurfaceScreenshot(surface: String?, outPath: String) throws {
554
549
  guard #available(macOS 15.2, *) else {
555
550
  throw HelperError.commandFailed(
556
551
  "screenshot on macOS desktop and menubar surfaces requires macOS 15.2 or newer"
@@ -0,0 +1,15 @@
1
+ import XCTest
2
+
3
+ @testable import AgentDeviceMacOSHelper
4
+
5
+ /// `captureSurfaceScreenshot` always reads the main display, so the response describes only
6
+ /// what was actually captured: no field for an argument the helper does not read.
7
+ final class ScreenshotResponseTests: XCTestCase {
8
+ func testResponseNeverCarriesAFullscreenField() throws {
9
+ let response = ScreenshotResponse(path: "/tmp/out.png", surface: "desktop")
10
+ let data = try JSONEncoder().encode(response)
11
+ let decoded = try JSONSerialization.jsonObject(with: data) as? [String: Any]
12
+
13
+ XCTAssertEqual(Set(decoded?.keys.map { $0 } ?? []), ["path", "surface"])
14
+ }
15
+ }
@@ -18,7 +18,7 @@
18
18
  NSString *const kProtocolVersionKey = @"protocolVersion";
19
19
  NSString *const kSourceVersionKey = @"sourceVersion";
20
20
  NSString *const kRequestIdKey = @"requestId";
21
- NSString *const kSourceVersion = @"agent-device-simulator-ax-v1.6.0";
21
+ NSString *const kSourceVersion = @"agent-device-simulator-ax-v1.7.0";
22
22
  const NSUInteger kProtocolVersion = 1;
23
23
  const uint32_t kMaximumFrameBytes = 16 * 1024 * 1024;
24
24
  const NSUInteger kMaximumDepth = 128;
@@ -34,8 +34,11 @@ static NSString *const kAttributeFrame = @"XC_kAXXCAttributeFrame";
34
34
  static NSString *const kAttributeAutomationType = @"XC_kAXXCAttributeAutomationType";
35
35
  static NSString *const kAttributeTraits = @"XC_kAXXCAttributeTraits";
36
36
  static NSString *const kAttributeChildren = @"XC_kAXXCAttributeChildren";
37
+ static NSString *const kAttributeIsUserInteractionEnabled = @"XC_kAXXCAttributeIsUserInteractionEnabled";
38
+ static NSString *const kPresentationDimmingViewClass = @"UIDimmingView";
37
39
  static NSString *const kSnapshotAttributes = @"UIAccessibilitySnapshotKeyAttributes";
38
40
  static NSString *const kSnapshotChildren = @"UIAccessibilitySnapshotKeyChildren";
41
+ static NSString *const kSnapshotElement = @"UIAccessibilitySnapshotKeyElement";
39
42
  static NSString *const kXctAutomationSupportPath =
40
43
  @"/Developer/Library/PrivateFrameworks/XCTAutomationSupport.framework/XCTAutomationSupport";
41
44
  static NSString *const kAxRuntimePath =
@@ -202,8 +205,35 @@ static void finishRequestWatchdog(dispatch_source_t watchdog, SnapshotWatchdogSt
202
205
  return nil;
203
206
  }
204
207
 
208
+ /*
209
+ * Whether a presentation's dimming view takes touches is what separates a sheet that blocks the
210
+ * content under it from one resting at an undimmed detent, and nothing else in the tree says so.
211
+ * It is read for dimming views alone: requesting it for every node costs about half again the
212
+ * capture time.
213
+ */
214
+ - (nullable NSNumber *)userInteractionEnabledForSnapshot:(NSDictionary *)snapshot
215
+ options:(nullable NSDictionary *)options
216
+ {
217
+ id element = snapshot[kSnapshotElement];
218
+ NSNumber *attribute = [options[@"attributes"] firstObject];
219
+ if (!element || ![attribute isKindOfClass:NSNumber.class]) return nil;
220
+ @try {
221
+ NSError *failure = nil;
222
+ NSDictionary *read = [_framework userTestingSnapshotForElement:element options:options error:&failure];
223
+ if (![read isKindOfClass:NSDictionary.class]) return nil;
224
+ NSDictionary *attributes = read[kSnapshotAttributes];
225
+ if (![attributes isKindOfClass:NSDictionary.class]) return nil;
226
+ id value = attributes[attribute];
227
+ return [value isKindOfClass:NSNumber.class] ? @([(NSNumber *)value boolValue]) : nil;
228
+ } @catch (NSException *exception) {
229
+ (void)exception;
230
+ return nil;
231
+ }
232
+ }
233
+
205
234
  - (nullable NSDictionary *)nodeFromSnapshot:(id)snapshot
206
235
  namesByNumber:(NSDictionary<NSNumber *, NSString *> *)namesByNumber
236
+ interactionOptions:(nullable NSDictionary *)interactionOptions
207
237
  depth:(NSUInteger)depth
208
238
  maxDepth:(NSUInteger)maxDepth
209
239
  maxNodes:(NSUInteger)maxNodes
@@ -233,6 +263,11 @@ static void finishRequestWatchdog(dispatch_source_t watchdog, SnapshotWatchdogSt
233
263
  id safe = [self jsonValue:attributes[number] name:name];
234
264
  if (safe) node[name] = safe;
235
265
  }
266
+ if ([node[kAttributeElementType] isEqual:kPresentationDimmingViewClass]) {
267
+ NSNumber *interaction = [self userInteractionEnabledForSnapshot:(NSDictionary *)snapshot
268
+ options:interactionOptions];
269
+ if (interaction) node[kAttributeIsUserInteractionEnabled] = interaction;
270
+ }
236
271
 
237
272
  NSArray *children = ((NSDictionary *)snapshot)[kSnapshotChildren];
238
273
  if (![children isKindOfClass:NSArray.class]) {
@@ -246,6 +281,7 @@ static void finishRequestWatchdog(dispatch_source_t watchdog, SnapshotWatchdogSt
246
281
  for (id child in children) {
247
282
  NSDictionary *built = [self nodeFromSnapshot:child
248
283
  namesByNumber:namesByNumber
284
+ interactionOptions:interactionOptions
249
285
  depth:depth + 1
250
286
  maxDepth:maxDepth
251
287
  maxNodes:maxNodes
@@ -317,6 +353,15 @@ static void finishRequestWatchdog(dispatch_source_t watchdog, SnapshotWatchdogSt
317
353
  options[@"maxDepth"] = @(maxDepth);
318
354
  options[@"maxChildren"] = @(maxNodes);
319
355
  options[@"maxArrayCount"] = @(maxNodes);
356
+ NSArray<NSNumber *> *interactionAttribute = _attributeNumbersForNames(@[ kAttributeIsUserInteractionEnabled ]);
357
+ NSMutableDictionary *interactionOptions = nil;
358
+ if ([interactionAttribute isKindOfClass:NSArray.class] && interactionAttribute.count == 1) {
359
+ interactionOptions = [options mutableCopy];
360
+ interactionOptions[@"attributes"] = interactionAttribute;
361
+ interactionOptions[@"maxDepth"] = @0;
362
+ interactionOptions[@"maxChildren"] = @0;
363
+ interactionOptions[@"maxArrayCount"] = @0;
364
+ }
320
365
  BOOL automationEnabled = [self assertAutomationMode:YES];
321
366
  NSError *runtimeError = nil;
322
367
  id snapshot = nil;
@@ -372,6 +417,7 @@ static void finishRequestWatchdog(dispatch_source_t watchdog, SnapshotWatchdogSt
372
417
  NSUInteger count = 0;
373
418
  NSDictionary *tree = [self nodeFromSnapshot:snapshot
374
419
  namesByNumber:namesByNumber
420
+ interactionOptions:interactionOptions
375
421
  depth:0
376
422
  maxDepth:maxDepth
377
423
  maxNodes:maxNodes
@@ -56,14 +56,24 @@ int main(int argc, const char *argv[]) {
56
56
 
57
57
  #else
58
58
  #import <UIKit/UIKit.h>
59
+ #if TARGET_OS_IOS
60
+ #import <UserNotifications/UserNotifications.h>
61
+ #endif
59
62
 
60
63
  @interface AgentDeviceRunnerViewController : UIViewController
61
64
  @property(nonatomic, strong) UILabel *alertActionStatus;
65
+ @property(nonatomic, strong) UILabel *alertActivationBusyAnswer;
62
66
  @property(nonatomic, assign) NSUInteger firstAlertActions;
63
67
  @property(nonatomic, assign) NSUInteger replacementAlertActions;
64
68
  @property(nonatomic, assign) BOOL alertFixtureStarted;
65
69
  @property(nonatomic, strong) NSTimer *alertActivationBusyBackstop;
70
+ @property(nonatomic, strong) NSTimer *alertBannerRepost;
71
+ @end
72
+
73
+ #if TARGET_OS_IOS
74
+ @interface AgentDeviceRunnerViewController () <UNUserNotificationCenterDelegate>
66
75
  @end
76
+ #endif
67
77
 
68
78
  @implementation AgentDeviceRunnerViewController
69
79
 
@@ -72,15 +82,21 @@ int main(int argc, const char *argv[]) {
72
82
  // may receive an event: the app keeps reporting work in flight, which is the state that cost an alert
73
83
  // command its whole deadline in #2546. It stops the moment an alert button is answered, since that
74
84
  // answer is the event the runner is trying to land, and the backstop stops it even when no answer
75
- // arrives so a regressed run finishes rather than waiting out XCTest's own timeout. A layer
85
+ // arrives so a regressed run finishes rather than waiting out XCTest's own timeout. The test passes
86
+ // the backstop after `--agent-device-alert-activation-busy`, sized to outlast its whole resolution
87
+ // and activation budget, so a slow host cannot end the busy state before the answer lands. A layer
76
88
  // animation on its own is not enough; only a UIView animation counts as in-flight work here.
77
- static NSTimeInterval const kAgentDeviceAlertActivationBusyWindow = 20.0;
89
+ static NSTimeInterval AgentDeviceAlertActivationBusyWindow(void) {
90
+ NSArray<NSString *> *arguments = NSProcessInfo.processInfo.arguments;
91
+ NSUInteger flag = [arguments indexOfObject:@"--agent-device-alert-activation-busy"];
92
+ return flag + 1 < arguments.count ? arguments[flag + 1].doubleValue : 0;
93
+ }
78
94
 
79
95
  - (void)startAlertActivationBusy {
80
96
  if (self.alertActivationBusyBackstop != nil) {
81
97
  return;
82
98
  }
83
- self.alertActivationBusyBackstop = [NSTimer scheduledTimerWithTimeInterval:kAgentDeviceAlertActivationBusyWindow
99
+ self.alertActivationBusyBackstop = [NSTimer scheduledTimerWithTimeInterval:AgentDeviceAlertActivationBusyWindow()
84
100
  target:self
85
101
  selector:@selector(stopAlertActivationBusy)
86
102
  userInfo:nil
@@ -101,6 +117,57 @@ static NSTimeInterval const kAgentDeviceAlertActivationBusyWindow = 20.0;
101
117
  self.alertActivationBusyBackstop = nil;
102
118
  }
103
119
 
120
+ // A banner from this app, shown over its own alert and re-posted before the previous one expires so
121
+ // one is on screen for as long as the first alert is unanswered. XCTest treats such a banner as an
122
+ // interruption of every event aimed at the app (#2546's late tap, from the banner side).
123
+ - (void)startAlertBannerThen:(dispatch_block_t)presentAlert {
124
+ UNUserNotificationCenter *center = UNUserNotificationCenter.currentNotificationCenter;
125
+ center.delegate = self;
126
+ [center requestAuthorizationWithOptions:UNAuthorizationOptionAlert
127
+ completionHandler:^(BOOL granted, NSError *error) {
128
+ (void)error;
129
+ if (!granted) {
130
+ return;
131
+ }
132
+ dispatch_async(dispatch_get_main_queue(), ^{
133
+ presentAlert();
134
+ [self postAlertBanner];
135
+ self.alertBannerRepost = [NSTimer scheduledTimerWithTimeInterval:2.0
136
+ target:self
137
+ selector:@selector(postAlertBanner)
138
+ userInfo:nil
139
+ repeats:YES];
140
+ });
141
+ }];
142
+ }
143
+
144
+ - (void)postAlertBanner {
145
+ UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init];
146
+ content.title = @"Agent Device banner";
147
+ content.body = @"Shown over the alert fixture";
148
+ UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:NSUUID.UUID.UUIDString
149
+ content:content
150
+ trigger:nil];
151
+ [UNUserNotificationCenter.currentNotificationCenter addNotificationRequest:request withCompletionHandler:nil];
152
+ }
153
+
154
+ - (void)stopAlertBanner {
155
+ if (self.alertBannerRepost == nil) {
156
+ return;
157
+ }
158
+ [self.alertBannerRepost invalidate];
159
+ self.alertBannerRepost = nil;
160
+ [UNUserNotificationCenter.currentNotificationCenter removeAllDeliveredNotifications];
161
+ }
162
+
163
+ - (void)userNotificationCenter:(UNUserNotificationCenter *)center
164
+ willPresentNotification:(UNNotification *)notification
165
+ withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler {
166
+ (void)center;
167
+ (void)notification;
168
+ completionHandler(UNNotificationPresentationOptionBanner);
169
+ }
170
+
104
171
 
105
172
  - (void)updateAlertActionStatus {
106
173
  self.alertActionStatus.text = [NSString stringWithFormat:@"First actions: %lu; replacement actions: %lu",
@@ -123,7 +190,12 @@ static NSTimeInterval const kAgentDeviceAlertActivationBusyWindow = 20.0;
123
190
  ? UIAlertActionStyleCancel : UIAlertActionStyleDefault;
124
191
  [alert addAction:[UIAlertAction actionWithTitle:buttonTitle style:style handler:^(UIAlertAction *action) {
125
192
  (void)action;
193
+ if (!replacement) {
194
+ self.alertActivationBusyAnswer.text = self.alertActivationBusyBackstop != nil
195
+ ? @"Answered while busy" : @"Answered after the app went idle";
196
+ }
126
197
  [self stopAlertActivationBusy];
198
+ [self stopAlertBanner];
127
199
  if (replacement) {
128
200
  self.replacementAlertActions += 1;
129
201
  } else {
@@ -145,9 +217,16 @@ static NSTimeInterval const kAgentDeviceAlertActivationBusyWindow = 20.0;
145
217
  if (!self.alertFixtureStarted &&
146
218
  [NSProcessInfo.processInfo.arguments containsObject:@"--agent-device-alert-replacement-regression"]) {
147
219
  self.alertFixtureStarted = YES;
148
- [self presentAlertFixtureReplacement:NO];
149
- if ([NSProcessInfo.processInfo.arguments containsObject:@"--agent-device-alert-activation-busy"]) {
150
- [self startAlertActivationBusy];
220
+ dispatch_block_t presentAlert = ^{
221
+ [self presentAlertFixtureReplacement:NO];
222
+ if ([NSProcessInfo.processInfo.arguments containsObject:@"--agent-device-alert-activation-busy"]) {
223
+ [self startAlertActivationBusy];
224
+ }
225
+ };
226
+ if ([NSProcessInfo.processInfo.arguments containsObject:@"--agent-device-alert-banner"]) {
227
+ [self startAlertBannerThen:presentAlert];
228
+ } else {
229
+ presentAlert();
151
230
  }
152
231
  }
153
232
  }
@@ -185,6 +264,19 @@ static NSTimeInterval const kAgentDeviceAlertActivationBusyWindow = 20.0;
185
264
  [self updateAlertActionStatus];
186
265
  }
187
266
 
267
+ if ([NSProcessInfo.processInfo.arguments containsObject:@"--agent-device-alert-activation-busy"]) {
268
+ UILabel *busyAnswer = [[UILabel alloc] init];
269
+ busyAnswer.text = @"Unanswered";
270
+ busyAnswer.accessibilityIdentifier = @"agent-device-alert-busy-answer";
271
+ busyAnswer.translatesAutoresizingMaskIntoConstraints = NO;
272
+ [self.view addSubview:busyAnswer];
273
+ [NSLayoutConstraint activateConstraints:@[
274
+ [busyAnswer.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor],
275
+ [busyAnswer.topAnchor constraintEqualToAnchor:label.bottomAnchor constant:24],
276
+ ]];
277
+ self.alertActivationBusyAnswer = busyAnswer;
278
+ }
279
+
188
280
  if ([NSProcessInfo.processInfo.arguments containsObject:@"--agent-device-text-entry-regression"]) {
189
281
  UITextField *textField = [[UITextField alloc] init];
190
282
  textField.accessibilityIdentifier = @"agent-device-hardware-keyboard-input";
@@ -227,7 +319,7 @@ static NSTimeInterval const kAgentDeviceAlertActivationBusyWindow = 20.0;
227
319
 
228
320
  @end
229
321
 
230
- #if defined(TARGET_OS_VISION) && TARGET_OS_VISION
322
+ // UIApplicationSceneManifest in Info.plist names this class; a rename must update the manifest.
231
323
  @interface AgentDeviceRunnerSceneDelegate : UIResponder <UIWindowSceneDelegate>
232
324
  @property(nonatomic, strong) UIWindow *window;
233
325
  @end
@@ -250,46 +342,11 @@ static NSTimeInterval const kAgentDeviceAlertActivationBusyWindow = 20.0;
250
342
  }
251
343
 
252
344
  @end
253
- #endif
254
345
 
255
346
  @interface AgentDeviceRunnerAppDelegate : UIResponder <UIApplicationDelegate>
256
- @property(nonatomic, strong) UIWindow *window;
257
347
  @end
258
348
 
259
349
  @implementation AgentDeviceRunnerAppDelegate
260
-
261
- - (BOOL)application:(UIApplication *)application
262
- didFinishLaunchingWithOptions:(NSDictionary<UIApplicationLaunchOptionsKey, id> *)launchOptions {
263
- (void)application;
264
- (void)launchOptions;
265
-
266
- #if defined(TARGET_OS_VISION) && TARGET_OS_VISION
267
- return YES;
268
- #else
269
- self.window = [[UIWindow alloc] initWithFrame:UIScreen.mainScreen.bounds];
270
- self.window.rootViewController = [[AgentDeviceRunnerViewController alloc] init];
271
- [self.window makeKeyAndVisible];
272
-
273
- return YES;
274
- #endif
275
- }
276
-
277
- #if defined(TARGET_OS_VISION) && TARGET_OS_VISION
278
- - (UISceneConfiguration *)application:(UIApplication *)application
279
- configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession
280
- options:(UISceneConnectionOptions *)options {
281
- (void)application;
282
- (void)connectingSceneSession;
283
- (void)options;
284
-
285
- UISceneConfiguration *configuration =
286
- [[UISceneConfiguration alloc] initWithName:@"Default Configuration"
287
- sessionRole:UIWindowSceneSessionRoleApplication];
288
- configuration.delegateClass = AgentDeviceRunnerSceneDelegate.class;
289
- return configuration;
290
- }
291
- #endif
292
-
293
350
  @end
294
351
 
295
352
  int main(int argc, char *argv[]) {
@@ -0,0 +1,23 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>UIApplicationSceneManifest</key>
6
+ <dict>
7
+ <key>UIApplicationSupportsMultipleScenes</key>
8
+ <false/>
9
+ <key>UISceneConfigurations</key>
10
+ <dict>
11
+ <key>UIWindowSceneSessionRoleApplication</key>
12
+ <array>
13
+ <dict>
14
+ <key>UISceneConfigurationName</key>
15
+ <string>Default Configuration</string>
16
+ <key>UISceneDelegateClassName</key>
17
+ <string>AgentDeviceRunnerSceneDelegate</string>
18
+ </dict>
19
+ </array>
20
+ </dict>
21
+ </dict>
22
+ </dict>
23
+ </plist>
@@ -21,9 +21,22 @@
21
21
  20EA2EDD2F2CFC7C001CF0EF /* AgentDeviceRunnerUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AgentDeviceRunnerUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
22
22
  /* End PBXFileReference section */
23
23
 
24
+ /* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
25
+ 20EA2EF02F2CFC7C001CF0EF /* Exceptions for "AgentDeviceRunner" folder in "AgentDeviceRunner" target */ = {
26
+ isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
27
+ membershipExceptions = (
28
+ Info.plist,
29
+ );
30
+ target = 20EA2EC52F2CFC7B001CF0EF /* AgentDeviceRunner */;
31
+ };
32
+ /* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
33
+
24
34
  /* Begin PBXFileSystemSynchronizedRootGroup section */
25
35
  20EA2EC82F2CFC7B001CF0EF /* AgentDeviceRunner */ = {
26
36
  isa = PBXFileSystemSynchronizedRootGroup;
37
+ exceptions = (
38
+ 20EA2EF02F2CFC7C001CF0EF /* Exceptions for "AgentDeviceRunner" folder in "AgentDeviceRunner" target */,
39
+ );
27
40
  path = AgentDeviceRunner;
28
41
  sourceTree = "<group>";
29
42
  };
@@ -352,6 +365,8 @@
352
365
  DEVELOPMENT_TEAM = 2S799L9W4M;
353
366
  ENABLE_PREVIEWS = YES;
354
367
  GENERATE_INFOPLIST_FILE = YES;
368
+ INFOPLIST_FILE = AgentDeviceRunner/Info.plist;
369
+ "INFOPLIST_FILE[sdk=macosx*]" = "";
355
370
  INFOPLIST_KEY_UIApplicationSceneManifest_Generation = NO;
356
371
  INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
357
372
  INFOPLIST_KEY_UILaunchScreen_Generation = YES;
@@ -386,6 +401,8 @@
386
401
  DEVELOPMENT_TEAM = 2S799L9W4M;
387
402
  ENABLE_PREVIEWS = YES;
388
403
  GENERATE_INFOPLIST_FILE = YES;
404
+ INFOPLIST_FILE = AgentDeviceRunner/Info.plist;
405
+ "INFOPLIST_FILE[sdk=macosx*]" = "";
389
406
  INFOPLIST_KEY_UIApplicationSceneManifest_Generation = NO;
390
407
  INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
391
408
  INFOPLIST_KEY_UILaunchScreen_Generation = YES;
@@ -54,6 +54,21 @@ enum RunnerAppScreenCaptureFailure: String, Error {
54
54
  }
55
55
  }
56
56
 
57
+ extension RunnerTests {
58
+
59
+
60
+ func selectObservedScreenCapture(
61
+ resolving: () -> Result<CapturedAppScreen, RunnerAppScreenCaptureFailure>,
62
+ fallingBack: () -> Result<CapturedAppScreen, RunnerAppScreenCaptureFailure>
63
+ ) -> Result<CapturedAppScreen, RunnerAppScreenCaptureFailure> {
64
+ let outcome = resolving()
65
+ if case .failure(.unresolvedWindow) = outcome {
66
+ return fallingBack()
67
+ }
68
+ return outcome
69
+ }
70
+ }
71
+
57
72
  #if canImport(UIKit) && os(iOS)
58
73
  extension RunnerTests {
59
74
 
@@ -103,19 +118,6 @@ extension RunnerTests {
103
118
  )
104
119
  }
105
120
 
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
121
  private static func resolveCapturedAppScreen(
120
122
  app: XCUIApplication
121
123
  ) -> Result<CapturedAppScreen, RunnerAppScreenCaptureFailure> {
@@ -140,6 +142,12 @@ extension RunnerTests {
140
142
  guard let cgImage = runnerCGImage(from: upright) else {
141
143
  return .failure(.unrenderableImage)
142
144
  }
145
+
146
+
147
+
148
+ guard cgImage.width > 0, cgImage.height > 0 else {
149
+ return .failure(.unrenderableImage)
150
+ }
143
151
  return .success(
144
152
  CapturedAppScreen(
145
153
  image: upright,