agent-device 0.20.4 → 0.20.6

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 (100) hide show
  1. package/README.md +2 -0
  2. package/android/ime-helper/dist/{agent-device-android-ime-helper-0.20.4.apk → agent-device-android-ime-helper-0.20.6.apk} +0 -0
  3. package/android/ime-helper/dist/agent-device-android-ime-helper-0.20.6.apk.sha256 +1 -0
  4. package/android/ime-helper/dist/{agent-device-android-ime-helper-0.20.4.manifest.json → agent-device-android-ime-helper-0.20.6.manifest.json} +4 -4
  5. package/android/snapshot-helper/dist/{agent-device-android-snapshot-helper-0.20.4.apk → agent-device-android-snapshot-helper-0.20.6.apk} +0 -0
  6. package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.20.6.apk.sha256 +1 -0
  7. package/android/snapshot-helper/dist/{agent-device-android-snapshot-helper-0.20.4.manifest.json → agent-device-android-snapshot-helper-0.20.6.manifest.json} +6 -6
  8. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunner/AgentDeviceRunnerApp.m +28 -0
  9. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/AgentDeviceRunnerUITests-Bridging-Header.h +1 -0
  10. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerAXSnapshotBridge.h +41 -1
  11. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerAXSnapshotBridge.m +298 -92
  12. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerSynthesizedGesture.m +126 -187
  13. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerSynthesizedTextEntry.h +32 -0
  14. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerSynthesizedTextEntry.m +212 -0
  15. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+AXSnapshotFallback.swift +118 -8
  16. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandExecution.swift +134 -27
  17. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Keyboard.swift +23 -5
  18. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Lifecycle.swift +10 -0
  19. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+LifecycleCacheTests.swift +0 -67
  20. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Models.swift +11 -1
  21. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+ScreenRecorder.swift +2 -22
  22. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SnapshotCapturePlan.swift +48 -11
  23. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SnapshotTraversalIdentityTests.swift +0 -20
  24. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SynthesizedTextEntry.swift +177 -0
  25. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextEntry.swift +105 -448
  26. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextEntryPolicyTests.swift +4 -0
  27. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextTyping.swift +587 -0
  28. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests.swift +18 -0
  29. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerXCTestEventBridge.h +72 -0
  30. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerXCTestEventBridge.m +89 -0
  31. package/dist/src/agent-device-client.js +2 -2
  32. package/dist/src/android.js +1 -1
  33. package/dist/src/app-log-request-scope.js +1 -1
  34. package/dist/src/app-resolution.js +1 -1
  35. package/dist/src/apps.js +5 -5
  36. package/dist/src/cli-config.js +1 -1
  37. package/dist/src/cli-help.js +34 -24
  38. package/dist/src/cli-runner.js +1 -1
  39. package/dist/src/cli.js +15 -15
  40. package/dist/src/command-metadata.js +1 -1
  41. package/dist/src/command-schema.js +3 -3
  42. package/dist/src/context.js +1 -1
  43. package/dist/src/debug-symbols.js +1 -1
  44. package/dist/src/find.js +1 -1
  45. package/dist/src/finders.js +1 -1
  46. package/dist/src/generic-settle.js +1 -0
  47. package/dist/src/generic.js +1 -1
  48. package/dist/src/ime-lifecycle.js +3 -3
  49. package/dist/src/index.d.ts +27 -7
  50. package/dist/src/input-actions.js +4 -4
  51. package/dist/src/interaction-runtime.js +1 -0
  52. package/dist/src/interaction-snapshot.js +1 -1
  53. package/dist/src/interaction-touch-reference-frame.js +1 -0
  54. package/dist/src/interaction.js +1 -1
  55. package/dist/src/interactor.js +2 -2
  56. package/dist/src/interactor2.js +1 -1
  57. package/dist/src/internal/bin.js +2 -2
  58. package/dist/src/internal/daemon.js +15 -15
  59. package/dist/src/lease-context.js +1 -1
  60. package/dist/src/lease.js +1 -1
  61. package/dist/src/limrun.d.ts +23 -2
  62. package/dist/src/linux.js +1 -1
  63. package/dist/src/process-exit.js +1 -0
  64. package/dist/src/react-native.js +1 -1
  65. package/dist/src/record-trace.js +1 -1
  66. package/dist/src/register-builtins.js +1 -1
  67. package/dist/src/registry.js +25 -24
  68. package/dist/src/remote-config.js +1 -1
  69. package/dist/src/remote-config2.js +1 -1
  70. package/dist/src/reporting.js +2 -2
  71. package/dist/src/runner-client.js +1 -1
  72. package/dist/src/runner-disposal.js +1 -1
  73. package/dist/src/runner-lease.js +1 -1
  74. package/dist/src/runtime.js +3 -3
  75. package/dist/src/screenshot-geometry.js +1 -1
  76. package/dist/src/screenshot-result.js +4 -4
  77. package/dist/src/sdk-android-adb.d.ts +20 -2
  78. package/dist/src/sdk-android-adb.js +1 -1
  79. package/dist/src/sdk-batch-runner.js +3 -3
  80. package/dist/src/sdk-contracts.d.ts +17 -2
  81. package/dist/src/sdk-finders.d.ts +4 -20
  82. package/dist/src/sdk-finders.js +1 -1
  83. package/dist/src/{interaction.d.ts → sdk-selectors.d.ts} +98 -3
  84. package/dist/src/sdk-selectors.js +1 -1
  85. package/dist/src/selector-runtime.js +1 -1
  86. package/dist/src/selector-vocabulary.js +1 -1
  87. package/dist/src/selectors.d.ts +1 -46
  88. package/dist/src/selectors.js +1 -1
  89. package/dist/src/server.js +3 -3
  90. package/dist/src/session-target-evidence.js +1 -1
  91. package/dist/src/session.js +9 -8
  92. package/dist/src/snapshot2.js +1 -1
  93. package/dist/src/text-surface.js +1 -0
  94. package/dist/src/viewport-dimension.js +13 -12
  95. package/dist/src/web.js +1 -1
  96. package/package.json +42 -34
  97. package/server.json +3 -3
  98. package/android/ime-helper/dist/agent-device-android-ime-helper-0.20.4.apk.sha256 +0 -1
  99. package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.20.4.apk.sha256 +0 -1
  100. package/dist/apple/runner/AgentDeviceRunner/RecordingScripts/recording-resize.swift +0 -225
