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


Estoy tratando de escribir un caso de prueba usando la nueva prueba de interfaz de usuario disponible en Xcode 7 beta 2. La Aplicación tiene una pantalla de inicio de sesión donde hace una llamada al servidor para iniciar sesión. Hay un retraso asociado con esto, ya que es una operación asíncrona.

¿Hay alguna manera de causar un mecanismo de demora o espera en el caso XCTest antes de proceder a otros pasos?

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

¿Alguna idea/sugerencia?

Author: Ted, 2015-07-02

9 answers

Las pruebas de interfaz de usuario asíncronas se introdujeron en Xcode 7 Beta 4. Esperar una etiqueta con el texto " Hola, mundo!"para aparecer puedes 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)

Más detalles sobre UI Testing se pueden encontrar en mi blog.

 131
Author: Joe Masilotti,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-08-26 13:32:48

Además, puedes dormir:

sleep(10)

Dado que los UITests se ejecutan en otro proceso, esto funciona. No se que tan recomendable es, pero funciona.

 187
Author: mxcl,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-05-25 20:33:00

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 la muestra

Antes De Xcode 9

Objetivo C

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

Swift

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

 60
Author: Ted,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-12-02 06:32:00

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 que sus pruebas se están ejecutando!

 38
Author: blackjacx,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-02-22 21:46:27

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 función wait, 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 usarlo 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)
}
 24
Author: onmyway133,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-03-03 08:33:20

Editar:

En realidad se me acaba de ocurrir que en Xcode 7b4, las pruebas de interfaz de usuario ahora tienen expectationForPredicate:evaluatedWithObject:handler:

Original:

Otra forma es hacer girar el bucle de ejecución durante un tiempo determinado. Realmente solo es útil si sabes cuánto tiempo (estimado) tendrás que esperar

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

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

Esto no es muy útil si necesita probar algunas condiciones para continuar su prueba. Para ejecutar comprobaciones condicionales, utilice un while bucle.

 9
Author: enmiller,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-07-22 13:18:59

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 a que un elemento desaparezca, o cualquier otra propiedad cambie (usando el bloque apropiado)

waitFor(object: element) { !$0.exists } // Wait for it to disappear
 7
Author: Ben Lings,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:18:15

El siguiente código solo funciona con Objective 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 haga una llamada a esta función como se indica a continuación.

[self wait: 10];
 4
Author: arango_86,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-09 10:59:55

De acuerdo con la API para XCUIElement .exists se 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 sus expectativas se cumplirán eventualmente, podría intentar ejecutar esto. Cabe señalar que el bloqueo podría ser preferible si la espera es demasiado larga, en cuyo caso se debe usar waitForExpectationsWithTimeout(_,handler:_) del post de @Joe Masilotti.

 1
Author: Reid,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-07-18 20:22:20