简体   繁体   中英

Cannot tap UIBarButtonItem in Xcode UI Test

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()
        }
    }
}

For those for whom Alex's answer is not working, try this:

extension XCUIElement {
    func forceTap() {
        coordinate(withNormalizedOffset: CGVector(dx:0.5, dy:0.5)).tap()
    }
}

I just had an issue with UIWebView which is hittable but the tap didn't work until done it via coordinate

A possibility is your bar button item pushes a new view controller that has a refresh control - you might especially encounter this on iPads only where your bar button item pushes onto the detail part of a split view controller. After fetching your items, you might stop your refresh control that's not even refreshing in the first place. Xcode's automation does throw hissy fits about this and sleeps/timeouts won't help there.

So to fix this issue, on your main app, always check your refresh control is refreshing before ending its refresh. I also like to check it's not nil in the first place: if you're using Swift, you can check this in one fell swoop:

if let refresh = self.refreshControl, refresh.refreshing {
  refresh.endRefreshing()
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM