What can a unit test assert about accessibility properties, and why does a standard UIButton report no label in a plain test process?

24.6.1 · L3 · Interview

Predict first

What do a plain simulator process and explicit assignments report?

import UIKit

@MainActor func run() {
    let button = UIButton(type: .system); button.setTitle("Share", for: .normal)
    let label = UILabel(); label.text = "Balance"
    let slider = UISlider(); slider.value = 0.5
    let image = UIImageView(image: UIImage(systemName: "star"))
    print("button element:", button.isAccessibilityElement, "label:", button.accessibilityLabel ?? "nil")
    print("traits button/label/slider/image:", button.accessibilityTraits.rawValue, label.accessibilityTraits.rawValue, slider.accessibilityTraits.rawValue, image.accessibilityTraits.rawValue)
    button.isAccessibilityElement = true
    button.accessibilityLabel = "Share report"
    button.accessibilityValue = "3 pending"
    button.accessibilityTraits.insert(.selected)
    print("explicit:", button.isAccessibilityElement, button.accessibilityLabel!, button.accessibilityValue!, button.accessibilityTraits.contains(.selected), button.accessibilityTraits.contains(.button))
}
MainActor.assumeIsolated { run() }
  • button element: false label: nil / traits button/label/slider/image: 1 64 0 4 / explicit: true Share report 3 pending true true
  • button element: true label: Share / …
  • It crashes without VoiceOver

Mental model. A car’s warning lights only light up with the ignition on. Checking the dashboard in a parked, switched-off car tells you nothing about the lights, only about what you wired on purpose.

Reading accessibility properties from a unit test or a command-line process is less informative than it looks. In the check below (run in the iOS Simulator without VoiceOver), a standard UIButton titled “Share” reports isAccessibilityElement as false and accessibilityLabel as nil, and a UILabel, UISlider and image view report only their class-default traits (button is 1, static text is 64, an image is 4, and the slider shows 0). The values you see in the Accessibility Inspector and from VoiceOver are produced by UIKit’s accessibility support, which is applied while an assistive technology (or the audit machinery of a UI test) is active, so a plain process sees the unpopulated defaults. In other words, defaults are not testable in a unit test; what is testable is what you set.

Anything you assign explicitly reads back exactly: after the check sets isAccessibilityElement, a label, a value and the selected trait, all four are returned, and the default button trait is still there, because insert only adds to the traits (assigning accessibilityTraits = .selected would have replaced them). So a useful split is: unit-test your own code that computes labels and values (a view model that returns “Savings, 1,204 pounds, selected”, or a function that builds the label for a row), and use XCUITest and performAccessibilityAudit() (Q476) to test the real tree, because the UI test process drives the app with accessibility on and queries the elements the way an assistive technology does. Remember that the label you set is also what Voice Control, Switch Control and UI tests find (Q449), so changing a label text is a behaviour change, and localised labels need localised tests.

Without an assistive technology the stock control reports no element and no label; explicit values read back, and insert keeps the default trait.

Common traps

  • Asserting isAccessibilityElement == true for a stock control in a unit test. It reads false without an assistive technology; use a UI test.
  • Assigning accessibilityTraits = .selected and losing the button trait. Assignment replaces; use insert to add.

They might follow up with

  • Then how do I test a custom control’s accessibility? Put the label, value and traits behind pure functions you unit test, and cover the live tree with a UI test and an audit.
  • Is this the same on a device? Not run here: on a device the values appear while VoiceOver or another assistive technology is on.

Related questions

  • 24.1.1 How does VoiceOver read your UI: accessibility elements, labels, values, hints and traits, and how do you fix…
  • 24.5.1 How do you test accessibility: the Accessibility Inspector, automated audits in XCUITest, identifiers versus…
  • 22.5.2 How do you write UI tests that stay stable: accessibility identifiers, page objects, launch arguments…
  • 24.6.2 How do you make a custom-drawn control (a canvas, a chart or a hand-built segmented control) accessible with…

How do you make a custom-drawn control (a canvas, a chart or a… →