Verzögerung / Warten in einem Testfall von Xcode UI-Tests


181

Ich versuche, einen Testfall mit dem neuen UI-Test zu schreiben, der in Xcode 7 Beta 2 verfügbar ist. Die App verfügt über einen Anmeldebildschirm, auf dem sie den Server anruft, um sich anzumelden. Damit ist eine Verzögerung verbunden, da es sich um eine asynchrone Operation handelt.

Gibt es eine Möglichkeit, im XCTestCase einen Verzögerungs- oder Wartemechanismus zu verursachen, bevor Sie mit weiteren Schritten fortfahren?

Es ist keine ordnungsgemäße Dokumentation verfügbar, und ich habe die Header-Dateien der Klassen durchgesehen. Konnte nichts im Zusammenhang damit finden.

Irgendwelche Ideen / Vorschläge?


13
Ich denke NSThread.sleepForTimeInterval(1)sollte funktionieren
Kametrixom

Toll! Das sieht so aus, als würde es funktionieren. Aber ich bin mir nicht sicher, ob dies der empfohlene Weg ist. Ich denke, Apple sollte einen besseren Weg geben, dies zu tun. Möglicherweise muss ein Radar
eingereicht werden

Ich denke wirklich, dass das in Ordnung ist. Es ist wirklich die häufigste Art, den aktuellen Thread für eine bestimmte Zeit anzuhalten. Wenn Sie mehr Kontrolle wünschen, können Sie auch in GCD (The dispatch_after, dispatch_queuestuff)
Kametrixom

@Kametrixom Kreuzen Sie nicht die Run-Schleife an - Apple hat in Beta 4 native asynchrone Tests eingeführt. Weitere Informationen finden Sie in meiner Antwort .
Joe Masilotti

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

Antworten:


168

Asynchrones UI-Testen wurde in Xcode 7 Beta 4 eingeführt. Warten auf ein Etikett mit dem Text "Hallo Welt!" Um zu erscheinen, können Sie Folgendes tun:

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)

Weitere Details zu UI-Tests finden Sie in meinem Blog.


19
Leider gibt es keine Möglichkeit zu akzeptieren, dass das Timeout aufgetreten ist, und weiterzumachen - waitForExpectationsWithTimeoutIhr Test wird automatisch nicht bestanden, was ziemlich unglücklich ist.
Jedidja

@Jedidja Eigentlich passiert das bei mir mit XCode 7.0.1 nicht.
Bastian

@ Bastian Hmm interessant; Ich werde das noch einmal überprüfen müssen.
Jedidja

1
es funktioniert nicht bei mir. Hier ist mein Beispiel: let xButton = app.toolbars.buttons ["X"] let existiert = NSPredicate (Format: "existiert == 1") ExpectationForPredicate (existiert, evaluiert mit Objekt: xButton, Handler: nil) waitForExpectationsWithTimeout (10, Handler: nil)
emoleumassi

Das app.launch()scheint nur die App neu zu starten. Ist es nötig?
Chris Prince

225

Außerdem können Sie einfach schlafen:

sleep(10)

Da die UITests in einem anderen Prozess ausgeführt werden, funktioniert dies. Ich weiß nicht, wie ratsam es ist, aber es funktioniert.


2
Irgendwann brauchen wir den Weg, um zu verzögern und wollen nicht, dass es einen Fehler auslöst! danke
Tai Le

13
Die beste Antwort, die ich je gesehen habe :) Ich würde + 100 Stimmen hinzufügen, wenn ich könnte :)
Bartłomiej Semańczyk

8
Ich mag NSThread.sleepForTimeInterval (0.2), da Sie Verzögerungen von weniger als einer Sekunde angeben können. (sleep () nimmt einen ganzzahligen Parameter an; nur Vielfache einer Sekunde sind möglich).
Graham Perks

5
@ AbrahamPerks, ja, obwohl es auch gibt:usleep
mxcl

3
Es ist kein schlechter Vorschlag (Sie verstehen nicht, wie UITesting funktioniert), aber selbst wenn es ein schlechter Vorschlag war, gibt es manchmal keine Möglichkeit, eine funktionierende Erwartung zu erstellen (Systemwarnungen?). Das ist also alles, was Sie haben.
mxcl

77

Xcode 9 hat mit XCTWaiter neue Tricks eingeführt

Testfall wartet explizit

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

Kellnerinstanz delegiert zum Testen

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

Die Kellnerklasse gibt das Ergebnis zurück

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
}

Beispielnutzung

Vor Xcode 9

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

VERWENDUNG

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

Schnell

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

VERWENDUNG

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

oder

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

QUELLE


1
auf der Suche nach mehr Illustration bezüglich des obigen xcode9 Beispiels
rd_


1
Geprüft. Klappt wunderbar! Vielen Dank!
Dawid Koncewicz

77

iOS 11 / Xcode 9

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

Dies ist ein großartiger Ersatz für alle benutzerdefinierten Implementierungen auf dieser Site!

