Delay/Wait in a test case of Xcode UI testing
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)
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)
}
Related videos on Youtube
Tejas HS
Updated on February 03, 2022Comments
-
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 almost 9 yearsI think
NSThread.sleepForTimeInterval(1)
should work -
Tejas HS almost 9 yearsGreat! 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 almost 9 yearsI 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 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 almost 7 yearsSwift 4.0 --> Thread.sleep(forTimeInterval: 2)
-
-
jazzy almost 9 yearsUnfortunately there's no way to accept that the timeout happened and move on -
waitForExpectationsWithTimeout
will automatically fail your test which is quite unfortunate. -
Bastian over 8 years@Jedidja Actually, this does not happen for me with XCode 7.0.1.
-
jazzy over 8 years@Bastian Hmm interesting; I will have to recheck this.
-
Tai Le over 8 yearsSome time we need the way to delay and don't wanna it raise a failure! thanks
-
emoleumassi over 8 yearsit 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 over 8 yearsI 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 over 8 years@GrahamPerks, yes, though there is also:
usleep
-
Chase Holland about 8 yearsI 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 about 8 yearsEven 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 almost 8 yearsThe
app.launch()
seems to just relaunch the app. Is it necessary? -
strongwillow almost 8 yearsThis comes especially useful in UI Testing
-
CouchDeveloper over 7 yearsThis is really a poor suggestion. If you have a couple of those, your integration tests will last for ever.
-
felixwcf over 7 yearsThis 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 over 7 yearsThis 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 over 7 yearsi dont think its necessary Chris, worked for me without it
-
Jesuslg123 about 7 yearsI think this is really bad suggestion, just increase wait for expectation timeout, never sleep the thread....
-
mxcl about 7 yearsIt'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 almost 7 yearsThanks @daidai I changed the text :)
-
MartianMartian over 6 yearsthis approach is simply to complicated, lord, wish i haven't read it
-
rd_ over 6 yearslooking for some more illustration regarding above xcode9 example
-
Ted over 6 years
-
Dawid Koncewicz over 6 yearsTested. Works like a charm! Thanks!
-
blackjacx about 6 yearsYep this is still the approach I am going for when using
XCTestCase
and it works like a charm. I do not understand why approaches likesleep(3)
are voted so high here since it extends the test time artificially and is really no option when your test suite grows. -
blackjacx about 6 yearsI 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 almost 6 yearsActually it requires Xcode 9, but works on devices/simulators running iOS 10 as well ;-)
-
blackjacx almost 6 yearsYeah I wrote that on the headline above. But now most people should have upgraded to at least Xcode 9 ;-)
-
Just a coder over 4 yearsError --> caught "NSInternalInconsistencyException", "API violation - call made to wait without any expectations having been set."
-
Max over 4 years@iOSCalendarpatchthecode.com, Have you found alternate solution for that?
-
Just a coder over 4 years@Max can you use any of the others on this page?
-
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 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 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 over 3 yearsMy 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 over 2 yearsFor Xcode 13.1 just change line: UInt = #line => line: Int = #line. There are compiler erros that basically require this fix
-
Mishka about 2 yearsNice catch @SevenDays, lineNumber must be an Int now.