25

How do I assert a button exists by his accessibilityLabel or identifier?

func testExitsButton() {
    XCTAssertTrue(app.windows.containing(.button, identifier: "Button Text").element.exists)
    XCTAssertTrue(app.buttons["Button Text"].exists)
    XCTAssertTrue(app.buttons["test"].exists) <- I want this, instead of accessing the text property I want by specific id, maybe the text property override the accessibilityLabel?
}

enter image description here

Cœur
  • 37,241
  • 25
  • 195
  • 267
Godfather
  • 4,040
  • 6
  • 43
  • 70

5 Answers5

42

Set an accessibility identifier in your application code, and then search for the button using that identifier in your tests.

// app code
let button: UIButton!
button.accessibilityIdentifier = "myButton"

// UI test code
func testMyButtonIsDisplayed() {
    let app = XCUIApplication()
    let button = app.buttons["myButton"]
    XCTAssertTrue(button.exists)
}

The accessibility identifier is set independently of text on the button, and is also independent of the accessibility label. It's not best practice to put identifiers for UI elements as the accessibility label, since the accessibility label is read to VoiceOver users to explain the element to them.

Oletha
  • 7,324
  • 1
  • 26
  • 46
  • Thx! If you dont set any accessibility id/label, can you access the UIButton via its text? – Godfather Jan 04 '17 at 16:36
  • You can, but that's not best practice as you'll need to do maintenance if you decide to change or localize the text. – Oletha Jan 04 '17 at 16:43
  • so app.buttons["id"] first try to access "id" accessibilityIdentifier, and if its not set then search for "id" text property? – Godfather Jan 04 '17 at 16:51
  • Yes, I think the order is identifier, then label, then text. – Oletha Jan 04 '17 at 21:59
  • and can you use identifier with frameworks like KIF which relays in "accesibilityLabel"? Its cofussing – Godfather Jan 04 '17 at 22:01
  • If KIF relies on accessibility labels then it's unlikely that identifiers will work with it. I'm not sure if identifiers will even be accessible to third party frameworks... – Oletha Jan 04 '17 at 22:21
16

IMPORTANT NOTE: If a superview is set to accessible, XCUITest might not be able to access its subviews.

You can access the element by setting its accessibility via the storyboard or programmatically as discussed above. You can click the record button when your cursor is in a function which starts with the prefix "test" in order to record how XCUITest sees an element. Sometimes it takes a couple cleans (command shift k) and a couple minutes for the record button to be available. You can also step down your tree from storyboard and use XCUITest functions such as element(boundBy: Int), children(matching: .textField), you can chain them as well: XCUIApplication().tables.cells.containing(.button, identifier: "id"). After that is the easy part, use .exists which returns a boolean.

enter image description here

ScottyBlades
  • 12,189
  • 5
  • 77
  • 85
  • 8
    That first sentence provided very useful information and ended an hourlong search for an error. – Nils Hott Nov 23 '17 at 13:10
  • 1
    Thanks! Where did you get that *Important note* knowledge? It's so critical that must be in every single contentt related to XCUItestting. For me also helped using _Accessibility Inspector_. – BuguiBu Oct 30 '18 at 13:02
  • 1
    @buguibu. Trial and error and a lot of research leading to articles which confirmed it. – ScottyBlades Oct 30 '18 at 23:08
9

add | "accessibilityIdentifier" String test | in the User Defined Runtime Attributes on the navigation bar, instead of in the accessibility label

Stefan
  • 762
  • 1
  • 8
  • 11
3

Accessible Elements Guide ☑️

I've outlined some of the most relevant best practices related to iOS Accessibility for codebases with UI tests in this answer. Note that even though this answer is geared towards UIKit apps, the same best practices are framework agnostic between SwiftUI and UIKit (only with different API names).

Apply the same reasoning to SwiftUI accessibility View Modifiers to ensure a great User Experience for both types of apps.

I highly recommend paying attention to accessibility in apps because Apple promotes apps that enforce high accessibility standards, and supporting accessibility is more "ethical" software development by service to a wider audience.

Use .accessibilityIdentifier

I am writing this answer to advise others / OP/comments against using .accessibilityLabel interchangeably with .accessibilityIdentifier for the sole purpose of enabling UI Testing.

Use . accessibilityIdentifier rather than .accessibilityLabel because otherwise, we create a poor User Experience for VoiceOver users:

button.accessibilityLabel = "test" // ❌

reads to the user as "Button. test" which doesn't help the user navigate the screen if they are visually impaired!

 button.accessibilityIdentifier = "test" // 

Using the identifier means VoiceOver reads the Button's title label text rather than "test".

NB: If you are still unable to find the element, double-check you haven't overridden the .accessibilityIdentifier configuration in code in a storyboard or xib file. Also check the button is an accessibility element in both places.

Enabling UI testing for buttons within container views

You may be unable to locate a button in UI tests because it's a subview of an accessible element. In order to enable accessibility on the button for UI tests and VoiceOver for the container, use .accessibilityElements instead:

containerView = UIView()
containerView.isAccessibilityElement = false
containerView.accessibilityElements = [firstLabel, secondLabel, button]
// These elements are subviews of containerView

Setting .accessibilityElements makes the parent view an Accessibility Container! The advantage here is that we get UI tests as well as an accessible VoiceOver User Experience that allows users to select/navigate within subviews too.

Screen readers go through the elements on a page/screen in the order in which they appear. Set the order you want VoiceOver to read the elements within the .accessibilityElements property.

.isHittable in UI Tests

We can now find the button with a simple XCUIElementQuery subscript:

XCTAssertTrue(app.buttons["test"].isHittable)

I recommend using .isHittable as outlined in this answer, rather than .exists because it provides a more robust test. We can see why from the docs:

isHittable returns true if the element exists and can be clicked, tapped, or pressed at its current location. It returns false if the element does not exist, is offscreen, or is covered by another element.

Pranav Kasetti
  • 8,770
  • 2
  • 50
  • 71
1

I got the same issue because the keyboard covered the button so:

app.buttons["Done"].tap()

solved the issue.

William Hu
  • 15,423
  • 11
  • 100
  • 121