Looking at XCTWaiter().wait(...) I believe we can wait for multiple expectations to become true using this code
let notHittablePredicate = NSPredicate(format: "hittable == false")
let myExpectation = XCTNSPredicateExpectation(predicate: notHittablePredicate, object: element)
let result = XCTWaiter().wait(for: [myExpectation], timeout: timeout)
//for takes array of expectations
But this uses like AND among the supplied expectations. Is there a way to do OR among the supplied expectations.
Like i have a use case at login that after tapping submit, i want to wait for one of two elements. First element is "You are already logged in on another device. If you continue any unsaved data on your other device will be lost?". And second element is the main screen after login. So any one can appear. Currently I'm first waiting for first element until timeout occurs and then for the second element. But I want to optimize time here and move on as soon as any of two elements exist==true. Then i'll check if element1 exists then tap YES and then wait for main screen otherwise just assert existence of element2.
Please comment if something isn't clear in the question. Thanks
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
@discardableResult
func 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.
@discardableResult
func 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 want
if 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With