@@ -1,53 +1,57 @@
1
1
  #import "RunnerSynthesizedGesture.h"
2
+ #import "RunnerXCTestEventBridge.h"
2
3
 
3
4
  #import <CoreGraphics/CoreGraphics.h>
4
5
  #import <objc/message.h>
5
6
 
6
- typedef NSInteger (*RunnerMsgSendInteger)(id, SEL);
7
+ static NSString *const RunnerGestureSynthesisSurface = @"event";
8
+
7
9
  typedef id (*RunnerMsgSendInitRecord)(id, SEL, NSString *, NSInteger);
8
10
  typedef id (*RunnerMsgSendInitPath)(id, SEL, CGPoint, NSTimeInterval);
9
11
  typedef void (*RunnerMsgSendPathMove)(id, SEL, CGPoint, NSTimeInterval);
10
12
  typedef void (*RunnerMsgSendPathOffset)(id, SEL, NSTimeInterval);
11
- typedef void (*RunnerMsgSendAddPath)(id, SEL, id);
12
- typedef void (*RunnerMsgSendSetInteger)(id, SEL, NSInteger);
13
- typedef BOOL (*RunnerMsgSendSynthesize)(id, SEL, NSError **);
14
13
 
14
+ // Gesture-specific extension of the shared bridge: the 2-arg
15
+ // `initWithName:interfaceOrientation:` record initializer and the touch-path
16
+ // factory/mutator selectors, none of which text-entry synthesis needs.
15
17
  typedef struct {
16
- Class recordClass;
17
- Class pathClass;
18
+ RunnerXCTestEventBridge core;
18
19
  SEL initRecordSelector;
19
- SEL addPathSelector;
20
- SEL setTargetProcessIDSelector;
21
- SEL synthesizeSelector;
22
20
  SEL interfaceOrientationSelector;
23
- SEL processIDSelector;
24
21
  SEL initPathSelector;
25
22
  SEL moveSelector;
26
23
  SEL liftSelector;
27
- } RunnerXCTestEventBridge;
24
+ } RunnerGestureEventBridge;
28
25
 
