ios - unit - Xcode 7 UI Testing: cómo descartar una serie de alertas del sistema en código
unit test ios swift (9)
C objetivo
-(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
}
Estoy escribiendo casos de prueba de IU usando la nueva función Xcode 7 UI Testing.
En algún momento de mi aplicación, le pido al usuario permiso para acceder a la cámara y enviar notificaciones.
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 en 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 pila de alertas del sistema en la prueba? Las ventanas emergentes del sistema interrumpen el flujo de mi aplicación y fallan mis casos de prueba de IU normales de inmediato. De hecho, se agradece cualquier recomendación 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 | Xcode UI Pruebas | ¿Cómo manejar la alerta del servicio de ubicación?
Gracias por adelantado.
Xcode 7.1
Xcode 7.1 finalmente ha solucionado el problema con las alertas del sistema. Hay, sin embargo, dos pequeñas trampas.
Primero, debe configurar un "Controlador de interrupción de la interfaz de usuario" antes de presentar la alerta. Esta es nuestra forma de decirle al framework cómo manejar una alerta cuando aparece.
En segundo lugar, después de presentar la alerta, debe interactuar con la interfaz. Simplemente tocar 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 volver
true
desde el 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 active el controlador nuevamente.
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 las pruebas de IU y creo que esta fue una de ellas.
También tenga en cuenta que estoy llamando
-element
directamente en
-alerts
.
Llamar a
-element
en un
XCUIElementQuery
obliga al marco a elegir el elemento coincidente "one and only" en la pantalla.
Esto funciona muy bien para alertas donde solo puede tener uno visible a la vez.
Sin embargo, si intenta esto para una etiqueta y tiene dos etiquetas, el marco generará una excepción.
¡Dios!
Odio que XCTest tenga el peor momento para lidiar con las Alertas UIView.
Tengo una aplicación donde recibo 2 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 de bienvenida, el usuario tiene que presionar un botón UIB llamado "Activar ubicación" y finalmente hay un notificación de alerta de SMS en un UIViewAlert y el usuario tiene que seleccionar "Aceptar".
El problema que teníamos era no poder interactuar con las Alertas del sistema, sino también una condición de carrera donde el comportamiento y su aparición en la pantalla eran inoportunos.
Parece que si usa
alert.element.buttons["whateverText"].tap
La lógica de XCTest es seguir presionando hasta que se agote el tiempo de la prueba.
Básicamente, sigue presionando cualquier cosa en la pantalla hasta que todas las alertas del sistema estén claras.
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 una alerta está a la vista y, finalmente, el botón que quería alcanzar ["Activar ubicación"] es accesible y el pase de prueba
~ Totalmente confundido, gracias Apple.
Gosh Siempre toca "No permitir" a pesar de que deliberadamente digo que toque "Permitir"
Al menos
if app.alerts.element.collectionViews.buttons["Allow"].exists {
app.tap()
}
me permite seguir adelante y hacer otras pruebas.
La respuesta de @Joe Masilotti es correcta y gracias por eso, me ayudó mucho :)
Solo me gustaría señalar una cosa, y es que UIInterruptionMonitor detecta todas las alertas del sistema presentadas en la serie JUNTAS , para que la acción que aplique en el controlador de finalización se aplique a cada alerta ( "No permitir" u "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 la acción "Aceptar" en las dos restantes:
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()
Lo único que encontré que solucionó esto de manera confiable 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, llamo a
app.tap()
nuevamente y luego hago el trabajo real.
Para aquellos que buscan descripciones específicas para cuadros de diálogo específicos del sistema (como lo hice) no hay ninguno :) la cadena es solo para fines de seguimiento de probadores. Enlace relacionado al documento de Apple: 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
Parece que el enfoque para implementar el acceso a la cámara y las notificaciones se enhebran como usted dice, pero no se manejan físicamente y se dejan al azar cuando y cómo se muestran.
Sospecho que uno es activado por el otro y cuando se hace clic programáticamente, también elimina al otro (que Apple probablemente nunca permitiría)
¿Piensas que estás pidiendo permiso a los usuarios y luego tomando la decisión en su nombre? ¿Por qué? Porque tal vez no puedas hacer que tu código funcione.
Cómo solucionarlo: rastree dónde están activando estos dos componentes los diálogos emergentes: ¿dónde se llaman ?, vuelva a escribir para activar solo uno, envíe una NSNotification cuando se haya completado un diálogo para activar y mostrar el restante.
Desalentaría seriamente el enfoque de hacer clic programáticamente en botones de diálogo destinados al usuario.
En xcode 9.1, las alertas solo se manejan si el dispositivo de prueba tiene iOS 11 . No funciona en versiones anteriores de iOS, por ejemplo, 10.3, etc. Referencia: https://forums.developer.apple.com/thread/86989
Para manejar alertas use 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