Ritardare / attendere in un caso di test del test dell'interfaccia utente Xcode


182

Sto cercando di scrivere un caso di prova utilizzando il nuovo test dell'interfaccia utente disponibile in Xcode 7 beta 2. L'app ha una schermata di accesso in cui effettua una chiamata al server per accedere. C'è un ritardo associato a questo in quanto si tratta di un'operazione asincrona.

Esiste un modo per causare un meccanismo di ritardo o di attesa in XCTestCase prima di procedere con ulteriori passaggi?

Non è disponibile alcuna documentazione adeguata e ho esaminato i file Header delle classi. Non sono riuscito a trovare nulla di simile a questo.

Qualche idea / suggerimento?


13
Penso che NSThread.sleepForTimeInterval(1)dovrebbe funzionare
Kametrixom il

Grande! Sembra che funzioni. Ma non sono sicuro che sia il modo consigliato per farlo. Penso che Apple dovrebbe dare un modo migliore per farlo. Potrebbe essere necessario presentare un radar
Tejas HS,

In realtà penso davvero che vada bene, è davvero il modo più comune di mettere in pausa il thread corrente per un certo tempo. Se vuoi un maggiore controllo puoi anche entrare in GCD (The dispatch_after, dispatch_queuestuff)
Kametrixom,

@Kametrixom Non barrare il ciclo di esecuzione - Apple ha introdotto i test asincroni nativi in ​​Beta 4. Vedi la mia risposta per i dettagli.
Joe Masilotti,

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

Risposte:


168

Il test asincrono dell'interfaccia utente è stato introdotto in Xcode 7 Beta 4. Per attendere un'etichetta con il testo "Ciao, mondo!" per apparire puoi fare quanto segue:

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)

Maggiori dettagli sui test dell'interfaccia utente sono disponibili sul mio blog.


19
Sfortunatamente non c'è modo di accettare che il timeout sia passato e andare avanti - waitForExpectationsWithTimeoutfallirà automaticamente il test, il che è abbastanza sfortunato.
Jedidja,

@Jedidja In realtà, questo non accade per me con XCode 7.0.1.
Bastian,

@Bastian Hmm interessante; Dovrò ricontrollare questo.
Jedidja,

1
non funziona per me. Ecco il mio esempio: let xButton = app.toolbars.buttons ["X"] let esiste = NSPredicate (formato: "esiste == 1") zero)
emoleumassi

La app.launch()sembra rilanciare solo l'applicazione. È necessario?
Chris Prince,

225

Inoltre, puoi semplicemente dormire:

sleep(10)

Poiché gli UITest vengono eseguiti in un altro processo, questo funziona. Non so quanto sia consigliabile, ma funziona.


2
Qualche volta abbiamo bisogno di un modo per ritardare e non vogliamo che si verifichi un fallimento! grazie
Tai Le

13
La migliore risposta che abbia mai visto :) Aggiungerei + 100 voti Se potessi :)
Bartłomiej Semańczyk

8
Mi piace NSThread.sleepForTimeInterval (0.2) in quanto è possibile specificare i ritardi dei secondi. (sleep () accetta un parametro intero; sono possibili solo multipli di secondo).
Graham Perks,

5
@GrahamPerks, sì, anche se c'è anche:usleep
mxcl

3
Non è un suggerimento scadente (non capisci come funziona UITesting), ma anche se è stato un suggerimento scadente a volte non c'è modo di elaborare un'aspettativa che funzioni (il sistema avvisa qualcuno?) Quindi questo è tutto ciò che hai.
mxcl

78

iOS 11 / Xcode 9

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

Questo è un ottimo sostituto per tutte le implementazioni personalizzate su questo sito!

Assicurati di dare un'occhiata alla mia risposta qui: https://stackoverflow.com/a/48937714/971329 . Qui descrivo un'alternativa all'attesa di richieste che ridurrà notevolmente il tempo di esecuzione dei test!


Grazie @daidai ho cambiato il testo :)
blackjacx il

1
Sì, questo è ancora l'approccio che sto usando per l'utilizzo XCTestCasee funziona come un fascino. Non capisco perché approcci come sleep(3)sono votati così in alto qui poiché estende artificialmente il tempo di test e non è davvero un'opzione quando la tua suite di test cresce.
Blackjacx,

