In my iOS i'm implementing UITests using XCUITest. It worked great since I had Swift 2.3, but after updating the app to Swift 3 basic actions like tap() don't work anymore.
Just a simple code that doesn't work anymore:
XCUIApplication().buttons["orgMenu"].tap()
throws
Assertion Failure: <unknown>:0: UI Testing Failure - Failure getting snapshot Error Domain=XCTestManagerErrorDomain Code=9 "Error -25204 getting snapshot for element <AXUIElement 0x7f8297d15a50> {pid=32375}" UserInfo={NSLocalizedDescription=Error -25204 getting snapshot for element <AXUIElement 0x7f8297d15a50> {pid=32375}}
The name of the button is correct: if I record the test and tap the button the line above is exactly what I get.
The button is in the view since I'm waiting for the existence of it (tried both manually, through a breakpoint, and programmatically with this:
let exists = NSPredicate(format: "exists == 1")
expectation(for: exists, evaluatedWith: XCUIApplication().buttons["orgMenu"], handler: nil
waitForExpectations(timeout: time, handler: nil)
)
And anyway, it worked before Swift 3.
Any idea? Thanks in advance!
I'll just answer to my own question, since I found a workaround.
Since Swift3, for some reason, UI tests are not able to manage views with a loadMore pattern. If I have in my view a TableView and somewhere in the code a manage the loadMore pattern manually, and in a test I tap a button, the loadMore is called in a sort of infinite loop, and that overheads the app's resources, making the test fail.
Workaround: just deactivate any loadMore if a UI Test is running.
In the tests' setup:
override func setUp() {
super.setUp()
let app = XCUIApplication()
continueAfterFailure = false
app.launchEnvironment = ["isUITest":"YES"]
app.launch()
}
In the view with the loadData:
-(void)loadMore
{
if ([Utils isRunningUITest]) {
return;
}
// My actual function.
}
and my Utils
+(BOOL)isRunningUITest {
NSDictionary *environment = [[NSProcessInfo processInfo] environment];
return environment[#"isUITest"];
}
Sorry for Swift and Objective-C mix, hope this is helpful to someone.
You can try and do something like:
let app = XCUIApplication()
XCTAssert(app.buttons["orgMenu"].waitForExistence(timeout: 10))
app.buttons["orgMenu"].tap()
Related
I am attempting to create a UI Test in XCode 10 that includes a step where the following window will appear:
(FYI - this is to support the creation of a video script, so we want to provide a full experience for the viewer)
Initially, I just recorded the steps to make sure I was properly capturing this request screen, and XCode dutifully logged out all of the steps.
Below is a lightly modified version from what XCode generates. For the record, using the unmodified version also fails.
func testDeletePhotos() {
let app = XCUIApplication()
continueAfterFailure = false
app.launch()
// Select two images from within app to delete
let collectionViewsQuery = app.collectionViews
collectionViewsQuery.children(matching: .cell)
.element(boundBy: 1)
.children(matching: .other)
.element.children(matching: .other)
.element(boundBy: 1)
.tap()
collectionViewsQuery.children(matching: .cell)
.element(boundBy: 0).children(matching: .other)
.element.children(matching: .other)
.element(boundBy: 1)
.tap()
let navigationBar = app.navigationBars["MY_APP_NAME"]
// Select the "Trash can" in nav bar to start delete process
navigationBar.buttons["Delete"].tap()
// Prompt appears properly and text is a precise match for string below
// Below step fails with error seen below
app.alerts["Allow “MY_APP_NAME” to delete 2 photos?"].buttons["Delete"].tap()
navigationBar.buttons["Back"].tap()
}
However, when I attempt to re-run this script, it fails to locate the Delete button as seen in the above image.
UI Test Activity: Assertion Failure: MyVideoScript.swift:###: No
matches found for Find: Descendants matching type Alert from input {(
Application, pid: 17838, label: 'MY_APP_NAME' )}
Also, since someone may mention this - the quantity of photos is the same, as this will change the text that appears in the alert. I also attempted to use the app.alerts.element(boundBy: 0) in place of the name search and got the same error.
The tap is probably performed before the alert finished appearing. You can tell the test to wait for it:
let deleteButton = app.alerts["Allow “MY_APP_NAME” to delete 2 photos?"].buttons["Delete"]
let exists = NSPredicate(format: "exists == 1")
expectationForPredicate(exists, evaluatedWithObject: deleteButton, handler: nil)
waitForExpectationsWithTimeout(5, handler: { error in
// check for error, if none:
deleteButton.tap()
})
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
The following tests works fine on iOS 11. It dismisses the alert asking permissions to use the locations services and then zooms in in the map. On iOS 10 or 9, it does none of this and the test still succeeds
func testExample() {
let app = XCUIApplication()
var handled = false
var appeared = false
let token = addUIInterruptionMonitor(withDescription: "Location") { (alert) -> Bool in
appeared = true
let allow = alert.buttons["Allow"]
if allow.exists {
allow.tap()
handled = true
return true
}
return false
}
// Interruption won't happen without some kind of action.
app.tap()
removeUIInterruptionMonitor(token)
XCTAssertTrue(appeared && handled)
}
Does anyone have an idea why and/or a workaround?
Here's a project where you can reproduce the issue: https://github.com/TitouanVanBelle/Map
Update
Xcode 9.3 Beta's Changelogs show the following
XCTest UI interruption monitors now work correctly on devices and simulators running iOS 10. (33278282)
let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard")
let allowBtn = springboard.buttons["Allow"]
if allowBtn.waitForExistence(timeout: 10) {
allowBtn.tap()
}
Update .exists to .waitForExistence(timeout: 10), detail please check comments.
I had this problem and River2202's solution worked for me.
Note that this is not a fix to get the UIInterruptionMonitor to work, but a different way of dismissing the alert. You may as well remove the addUIInterruptionMonitor setup. You'll need to have the springboard.buttons["Allow"].exists test anywhere the permission alert could appear. If possible, force it to appear at an early stage of the testing so you don't need to worry about it again later.
Happily the springboard.buttons["Allow"].exists code still works in iOS 11, so you can have a single code path and not have to do one thing for iOS 10 and another for iOS 11.
Incidentally, I logged the base issue (that addUIInterruptionMonitor is not working pre-iOS 11) as a bug with Apple. It has been closed as a duplicate now, so I guess they acknowledge that it is a bug.
I used the #River2202 solution and it works better than the interruption one.
If you decide to use that, I strongly suggest that you use a waiter function. I created this one in order to wait on any kind of XCUIElement to appear:
Try it!
// function to wait for an ui element to appear on screen, with a default wait time of 20 seconds
// XCTWaiter was introduced after Xcode 8.3, which is handling better the timewait, it's not failing the test. It uses an enum which returns: 'Waiters can be used with or without a delegate to respond to events such as completion, timeout, or invalid expectation fulfilment.'
#discardableResult
func uiElementExists(for element: XCUIElement, timeout: TimeInterval = 20) -> Bool {
let expectation = XCTNSPredicateExpectation(predicate: NSPredicate(format: "exists == true"), object: element)
let result = XCTWaiter().wait(for: [expectation], timeout: timeout)
guard result == .completed else {
return false
}
return true
}
I am using UI Test with Xcode 7 but have a few problems.
When I do record UI Test, Xcode translates Chinese to Unicode with uppercase 'U' and it shows errors.
And ui test code
XCUIApplication *app = [[XCUIApplication alloc] init];
[app.navigationBars[#"\u5934\u6761"].images[#"new_not_login30"] tap];
XCUIElementQuery *tablesQuery = app.tables;
[tablesQuery.cells.staticTexts[#"\u6211\u7684\u864e\u94b1"] tap];
the problem is: after tapping image, there is an animation showing sidebar with UITableView or showing UIAlertController but I cannot handle the duration time. Actually, within animation the testing continues to find next elements to match but these elements do not exist or generating. So the test always be failed.
Any solution to answer this question? Please help me. Thanks.
try it with expectationForPredicate. I don't know the syntax in objective-c. But here is a part of code in swift:
let app = XCUIApplication()
app.navigationBars["\u5934\u6761"].images["new_not_login30"].tap()
let label = app.cells.staticTexts["\u6211\u7684\u864e\u94b1"]
let exists = NSPredicate(format: "exists == 1")
expectationForPredicate(exists, evaluatedWithObject: label) {
// If the label exists, also check that it is enabled
if label.enabled {
label.tap()
return true
} else {
return false
}
}
waitForExpectationsWithTimeout(5) { error in
if (error != nil) { assertion ....}
}
Just translate this code in objective-c.
Cheers
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.