86
votes

I am trying to dismiss the search field by tapping 'Cancel' button in search bar.

The test case is failing to find the cancel button. It was working fine in Xcode 7.0.1

I have added predicate to wait for button to appear. The test case is failing when we tap of "cancel" button

let button = app.buttons[“Cancel”]
let existsPredicate = NSPredicate(format: "exists == 1")

expectationForPredicate(existsPredicate, evaluatedWithObject: button, handler: nil)
waitForExpectationsWithTimeout(5, handler: nil)

button.tap() // Failing here

logs:

    t =     7.21s     Tap SearchField
t =     7.21s         Wait for app to idle
t =     7.29s         Find the SearchField
t =     7.29s             Snapshot accessibility hierarchy for com.test.mail
t =     7.49s             Find: Descendants matching type SearchField
t =     7.49s             Find: Element at index 0
t =     7.49s             Wait for app to idle
t =     7.55s         Synthesize event
t =     7.84s         Wait for app to idle
t =     8.97s     Type '[email protected]' into
t =     8.97s         Wait for app to idle
t =     9.03s         Find the "Search" SearchField
t =     9.03s             Snapshot accessibility hierarchy for com.test.mail
t =     9.35s             Find: Descendants matching type SearchField
t =     9.35s             Find: Element at index 0
t =     9.36s             Wait for app to idle
t =     9.42s         Synthesize event
t =    10.37s         Wait for app to idle
t =    10.44s     Check predicate `exists == 1` against object `"Cancel" Button`
t =    10.44s         Snapshot accessibility hierarchy for com.test.mail
t =    10.58s         Find: Descendants matching type Button
t =    10.58s         Find: Elements matching predicate '"Cancel" IN identifiers'
t =    10.58s     Tap "Cancel" Button
t =    10.58s         Wait for app to idle
t =    10.64s         Find the "Cancel" Button
t =    10.64s             Snapshot accessibility hierarchy for com.test.mail
t =    10.78s             Find: Descendants matching type Button
t =    10.78s             Find: Elements matching predicate '"Cancel" IN identifiers'
t =    10.79s             Wait for app to idle
t =    11.08s         Synthesize event
t =    11.13s             Scroll element to visible
t =    11.14s             Assertion Failure: UI Testing Failure - Failed to scroll to visible (by AX action) Button 0x7f7fcaebde40: traits: 8589934593, {{353.0, 26.0}, {53.0, 30.0}}, label: 'Cancel', error: Error -25204 performing AXAction 2003
9
@ Joe Masilotti any idea?Vinpai
Just a sanity check, but is the Cancel button on the screen or does it need to be scrolled to? I know there are still issues where scrolling to elements doesn't always work.Joe Masilotti
@JoeMasilotti cancel button is on the screen. Cancel button is system default button which is part of UISearchbar. It was working fine in Xcode 7.0.1 when I did [self.buttons[@"Cancel"] tap];Vinpai
@Vinpai what happens when you add a app.tables.cells.allElementsBoundByAccessibilityElement.count before tapping your button? Just curious--this has helped 'refresh' the screen for me at times.cakes88
@Konnor, I tried with API you have suggested, but it is failing when I tap on Cancel button. When I query application.buttons in debugger, it is showing cancel buttonVinpai

9 Answers

144
votes

I guess here "Cancel" button returns false for hittable property, that is preventing it from tapping.

If you see tap() in documentation it says

/*!
 * Sends a tap event to a hittable point computed for the element.
 */
- (void)tap;

It seems things are broken with XCode 7.1.To keep myself (and u too ;)) unblocked from these issues I wrote a extension on XCUIElement that allows tap on element even if it is not hittable. Following can help you.

/*Sends a tap event to a hittable/unhittable element.*/
extension XCUIElement {
    func forceTapElement() {
        if self.hittable {
            self.tap()
        }
        else {
            let coordinate: XCUICoordinate = self.coordinateWithNormalizedOffset(CGVectorMake(0.0, 0.0))
            coordinate.tap()
        }
    }
}

Now you can call as

button.forceTapElement()

Update - For swift 3 use following:

extension XCUIElement {
    func forceTapElement() {
        if self.isHittable {
            self.tap()
        }
        else {
            let coordinate: XCUICoordinate = self.coordinate(withNormalizedOffset: CGVector(dx:0.0, dy:0.0))
            coordinate.tap()
        }
    }
}
11
votes

For me, the root cause was that the objects I wanted to tap

  • have been set to hidden (and back)
  • have been removed and re-attached

In both cases the isAccessibilityElement property was false afterwards. Setting it back to true fixed it.

5
votes

This question ranks well for Google queries around the term "Failed to scroll to visible (by AX action) Button". Given the age of the question I was inclined to think this was no longer an issue with the XCUITest framework as the accepted answer suggests.

I found this issue was due to the XCElement existing, but being hidden behind the software keyboard. The error is emitted by the framework since it is unable to scroll a view that exists into view to be tappable. In my case the button in question was behind the software keyboard sometimes.

I found the iOS Simulator's software keyboard may be toggled off in some cases (eg: on your machine) and toggled on in others (eg: on your CI). In my case I had toggled the software keyboard off on one machine, and by default it was toggled on on others.

Solution: Dismiss the keyboard before attempting to tap buttons that may be behind it.

I found tapping somewhere that explicitly dismissed the keyboard before tapping on the button solved my problem in all environments.

I added add some actions to get the current responder to resignFirstResponder. The views behind my text views will force the first responder to resign, so I tap somewhere just underneath the last text area.

 /// The keyboard may be up, dismiss it by tapping just below the password field
let pointBelowPassword = passwordSecureTextField.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 1))
pointBelowPassword.press(forDuration: 0.1)
2
votes

Please check the trait of the element, i was facing the same issue with TableViewSectionHeader, i was trying to tap but it was failing at every point

enter image description here

2
votes

The workaround of Sandy seemed help for a while but then no more - I then changed it like this:

func waitAndForceTap(timeout: UInt32 = 5000) {
    XCTAssert(waitForElement(timeout: timeout))
    coordinate(withNormalizedOffset: CGVector(dx:0.5, dy:0.5)).tap()
}

Main point being that as the issue is that isHittable check throws an exception, I don't do this check at all and go straight for coordinates after the element is found.

0
votes

In my case it was having a programmatically added UI element covering the button.

0
votes

If you're using the AppCenter simulator to run the tests, you should make sure that you're running the tests on the same device version than your local simulator. I lost 3 days of work because of this.

0
votes

In the spirit of things that can cover your element, I had the RN debugger partially overlayed on top of my icon:

enter image description here

0
votes

Try this:

if !button.isHittable {
     let coordinate: XCUICoordinate = button.coordinate(withNormalizedOffset: CGVector(dx:0.0, dy:0.0))
     coordinate.tap()
}