My navigation bar as an "Add" button on it, and I need to have Xcode's UI test tap that button to perform tests in the view controller it opens. I add the button programmatically like so:
UIBarButtonItem *addButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(showAddVC)];
self.navigationItem.rightBarButtonItem = addButton;
And in my test I have:
XCUIApplication *app = [[XCUIApplication alloc] init];
XCTAssert([app.buttons[@"Add"] exists]); // <-- This passes, so the test runner does see the button.
But when I try to tap it using either:
// Generated using the test recorder
[app.navigationBars[@"App Title"].buttons[@"Add"] tap];
or:
// Same expression used with the XCTAsset earlier
[app.buttons[@"Add"] tap];
Nothing happens. The action that should take place when the button is tapped does not happen. I tried adding some sleep(5)
's between lines to let the app load, but that didn't help much.
This is the test log:
Test Case '-[xx]' started.
t = 0.00s Start Test
t = 0.00s Set Up
2015-12-22 16:25:02.898 XCTRunner[10978:384690] Continuing to run tests in the background with task ID 1
t = 0.94s Launch xx
t = 1.01s Waiting for accessibility to load
t = 3.45s Wait for app to idle
t = 9.02s Tap "Add" Button
t = 9.02s Wait for app to idle
t = 39.07s Assertion Failure: UI Testing Failure - App failed to quiesce within 30s
xx: error: -[xx] : UI Testing Failure - App failed to quiesce within 30s
None of the above answers worked for me. What finally made it work, after hours of struggle, was repeating the tap. Try this:
[app.navigationBars[@"App Title"].buttons[@"Add"] tap];
[app.navigationBars[@"App Title"].buttons[@"Add"] tap];
While the above worked for me initially, I found that sometimes the first tap worked, which would result in two taps. My solution to this was to instead tap an arbitrary UI element that doesn't trigger any actions at the beginning of the UI test, and then proceed as normal. I think that the first tap works on certain devices, or maybe after the first UI test is run.
Testing for exists
does not seem to be sufficient in your case. Wait for the button to be hittable
before trying to tap it.
expectationForPredicate(predicate, evaluatedWithObject: element, handler: nil)
waitForExpectationsWithTimeout(timeoutSeconds, handler: nil)
Where in your case it would be:
expectationForPredicate(NSPredicate(format: "hittable == YES"), evaluatedWithObject: [app.buttons[@"Add"], handler: nil)
waitForExpectationsWithTimeout(15, handler: nil)
[app.buttons[@"Add"] tap];
This will pause execution of code after the waitForExpectationWithTimeout
until that predicate has been satisfied with the given element.
Otherwise, in extreme cases I have found that sometimes errors occur when trying to interact with certain components. How, why and when these occur is a bit of a mystery, but they seem to be somewhat consistent to certain components, and things involving UINavigationBars seem to have them happen more often.
To overcome these, I have found that using this extension will sometimes work.
extension XCUIElement {
/* Sends a tap event to a hittable/unhittable element. Needed to get past bug */
func forceTapElement() {
if hittable {
tap()
}
else {
let coordinate: XCUICoordinate = coordinateWithNormalizedOffset(CGVectorMake(0.0, 0.0))
coordinate.tap()
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With