Removing Interruption Monitors In Xctest: A Step-By-Step Guide

how to remove interruption monitors xctest

Unexpected interruptions can cause UI tests to fail. To prevent this, you can add a UI interruption monitor to your XCUITests. The XCTest instance method addUIInterruptionMonitor() can be used to monitor and react to system alerts. However, this method should only be used for alerts presented outside the normal workflow. For alerts that are part of the normal flow, you can use standard XCTestCase methods.

shundigital

Using the addUIInterruptionMonitor() instance method

The `addUIInterruptionMonitor()` instance method is used to register a handler that is called when the UI is interrupted by an alert. This method is part of the XCTest framework and can be used to intercept and interact with system alerts and dialogs.

Swift

Override func setUp() {

ContinueAfterFailure = false

App.launch()

AddUIInterruptionMonitor(withDescription: "System Dialog") { [self] alert -> Bool in

Alert.buttons ["Allow"].tap()

Return true

}

app.tap()

}

In this example, the `addUIInterruptionMonitor()` method is added to the `setUp()` method. The withDescription parameter provides a description of the handler for debugging purposes. The handler block is then defined, which taps the "Allow" button on the alert and returns `true` to indicate that the alert has been handled. Finally, the app.tap() method is called to interact with the app and trigger the handler.

The `addUIInterruptionMonitor()` method can be used to handle different types of system alerts and dialogs, such as notifications, microphone access, and location permissions. It is important to note that the method is triggered only when the UI is interrupted by an alert, and not when the alert is a direct response to a test action.

Additionally, the `addUIInterruptionMonitor()` method follows a last-in, first-out (LIFO) order. This means that the last UI interruption monitor added will be the first one to be triggered. If a monitor returns `false`, indicating that it could not handle the interruption, the next monitor in the stack will be executed.

Swift

Import XCTest

Class AppleMapsUITests: XCTestCase {

Let app = XCUIApplication(bundleIdentifier: "com.apple.Maps")

Override func setUpWithError() throws {

App.resetAuthorizationStatus(for: .location)

ContinueAfterFailure = false

}

override func tearDownWithError() throws {

App.terminate()

}

func testOpenAppleMaps() throws {

App.launch()

// Add a UI Interruption Monitor

Let locationDialogMonitor = addUIInterruptionMonitor(withDescription: "Location Permission Alert") { [self] alertElement -> Bool in

For i in 0..

Let buttonElement = alertElement.buttons.element(boundBy: i)

Print("Button Label: \(buttonElement.label)")}

Return true

}

// Performing an arbitrary action in the Maps app

// This action will get blocked by the permission alert, triggering our UI Interruption Monitor

App.swipeUp()

// Remove the UI Interruption Monitor

Self.removeUIInterruptionMonitor(locationDialogMonitor)

}

}

In this example, the `AppleMapsUITests` class launches the Apple Maps app, which prompts for location permission. The `addUIInterruptionMonitor()` method is used to register a handler for the location permission alert. The handler prints the labels of the buttons in the alert and returns `true`. Then, an arbitrary action (swiping up) is performed in the Maps app, which triggers the location permission alert and the UI interruption monitor. Finally, the UI interruption monitor is removed using the `removeUIInterruptionMonitor()` method.

shundigital

Understanding when to use interruption handlers

Interruption handlers, also known as UI interruption handlers, are closures that are invoked by XCTest when a UI interruption occurs. They have been a part of XCTest for many years, but it's not always clear when to use them. Here are some key points to help you understand when to utilise interruption handlers:

  • Nature of Interruptions: Interruptions are typically unexpected or non-deterministic. They are not triggered directly by a test action, such as clicking a button. If your app encounters elements that block access to other elements unexpectedly, it's a good indication that you need to use interruption handlers.
  • Handling Complex Interruptions: Interruption handlers are particularly useful when dealing with complex or dynamic interruptions. For example, if an interruption requires specific logic or decisions based on the UI and contents of the interrupting element, interruption handlers allow you to design custom logic to handle these situations effectively.
  • Multiple Interruptions and Stack Management: XCTest maintains a stack of interruption handlers and invokes them in reverse order (LIFO). This means you can register multiple interruption handlers simultaneously, and they will be executed in the order they were added. If one handler is unable to handle the interruption, the next handler in the stack will be invoked. This stack management ensures that you can address various types of interruptions effectively.
  • Automatic and Manual Removal: Interruption handlers are automatically removed at the end of a test. However, if you know the specific scenarios or alerts that may trigger interruptions, you can manually remove the corresponding handlers once they have been handled. This can be done using the removeUIInterruptionMonitor() method and providing the monitor's identifier token.
  • Built-in Interruption Handlers: iOS and macOS have built-in interruption handlers for common interruptions. For example, iOS handles interrupting elements with a cancel or default button, and macOS handles user permission dialogs by selecting "Don't Allow". Understanding these built-in handlers will help you decide when to implement custom interruption handlers.
  • Expected Alerts vs. Interruptions: It's important to distinguish between expected alerts and interruptions. Expected alerts are often deterministic and the direct result of a UI test action. Unlike interruptions, expected alerts should be handled as part of your test validation process using standard queries and events.