In realtà richiede Xcode 9, ma funziona anche su dispositivi / simulatori che eseguono iOS 10 ;-)
d4Rk

Sì, l'ho scritto sul titolo sopra. Ma ora la maggior parte delle persone avrebbe dovuto passare ad almeno Xcode 9 ;-)
blackjacx,

77

Xcode 9 ha introdotto nuovi trucchi con XCTWaiter

Il test case attende esplicitamente

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

Delega dell'istanza del cameriere da testare

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

La classe del cameriere restituisce il risultato

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
}

utilizzo del campione

Prima di Xcode 9

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

veloce

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)

FONTE


1
alla ricerca di qualche illustrazione in più sull'esempio di xcode9 sopra
rd_


1
Provato. Funziona come un fascino! Grazie!
Dawid Koncewicz,

32

A partire da Xcode 8.3, possiamo usare 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
}

Un altro trucco è scrivere una waitfunzione, il merito va a John Sundell per avermelo mostrato

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

e usalo come

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

Sulla base della risposta di @ Ted , ho usato questa estensione:

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

}

Puoi usarlo in questo modo

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

Consente inoltre di attendere la scomparsa di un elemento o di modificare qualsiasi altra proprietà (utilizzando il blocco appropriato)

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

+1 molto veloce, e utilizza il predicato di blocco che penso sia molto meglio perché le espressioni di predicato standard non funzionavano per me a volte, ad esempio quando aspettavo alcune proprietà su XCUIElements ecc.
lawicko

10

Modificare:

In realtà mi è appena venuto in mente che in Xcode 7b4, i test dell'interfaccia utente ora hanno expectationForPredicate:evaluatedWithObject:handler:

Originale:

Un altro modo è di girare il ciclo di esecuzione per un determinato periodo di tempo. Utile solo se sai quanto tempo (stimato) dovrai aspettare

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

Questo non è molto utile se è necessario testare alcune condizioni per continuare il test. Per eseguire controlli condizionali, utilizzare un whileciclo.


Questo è pulito e molto utile per me, ad esempio in attesa dell'avvio dell'app, richiedere dati precaricati ed eseguire operazioni di accesso / disconnessione. Grazie.
felixwcf,

4

Il seguente codice funziona solo con l'obiettivo 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];
}

Basta chiamare questa funzione come indicato di seguito.

[self wait: 10];

Errore -> rilevato "NSInternalInconsistencyException", "Violazione API - chiamata fatta attendere senza che siano state impostate aspettative."
FlowUI. SimpleUITesting.com

@ iOSCalendarpatchthecode.com, hai trovato una soluzione alternativa per questo?
Max

@Max puoi usare qualcuno degli altri in questa pagina?
FlowUI. SimpleUITesting.com

@ iOSCalendarpatchthecode.com No, ho solo bisogno di un po 'di ritardo senza alcun elemento da controllare. Quindi ho bisogno di un'alternativa di questo.
Max

@ Max ho usato la risposta selezionata in questa pagina. Ha funzionato per me. Forse puoi chiedere loro cosa stai cercando in modo specifico.
FlowUI. SimpleUITesting.com

4

Nel mio caso ho sleepcreato effetti collaterali così ho usatowait

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

0

Secondo l'API per XCUIElement .existspuò essere utilizzato per verificare se esiste una query o meno, quindi la seguente sintassi potrebbe essere utile in alcuni casi!

let app = XCUIApplication()
app.launch()

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

Se sei sicuro che alla fine le tue aspettative saranno soddisfatte, potresti provare a gestirlo. Va notato che lo schianto potrebbe essere preferibile se l'attesa è troppo lunga, nel qual caso si waitForExpectationsWithTimeout(_,handler:_)dovrebbe usare il post di @Joe Masilotti.


0

sleep bloccherà il thread

"Nessuna elaborazione del ciclo di esecuzione si verifica mentre il thread è bloccato."

puoi usare waitForExistence

let app = XCUIApplication()
app.launch()

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

0

Ciò creerà un ritardo senza mettere in pausa il thread o generare un errore al timeout:

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

Poiché l'aspettativa è invertita, scadrà silenziosamente.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.