it-roy-ru.com

Тестирование пользовательского интерфейса Xcode 7: как отклонить серию системных предупреждений в коде

Я пишу тестовые примеры пользовательского интерфейса, используя новую функцию тестирования пользовательского интерфейса Xcode 7. В какой-то момент моего приложения я запрашиваю у пользователя разрешение на доступ к камере и Push-уведомление. Таким образом, появятся два всплывающих окна iOS: "MyApp Would Like to Access the Camera" popup и "MyApp Would Like to Send You Notifications" popup. Я хотел бы, чтобы мой тест отклонил оба всплывающих окна.

Запись интерфейса вызвала для меня следующий код: 

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

Заранее спасибо.

49
SeaJelly

Xcode 7.1

Xcode 7.1 наконец исправил проблему с системными оповещениями. Есть, однако, две маленькие ошибки.

Во-первых, вам нужно настроить «обработчик прерываний пользовательского интерфейса» перед тем, как представить предупреждение. Это наш способ рассказать фреймворку, как обрабатывать оповещение при его появлении.

Во-вторых, после подачи предупреждения вы должны взаимодействовать с интерфейсом. Простое нажатие на приложение работает просто отлично, но обязательно.

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 представила множество исправлений для UI Testing, и я считаю, что это был один из них.

Также обратите внимание, что я звоню -element непосредственно на -alerts. Вызов -element для XCUIElementQuery вынуждает каркас выбирать «один и только» соответствующий элемент на экране. Это прекрасно работает для оповещений, когда вы можете видеть только одно изображение за раз. Однако, если вы попробуете это для метки и будете иметь две метки, фреймворк вызовет исключение.

47
Joe Masilotti

Гоша. Он всегда нажимает на «Не разрешать», хотя я сознательно говорю, нажмите «Разрешить»

По крайней мере 

if app.alerts.element.collectionViews.buttons["Allow"].exists {
    app.tap()
}

позволяет мне двигаться дальше и делать другие тесты.

3
Ravindran Antonysamy

Цель - С

-(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
}
3
Sazzad Hissain Khan

Для тех, кто ищет конкретные описания для определенных системных диалогов (как я это сделал), их нет :), строка предназначена только для целей отслеживания тестировщиками. Связанная ссылка на документ Apple: https://developer.Apple.com/documentation/xctest/xctestcase/1496273-adduiinterruptionmonitor


Обновление: xcode 9.2

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

sleep(2)
app.tap()

и системное оповещение пропало

1
ergunkocak

Единственное, что я обнаружил, что надежно исправило это, было настроить два отдельных теста для обработки предупреждений. В первом тесте я вызываю app.tap() и больше ничего не делаю. Во втором тесте я снова вызываю app.tap() и затем выполняю реальную работу.

1
Kevin London

Бог! Я ненавижу то, что XCTest хуже всего работает с UIView Alerts. У меня есть приложение, в котором я получаю 2 оповещения, первое из которых требует, чтобы я выбрал «Разрешить», чтобы включить службы определения местоположения для разрешений приложения, затем на заставочной странице пользователь должен нажать кнопку UIB под названием «Включить местоположение», и, наконец, появляется Уведомление смс оповещение в 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, когда отображается предупреждение, и, наконец, кнопка, которую я хотел достичь [«Turn On Location»], становится доступной и тестовый проход 

~ Полностью смущен, спасибо Apple.

1
JJacquet

В 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
0
Hasaan Ali

@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["Don’t Allow"].tap()
        } else {
            alert.buttons["OK"].tap()
        }
        return true
    }
app.tap()
0
bra.Scene