By considering these points, you can effectively determine when to use interruption handlers in your testing process. They are a powerful tool to handle UI interruptions gracefully, ensuring that your tests can continue even when faced with unexpected elements blocking access to other UI elements.

shundigital

Handling alerts that are expected to show up

Alerts that are expected to show up should be handled as part of your test and should participate in its validation process. Expected alerts are often deterministic and the direct result of an action performed by the UI test.

Func testDeleteRecipe() throws {

Let breadCell = cell(recipeName: "Banana Bread")

DeleteCell(breadCell)

Let alert = app.alerts ["Delete Recipe"].firstMatch

Let alertExists = alert.waitForExistence(timeout: 30)

XCTAssert(alertExists, "Expected alert to show up")

Let description = """

"""

Let alertDescription = alert.staticTexts [description]

XCTAssert(alertDescription.exists)

Alert.buttons ["Delete"].tap()

XCTAssertFalse(breadCell.exists)

}

In this example, the 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 wait for existence APIs. Once it appears on screen, 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.

It's important to note that expected alerts are different from interruptions. UI interruptions are unexpected or at least not deterministic. The appearance of an alert in direct response to a test action, such as clicking a button, is not an interruption.

Here's another example of handling an expected alert that results from the use of protected resources:

Func testAddingPhotosFirstTime() throws {

Let app = XCUIApplication()

App.resetAuthorizationStatus(for: .photos)

App.launch()

// Test code…

}

In this example, we are testing 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 launched the app after the reset. After that, we continue with our usual test code, verifying that the alert appears after requesting access to the protected resource and dismissing the alert.

shundigital

Using XCTNSPredicateExpectation() to prove interruption

When unexpected system alerts interrupt your test from interacting with UI elements, it can cause failures. To handle these interruptions, XCTest offers the `addUIInterruptionMonitor()` method, which allows you to monitor and react to system alerts.

However, another approach to handling interruptions involves using `XCTNSPredicateExpectation()`. This is particularly useful when you want to test asynchronous behaviour that changes the state of the system as a result of a method call without a callback to hook into.

`XCTNSPredicateExpectation()` is an XCTest feature that allows you to test asynchronous code. It is used when the behaviour you want to test changes the state of the system due to a method call, but there is no callback to hook into.

Let's say you have an ``AsyncWorkPerformer` object with a `toggleAsynchronously(after:)` method. This method changes the value of the `flag` property from `false` to `true`. To test this using `XCTNSPredicateExpectation()`, you can follow these steps:

Arrange: Create an instance of `AsyncWorkPerformer` and set up the expectation.

Swift

Let sut = AsyncWorkPerformer()

Let expectation = XCTNSPredicateExpectation(predicate: NSPredicate { _, _ in sut.flag }, object: .none)

Act: Call the `toggleAsynchronously(after:)` method on the `sut`.

Swift

Sut.toggleAsynchronously(after: 0.1)

Assert: Wait for the expectation to be fulfilled with an appropriate timeout.

Swift

Wait(for: [expectation], timeout: 2)

In this example, the `XCTNSPredicateExpectation()` is set up to expect the `flag` property of the `AsyncWorkPerformer` object to be `true`. After calling the `toggleAsynchronously(after:)` method, you wait for the expectation to be fulfilled with a timeout of 2 seconds.

