Video hosted by Apple at devstreaming-cdn.apple.com

Configure player

Close

WWDC Index does not host video files

If you have access to video files, you can configure a URL pattern to be used in a video player.

URL pattern

preview

Use any of these variables in your URL pattern, the pattern is stored in your browsers' local storage.

$id
ID of session: wwdc2020-10220
$eventId
ID of event: wwdc2020
$eventContentId
ID of session without event part: 10220
$eventShortId
Shortened ID of event: wwdc20
$year
Year of session: 2020
$extension
Extension of original filename: mp4
$filenameAlmostEvery
Filename from "(Almost) Every..." gist: [2020] [Session 10220] Handle in...

WWDC20 • Session 10220

Handle interruptions and alerts in UI tests

Developer Tools • iOS, macOS, tvOS, watchOS • 11:42

Learn how to anticipate potential interruptions to your app’s interface and build smart tests to identify them. UI interruptions often appear indeterminately, typically during onboarding or first launch, which can make them hard to track down. Learn how to understand interruptions, write stronger tests with UI interruption handlers, and manage expected alerts. To learn more about the latest improvements for testing your app in Xcode, check out “XCTSkip your tests”, “Get your test results faster”, and “Triage test failures with XCTIssue”.

Speaker: Dennis Weissmann

Open in Apple Developer site

Transcript

Hello and welcome to WWDC. Hi, everyone. Welcome to Handle Interruptions and Alerts in UI Tests. My name is Dennis, and I'm a software engineer on the Xcode team. In today's session, we'll define what a UI interruption is and how to handle interruptions utilizing interruption handlers. Interruption handlers have been part of XCTest for many years, but it's not always clear when they should be used.

In the second half of this session, I'll demonstrate how to interact with expected alerts. For example, the ones that appear when the app wants to get access to protected resources such as Bluetooth or location data for the first time. And lastly, we'll talk about how we can work with protected resources and how we can reset the authorization status so that you can test these kinds of alerts in a deterministic way.

Let's start with what is a UI Interruption? A UI Interruption is any element which unexpectedly blocks access to another element with which a UI Test is trying to interact. In the example on the right, you can see a simple recipes app. Imagine there's a UI Test that wants to tab one of the recipes to view it.

Even though there's a banner at the top, the banner is not considered a UI interruption because the banner's UI does not cover the table view row that the UI Test is going to tab. Now that we're in the recipe's detail view, we make sure that we actually navigated there by asserting that the UI elements appear as expected.

After that, we want to navigate back to the list of recipes to ensure that this part of navigation works as well. But now, the "Back" button is covered by the banner notification. So to be able to tap the button, the UI Test needs to interact with the banner first. In this case, the banner is considered a UI interruption.

Interrupting elements are most commonly banners, like we see in the example, alerts, dialogs or windows, but can be other types as well. The important thing to remember is that interruptions are unexpected or at least not deterministic. The appearance of an alert in direct response to a test selection, such as clicking a button, is not an interruption.

Now that we understand what a UI Interruption is, let's go ahead and explore how we can use UI interruption handlers to handle these interruptions. We've learned that interruptions are unexpected and appear nondeterministically. Per that definition, there is no way to handle interruptions efficiently using the usual automation APIs. Interruption handlers are closures that are invoked by XCTest when an interruption occurs. There can be multiple interruption handlers registered at anytime, and the order in which they are added is decisive of the order in which they are invoked.

XCTest keeps a stack of interruption handlers and invokes them in reverse order until one of them signals that it handled the interruption. You are free to use any of these handlers as general or as specific as you wish. Some handlers may just attempt to find a "Cancel" button and press it, others might make decisions based on the UI and contents of the interrupting element. If an interruption handler successfully handled an interruption, it returns "True" and the iTest continues.

If it was not able to handle the interruption, it returns "False" and the next handler on the stack gets invoked. UI interruption handlers are automatically removed at the end of the test or can be removed manually at anytime. At the lowest level in the stack of UI interruption handlers, XCTest provides its own implicit interruption handler that takes care of the most common interruptions for you. On iOS, XCTest handles interrupting elements if they have a "Cancel" button or a "Default" button. And new in Xcode 12, XCTest also implicitly handles banner notifications.

On macOS, XCTest implicitly handles user permission dialogs by clicking the "Don't Allow" button, and the Bluetooth Setup Assistant, which is well-known for interrupting UI Test flows, especially in CI where no keyboard is attached. Let's take a look at how all of this works in action. When I lived in Germany and studied computer science, my grandma not only helped me with my homework for uni, she also used to cook for me almost every day. Since I moved to the US, she's been really worried about my health and wants to make sure that I eat healthy food.

She had this idea of having an app that lists all her favorite recipes she inherited and collected over the years, so that I could easily cook the "good food" myself. She's an exemplary programmer and knows that she needs to have tests that validate her app's behavior. Here, you can see one of the iTests she wrote. She told me that this test would sometimes fail, but she has no idea why. I promised her to take a look at this. And sure enough, after several runs of the test, I was able to reproduce this.

