WWDC Quick Look 💓 By SwiftGGTeam
Perform accessibility audits for your app

Perform accessibility audits for your app

Watch original video

Highlight

Apple added performAccessibilityAudit() to the XCTest framework in Xcode 15, letting developers automatically check 10 types of accessibility issues—VoiceOver labels, contrast, Dynamic Type, and more—in UI tests with one call, filter false positives through a closure, and integrate accessibility testing into CI/CD.


Core Content

The problem: why accessibility testing always gets postponed

Many developers want to do accessibility work, but when projects tighten, accessibility optimization is often cut first. The reason is simple—testing cost is too high.

Traditional accessibility testing: manually enable VoiceOver, swipe and tap through the screen element by element, listen whether labels correctly describe UI elements. This process has several problems:

  1. Time-consuming: a moderately complex screen takes 10–20 minutes to test fully
  2. Hard to regression-test: every UI change requires manual retesting
  3. Easy to miss issues: eyes and ears fatigue; contrast problems and layout issues after font scaling are easily overlooked
  4. Cannot automate: no tool runs accessibility checks in CI automatically

Result: most apps get one accessibility test before release, or none at all.

Apple’s solution: automated accessibility audits

Xcode 15 introduces XCUIApplication.performAccessibilityAudit(), which automatically detects 10 accessibility issue types in UI tests:

  • Dynamic Type: whether elements get clipped when fonts scale up
  • Contrast: whether text/background contrast meets WCAG AA
  • Element existence: whether interactive elements are visible to assistive technology
  • Labels: whether buttons and images have descriptive accessibilityLabel
  • Traits: whether interactive elements declare correct traits (e.g. .button)
  • Hints: whether accessibilityHint is provided for state changes
  • Accessibility: whether disabled state is correctly reflected
  • Actions: whether custom interactions provide accessibilityCustomActions
  • Hit testing: whether tap targets are large enough (at least 44×44 points)
  • Keyboard focus: whether keyboard navigation order is reasonable

These checks run in UI tests like ordinary XCTAssert—fail the test on issues, continue on success. You can put accessibility tests in continuous integration and run them on every build.


Detailed Content

Basic usage: audit the entire app in one call

The simplest usage takes only three lines (02:52):

func testAccessibility() throws {
    let app = XCUIApplication()
    app.launch()
    
    try app.performAccessibilityAudit()
}

Key points:

  • XCUIApplication() is the app process proxy in UI tests; launch() starts the app
  • performAccessibilityAudit() traverses all visible UI elements and checks 10 accessibility issue types
  • Throws on any issue found; test fails
  • Checks all visible elements on the current screen, including those with isHittable == false

Filtering false positives: custom audit rules

In practice, some “issues” are design decisions, not bugs—a purely decorative background image without a label, or low-contrast text required by brand colors.

Apple provides a closure parameter to decide whether to ignore each issue (09:57):

try app.performAccessibilityAudit(for: [.dynamicType, .contrast]) { issue in
    var shouldIgnore = false
    
    // Ignore contrast issue for "My Label"
    if let element = issue.element,
       element.label == "My Label",
       issue.auditType == .contrast {
        shouldIgnore = true
    }
    return shouldIgnore
}

Key points:

  • for: specifies which audit types to run; pass [] to audit all 10 types
  • issue is AccessibilityIssue with element (problematic UI element), auditType (issue type), message (description)
  • Closure returns true to ignore the issue, false to fail the test
  • Can judge false positives by element label, identifier, elementType, etc.
  • This closure only affects the current audit call; it does not modify global configuration

Controlling audit scope: expose specific elements to assistive technology

Some elements you want VoiceOver to access but not UI tests (internal layout containers), or vice versa.

iOS 17 adds accessibilityElements to precisely control VoiceOver-visible element scope (08:40):

// Only let VoiceOver see these two elements
view.accessibilityElements = [quoteTextView, newQuoteButton]

