In XCUITests, how to wait for existence of either of two ui elements In XCUITests, how to wait for existence of either of two ui elements xcode xcode

In XCUITests, how to wait for existence of either of two ui elements


Inspired by http://masilotti.com/ui-testing-tdd/, you don't have to rely on XCTWaiter. You can simply run a loop and test whether one of them exists.

/// Waits for either of the two elements to exist (i.e. for scenarios where you might have/// conditional UI logic and aren't sure which will show)////// - Parameters:///   - elementA: The first element to check for///   - elementB: The second, or fallback, element to check for/// - Returns: the element that existed@discardableResultfunc waitForEitherElementToExist(_ elementA: XCUIElement, _ elementB: XCUIElement) -> XCUIElement? {    let startTime = NSDate.timeIntervalSinceReferenceDate    while (!elementA.exists && !elementB.exists) { // while neither element exists        if (NSDate.timeIntervalSinceReferenceDate - startTime > 5.0) {            XCTFail("Timed out waiting for either element to exist.")            break        }        sleep(1)    }    if elementA.exists { return elementA }    if elementB.exists { return elementB }    return nil}

then you could just do:

let foundElement = waitForEitherElementToExist(elementA, elementB)if foundElement == elementA {    // e.g. if it's a button, tap it} else {    // element B was found}


lagoman's answer is absolutely correct and great. I needed wait on more than 2 possible elements though, so I tweaked his code to support an Array of XCUIElement instead of just two.

@discardableResultfunc waitForAnyElement(_ elements: [XCUIElement], timeout: TimeInterval) -> XCUIElement? {    var returnValue: XCUIElement?    let startTime = Date()        while Date().timeIntervalSince(startTime) < timeout {        if let elementFound = elements.first(where: { $0.exists }) {            returnValue = elementFound            break        }        sleep(1)    }    return returnValue}

which can be used like

let element1 = app.tabBars.buttons["Home"]let element2 = app.buttons["Submit"]let element3 = app.staticTexts["Greetings"]foundElement = waitForAnyElement([element1, element2, element3], timeout: 5)// do whatever checks you may wantif foundElement == element1 {     // code}


NSPredicate supports OR predicates too.

For example I wrote something like this to ensure my application is fully finished launching before I start trying to interact with it in UI tests. This is checking for the existence of various landmarks in the app that I know are uniquely present on each of the possible starting states after launch.

extension XCTestCase {  func waitForLaunchToFinish(app: XCUIApplication) {    let loginScreenPredicate = NSPredicate { _, _ in      app.logInButton.exists    }    let tabBarPredicate = NSPredicate { _, _ in      app.tabBar.exists    }    let helpButtonPredicate = NSPredicate { _, _ in      app.helpButton.exists    }    let predicate = NSCompoundPredicate(      orPredicateWithSubpredicates: [        loginScreenPredicate,        tabBarPredicate,        helpButtonPredicate,      ]    )    let finishedLaunchingExpectation = expectation(for: predicate, evaluatedWith: nil, handler: nil)    wait(for: [finishedLaunchingExpectation], timeout: 30)  }}

In the console while the test is running there's a series of repeated checks for the existence of the various buttons I want to check for, with a variable amount of time between each check.

t = 13.76s Wait for com.myapp.name to idle

t = 18.15s Checking existence of "My Tab Bar" Button

t = 18.88s Checking existence of "Help" Button

t = 20.98s Checking existence of "Log In" Button

t = 22.99s Checking existence of "My Tab Bar" Button

t = 23.39s Checking existence of "Help" Button

t = 26.05s Checking existence of "Log In" Button

t = 32.51s Checking existence of "My Tab Bar" Button

t = 16.49s Checking existence of "Log In" Button

And voila, now instead of waiting for each element individually I can do it concurrently.

This is very flexible of course, since you can add as many elements as you want, with whatever conditions you want. And if you want a combination of OR and AND predicates you can do that too with NSCompoundPredicate. This can easily be adapted into a more generic function that accepts an array of elements like so:

func wait(for elements: XCUIElement...) { … }

Could even pass a parameter that controls whether it uses OR or AND.