[XCTest] XCUIElement.isHittable has wrong value and tap method taps on wrong element.

Hi I am writing some UI Test cases using XCTest for my iOS app and I am facing some issues because the property isHittable and the tap method both of them don't work as expected in the following case:

I have a button on a fixed position on all the app screens (floating button) with accessibilityElementsHidden set to true, and another button which might be below the floating one on some devices, isHittable property for the button below the floating button returns true which i think is not right because the button is not hittable, and when I try to tap on that button using tap method the button on the top receives the event instead of the button I targeted.

we can reproduce the issue by running the following test case for the following viewController:


final class TestUITests: XCTestCase {

  func testExample() throws {
    let app = XCUIApplication()
    app.launch()
    let button = app.buttons.matching(identifier: "button1").firstMatch
    XCTAssertTrue(button.waitForExistence(timeout: 10))
    XCTAssertTrue(button.isHittable)
    button.tap()
  }
}
class ViewController: UIViewController {
  var button1: UIButton!
  var topButton: UIButton!

  override func viewDidLoad() {
    super.viewDidLoad()
    button1 = UIButton()
    button1.backgroundColor = .blue
    button1.setTitle("button 1", for: .normal)
    button1.accessibilityIdentifier = "button1"
    button1.backgroundColor = .red
    addButtonToCenter(button: button1)
    button1.addTarget(self, action: #selector(didClickOnButton1), for: .touchUpInside)
     
    topButton = UIButton()
    topButton.setTitle("top Button", for: .normal)
    topButton.backgroundColor = .blue
    topButton.addTarget(self, action: #selector(didClickOnTopButton), for: .touchUpInside)
    topButton.accessibilityElementsHidden = true
    addButtonToCenter(button: topButton)
  }
   
  @objc func didClickOnTopButton(_ sender: UIButton) {
    assertionFailure("unexepected call")
  }
   
  @objc func didClickOnButton1(_ sender: UIButton) {
  }
   
  func addButtonToCenter(button: UIButton) {
    view.addSubview(button)
    button.translatesAutoresizingMaskIntoConstraints = false
      let horizontalConstraint = NSLayoutConstraint(item: button, attribute: NSLayoutConstraint.Attribute.centerX, relatedBy: NSLayoutConstraint.Relation.equal, toItem: view, attribute: NSLayoutConstraint.Attribute.centerX, multiplier: 1, constant: 0)
      let verticalConstraint = NSLayoutConstraint(item: button, attribute: NSLayoutConstraint.Attribute.centerY, relatedBy: NSLayoutConstraint.Relation.equal, toItem: view, attribute: NSLayoutConstraint.Attribute.centerY, multiplier: 1, constant: 0)
      let widthConstraint = NSLayoutConstraint(item: button, attribute: NSLayoutConstraint.Attribute.width, relatedBy: NSLayoutConstraint.Relation.equal, toItem: nil, attribute: NSLayoutConstraint.Attribute.notAnAttribute, multiplier: 1, constant: 100)
      let heightConstraint = NSLayoutConstraint(item: button, attribute: NSLayoutConstraint.Attribute.height, relatedBy: NSLayoutConstraint.Relation.equal, toItem: nil, attribute: NSLayoutConstraint.Attribute.notAnAttribute, multiplier: 1, constant: 100)
      view.addConstraints([horizontalConstraint, verticalConstraint, widthConstraint, heightConstraint])
  }
}

Hello! Thanks for the question.

XCTest generated taps enter the system at the same place that a user's taps do- at the human interface layer. This ensures all taps behave the same way that a user’s taps would.

If the button on top has its accessibility elements hidden, then the button below it is not covered, since there is no accessibility element covering it. If the button is not obstructed, isHittable returns true.

To me, it seems like this is all behaving as expected. Taps are being generated at the positions that a user’s taps would be generated, and are behaving as a user’s taps would.

Thank you for your comment,

It is mentioned in apple's document that isHittable returns true if the element exists and can be clicked, tapped, or pressed at its current location (source) but in my case the button can not be clicked or tapped at its current location because if we tapped/clicked at the button's position it will not be tapped/clicked, is there any other way to tap/click on it which makes the sentence "can be clicked, tapped, or pressed at its current location" true ?

The other way to interact with this element would be using a unit test that executes as an "API UI Test" as opposed to a regular UI test.

This would execute using the button objects in your app code, as opposed to your app's exposed accessibility elements.

If you write a unit test that goes through your app's object structure, potentially starting with the app delegate as a root object (or your shared UIApplication), you could find the button object you want to interact with and call methods on it directly (such as its action), to test its functionality.

I am not looking for a workaround to test the functionality of the button. I ma trying to understand how can isHittable return true for an element which the user the XCTest tap method can not tap on it which in my opinion doesn't match isHittable info mentioned in apple's document here: https://developer.apple.com/documentation/xctest/xcuielement/1500561-ishittable

how can the button "button1" in the code I shared is considered to be "can be clicked, tapped, or pressed at its current location" ? it is clearly can not be clicked, tapped, or pressed at its current location, and based on that isHittable expected to return false but it returns true !

The isHittable property of XCUIElement is a total disaster. It doesn't behave as the documentation indicates, and worse than that it randomly kills tests with this sort of failure when it can't figure out whether an element is hittable or not:

Failed to determine hittability of "storeListSearchField" SearchField: Activation point invalid and no suggested hit points based on element frame

This is a longstanding issue that hasn't received any notable attention. The behavior of this property is totally wrong even in its response to an inability to determine the "activation point". If this point can't be ascertained, the property should just return false, not kill the test with an unsolicited assertion failure.

I just went thru the arduous task of removing all reliance on the isHittable property from our UI automation framework and tests, replacing it with our own "scroll margin" implementation. This is far from ideal, because this implementation doesn't have access to the element layering information available to the display rendering engine. However, this is a definite improvement over the erratic behavior and random failures of isHittable.

[XCTest] XCUIElement.isHittable has wrong value and tap method taps on wrong element.
 
 
Q