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:
- Time-consuming: a moderately complex screen takes 10–20 minutes to test fully
- Hard to regression-test: every UI change requires manual retesting
- Easy to miss issues: eyes and ears fatigue; contrast problems and layout issues after font scaling are easily overlooked
- 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
accessibilityHintis 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 appperformAccessibilityAudit()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 typesissueisAccessibilityIssuewithelement(problematic UI element),auditType(issue type),message(description)- Closure returns
trueto ignore the issue,falseto 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:
accessibilityElementsis a property onUIViewandNSView- 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:
automationElementscontrols which elements XCTest can see- Useful when you do not want tests coupled to implementation details
- After setting,
XCUIApplicationqueries 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 type | Typical cause | Fix |
|---|---|---|
| Contrast | Text and background too similar | Increase text brightness or decrease background brightness, or use bold text (small sizes allow 3:1) |
| Labels | Button/image missing accessibilityLabel | Set accessibilityLabel = "" for decorative elements; provide descriptive labels for functional elements |
| Dynamic Type | Fixed-height constraints clip scaled fonts | Use contentHuggingPriority and compressionResistancePriority, or UIStackView for adaptive subviews |
| Actions | Custom gestures lack alternative actions | Add 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:
- Find your project’s highest-coverage UI tests (usually login flow, home navigation, etc.)
- Add
try app.performAccessibilityAudit()afterapp.launch() - Run tests once and record the initial issue count
- 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
accessibilityLabelor meaningfultitle - 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
accessibilityCustomActionsor 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.
Related Sessions
- What’s new in accessibility — latest accessibility features and frameworks in iOS 17, iPadOS 17, macOS Sonoma
- Testing tips and tricks — techniques for writing better tests, including UI and unit test strategies
- Meet Apple Store Accessibility: A new way to shop — how Apple Store accessibility features enhance shopping for users with disabilities
- Design for accessibility — designing interfaces for everyone, including visual, motor, and cognitive disability users
- Accessibility in SwiftUI — how SwiftUI makes accessible interfaces easy and how to customize the accessibility experience
Comments
GitHub Issues · utterances