Replicate pull to refresh in XCTest UI testing
Asked Answered
E

3

26

I am trying to replicate a pull to refresh on a UITableView using the new Xcode UI testing framework in Xcode 7 (beta 3)

My current approach is dragging from the table to whatever element below the table I can find. This works when there is a fixed item below the table like a UIToolbar or UITabBar I would rather not rely on having a UITabBar or UIToolbar but I can't figure out a way to do the pull to refresh/drag action without using the method in XCUIElement.

func pressForDuration(duration: NSTimeInterval, thenDragToElement otherElement: XCUIElement)

Refresh success

But it fails when I don't have a toolbar/tabbar and try to drag using the cells

Refresh failure

This is the relevant portion of my code:

func testRefresh() {
    //For testing cell
    for _ in 0...9 {
        addCell()
    }
    refreshTable()
}

func refreshTable(var tbl: XCUIElement? = nil) {
    let app = XCUIApplication()

    if tbl == nil {
        let tables = app.tables
        if tables.count > 0 {
            tbl = tables.elementAtIndex(0)
        }
    }

    guard let table = tbl else {
        XCTFail("Cannot find a table to refresh, you can provide on explicitly if you do have a table")
        return
    }

    var topElement = table
    let bottomElement: XCUIElement?

    //Try to drag to a tab bar then to a toolbar then to the last cell
    if app.tabBars.count > 0 {
        bottomElement = app.tabBars.elementAtIndex(0)
    }
    else if app.toolbars.count > 0 {
        bottomElement = app.toolbars.elementAtIndex(0)
    }
    else {
        let cells = app.cells
        if cells.count > 0 {
            topElement = cells.elementAtIndex(0)
            bottomElement = cells.elementAtIndex(cells.count - 1)
        }
        else {
            bottomElement = nil
        }
    }
    if let dragTo = bottomElement {
        topElement.pressForDuration(0.1, thenDragToElement: dragTo)
    }
}

func addCell() {
    let app = XCUIApplication()
    app.navigationBars["Master"].buttons["Add"].tap()
}

Additional failed attempts:

  • swipeDown() (multiples times as well)
  • scrollByDeltaX/deltaY (OS X only)
Explicable answered 8/7, 2015 at 19:20 Comment(1)
For those that have read this far, my answer works in Xcode 7 and Xcode 7.1.Drida
D
23

You can use the XCUICoordinate API to interact with specific points on the screen, not necessarily tied to any particular element.

  1. Grab a reference to the first cell in your table. Then create a coordinate with zero offset, CGVectorMake(0, 0). This will normalize a point right on top of the first cell.
  2. Create an imaginary coordinate farther down the screen. (I've found that a dy of six is the smallest amount needed to trigger the pull-to-refresh gesture.)
  3. Execute the gesture by grabbing the first coordinate and dragging it to the second one.

enter image description here

let firstCell = app.staticTexts["Adrienne"]
let start = firstCell.coordinateWithNormalizedOffset(CGVectorMake(0, 0))
let finish = firstCell.coordinateWithNormalizedOffset(CGVectorMake(0, 6))
start.pressForDuration(0, thenDragToCoordinate: finish)

More information along with a working sample app is also available.

Drida answered 21/9, 2015 at 12:8 Comment(4)
Thanks. For me, it works on simulators but not on a real iPhone 6.Ezarra
Is this working for you in Xcode 9b5? I used this extensively in Xcode 7 and 8, but it appears to be broken now.Scatterbrain
let start = firstCell.coordinate(withNormalizedOffset: CGVector(dx: 0, dy: 0)) let finish = firstCell.coordinate(withNormalizedOffset: CGVector(dx: 0, dy: 6)) start.press(forDuration: 0, thenDragTo: finish) @ChaseHollandCumbrous
Any hint on this one https://mcmap.net/q/537059/-xctest-ui-testing-with-xcuielement-using-pinch-gesture-on-custom-coordinatesSherrilsherrill
N
6

I managed to do such tasks with coordinates like Joe suggested. But i went a step further using coordinateWithOffset()

let startPosition = CGPointMake(200, 300)
let endPosition = CGPointMake(200, 0)
let start = elementToSwipeOn.coordinateWithNormalizedOffset(CGVectorMake(0, 0)).coordinateWithOffset(CGVector(dx: startPosition.x, dy: startPosition.y))
let finish = elementToSwipeOn.coordinateWithNormalizedOffset(CGVector(dx: 0, dy: 0)).coordinateWithOffset(CGVector(dx: endPosition.x, dy: endPosition.y))
start.pressForDuration(0, thenDragToCoordinate: finish)

Now i am able to drag from a specific point to another specific point. I implemented a custom refresh on some of my views. While implementing this, i also discovered that i can even use this to access the control center or the top menu.

Neoclassic answered 7/12, 2015 at 11:43 Comment(2)
Hi Tomte, what is the value of elementToSwipeOn if I want to toggle control center?Jeanne
@BillChan Try to get the height of your main view (or the very last y-point of the bottom) and add 10 or something to it. that means, the swipe will start outside screen. But you can achieve this by simply swiping up. This code is intended to be used for swipe and hold (e.g. for a refresh of a tableview).Neoclassic
K
1

Here is a Swift 5.1 version

    let firstCell = staticTexts["Id"]
    let start = firstCell.coordinate(withNormalizedOffset: CGVector(dx: 0, dy: 0))
    let finish = firstCell.coordinate(withNormalizedOffset: CGVector(dx: 0, dy: 100))
    start.press(forDuration: 0, thenDragTo: finish)
Kkt answered 25/6, 2020 at 7:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.