Xcode 7 UI Testing: cómo descartar una serie de alertas del sistema en código

Estoy escribiendo casos de prueba de UI usando la nueva característica de prueba de interfaz de usuario Xcode 7. En algún momento de mi aplicación, solicito permiso al usuario para acceder a la cámara y enviar notificaciones. Así que aparecerán dos ventanas emergentes de iOS: "MyApp Would Like to Access the Camera" ventana emergente "MyApp Would Like to Send You Notifications" "MyApp Would Like to Access the Camera" y la ventana emergente "MyApp Would Like to Send You Notifications" . Me gustaría que mi prueba descarte ambas ventanas emergentes.

La grabación de UI generó el siguiente código para mí:

 [app.alerts[@"cameraAccessTitle"].collectionViews.buttons[@"OK"] tap]; 

Sin embargo, [app.alerts[@"cameraAccessTitle"] exists] resuelve como falso, y el código anterior genera un error: Error de Assertion Failure: UI Testing Failure - Failure getting refresh snapshot Error Domain=XCTestManagerErrorDomain Code=13 "Error copying attributes -25202" : Error de Assertion Failure: UI Testing Failure - Failure getting refresh snapshot Error Domain=XCTestManagerErrorDomain Code=13 "Error copying attributes -25202" .

Entonces, ¿cuál es la mejor manera de descartar una stack de alertas del sistema en prueba? Las ventanas emergentes del sistema interrumpen el flujo de mi aplicación y fallan mis casos de prueba de UI normales de inmediato. De hecho, se agradecen todas las recomendaciones sobre cómo puedo evitar las alertas del sistema para poder reanudar las pruebas del flujo habitual.

Esta pregunta podría estar relacionada con esta publicación SO que tampoco tiene una respuesta: Xcode7 | Pruebas de interfaz de usuario de Xcode | ¿Cómo manejar la alerta de servicio de ubicación?

