Delay/Wait in a test case of Xcode UI testing

124,079

Solution 1

Asynchronous UI Testing was introduced in Xcode 7 Beta 4. To wait for a label with the text "Hello, world!" to appear you can do the following:

let app = XCUIApplication()
app.launch()

let label = app.staticTexts["Hello, world!"]
let exists = NSPredicate(format: "exists == 1")

expectationForPredicate(exists, evaluatedWithObject: label, handler: nil)
waitForExpectationsWithTimeout(5, handler: nil)

More details about UI Testing can be found on my blog.

Solution 2

Additionally, you can just sleep:

sleep(10)

Since the UITests run in another process, this works. I don’t know how advisable it is, but it works.

Solution 3

iOS 11 / Xcode 9

<#yourElement#>.waitForExistence(timeout: 5)

This is a great replacement for all the custom implementations on this site!

Be sure to have a look at my answer here: https://stackoverflow.com/a/48937714/971329. There I describe an alternative to waiting for requests which will greatly reduce the time your tests are running!

Solution 4

Xcode 9 introduced new tricks with XCTWaiter

Test case waits explicitly

wait(for: [documentExpectation], timeout: 10)

Waiter instance delegates to test

XCTWaiter(delegate: self).wait(for: [documentExpectation], timeout: 10)

Waiter class returns result

let result = XCTWaiter.wait(for: [documentExpectation], timeout: 10)
switch(result) {
case .completed:
    //all expectations were fulfilled before timeout!
case .timedOut:
    //timed out before all of its expectations were fulfilled
case .incorrectOrder:
    //expectations were not fulfilled in the required order
case .invertedFulfillment:
    //an inverted expectation was fulfilled
case .interrupted:
    //waiter was interrupted before completed or timedOut
}

sample usage

Before Xcode 9

Objective C

- (void)waitForElementToAppear:(XCUIElement *)element withTimeout:(NSTimeInterval)timeout
{
    NSUInteger line = __LINE__;
    NSString *file = [NSString stringWithUTF8String:__FILE__];
    NSPredicate *existsPredicate = [NSPredicate predicateWithFormat:@"exists == true"];

    [self expectationForPredicate:existsPredicate evaluatedWithObject:element handler:nil];

    [self waitForExpectationsWithTimeout:timeout handler:^(NSError * _Nullable error) {
        if (error != nil) {
            NSString *message = [NSString stringWithFormat:@"Failed to find %@ after %f seconds",element,timeout];
            [self recordFailureWithDescription:message inFile:file atLine:line expected:YES];
        }
    }];
}

USAGE

XCUIElement *element = app.staticTexts["Name of your element"];
[self waitForElementToAppear:element withTimeout:5];

Swift

func waitForElementToAppear(element: XCUIElement, timeout: NSTimeInterval = 5,  file: String = #file, line: UInt = #line) {
        let existsPredicate = NSPredicate(format: "exists == true")

        expectationForPredicate(existsPredicate,
                evaluatedWithObject: element, handler: nil)

        waitForExpectationsWithTimeout(timeout) { (error) -> Void in
            if (error != nil) {
                let message = "Failed to find \(element) after \(timeout) seconds."
                self.recordFailureWithDescription(message, inFile: file, atLine: line, expected: true)
            }
        }
    }

USAGE

let element = app.staticTexts["Name of your element"]
self.waitForElementToAppear(element)

or

let element = app.staticTexts["Name of your element"]
self.waitForElementToAppear(element, timeout: 10)

SOURCE

Solution 5

As of Xcode 8.3, we can use XCTWaiter http://masilotti.com/xctest-waiting/

