Setting Up Automated Accessibility Audits in Your iOS App CI/CD Pipeline

Sahil Sharma
5 min readNov 27, 2023
Photo by AbsolutVision on Unsplash

Ensuring the accessibility of your iOS app is paramount for an inclusive user experience. This blog post guides you through the process of seamlessly integrating automated accessibility audits into your CI/CD pipeline, allowing you to catch and address potential issues early in your development workflow.

Why Automated Accessibility Audits?

Automated accessibility audits help identify and rectify potential issues before your app reaches users. By incorporating these audits into your CI/CD pipeline, you can proactively address accessibility concerns throughout the development cycle.

Steps to Set Up Automated Accessibility Audits:

  1. Add a new accessibility audit test testAccessibilityPokemonScreen()in your XCTestCase file:
final class SampleAppForMediumUITests: XCTestCase {
private var app: XCUIApplication!

override func setUpWithError() throws {
continueAfterFailure = true
app = XCUIApplication()
app.launch()
}

func testAccessibilityPokemonScreen() throws {
//Please note performAccessibilityAudit API is available on iOS 17.0 onwards only.
if #available(iOS 17.0, *) {
try app.performAccessibilityAudit()
}
}
}
SampleApp running accessibility audit XCUITest
Sample app running testAccessibilityPokemonScreen() test

Let’s first explore performAccessibilityAudit:

Let’s delve into performAccessibilityAudit: Invoking this method within a test will comprehensively audit the accessibility of the current view in XCUIApplication, akin to the Accessibility Inspector tool. No explicit assertions are required; any identified issues will automatically trigger a failure in the XCTest.

By default, performAccessibilityAudit() evaluates various accessibility types:

  • contrast: Checks for sufficient contrast in elements (available on all platforms).
  • elementDetection: Ensures all elements are detectable and accessible (available on all platforms).
  • hitRegion: Verifies elements are hittable and not obscured by other views (available on all platforms).
  • sufficientElementDescription: Validates elements have appropriate descriptions and/or labels (available on all platforms).
  • dynamicType: Ensures text within elements scales with system font size changes (available on iOS, watchOS, tvOS but not on macOS).
  • textClipped: Confirms text is not truncated and remains visible (available on iOS, watchOS, tvOS but not on macOS).
  • trait: Checks for proper trait definitions (available on iOS, watchOS, tvOS but not on macOS).
  • action: (available to MacOS apps only).
  • parentChild: (available to MacOS apps only).

How about handling specific audit issues selectively?

If there are particular audit concerns you wish to overlook, you have the option to ignore them based on the element or audit type. Here’s an example where I choose to exclude dynamicType audit issues for a UITextField.

func testAccessibilityPokemonScreen() throws {
if #available(iOS 17.0, *) {
try app.performAccessibilityAudit() { issue in
var shouldIgnore = false

if let element = issue.element, element.identifier == "pikachu", issue.auditType == .dynamicType {
shouldIgnore = true
}

return shouldIgnore
}
}

The closure of the performAccessibilityAudit method will be executed for every issue identified during the audit, affording you the flexibility to determine which issues to disregard within this context. In the aforementioned example, I am excluding the dynamicType audit issue for a UITextField with an accessibility identifier equal to 'pikachu.'

Additionally, you have the option to selectively run an accessibility audit for specific audit types. Consider the following example where I intend to execute an accessibility audit solely for contrast, dynamicType, and trait.

// Run accessibility audit for contrast, dynamicType, and trait only
try app.performAccessibilityAudit(for: [.contrast, .dynamicType, .trait])

Or everything except dynamicType

// Run accessibility audit for all audit types except dynamicType
try app.performAccessibilityAudit(for: .all.subtracting(.dynamicType))

Analyzing Test Failures:

accessibility audit xcuitest failed because one of the element on screen doesn’t support dynamic fonts
XCUITest Report

Within Xcode under Test Reports, the error description will specify the type of audit issue identified and the associated element. In the provided screenshot of a test failure, it indicates that Dynamic Type is not supported for the UITextField with the accessibility identifier ‘pikachu.’ For more in-depth debugging, you can leverage Accessibility Inspector if necessary.

2. I recommend creating a method dedicated to testing accessibility within each screen class. Let’s proceed by creating a new screen class specifically for my ‘Pokemon List’ view.

import Foundation
import XCTest

class PokemonListScreen {
let app: XCUIApplication

init(app: XCUIApplication) {
self.app = app
}

func testAccessibility() throws {
if #available(iOS 17.0, *) {
try app.performAccessibilityAudit()
}
}
}

Now, in your XCUITest, invoke this method when the relevant view is being displayed in the app.

func testAccessibilityPokemonScreen() throws {        
let pokemonListScreen = PokemonListScreen(app: app)
try pokemonListScreen.testAccessibility()
}

Following this pattern, create accessibility XCUITest for each screen.

If you’re unfamiliar with the concept of a screen class, I highly recommend checking out my blog on the Page Object Model pattern in XCTest: https://medium.com/next-level-swift/your-ultimate-xcuitest-ios-design-pattern-page-object-model-pom-bfb82b265bb0

3. Let’s create a dedicated Test Plan specifically for Accessibility Audit Tests. To do this, navigate to Edit Scheme > Test > Click “+”, and choose ‘Create empty test plan’. Within the newly created test plan, include all tests related to accessibility audits

4. All set! Now, effortlessly integrate the execution of this new Test Plan into your CI/CD process. If you’re employing xcodebuild to run your XCUITests on CI/CD runners, simply specify this test plan

xcodebuild test 
-workspace SampleAppForMedium.xcworkspace
-scheme SampleAppForMedium
-sdk iphonesimulator
-destination platform="iOS Simulator",OS=17.0,name="iPhone 14"
-derivedDataPath ~/builds/DerivedData/SampleAppForMedium
-testPlan SampleApp_AccessibilityAudit_1.xctestplan
-parallel-testing-enabled YES
-parallel-testing-worker-count 2
-quiet

Sources:

--

--

Sahil Sharma

QA Automation | iOS Developer | SDET - I love coding, reading, health & fitness, and travelling.