agent-device 0.19.2 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. package/README.md +61 -70
  2. package/android/ime-helper/dist/agent-device-android-ime-helper-0.20.0.apk +0 -0
  3. package/android/ime-helper/dist/agent-device-android-ime-helper-0.20.0.apk.sha256 +1 -0
  4. package/android/ime-helper/dist/agent-device-android-ime-helper-0.20.0.manifest.json +10 -0
  5. package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.20.0.apk +0 -0
  6. package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.20.0.apk.sha256 +1 -0
  7. package/{android-snapshot-helper/dist/agent-device-android-snapshot-helper-0.19.2.manifest.json → android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.20.0.manifest.json} +6 -6
  8. package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerAXSnapshotBridge.h +2 -0
  9. package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerAXSnapshotBridge.m +5 -0
  10. package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerSynthesizedGesture.h +12 -10
  11. package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerSynthesizedGesture.m +154 -185
  12. package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Alert.swift +113 -19
  13. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+BlockingSystemModalResolution.swift +82 -0
  14. package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandExecution.swift +275 -113
  15. package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandJournal.swift +2 -2
  16. package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Interaction.swift +150 -104
  17. package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Lifecycle.swift +56 -2
  18. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+LifecycleCacheTests.swift +73 -0
  19. package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Models.swift +44 -14
  20. package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+ScrollGesture.swift +2 -2
  21. package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SequenceExecution.swift +2 -106
  22. package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Snapshot.swift +126 -11
  23. package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SnapshotCapturePlan.swift +51 -8
  24. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SnapshotTraversalIdentityTests.swift +24 -0
  25. package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SynthesizedGesturePolicy.swift +1 -1
  26. package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SystemModal.swift +43 -35
  27. package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests.swift +14 -0
  28. package/dist/src/agent-browser-tool.js +1 -1
  29. package/dist/src/agent-device-client.js +3 -0
  30. package/dist/src/android-adb.d.ts +1 -1
  31. package/dist/src/android-adb.js +1 -1
  32. package/dist/src/android-input-method-overlays.js +1 -0
  33. package/dist/src/android-input-ownership.js +1 -6
  34. package/dist/src/android-system-chrome.js +1 -0
  35. package/dist/src/android.js +1 -1
  36. package/dist/src/app-lifecycle.js +5 -5
  37. package/dist/src/app-log-request-scope.js +1 -0
  38. package/dist/src/app-resolution.js +1 -0
  39. package/dist/src/apple-multitouch-support.js +1 -0
  40. package/dist/src/apps.js +5 -5
  41. package/dist/src/audio-probe.js +1 -1
  42. package/dist/src/boot-diagnostics.js +2 -0
  43. package/dist/src/cli-config.js +1 -0
  44. package/dist/src/cli-help.js +64 -30
  45. package/dist/src/cli-runner.js +1 -1
  46. package/dist/src/cli.js +15 -11
  47. package/dist/src/command-schema.js +3 -3
  48. package/dist/src/config.js +1 -0
  49. package/dist/src/context.js +1 -0
  50. package/dist/src/contracts.d.ts +1 -1
  51. package/dist/src/contracts.js +1 -1
  52. package/dist/src/daemon-shutdown-report.js +1 -0
  53. package/dist/src/debug-symbols.js +1 -1
  54. package/dist/src/device-input-state.js +1 -1
  55. package/dist/src/devices2.js +1 -1
  56. package/dist/src/find.js +1 -1
  57. package/dist/src/finders.js +1 -1
  58. package/dist/src/generic.js +1 -1
  59. package/dist/src/helper.js +1 -1
  60. package/dist/src/index.d.ts +412 -289
  61. package/dist/src/index.js +1 -1
  62. package/dist/src/input-actions.js +4 -3
  63. package/dist/src/input-actions2.js +1 -1
  64. package/dist/src/install-artifact.js +1 -0
  65. package/dist/src/install-source.d.ts +5 -1
  66. package/dist/src/install-source.js +1 -1
  67. package/dist/src/install-source2.js +1 -1
  68. package/dist/src/interaction-snapshot.js +1 -1
  69. package/dist/src/interaction.d.ts +230 -0
  70. package/dist/src/interaction.js +1 -1
  71. package/dist/src/interactor.js +2 -2
  72. package/dist/src/internal/bin.js +1 -1
  73. package/dist/src/internal/daemon.js +43 -17
  74. package/dist/src/internal/png-worker.d.ts +22 -0
  75. package/dist/src/internal/png-worker.js +1 -1
  76. package/dist/src/launch-console.js +1 -0
  77. package/dist/src/lease-context.js +1 -0
  78. package/dist/src/lease.js +1 -1
  79. package/dist/src/linux.js +1 -1
  80. package/dist/src/logcat.js +5 -0
  81. package/dist/src/metro.d.ts +2 -2
  82. package/dist/src/notifications.js +1 -1
  83. package/dist/src/png-worker-contract.js +1 -1
  84. package/dist/src/process-lock.js +1 -0
  85. package/dist/src/react-native.js +1 -1
  86. package/dist/src/record-trace.js +1 -1
  87. package/dist/src/register-builtins.js +1 -0
  88. package/dist/src/registry.js +24 -24
  89. package/dist/src/reporting.js +8 -7
  90. package/dist/src/runner-client.js +1 -1
  91. package/dist/src/runner-disposal.js +2 -0
  92. package/dist/src/runner-lease.js +1 -0
  93. package/dist/src/runner-sequence.js +1 -1
  94. package/dist/src/runtime-transport.js +1 -1
  95. package/dist/src/runtime.js +3 -3
  96. package/dist/src/runtime2.js +1 -0
  97. package/dist/src/sdk-android-adb.d.ts +119 -33
  98. package/dist/src/sdk-android-adb.js +4 -4
  99. package/dist/src/sdk-app-inventory.js +1 -1
  100. package/dist/src/sdk-batch-runner.d.ts +2 -1
  101. package/dist/src/sdk-batch-runner.js +2 -2
  102. package/dist/src/sdk-batch.js +2 -2
  103. package/dist/src/sdk-contracts.d.ts +105 -98
  104. package/dist/src/sdk-contracts.js +2 -2
  105. package/dist/src/sdk-device.js +1 -0
  106. package/dist/src/sdk-finders.d.ts +3 -3
  107. package/dist/src/sdk-finders.js +1 -1
  108. package/dist/src/sdk-io.d.ts +1 -1
  109. package/dist/src/sdk-io.js +1 -1
  110. package/dist/src/sdk-remote-config.d.ts +9 -1
  111. package/dist/src/sdk-remote-config.js +1 -1
  112. package/dist/src/sdk-selectors.js +1 -1
  113. package/dist/src/selector-runtime.js +1 -1
  114. package/dist/src/selector-vocabulary.js +1 -0
  115. package/dist/src/selectors.d.ts +12 -3
  116. package/dist/src/selectors.js +1 -1
  117. package/dist/src/server.js +3 -3
  118. package/dist/src/session-target-evidence.js +1 -0
  119. package/dist/src/session.js +8 -10
  120. package/dist/src/setting-state.js +1 -1
  121. package/dist/src/simulator.js +1 -1
  122. package/dist/src/snapshot-diagnostics.js +5 -2
  123. package/dist/src/snapshot.js +1 -1
  124. package/dist/src/snapshot2.js +1 -2
  125. package/dist/src/tool-provider.js +2 -2
  126. package/dist/src/tool-provider2.js +1 -1
  127. package/dist/src/tv-remote.js +13 -12
  128. package/dist/src/url.js +1 -1
  129. package/dist/src/web.js +1 -1
  130. package/package.json +33 -20
  131. package/server.json +2 -2
  132. package/android-multitouch-helper/dist/agent-device-android-multitouch-helper-0.19.2.apk +0 -0
  133. package/android-multitouch-helper/dist/agent-device-android-multitouch-helper-0.19.2.apk.sha256 +0 -1
  134. package/android-multitouch-helper/dist/agent-device-android-multitouch-helper-0.19.2.manifest.json +0 -10
  135. package/android-snapshot-helper/dist/agent-device-android-snapshot-helper-0.19.2.apk +0 -0
  136. package/android-snapshot-helper/dist/agent-device-android-snapshot-helper-0.19.2.apk.sha256 +0 -1
  137. package/dist/src/apple-runner-options.js +0 -1
  138. package/dist/src/cli-output.js +0 -1
  139. package/dist/src/client.js +0 -3
  140. package/dist/src/handler-utils.js +0 -1
  141. package/dist/src/output-path.js +0 -1
  142. package/dist/src/points.js +0 -4
  143. package/dist/src/provider.js +0 -1
  144. package/dist/src/providers.js +0 -1
  145. package/dist/src/record-trace-recording.js +0 -23
  146. package/dist/src/recording-provider.js +0 -1
  147. package/dist/src/selectors2.js +0 -1
  148. package/dist/src/simctl.js +0 -1
  149. package/dist/src/snapshot-helper.js +0 -5
  150. package/dist/src/ttl-memo.js +0 -1
  151. package/dist/src/xml.js +0 -2
  152. /package/{macos-helper → apple/macos-helper}/Package.swift +0 -0
  153. /package/{macos-helper → apple/macos-helper}/Sources/AgentDeviceMacOSHelper/AudioProbe.swift +0 -0
  154. /package/{macos-helper → apple/macos-helper}/Sources/AgentDeviceMacOSHelper/SnapshotTraversal.swift +0 -0
  155. /package/{macos-helper → apple/macos-helper}/Sources/AgentDeviceMacOSHelper/main.swift +0 -0
  156. /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunner/AgentDeviceRunnerApp.m +0 -0
  157. /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunner.xcodeproj/project.pbxproj +0 -0
  158. /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -0
  159. /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunner.xcodeproj/xcshareddata/xcschemes/AgentDeviceRunner.xcscheme +0 -0
  160. /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/AgentDeviceRunnerUITests-Bridging-Header.h +0 -0
  161. /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/AgentDeviceRunnerUITests.entitlements +0 -0
  162. /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerObjCExceptionCatcher.h +0 -0
  163. /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerObjCExceptionCatcher.m +0 -0
  164. /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTapPointPolicy.swift +0 -0
  165. /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+AXSnapshotFallback.swift +0 -0
  166. /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Environment.swift +0 -0
  167. /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Exceptions.swift +0 -0
  168. /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+FlatSnapshotFiltering.swift +0 -0
  169. /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Navigation.swift +0 -0
  170. /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+ScreenRecorder.swift +0 -0
  171. /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextEntry.swift +0 -0
  172. /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Transport.swift +0 -0
  173. /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TvRemote.swift +0 -0
  174. /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests.xctestplan +0 -0
  175. /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/RecordingScripts/recording-overlay.swift +0 -0
  176. /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/RecordingScripts/recording-resize.swift +0 -0
  177. /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/RecordingScripts/recording-trim.swift +0 -0
  178. /package/{src/platforms/linux → linux}/atspi-dump.py +0 -0
