Swift2 UI Test - Wait for Element to Appear
Asked Answered
M

6

16

I want to have a test pause and wait for an element to appear on screen before proceeding.

I don't see a good way to create an expectation for this and wait using

public func waitForExpectationsWithTimeout(timeout: NSTimeInterval, handler: XCWaitCompletionHandler?)

The method to create an expectation I have been using has been

public func expectationForPredicate(predicate: NSPredicate, evaluatedWithObject object: AnyObject, handler: XCPredicateExpectationHandler?) -> XCTestExpectation

But this takes an already existing element, whereas I would like to have the test wait for an element that does not yet exist.

Does anyone know the best way to do this?

Moretta answered 2/12, 2015 at 16:29 Comment(0)
C
21

In expectationForPredicate(predicate: evaluatedWithObject: handler:) you don't give an actual object, but a query to find it in the view hierarchy. So, for example, this is a valid test:

let predicate = NSPredicate(format: "exists == 1")
let query = XCUIApplication().buttons["Button"]
expectationForPredicate(predicate, evaluatedWithObject: query, handler: nil)

waitForExpectationsWithTimeout(3, handler: nil)

Check out UI Testing Cheat Sheet and documentation generated from the headers (there is no official documentation at the moment), all by Joe Masilotti.

Component answered 2/12, 2015 at 17:0 Comment(3)
I could have sworn I tried this in an earlier version of the UI tests and it didn't work, oh well, thanksMoretta
As of Xcode 8.3 (still beta at the moment) there is a better way to do this - XCTestWaiter. I will update my answer when release rolls out. Once again thanks to Joe Masilotti for a great resource in the topic masilotti.com/xctest-waitingQatar
I decided to not update current answer, but posted a new one instead to keep the old one for historical reasons. Please, consider marking https://mcmap.net/q/717638/-swift2-ui-test-wait-for-element-to-appear as an accepted answerQatar
B
11

This question was asked about Swift2 but it's still a top search result in 2019 so I wanted to give an up-to-date answer.

With Xcode 9.0+ things are a bit simpler thanks to waitForExistence:

let app = XCUIApplication()
let myButton = app.buttons["My Button"]
XCTAssertTrue(myButton.waitForExistence(timeout: 10))
sleep(1)
myButton.tap()

WebViews Example:

let app = XCUIApplication()
let webViewsQuery = app.webViews
let myButton = webViewsQuery.staticTexts["My Button"]
XCTAssertTrue(myButton.waitForExistence(timeout: 10))
sleep(1)
myButton.tap()
Bookstand answered 5/7, 2019 at 22:23 Comment(1)
Why sleeping, though?Pharsalus
E
5

You can use this, in Swift 3

func wait(element: XCUIElement, duration: TimeInterval) {
  let predicate = NSPredicate(format: "exists == true")
  let _ = expectation(for: predicate, evaluatedWith: element, handler: nil)

  // We use a buffer here to avoid flakiness with Timer on CI
  waitForExpectations(timeout: duration + 0.5)
}

In Xcode 9, iOS 11, you can use the new API waitForExistence

Explain answered 31/5, 2017 at 8:12 Comment(0)
F
1

It doesn't take an already existing element. You just need to define the following predicate:

let exists = NSPredicate(format: "exists = 1")

Then just use this predicate in your expectation. Then of course wait on your expectation.

Frig answered 2/12, 2015 at 16:47 Comment(1)
I have an example in Objective-C, not in swift. I can include it if you need it.Frig
C
1

For Xcode 8.3 and later you can wait for expectation with a new class - XCTWaiter, an example test looks like this:

func testExample() {
  let element = // ...
  let predicate = NSPredicate(format: "exists == true")
  let expectation = XCTNSPredicateExpectation(predicate: predicate, object: element)

  let result = XCTWaiter().wait(for: [expectation], timeout: 1)
  XCTAssertEqual(.completed, result)
}

Read the documentation for more information.

Component answered 8/11, 2017 at 14:4 Comment(0)
B
0

Based on onmyway133 code I came up with to extensions (Swift 3.2):

extension XCTestCase {
  func wait(for element: XCUIElement, timeout: TimeInterval) {
    let p = NSPredicate(format: "exists == true")
    let e = expectation(for: p, evaluatedWith: element, handler: nil)
    wait(for: [e], timeout: timeout)
  }
}

extension XCUIApplication {
  func getElement(withIdentifier identifier: String) -> XCUIElement {
    return otherElements[identifier]
  }
}

So on your call site you can use:

wait(for: app.getElement(withIdentifier: "ViewController"), timeout: 10)
Balladeer answered 20/12, 2017 at 1:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.