Schauen Sie sich unbedingt meine Antwort hier an: https://stackoverflow.com/a/48937714/971329 . Dort beschreibe ich eine Alternative zum Warten auf Anfragen, die die Laufzeit Ihrer Tests erheblich verkürzt!


Dank @daidai ich den Text geändert :)
blackjacx

1
Ja, das ist immer noch der Ansatz, den ich bei der Verwendung anstrebe, XCTestCaseund er funktioniert wie ein Zauber. Ich verstehe nicht, warum Ansätze wie sleep(3)hier hier so hoch bewertet werden, da dies die Testzeit künstlich verlängert und wirklich keine Option ist, wenn Ihre Testsuite wächst.
Blackjacx

Eigentlich benötigt es Xcode 9, funktioniert aber auch auf Geräten / Simulatoren mit iOS 10
;-)

Ja, das habe ich in der Überschrift oben geschrieben. Aber jetzt sollten die meisten Leute auf mindestens Xcode 9 aktualisiert haben ;-)
blackjacx

31

Ab Xcode 8.3 können wir XCTWaiter http://masilotti.com/xctest-waiting/ verwenden.

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
}

Ein weiterer Trick besteht darin, eine waitFunktion zu schreiben. John Sundell ist es zu verdanken, dass er sie mir gezeigt hat

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

und benutze es gerne

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

Basierend auf der Antwort von @ Ted habe ich diese Erweiterung verwendet:

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

}

Sie können es so verwenden

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

Sie können auch darauf warten, dass ein Element verschwindet oder eine andere Eigenschaft geändert wird (mithilfe des entsprechenden Blocks).

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

+1 sehr schnell, und es wird das Blockprädikat verwendet, das meiner Meinung nach viel besser ist, da die Standardprädikatausdrücke manchmal bei mir nicht funktionierten, zum Beispiel beim Warten auf einige Eigenschaften auf XCUIElements usw.
Lawicko

10

Bearbeiten:

Mir ist gerade eingefallen, dass in Xcode 7b4 jetzt UI-Tests durchgeführt wurden expectationForPredicate:evaluatedWithObject:handler:

Original:

Eine andere Möglichkeit besteht darin, die Laufschleife für eine festgelegte Zeitspanne zu drehen. Wirklich nur nützlich, wenn Sie wissen, auf wie viel (geschätzte) Zeit Sie warten müssen

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

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

Dies ist nicht besonders nützlich, wenn Sie einige Bedingungen testen müssen, um Ihren Test fortzusetzen. Verwenden Sie eine whileSchleife, um bedingte Prüfungen durchzuführen.


Dies ist sauber und sehr nützlich für mich, insbesondere wenn ich auf den Start der App warte, vorinstallierte Daten anfordere und Login / Logout-Aufgaben erledige. Danke dir.
felixwcf

4

Der folgende Code funktioniert nur mit Ziel 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];
}

Rufen Sie einfach diese Funktion wie unten angegeben auf.

[self wait: 10];

Fehler -> "NSInternalInconsistencyException" abgefangen, "API-Verletzung - Aufruf zum Warten, ohne dass Erwartungen festgelegt wurden."
FlowUI. SimpleUITesting.com

@ iOSCalendarpatchthecode.com, Haben Sie eine alternative Lösung dafür gefunden?
Max

@Max können Sie einen der anderen auf dieser Seite verwenden?
FlowUI. SimpleUITesting.com

@ iOSCalendarpatchthecode.com Nein, ich brauche nur eine Verzögerung, ohne dass ein Element überprüft werden muss. Also brauche ich eine Alternative dazu.
Max

@ Max Ich habe die ausgewählte Antwort auf dieser Seite verwendet. Es hat bei mir funktioniert. Vielleicht können Sie sie fragen, wonach Sie speziell suchen.
FlowUI. SimpleUITesting.com

4

In meinem Fall sleepentstand ein Nebeneffekt, den ich benutztewait

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

0

Gemäß der API für XCUIElement .existskann überprüft werden, ob eine Abfrage vorhanden ist oder nicht, sodass die folgende Syntax in einigen Fällen hilfreich sein kann!

let app = XCUIApplication()
app.launch()

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

Wenn Sie sicher sind, dass Ihre Erwartungen irgendwann erfüllt werden, können Sie versuchen, dies auszuführen. Es sollte beachtet werden, dass ein Absturz vorzuziehen ist, wenn die Wartezeit zu lang ist. In diesem Fall waitForExpectationsWithTimeout(_,handler:_)sollte der Beitrag von @Joe Masilotti verwendet werden.


0

Schlaf blockiert den Thread

"Während der Thread blockiert ist, findet keine Run-Loop-Verarbeitung statt."

Sie können waitForExistence verwenden

let app = XCUIApplication()
app.launch()

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

0

Dies führt zu einer Verzögerung, ohne den Thread in den Ruhezustand zu versetzen oder beim Timeout einen Fehler auszulösen:

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

Da die Erwartung umgekehrt ist, tritt eine leise Zeitüberschreitung auf.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.