Retraso / Espera en un caso de prueba de Xcode UI testing


182

Estoy tratando de escribir un caso de prueba usando la nueva Prueba de IU disponible en Xcode 7 beta 2. La aplicación tiene una pantalla de inicio de sesión donde realiza una llamada al servidor para iniciar sesión. Hay un retraso asociado con esto, ya que es una operación asincrónica.

¿Hay alguna forma de causar un retraso o mecanismo de espera en XCTestCase antes de continuar con los pasos?

No hay documentación adecuada disponible y revisé los archivos de encabezado de las clases. No pude encontrar nada relacionado con esto.

¿Alguna idea / sugerencia?


13
Creo que NSThread.sleepForTimeInterval(1)debería funcionar
Kametrixom

¡Excelente! Esto parece que funciona. Pero no estoy seguro de si es la forma recomendada de hacerlo. Creo que Apple debería dar una mejor manera de hacerlo. Podría tener que presentar un radar
Tejas HS

De hecho, realmente creo que está bien, es realmente la forma más común de pausar el hilo actual durante un tiempo determinado. Si desea más control, también puede ingresar a GCD (The dispatch_after, dispatch_queuestuff)
Kametrixom

@Kametrixom No marque el ciclo de ejecución: Apple introdujo las pruebas asincrónicas nativas en Beta 4. Vea mi respuesta para más detalles.
Joe Masilotti

2
Swift 4.0 -> Thread.sleep (forTimeInterval: 2)
uplearnedu.com

Respuestas:


168

Asynchronous UI Testing se introdujo en Xcode 7 Beta 4. Para esperar una etiqueta con el texto "¡Hola, mundo!" para aparecer puede hacer lo siguiente:

let app = XCUIApplication()
app.launch()

let label = app.staticTexts["Hello, world!"]
let exists = NSPredicate(format: "exists == 1")

expectationForPredicate(exists, evaluatedWithObject: label, handler: nil)
waitForExpectationsWithTimeout(5, handler: nil)

Se pueden encontrar más detalles sobre las pruebas de IU en mi blog.


19
Desafortunadamente, no hay forma de aceptar que el tiempo de espera haya pasado y seguir adelante; waitForExpectationsWithTimeoutautomáticamente fallará su prueba, lo cual es bastante desafortunado.
Jedidja

@Jedidja En realidad, esto no me sucede con XCode 7.0.1.
Bastian

@Bastian Hmm interesante; Tendré que volver a verificar esto.
Jedidja

1
no funciona para mi Aquí está mi muestra: let xButton = app.toolbars.buttons ["X"] let exist = NSPredicate (formato: "exist == 1") expectationForPredicate (existe, evaluadoWithObject: xButton, handler: nil) waitForExpectationsWithTimeout (10, handler: nil)
emoleumassi

El app.launch()parece simplemente relanzar la aplicación. ¿Es necesario?
Chris Prince

225

Además, solo puedes dormir:

sleep(10)

Como los UITests se ejecutan en otro proceso, esto funciona. No sé qué tan aconsejable es, pero funciona.


2
¡En algún momento necesitamos la forma de retrasar y no queremos que provoque un fracaso! gracias
Tai Le

13
La mejor respuesta que he visto :) Añadiría + 100 votos si pudiera :)
Bartłomiej Semańczyk

8
Me gusta NSThread.sleepForTimeInterval (0.2) ya que puede especificar retrasos de menos de un segundo. (sleep () toma un parámetro entero; solo son posibles múltiplos de un segundo).
Graham Perks

55
@GrahamPerks, sí, aunque también hay:usleep
mxcl

3
No es una sugerencia deficiente (no se entiende cómo funciona UITesting), pero incluso si fue una sugerencia deficiente, a veces no hay forma de crear una expectativa que funcione (¿el sistema alerta a alguien?), Así que esto es todo lo que tiene.
mxcl

78

iOS 11 / Xcode 9

<#yourElement#>.waitForExistence(timeout: 5)

¡Este es un gran reemplazo para todas las implementaciones personalizadas en este sitio!

Asegúrese de echar un vistazo a mi respuesta aquí: https://stackoverflow.com/a/48937714/971329 . ¡Allí describo una alternativa a la espera de solicitudes que reducirá en gran medida el tiempo de ejecución de sus pruebas!


Gracias @daidai Cambié el texto :)
blackjacx

1
Sí, este sigue siendo el enfoque al que me dirijo cuando lo uso XCTestCasey funciona de maravilla. No entiendo por qué los enfoques como sleep(3)se votan tan alto aquí, ya que extiende el tiempo de prueba artificialmente y realmente no es una opción cuando crece su conjunto de pruebas.
blackjacx