Key points:

  • accessibilityElements is a property on UIView and NSView
  • After setting, assistive technology only sees elements in this array; child views are ignored
  • This is a “whitelist” mode for scenes where you do not want to expose internal layout structure

UI test visibility can be controlled separately (14:07):

// Let UI tests access only these three elements
view.automationElements = [imageView, quoteTextView, newQuoteButton]

Key points:

  • automationElements controls which elements XCTest can see
  • Useful when you do not want tests coupled to implementation details
  • After setting, XCUIApplication queries only find elements in the array

Interpreting audit results: how to fix failures

When performAccessibilityAudit() finds issues, test logs output something like:

Accessibility Issue - Contrast
Element: Button, label = "Submit"
Issue: Contrast ratio is 2.1:1, which is below the WCAG AA standard of 4.5:1 for normal text

Common fixes:

Issue typeTypical causeFix
ContrastText and background too similarIncrease text brightness or decrease background brightness, or use bold text (small sizes allow 3:1)
LabelsButton/image missing accessibilityLabelSet accessibilityLabel = "" for decorative elements; provide descriptive labels for functional elements
Dynamic TypeFixed-height constraints clip scaled fontsUse contentHuggingPriority and compressionResistancePriority, or UIStackView for adaptive subviews
ActionsCustom gestures lack alternative actionsAdd accessibilityCustomActions for VoiceOver menu-style operations

Core Takeaways

1. Add accessibility audits to every UI test

From today, add try app.performAccessibilityAudit() to every major screen’s UI test. It is like “accessibility insurance” for your test suite—anyone who breaks accessibility on that screen fails the test immediately.

Implementation steps:

  1. Find your project’s highest-coverage UI tests (usually login flow, home navigation, etc.)
  2. Add try app.performAccessibilityAudit() after app.launch()
  3. Run tests once and record the initial issue count
  4. Fix issues one by one, or use the filter closure to suppress known issues and clean up later

2. Create an accessibility checklist template

Many accessibility issues are patterned: navigation back button missing label, card view tap area too small, chart missing accessibilityValue. Create a project-level checklist and review against it when each new screen is done.

Template example:

  • All buttons have accessibilityLabel or meaningful title
  • Decorative images set accessibilityElementsHidden = true
  • Tappable elements minimum 44×44 points
  • Text/background contrast >= 4.5:1 (normal size) or 3:1 (large/bold)
  • Custom gestures provide accessibilityCustomActions or alternative operations

3. Build accessibility contrast tests for brand color systems

Many brand color systems were designed without contrast in mind, making text hard to read across the app. Write a script to automatically calculate contrast between brand palette and all text colors, generating “accessibility-friendly palette suggestions.”

Implementation idea:

func calculateContrastRatio(foreground: UIColor, background: UIColor) -> CGFloat {
    // Calculate WCAG contrast
    // Formula: (L1 + 0.05) / (L2 + 0.05), where L1 is the lighter color's relative luminance
    // Return value >= 4.5 means AA standard met
}

Then iterate all foregroundColor and backgroundColor combinations in your Design Tokens and output combinations below 4.5:1.

4. Use automationElements to reduce UI test fragility

UI tests often fail when layout changes—a new container view, changed view hierarchy. Use automationElements to expose only important test anchors so test code does not couple to implementation details.

Good fit:

  • Complex custom controls (calendar, charts) whose internal subviews change often
  • Third-party component libraries where you do not want tests depending on library internals
  • Hiding debug helper views from UI tests

5. Establish a tiered accessibility issue handling process

Not every accessibility issue needs immediate fix. Tier by severity:

  • P0: Blocks basic use (main button missing label, contrast too low to see at all)—must fix before this release
  • P1: Affects specific user groups (custom gesture without alternative)—fix next version
  • P2: Experience optimization (missing Hint, action description not detailed enough)—improve gradually
  • P3: False positive or design decision (brand color contrast slightly low but compensated elsewhere)—document reason, filter with audit closure

This balances release speed and accessibility quality.


Comments

GitHub Issues · utterances