Discover how you can test your app for accessibility with every build. Learn how to perform automated audits for accessibility using XCTest and find out how to interpret the results. We'll also share enhancements to the accessibility API that can help you improve UI test coverage.
♪ ♪ Bhavya: Hi, my name is Bhavya, and I'm an engineer on the Accessibility team. In today's session, we're going to focus on how to perform accessibility audits in your app. First, we'll cover how easy it is to perform automated accessibility audits in your UI tests. And then we'll discuss how to expose elements in a way that gives us a great testing experience and a great accessibility experience at the same time. Let's start with accessibility audits. Testing is a fundamental component in the app development process. By writing tests, we're able to catch and fix bugs before we ship code. It's how we ensure the quality of the product. And an accessible product is a high-quality product. Approximately one in seven people worldwide have a disability that affects the way they interact with the world and their devices. People use tools like VoiceOver to interact with your apps in the way that's best for them. Delivering a high-quality product means delivering an application that's accessible for everyone so they can have the highest-quality experience with your app. I know from personal experience how accessibility can be a deep and complex subject. Let's explore how accessibility audits can make this a simple task.
Xcode ships with a tool called the Accessibility Inspector. This tool provides an easy way to find, diagnose, and fix accessibility issues within your app. One of the powerful ways in which I can leverage this tool is by performing audits on my app. The Inspector can audit individual views in your app for common accessibility issues.
This is my sample app. It has two tabs. The first tab provides me with motivational quotes, and the second lets me write down my thoughts for self-reflection. In the quote tab, I have a text view which displays the quote. And this text view is placed on top of a background image. There's also a New Quote button which refreshes the quote. I can launch the Accessibility Inspector and perform an audit of my app. The Inspector checks for all kinds of issues, like providing sufficient element descriptions and ensuring proper contrast. And the issues it finds are displayed in a table with detailed descriptions about each issue. Accessibility audits are powerful, and now they are automatable. You are now able to perform audits in your UI tests. Calling performAccessibilityAudit on your XCUIApplication will audit the current view for accessibility issues just as the Inspector does. There's no need for assertions: if any issues are found, your test automatically fails. Let's dive into a quick demo to see this in action.
I've opened my demo app in Xcode. It's written in Swift and utilizes standard UIKit views. I've already written a few passing tests which verify that the elements on the screen exist. For example, testQuoteTabView verifies that the image view and the text view exist in the quote tab. One thing to note is that these tests also help us test accessibility. In order for XCTest to find these views, they must be accessibility elements. That means if your UI tests can find the elements, so can our assistive technologies. It's great that I get a little bit of accessibility testing this way, but I want to add some audits to my tests to make sure I'm catching all kinds of issues. I'll create another test called testAccessibilityQuoteTabView. I'll do some setup to launch my app and navigate to the Quote tab.
And finally, I'll call performAccessibilityAudit on the application.
The audit can report multiple issues, so to allow my test to continue reporting issues after the first failure, I'll set continueAfterFailure to true.
That's it. Let's run the test by clicking on the test diamond.
Seems like my test failed. The issues are reported in-line within the Xcode source editor. My audit caught two issues: Element has no description, and the label is not human-readable. Let's figure out what the problematic elements are. I can dig deeper into these two issues by going to the Report navigator, clicking on Tests, and then clicking on the disclosure triangle next to my test. This view shows a detailed breakdown of the test run and the issues. For the first issue, "element has no description," I can double-click the element screenshot which shows me that the image view has no description. I can do so similarly for the second issue, which shows me that the label on the text view is not human-readable. Let's take a moment to discuss how to handle the issues my audit just found. It's important to explore each issue individually and fix it, as they will lead to real interaction or navigation issues for users of your app who rely on assistive technologies. It's also important to acknowledge that you may run into issues which should be filtered out and ignored. Perhaps the issue is a false positive or expected behavior. Accessibility audits makes it easy to ignore these kinds of issues. We'll touch on an example for ignoring issues later in the talk. To learn more about best practices in accessibility, please check out our 2018 talk titled "Deliver an Exceptional Accessibility Experience." Let's investigate the first issue my audit found: the accessibility label on the text view is not human-readable. If I inspect the text view in the Storyboard, I can see that the accessibility label has been set to QUOTE_TEXTVIEW. Users who rely on assistive technologies like VoiceOver will first hear the accessibility label, and then the accessibility value, like this. VoiceOver: QUOTE_TEXTVIEW, "Live one day at a time and make it a masterpiece." Bhavya: The label doesn't sound great, and ideally, VoiceOver should skip it and speak just the quote itself. I can delete the accessibility label, but then my UI tests will break, because they depend on this label to identify the text view. Ideally, this string should be set as the accessibility identifier. The accessibility identifier allows you to uniquely identify an element when writing UI tests without affecting the accessibility or UI experience. I'll head over to my Storyboard.
I'll select my text view, cut this string from the label, and paste it into the identifier.
The other issue my audit found was that the image view has no description. Typically, it's important that images are accessible with descriptive but succinct labels. However, in my app, this is a background image which is decorative. It isn't a part of the main content and doesn't add additional meaning to the quote itself. Ideally, technologies like VoiceOver should skip this image view. I can achieve this behavior by overriding accessibility elements on the view controller's view. By setting it to just the quote text view and the New Quote button, VoiceOver will no longer land on the image view. Let's head over to Xcode and do that now. I'll go to my view controller file and set accessibilityElements.
Awesome. Let's go back to my audit and run my test case to see if I fixed all the issues.
Sweet. My audit is now passing. You'll notice one of my UI tests is now failing, but we'll come back to that later.
When adding accessibility audits, you may run into issues which need to be filtered. As an example, let's say my audit found an issue with the contrast being too low on a specific label. After investigating, the contrast turns out to be just fine, and the issue seems to be a false positive. Let's explore how I can ignore this issue. The performAccessibilityAudit function takes in additional parameters. The first parameter allows me to specify an option set of the categories of audits that I want to run. These are categories like dynamic type and contrast, the same categories that you're already familiar with in the Accessibility Inspector. In this example, I'm choosing to audit for only dynamic type and contrast issues. The second parameter allows me to specify a closure. This closure is called on all the issues found by the audit and lets me choose which issues to ignore and which issues to report. I'll start by defining a variable called shouldIgnore to false. By default, issues should not be ignored. Let's say I'd like to ignore a contrast issue on an element with the label "My Label." I can get the XCUIElement associated with the issue using issue.element. If this element has the label "My Label" and the type of issue is a contrast issue, then I know I've got the right issue, so I'll set shouldIgnore to true. Setting it to true indicates that I'd like the issue to be ignored. At the end, I'll return shouldIgnore. If the conditions above aren't met, then shouldIgnore will be false, indicating the issue should be reported as a failure. And that's it. You can extend this example and customize the criteria for ignoring by using other properties, like the element type or the identifier, and so on. As you begin to write accessibility audits for your own apps, it's helpful to keep in mind the following considerations. An audit is limited to the elements on the screen. That means in order to provide complete coverage, you should add accessibility audit tests for all the different views your app may show. In the case of my sample app, I should add another test which navigates to the second tab and runs an audit. A quick way to immediately add audits for multiple tests is to override and perform the audit in teardown. You could define variables in the scope of the class. This way, tests can override these variables to opt in or out of the audit and to allow the tests to customize the closure for ignoring issues. Test plans are an excellent way to group audit-specific tests in your project. They allow you to selectively enable test targets, cases, or individual methods in the test plan. And finally, audits shouldn't substitute real testing with assistive technologies. Ultimately, testing your app by turning on technologies like VoiceOver or Dynamic Type is the best way to ensure a high-quality experience. You can achieve great accessibility and great testing without having to compromise on either. Automation elements allow you to expose elements specifically for the purpose of automation without affecting the accessibility of those elements. Now, in UIKit, you'll be able to leverage this API to expose exactly the elements you need for automation, while still being able to customize the accessibility for these elements at the same time. You may remember from earlier that as I fixed the issues from my audit, I also broke one of my UI tests. The image view doesn't seem to be available anymore. It's missing in my UI test because it's also missing in accessibility. Because this image view was decorative, I overrode accessibility elements to exclude it from accessibility. However, by doing so, I also caused it to become excluded from my UI test. Let's explore how automation elements can help me expose my image view to my UI test. I'll go to the view controller file in Xcode.
And I'll set automationElements on the view controller's view to the image view. Keep in mind that when overriding automationElements, you need to specify all the elements which need to be exposed to automation.
That means I also need to add the text view and the button to my list. When overriding automation elements, you are overriding the existing elements that are already exposed to automation. Let's try running our test case to see if it's passing again.
Awesome. We were able to write some great UI and accessibility tests and fix some accessibility issues too. Accessibility audits are a fantastic way to add easy, automated accessibility testing for your app. Fixing the issues identified by the audits helps ensure everyone can enjoy your app. Create great accessibility and automation experiences without having to pick one over the other. Automation elements allows you to expose elements specifically for your UI tests without impacting the accessibility experience. I encourage you to go to your UI tests and add a quick call to performAccessibilityAudit. Thank you.