Xcode 7 UI Testing: как отклонить ряд системных предупреждений в коде
Я пишу тестовые примеры UI, используя новую функцию тестирования интерфейса Xcode 7. В какой-то момент моего приложения я спрашиваю у пользователя разрешение на доступ к камере и push-уведомление. Таким образом, появятся два всплывающих окна iOS: "MyApp Would Like to Access the Camera"
popup и "MyApp Would Like to Send You Notifications"
всплывающее окно. Я бы хотел, чтобы мой тест отклонил оба всплывающих окна.
Запись пользовательского интерфейса создала для меня следующий код:
[app.alerts[@"cameraAccessTitle"].collectionViews.buttons[@"OK"] tap];
Однако [app.alerts[@"cameraAccessTitle"] exists]
разрешает false, а код выше генерирует ошибку: Assertion Failure: UI Testing Failure - Failure getting refresh snapshot Error Domain=XCTestManagerErrorDomain Code=13 "Error copying attributes -25202"
.
Итак, какой лучший способ отклонить стек системных предупреждений в тесте? Всплывающие окна системы прерывают мой поток приложений и немедленно прерывают мои обычные тестовые примеры. Фактически, любые рекомендации относительно того, как я могу обойти системные предупреждения, чтобы я мог возобновить тестирование обычного потока, оцениваются.
Этот вопрос может быть связан с этим сообщением SO, который также не имеет ответа: Xcode7 | Тесты интерфейса Xcode | Как справиться с оповещением о местоположении?
Спасибо заранее.
Ответы
Ответ 1
Xcode 7.1
Xcode 7.1 наконец-то исправил проблему системными предупреждениями. Есть, однако, две небольшие ошибки.
Во-первых, перед представлением оповещения вам необходимо настроить "обработчик прерываний UI". Это наш способ сообщить структуре, как обращаться с предупреждением, когда оно появляется.
Во-вторых, после представления предупреждения вы должны взаимодействовать с интерфейсом. Просто прослушивание приложения работает отлично, но это необходимо.
addUIInterruptionMonitorWithDescription("Location Dialog") { (alert) -> Bool in
alert.buttons["Allow"].tap()
return true
}
app.buttons["Request Location"].tap()
app.tap() // need to interact with the app for the handler to fire
"Диалоговое окно" Расположение "- это всего лишь строка, которая помогает разработчику определить, к какому обработчику был обращен доступ, и не относится к типу предупреждения.
Я считаю, что возврат true
из обработчика означает его "завершенным", что означает, что он не будет вызываться снова. Для вашей ситуации я бы попробовал вернуть false
, чтобы второе предупреждение снова вызвало обработчик.
Xcode 7.0
Следующее отклонит одно "системное предупреждение" в Xcode 7 Beta 6:
let app = XCUIApplication()
app.launch()
// trigger location permission dialog
app.alerts.element.collectionViews.buttons["Allow"].tap()
Бета 6 представила множество исправлений для тестирования пользовательского интерфейса, и я считаю, что это был один из них.
Также обратите внимание, что я вызываю -element
непосредственно на -alerts
. Вызов -element
на XCUIElementQuery
заставляет фреймворк выбирать один и тот же элемент соответствия на экране. Это отлично подходит для предупреждений, где вы можете видеть только одно видимое за раз. Однако, если вы попробуете это для метки и имеете две метки, фреймворк будет создавать исключение.
Ответ 2
Гоша. Он всегда нажимает "Не разрешать", хотя я намеренно говорю, нажмите "Разрешить"
По крайней мере
if app.alerts.element.collectionViews.buttons["Allow"].exists {
app.tap()
}
позволяет мне двигаться дальше и делать другие тесты.
Ответ 3
Цель - C
-(void) registerHandlerforDescription: (NSString*) description {
[self addUIInterruptionMonitorWithDescription:description handler:^BOOL(XCUIElement * _Nonnull interruptingElement) {
XCUIElement *element = interruptingElement;
XCUIElement *allow = element.buttons[@"Allow"];
XCUIElement *ok = element.buttons[@"OK"];
if ([ok exists]) {
[ok tap];
return YES;
}
if ([allow exists]) {
[allow tap];
return YES;
}
return NO;
}];
}
-(void)setUp {
[super setUp];
self.continueAfterFailure = NO;
self.app = [[XCUIApplication alloc] init];
[self.app launch];
[self registerHandlerforDescription:@""MyApp" would like to make data available to nearby Bluetooth devices even when you're not using app."];
[self registerHandlerforDescription:@""MyApp" Would Like to Access Your Photos"];
[self registerHandlerforDescription:@""MyApp" Would Like to Access the Camera"];
}
Swift
addUIInterruptionMonitorWithDescription("Description") { (alert) -> Bool in
alert.buttons["Allow"].tap()
alert.buttons["OK"].tap()
return true
}
Ответ 4
Бог! Я ненавижу, как XCTest имеет худшее время, связанное с оповещениями UIView. У меня есть приложение, в котором я получаю 2 предупреждения, первый из которых хочет, чтобы я выбрал "Разрешить" для включения служб местоположений для разрешений приложений, а затем на странице всплеска пользователь должен нажать UIButton под названием "Включить местоположение" и, наконец, уведомление о смс в UIViewAlert, и пользователь должен выбрать "ОК". Проблема, с которой мы столкнулись, заключалась в невозможности взаимодействия с системными оповещениями, а также в условиях гонки, когда поведение и внешний вид на экране были безвременными. Кажется, что если вы используете alert.element.buttons["whateverText"].tap
, логика XCTest должна продолжать нажимать до тех пор, пока время теста не закончится. Поэтому в основном продолжайте нажимать что-либо на экране, пока все системные предупреждения не будут видны.
Это взломать, но это то, что сработало для меня.
func testGetPastTheStupidAlerts(){
let app = XCUIApplication()
app.launch()
if app.alerts.element.collectionViews.buttons["Allow"].exists {
app.tap()
}
app.buttons["TURN ON MY LOCATION"].tap()
}
Строка "Разрешить" полностью игнорируется, а логика app.tap()
называется evreytime, и появляется кнопка, которую я хотел достичь [ "Включить местоположение" ], и тестовый проход
~ Полностью смущен, спасибо Apple.
Ответ 5
Единственное, что я обнаружил, что это было надежно исправлено, это установить два отдельных теста для обработки предупреждений. В первом тесте я вызываю app.tap()
и ничего не делаю. Во втором тесте я снова вызываю app.tap()
, а затем выполняю настоящую работу.
Ответ 6
Для тех, кто ищет конкретные описания для определенных системных диалогов (как я это сделал), их нет :), строка предназначена только для целей тестирования тестеров. Связанная ссылка на документ Apple: https://developer.apple.com/documentation/xctest/xctestcase/1496273-adduiinterruptionmonitor
Обновление: xcode 9.2
Метод иногда срабатывает, иногда нет. Лучший обходной путь для меня, когда я знаю, что будет системное предупреждение, я добавляю:
sleep(2)
app.tap()
и системное оповещение пропало
Ответ 7
В xcode 9.1 предупреждения обрабатываются только в том случае, если тестовое устройство имеет iOS 11. Не работает на старых версиях iOS, например, 10.3 и т.д. Ссылка: https://forums.developer.apple.com/thread/86989
Для обработки предупреждений используйте это:
//Use this before the alerts appear. I am doing it before app.launch()
let allowButtonPredicate = NSPredicate(format: "label == 'Always Allow' || label == 'Allow'")
//1st alert
_ = addUIInterruptionMonitor(withDescription: "Allow to access your location?") { (alert) -> Bool in
let alwaysAllowButton = alert.buttons.matching(allowButtonPredicate).element.firstMatch
if alwaysAllowButton.exists {
alwaysAllowButton.tap()
return true
}
return false
}
//Copy paste if there are more than one alerts to handle in the app
Ответ 8
@Joe Masilotti ответ правильный и спасибо за это, мне очень помогло :)
Я просто хотел бы указать на одну вещь: UIInterruptionMonitor перехватывает все системные предупреждения, представленные в серии ВМЕСТЕ, так что действие, которое вы применяете в обработчике завершения, применяется к каждому предупреждению ("Не разрешать" или "ОК"). "). Если вы хотите обрабатывать действия предупреждений по-другому, вы должны проверить в обработчике завершения, какое предупреждение в данный момент представлено, например, проверяя его статический текст, и тогда действие будет применено только к этому предупреждению.
Вот небольшой фрагмент кода для применения действия "Не разрешать" ко второму предупреждению, в серии из трех предупреждений и действия "ОК" для оставшихся двух:
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["Dont Allow"].tap()
} else {
alert.buttons["OK"].tap()
}
return true
}
app.tap()
Ответ 9
@Джо-masilotti
Я использую Mac Mojave 10.14.3 и версию с исходным кодом - 0.59.1, fastlane. Но я не смог отключить системное оповещение. Я приложил свой код и экран предупреждения
Не могли бы вы мне помочь
введите описание изображения здесь введите описание изображения здесь
введите описание изображения здесь
Ответ 10
Похоже, что подход к внедрению доступа к камерам и уведомлениям потоки, как вы говорите, но физически не управляемы и не оставляете на волю случая, когда и как они отображаются.
Я подозреваю, что один из них запускается другим, и когда он программно нажат, он уничтожает и другой (что Apple, вероятно, никогда не позволит)
Подумайте об этом, вы просите разрешения у пользователей, а затем принимаете решение от их имени? Зачем? Потому что вы не можете заставить свой код работать.
Как исправить - трассировать, где эти два компонента запускают всплывающие диалоги - где они вызываются?, переписать для запуска только один, отправить NSNotification, когда один диалог был завершен, чтобы вызвать и отобразить оставшуюся.
Я бы серьезно отказался от подхода к программному нажатию диалоговых кнопок, предназначенных для пользователя.