Delay / Wait dans un cas de test de test de l'interface utilisateur Xcode


185

J'essaie d'écrire un cas de test en utilisant le nouveau test d'interface utilisateur disponible dans Xcode 7 beta 2. L'application dispose d'un écran de connexion où elle appelle le serveur pour se connecter. Il y a un délai associé à cela car il s'agit d'une opération asynchrone.

Existe-t-il un moyen de provoquer un retard ou un mécanisme d'attente dans XCTestCase avant de passer aux étapes suivantes?

Il n'y a pas de documentation appropriée disponible et j'ai parcouru les fichiers d'en-tête des classes. Je n'ai rien pu trouver à ce sujet.

Des idées / suggestions?


13
Je pense que NSThread.sleepForTimeInterval(1)devrait fonctionner
Kametrixom

Génial! Cela semble fonctionner. Mais je ne suis pas sûr que ce soit la manière recommandée de le faire. Je pense qu'Apple devrait proposer une meilleure façon de le faire. Pourrait avoir à déposer un radar
Tejas HS

En fait, je pense vraiment que ce n'est pas grave, c'est vraiment le moyen le plus courant de mettre en pause le fil actuel pendant un certain temps. Si vous voulez plus de contrôle, vous pouvez également entrer dans GCD (The dispatch_after, dispatch_queuestuff)
Kametrixom

@Kametrixom Ne cochez pas la boucle d'exécution - Apple a introduit les tests asynchrones natifs dans la bêta 4. Voir ma réponse pour plus de détails.
Joe Masilotti

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

Réponses:


170

Le test asynchrone de l'interface utilisateur a été introduit dans Xcode 7 Beta 4. Attendre une étiquette avec le texte "Hello, world!" pour apparaître, vous pouvez faire ce qui suit:

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)

Plus de détails sur les tests d'interface utilisateur peuvent être trouvés sur mon blog.


20
Malheureusement, il n'y a aucun moyen d'accepter que le délai d'attente s'est produit et de passer à autre chose - waitForExpectationsWithTimeoutéchouera automatiquement votre test, ce qui est assez regrettable.
Jedidja

@Jedidja En fait, cela ne se produit pas pour moi avec XCode 7.0.1.
Bastian

@Bastian Hmm intéressant; Je vais devoir revérifier cela.
Jedidja

1
ça ne marche pas pour moi. Voici mon exemple: let xButton = app.toolbars.buttons ["X"] let existe = NSPredicate (format: "existe == 1") expectationForPredicate (existe, évaluéWithObject: xButton, gestionnaire: nil) waitForExpectationsWithTimeout (10, gestionnaire: nil)
emoleumassi

Le app.launch()semble juste relancer l'application. Est-ce nécessaire?
Chris Prince

228

De plus, vous pouvez simplement dormir:

sleep(10)

Puisque les tests UIT sont exécutés dans un autre processus, cela fonctionne. Je ne sais pas à quel point c'est souhaitable, mais ça marche.


2
Un certain temps, nous avons besoin du moyen de retarder et ne voulons pas que cela provoque un échec! merci
Tai Le

8
J'aime NSThread.sleepForTimeInterval (0.2) car vous pouvez spécifier des délais inférieurs à la seconde. (sleep () prend un paramètre entier; seuls les multiples d'une seconde sont possibles).
Graham Perks

5
@GrahamPerks, oui, mais il y a aussi:usleep
mxcl

2
J'aurais aimé qu'il y ait une meilleure réponse, mais cela semble être le seul moyen pour le moment si vous ne voulez pas provoquer un échec.
Chase Holland le

3
Ce n'est pas une mauvaise suggestion (vous ne comprenez pas comment fonctionne UITesting), mais même si c'était une mauvaise suggestion, il n'y a parfois aucun moyen de créer une attente qui fonctionne (le système alerte quelqu'un?) Alors c'est tout ce que vous avez.
mxcl

81

iOS 11 / Xcode 9

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

C'est un excellent remplacement pour toutes les implémentations personnalisées sur ce site!

Assurez-vous de jeter un œil à ma réponse ici: https://stackoverflow.com/a/48937714/971329 . Là, je décris une alternative à l'attente de demandes qui réduira considérablement le temps d'exécution de vos tests!


Merci @daidai j'ai changé le texte :)
blackjacx

1
Oui, c'est toujours l'approche que je privilégie lors de l'utilisation XCTestCaseet cela fonctionne comme un charme. Je ne comprends pas pourquoi des approches comme sleep(3)sont votées si haut ici car cela prolonge artificiellement le temps de test et n'est vraiment pas une option lorsque votre suite de tests se développe.
blackjacx

En fait, il nécessite Xcode 9, mais fonctionne également sur les appareils / simulateurs exécutant iOS 10 ;-)
d4Rk