@@ -1,7 +1,6 @@
1
1
  #import "RunnerSynthesizedGesture.h"
2
2
 
3
3
  #import <CoreGraphics/CoreGraphics.h>
4
- #import <math.h>
5
4
  #import <objc/message.h>
6
5
 
7
6
  typedef NSInteger (*RunnerMsgSendInteger)(id, SEL);
@@ -27,6 +26,13 @@ typedef struct {
27
26
  SEL liftSelector;
28
27
  } RunnerXCTestEventBridge;
29
28
 
29
+ typedef id (*RunnerDragPointerPathFactory)(
30
+ const RunnerXCTestEventBridge *,
31
+ CGPoint,
32
+ CGPoint,
33
+ double
34
+ );
35
+
30
36
  static NSString * _Nullable RunnerResolveXCTestEventBridge(
31
37
  id application,
32
38
  RunnerXCTestEventBridge *bridge
@@ -34,64 +40,51 @@ static NSString * _Nullable RunnerResolveXCTestEventBridge(
34
40
  static NSString * _Nullable RunnerRequireClass(Class cls, NSString *className);
35
41
  static NSString * _Nullable RunnerRequireSelector(Class cls, SEL selector, NSString *selectorName);
36
42
  static NSString * _Nullable RunnerRequireApplicationSelector(id application, SEL selector, NSString *selectorName);
37
- static id RunnerPointerPath(
43
+ static id RunnerSwipePointerPath(
38
44
  const RunnerXCTestEventBridge *bridge,
39
45
  CGPoint start,
40
- double x,
41
- double y,
42
- double dx,
43
- double dy,
44
- double scale,
45
- double degrees,
46
- double radius,
47
- double durationMs,
48
- double side
46
+ CGPoint end,
47
+ double durationMs
49
48
  );
50
- static id RunnerSwipePointerPath(
49
+ static id RunnerContinuousDragPointerPath(
51
50
  const RunnerXCTestEventBridge *bridge,
52
51
  CGPoint start,
53
52
  CGPoint end,
54
53
  double durationMs
55
54
  );
55
+ static NSString * _Nullable RunnerTrySynthesizeDrag(
56
+ id application,
57
+ CGPoint start,
58
+ CGPoint end,
59
+ double durationMs,
60
+ NSString *recordName,
61
+ RunnerDragPointerPathFactory pathFactory
62
+ );
63
+ // XCTest's proven swipe profile reaches the endpoint in 100 ms, then holds for the planned
64
+ // fling duration. Fast movement is what lets UIKit distinguish a fling from a timed pan.
65
+ static const NSTimeInterval RunnerSwipeMovementDurationSeconds = 0.1;
56
66
  static id RunnerTapPointerPath(
57
67
  const RunnerXCTestEventBridge *bridge,
58
68
  CGPoint point
59
69
  );
60
- static CGPoint RunnerPointerPointAt(
61
- double x,
62
- double y,
63
- double dx,
64
- double dy,
65
- double scale,
66
- double degrees,
67
- double baseRadius,
68
- double t,
69
- double side
70
- );
71
- static CGPoint RunnerInterpolatedPoint(CGPoint start, CGPoint end, double t);
72
- static double RunnerSmoothStep(double t);
73
70
 
74
71
  @implementation RunnerSynthesizedGesture
75
72
 
76
- + (NSString * _Nullable)synthesizeTransformWithApplication:(id)application
77
- x:(double)x
78
- y:(double)y
79
- dx:(double)dx
80
- dy:(double)dy
81
- scale:(double)scale
82
- degrees:(double)degrees
83
- radius:(double)radius
84
- durationMs:(double)durationMs {
73
+ + (NSString * _Nullable)synthesizeSwipeWithApplication:(id)application
74
+ x:(double)x
75
+ y:(double)y
76
+ x2:(double)x2
77
+ y2:(double)y2
78
+ durationMs:(double)durationMs {
85
79
  @try {
86
- return [self trySynthesizeTransformWithApplication:application
87
- x:x
88
- y:y
89
- dx:dx
90
- dy:dy
91
- scale:scale
92
- degrees:degrees
93
- radius:radius
94
- durationMs:durationMs];
80
+ return RunnerTrySynthesizeDrag(
81
+ application,
82
+ CGPointMake(x, y),
83
+ CGPointMake(x2, y2),
84
+ durationMs,
85
+ @"agent-device-swipe",
86
+ RunnerSwipePointerPath
87
+ );
95
88
  } @catch (NSException *exception) {
96
89
  NSString *name = exception.name ?: @"NSException";
97
90
  NSString *reason = exception.reason ?: @"private XCTest event synthesis failed";
@@ -99,19 +92,21 @@ static double RunnerSmoothStep(double t);
99
92
  }
100
93
  }
101
94
 
102
- + (NSString * _Nullable)synthesizeSwipeWithApplication:(id)application
103
- x:(double)x
104
- y:(double)y
105
- x2:(double)x2
106
- y2:(double)y2
107
- durationMs:(double)durationMs {
95
+ + (NSString * _Nullable)synthesizeContinuousDragWithApplication:(id)application
96
+ x:(double)x
97
+ y:(double)y
98
+ x2:(double)x2
99
+ y2:(double)y2
100
+ durationMs:(double)durationMs {
108
101
  @try {
109
- return [self trySynthesizeSwipeWithApplication:application
110
- x:x
111
- y:y
112
- x2:x2
113
- y2:y2
114
- durationMs:durationMs];
102
+ return RunnerTrySynthesizeDrag(
103
+ application,
104
+ CGPointMake(x, y),
105
+ CGPointMake(x2, y2),
106
+ durationMs,
107
+ @"agent-device-continuous-drag",
108
+ RunnerContinuousDragPointerPath
109
+ );
115
110
  } @catch (NSException *exception) {
116
111
  NSString *name = exception.name ?: @"NSException";
117
112
  NSString *reason = exception.reason ?: @"private XCTest event synthesis failed";
@@ -131,84 +126,94 @@ static double RunnerSmoothStep(double t);
131
126
  }
132
127
  }
133
128
 
134
- + (NSInteger)interfaceOrientationForApplication:(id)application {
135
- SEL selector = NSSelectorFromString(@"interfaceOrientation");
136
- if (![application respondsToSelector:selector]) {
137
- return 0; // UIInterfaceOrientationUnknown
138
- }
139
- return ((RunnerMsgSendInteger)objc_msgSend)(application, selector);
140
- }
141
-
142
- + (NSString * _Nullable)trySynthesizeTransformWithApplication:(id)application
143
- x:(double)x
144
- y:(double)y
145
- dx:(double)dx
146
- dy:(double)dy
147
- scale:(double)scale
148
- degrees:(double)degrees
149
- radius:(double)radius
150
- durationMs:(double)durationMs {
151
- RunnerXCTestEventBridge bridge;
152
- NSString *missing = RunnerResolveXCTestEventBridge(application, &bridge);
153
- if (missing != nil) {
154
- return missing;
155
- }
156
-
157
- NSInteger interfaceOrientation =
158
- ((RunnerMsgSendInteger)objc_msgSend)(application, bridge.interfaceOrientationSelector);
159
- NSInteger targetProcessID = ((RunnerMsgSendInteger)objc_msgSend)(application, bridge.processIDSelector);
160
- if (targetProcessID <= 0) {
161
- return @"private XCTest event synthesis unavailable: could not resolve target process ID";
162
- }
163
-
164
- id record = ((RunnerMsgSendInitRecord)objc_msgSend)(
165
- [bridge.recordClass alloc],
166
- bridge.initRecordSelector,
167
- @"agent-device-transform",
168
- interfaceOrientation
169
- );
170
- if (record == nil) {
171
- return @"private XCTest event synthesis failed: could not create event record";
172
- }
173
- ((RunnerMsgSendSetInteger)objc_msgSend)(record, bridge.setTargetProcessIDSelector, targetProcessID);
129
+ + (NSString * _Nullable)synthesizeGestureWithApplication:(id)application
130
+ pointerSamples:(NSArray<NSArray<NSDictionary<NSString *, NSNumber *> *> *> *)pointerSamples {
131
+ @try {
132
+ RunnerXCTestEventBridge bridge;
133
+ NSString *missing = RunnerResolveXCTestEventBridge(application, &bridge);
134
+ if (missing != nil) return missing;
135
+
136
+ NSInteger interfaceOrientation =
137
+ ((RunnerMsgSendInteger)objc_msgSend)(application, bridge.interfaceOrientationSelector);
138
+ NSInteger targetProcessID =
139
+ ((RunnerMsgSendInteger)objc_msgSend)(application, bridge.processIDSelector);
140
+ if (targetProcessID <= 0) {
141
+ return @"private XCTest event synthesis unavailable: could not resolve target process ID";
142
+ }
174
143
 
175
- double sides[] = {1.0, -1.0};
176
- for (int index = 0; index < 2; index += 1) {
177
- double side = sides[index];
178
- id path = RunnerPointerPath(
179
- &bridge,
180
- RunnerPointerPointAt(x, y, dx, dy, scale, degrees, radius, 0.0, side),
181
- x,
182
- y,
183
- dx,
184
- dy,
185
- scale,
186
- degrees,
187
- radius,
188
- durationMs,
189
- side
144
+ id record = ((RunnerMsgSendInitRecord)objc_msgSend)(
145
+ [bridge.recordClass alloc],
146
+ bridge.initRecordSelector,
147
+ @"agent-device-gesture-plan",
148
+ interfaceOrientation
190
149
  );
191
- if (path == nil) {
192
- return @"private XCTest event synthesis failed: could not create pointer path";
150
+ if (record == nil) {
151
+ return @"private XCTest event synthesis failed: could not create event record";
152
+ }
153
+ ((RunnerMsgSendSetInteger)objc_msgSend)(record, bridge.setTargetProcessIDSelector, targetProcessID);
154
+
155
+ for (NSArray<NSDictionary<NSString *, NSNumber *> *> *samples in pointerSamples) {
156
+ NSDictionary<NSString *, NSNumber *> *first = samples.firstObject;
157
+ if (first == nil) return @"private XCTest event synthesis failed: empty pointer path";
158
+ CGPoint start = CGPointMake(first[@"x"].doubleValue, first[@"y"].doubleValue);
159
+ id path = ((RunnerMsgSendInitPath)objc_msgSend)(
160
+ [bridge.pathClass alloc],
161
+ bridge.initPathSelector,
162
+ start,
163
+ first[@"offsetMs"].doubleValue / 1000.0
164
+ );
165
+ if (path == nil) {
166
+ return @"private XCTest event synthesis failed: could not create pointer path";
167
+ }
168
+ for (NSUInteger index = 1; index < samples.count; index += 1) {
169
+ NSDictionary<NSString *, NSNumber *> *sample = samples[index];
170
+ CGPoint point = CGPointMake(sample[@"x"].doubleValue, sample[@"y"].doubleValue);
171
+ ((RunnerMsgSendPathMove)objc_msgSend)(
172
+ path,
173
+ bridge.moveSelector,
174
+ point,
175
+ sample[@"offsetMs"].doubleValue / 1000.0
176
+ );
177
+ }
178
+ NSDictionary<NSString *, NSNumber *> *last = samples.lastObject;
179
+ ((RunnerMsgSendPathOffset)objc_msgSend)(
180
+ path,
181
+ bridge.liftSelector,
182
+ last[@"offsetMs"].doubleValue / 1000.0
183
+ );
184
+ ((RunnerMsgSendAddPath)objc_msgSend)(record, bridge.addPathSelector, path);
193
185
  }
194
- ((RunnerMsgSendAddPath)objc_msgSend)(record, bridge.addPathSelector, path);
186
+
187
+ NSError *error = nil;
188
+ BOOL ok = ((RunnerMsgSendSynthesize)objc_msgSend)(record, bridge.synthesizeSelector, &error);
189
+ if (!ok) {
190
+ NSString *detail = error.localizedDescription ?: @"synthesizeWithError returned false";
191
+ return [NSString stringWithFormat:@"private XCTest event synthesis failed: %@", detail];
192
+ }
193
+ return nil;
194
+ } @catch (NSException *exception) {
195
+ NSString *name = exception.name ?: @"NSException";
196
+ NSString *reason = exception.reason ?: @"private XCTest event synthesis failed";
197
+ return [NSString stringWithFormat:@"%@: %@", name, reason];
195
198
  }
199
+ }
196
200
 
197
- NSError *error = nil;
198
- BOOL ok = ((RunnerMsgSendSynthesize)objc_msgSend)(record, bridge.synthesizeSelector, &error);
199
- if (!ok) {
200
- NSString *detail = error.localizedDescription ?: @"synthesizeWithError returned false";
201
- return [NSString stringWithFormat:@"private XCTest event synthesis failed: %@", detail];
201
+ + (NSInteger)interfaceOrientationForApplication:(id)application {
202
+ SEL selector = NSSelectorFromString(@"interfaceOrientation");
203
+ if (![application respondsToSelector:selector]) {
204
+ return 0; // UIInterfaceOrientationUnknown
202
205
  }
203
- return nil;
206
+ return ((RunnerMsgSendInteger)objc_msgSend)(application, selector);
204
207
  }
205
208
 
206
- + (NSString * _Nullable)trySynthesizeSwipeWithApplication:(id)application
207
- x:(double)x
208
- y:(double)y
209
- x2:(double)x2
210
- y2:(double)y2
211
- durationMs:(double)durationMs {
209
+ static NSString * _Nullable RunnerTrySynthesizeDrag(
210
+ id application,
211
+ CGPoint start,
212
+ CGPoint end,
213
+ double durationMs,
214
+ NSString *recordName,
215
+ RunnerDragPointerPathFactory pathFactory
216
+ ) {
212
217
  RunnerXCTestEventBridge bridge;
213
218
  NSString *missing = RunnerResolveXCTestEventBridge(application, &bridge);
214
219
  if (missing != nil) {
@@ -225,7 +230,7 @@ static double RunnerSmoothStep(double t);
225
230
  id record = ((RunnerMsgSendInitRecord)objc_msgSend)(
226
231
  [bridge.recordClass alloc],
227
232
  bridge.initRecordSelector,
228
- @"agent-device-swipe",
233
+ recordName,
229
234
  interfaceOrientation
230
235
  );
231
236
  if (record == nil) {
@@ -233,7 +238,7 @@ static double RunnerSmoothStep(double t);
233
238
  }
234
239
  ((RunnerMsgSendSetInteger)objc_msgSend)(record, bridge.setTargetProcessIDSelector, targetProcessID);
235
240
 
236
- id path = RunnerSwipePointerPath(&bridge, CGPointMake(x, y), CGPointMake(x2, y2), durationMs);
241
+ id path = pathFactory(&bridge, start, end, durationMs);
237
242
  if (path == nil) {
238
243
  return @"private XCTest event synthesis failed: could not create pointer path";
239
244
  }
@@ -377,18 +382,12 @@ static NSString * _Nullable RunnerRequireApplicationSelector(
377
382
  return nil;
378
383
  }
379
384
 
380
- static id RunnerPointerPath(
385
+
386
+ static id RunnerSwipePointerPath(
381
387
  const RunnerXCTestEventBridge *bridge,
382
388
  CGPoint start,
383
- double x,
384
- double y,
385
- double dx,
386
- double dy,
387
- double scale,
388
- double degrees,
389
- double radius,
390
- double durationMs,
391
- double side
389
+ CGPoint end,
390
+ double durationMs
392
391
  ) {
393
392
  id path =
394
393
  ((RunnerMsgSendInitPath)objc_msgSend)([bridge->pathClass alloc], bridge->initPathSelector, start, 0.0);
@@ -396,20 +395,22 @@ static id RunnerPointerPath(
396
395
  return nil;
397
396
  }
398
397
 
399
- int frameCount = MAX(3, (int)(durationMs / 16.0));
400
398
  NSTimeInterval durationSeconds = durationMs / 1000.0;
401
- for (int index = 1; index <= frameCount; index += 1) {
402
- double t = (double)index / (double)frameCount;
403
- CGPoint point = RunnerPointerPointAt(x, y, dx, dy, scale, degrees, radius, t, side);
404
- NSTimeInterval offset = durationSeconds * t;
405
- ((RunnerMsgSendPathMove)objc_msgSend)(path, bridge->moveSelector, point, offset);
406
- }
407
-
408
- ((RunnerMsgSendPathOffset)objc_msgSend)(path, bridge->liftSelector, durationSeconds);
399
+ ((RunnerMsgSendPathMove)objc_msgSend)(
400
+ path,
401
+ bridge->moveSelector,
402
+ end,
403
+ RunnerSwipeMovementDurationSeconds
404
+ );
405
+ ((RunnerMsgSendPathOffset)objc_msgSend)(
406
+ path,
407
+ bridge->liftSelector,
408
+ RunnerSwipeMovementDurationSeconds + durationSeconds
409
+ );
409
410
  return path;
410
411
  }
411
412
 
412
- static id RunnerSwipePointerPath(
413
+ static id RunnerContinuousDragPointerPath(
413
414
  const RunnerXCTestEventBridge *bridge,
414
415
  CGPoint start,
415
416
  CGPoint end,
@@ -425,9 +426,12 @@ static id RunnerSwipePointerPath(
425
426
  NSTimeInterval durationSeconds = durationMs / 1000.0;
426
427
  for (int index = 1; index <= frameCount; index += 1) {
427
428
  double t = (double)index / (double)frameCount;
428
- CGPoint point = RunnerInterpolatedPoint(start, end, RunnerSmoothStep(t));
429
- NSTimeInterval offset = durationSeconds * t;
430
- ((RunnerMsgSendPathMove)objc_msgSend)(path, bridge->moveSelector, point, offset);
429
+ double easedT = t * t * (3.0 - 2.0 * t);
430
+ CGPoint point = CGPointMake(
431
+ start.x + (end.x - start.x) * easedT,
432
+ start.y + (end.y - start.y) * easedT
433
+ );
434
+ ((RunnerMsgSendPathMove)objc_msgSend)(path, bridge->moveSelector, point, durationSeconds * t);
431
435
  }
432
436
 
433
437
  ((RunnerMsgSendPathOffset)objc_msgSend)(path, bridge->liftSelector, durationSeconds);
@@ -447,39 +451,4 @@ static id RunnerTapPointerPath(
447
451
  return path;
448
452
  }
449
453
 
450
- static CGPoint RunnerPointerPointAt(
451
- double x,
452
- double y,
453
- double dx,
454
- double dy,
455
- double scale,
456
- double degrees,
457
- double baseRadius,
458
- double t,
459
- double side
460
- ) {
461
- double centerX = x + dx * t;
462
- double centerY = y + dy * t;
463
- double startRadius = baseRadius / MAX(scale, 1.0);
464
- double endRadius = baseRadius;
465
- if (scale < 1.0) {
466
- startRadius = baseRadius;
467
- endRadius = baseRadius * scale;
468
- }
469
- double radius = startRadius + (endRadius - startRadius) * t;
470
- double angle = (-M_PI_2) + (degrees * M_PI / 180.0) * t;
471
- return CGPointMake(centerX + cos(angle) * radius * side, centerY + sin(angle) * radius * side);
472
- }
473
-
474
- static CGPoint RunnerInterpolatedPoint(CGPoint start, CGPoint end, double t) {
475
- return CGPointMake(
476
- start.x + (end.x - start.x) * t,
477
- start.y + (end.y - start.y) * t
478
- );
479
- }
480
-
481
- static double RunnerSmoothStep(double t) {
482
- return t * t * (3.0 - 2.0 * t);
483
- }
484
-
485
454
  @end
@@ -1,48 +1,89 @@
1
1
  import XCTest
2
2
 
3
3
  extension RunnerTests {
4
+ enum RunnerAlertSource {
5
+ case blockingSystemModal
6
+ case appAlert
7
+ case dismissPopup
8
+ }
9
+
4
10
  struct RunnerAlert {
5
11
  let root: XCUIElement
6
12
  let ownerApp: XCUIApplication
7
13
  let buttons: [XCUIElement]
14
+ let source: RunnerAlertSource
15
+ }
16
+
17
+ static let defaultAlertCommandTimeout: TimeInterval = 10
18
+
19
+ static func alertCommandTimeout(timeoutMs: Double?) -> TimeInterval {
20
+ guard let timeoutMs, timeoutMs.isFinite else { return defaultAlertCommandTimeout }
21
+ return max(0.001, timeoutMs / 1000)
8
22
  }
9
23
 
10
- func resolveAlert(app activeApp: XCUIApplication) -> RunnerAlert? {
24
+ func resolveAlert(app activeApp: XCUIApplication, deadline: Date) -> RunnerAlert? {
11
25
  #if !os(macOS)
12
- if let systemModal = firstBlockingSystemModal(in: springboard) {
13
- return runnerAlert(root: systemModal, ownerApp: springboard)
26
+ switch resolveBlockingSystemModal(deadline: deadline) {
27
+ case .resolved(let modal):
28
+ return runnerAlert(modal)
29
+ case .unresolved:
30
+ return nil
31
+ case .absent:
32
+ break
14
33
  }
15
34
  #endif
16
- if let alert = firstExistingElement(in: activeApp.alerts.allElementsBoundByIndex) {
17
- return runnerAlert(root: alert, ownerApp: activeApp)
35
+ // Guard the query: when a remote-hosted modal (e.g. the AccessorySetupKit
36
+ // picker) was just dismissed, the dismissal re-check re-enters here with the
37
+ // now-gone host as `activeApp`, and its `alerts` query raises
38
+ // kAXErrorServerNotFound. safeElementsQuery absorbs it and reports no alert.
39
+ if let alert = firstExistingElement(in: safeElementsQuery { activeApp.alerts.allElementsBoundByIndex }) {
40
+ return runnerAlert(root: alert, ownerApp: activeApp, source: .appAlert)
18
41
  }
19
42
  if let popup = firstDismissPopupWindow(in: activeApp) {
20
- return runnerAlert(root: popup, ownerApp: activeApp)
43
+ return runnerAlert(root: popup, ownerApp: activeApp, source: .dismissPopup)
21
44
  }
22
45
  return nil
23
46
  }
24
47
 
25
- func handleAlert(_ alert: RunnerAlert, action: String) -> Response {
48
+ func handleAlert(_ alert: RunnerAlert, action: String, deadline: Date) -> Response {
26
49
  if action == "accept" || action == "dismiss" {
27
50
  guard let button = chooseAlertButton(alert.buttons, action: action) else {
28
51
  return Response(ok: false, error: ErrorPayload(message: "alert \(action) button not found"))
29
52
  }
53
+ let previousTitle = preferredAlertTitle(alert.root, buttons: alert.buttons)
54
+ let actionButtonLabel = button.label.trimmingCharacters(in: .whitespacesAndNewlines)
55
+ let actionButtonFrame = button.frame
30
56
  let outcome = activateElement(app: alert.ownerApp, element: button, action: "alert \(action)")
31
57
  if let response = unsupportedResponse(for: outcome) {
32
58
  return response
33
59
  }
34
60
  sleepFor(0.2)
35
- if alertStillVisible(alert, actionButtonLabel: button.label) {
36
- let frame = button.frame
37
- if !frame.isNull && !frame.isEmpty {
38
- let coordinateOutcome = tapAt(app: alert.ownerApp, x: frame.midX, y: frame.midY)
61
+ if alertStillVisible(
62
+ in: alert.ownerApp,
63
+ source: alert.source,
64
+ previousTitle: previousTitle,
65
+ actionButtonLabel: actionButtonLabel,
66
+ deadline: deadline
67
+ ) {
68
+ if !actionButtonFrame.isNull && !actionButtonFrame.isEmpty {
69
+ let coordinateOutcome = tapAt(
70
+ app: alert.ownerApp,
71
+ x: actionButtonFrame.midX,
72
+ y: actionButtonFrame.midY
73
+ )
39
74
  if let response = unsupportedResponse(for: coordinateOutcome) {
40
75
  return response
41
76
  }
42
77
  sleepFor(0.2)
43
78
  }
44
79
  }
45
- if alertStillVisible(alert, actionButtonLabel: button.label) {
80
+ if alertStillVisible(
81
+ in: alert.ownerApp,
82
+ source: alert.source,
83
+ previousTitle: previousTitle,
84
+ actionButtonLabel: actionButtonLabel,
85
+ deadline: deadline
86
+ ) {
46
87
  return Response(
47
88
  ok: false,
48
89
  error: ErrorPayload(
@@ -64,26 +105,79 @@ extension RunnerTests {
64
105
  )
65
106
  }
66
107
 
67
- private func runnerAlert(root: XCUIElement, ownerApp: XCUIApplication) -> RunnerAlert? {
108
+ private func runnerAlert(_ modal: ResolvedBlockingSystemModal) -> RunnerAlert? {
109
+ let buttons = modal.actions.filter { isEnabledElement($0) }
110
+ guard !buttons.isEmpty else {
111
+ return nil
112
+ }
113
+ return RunnerAlert(
114
+ root: modal.root,
115
+ ownerApp: modal.ownerApp,
116
+ buttons: buttons,
117
+ source: .blockingSystemModal
118
+ )
119
+ }
120
+
121
+ private func runnerAlert(
122
+ root: XCUIElement,
123
+ ownerApp: XCUIApplication,
124
+ source: RunnerAlertSource
125
+ ) -> RunnerAlert? {
68
126
  let buttons = actionableElements(in: root).filter { isEnabledElement($0) }
69
127
  guard !buttons.isEmpty else {
70
128
  return nil
71
129
  }
72
- return RunnerAlert(root: root, ownerApp: ownerApp, buttons: buttons)
130
+ return RunnerAlert(root: root, ownerApp: ownerApp, buttons: buttons, source: source)
73
131
  }
74
132
 
75
- private func alertStillVisible(_ alert: RunnerAlert, actionButtonLabel: String) -> Bool {
76
- guard let current = resolveAlert(app: alert.ownerApp) else {
133
+ private func alertStillVisible(
134
+ in ownerApp: XCUIApplication,
135
+ source: RunnerAlertSource,
136
+ previousTitle: String,
137
+ actionButtonLabel: String,
138
+ deadline: Date
139
+ ) -> Bool {
140
+ guard Date() < deadline,
141
+ let current = resolveAlert(source: source, app: ownerApp, deadline: deadline)
142
+ else {
77
143
  return false
78
144
  }
79
- let previousTitle = preferredAlertTitle(alert.root, buttons: alert.buttons)
80
145
  let currentTitle = preferredAlertTitle(current.root, buttons: current.buttons)
81
146
  if previousTitle == currentTitle {
82
147
  return true
83
148
  }
84
- let normalizedActionLabel = actionButtonLabel.trimmingCharacters(in: .whitespacesAndNewlines)
85
149
  return current.buttons.contains { button in
86
- button.label.trimmingCharacters(in: .whitespacesAndNewlines) == normalizedActionLabel
150
+ button.label.trimmingCharacters(in: .whitespacesAndNewlines) == actionButtonLabel
151
+ }
152
+ }
153
+
154
+ private func resolveAlert(
155
+ source: RunnerAlertSource,
156
+ app: XCUIApplication,
157
+ deadline: Date
158
+ ) -> RunnerAlert? {
159
+ switch source {
160
+ case .blockingSystemModal:
161
+ #if os(macOS)
162
+ return nil
163
+ #else
164
+ guard case .resolved(let modal) = resolveBlockingSystemModal(deadline: deadline) else {
165
+ return nil
166
+ }
167
+ return runnerAlert(modal)
168
+ #endif
169
+ case .appAlert:
170
+ guard let alert = firstExistingElement(
171
+ in: safeElementsQuery { app.alerts.allElementsBoundByIndex }
172
+ ) else {
173
+ return nil
174
+ }
175
+ return runnerAlert(root: alert, ownerApp: app, source: .appAlert)
176
+ case .dismissPopup:
177
+ guard let popup = firstDismissPopupWindow(in: app) else {
178
+ return nil
179
+ }
180
+ return runnerAlert(root: popup, ownerApp: app, source: .dismissPopup)
87
181
  }
88
182
  }
89
183