Подтвердить что ты не робот

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 | Как справиться с оповещением о местоположении?

Спасибо заранее.

4b9b3361

Ответ 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, когда один диалог был завершен, чтобы вызвать и отобразить оставшуюся.

Я бы серьезно отказался от подхода к программному нажатию диалоговых кнопок, предназначенных для пользователя.