@@ -36,6 +36,43 @@ extension RunnerTests {
3636 case drag( DragVisualizationFrame )
3737 }
3838
39+ struct GestureFallback {
40+ let strategy : String
41+ let message : String
42+ let hint : String ?
43+ }
44+
45+ private func gestureFallback( strategy: String , from outcome: RunnerInteractionOutcome ) -> GestureFallback ? {
46+ switch outcome {
47+ case . performed:
48+ return nil
49+ case . unsupported( let message, let hint) :
50+ return GestureFallback ( strategy: strategy, message: message, hint: hint)
51+ }
52+ }
53+
54+ private func performDragSeries(
55+ count: Int ,
56+ pauseMs: Double ,
57+ pattern: String ,
58+ points: DragPoints ,
59+ _ drag: ( _ x: Double , _ y: Double , _ x2: Double , _ y2: Double ) -> RunnerInteractionOutcome
60+ ) -> RunnerInteractionOutcome {
61+ var outcome = RunnerInteractionOutcome . performed
62+ runSeries ( count: count, pauseMs: pauseMs) { idx in
63+ guard case . performed = outcome else {
64+ return
65+ }
66+ let reverse = pattern == " ping-pong " && ( idx % 2 == 1 )
67+ let startX = reverse ? points. x2 : points. x
68+ let startY = reverse ? points. y2 : points. y
69+ let endX = reverse ? points. x : points. x2
70+ let endY = reverse ? points. y : points. y2
71+ outcome = drag ( startX, startY, endX, endY)
72+ }
73+ return outcome
74+ }
75+
3976 /// Runs a gesture action with uniform timing capture. Touch gestures pass `idleTimeout: true`
4077 /// (the default) to run inside the scroll idle-timeout + quiescence-skip wrapper; synthesis
4178 /// gestures (pinch/rotate/transform) pass `false` because RunnerSynthesizedGesture governs its
@@ -64,15 +101,19 @@ extension RunnerTests {
64101 private func gestureResponse(
65102 message: String ,
66103 timing: ( gestureStartUptimeMs: Double , gestureEndUptimeMs: Double ) ,
67- frame: GestureFrame = . none
104+ frame: GestureFrame = . none,
105+ fallback: GestureFallback ? = nil
68106 ) -> Response {
69107 let data : DataPayload
70108 switch frame {
71109 case . none:
72110 data = DataPayload (
73111 message: message,
74112 gestureStartUptimeMs: timing. gestureStartUptimeMs,
75- gestureEndUptimeMs: timing. gestureEndUptimeMs
113+ gestureEndUptimeMs: timing. gestureEndUptimeMs,
114+ gestureFallback: fallback? . strategy,
115+ gestureFallbackMessage: fallback? . message,
116+ gestureFallbackHint: fallback? . hint
76117 )
77118 case . touch( let f) :
78119 data = DataPayload (
@@ -82,7 +123,10 @@ extension RunnerTests {
82123 x: f? . x,
83124 y: f? . y,
84125 referenceWidth: f? . referenceWidth,
85- referenceHeight: f? . referenceHeight
126+ referenceHeight: f? . referenceHeight,
127+ gestureFallback: fallback? . strategy,
128+ gestureFallbackMessage: fallback? . message,
129+ gestureFallbackHint: fallback? . hint
86130 )
87131 case . drag( let f) :
88132 data = DataPayload (
@@ -94,7 +138,10 @@ extension RunnerTests {
94138 x2: f. x2,
95139 y2: f. y2,
96140 referenceWidth: f. referenceWidth,
97- referenceHeight: f. referenceHeight
141+ referenceHeight: f. referenceHeight,
142+ gestureFallback: fallback? . strategy,
143+ gestureFallbackMessage: fallback? . message,
144+ gestureFallbackHint: fallback? . hint
98145 )
99146 }
100147 return Response ( ok: true , data: data)
@@ -507,6 +554,7 @@ extension RunnerTests {
507554 x2: dragPoints. x2,
508555 y2: dragPoints. y2
509556 )
557+ var fallback : GestureFallback ?
510558 if command. synthesized == true {
511559 let durationMs = min ( max ( command. durationMs ?? 250 , 16 ) , 10000 )
512560 let ( timing, outcome) = performGesture ( activeApp, idleTimeout: false ) {
@@ -522,6 +570,7 @@ extension RunnerTests {
522570 if case . performed = outcome {
523571 return gestureResponse ( message: " dragged " , timing: timing, frame: . drag( dragFrame) )
524572 }
573+ fallback = gestureFallback ( strategy: " xctest-coordinate-drag " , from: outcome)
525574 }
526575 let holdDuration = command. synthesized == true
527576 ? synthesizedSwipeFallbackHoldDuration ( durationMs: command. durationMs ?? 250 )
@@ -539,7 +588,12 @@ extension RunnerTests {
539588 if let response = unsupportedResponse ( for: outcome) {
540589 return response
541590 }
542- return gestureResponse ( message: " dragged " , timing: timing, frame: . drag( dragFrame) )
591+ return gestureResponse (
592+ message: " dragged " ,
593+ timing: timing,
594+ frame: . drag( dragFrame) ,
595+ fallback: fallback
596+ )
543597 case . dragSeries:
544598 guard let x = command. x, let y = command. y, let x2 = command. x2, let y2 = command. y2 else {
545599 return Response ( ok: false , error: ErrorPayload ( message: " dragSeries requires x, y, x2, and y2 " ) )
@@ -550,41 +604,56 @@ extension RunnerTests {
550604 if pattern != " one-way " && pattern != " ping-pong " {
551605 return Response ( ok: false , error: ErrorPayload ( message: " dragSeries pattern must be one-way or ping-pong " ) )
552606 }
553- let holdDuration = min ( max ( ( command. durationMs ?? 60 ) / 1000.0 , 0.016 ) , 10.0 )
554607 let dragPoints = keyboardAvoidingDragPoints ( app: activeApp, x: x, y: y, x2: x2, y2: y2)
555- let ( timing, outcome) = performGesture ( activeApp) {
556- var outcome = RunnerInteractionOutcome . performed
557- runSeries ( count: count, pauseMs: pauseMs) { idx in
558- guard case . performed = outcome else {
559- return
560- }
561- let reverse = pattern == " ping-pong " && ( idx % 2 == 1 )
562- if reverse {
563- outcome = dragAt (
564- app: activeApp,
565- x: dragPoints. x2,
566- y: dragPoints. y2,
567- x2: dragPoints. x,
568- y2: dragPoints. y,
569- holdDuration: holdDuration
570- )
571- } else {
572- outcome = dragAt (
608+ var fallback : GestureFallback ?
609+ if command. synthesized == true {
610+ let durationMs = min ( max ( command. durationMs ?? 250 , 16 ) , 10000 )
611+ let ( timing, outcome) = performGesture ( activeApp, idleTimeout: false ) {
612+ performDragSeries (
613+ count: count,
614+ pauseMs: pauseMs,
615+ pattern: pattern,
616+ points: dragPoints
617+ ) { startX, startY, endX, endY in
618+ synthesizedDragAt (
573619 app: activeApp,
574- x: dragPoints . x ,
575- y: dragPoints . y ,
576- x2: dragPoints . x2 ,
577- y2: dragPoints . y2 ,
578- holdDuration : holdDuration
620+ x: startX ,
621+ y: startY ,
622+ x2: endX ,
623+ y2: endY ,
624+ durationMs : durationMs
579625 )
580626 }
581627 }
582- return outcome
628+ if case . performed = outcome {
629+ return gestureResponse ( message: " drag series " , timing: timing)
630+ }
631+ fallback = gestureFallback ( strategy: " xctest-coordinate-drag-series " , from: outcome)
632+ }
633+ let holdDuration = command. synthesized == true
634+ ? synthesizedSwipeFallbackHoldDuration ( durationMs: command. durationMs ?? 250 )
635+ : min ( max ( ( command. durationMs ?? 60 ) / 1000.0 , 0.016 ) , 10.0 )
636+ let ( timing, outcome) = performGesture ( activeApp) {
637+ performDragSeries (
638+ count: count,
639+ pauseMs: pauseMs,
640+ pattern: pattern,
641+ points: dragPoints
642+ ) { startX, startY, endX, endY in
643+ dragAt (
644+ app: activeApp,
645+ x: startX,
646+ y: startY,
647+ x2: endX,
648+ y2: endY,
649+ holdDuration: holdDuration
650+ )
651+ }
583652 }
584653 if let response = unsupportedResponse ( for: outcome) {
585654 return response
586655 }
587- return gestureResponse ( message: " drag series " , timing: timing)
656+ return gestureResponse ( message: " drag series " , timing: timing, fallback : fallback )
588657 case . remotePress:
589658 guard let button = tvRemoteButton ( from: command. remoteButton) else {
590659 return Response ( ok: false , error: ErrorPayload ( message: " remotePress requires remoteButton " ) )
0 commit comments