Ouais, j'ai écrit ça sur le titre ci-dessus. Mais maintenant, la plupart des gens devraient avoir mis à jour au moins Xcode 9 ;-)
blackjacx

77

Xcode 9 a introduit de nouvelles astuces avec XCTWaiter

Le cas de test attend explicitement

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

Délégués d'instance de serveur pour tester

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

La classe de serveur renvoie le résultat

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
}

utilisation de l'échantillon

Avant Xcode 9

Objectif 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];
        }
    }];
}

USAGE

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

Rapide

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

USAGE

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

ou

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

LA SOURCE


1
à la recherche d'une illustration supplémentaire concernant l'exemple de xcode9 ci-dessus
rd_


1
Testé. Fonctionne comme un charme! Merci!
Dawid Koncewicz

34

Depuis Xcode 8.3, nous pouvons utiliser 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
}

Une autre astuce consiste à écrire une waitfonction, le mérite revient à John Sundell de me l'avoir montré

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

et utilisez-le comme

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

12

Sur la base de la réponse de @ Ted , j'ai utilisé cette extension:

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."
                let location = XCTSourceCodeLocation(filePath: file, lineNumber: line)
                let issue = XCTIssue(type: .assertionFailure, compactDescription: message, detailedDescription: nil, sourceCodeContext: .init(location: location), associatedError: nil, attachments: [])
                self.record(issue)
            }
        }
    }

}

Vous pouvez l'utiliser comme ça

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

Il permet également d'attendre la disparition d'un élément ou de modifier toute autre propriété (en utilisant le bloc approprié)

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

+1 très rapide, et il utilise le prédicat de bloc qui, à mon avis, est bien meilleur parce que les expressions de prédicat standard ne fonctionnaient pas toujours pour moi, par exemple en attendant certaines propriétés sur XCUIElements, etc.
lawicko

10

Éditer:

Il m'est venu à l'esprit que dans Xcode 7b4, les tests d'interface utilisateur ont maintenant expectationForPredicate:evaluatedWithObject:handler:

Original:

Une autre méthode consiste à faire tourner la boucle d'exécution pendant une durée définie. Vraiment utile uniquement si vous savez combien de temps (estimé) vous devrez attendre

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

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

Ce n'est pas très utile si vous devez tester certaines conditions afin de continuer votre test. Pour exécuter des vérifications conditionnelles, utilisez une whileboucle.


C'est propre et très utile pour moi en particulier, par exemple en attendant le lancement de l'application, en demandant des données préchargées et en effectuant des tâches de connexion / déconnexion. Merci.
felixwcf

5

Dans mon cas, j'ai sleepcréé un effet secondaire alors j'ai utiliséwait

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

4

Le code suivant fonctionne uniquement avec 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];
}

Appelez simplement cette fonction comme indiqué ci-dessous.

[self wait: 10];

Erreur -> attrapé "NSInternalInconsistencyException", "Violation d'API - appel effectué pour attendre sans qu'aucune attente ait été définie."
FlowUI. SimpleUITesting.com

@ iOSCalendarpatchthecode.com, avez-vous trouvé une solution alternative pour cela?
Max

@Max pouvez-vous utiliser l'un des autres sur cette page?
FlowUI. SimpleUITesting.com

@ iOSCalendarpatchthecode.com Non, j'ai juste besoin d'un peu de retard sans aucun élément à vérifier. J'ai donc besoin d'une alternative à cela.
Max

@Max j'ai utilisé la réponse sélectionnée sur cette page. Cela a fonctionné pour moi. Vous pouvez peut-être leur demander ce que vous recherchez précisément.
FlowUI. SimpleUITesting.com

3

Cela créera un délai sans mettre le thread en veille ou générer une erreur à l'expiration du délai:

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

Parce que l'attente est inversée, elle expirera silencieusement.


0

Selon l'API pour XCUIElement .existspeut être utilisé pour vérifier si une requête existe ou non donc la syntaxe suivante pourrait être utile dans certains cas!

let app = XCUIApplication()
app.launch()

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

Si vous êtes convaincu que vos attentes seront éventuellement satisfaites, vous pouvez essayer de l'exécuter. Il convient de noter que le crash peut être préférable si l'attente est trop longue, auquel cas le waitForExpectationsWithTimeout(_,handler:_)message de @Joe Masilotti doit être utilisé.


0

le sommeil bloquera le fil

"Aucun traitement de boucle d'exécution ne se produit pendant que le thread est bloqué."

vous pouvez utiliser waitForExistence

let app = XCUIApplication()
app.launch()

if let label = app.staticTexts["Hello, world!"] {
label.waitForExistence(timeout: 5)
}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.