» » Interacting with alerts and permissions in iOS ui testing

Interacting with alerts and permissions in iOS ui testing

In this article, I want to talk about interacting with alerts and permissions in iOS tests and show how they can be effectively handled in my project.

This article will be useful for novice iOS automators, or developers who decide to study XCUITest and cover their project with ui tests.

As part of the article, we will analyze:

  • What are the alerts and permissions of your iOS app;

  • How to handle alerts in test code where they are part of the script;

  • How to handle permissions automatically;

  • How to implement a mechanism to enable and disable alerts and permissions by passing arguments when running tests.

What are alerts and permissions?

Alerts - provide the ability to provide users with critical information.
For example, an alert might tell the user that the app is asking for permission to send them notifications, and give them the option to approve or decline. Alerts appear when you first launch your application.

 

Permissions - Allow applications to request permission to use privacy settings. For example, such as Location Services to determine your exact location.

Permissions appear when you first enter the section to which you want to ask for permission.

 

How to handle alerts?

The ways to interact with alerts are to make them part of the script (onboarding/authorization, etc.) and click on the desired buttons from the test code, or to make a separate test in a set that explicitly runs first and passes all alerts the first time the application is launched.

Alerts are in the hierarchy of the current active app, sometimes it's your tester, sometimes it's springboard (the built-in app that manages the iOS home screen).

let springBoardApp = XCUIApplication(
    bundleIdentifier: "com.apple.springboard"
)

let app = XCUIApplication()

if springBoardApp.alerts.buttons["Allow"].waitForExistence(timeout: 1) {
		springBoardApp.alerts.buttons["Allow"].tap()
} else if app.alerts.buttons.element(boundBy: 1).waitForExistence(timeout: 1) {
		app.alerts.buttons["Allow"].tap()
}

The most efficient way is to handle alerts when they are part of a script.

Creating a separate test to handle all alerts on initial run can lead to instability in running the entire test suite. Often, a crash can occur when clicking alerts, which subsequently leads to a partial crash of the next tests in the suite.

How to handle permissions automatically?

You need to add a UI break monitor to the test project. XCTest has a method addUIInterruptionMonitor() that we can use to monitor and respond to permissions.

addUIInterruptionMonitor(withDescription: "Tracking Usage Permission Alert") {
    (alert) -> Bool in
    if alert.buttons["Allow"].exists {
        alert.buttons["Allow"].tap()
        self.app.activate()
        return true
    }
    return false    
}

It is most efficient to call addUIInterruptionMonitor() at the beginning of the test, where we know for sure that permission will appear in the script. Calling the handler in setUp() will not work automatically on all tests. Also, each permission needs a separate call to the UI interrupt monitor.

How to turn alerts and permissions on and off by passing arguments?

You can enable alerts and permissions to appear only in those tests where they will be part of the test script.

You will need to implement a control mechanism for adding delegates responsible for alerts and permissions to the initialization .

We implement a mechanism for enabling/disabling notifications about activity tracking. The ATTrackingManager mechanism is responsible for the appearance of the tracking manager dialog box in our application.

 

Let's create an extension for XCUIApplication, where we will write the launch function with the ability to pass arguments:

extension XCUIApplication {
	
	func launch(arguments: [String] = ["TrackingManager"]) {
	    if state != .notRunning {
	        terminate()
	        launchArguments = []
	    }
	
	    launchArguments = arguments
	
	    launch()
	    _ = wait(for: .runningForeground, timeout: 10)
	}

}

Next, we need to find out in which delegate we have implemented the call to the tracking manager dialog box. It is enough to enter ATTrackingManager in the search and take the name of the delegate.

final class AppsFlyerAppDelegate: NSObject, UIApplicationDelegate {

    func applicationDidBecomeActive(_ application: UIApplication) {
        // for iOS 13 and below - The IDFA will be collected by the SDK. The user will NOT be prompted for permission.
        if #available(iOS 14, *) {
            // Show the user the Apple IDFA consent dialog (AppTrackingTransparency)
            // Can be called in any place
            ATTrackingManager.requestTrackingAuthorization { _ in
            }
        }
        
        if let deviceId = CheckEmailAccessExperiment.obtainDeviceId(inPlace: "AppsFlyerInstance") {
            appsFlyerInstance.customerUserID = deviceId
        }
        
        appsFlyerInstance.start()
    }
}

We implement the handler in the AppDelegate and launch the ATTrackingManager in tests where the “TrackingManager” argument is not passed:

public class AppDelegate: UIResponder {
    
    let pushNotificationsAppDelegate = PushNotificationsAppDelegate()
    let appsFlyerAppDelegate = AppsFlyerAppDelegate()

    private lazy var appDelegates: [UIApplicationDelegate] = {

        var appDelegates: [UIApplicationDelegate] = [
            pushNotificationsAppDelegate
        ]
        
        if !ProcessInfo.processInfo.arguments.contains("TrackingManager") {
            appDelegates.append(appsFlyerAppDelegate)
        }
        
        return appDelegates
    }()
    
}

In this way, you can disable all alerts and permissions individually, as shown in the example above, or you can create one common argument to handle adding delegates to appDelegates responsible for all alerts and permissions .

By combining all three ways of interacting with alerts and permissions , you can keep your code clean and reduce the number of flags when running a test set.

Related Articles

Add Your Comment

reload, if the code cannot be seen

All comments will be moderated before being published.