29
26
  typedef id (*RunnerDragPointerPathFactory)(
30
- const RunnerXCTestEventBridge *,
27
+ const RunnerGestureEventBridge *,
31
28
  CGPoint,
32
29
  CGPoint,
33
30
  double
34
31
  );
35
32
 
36
- static NSString * _Nullable RunnerResolveXCTestEventBridge(
33
+ static NSString * _Nullable RunnerResolveGestureEventBridge(
34
+ id application,
35
+ RunnerGestureEventBridge *bridge
36
+ );
37
+ static NSString * _Nullable RunnerCreateEventRecord(
37
38
  id application,
38
- RunnerXCTestEventBridge *bridge
39
+ NSString *recordName,
40
+ RunnerGestureEventBridge *bridge,
41
+ id *record
42
+ );
43
+ static NSString * _Nullable RunnerSynthesizeEventRecord(
44
+ const RunnerGestureEventBridge *bridge,
45
+ id record
39
46
  );
40
- static NSString * _Nullable RunnerRequireClass(Class cls, NSString *className);
41
- static NSString * _Nullable RunnerRequireSelector(Class cls, SEL selector, NSString *selectorName);
42
- static NSString * _Nullable RunnerRequireApplicationSelector(id application, SEL selector, NSString *selectorName);
43
47
  static id RunnerSwipePointerPath(
44
- const RunnerXCTestEventBridge *bridge,
48
+ const RunnerGestureEventBridge *bridge,
45
49
  CGPoint start,
46
50
  CGPoint end,
47
51
  double durationMs
48
52
  );
49
53
  static id RunnerContinuousDragPointerPath(
50
- const RunnerXCTestEventBridge *bridge,
54
+ const RunnerGestureEventBridge *bridge,
51
55
  CGPoint start,
52
56
  CGPoint end,
53
57
  double durationMs
@@ -60,11 +64,12 @@ static NSString * _Nullable RunnerTrySynthesizeDrag(
60
64
  NSString *recordName,
61
65
  RunnerDragPointerPathFactory pathFactory
62
66
  );
67
+ static NSString * _Nullable RunnerTrySynthesizeTap(id application, CGPoint point);
63
68
  // XCTest's proven swipe profile reaches the endpoint in 100 ms, then holds for the planned
64
69
  // fling duration. Fast movement is what lets UIKit distinguish a fling from a timed pan.
65
70
  static const NSTimeInterval RunnerSwipeMovementDurationSeconds = 0.1;
66
71
  static id RunnerTapPointerPath(
67
- const RunnerXCTestEventBridge *bridge,
72
+ const RunnerGestureEventBridge *bridge,
68
73
  CGPoint point
69
74
  );
70
75
 
@@ -86,9 +91,7 @@ static id RunnerTapPointerPath(
86
91
  RunnerSwipePointerPath
87
92
  );
88
93
  } @catch (NSException *exception) {
89
- NSString *name = exception.name ?: @"NSException";
90
- NSString *reason = exception.reason ?: @"private XCTest event synthesis failed";
91
- return [NSString stringWithFormat:@"%@: %@", name, reason];
94
+ return RunnerFormatXCTestException(exception, @"private XCTest event synthesis failed");
92
95
  }
93
96
  }
94
97
 
@@ -108,9 +111,7 @@ static id RunnerTapPointerPath(
108
111
  RunnerContinuousDragPointerPath
109
112
  );
110
113
  } @catch (NSException *exception) {
111
- NSString *name = exception.name ?: @"NSException";
112
- NSString *reason = exception.reason ?: @"private XCTest event synthesis failed";
113
- return [NSString stringWithFormat:@"%@: %@", name, reason];
114
+ return RunnerFormatXCTestException(exception, @"private XCTest event synthesis failed");
114
115
  }
115
116
  }
116
117
 
