Logo for tanaschita.com

Automating accessibility audits for SwiftUI apps with XCTest

Learn how to detect common accessibility issues automatically as part of a UI test suite.

28 Sep 2026 · 5 min read

Many standard SwiftUI views include accessibility support out of the box. However, custom layouts, styling and content can still lead to missing descriptions, insufficient contrast, clipped text or controls that are difficult to activate.

XCTest accessibility audits allow us to detect many of these issues automatically in a UI test. In this article, we'll look at how to do that. Let's jump in.

Sponsorship logo
Architecture & Design Patterns for iOS
This book is a practical guide to essential architectural principles and design patterns for iOS development. It covers strategies for building maintainable apps with Swift and SwiftUI, including dependency injection, navigation, common patterns, and modularization.
LEARN MORE

Adding an accessibility audit to a UI test

We can add accessibility audits to an Xcode UI test target using XCTest. The audit API is available on XCUIApplication, so it isn't available from a unit test or a Swift Testing test function.

To navigate to a specific screen reliably, the UI test needs stable identifiers. We assign them to the corresponding SwiftUI views using accessibilityIdentifier(_:), for example:

Text("Library")
.accessibilityIdentifier("screen.library.title")

With identifiers in place, a first audit example may look as follows:

import XCTest
final class LibraryAccessibilityTests: XCTestCase {
private var app: XCUIApplication!
@MainActor
override func setUpWithError() throws {
app = XCUIApplication()
app.launch()
}
@MainActor
func testOverview() throws {
app.tabBars.buttons["tab.library"].tap()
XCTAssertTrue(
app.staticTexts["screen.library.title"]
.waitForExistence(timeout: 2)
)
try app.performAccessibilityAudit()
}
}

performAccessibilityAudit() inspects the currently visible screen. If it finds an issue, the UI test fails and Xcode reports the affected element and audit type. We don't need to add a separate assertion.

By default, the method runs all applicable audit types. On iOS, these include checks for:

  • missing or insufficient element descriptions
  • small hit regions
  • insufficient contrast
  • inaccessible content that may need to be exposed as separate elements
  • clipped text
  • missing Dynamic Type support
  • invalid accessibility traits

The audit inspects the state currently presented by the app.

Auditing relevant screens and states

To cover different screens and states, we can use familiar UI testing techniques to navigate to each one before running the audit. For example, another test can open a book from the library and audit its detail screen:

@MainActor
func testBookDetails() throws {
app.tabBars.buttons["tab.library"].tap()
let book = app.buttons["library.book.swift-concurrency"]
XCTAssertTrue(book.waitForExistence(timeout: 2))
book.tap()
XCTAssertTrue(
app.staticTexts["screen.bookDetails.title"]
.waitForExistence(timeout: 2)
)
try app.performAccessibilityAudit()
}

Waiting for the detail title before starting the audit prevents the test from accidentally inspecting the library while the transition is still in progress.

Useful cases are states that expose different content or interactions, for example:

  • an empty list and a populated list
  • a form before and after validation errors appear
  • loading, success and error states
  • sheets, alerts and menus
  • screens at important points in the main user workflows

Handling known audit issues

Sometimes an audit reports an issue that we have reviewed and knowingly accepted, for example while a design change is still pending. An audit can also occasionally report a false positive.

In those cases, we can use the optional issue handler to make an exception for that exact finding:

@MainActor
func testFeaturedBooks() throws {
app.tabBars.buttons["tab.library"].tap()
XCTAssertTrue(
app.staticTexts["screen.library.featured.title"]
.waitForExistence(timeout: 2)
)
try app.performAccessibilityAudit { issue in
issue.auditType == .contrast &&
issue.element?.identifier == "screen.library.featured.title"
}
}

The handler returns true only for the accepted contrast issue on the featured title. Every other issue continues to fail the test.

Investigating issues with Accessibility Inspector

When an automated audit fails, Accessibility Inspector helps us inspect the issue interactively. We can open it from Xcode → Open Developer Tool → Accessibility Inspector, select the running app and switch to the Audits pane.

After running an audit for the current screen, we can:

  • select an issue to highlight the affected element in the app
  • inspect its accessibility label, value, traits and available actions
  • read the explanation and suggested fix
  • save the audit results as an HTML report

Accessibility Inspector uses the same kinds of checks as performAccessibilityAudit(), but it is better suited to understanding and debugging a reported issue. Automated UI tests are better suited to detecting when that issue returns later.

Understanding the limits of automated audits

Passing an audit doesn't guarantee that an app provides a good accessibility experience. An audit can detect a missing label, but it can't reliably decide whether the label is useful in context or whether the VoiceOver navigation order makes sense for the workflow.

Automated audits should therefore be complemented by testing the main workflows with assistive technologies such as VoiceOver.

Conclusion

XCTest accessibility audits give us a repeatable way to catch common accessibility regressions during development. By running them after navigating to meaningful screens and states, we can include accessibility checks in the regular UI test suite rather than relying on a one-time review before release.

When an audit fails, Accessibility Inspector helps us understand the affected element and resolve the issue. Together, automated audits and focused investigation provide fast feedback whenever a UI change introduces a common accessibility problem.

Sponsorship logo
Preparing for a technical iOS job interview
Preparing for a technical iOS Job Interview with over 300 questions & answers. Covering Swift & Objective-C, SwiftUI & UIKit, Combine, HTTP Networking, iOS File System, Core Data, Concurrency with async/await, Security, Automated Testing, Dependency Management, AI & Machine Learning and more.
LEARN MORE
Sponsorship logo
Architecture & Design Patterns for iOS
This book is a practical guide to essential architectural principles and design patterns for iOS development. It covers strategies for building maintainable apps with Swift and SwiftUI, including dependency injection, navigation, common patterns, and modularization.
LEARN MORE
Sponsorship logo
Become a sponsor of tanaschita.com
By publishing an article on different iOS topics every week, tanaschita.com is constantly growing in the developer community and may provide a great audience for you as a sponsor.
CLICK TO LEARN MORE

Newsletter

Image of a reading marmot
Subscribe

Like to support my work?

Say hi

Related tags

Articles with related topics

accessibility

swiftui

xcode

ios

Introduction to supporting VoiceOver in SwiftUI

Learn how to use different accessibility modifiers in SwiftUI.

09 Sep 2024 · 8 min read

Latest articles and tips

© 2026 tanaschita.com

Privacy policy

Impressum