The underlying issue here is that sometimes the connection to the server fails and the app cannot update the recipes. In that case, the app displays an alert, letting the user know that there was a problem and gives them the opportunity to retry. Now, for the purpose of this demo, I've shutdown the web server so that we can easily reproduce the issue ourselves. As you can see at the top, I've already added a UIInterruptionMonitor skeleton to the test setUpWithError method that we'll complete later. Let's run the test step-by-step. First, we create and launch the application.

Then, we try to tap one of the table view cells, but the alert is in the way. Since we're actually trying to interact with an element, the alert is considered interruption, and XCTest invokes our interruption handler. In our skeleton implementation, we just return "False" indicating that we did not handle the interruption. This is where XCTest implicit interruption handling kicks in. Once I step over here, you can see how XCTest presses the "Cancel" button to dismiss the alert and tap the cell.

Now, the test continues. Great. You can see that XCTest handled this interruption for us by clicking the "Cancel" button. But Grandma wants to make sure that this test used the latest relatable data, so pressing "Retry" here would be the better choice. Let's modify our interruption handler to press the "Retry" button instead. Inside the interruption handler, we check if the interrupting element is an alert and if it has a "Retry" button. If it has, we tap it, and return "True" to indicate that we handled the interruption and the test can continue. Let's run the test one more time.

Great. Now, our test handles these network interruptions by retrying to fetch the latest set of recipes instead of just canceling. Now that we know how to handle interruptions, let's see how to best handle alerts that are expected to show up. Unlike interruptions, expected alerts are often deterministic, and the direct result of an action performed by the UI Test. The majority of alerts are expected, and should, therefore, not be handled as an interruption, but as part of your test, and should participate in its validation process using standard queries and events.

In the demo, the test did not explicitly trigger the alert's appearance, and we don't know beforehand when it will appear. The server will probably respond correctly most of the time but not always. That's why they had to use a UI interruption handler. Let's look at a different example to see how it can interact with expected alerts. Here, our test swipes left on one of the recipes to remove it.

We know that after deleting a recipe an alert shows up asking if you really want to delete that recipe. Since the alert is expected to show up, we use traditional UI element query and waitForExistence APIs. Once it appears onscreen, we validate that it contains the text we expect.

Lastly, we dismiss the alert by confirming the action and validate that the row does not exist anymore. The expected alerts we've seen so far have all been in our control. This makes it straightforward to get the app on our test in the state we need to validate these scenarios. Protected resources, stuff like location, Bluetooth or the microphone, are very privacy sensitive, and the system needs to make sure that the user explicitly allowed each app access to these resources before letting the app access them.

Once the user interacted with the alert asking for access, their decision is stored by the system. In a test, there are two or more distinct branches that should be tested: How does the app respond to the user granting permission, and how does the app respond to the user denying permission? However, the user response is stored as system state, so after the first such interaction, the device is no longer in a clean state, and it is difficult or impossible to explore all the other branches. For more information about protected resources, please watch "Better Apps through Better Privacy" and "Your Apps and the Future of macOS Security" from WWDC 2018.

In Xcode 11.4, and iOS and tvOS 13.4, and macOS 10.15.4, we introduced API on XCUIApplication to reset the authorization status of protected resources. Resetting the authorization status of protected resources makes the app behave like it never asked the user for permission before. This allows you to retest these authorizations or initial launch experience workflows in a deterministic way. Note that these alerts did not originate from your app but from the system.

Therefore, you need to make sure to adjust your queries accordingly. Also note that resetting the authorization status of a protected resource may terminate the app process. This behavior's not exclusive to the iTesting, though, and also happens when the user changes the authorization status in settings while the app is running. Supported resources for all platforms are, for example, context, calendar, photos, microphone, camera and location. On iOS, we also support keyboard network access, Bluetooth, and new in Xcode 12 and iOS 14, health.

On macOS, we additionally support researching the access to various directories like the user's downloads or desktop folder. Here's an example of what a test looks like that validates the flow of accessing the user's photos for the first time. First, we reset the app's authorization status for "photos." Resetting the authorization status for photos terminates the app process, which is why we launch the app after the reset.

After that, we continue with our usual test code, where we find that the alert appears after requesting access to the protected resource and dismissing the alert. That's it. Let's recap what we talked about in this session. We learned that UI interruptions are unexpected, or at least not deterministic UI elements that block access to an element which the UI Test needs to interact with.

We covered UI interruption handlers, and when and how to use them. And that XCTest provides an implicit interruption handler that can handle most interruptions out of the box. We talked about expected alerts and how they're different from interruptions. And lastly, we saw how it can work with expected alerts that result from the use of protected resources by using new API in XCTest to thoroughly test the initial launch experience of your app. Grandma and I want to thank you for watching.