@@ -47,6 +47,12 @@ static id RunnerPointerPath(
4747 double durationMs,
4848 double side
4949);
50+ static id RunnerSwipePointerPath (
51+ const RunnerXCTestEventBridge *bridge,
52+ CGPoint start,
53+ CGPoint end,
54+ double durationMs
55+ );
5056static CGPoint RunnerPointerPointAt (
5157 double x,
5258 double y,
@@ -58,6 +64,8 @@ static CGPoint RunnerPointerPointAt(
5864 double t,
5965 double side
6066);
67+ static CGPoint RunnerInterpolatedPoint (CGPoint start, CGPoint end, double t);
68+ static double RunnerSmoothStep (double t);
6169
6270@implementation RunnerSynthesizedGesture
6371
@@ -87,6 +95,26 @@ + (NSString * _Nullable)synthesizeTransformWithApplication:(id)application
8795 }
8896}
8997
98+ + (NSString * _Nullable)synthesizeSwipeWithApplication : (id )application
99+ x : (double )x
100+ y : (double )y
101+ x2 : (double )x2
102+ y2 : (double )y2
103+ durationMs : (double )durationMs {
104+ @try {
105+ return [self trySynthesizeSwipeWithApplication: application
106+ x: x
107+ y: y
108+ x2: x2
109+ y2: y2
110+ durationMs: durationMs];
111+ } @catch (NSException *exception) {
112+ NSString *name = exception.name ?: @" NSException" ;
113+ NSString *reason = exception.reason ?: @" private XCTest event synthesis failed" ;
114+ return [NSString stringWithFormat: @" %@ : %@ " , name, reason];
115+ }
116+ }
117+
90118+ (NSString * _Nullable)trySynthesizeTransformWithApplication : (id )application
91119 x : (double )x
92120 y : (double )y
@@ -151,6 +179,51 @@ + (NSString * _Nullable)trySynthesizeTransformWithApplication:(id)application
151179 return nil ;
152180}
153181
182+ + (NSString * _Nullable)trySynthesizeSwipeWithApplication : (id )application
183+ x : (double )x
184+ y : (double )y
185+ x2 : (double )x2
186+ y2 : (double )y2
187+ durationMs : (double )durationMs {
188+ RunnerXCTestEventBridge bridge;
189+ NSString *missing = RunnerResolveXCTestEventBridge (application, &bridge);
190+ if (missing != nil ) {
191+ return missing;
192+ }
193+
194+ NSInteger interfaceOrientation =
195+ ((RunnerMsgSendInteger)objc_msgSend)(application, bridge.interfaceOrientationSelector );
196+ NSInteger targetProcessID = ((RunnerMsgSendInteger)objc_msgSend)(application, bridge.processIDSelector );
197+ if (targetProcessID <= 0 ) {
198+ return @" private XCTest event synthesis unavailable: could not resolve target process ID" ;
199+ }
200+
201+ id record = ((RunnerMsgSendInitRecord)objc_msgSend)(
202+ [bridge.recordClass alloc ],
203+ bridge.initRecordSelector ,
204+ @" agent-device-swipe" ,
205+ interfaceOrientation
206+ );
207+ if (record == nil ) {
208+ return @" private XCTest event synthesis failed: could not create event record" ;
209+ }
210+ ((RunnerMsgSendSetInteger)objc_msgSend)(record, bridge.setTargetProcessIDSelector , targetProcessID);
211+
212+ id path = RunnerSwipePointerPath (&bridge, CGPointMake (x, y), CGPointMake (x2, y2), durationMs);
213+ if (path == nil ) {
214+ return @" private XCTest event synthesis failed: could not create pointer path" ;
215+ }
216+ ((RunnerMsgSendAddPath)objc_msgSend)(record, bridge.addPathSelector , path);
217+
218+ NSError *error = nil ;
219+ BOOL ok = ((RunnerMsgSendSynthesize)objc_msgSend)(record, bridge.synthesizeSelector , &error);
220+ if (!ok) {
221+ NSString *detail = error.localizedDescription ?: @" synthesizeWithError returned false" ;
222+ return [NSString stringWithFormat: @" private XCTest event synthesis failed: %@ " , detail];
223+ }
224+ return nil ;
225+ }
226+
154227static NSString * _Nullable RunnerResolveXCTestEventBridge (
155228 id application,
156229 RunnerXCTestEventBridge *bridge
@@ -270,6 +343,31 @@ static id RunnerPointerPath(
270343 return path;
271344}
272345
346+ static id RunnerSwipePointerPath (
347+ const RunnerXCTestEventBridge *bridge,
348+ CGPoint start,
349+ CGPoint end,
350+ double durationMs
351+ ) {
352+ id path =
353+ ((RunnerMsgSendInitPath)objc_msgSend)([bridge->pathClass alloc ], bridge->initPathSelector , start, 0.0 );
354+ if (path == nil ) {
355+ return nil ;
356+ }
357+
358+ int frameCount = MAX (3 , (int )(durationMs / 16.0 ));
359+ NSTimeInterval durationSeconds = durationMs / 1000.0 ;
360+ for (int index = 1 ; index <= frameCount; index += 1 ) {
361+ double t = (double )index / (double )frameCount;
362+ CGPoint point = RunnerInterpolatedPoint (start, end, RunnerSmoothStep (t));
363+ NSTimeInterval offset = durationSeconds * t;
364+ ((RunnerMsgSendPathMove)objc_msgSend)(path, bridge->moveSelector , point, offset);
365+ }
366+
367+ ((RunnerMsgSendPathOffset)objc_msgSend)(path, bridge->liftSelector , durationSeconds);
368+ return path;
369+ }
370+
273371static CGPoint RunnerPointerPointAt (
274372 double x,
275373 double y,
@@ -294,4 +392,15 @@ static CGPoint RunnerPointerPointAt(
294392 return CGPointMake (centerX + cos (angle) * radius * side, centerY + sin (angle) * radius * side);
295393}
296394
395+ static CGPoint RunnerInterpolatedPoint (CGPoint start, CGPoint end, double t) {
396+ return CGPointMake (
397+ start.x + (end.x - start.x ) * t,
398+ start.y + (end.y - start.y ) * t
399+ );
400+ }
401+
402+ static double RunnerSmoothStep (double t) {
403+ return t * t * (3.0 - 2.0 * t);
404+ }
405+
297406@end
0 commit comments