J'essaie d'écrire un scénario de test à l'aide de la nouvelle interface utilisateur Test disponible dans Xcode 7 beta 2. L'application dispose d'un écran de connexion permettant d'appeler le serveur. Ceci est associé à un délai car il s’agit d’une opération asynchrone.
Existe-t-il un moyen de provoquer un délai 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. N'a pas pu trouver quoi que ce soit lié à cela.
Des idées/suggestions?
Le test d'interface utilisateur asynchrone a été introduit dans Xcode 7 Beta 4. Pour 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 détails sur les tests d'interface utilisateur peut être trouvé sur mon blog.
De plus, vous pouvez simplement dormir:
sleep(10)
Puisque les UITest s'exécutent dans un autre processus, cela fonctionne. Je ne sais pas à quel point c'est conseillé, mais ça marche.
Xcode 9 introduit de nouvelles astuces avec XCTWaiter
Le test élémentaire attend explicitement
wait(for: [documentExpectation], timeout: 10)
Délégués de l'instance de serveur pour tester
XCTWaiter(delegate: self).wait(for: [documentExpectation], timeout: 10)
La classe 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
}
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)
iOS 11/Xcode 9
<#yourElement#>.waitForExistence(timeout: 5)
C'est un excellent remplacement pour toutes les implémentations personnalisées sur ce site!
Soyez sûr de regarder ma réponse ici: https://stackoverflow.com/a/48937714/971329 . Je décris ici une alternative à l'attente des requêtes qui réduira considérablement le temps d'exécution de vos tests!
A partir de 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 fonction wait
. John Sundell en est le mérite.
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 l'utiliser 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)
}
Edit:
En fait, je viens de me rendre compte que dans Xcode 7b4, les tests d'interface utilisateur ont maintenant expectationForPredicate:evaluatedWithObject:handler:
Original:
Une autre méthode consiste à faire tourner la boucle pendant un certain temps. Vraiment utile que si vous savez combien de temps (estimé) vous devrez attendre
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>>))
Ce n'est pas très utile si vous devez tester certaines conditions pour pouvoir continuer votre test. Pour exécuter des contrôles conditionnels, utilisez une boucle while
.
Basé sur @ Ted's answer , 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."
self.recordFailure(withDescription: message, inFile: file, atLine: line, expected: true)
}
}
}
}
Vous pouvez l'utiliser comme ça
let element = app.staticTexts["Name of your element"]
waitFor(object: element) { $0.exists }
Cela permet également d'attendre la disparition d'un élément ou toute autre propriété de changer (en utilisant le bloc approprié)
waitFor(object: element) { !$0.exists } // Wait for it to disappear
Le code suivant ne fonctionne qu'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];
}
Il suffit d'appeler cette fonction comme indiqué ci-dessous.
[self wait: 10];
Dans mon cas, sleep
a créé un effet secondaire, alors j’ai utilisé
XCTWaiter.wait(for: [XCTestExpectation(description: "Hello World!")], timeout: 2.0)
Selon l'API pour XCUIElement, .exists
peut être utilisé pour vérifier si une requête existe ou non. La syntaxe suivante pourrait donc être utile dans certains cas!
let app = XCUIApplication()
app.launch()
let label = app.staticTexts["Hello, world!"]
while !label.exists {
sleep(1)
}
Si vous êtes sûr que vos attentes seront satisfaites, vous pourrez éventuellement essayer de le faire. Il est à noter que le crash pourrait être préférable si l’attente est trop longue, auquel cas waitForExpectationsWithTimeout(_,handler:_)
du poste de @ Joe Masilotti devrait être utilisé.