Wait 30 sec in Xcode UI Test - ios

At a point many web service call is get called. and I just want to wait 30sec, as I know all will finish during this time period. I need a first rough solution.
I tried this, but it raise some error:
tablesQuery.buttons["Button"].tap()
DispatchQueue.main.asyncAfter(deadline: .now() + 30.0) {
let tablesQuery2 = tablesQuery
Do you have any idea?

The simplest way is just sleeping the execution for the time being:
sleep(30)
However, in case you are expecting something to appear, it's better to use the built-in function for waiting for existence:
element.waitForExistence(30)
It doesn't fail if nothing appears, so if it's crucial part of your logic, you probably better to check it via expectation with timer:
let exists = NSPredicate(format: "exists == %#", true)
expectationForPredicate(exists, evaluatedWithObject: element, handler: nil)
waitForExpectationsWithTimeout(30, handler: nil)

Related

In Xcode UI Test, how can I repeatedly check if an element exists and if so perform an action?

I’m implementing UI tests. The app makes API calls that could make alerts ( it's a UIView attached to the window ) appear. Of course, these are random/not predictable. If they show up, I have to dismiss them (clicking on the close button). Any idea how to do this? Do I have some event that says that something happened on the UI? I was thinking to have a thread that executes every 0.5 seconds that checks if the dismiss button exists and if so I tap on it.
DispatchQueue.global().async {
while true
{
DispatchQueue.main.async {
if(self.app.buttons["NotificationCloseButton"].exists)
{
self.app.buttons["NotificationCloseButton"].tap()
}
}
sleep(5)
}
}
The problem with this is that it causes random crashes: Neither attributes nor error returned
There is nice example of how to wait for element to appear on screen here. Here is example of code taken from the link:
let nextGame = self.app.staticTexts["Game 4 - Tomorrow"]
let exists = NSPredicate(format: "exists == true")
expectation(for: exists, evaluatedWithObject: nextGame, handler: nil)
app.buttons["Load More Games"].tap()
waitForExpectations(timeout: 5, handler: nil)
XCTAssert(nextGameLabel.exists)
Link also provides how to wait for system alert to appear:
addUIInterruptionMonitor(withDescription: "Location Dialog") { (alert) -> Bool in
alert.buttons["Allow"].tap()
return true
}
app.buttons["Find Games Nearby?"].tap()
app.tap() // need to interact with the app for the handler to fire
XCTAssert(app.staticTexts["Authorized"].exists)

GCD-based login timeout sometimes fires at next login attempt

I had some trouble formulating the Question title, please make an edit if you have a better alternative.
I have a Login procedure to which I add a timeout:
private func startTimeout() {
self.timeoutActive = true
DispatchQueue.main.asyncAfter(deadline: .now() + 20) {
[weak self] in
guard let weakSelf = self else {
return
}
if weakSelf.timeoutActive && !weakSelf.loggedIn {
weakSelf.onError("Login timed out")
}
}
}
I have designed the Login procedure so that if we for any reason need to log in again (for example after logging out, or after the login process determined that there missing or wrong credentials), we end up in the same instance of the class that performs the Login.
Now, as far as I see, we can never prevent a scheduled block from executing, only prevent it by using some flags, which is what I have done with the timeoutActive flag. This works like a charm.
However, I run into a problem if the second Login is timed exactly so that the Previous dispatch block executes after the new Login process is started (When the new login procedure is initiated, the timeoutActive flag is once again set to true). The new Login receives a Timeout that is not correct.
I have been thinking about different ways to solve it, and tried a few, but couldn't get any of them to work.
I had an idea to use performSelectorAfterDelay instead of GCD, which is cancellable, but not available in Swift (3).
I also played with the thought of having some unique block ID with a list of blocked block IDs - but it just seems overkill.
I also had an idea about comparing the current dispatch time (.now()) in the block with the original deadline (.now() + 20) and see if it matches, but I don't know how exact this deadline is and it feels unstable.
The only idea I'm left with is making some kind of Task like object around the Login procedure itself, with the timeout included and create a new instance of that task for different Logins. Seems like a bit of work and I'd prefer if I found an easier way.
Has anyone encountered this type of situation before and have a solution?
So this is what I made from Dan's comment on the Question:
private var timeoutBlock: DispatchWorkItem? = nil
private func startTimeout() {
self.timeoutBlock = DispatchWorkItem(block: { [weak self] in
guard let weakSelf = self else {
return
}
if !weakSelf.loggedIn {
weakSelf.onError("Login timed out")
}
})
DispatchQueue.main.asyncAfter(wallDeadline: .now() + 20, execute: self.timeoutBlock!)
}
And using self.timeoutBlock?.cancel() when leaving the ViewController or completing the process. This works as expected!

XCTest is not giving me accurate results each time (Sometimes it fails, sometimes it passes)

I am using XCode 8.2.1 (Swift) with XCode UI tests to test my app in terms of UI, so my problem with this tool is it doesn't give me accurate results some tests are passed and at the next time they fail.
My question might be generic but is this problem related to me or is it common one? i believe this happened after migrating to XCode 7+
Do you guys recommend using anther tool for UI tests other than this one? as am looking for faster, accurate and ability to integrate with a CI server.
private func waitForElementToAppear(element: XCUIElement,
file: String = #file, line: UInt = #line) {
let existsPredicate = NSPredicate(format: "exists == true")
expectation(for: existsPredicate,
evaluatedWith: element, handler: nil)
waitForExpectations(timeout: 30) { (error) -> Void in
if (error != nil) {
let message = "Failed to find \(element) after 30 seconds."
self.recordFailure(withDescription: message,
inFile: file, atLine: line, expected: true)
}
}
}
invoke this method and avoid test fails because sometimes our UI takes more time to getting result from Server.

Wait for all HTTP requests to finish in XCode UI tests?

Is there a way to wait for all network requests to finish when testing UI in XCode?
I have an app that sends HTTP requests to get some data from a server, and, in UI tests, I'd like to wait for this data to be retrieved before continuing. Currently I'm using sleep(1) but this approach doesn't seem reliable.
Your best bet is to wait for some UI element to appear or disappear. Think of it this way:
The framework acts like a user. It doesn't care what code is running under the hood. The only thing that matters is what is visible on the screen.
That said, here is how you can wait for a label titled "Go!" to appear in your UI Tests.
let app = XCUIApplication()
let goLabel = self.app.staticTexts["Go!"]
XCTAssertFalse(goLabel.exists)
let exists = NSPredicate(format: "exists == true")
expectationForPredicate(exists, evaluatedWithObject: goLabel, handler: nil)
app.buttons["Ready, set..."].tap()
waitForExpectationsWithTimeout(5, handler: nil)
XCTAssert(goLabel.exists)
You could also extract that into a helper method. If you use some Swift compiler magic you can even get the failure message to occur on the line that called the method.
private fund waitForElementToAppear(element: XCUIElement, file: String = #file, line: UInt = #line) {
let existsPredicate = NSPredicate(format: "exists == true")
expectationForPredicate(existsPredicate, evaluatedWithObject: element, handler: nil)
waitForExpectationsWithTimeout(5) { (error) -> Void in
if (error != nil) {
let message = "Failed to find \(element) after 5 seconds."
self.recordFailureWithDescription(message, inFile: file, atLine: line, expected: true)
}
}
}
You can set up your methods with delegates or completion blocks, and in your test cases use a XCTestExpectation which you can fulfill when the data has been returned.

How to check for the presence of static text displayed from the network in UI tests in Xcode?

I am using the UI test APIs introduced in Xcode 7 XCTest. On my screen I have a text that is loaded from the network.
The test fails if I simply check it with exists property.
XCTAssert(app.staticTexts["Text from the network"].exists) // fails
It does work though if I first send the tap or any other event to the text like this:
app.staticTexts["Text from the network"].tap()
XCTAssert(app.staticTexts["Text from the network"].exists) // works
It looks like if I just call exists it evaluates it immediately and fails because the text has not been downloaded from the network yet. But I think when I call the tap() method it waits for the text to appear.
Is there a better way to check for the presence of a text that is delivered from the network?
Something like (this code will not work):
XCTAssert(app.staticTexts["Text from the network"].eventuallyExists)
Xcode 7 Beta 4 added native support for asynchronous events. Here's a quick example of how to wait for a UILabel to appear.
XCUIElement *label = self.app.staticTexts[#"Hello, world!"];
NSPredicate *exists = [NSPredicate predicateWithFormat:#"exists == 1"];
[self expectationForPredicate:exists evaluatedWithObject:label handler:nil];
[self waitForExpectationsWithTimeout:5 handler:nil];
First create a query to wait for a label with text "Hello, world!" to appear. The predicate matches when the element exists (element.exists == YES). Then pass the predicate in and evaluate it against the label.
If five seconds pass before the expectation is met then the test will fail. You can also attach a handler block in that gets called when the expectation fails or times out.
If you're looking for more information regarding UI Testing in general, check out UI Testing in Xcode 7.
Swift 3:
let predicate = NSPredicate(format: "exists == 1")
let query = app!.staticTexts["identifier"]
expectation(for: predicate, evaluatedWith: query, handler: nil)
waitForExpectations(timeout: 5, handler: nil)
It will continuously check for 5 seconds whether that text is displayed or not.
As soon as it will find the text may be in less than 5 seconds, it will execute further code.
XCode9 has a method waitForExistence(timeout: TimeInterval) of XCUIElement
extension XCUIElement {
// A method for tap element
#discardableResult
func waitAndTap() -> Bool {
let _ = self.waitForExistence(timeout: 10)
let b = self.exists && self.isHittable
if (b) {
self.tap()
}
return b
}
}
// Ex:
if (btnConfig.waitAndTap() == true) {
// Continue UI automation
} else {
// `btnConfig` is not exist or not hittable.
}
But I encounter another problem, the element is exist, but not hittable. So I extend a method to wait an element to be hittable.
extension XCTestCase {
/// Wait for XCUIElement is hittable.
func waitHittable(element: XCUIElement, timeout: TimeInterval = 30) {
let predicate = NSPredicate(format: "isHittable == 1")
expectation(for: predicate, evaluatedWith: element, handler: nil)
waitForExpectations(timeout: timeout, handler: nil)
}
}
// Ex:
// waitHittable(element: btnConfig)
If I'm right in understanding you that the target text has already displayed when you're checking it's existing, you can try to use hittable property.

Resources