@@ -118,46 +119,31 @@ static id RunnerTapPointerPath(
118
119
  x:(double)x
119
120
  y:(double)y {
120
121
  @try {
121
- return [self trySynthesizeTapWithApplication:application x:x y:y];
122
+ return RunnerTrySynthesizeTap(application, CGPointMake(x, y));
122
123
  } @catch (NSException *exception) {
123
- NSString *name = exception.name ?: @"NSException";
124
- NSString *reason = exception.reason ?: @"private XCTest event synthesis failed";
125
- return [NSString stringWithFormat:@"%@: %@", name, reason];
124
+ return RunnerFormatXCTestException(exception, @"private XCTest event synthesis failed");
126
125
  }
127
126
  }
128
127
 
129
128
  + (NSString * _Nullable)synthesizeGestureWithApplication:(id)application
130
129
  pointerSamples:(NSArray<NSArray<NSDictionary<NSString *, NSNumber *> *> *> *)pointerSamples {
131
130
  @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
- }
143
-
144
- id record = ((RunnerMsgSendInitRecord)objc_msgSend)(
145
- [bridge.recordClass alloc],
146
- bridge.initRecordSelector,
131
+ RunnerGestureEventBridge bridge;
132
+ id record = nil;
133
+ NSString *error = RunnerCreateEventRecord(
134
+ application,
147
135
  @"agent-device-gesture-plan",
148
- interfaceOrientation
136
+ &bridge,
137
+ &record
149
138
  );
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);
139
+ if (error != nil) return error;
154
140
 
155
141
  for (NSArray<NSDictionary<NSString *, NSNumber *> *> *samples in pointerSamples) {
156
142
  NSDictionary<NSString *, NSNumber *> *first = samples.firstObject;
157
143
  if (first == nil) return @"private XCTest event synthesis failed: empty pointer path";
158
144
  CGPoint start = CGPointMake(first[@"x"].doubleValue, first[@"y"].doubleValue);
159
145
  id path = ((RunnerMsgSendInitPath)objc_msgSend)(
160
- [bridge.pathClass alloc],
146
+ [bridge.core.pathClass alloc],
161
147
  bridge.initPathSelector,
162
148
  start,
163
149
  first[@"offsetMs"].doubleValue / 1000.0
@@ -181,20 +167,12 @@ static id RunnerTapPointerPath(
181
167
  bridge.liftSelector,
182
168
  last[@"offsetMs"].doubleValue / 1000.0
183
169
  );
184
- ((RunnerMsgSendAddPath)objc_msgSend)(record, bridge.addPathSelector, path);
170
+ ((RunnerMsgSendAddPath)objc_msgSend)(record, bridge.core.addPathSelector, path);
185
171
  }
186
172
 
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;
173
+ return RunnerSynthesizeEventRecord(&bridge, record);
194
174
  } @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];
175
+ return RunnerFormatXCTestException(exception, @"private XCTest event synthesis failed");
198
176
  }
199
177
  }
200
178
 
@@ -214,135 +192,78 @@ static NSString * _Nullable RunnerTrySynthesizeDrag(
214
192
  NSString *recordName,
215
193
  RunnerDragPointerPathFactory pathFactory
216
194
  ) {
217
- RunnerXCTestEventBridge bridge;
218
- NSString *missing = RunnerResolveXCTestEventBridge(application, &bridge);
219
- if (missing != nil) {
220
- return missing;
221
- }
222
-
223
- NSInteger interfaceOrientation =
224
- ((RunnerMsgSendInteger)objc_msgSend)(application, bridge.interfaceOrientationSelector);
225
- NSInteger targetProcessID = ((RunnerMsgSendInteger)objc_msgSend)(application, bridge.processIDSelector);
226
- if (targetProcessID <= 0) {
227
- return @"private XCTest event synthesis unavailable: could not resolve target process ID";
228
- }
229
-
230
- id record = ((RunnerMsgSendInitRecord)objc_msgSend)(
231
- [bridge.recordClass alloc],
232
- bridge.initRecordSelector,
233
- recordName,
234
- interfaceOrientation
235
- );
236
- if (record == nil) {
237
- return @"private XCTest event synthesis failed: could not create event record";
238
- }
239
- ((RunnerMsgSendSetInteger)objc_msgSend)(record, bridge.setTargetProcessIDSelector, targetProcessID);
195
+ RunnerGestureEventBridge bridge;
196
+ id record = nil;
197
+ NSString *error = RunnerCreateEventRecord(application, recordName, &bridge, &record);
198
+ if (error != nil) return error;
240
199
 
241
200
  id path = pathFactory(&bridge, start, end, durationMs);
242
201
  if (path == nil) {
243
202
  return @"private XCTest event synthesis failed: could not create pointer path";
244
203
  }
245
- ((RunnerMsgSendAddPath)objc_msgSend)(record, bridge.addPathSelector, path);
204
+ ((RunnerMsgSendAddPath)objc_msgSend)(record, bridge.core.addPathSelector, path);
246
205
 
247
- NSError *error = nil;
248
- BOOL ok = ((RunnerMsgSendSynthesize)objc_msgSend)(record, bridge.synthesizeSelector, &error);
249
- if (!ok) {
250
- NSString *detail = error.localizedDescription ?: @"synthesizeWithError returned false";
251
- return [NSString stringWithFormat:@"private XCTest event synthesis failed: %@", detail];
252
- }
253
- return nil;
206
+ return RunnerSynthesizeEventRecord(&bridge, record);
254
207
  }