It is important to note that `XCTNSPredicateExpectation()` requires a minimum timeout of 1.1 seconds; otherwise, it will fail, regardless of whether the behaviour under test occurred. This is because `XCTNSPredicateExpectation()` uses a polling mechanism with a long sampling interval, making it slower than other approaches.

To avoid slowing down your tests, an alternative approach is to use Nimble's `toEventually` API, which checks its condition every 10 milliseconds by default, resulting in faster and clearer tests.

shundigital

Dismissing alerts with the correct syntax

Using addUIInterruptionMonitor()

XCTest provides the `addUIInterruptionMonitor() method to monitor and react to system alerts. This method allows you to specify a custom closure that will be executed when an alert blocks a touch action. Here's an example:

Swift

Let locationDialogMonitor = addUIInterruptionMonitor(withDescription: "Location Permission Alert") { (alertElement) -> Bool in

For i in 0..

Let buttonElement = alertElement.buttons.element(boundBy: i)

Print("Button Label: \(buttonElement.label))"

}

Return true

}

In this code snippet, we create a UI interruption monitor for handling the "Location Permission Alert". The closure iterates through the buttons in the alert and prints their labels. Finally, we return `true` to indicate that we have handled the interruption.

Tapping Specific Buttons

You can also dismiss alerts by directly tapping specific buttons within them. Here's an example:

Swift

Let systemAlerts = XCUIApplication (bundleIdentifier: "com.apple.springboard").alerts

If systemAlerts.buttons["Allow"].exists {

SystemAlerts.buttons["Allow"].tap()

}

In this code, we first access the system alerts using `XCUIApplication` with the bundle identifier "com.apple.springboard". We then check if the "Allow" button exists and tap it if it does.

Using addUIInterruptionMonitorWithDescription()

Another approach is to use the `addUIInterruptionMonitorWithDescription()` method, which allows you to specify a description for the interruption monitor. Here's an example:

Swift

AddUIInterruptionMonitorWithDescription("Location Dialog") { (alert) -> Bool in

Alert.buttons["Allow"].tap()

Return true

}

In this code, we create an interruption monitor with the description "Location Dialog". Within the closure, we tap the "Allow" button in the alert and return `true` to indicate that we have handled the interruption.

Handling Multiple Alerts

If you need to handle multiple alerts in a sequence, you can use the `addUIInterruptionMonitor()` approach and modify the closure to handle different alerts based on their content. Here's an example:

Swift

AddUIInterruptionMonitor(withDescription: "Access to sound recording") { (alert) -> Bool in

If alert.staticTexts["MyApp would like to use your microphone for recording your sound."].exists {

Alert.buttons["Don’t Allow"].tap()

} else {

Alert.buttons["OK"].tap()

}

Return true

}

In this code, we check the content of the alert using `alert.staticTexts` and tap the appropriate button based on the alert's text.

Using XCUIApplication Context Switching

In some cases, you can use the `XCUIApplication` context switching feature introduced in Xcode 9.0 and above. This allows you to switch between app contexts and interact with system alerts directly. Here's an example:

Swift

Let app = XCUIApplication()

Let springboardApp = XCUIApplication(bundleIdentifier: "com.apple.springboard")

If springboardApp.alerts["FunHappyApp" would like permission to own your soul"].exists {

SpringboardApp.alerts.buttons["Allow"].tap()

}

In this code, we create two `XCUIApplication` instances: `app` for our test app and `springboardApp` for the system alerts. We then check if a specific alert exists and tap the "Allow" button if it does.

By utilising these approaches and tailoring them to your specific test case, you can effectively dismiss alerts with the correct syntax during your UI tests.

Frequently asked questions

A UI interruption is any element that unexpectedly blocks access to another element with which a UI test is trying to interact.

XCTest has an instance method called addUIInterruptionMonitor() that can be used to monitor and react to system alerts.

You can remove a UI interruption monitor by calling the removeUIInterruptionMonitor() method and providing the monitor's identifier token, which is the return value of calling addUIInterruptionMonitor.

UI interruption monitors should be used when an alert or other modal UI is not an expected part of the test workflow. If the alert is presented by the normal flow, you can use normal XCTestCase methods instead.

Written by
Reviewed by
Share this post
Print
Did this article help you?

Leave a comment