En realidad, requiere Xcode 9, pero también funciona en dispositivos / simuladores con iOS 10 ;-)
d4Rk

Sí, escribí eso en el titular de arriba. Pero ahora la mayoría de las personas deberían haberse actualizado al menos a Xcode 9 ;-)
blackjacx

77

Xcode 9 introdujo nuevos trucos con XCTWaiter

El caso de prueba espera explícitamente

wait(for: [documentExpectation], timeout: 10)

Delegados de instancia de camarero para probar

XCTWaiter(delegate: self).wait(for: [documentExpectation], timeout: 10)

La clase de camarero devuelve el resultado

let result = XCTWaiter.wait(for: [documentExpectation], timeout: 10)
switch(result) {
case .completed:
    //all expectations were fulfilled before timeout!
case .timedOut:
    //timed out before all of its expectations were fulfilled
case .incorrectOrder:
    //expectations were not fulfilled in the required order
case .invertedFulfillment:
    //an inverted expectation was fulfilled
case .interrupted:
    //waiter was interrupted before completed or timedOut
}

uso de muestra

Antes de Xcode 9

C objetivo

- (void)waitForElementToAppear:(XCUIElement *)element withTimeout:(NSTimeInterval)timeout
{
    NSUInteger line = __LINE__;
    NSString *file = [NSString stringWithUTF8String:__FILE__];
    NSPredicate *existsPredicate = [NSPredicate predicateWithFormat:@"exists == true"];

    [self expectationForPredicate:existsPredicate evaluatedWithObject:element handler:nil];

    [self waitForExpectationsWithTimeout:timeout handler:^(NSError * _Nullable error) {
        if (error != nil) {
            NSString *message = [NSString stringWithFormat:@"Failed to find %@ after %f seconds",element,timeout];
            [self recordFailureWithDescription:message inFile:file atLine:line expected:YES];
        }
    }];
}

USO

XCUIElement *element = app.staticTexts["Name of your element"];
[self waitForElementToAppear:element withTimeout:5];

Rápido

func waitForElementToAppear(element: XCUIElement, timeout: NSTimeInterval = 5,  file: String = #file, line: UInt = #line) {
        let existsPredicate = NSPredicate(format: "exists == true")

        expectationForPredicate(existsPredicate,
                evaluatedWithObject: element, handler: nil)

        waitForExpectationsWithTimeout(timeout) { (error) -> Void in
            if (error != nil) {
                let message = "Failed to find \(element) after \(timeout) seconds."
                self.recordFailureWithDescription(message, inFile: file, atLine: line, expected: true)
            }
        }
    }

USO

let element = app.staticTexts["Name of your element"]
self.waitForElementToAppear(element)

o

let element = app.staticTexts["Name of your element"]
self.waitForElementToAppear(element, timeout: 10)

FUENTE


1
buscando más ilustración sobre el ejemplo anterior de xcode9
rd_


1
Probado ¡Funciona de maravilla! ¡Gracias!
Dawid Koncewicz

32

A partir de Xcode 8.3, podemos usar XCTWaiter http://masilotti.com/xctest-waiting/

func waitForElementToAppear(_ element: XCUIElement) -> Bool {
    let predicate = NSPredicate(format: "exists == true")
    let expectation = expectation(for: predicate, evaluatedWith: element, 
                                  handler: nil)

    let result = XCTWaiter().wait(for: [expectation], timeout: 5)
    return result == .completed
}

Otro truco es escribir una waitfunción, el crédito va a John Sundell por mostrármelo.

extension XCTestCase {

  func wait(for duration: TimeInterval) {
    let waitExpectation = expectation(description: "Waiting")

    let when = DispatchTime.now() + duration
    DispatchQueue.main.asyncAfter(deadline: when) {
      waitExpectation.fulfill()
    }

    // We use a buffer here to avoid flakiness with Timer on CI
    waitForExpectations(timeout: duration + 0.5)
  }
}

y úsalo como

func testOpenLink() {
  let delegate = UIApplication.shared.delegate as! AppDelegate
  let route = RouteMock()
  UIApplication.shared.open(linkUrl, options: [:], completionHandler: nil)

  wait(for: 1)

  XCTAssertNotNil(route.location)
}

11

Basado en la respuesta de @ Ted , he usado esta extensión:

extension XCTestCase {