Gracias por adelantado.

    Xcode 7.1

    Xcode 7.1 finalmente ha solucionado el problema con las alertas del sistema. Sin embargo, hay dos pequeños errores.

    En primer lugar, debe configurar un “controlador de interupción UI” antes de presentar la alerta. Esta es nuestra manera de decirle al marco cómo manejar una alerta cuando aparece.

    En segundo lugar, después de presentar la alerta debe interactuar con la interfaz. Simplemente tocando la aplicación funciona bien, pero es obligatorio.

     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 

    El “Diálogo de ubicación” es solo una cadena para ayudar al desarrollador a identificar a qué controlador se accedió, no es específico para el tipo de alerta.

    Creo que el hecho de regresar true del controlador lo marca como “completo”, lo que significa que no se volverá a llamar. Para su situación, trataría de devolver false para que la segunda alerta vuelva a activar el controlador.

    Xcode 7.0

    Lo siguiente descartará una sola “alerta de sistema” en Xcode 7 Beta 6:

     let app = XCUIApplication() app.launch() // trigger location permission dialog app.alerts.element.collectionViews.buttons["Allow"].tap() 

    Beta 6 introdujo una serie de correcciones para UI Testing y creo que esta fue una de ellas.

    También tenga en cuenta que estoy llamando -element directamente en -alerts . Llamar a un elemento de XCUIElementQuery obliga al marco a elegir el elemento coincidente “único” en la pantalla. Esto funciona muy bien para alertas donde solo puede tener una visible a la vez. Sin embargo, si prueba esto para una etiqueta y tiene dos tags, el marco generará una excepción.

    C objective

     -(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"]; } 

    Rápido

     addUIInterruptionMonitorWithDescription("Description") { (alert) -> Bool in alert.buttons["Allow"].tap() alert.buttons["OK"].tap() return true } 

    Gosh. Siempre toca “No permitir” aunque deliberadamente digo “Permitir”

    Al menos

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

    me permite seguir y hacer otras pruebas.

    ¡Dios! No me gusta cómo XCTest tiene el peor momento para lidiar con Alertas UIView. Tengo una aplicación donde recibo dos alertas, la primera quiere que seleccione “Permitir” para habilitar los servicios de ubicación para los permisos de la aplicación, luego, en una página emergente, el usuario tiene que presionar un UIButton llamado “Activar ubicación” y finalmente hay un alerta de alerta de SMS en un UIViewAlert y el usuario tiene que seleccionar “OK”. El problema que teníamos no era poder interactuar con las Alertas del sistema, sino también con una condición de carrera en la que el comportamiento y su aparición en la pantalla eran inoportunos. Parece que si usa el alert.element.buttons["whateverText"].tap la lógica de XCTest es seguir presionando hasta que se agote el tiempo de la prueba. Así que, básicamente, sigue presionando cualquier cosa en la pantalla hasta que todas las alertas del sistema estén fuera de la vista.

    Esto es un truco, pero esto es lo que funcionó para mí.

      func testGetPastTheStupidAlerts(){ let app = XCUIApplication() app.launch() if app.alerts.element.collectionViews.buttons["Allow"].exists { app.tap() } app.buttons["TURN ON MY LOCATION"].tap() } 

    La cadena “Permitir” se ignora por completo y la lógica de app.tap() se llama evreytime y se puede ver una alerta y, finalmente, el botón al que quería llegar [“Activar ubicación”] es accesible y el pase de prueba

    ~ Totalmente confundido, gracias Apple.

    Lo único que encontré que solucionó de manera confiable esto fue configurar dos pruebas separadas para manejar las alertas. En la primera prueba, llamo a app.tap() y no hago nada más. En la segunda prueba, vuelvo a llamar a app.tap() y luego hago el trabajo real.

    En xcode 9.1, las alertas solo se están manejando si el dispositivo de prueba tiene iOS 11 . No funciona en versiones anteriores de iOS, ej. 10.3, etc. Referencia: https://forums.developer.apple.com/thread/86989

    Para manejar alertas usa esto:

     //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 

    Para los que están buscando descripciones específicas para diálogos de sistema específicos (como lo hice) no hay ninguno 🙂 la cadena es solo para fines de seguimiento de probadores. Enlace de documento de manzana relacionado: https://developer.apple.com/documentation/xctest/xctestcase/1496273-adduiinterruptionmonitor


    Actualización: xcode 9.2

    El método a veces se activa a veces no. La mejor solución para mí es cuando sé que habrá una alerta del sistema, agrego:

     sleep(2) app.tap() 

    y la alerta del sistema se ha ido

    La respuesta de @Joe Masilotti es correcta y gracias por eso, me ayudó mucho 🙂

    Solo quisiera señalar una cosa, y es que UIInterruptionMonitor detecta todas las alertas del sistema presentadas en serie JUNTAS , de modo que la acción que aplica en el controlador de finalización se aplica a cada alerta ( “No permitir” o “Aceptar” “ ). Si desea manejar las acciones de alerta de manera diferente, debe verificar, dentro del controlador de finalización, qué alerta se presenta actualmente, por ejemplo, verificando su texto estático, y luego la acción se aplicará solo en esa alerta.

    Aquí hay un pequeño fragmento de código para aplicar la acción “No permitir” en la segunda alerta, en una serie de tres alertas y una acción “Aceptar” en las otras dos:

     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() 

    Suena como el enfoque para implementar el acceso a la cámara y las notificaciones se engarzan como dices, pero no se administran físicamente y se dejan a la suerte cuando y cómo se muestran.

    Sospecho que uno se desencadena por el otro y cuando se hace clic en un progtwig, también elimina el otro (lo que Apple probablemente nunca permitiría)

    ¿Piensa que está pidiendo permiso a los usuarios y luego toma la decisión en su nombre? ¿Por qué? Porque no puedes hacer que tu código funcione, tal vez.

    Cómo solucionar: trace donde estos dos componentes están activando los diálogos emergentes, ¿a dónde se están llamando ?, reescriba para activar solo uno, envíe una NSNotificación cuando se haya completado un diálogo para activar y mostrar el restante.

    Desaconsejaría seriamente el enfoque de hacer clic programáticamente en los botones de diálogo destinados al usuario.