Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

XCTest Swift - How do I swipe faster or more precisely?

I have tests in XCTest using Swift and I am trying to swipe through a feed until a condition is met. I am using the swipeUp(), but the problem with this is it acts like a human swipe and has this painful animation of slowing down when it lets go. It doesn't try the swipe again until the slowing-down-animation is complete. Additionally, that method takes no arguments. I would like to see if there is something like Calabash or shoot even Androids Espresso where there are properties to a swipe method like swipeUp(fast) or swipeUp(medium) or even swipeUp(x0,y200).

Here is what I am trying to do in code:

    func scrollDownUntilYouFindSomething() {
    while !findingSomehting.exists {
        app.swipeUp()
    }
}

Simple enough, but the swipeUp() in XCUIApplication is painfully slow. I want to be able to swipe with precision and even by coordinate. I tried to use the coordinate approach taken from Replicate pull to refresh in XCTest UI testing

but its equally as slow when put into a loop.

like image 428
Laser Hawk Avatar asked Sep 01 '16 21:09

Laser Hawk


4 Answers

Updated the first answer for Swift 4.

extension XCUIElement {
    enum Direction: Int {
        case up, down, left, right
    }

    func gentleSwipe(_ direction: Direction) {
        let half: CGFloat = 0.5
        let adjustment: CGFloat = 0.25
        let pressDuration: TimeInterval = 0.05

        let lessThanHalf = half - adjustment
        let moreThanHalf = half + adjustment

        let centre = self.coordinate(withNormalizedOffset: CGVector(dx: half, dy: half))
        let aboveCentre = self.coordinate(withNormalizedOffset: CGVector(dx: half, dy: lessThanHalf))
        let belowCentre = self.coordinate(withNormalizedOffset: CGVector(dx: half, dy: moreThanHalf))
        let leftOfCentre = self.coordinate(withNormalizedOffset: CGVector(dx: lessThanHalf, dy: half))
        let rightOfCentre = self.coordinate(withNormalizedOffset: CGVector(dx: moreThanHalf, dy: half))

        switch direction {
        case .up:
            centre.press(forDuration: pressDuration, thenDragTo: aboveCentre)
        case .down:
            centre.press(forDuration: pressDuration, thenDragTo: belowCentre)
        case .left:
            centre.press(forDuration: pressDuration, thenDragTo: leftOfCentre)
        case .right:
            centre.press(forDuration: pressDuration, thenDragTo: rightOfCentre)
        }
    }
}
like image 104
Benzy Avatar answered Nov 13 '22 17:11

Benzy


I wrote an extension based on the excellent answer given by Bharathram C.

I find it to be a much gentler swipe than XCUIElement.swipeUp()

//  XCUIElement+GentleSwipe.swift

import Foundation
import XCTest

extension XCUIElement
{
    enum direction : Int {
        case Up, Down, Left, Right
    }

    func gentleSwipe(_ direction : direction) {
        let half : CGFloat = 0.5
        let adjustment : CGFloat = 0.25
        let pressDuration : TimeInterval = 0.05

        let lessThanHalf = half - adjustment
        let moreThanHalf = half + adjustment

        let centre = self.coordinate(withNormalizedOffset: CGVector(dx: half, dy: half))
        let aboveCentre = self.coordinate(withNormalizedOffset: CGVector(dx: half, dy: lessThanHalf))
        let belowCentre = self.coordinate(withNormalizedOffset: CGVector(dx: half, dy: moreThanHalf))
        let leftOfCentre = self.coordinate(withNormalizedOffset: CGVector(dx: lessThanHalf, dy: half))
        let rightOfCentre = self.coordinate(withNormalizedOffset: CGVector(dx: moreThanHalf, dy: half))

        switch direction {
        case .Up:
            centre.press(forDuration: pressDuration, thenDragTo: aboveCentre)
            break
        case .Down:
            centre.press(forDuration: pressDuration, thenDragTo: belowCentre)
            break
        case .Left:
            centre.press(forDuration: pressDuration, thenDragTo: leftOfCentre)
            break
        case .Right:
            centre.press(forDuration: pressDuration, thenDragTo: rightOfCentre)
            break
        }
    }
}
like image 17
UglyBlueCat Avatar answered Nov 13 '22 16:11

