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.

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 XCTestfinal class LibraryAccessibilityTests: XCTestCase {private var app: XCUIApplication!@MainActoroverride func setUpWithError() throws {app = XCUIApplication()app.launch()}@MainActorfunc 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:
@MainActorfunc 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:
@MainActorfunc testFeaturedBooks() throws {app.tabBars.buttons["tab.library"].tap()XCTAssertTrue(app.staticTexts["screen.library.featured.title"].waitForExistence(timeout: 2))try app.performAccessibilityAudit { issue inissue.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.



Newsletter
Like to support my work?
Say hi
Related tags
Articles with related topics
Latest articles and tips



