Skip to Content
Course content

205: Accessibility Identifiers for Testing

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I've seen this exact scenario play out in almost every professional project I've touched. You spend a few hours writing a suite of UI tests that pass perfectly. You feel great. Then, a designer asks you to change "Get Started" to "Let's Go!" on the onboarding screen. Suddenly, half your test suite is red, and you're spending your afternoon hunting down string mismatches in your test files instead of building features.

// This test works today...
func testOnboardingFlow() {
    let app = XCUIApplication()
    app.launch()
    
    // We are searching for the button by its visible text
    let startButton = app.buttons["Get Started"] 
    XCTAssertTrue(startButton.exists)
    startButton.tap()
}

The Fragile Link Between UI and Tests

The problem here is that the test is coupled to the content of the UI. In the example above, app.buttons["Get Started"] tells XCTest to look for a button whose label is exactly "Get Started". This seems intuitive, but it's a trap. Content changes for a dozen reasons: A/B testing, localization for different languages, or just a simple wording tweak. When you use the label as your primary hook, your tests aren't actually testing the logic—they're testing the copywriting.

If you change that button to "Let's Go!", the test fails. Not because the app is broken, but because the "hook" you used to find the button vanished. I call these "brittle tests," and they are the primary reason developers eventually stop writing UI tests altogether because the maintenance overhead becomes a nightmare.

Decoupling Logic with Accessibility Identifiers

The solution is to use accessibilityIdentifier. Unlike the accessibilityLabel (which is what VoiceOver reads aloud to users), the accessibilityIdentifier is a developer-facing string. It's invisible to the user and doesn't change regardless of the language the app is running in. It acts as a permanent "ID tag" for the element.

Here is how I would rewrite that button and the corresponding test to make it bulletproof:

// In your View code
let startButton = UIButton()
startButton.setTitle("Get Started", for: .normal)
// This is the secret sauce:
startButton.accessibilityIdentifier = "onboarding_start_button" 
// In your Test code
func testOnboardingFlow() {
    let app = XCUIApplication()
    app.launch()
    
    // Now we search for the identifier, not the text
    let startButton = app.buttons["onboarding_start_button"] 
    XCTAssertTrue(startButton.exists)
    startButton.tap()
}

Now, the designer can change the button text to "Click Me!", "Start Journey", or "¡Empecemos!" and the test will still pass. You've successfully decoupled the what (the button's purpose) from the how (the button's appearance). Just remember: always use a consistent naming convention for these identifiers—like screenName_elementPurpose_elementType—so you don't end up with a chaotic mess of random strings as your app grows.




📋 Practical Task

Hardening the Checkout Screen Tests

You have a checkout screen where the "Pay Now" button is causing tests to fail because the marketing team keeps changing the text to "Complete Purchase" and "Secure Checkout".

Your Task:

  • Create a UIButton called paymentButton.
  • Set its initial title to "Complete Purchase".
  • Assign it a unique accessibilityIdentifier that follows a professional naming convention (e.g., checkout_pay_button).
  • Write a mock XCTest snippet that attempts to find and tap this button using the identifier rather than the title.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.