UglyBlueCat


Yes, we can have a co-ordinate based swipe in Swift. We can create our own swipe action by using the pressForDuration(duration, thenDragToElement: XCElement). Please see an example below.

For this we will need

  1. Reference Element in the view.(Pick one in the section where the swiping action has to be performed, and it should not be a draggable element).
  2. A beginning co-ordinate with respect to the reference element.
  3. An end co-ordinate/XCUIElement with respect to the reference element.

Then we can have a function similar to below (in Swift 3)

func customSwipe(refElement:XCUIElement,startdelxy:CGVector,enddeltaxy: CGVector){
      let swipeStartPoint = refElement.coordinate(withNormalizedOffset: startdelxy)
      let swipeEndPoint = refElement.coordinate(withNormalizedOffset: enddeltaxy)
      swipeStartPoint.press(forDuration: 0.1, thenDragTo: swipeEndPoint)

}

Here if startdelxy is (0.0,0.0) and enddeltaxy is (0.0,-ve), swipe will go upward from the refElement. If enddeltaxy is (0.0,+ve) swipe will go down.This is because our reference element will be the origin, X is +ve going right and Y is +ve going below our reference element. Start with smaller values of Y (say -0.75) and change as necessary. More the Difference, Longer the Swipe.

The swipeStartPoint and swipeEndPoint can be replaced with an element (if such exists in the view). Usually, for swipeEndPoint we can go for an element in the Nav Bar for a swipeUp action. Also, Changing the X can give us customSwipes in the left and right directions.

As for the Speed of swiping, I'm not aware of any API that can swipe faster/slower than the default rate.

like image 9
Bharathram C Avatar answered Nov 13 '22 16:11

Bharathram C


I tried to refactor gentleSwipe function to target element. Following refactor focus to reach targeted element. We can pass element in swipeUp function and it will call gentleSwipe that's will help us to reach targeted element. I hope it will help others as well.

Here is code :

public func swipeUp(to element: XCUIElement) {
    while !(elementIsWithinWindow(element: element)) {
           XCUIApplication().gentleSwipe(.up)
    }
}

private func elementIsWithinWindow(element: XCUIElement) -> Bool{
    guard element.exists && element.isHittable else {return false}
    return true
}

enum direction : Int {
    case up, down, left, right
}

func gentleSwipe(_ direction : direction) {
    let half : CGFloat = 0.5
    let adjustment : CGFloat = 0.25
    let pressDuration : TimeInterval = 0.05

    let lessThanHalf = half - adjustment
    let moreThanHalf = half + adjustment

    let centre = self.coordinate(withNormalizedOffset: CGVector(dx: half, dy: half))
    let aboveCentre = self.coordinate(withNormalizedOffset: CGVector(dx: half, dy: lessThanHalf))
    let belowCentre = self.coordinate(withNormalizedOffset: CGVector(dx: half, dy: moreThanHalf))
    let leftOfCentre = self.coordinate(withNormalizedOffset: CGVector(dx: lessThanHalf, dy: half))
    let rightOfCentre = self.coordinate(withNormalizedOffset: CGVector(dx: moreThanHalf, dy: half))

    switch direction {
    case .up:
        centre.press(forDuration: pressDuration, thenDragTo: aboveCentre)
        break
    case .down:
        centre.press(forDuration: pressDuration, thenDragTo: belowCentre)
        break
    case .left:
        centre.press(forDuration: pressDuration, thenDragTo: leftOfCentre)
        break
    case .right:
        centre.press(forDuration: pressDuration, thenDragTo: rightOfCentre)
        break
    }
}

Call Tips

app.swipeUp(to: "element")

like image 2
N.. Avatar answered Nov 13 '22 16:11

N..