222

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?

yoAlex5
  • 29,217
  • 8
  • 193
  • 205
Tejas HS
  • 2,565
  • 2
  • 16
  • 13
  • 14
    I think `NSThread.sleepForTimeInterval(1)` should work – Kametrixom Jul 02 '15 at 11:04
  • 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 – Tejas HS Jul 02 '15 at 11:55
  • 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) – Kametrixom Jul 02 '15 at 13:49
  • @Kametrixom Don't tick the run loop - Apple introduced native asynchronous testing in Beta 4. See [my answer](http://stackoverflow.com/a/32228104/384110) for details. – Joe Masilotti Aug 26 '15 at 13:34
  • 2
    Swift 4.0 --> Thread.sleep(forTimeInterval: 2) – uplearned.com Sep 28 '17 at 05:10

14 Answers14

253

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.

mxcl
  • 26,392
  • 12
  • 99
  • 98
  • 2
    Some time we need the way to delay and don't wanna it raise a failure! thanks – Tai Le Dec 15 '15 at 10:32
  • 11
    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). – Graham Perks Feb 18 '16 at 21:27
  • 5
    @GrahamPerks, yes, though there is also: `usleep` – mxcl Feb 18 '16 at 22:33
  • 2
    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. – Chase Holland Apr 04 '16 at 18:50
  • 2
    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. – ChidG Jun 10 '16 at 03:24
  • This comes especially useful in UI Testing – strongwillow Aug 22 '16 at 00:57
  • 2
    This is really a poor suggestion. If you have a couple of those, your integration tests will last for ever. – CouchDeveloper Oct 21 '16 at 11:15
  • 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) – Michael Jan 20 '17 at 18:24
  • 1
    I think this is really bad suggestion, just increase wait for expectation timeout, never sleep the thread.... – Jesuslg123 May 31 '17 at 08:01
  • 3
    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. – mxcl May 31 '17 at 10:17
  • 1
    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 (https://stackoverflow.com/a/45987987/971329) which works like a charm. – blackjacx May 23 '18 at 12:34
191

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.

Joe Masilotti
  • 16,815
  • 6
  • 77
  • 87
101

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!

blackjacx
  • 9,011
  • 7
  • 45
  • 56
  • Thanks @daidai I changed the text :) – blackjacx Sep 21 '17 at 09:49
  • 2
    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 May 23 '18 at 12:31
  • Actually it requires Xcode 9, but works on devices/simulators running iOS 10 as well ;-) – d4Rk Sep 13 '18 at 08:12
  • Yeah I wrote that on the headline above. But now most people should have upgraded to at least Xcode 9 ;-) – blackjacx Sep 13 '18 at 08:55
  • 9
    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. – mxcl Dec 01 '20 at 14:40
79

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

Ted
  • 22,696
  • 11
  • 95
  • 109
37

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)
}
onmyway133
  • 45,645
  • 31
  • 257
  • 263
19

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.

Ken Murphy
  • 191
  • 1
  • 2
14

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
Inti
  • 3,443
  • 1
  • 29
  • 34
Ben Lings
  • 28,823
  • 13
  • 72
  • 81
  • +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. – lawicko Nov 20 '19 at 10:39
  • 1
    For Xcode 13.1 just change line: UInt = #line => line: Int = #line. There are compiler erros that basically require this fix – SevenDays Nov 20 '21 at 21:48
  • 1
    Nice catch @SevenDays, lineNumber must be an Int now. – Mishka Apr 20 '22 at 01:43
12

Xcode testing Wait

In my case sleep created a side effect so I used wait

let _ = XCTWaiter.wait(for: [XCTestExpectation(description: "Hello World!")], timeout: 2.0)
yoAlex5
  • 29,217
  • 8
  • 193
  • 205
10

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.

enmiller
  • 101
  • 6
  • 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. – felixwcf Nov 08 '16 at 09:13
8

How we're doing it at my current company is we create an XCUIElement expression expectation (to create a versatile wait method). We do it the following way to make sure it's maintainable (lots of expectation variety, and don't want to create lots of methods/specific predicates to do so.

Swift 5

Base method

The expression is used to form a dynamic predicate value. We can create XCTNSPredicateExpectation's from predicates, which we then pass into XCTWaiter to explicitly wait. If the result was anything other than completed, then we fail with an optional message.

@discardableResult
func wait(
    until expression: @escaping (XCUIElement) -> Bool,
    timeout: TimeInterval = 15,
    message: @autoclosure () -> String = "",
    file: StaticString = #file,
    line: UInt = #line
) -> Self {
    if expression(self) {
        return self
    }

    let predicate = NSPredicate { _, _ in
        expression(self)
    }

    let expectation = XCTNSPredicateExpectation(predicate: predicate, object: nil)

    let result = XCTWaiter().wait(for: [expectation], timeout: timeout)

    if result != .completed {
        XCTFail(
            message().isEmpty ? "expectation not matched after waiting" : message(),
            file: file,
            line: line
        )
    }

    return self
}

Usage

app.buttons["my_button"].wait(until: { $0.exists })
app.buttons["my_button"].wait(until: { $0.isHittable })

Keypaths

Then we wrap that in a method where a keyPath and match value form the expression.

@discardableResult
func wait<Value: Equatable>(
    until keyPath: KeyPath<XCUIElement, Value>,
    matches match: Value,
    timeout: TimeInterval = 15,
    message: @autoclosure () -> String = "",
    file: StaticString = #file,
    line: UInt = #line
) -> Self {
    wait(
        until: { $0[keyPath: keyPath] == match },
        timeout: timeout,
        message: message,
        file: file,
        line: line
    )
}

Usage

app.buttons["my_button"].wait(until: \.exists, matches: true)
app.buttons["my_button"].wait(until: \.isHittable, matches: false)

Then you can wrap that method, where the match value is always true for a use case I found most common.

Usage

app.buttons["my_button"].wait(until: \.exists)
app.buttons["my_button"].wait(until: \.isHittable)

I wrote a post about it, and get the full extension file there too: https://sourcediving.com/clean-waiting-in-xcuitest-43bab495230f

Ryan
  • 145
  • 1
  • 6
4

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];
arango_86
  • 4,236
  • 4
  • 40
  • 46
2

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)
}
zdravko zdravkin
  • 2,090
  • 19
  • 21
1
   let app = XCUIApplication()
    app.launch()

     //Find the button in the UI 
    let SettingsButton =
        app.navigationBars["HomeView"].buttons["Settings"]
    XCTAssertTrue(settingButton.waitForExistence(timeout: 10))
Sateesh Pasala
  • 772
  • 8
  • 15
0

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.

Reid
  • 734
  • 8
  • 15