    // Based on https://stackoverflow.com/a/33855219
    func waitFor<T>(object: T, timeout: TimeInterval = 5, file: String = #file, line: UInt = #line, expectationPredicate: @escaping (T) -> Bool) {
        let predicate = NSPredicate { obj, _ in
            expectationPredicate(obj as! T)
        }
        expectation(for: predicate, evaluatedWith: object, handler: nil)

        waitForExpectations(timeout: timeout) { error in
            if (error != nil) {
                let message = "Failed to fulful expectation block for \(object) after \(timeout) seconds."
                self.recordFailure(withDescription: message, inFile: file, atLine: line, expected: true)
            }
        }
    }

}

Puedes usarlo así

let element = app.staticTexts["Name of your element"]
waitFor(object: element) { $0.exists }

También permite esperar que un elemento desaparezca o que cambie cualquier otra propiedad (utilizando el bloque apropiado)

waitFor(object: element) { !$0.exists } // Wait for it to disappear

+1 es muy rápido, y utiliza el predicado de bloque que creo que es mucho mejor porque las expresiones de predicado estándar no me funcionaron a veces, por ejemplo, cuando esperaba algunas propiedades en XCUIElements, etc.
lawicko

10

Editar:

En realidad, se me ocurrió que en Xcode 7b4, las pruebas de IU ahora tienen expectationForPredicate:evaluatedWithObject:handler:

Original:

Otra forma es hacer girar el ciclo de ejecución durante un período de tiempo determinado. Realmente solo es útil si sabe cuánto tiempo (estimado) necesitará esperar

Obj-C: [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow: <<time to wait in seconds>>]]

Rápido: NSRunLoop.currentRunLoop().runMode(NSDefaultRunLoopMode, beforeDate: NSDate(timeIntervalSinceNow: <<time to wait in seconds>>))

Esto no es súper útil si necesita probar algunas condiciones para continuar su prueba. Para ejecutar comprobaciones condicionales, use un whilebucle.


Esto es limpio y muy útil para mí, especialmente, por ejemplo, esperar el inicio de la aplicación, solicitar datos precargados y hacer cosas de inicio / cierre de sesión. Gracias.
felixwcf

4

El siguiente código solo funciona con el objetivo C.

- (void)wait:(NSUInteger)interval {

    XCTestExpectation *expectation = [self expectationWithDescription:@"wait"];
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(interval * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [expectation fulfill];
    });
    [self waitForExpectationsWithTimeout:interval handler:nil];
}

Simplemente llame a esta función como se indica a continuación.

[self wait: 10];

Error -> captó "NSInternalInconsistencyException", "Infracción API - llamada realizada para esperar sin que se hayan establecido expectativas".
FlowUI. SimpleUITesting.com

@ iOSCalendarpatchthecode.com, ¿ha encontrado una solución alternativa para eso?
Max

@Max, ¿puedes usar alguno de los otros en esta página?
FlowUI. SimpleUITesting.com

@ iOSCalendarpatchthecode.com No, solo necesito un poco de retraso sin ningún elemento para verificar. Entonces necesito una alternativa de esto.
Max

@Max utilicé la respuesta seleccionada en esta página. A mí me funcionó. Quizás puedas preguntarles qué es lo que estás buscando específicamente.
FlowUI. SimpleUITesting.com

4

En mi caso sleepcreó efectos secundarios, así que uséwait

let _ = XCTWaiter.wait(for: [XCTestExpectation(description: "Hello World!")], timeout: 2.0)

0

De acuerdo con la API para XCUIElement, .existsse puede usar para verificar si existe una consulta o no, por lo que la siguiente sintaxis podría ser útil en algunos casos.

let app = XCUIApplication()
app.launch()

let label = app.staticTexts["Hello, world!"]
while !label.exists {
    sleep(1)
}

Si está seguro de que su expectativa se cumplirá eventualmente, puede intentar ejecutar esto. Cabe señalar que el bloqueo podría ser preferible si la espera es demasiado larga, en cuyo caso se waitForExpectationsWithTimeout(_,handler:_)debe utilizar la publicación de @Joe Masilotti.


0

dormir bloqueará el hilo

"No se procesa el ciclo de ejecución mientras el hilo está bloqueado".

puedes usar waitForExistence

let app = XCUIApplication()
app.launch()

if let label = app.staticTexts["Hello, world!"] {
label.waitForExistence(timeout: 5)
}

0

Esto creará un retraso sin poner el hilo a dormir o lanzar un error en el tiempo de espera:

let delayExpectation = XCTestExpectation()
delayExpectation.isInverted = true
wait(for: [delayExpectation], timeout: 5)

Debido a que la expectativa se invierte, expirará en silencio.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.