105

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 'vinayak@xmd.net' 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
Vinpai
  • 1,689
  • 3
  • 16
  • 18
  • @ Joe Masilotti any idea? – Vinpai Oct 29 '15 at 19:17
  • 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 Oct 29 '15 at 20:02
  • @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 Oct 30 '15 at 05:50
  • 1
    @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 Oct 30 '15 at 17:44
  • @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 button – Vinpai Oct 30 '15 at 18:12
  • Having the same exact problem in Xcode 7.1. The test runs fine in 7.0.1. For me it is the "Choose" button in the photo library. – Ravi Desai Oct 30 '15 at 21:30
  • So the issue for me seems to be that the Choose button (that you get from the Photo library picker) is not 'hittable'. You can't scroll it into position and you cannot wait it out; I mimicked the expectationForPredicate code above replacing 'exists == 1' with 'hittable == 1' to no effect. I think this is just a bug that Apple needs to fix. To clarify, you can find the "Choose" button, and .exists returns true, and it is clearly visible on the screen, but .hittable always returns false. So tap fails. – Ravi Desai Oct 30 '15 at 21:55

10 Answers10

170

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 you too ;)) unblocked from these issues I wrote an extension on XCUIElement that allows tap on an 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 the 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()
        }
    }
}
shim
  • 9,289
  • 12
  • 69
  • 108
Sandy
  • 3,021
  • 1
  • 21
  • 29
  • Thanks for the fix. I was encountering similar problem with `cell.tap()` and this fixed it. Something seems to have changed in Xcode7.1 causing some element to no longer hittable – francisOpt Nov 11 '15 at 00:55
  • I have a custom navigationBar backButtonItem that isn't 'hittable' in Xcode7.1, and this solution works for me `self.app.navigationBars["Nav Title"].staticTexts["Custom Back Btn Title"].forceTapElement()` – Alex Nov 17 '15 at 15:57
  • Thanks - Worked for my same issue. – Tache Mar 06 '16 at 01:50
  • 15
    In my case it worked when the tap is in the center of the view. Change the offset to (0.5, 0.5). `let coordinate: XCUICoordinate = self.coordinateWithNormalizedOffset(CGVectorMake(0.5, 0.5))` – Graham Perks Mar 09 '16 at 19:45
  • i have a same issue of a normal view with a button, but this sample doesn't work for me. – emoleumassi Mar 18 '16 at 15:47
  • 1
    great work. I was almost starting killing my macbook before I seen this. – ChaosSpeeder Apr 04 '16 at 16:55
  • Did anyone submit bug report on this to Apple's Radar? I'm using Xcode 7.3(7D175) and have custom `NSControl` with `NSAccessibilityCheckBoxRole`. When I check for `XCTAssertTrue(customCheckBox.hittable)` it fails. Accessibility Inspector didn't find any issues or errors with it, but it can't display it's properties in Inspector panel while hovering it. Looks like I've missed something important. – Andriy May 31 '16 at 07:47
  • Great finding - although this only works most of the times for me. Sometimes it just crashes when trying to tap on the coordinate ?! – dogsgod Oct 06 '16 at 13:52
  • I don't know why, but nothing here works for me. I had to put `sleep(1)`, then XCode can find the damn button. – Caio Nov 18 '16 at 03:55
  • I was getting the same issue but not with UIButtons, but with some SpriteKit sprites that had some UIAccessibilityElement associated with them so I could UI-test them. I get the "failed to scroll to visible" error on Xcode 9.4.1 but older devices (tested on iOS 10.3.3 and 11.2.1). As soon as I updated those devices to iOS 11.4.1 the error disappeared. Tested on 5 different devices. They all work after updating. – endavid Jul 30 '18 at 16:05
  • One thousand likes to you! In my case, a button in table cell has `isHittable` being `false` because the table view scroll bar could potentially blocking it, while it's not actually blocking it. – Haoxin Li Sep 06 '19 at 18:30
  • In our case, we need this function because we override `func hitTest()` to forward taps to buttons under the view, which prevents normal UI test interactions for these buttons. In this case, checking `self.isHittable` as suggested was causing an infinite loop. Skipping that `if` clause and directly calling `coordinate.tap()` fixed the issue. – blwinters Jan 26 '20 at 20:22
  • Still super relevant even today, 4 years later! This fixed the random ass failures I'm getting using GitHub's Action CI build server. Can't for the life of me get the failures to occur locally, even running the same cmd line xcodebuild scripts, but the CI sure does love to fail. thanks!! – BigMcLargeHuge Apr 07 '20 at 05:04
  • Started seeing this w/existing UI tests running on iOS 14 from Xcode 12 that had worked fine till then (labels inside custom views in the navbar). This fixed it perfectly.. – Mark Thormann Sep 22 '20 at 19:24
  • 1
    Suggestion. The CGVector used in the swift 3 example is the top left computed point of the XCUIElement. Instead, you could do something like `CGVector(dx:0.5, dy:0.5)`, which will be the middle of the element. You'll be less likely to tap something you didn't intend to. – Cody Potter Nov 22 '21 at 17:26
  • I faced this issue in Bitrise (Xcode 13) not in my local Xcode but this solution works for Bitrise environment :D thanks @Sandy – SM Abu Taher Asif Oct 21 '22 at 09:38
  • Still seeing this issue in Xcode 14.2. The suggested `forceTapElement` function does not work for me. – Kukiwon Jan 17 '23 at 11:29
14

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.

dogsgod
  • 6,267
  • 6
  • 25
  • 53
8

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)
Jessedc
  • 12,320
  • 3
  • 50
  • 63
  • Thanks, I found the same issue from CI test run, predictive zone of keyboard covers text field. Users are unable to see what they typed. Instead we confirm it is a design bug and change app's layout. – Zhou Haibo Apr 14 '22 at 02:50
3

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.

  • I put this in the `extension XCUIElement` with a couple of small changes: `XCTAssert(self.waitForExistence(timeout: timeout))`, and `timeout` needs to be `Double` – tospig Sep 12 '22 at 04:04
2

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

Community
  • 1
  • 1
Nagaraj
  • 802
  • 9
  • 11
1

Try this:

if !button.isHittable {
     let coordinate: XCUICoordinate = button.coordinate(withNormalizedOffset: CGVector(dx:0.0, dy:0.0))
     coordinate.tap()
}
Nuno Ferro
  • 1,261
  • 12
  • 17
0

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

PruitIgoe
  • 6,166
  • 16
  • 70
  • 137
0

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.

fdelam
  • 11
  • 3
0

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

Jeroen Vannevel
  • 43,651
  • 22
  • 107
  • 170
0

I had this issue because I set a toolbar (and ToolbarItem) on a GeometryReader:

GeometryReader { proxy in
    ZStack { ... }
}
.toolbar {
    ToolbarItem(placement: .navigationBarTrailing) {
        Button(action: { ... }) {
            Text(Localizable.Global.login.localized)
        }
        .accessibilityIdentifier("welcomeViewLoginButton")
    }
}

After setting the toolbar on the ZStack instead, the toolbar item button was hittable again.

GeometryReader { proxy in
    ZStack {
        ...
    }
    .toolbar {
        ToolbarItem(placement: .navigationBarTrailing) {
            Button(action: { ... }) {
                Text(Localizable.Global.login.localized)
            }
            .accessibilityIdentifier("welcomeViewLoginButton")
        }
    }
}
Kukiwon
  • 1,222
  • 12
  • 20