255
208
 
256
- + (NSString * _Nullable)trySynthesizeTapWithApplication:(id)application
257
- x:(double)x
258
- y:(double)y {
259
- RunnerXCTestEventBridge bridge;
260
- NSString *missing = RunnerResolveXCTestEventBridge(application, &bridge);
261
- if (missing != nil) {
262
- return missing;
263
- }
264
-
265
- NSInteger interfaceOrientation =
266
- ((RunnerMsgSendInteger)objc_msgSend)(application, bridge.interfaceOrientationSelector);
267
- NSInteger targetProcessID = ((RunnerMsgSendInteger)objc_msgSend)(application, bridge.processIDSelector);
268
- if (targetProcessID <= 0) {
269
- return @"private XCTest event synthesis unavailable: could not resolve target process ID";
270
- }
271
-
272
- id record = ((RunnerMsgSendInitRecord)objc_msgSend)(
273
- [bridge.recordClass alloc],
274
- bridge.initRecordSelector,
209
+ static NSString * _Nullable RunnerTrySynthesizeTap(id application, CGPoint point) {
210
+ RunnerGestureEventBridge bridge;
211
+ id record = nil;
212
+ NSString *error = RunnerCreateEventRecord(
213
+ application,
275
214
  @"agent-device-tap",
276
- interfaceOrientation
215
+ &bridge,
216
+ &record
277
217
  );
278
- if (record == nil) {
279
- return @"private XCTest event synthesis failed: could not create event record";
280
- }
281
- ((RunnerMsgSendSetInteger)objc_msgSend)(record, bridge.setTargetProcessIDSelector, targetProcessID);
218
+ if (error != nil) return error;
282
219
 
283
- id path = RunnerTapPointerPath(&bridge, CGPointMake(x, y));
220
+ id path = RunnerTapPointerPath(&bridge, point);
284
221
  if (path == nil) {
285
222
  return @"private XCTest event synthesis failed: could not create pointer path";
286
223
  }
287
- ((RunnerMsgSendAddPath)objc_msgSend)(record, bridge.addPathSelector, path);
288
-
289
- NSError *error = nil;
290
- BOOL ok = ((RunnerMsgSendSynthesize)objc_msgSend)(record, bridge.synthesizeSelector, &error);
291
- if (!ok) {
292
- NSString *detail = error.localizedDescription ?: @"synthesizeWithError returned false";
293
- return [NSString stringWithFormat:@"private XCTest event synthesis failed: %@", detail];
294
- }
295
- return nil;
224
+ ((RunnerMsgSendAddPath)objc_msgSend)(record, bridge.core.addPathSelector, path);
225
+ return RunnerSynthesizeEventRecord(&bridge, record);
296
226
  }
297
227
 
298
- static NSString * _Nullable RunnerResolveXCTestEventBridge(
228
+ static NSString * _Nullable RunnerResolveGestureEventBridge(
299
229
  id application,
300
- RunnerXCTestEventBridge *bridge
230
+ RunnerGestureEventBridge *bridge
301
231
  ) {
302
- Class recordClass = NSClassFromString(@"XCSynthesizedEventRecord");
303
- Class pathClass = NSClassFromString(@"XCPointerEventPath");
232
+ RunnerXCTestEventBridge core;
233
+ NSString *missing = RunnerResolveXCTestEventBridge(application, RunnerGestureSynthesisSurface, &core);
234
+ if (missing != nil) return missing;
235
+
304
236
  SEL initRecordSelector = NSSelectorFromString(@"initWithName:interfaceOrientation:");
305
- SEL addPathSelector = NSSelectorFromString(@"addPointerEventPath:");
306
- SEL setTargetProcessIDSelector = NSSelectorFromString(@"setTargetProcessID:");
307
- SEL synthesizeSelector = NSSelectorFromString(@"synthesizeWithError:");
308
237
  SEL interfaceOrientationSelector = NSSelectorFromString(@"interfaceOrientation");
309
- SEL processIDSelector = NSSelectorFromString(@"processID");
310
238
  SEL initPathSelector = NSSelectorFromString(@"initForTouchAtPoint:offset:");
311
239
  SEL moveSelector = NSSelectorFromString(@"moveToPoint:atOffset:");
312
240
  SEL liftSelector = NSSelectorFromString(@"liftUpAtOffset:");
313
241
 
314
- NSString *missing = RunnerRequireClass(recordClass, @"XCSynthesizedEventRecord");
315
- if (missing != nil) return missing;
316
- missing = RunnerRequireClass(pathClass, @"XCPointerEventPath");
317
- if (missing != nil) return missing;
318
- missing = RunnerRequireSelector(recordClass, initRecordSelector, @"initWithName:interfaceOrientation:");
319
- if (missing != nil) return missing;
320
- missing = RunnerRequireSelector(recordClass, addPathSelector, @"addPointerEventPath:");
321
- if (missing != nil) return missing;
322
- missing = RunnerRequireSelector(recordClass, setTargetProcessIDSelector, @"setTargetProcessID:");
323
- if (missing != nil) return missing;
324
- missing = RunnerRequireSelector(recordClass, synthesizeSelector, @"synthesizeWithError:");
325
- if (missing != nil) return missing;
326
- missing = RunnerRequireSelector(pathClass, initPathSelector, @"initForTouchAtPoint:offset:");
242
+ missing = RunnerRequireSelector(
243
+ core.recordClass, initRecordSelector, @"initWithName:interfaceOrientation:", RunnerGestureSynthesisSurface
244
+ );
327
245
  if (missing != nil) return missing;
328
- missing = RunnerRequireSelector(pathClass, moveSelector, @"moveToPoint:atOffset:");
246
+ missing = RunnerRequireSelector(
247
+ core.pathClass, initPathSelector, @"initForTouchAtPoint:offset:", RunnerGestureSynthesisSurface
248
+ );
329
249
  if (missing != nil) return missing;
330
- missing = RunnerRequireSelector(pathClass, liftSelector, @"liftUpAtOffset:");
250
+ missing = RunnerRequireSelector(
251
+ core.pathClass, moveSelector, @"moveToPoint:atOffset:", RunnerGestureSynthesisSurface
252
+ );
331
253
  if (missing != nil) return missing;
332
- missing = RunnerRequireApplicationSelector(application, interfaceOrientationSelector, @"interfaceOrientation");
254
+ missing = RunnerRequireSelector(
255
+ core.pathClass, liftSelector, @"liftUpAtOffset:", RunnerGestureSynthesisSurface
256
+ );
333
257
  if (missing != nil) return missing;
334
- missing = RunnerRequireApplicationSelector(application, processIDSelector, @"processID");
258
+ missing = RunnerRequireApplicationSelector(
259
+ application, interfaceOrientationSelector, @"interfaceOrientation", RunnerGestureSynthesisSurface
260
+ );
335
261
  if (missing != nil) return missing;
336
262
 
337
- *bridge = (RunnerXCTestEventBridge){
338
- .recordClass = recordClass,
339
- .pathClass = pathClass,
263
+ *bridge = (RunnerGestureEventBridge){
264
+ .core = core,
340
265
  .initRecordSelector = initRecordSelector,
341
- .addPathSelector = addPathSelector,
342
- .setTargetProcessIDSelector = setTargetProcessIDSelector,
343
- .synthesizeSelector = synthesizeSelector,
344
266
  .interfaceOrientationSelector = interfaceOrientationSelector,
345
- .processIDSelector = processIDSelector,
346
267
  .initPathSelector = initPathSelector,
347
268
  .moveSelector = moveSelector,
348
269
  .liftSelector = liftSelector,
@@ -350,47 +271,62 @@ static NSString * _Nullable RunnerResolveXCTestEventBridge(
350
271
  return nil;
351
272
  }
352
273
 
353
- static NSString * _Nullable RunnerRequireClass(Class cls, NSString *className) {
354
- if (cls == Nil) {
355
- return [NSString stringWithFormat:@"private XCTest event synthesis unavailable: missing %@", className];
274
+ static NSString * _Nullable RunnerCreateEventRecord(
275
+ id application,
276
+ NSString *recordName,
277
+ RunnerGestureEventBridge *bridge,
278
+ id *record
279
+ ) {
280
+ NSString *missing = RunnerResolveGestureEventBridge(application, bridge);
281
+ if (missing != nil) return missing;
282
+
283
+ NSInteger interfaceOrientation =
284
+ ((RunnerMsgSendInteger)objc_msgSend)(application, bridge->interfaceOrientationSelector);
285
+ NSInteger targetProcessID =
286
+ ((RunnerMsgSendInteger)objc_msgSend)(application, bridge->core.processIDSelector);
287
+ if (targetProcessID <= 0) {
288
+ return @"private XCTest event synthesis unavailable: could not resolve target process ID";
356
289
  }
357
- return nil;
358
- }
359
290
 
360
- static NSString * _Nullable RunnerRequireSelector(Class cls, SEL selector, NSString *selectorName) {
361
- if (![cls instancesRespondToSelector:selector]) {
362
- return [NSString stringWithFormat:
363
- @"private XCTest event synthesis unavailable: %@ missing %@",
364
- NSStringFromClass(cls),
365
- selectorName
366
- ];
291
+ id eventRecord = ((RunnerMsgSendInitRecord)objc_msgSend)(
292
+ [bridge->core.recordClass alloc],
293
+ bridge->initRecordSelector,
294
+ recordName,
295
+ interfaceOrientation
296
+ );
297
+ if (eventRecord == nil) {
298
+ return @"private XCTest event synthesis failed: could not create event record";
367
299
  }
300
+ ((RunnerMsgSendSetInteger)objc_msgSend)(
301
+ eventRecord,
302
+ bridge->core.setTargetProcessIDSelector,
303
+ targetProcessID
304
+ );
305
+ *record = eventRecord;
368
306
  return nil;
369
307
  }
370
308
 
371
- static NSString * _Nullable RunnerRequireApplicationSelector(
372
- id application,
373
- SEL selector,
374
- NSString *selectorName
309
+ static NSString * _Nullable RunnerSynthesizeEventRecord(
310
+ const RunnerGestureEventBridge *bridge,
311
+ id record
375
312
  ) {
376
- if (![application respondsToSelector:selector]) {
377
- return [NSString stringWithFormat:
378
- @"private XCTest event synthesis unavailable: XCUIApplication missing %@",
379
- selectorName
380
- ];
313
+ NSError *error = nil;
314
+ BOOL ok = ((RunnerMsgSendSynthesize)objc_msgSend)(record, bridge->core.synthesizeSelector, &error);
315
+ if (!ok) {
316
+ NSString *detail = error.localizedDescription ?: @"synthesizeWithError returned false";
317
+ return [NSString stringWithFormat:@"private XCTest event synthesis failed: %@", detail];
381
318
  }
382
319
  return nil;
383
320
  }
384
321
 
385
-
386
322
  static id RunnerSwipePointerPath(
387
- const RunnerXCTestEventBridge *bridge,
323
+ const RunnerGestureEventBridge *bridge,
388
324
  CGPoint start,
389
325
  CGPoint end,
390
326
  double durationMs
391
327
  ) {
392
328
  id path =
393
- ((RunnerMsgSendInitPath)objc_msgSend)([bridge->pathClass alloc], bridge->initPathSelector, start, 0.0);
329
+ ((RunnerMsgSendInitPath)objc_msgSend)([bridge->core.pathClass alloc], bridge->initPathSelector, start, 0.0);
394
330
  if (path == nil) {
395
331
  return nil;
396
332
  }
@@ -411,17 +347,20 @@ static id RunnerSwipePointerPath(
411
347
  }
412
348
 
413
349
  static id RunnerContinuousDragPointerPath(
414
- const RunnerXCTestEventBridge *bridge,
350
+ const RunnerGestureEventBridge *bridge,
415
351
  CGPoint start,
416
352
  CGPoint end,
417
353
  double durationMs
418
354
  ) {
419
355
  id path =
420
- ((RunnerMsgSendInitPath)objc_msgSend)([bridge->pathClass alloc], bridge->initPathSelector, start, 0.0);
356
+ ((RunnerMsgSendInitPath)objc_msgSend)([bridge->core.pathClass alloc], bridge->initPathSelector, start, 0.0);
421
357
  if (path == nil) {
422
358
  return nil;
423
359
  }
424
360
 
361
+ // This is velocity shaping, not just interpolation density: smoothstep's endpoint slope is zero,
362
+ // while a planned linear segment reaches lift with nonzero velocity unless a destination hold
363
+ // follows it. UIKit uses finger-up velocity for scroll deceleration. See ADR 0013 and issue #1586.
425
364
  int frameCount = MAX(3, (int)(durationMs / 16.0));
426
365
  NSTimeInterval durationSeconds = durationMs / 1000.0;
427
366
  for (int index = 1; index <= frameCount; index += 1) {
@@ -439,11 +378,11 @@ static id RunnerContinuousDragPointerPath(
439
378
  }
440
379
 
441
380
  static id RunnerTapPointerPath(
442
- const RunnerXCTestEventBridge *bridge,
381
+ const RunnerGestureEventBridge *bridge,
443
382
  CGPoint point
444
383
  ) {
445
384
  id path =
446
- ((RunnerMsgSendInitPath)objc_msgSend)([bridge->pathClass alloc], bridge->initPathSelector, point, 0.0);
385
+ ((RunnerMsgSendInitPath)objc_msgSend)([bridge->core.pathClass alloc], bridge->initPathSelector, point, 0.0);
447
386
  if (path == nil) {
448
387
  return nil;
449
388
  }
@@ -0,0 +1,32 @@
1
+ #import <Foundation/Foundation.h>
2
+
3
+ NS_ASSUME_NONNULL_BEGIN
4
+
5
+ typedef NS_ENUM(NSInteger, RunnerSynthesizedTextEntryStatus) {
6
+ RunnerSynthesizedTextEntryStatusSucceeded,
7
+ RunnerSynthesizedTextEntryStatusUnavailable,
8
+ RunnerSynthesizedTextEntryStatusFailed,
9
+ };
10
+
11
+ @interface RunnerSynthesizedTextEntryResult : NSObject
12
+
13
+ @property(nonatomic, readonly) RunnerSynthesizedTextEntryStatus status;
14
+ @property(nonatomic, readonly, nullable) NSString *message;
15
+
16
+ @end
17
+
18
+ @interface RunnerSynthesizedTextEntry : NSObject
19
+
20
+ // Synthesizes keyboard input for the current first responder without resolving an
21
+ // XCUIElement or serializing the application's accessibility tree.
22
+ + (RunnerSynthesizedTextEntryResult *)synthesizeTextWithApplication:(id)application
23
+ text:(NSString *)text;
24
+
25
+ // Replaces the current first responder's contents using one synthesized
26
+ // Command-A, Delete, and text-input event sequence.
27
+ + (RunnerSynthesizedTextEntryResult *)replaceTextWithApplication:(id)application
28
+ text:(NSString *)text;
29
+
30
+ @end
31
+
32
+ NS_ASSUME_NONNULL_END