@@ -13,6 +13,18 @@ extension RunnerTests {
1313 return ( gestureStartUptimeMs, currentUptimeMs ( ) )
1414 }
1515
16+ private func unsupportedResponse( for outcome: RunnerInteractionOutcome ) -> Response ? {
17+ switch outcome {
18+ case . performed:
19+ return nil
20+ case . unsupported( let message) :
21+ return Response (
22+ ok: false ,
23+ error: ErrorPayload ( code: " UNSUPPORTED_OPERATION " , message: message)
24+ )
25+ }
26+ }
27+
1628 func execute( command: Command ) throws -> Response {
1729 if Thread . isMainThread {
1830 return try executeOnMainSafely ( command: command)
@@ -231,11 +243,15 @@ extension RunnerTests {
231243 case . tap:
232244 if let text = command. text {
233245 if let element = findElement ( app: activeApp, text: text) {
246+ var outcome = RunnerInteractionOutcome . performed
234247 let timing = measureGesture {
235248 withTemporaryScrollIdleTimeoutIfSupported ( activeApp) {
236- element. tap ( )
249+ outcome = activateElement ( app : activeApp , element: element , action : " tap by text " )
237250 }
238251 }
252+ if let response = unsupportedResponse ( for: outcome) {
253+ return response
254+ }
239255 return Response (
240256 ok: true ,
241257 data: DataPayload (
@@ -249,11 +265,15 @@ extension RunnerTests {
249265 }
250266 if let x = command. x, let y = command. y {
251267 let touchFrame = resolvedTouchVisualizationFrame ( app: activeApp, x: x, y: y)
268+ var outcome = RunnerInteractionOutcome . performed
252269 let timing = measureGesture {
253270 withTemporaryScrollIdleTimeoutIfSupported ( activeApp) {
254- tapAt ( app: activeApp, x: x, y: y)
271+ outcome = tapAt ( app: activeApp, x: x, y: y)
255272 }
256273 }
274+ if let response = unsupportedResponse ( for: outcome) {
275+ return response
276+ }
257277 return Response (
258278 ok: true ,
259279 data: DataPayload (
@@ -309,13 +329,19 @@ extension RunnerTests {
309329 let doubleTap = command. doubleTap ?? false
310330 let touchFrame = resolvedTouchVisualizationFrame ( app: activeApp, x: x, y: y)
311331 if doubleTap {
332+ var outcome = RunnerInteractionOutcome . performed
312333 let timing = measureGesture {
313334 withTemporaryScrollIdleTimeoutIfSupported ( activeApp) {
314335 runSeries ( count: count, pauseMs: intervalMs) { _ in
315- doubleTapAt ( app: activeApp, x: x, y: y)
336+ if case . performed = outcome {
337+ outcome = doubleTapAt ( app: activeApp, x: x, y: y)
338+ }
316339 }
317340 }
318341 }
342+ if let response = unsupportedResponse ( for: outcome) {
343+ return response
344+ }
319345 return Response (
320346 ok: true ,
321347 data: DataPayload (
@@ -329,13 +355,19 @@ extension RunnerTests {
329355 )
330356 )
331357 }
358+ var outcome = RunnerInteractionOutcome . performed
332359 let timing = measureGesture {
333360 withTemporaryScrollIdleTimeoutIfSupported ( activeApp) {
334361 runSeries ( count: count, pauseMs: intervalMs) { _ in
335- tapAt ( app: activeApp, x: x, y: y)
362+ if case . performed = outcome {
363+ outcome = tapAt ( app: activeApp, x: x, y: y)
364+ }
336365 }
337366 }
338367 }
368+ if let response = unsupportedResponse ( for: outcome) {
369+ return response
370+ }
339371 return Response (
340372 ok: true ,
341373 data: DataPayload (
@@ -354,11 +386,15 @@ extension RunnerTests {
354386 }
355387 let duration = ( command. durationMs ?? 800 ) / 1000.0
356388 let touchFrame = resolvedTouchVisualizationFrame ( app: activeApp, x: x, y: y)
389+ var outcome = RunnerInteractionOutcome . performed
357390 let timing = measureGesture {
358391 withTemporaryScrollIdleTimeoutIfSupported ( activeApp) {
359- longPressAt ( app: activeApp, x: x, y: y, duration: duration)
392+ outcome = longPressAt ( app: activeApp, x: x, y: y, duration: duration)
360393 }
361394 }
395+ if let response = unsupportedResponse ( for: outcome) {
396+ return response
397+ }
362398 return Response (
363399 ok: true ,
364400 data: DataPayload (
@@ -377,11 +413,15 @@ extension RunnerTests {
377413 }
378414 let holdDuration = min ( max ( ( command. durationMs ?? 60 ) / 1000.0 , 0.016 ) , 10.0 )
379415 let dragFrame = resolvedDragVisualizationFrame ( app: activeApp, x: x, y: y, x2: x2, y2: y2)
416+ var outcome = RunnerInteractionOutcome . performed
380417 let timing = measureGesture {
381418 withTemporaryScrollIdleTimeoutIfSupported ( activeApp) {
382- dragAt ( app: activeApp, x: x, y: y, x2: x2, y2: y2, holdDuration: holdDuration)
419+ outcome = dragAt ( app: activeApp, x: x, y: y, x2: x2, y2: y2, holdDuration: holdDuration)
383420 }
384421 }
422+ if let response = unsupportedResponse ( for: outcome) {
423+ return response
424+ }
385425 return Response (
386426 ok: true ,
387427 data: DataPayload (
@@ -407,18 +447,25 @@ extension RunnerTests {
407447 return Response ( ok: false , error: ErrorPayload ( message: " dragSeries pattern must be one-way or ping-pong " ) )
408448 }
409449 let holdDuration = min ( max ( ( command. durationMs ?? 60 ) / 1000.0 , 0.016 ) , 10.0 )
450+ var outcome = RunnerInteractionOutcome . performed
410451 let timing = measureGesture {
411452 withTemporaryScrollIdleTimeoutIfSupported ( activeApp) {
412453 runSeries ( count: count, pauseMs: pauseMs) { idx in
454+ guard case . performed = outcome else {
455+ return
456+ }
413457 let reverse = pattern == " ping-pong " && ( idx % 2 == 1 )
414458 if reverse {
415- dragAt ( app: activeApp, x: x2, y: y2, x2: x, y2: y, holdDuration: holdDuration)
459+ outcome = dragAt ( app: activeApp, x: x2, y: y2, x2: x, y2: y, holdDuration: holdDuration)
416460 } else {
417- dragAt ( app: activeApp, x: x, y: y, x2: x2, y2: y2, holdDuration: holdDuration)
461+ outcome = dragAt ( app: activeApp, x: x, y: y, x2: x2, y2: y2, holdDuration: holdDuration)
418462 }
419463 }
420464 }
421465 }
466+ if let response = unsupportedResponse ( for: outcome) {
467+ return response
468+ }
422469 return Response (
423470 ok: true ,
424471 data: DataPayload (
@@ -427,6 +474,18 @@ extension RunnerTests {
427474 gestureEndUptimeMs: timing. gestureEndUptimeMs
428475 )
429476 )
477+ case . remotePress:
478+ guard let button = tvRemoteButton ( from: command. remoteButton) else {
479+ return Response ( ok: false , error: ErrorPayload ( message: " remotePress requires remoteButton " ) )
480+ }
481+ let duration = ( command. durationMs ?? 0 ) / 1000.0
482+ guard pressTvRemote ( button, duration: duration) else {
483+ return Response (
484+ ok: false ,
485+ error: ErrorPayload ( code: " UNSUPPORTED_OPERATION " , message: " remotePress is only supported on tvOS " )
486+ )
487+ }
488+ return Response ( ok: true , data: DataPayload ( message: " remote pressed " ) )
430489 case . type:
431490 guard let text = command. text else {
432491 return Response ( ok: false , error: ErrorPayload ( message: " type requires text " ) )
@@ -633,13 +692,23 @@ extension RunnerTests {
633692 return Response ( ok: false , error: ErrorPayload ( message: " alert not found " ) )
634693 }
635694 if action == " accept " {
636- let button = alert. buttons. allElementsBoundByIndex. first
637- button? . tap ( )
695+ guard let button = alert. buttons. allElementsBoundByIndex. first else {
696+ return Response ( ok: false , error: ErrorPayload ( message: " alert accept button not found " ) )
697+ }
698+ let outcome = activateElement ( app: activeApp, element: button, action: " alert accept " )
699+ if let response = unsupportedResponse ( for: outcome) {
700+ return response
701+ }
638702 return Response ( ok: true , data: DataPayload ( message: " accepted " ) )
639703 }
640704 if action == " dismiss " {
641- let button = alert. buttons. allElementsBoundByIndex. last
642- button? . tap ( )
705+ guard let button = alert. buttons. allElementsBoundByIndex. last else {
706+ return Response ( ok: false , error: ErrorPayload ( message: " alert dismiss button not found " ) )
707+ }
708+ let outcome = activateElement ( app: activeApp, element: button, action: " alert dismiss " )
709+ if let response = unsupportedResponse ( for: outcome) {
710+ return response
711+ }
643712 return Response ( ok: true , data: DataPayload ( message: " dismissed " ) )
644713 }
645714 let buttonLabels = alert. buttons. allElementsBoundByIndex. map { $0. label }
@@ -648,8 +717,12 @@ extension RunnerTests {
648717 guard let scale = command. scale, scale > 0 else {
649718 return Response ( ok: false , error: ErrorPayload ( message: " pinch requires scale > 0 " ) )
650719 }
720+ var outcome = RunnerInteractionOutcome . performed
651721 let timing = measureGesture {
652- pinch ( app: activeApp, scale: scale, x: command. x, y: command. y)
722+ outcome = pinch ( app: activeApp, scale: scale, x: command. x, y: command. y)
723+ }
724+ if let response = unsupportedResponse ( for: outcome) {
725+ return response
653726 }
654727 return Response (
655728 ok: true ,
0 commit comments