Тестирование пользовательского интерфейса Xcode 7: как отклонить серию системных предупреждений в коде
Я пишу тестовые примеры UI, используя новую функцию тестирования Xcode 7 UI. В какой-то момент моего приложения я прошу у пользователя разрешения на доступ к камере и 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 UI тесты | как обрабатывать оповещения службы определения местоположения?
спасибо продвижение.
9 ответов
Xcode 7.1
Xcode 7.1, наконец, исправил проблему с системными предупреждениями. Есть, однако, два небольших gotchas.
во-первых, вам нужно настроить "обработчик UI Interuption" перед представлением предупреждения. Это наш способ рассказать структуре, как обрабатывать предупреждение, когда оно появляется.
во-вторых, после вручения уведомления вы должны взаимодействовать с интерфейсом. Просто нажав на приложение работает просто отлично, но требуется.
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()
Beta 6 представила множество исправлений для тестирования пользовательского интерфейса, и я считаю, что это был один из них.
Также обратите внимание, что я называю -element
непосредственно на -alerts
. Зову -element
на XCUIElementQuery
заставляет фреймворк выбрать" один и только " соответствующий элемент на экране. Это отлично работает для предупреждений, где вы можете иметь только один видимый в то время. Однако, если вы попробуете это для метки и имеете две метки, платформа вызовет исключение.
Гоша. Он всегда нажимает на "не позволяйте", хотя я намеренно говорю нажмите на"Разрешить"
по крайней мере
if app.alerts.element.collectionViews.buttons["Allow"].exists {
app.tap()
}
позволяет мне двигаться дальше и делать другие тесты.
С
-(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"];
}
Свифт
addUIInterruptionMonitorWithDescription("Description") { (alert) -> Bool in
alert.buttons["Allow"].tap()
alert.buttons["OK"].tap()
return true
}
Бог! Я ненавижу, как XCTest имеет худшее время работы с предупреждениями UIView. У меня есть приложение, в котором я получаю 2 предупреждения первый хочет, чтобы я выбрал "разрешить", чтобы включить службы местоположений для разрешений приложений, затем на странице заставки пользователь должен нажать UIButton под названием "включить местоположение" и, наконец, есть уведомление sms-оповещение в 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()
}
строка "Allow" полностью игнорируется, а логика -app.tap()
называется evreytime предупреждение находится в поле зрения и, наконец, кнопка, которую я хотел reach ["включить местоположение"] доступен и тестовый проход
~полностью запутался, спасибо Apple.
единственное, что я обнаружил, что это надежно исправлено, - это настроить два отдельных теста для обработки предупреждений. В первом тесте я вызываю app.tap()
и ничего больше. Во втором тесте я вызываю app.tap()
снова, а затем сделать реальную работу.
на 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
для тех, кто ищет конкретные описания для конкретных системных диалогов (как и я), нет :) строка предназначена только для целей отслеживания тестировщиков. Связанная ссылка на документ apple: https://developer.apple.com/documentation/xctest/xctestcase/1496273-adduiinterruptionmonitor
обновление: xcode 9.2
метод иногда срабатывает, иногда нет. Лучший обходной путь для меня, когда я знаю, что будет системное предупреждение, я добавляю :
sleep(2)
app.tap()
и системное оповещение пропало
@Joe Masilotti's ответ правильный и спасибо за это, это помогло мне много :)
Я просто хотел бы указать на одну вещь, и это UIInterruptionMonitor догоняет все системные оповещения, представленные в серии вместе, чтобы действие, которое вы применяете в обработчике завершения, применялось к каждому оповещению ("не разрешать" или "OK"). Если вы хотите обрабатывать действия оповещения по-разному, вы должны проверьте внутри обработчика завершения, какое предупреждение в данный момент представлено, например, проверив его статический текст, и тогда действие будет применено только к этому предупреждению.
вот небольшой фрагмент кода для применения "не разрешать" действие на втором оповещении, в серии из трех предупреждений, и "OK" действие на оставшихся двух:
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
}
app.tap()
звучит как подход к реализации доступа к камере и уведомления резьбовые, как вы говорите, но не физически управляемые и оставлены на волю случая, когда и как они отображаются.
Я подозреваю, что один запускается другим, и когда он программно щелкнул, он также стирает другой (что Apple, вероятно, никогда не позволит)
подумайте об этом, вы просите разрешения пользователей, а затем принимаете решение от их имени? Почему? Потому что ты не можешь получить возможно, ваш код для работы.
Как исправить-трассировка, где эти два компонента запускают всплывающие диалоги - где они вызываются?, перепишите, чтобы вызвать только один, отправьте NSNotification, когда один диалог был завершен, чтобы вызвать и отобразить оставшийся.
Я бы серьезно препятствовал подходу к программному щелчку диалоговых кнопок, предназначенных для пользователя.