func waitForElementToAppear(_ element: XCUIElement) -> Bool {
    let predicate = NSPredicate(format: "exists == true")
    let expectation = expectation(for: predicate, evaluatedWith: element, 
                                  handler: nil)

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

Another trick is to write a wait function, credit goes to John Sundell for showing it to me

extension XCTestCase {

  func wait(for duration: TimeInterval) {
    let waitExpectation = expectation(description: "Waiting")

    let when = DispatchTime.now() + duration
    DispatchQueue.main.asyncAfter(deadline: when) {
      waitExpectation.fulfill()
    }

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

and use it like

func testOpenLink() {
  let delegate = UIApplication.shared.delegate as! AppDelegate
  let route = RouteMock()
  UIApplication.shared.open(linkUrl, options: [:], completionHandler: nil)

  wait(for: 1)

  XCTAssertNotNil(route.location)
}
Share:
124,079

Related videos on Youtube

Tejas HS
Author by

Tejas HS

Updated on February 03, 2022

Comments

  • Tejas HS
    Tejas HS over 2 years

    I am trying to write a test case using the new UI Testing available in Xcode 7 beta 2. The App has a login screen where it makes a call to the server to login. There is a delay associated with this as it is an asynchronous operation.

    Is there a way to cause a delay or wait mechanism in the XCTestCase before proceeding to further steps?

    There is no proper documentation available and I went through the Header files of the classes. Was not able to find anything related to this.

    Any ideas/suggestions?

    • Kametrixom
      Kametrixom almost 9 years
      I think NSThread.sleepForTimeInterval(1) should work
    • Tejas HS
      Tejas HS almost 9 years
      Great! This looks like it works. But I'm not sure if it's the recommended way to do it. I think Apple should give a better way to do it. Might have to file a Radar
    • Kametrixom
      Kametrixom almost 9 years
      I actually really think that's okay, it's really the most common way to pause the current thread for a certain time. If you want more control you can also get into GCD (The dispatch_after, dispatch_queue stuff)
    • Joe Masilotti
      Joe Masilotti almost 9 years
      @Kametrixom Don't tick the run loop - Apple introduced native asynchronous testing in Beta 4. See my answer for details.
    • uplearned.com
      uplearned.com almost 7 years
      Swift 4.0 --> Thread.sleep(forTimeInterval: 2)
  • jazzy
    jazzy almost 9 years
    Unfortunately there's no way to accept that the timeout happened and move on - waitForExpectationsWithTimeout will automatically fail your test which is quite unfortunate.
  • Bastian
    Bastian over 8 years
    @Jedidja Actually, this does not happen for me with XCode 7.0.1.
  • jazzy
    jazzy over 8 years
    @Bastian Hmm interesting; I will have to recheck this.
  • Tai Le
    Tai Le over 8 years
    Some time we need the way to delay and don't wanna it raise a failure! thanks
  • emoleumassi
    emoleumassi over 8 years
    it doesn't work for me. Here is my sample: let xButton = app.toolbars.buttons["X"] let exists = NSPredicate(format: "exists == 1") expectationForPredicate(exists, evaluatedWithObject: xButton, handler: nil) waitForExpectationsWithTimeout(10, handler: nil)
  • Graham Perks
    Graham Perks over 8 years
    I like NSThread.sleepForTimeInterval(0.2) as you can specify sub-second delays. (sleep() takes an integer parameter; only multiples of a second are possible).
  • mxcl
    mxcl over 8 years
    @GrahamPerks, yes, though there is also: usleep
  • Chase Holland
    Chase Holland about 8 years
    I wish there was a better answer, but this seems to be the only way right now if you don't want to cause a failure.
  • ChidG
    ChidG about 8 years
    Even using Joe Masilotti's solution, I still had some weird failures - I suspect XCode/UITest bugs - when trying to test webViews. Adding some sleep() calls fixed that.
  • Chris Prince
    Chris Prince almost 8 years
    The app.launch() seems to just relaunch the app. Is it necessary?
  • strongwillow
    strongwillow almost 8 years
    This comes especially useful in UI Testing
  • CouchDeveloper
    CouchDeveloper over 7 years
    This is really a poor suggestion. If you have a couple of those, your integration tests will last for ever.
  • felixwcf
    felixwcf over 7 years
    This is clean and very useful for me especially e.g waiting for app launch, request preloaded data and do login / logout stuffs. Thank you.
  • Michael
    Michael over 7 years
    This is appropriate in circumstances where you really need to wait a short period, but its incredibly inconvenient to fulfill an expectation (or would otherwise involve modifying the source in an ugly way)
  • mourodrigo
    mourodrigo over 7 years
    i dont think its necessary Chris, worked for me without it
  • Jesuslg123
    Jesuslg123 about 7 years
    I think this is really bad suggestion, just increase wait for expectation timeout, never sleep the thread....
  • mxcl
    mxcl about 7 years
    It's not a poor suggestion (you don’t get how UITesting works), but even if it is was a poor suggestion sometimes there is no way to craft an expectation that works (system alerts anyone?) so this is all you have.
  • blackjacx
    blackjacx almost 7 years
    Thanks @daidai I changed the text :)
  • MartianMartian
    MartianMartian over 6 years
    this approach is simply to complicated, lord, wish i haven't read it
  • rd_
    rd_ over 6 years
    looking for some more illustration regarding above xcode9 example
  • Ted
    Ted over 6 years
  • Dawid Koncewicz
    Dawid Koncewicz over 6 years
    Tested. Works like a charm! Thanks!
  • blackjacx
    blackjacx about 6 years
    Yep this is still the approach I am going for when using XCTestCase and it works like a charm. I do not understand why approaches like sleep(3) are voted so high here since it extends the test time artificially and is really no option when your test suite grows.
  • blackjacx
    blackjacx about 6 years
    I do not understand why this approach is voted so high here since it extend the test time artificially and is really no option when your test suite grows.It prevents running your test suite often - and this is what we all should do! I still go with my approach below (stackoverflow.com/a/45987987/971329) which works like a charm.
  • d4Rk
    d4Rk almost 6 years
    Actually it requires Xcode 9, but works on devices/simulators running iOS 10 as well ;-)
  • blackjacx
    blackjacx almost 6 years
    Yeah I wrote that on the headline above. But now most people should have upgraded to at least Xcode 9 ;-)
  • Just a coder
    Just a coder over 4 years
    Error --> caught "NSInternalInconsistencyException", "API violation - call made to wait without any expectations having been set."
  • Max
    Max over 4 years
    @iOSCalendarpatchthecode.com, Have you found alternate solution for that?
  • Just a coder
    Just a coder over 4 years
    @Max can you use any of the others on this page?
  • Max
    Max over 4 years
    @iOSCalendarpatchthecode.com No, I just need some delay without any element to check. So I need alternate of this.
  • Just a coder
    Just a coder over 4 years
    @Max i used the selected answer on this page. It worked for me. Maybe you can ask them what specifically you are looking for.
  • lawicko
    lawicko over 4 years
    +1 very swifty, and it uses the block predicate which I think is a lot better because the standard predicate expressions didn't work for me sometimes, for example when waiting for some properties on XCUIElements etc.
  • mxcl
    mxcl over 3 years
    My answer is voted that high because I wrote it NINE years ago at the time it was the only option. And it still is the only option in some cases. You don’t always have an element to wait for the existence of. I gave you an upvote, I hope this cools your temper a little.
  • SevenDays
    SevenDays over 2 years
    For Xcode 13.1 just change line: UInt = #line => line: Int = #line. There are compiler erros that basically require this fix
  • Mishka
    Mishka about 2 years
    Nice catch @SevenDays, lineNumber must be an Int now.