Привет, Хабр!
Меня зовут Ахмат. Я iOS QA Automation Engineer - в Vivid Money.
В этой статье я хочу рассказать про взаимодействие с alerts и permissions в iOS тестах и показать, как их можно эффективно обрабатывать в своём проекте.
Данная статья будет полезна начинающим iOS-автоматизаторам, либо разработчикам, которые решили изучить XCUITest и покрыть свой проект ui-тестами.
В рамках статьи мы разберем:
Что такое alerts и permissions вашего iOS приложения;
Как обрабатывать alerts в коде теста, где они являются частью сценария;
Как автоматически обрабатывать permissions;
Как реализовать механизм включения и выключения alerts и permissions с помощью передачи аргументов при запуске тестов.
Что такое alerts и permissions?
Alerts - дают возможность предоставлять пользователям критически важную информацию.
Например, alert может сообщать пользователю, что приложение запрашивает разрешение на отправку им нотификаций, и давать им возможность подтвердить или отклонить. Alerts появляются при первичном запуске вашего приложения.

Permissions - позволяют приложениям запрашивать разрешение на использование настроек конфиденциальности. Например таких, как Location Services для определения вашего точного местоположения.
Permissions появляются при первичном входе в раздел, к которому требуется запросить у вас разрешение.

Как обрабатывать alerts?
Способы взаимодействия с alerts - сделать их частью сценария (онбординг/авторизация и т.д) и кликать на нужные кнопки из кода теста или делать отдельный тест в наборе, который явно запускается первым и проходит все alerts при первом запуске приложения.
Alerts находятся в иерархии текущего активного приложения, иногда это ваше тестируемое, иногда это springboard (встроенное приложение, которое управляет домашним экраном iOS).
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() }
Наиболее эффективный способ - это обрабатывать alerts, когда они являются частью сценария.
Создание отдельного теста для обработки всех alerts при первичном запуске может привести к нестабильности прогона всего тестового набора. Зачастую может произойти сбой во время прокликивания alerts, что в последствии приведет к частичному падению следующих тестов в наборе.
Как автоматически обрабатывать permissions?
В тестовый проект нужно добавить монитор прерывания пользовательского интерфейса. В XCTest есть метод, addUIInterruptionMonitor() который мы можем использовать для мониторинга и реагирования на появление 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 }
Наиболее эффективно вызывать addUIInterruptionMonitor() в начале теста, где мы точно знаем что по сценарию появится permission. Вызов обработчика в setUp() не будет отрабатывать автоматически на всех тестах. Так же для каждого permission нужен отдельный вызов монитора прерывания пользовательского интерфейса.
Как включать и выключать alerts и permissions с помощью передачи аргументов?
Можно включать появление alerts и permissions только в тех тестах, где они будут являться частью тестового сценария.
Потребуется реализовать механизм управления добавлением в инициализацию делегатов, отвечающих за alerts и permissions.
Реализуем механизм включения/выключения оповещения о трекинге активности. За появление диалогового окна трекинг менеджера в нашем приложении отвечает механизм ATTrackingManager.

Создадим расширение для XCUIApplication, где напишем функцию launch с возможностью передачи аргументов:
extension XCUIApplication { func launch(arguments: [String] = ["TrackingManager"]) { if state != .notRunning { terminate() launchArguments = [] } launchArguments = arguments launch() _ = wait(for: .runningForeground, timeout: 10) } }
Далее нам потребуется выяснить в каком делегате у нас реализован вызов диалогового окна трекинг менеджера. Достаточно в поиске ввести ATTrackingManager и взять название делегата.
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() } }
Реализуем обработчик в AppDelegate и запуск ATTrackingManager в тестах, где аргумент “TrackingManager” не передается:
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 }() }
Таким способом можно отключать все виды alerts и permissions по отдельности, как показано в примере выше или можно создать один общий аргумент для обработки добавления делегатов в appDelegates, отвечающий за все виды alerts и permissions.
Сочетая все три способа взаимодействия с alerts и permissions, можно сделать ваш код чистым и уменьшить число флаков при прогоне тесового набора.
