[ios] Delay/Wait in a test case of Xcode UI testing

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?

This question is related to ios ios9 xcode-ui-testing xcode7-beta2 xctwaiter

The answer is


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.


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!


The following code just works with Objective C.

- (void)wait:(NSUInteger)interval {

    XCTestExpectation *expectation = [self expectationWithDescription:@"wait"];
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(interval * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [expectation fulfill];
    });
    [self waitForExpectationsWithTimeout:interval handler:nil];
}

Just make call to this function as given below.

[self wait: 10];

Based on @Ted's answer, I've used this extension:

extension XCTestCase {

    // Based on https://stackoverflow.com/a/33855219
    func waitFor<T>(object: T, timeout: TimeInterval = 5, file: String = #file, line: UInt = #line, expectationPredicate: @escaping (T) -> Bool) {
        let predicate = NSPredicate { obj, _ in
            expectationPredicate(obj as! T)
        }
        expectation(for: predicate, evaluatedWith: object, handler: nil)

        waitForExpectations(timeout: timeout) { error in
            if (error != nil) {
                let message = "Failed to fulful expectation block for \(object) after \(timeout) seconds."
                let location = XCTSourceCodeLocation(filePath: file, lineNumber: line)
                let issue = XCTIssue(type: .assertionFailure, compactDescription: message, detailedDescription: nil, sourceCodeContext: .init(location: location), associatedError: nil, attachments: [])
                self.record(issue)
            }
        }
    }

}

You can use it like this

let element = app.staticTexts["Name of your element"]
waitFor(object: element) { $0.exists }

It also allows for waiting for an element to disappear, or any other property to change (by using the appropriate block)

waitFor(object: element) { !$0.exists } // Wait for it to disappear

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)
}

According to the API for XCUIElement .exists can be used to check if a query exists or not so the following syntax could be useful in some cases!

let app = XCUIApplication()
app.launch()

let label = app.staticTexts["Hello, world!"]
while !label.exists {
    sleep(1)
}

If you are confident that your expectation will be met eventually you could try running this. It should be noted that crashing might be preferable if the wait is too long in which case waitForExpectationsWithTimeout(_,handler:_) from @Joe Masilotti's post should be used.


This will create a delay without putting the thread to sleep or throwing an error on timeout:

let delayExpectation = XCTestExpectation()
delayExpectation.isInverted = true
wait(for: [delayExpectation], timeout: 5)

Because the expectation is inverted, it will timeout quietly.


sleep will block the thread

"No run loop processing occurs while the thread is blocked."

you can use waitForExistence

let app = XCUIApplication()
app.launch()

if let label = app.staticTexts["Hello, world!"] {
label.waitForExistence(timeout: 5)
}

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


In my case sleep created side effect so I used wait

let _ = XCTWaiter.wait(for: [XCTestExpectation(description: "Hello World!")], timeout: 2.0)

Edit:

It actually just occurred to me that in Xcode 7b4, UI testing now has expectationForPredicate:evaluatedWithObject:handler:

Original:

Another way is to spin the run loop for a set amount of time. Really only useful if you know how much (estimated) time you'll need to wait for

Obj-C: [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow: <<time to wait in seconds>>]]

Swift: NSRunLoop.currentRunLoop().runMode(NSDefaultRunLoopMode, beforeDate: NSDate(timeIntervalSinceNow: <<time to wait in seconds>>))

This is not super useful if you need to test some conditions in order to continue your test. To run conditional checks, use a while loop.


Examples related to ios

Adding a UISegmentedControl to UITableView Crop image to specified size and picture location Undefined Symbols error when integrating Apptentive iOS SDK via Cocoapods Keep placeholder text in UITextField on input in IOS Accessing AppDelegate from framework? Autoresize View When SubViews are Added Warp \ bend effect on a UIView? Speech input for visually impaired users without the need to tap the screen make UITableViewCell selectable only while editing Xcode 12, building for iOS Simulator, but linking in object file built for iOS, for architecture arm64

Examples related to ios9

What does the shrink-to-fit viewport meta attribute do? Change status bar text color to light in iOS 9 with Objective-C The resource could not be loaded because the App Transport Security policy requires the use of a secure connection Transport security has blocked a cleartext HTTP How can I add NSAppTransportSecurity to my info.plist file? Delay/Wait in a test case of Xcode UI testing iOS 9 not opening Instagram app with URL SCHEME New warnings in iOS 9: "all bitcode will be dropped" NSURLSession/NSURLConnection HTTP load failed on iOS 9 How do I load an HTTP URL with App Transport Security enabled in iOS 9?

Examples related to xcode-ui-testing

Delay/Wait in a test case of Xcode UI testing

Examples related to xcode7-beta2

Delay/Wait in a test case of Xcode UI testing

Examples related to xctwaiter

Delay/Wait in a test case of Xcode UI testing