I am running a XCUI test on a view controller. Every time the view is initialize, an api call is made and an MBProgressHud is shown. My test demands to search for a button and tap on it, however I can't tap since an overlapping MBProgressHud is shown and also the api doesn't receive the response it needs to show the button. This led to failure of test.
My questions is what I am doing wrong.
Can we include this type of scenarios (like api calling) in our ui testing?
How to wait for the api call completion to continue our ui test?
Yes. We can include these scenarios in our ui testing. Here is the function we need to wait for the ui element to appear:
func waitForElementToAppear(_ element: XCUIElement) -> XCUIElement? {
let predicate = NSPredicate(format: "exists == true")
let expectation = XCTNSPredicateExpectation(predicate: predicate,
object: element)
let result = XCTWaiter().wait(for: [expectation], timeout: 10)
if result == .completed {
return element
}
return nil
}
This function will wait for api call to be completed and requires ui element to appear.
You can use and this works fine.
func waitForExistence(timeout: TimeInterval) -> Bool
https://developer.apple.com/documentation/xctest/xcuielement/2879412-waitforexistence
Related
I use XCUITest for testing an app that allows the user to select an avatar by picking a photo from the gallery. When I tap on the button that opens the gallery window, I can see the elements in debugDescription. There is a table that contains the folders with photos. The problem is when I tap for the first time on any cell the test fails with error:
Assertion Failure: UserProfileAndSettingsTests.swift:434: Failed to get matching snapshot: No matches found for Element at index 2 from input {(
Table
)}".
If I put a breakpoint there, the second time I tap on any cell, it works.
The command is the following:
XCUIApplication().tables.element(boundBy: 2).cells.element(boundBy: 1).tap()
If before the command I put the line: XCUIApplication().tables.element(boundBy: 2).cells.element(boundBy: 1), it doesn't fail. It fails when trying to tap().
Looks like a timing issue. Familiarize yourself with the XCUIElement class, specifically with this:
/** Waits the specified amount of time for the element's exist property to be true and returns false if the timeout expires without the element coming into existence. */
open func waitForExistence(timeout: TimeInterval) -> Bool
You should be able to do something like this:
let element = XCUIApplication().tables.element(boundBy: 2).cells.element(boundBy: 1)
if element.waitForExistence(timeout: 2) {
element.tap()
}
I recommend making friends with this method, and also other similar methods and expectations, to be able to do convenient stuff like this (self in this context is a XCTestCase):
func waitUntilTappable(_ element:XCUIElement, timeout: TimeInterval = 2) {
let tappableExpectation = self.expectation(for: NSPredicate(format: "isHittable == true"),
evaluatedWith: element)
self.wait(for: [tappableExpectation], timeout: timeout.rawValue)
}
Is it possible to cancel Firebase HTTPS Callable function during it's request?
I have a function with some predictive search. It is called each time when user inputs a character in a search field.
Code:
func startSearch(_ query: String, completion: #escaping (_ results: [SearcheResults]) -> Void) {
let data = [
"query": query
]
functions.httpsCallable("startSearch").call(data) { (result, error) in
if error != nil {
completion([])
} else if let data = result?.data {
// some data manipulations
completion(elements)
}
}
}
Or maybe somehow dismiss earlier completions? Because for now, if user is very rapid and enter text, for example "Berlin" - completion will fire 6 times. I'd like to have a way to cancel a function or cancel previous completions.
Thanks in advance.
You should try debounce, basically in debounce before you fire a request you wait for short span(eg:- 2 secs), and if user types in that span again, timer is reset to again 2 secs,check the link
Once the call is made it cannot be cancelled, It will Either get executed to completition or timedout.
Once you invoke a callable function, it can't be canceled. The function will run to completion or timeout. You will need to be sure on the client that you really want to invoke the function. You are by no means obliged to consume the result (you can ignore it if you want), but the transaction will complete, unless the client app dies in the process. In that case, the function on the backend will still complete, but it will just not be able to deliver the response.
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)
I am implementing a search textfield using ReactiveCocoa 4, and want to only hit the search API after no text has been inputted for X amount of time. I have done this previously by canceling previously scheduled and firing off a "executeSearch" selector in the textDidChange delegate method. This ensures that every time I type, any previously scheduled "executeSearch" selector is canceled, and a new one is scheduled to fire in X seconds.
I now want to do this same behavior, but from a signal producer bound to my input text. My current implementation is close, but not the same. This behavior merely throttles the text input event to only fire every 0.5 seconds, instead of canceling the previous event.
searchTextInput.producer.delay(0.3, onScheduler: RACScheduler.currentScheduler())
.throttle(0.5, onScheduler: RACScheduler.currentScheduler())
.producer.startWithNext({ [unowned self] searchText in
self.executeSearch(searchText)
})
I'm having a hard time sifting through the ReactiveCocoa 4 documentation to know which signal functions I should be using! Thank you!
You need use DateSchedulerType. For example:
textField.rac_textSignal()
.toSignalProducer()
.map { $0 as! String }
.flatMapError { _ in SignalProducer<String, NoError>.empty }
.throttle(2.0, onScheduler: QueueScheduler.mainQueueScheduler)
.filter { $0.isEmpty }
.startWithNext { text in
print("t: \(text)")
}
Also you can write your executeSearch as SignalProducer and use flatMap(.Latest) for create signal-chains.
And don't forget using mainQueueSheduler for get result to UI
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.