Привет, Хабр!
Меня зовут Ахмат. Я 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, можно сделать ваш код чистым и уменьшить число флаков при прогоне тесового набора.