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

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