Trì hoãn / Đợi trong trường hợp kiểm tra Xcode UI thử nghiệm


182

Tôi đang cố gắng viết một trường hợp thử nghiệm bằng cách sử dụng Kiểm tra giao diện người dùng mới có sẵn trong Xcode 7 beta 2. Ứng dụng có màn hình đăng nhập nơi nó thực hiện cuộc gọi đến máy chủ để đăng nhập. Có một độ trễ liên quan đến điều này vì đây là một hoạt động không đồng bộ.

Có cách nào để gây ra sự chậm trễ hoặc cơ chế chờ trong XCTestCase trước khi tiến hành các bước tiếp theo không?

Không có tài liệu phù hợp có sẵn và tôi đã xem qua các tệp Header của các lớp. Không thể tìm thấy bất cứ điều gì liên quan đến điều này.

Bất kỳ ý tưởng / đề xuất?


13
Tôi nghĩ NSThread.sleepForTimeInterval(1)nên làm việc
Kametrixom

Tuyệt quá! Điều này có vẻ như nó hoạt động. Nhưng tôi không chắc đó có phải là cách nên làm hay không. Tôi nghĩ Apple nên đưa ra một cách tốt hơn để làm điều đó. Có thể phải nộp một Radar
Tejas HS

Tôi thực sự nghĩ rằng điều đó ổn, đó thực sự là cách phổ biến nhất để tạm dừng chuỗi hiện tại trong một thời gian nhất định. Nếu bạn muốn kiểm soát nhiều hơn, bạn cũng có thể vào GCD (The dispatch_after, dispatch_queueStuff)
Kametrixom

@Kametrixom Đừng đánh dấu vào vòng chạy - Apple đã giới thiệu thử nghiệm không đồng bộ riêng trong bản Beta 4. Xem câu trả lời của tôi để biết chi tiết.
Joe Masilotti

2
Swift 4.0 -> Thread.s ngủ (forTimeInterval: 2)
uplearnedu.com

Câu trả lời:


168

Kiểm tra giao diện người dùng không đồng bộ đã được giới thiệu trong Xcode 7 Beta 4. Để chờ nhãn có dòng chữ "Xin chào, thế giới!" để xuất hiện, bạn có thể làm như sau:

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)

Thông tin chi tiết về Kiểm tra giao diện người dùng có thể được tìm thấy trên blog của tôi.


19
Thật không may, không có cách nào để chấp nhận rằng thời gian chờ đã xảy ra và tiếp tục - waitForExpectationsWithTimeoutsẽ tự động thất bại bài kiểm tra của bạn, điều này khá đáng tiếc.
Jedidja

@Jedidja Trên thực tế, điều này không xảy ra với tôi với XCode 7.0.1.
Bastian

@Bastian Hmm thú vị; Tôi sẽ phải kiểm tra lại điều này.
Jedidja

1
nó không làm việc cho tôi Đây là ví dụ của tôi: let xButton = app.toolbars.buttons ["X"] let tồn tại = NSPredicate (định dạng: "tồn tại == 1") kì vọngForPredicate (tồn tại, được đánh giáWithObject: xButton, handler: nil) WaitForExpectations nil)
em Tombassi

Các app.launch()dường như chỉ relaunch ứng dụng. Có cần thiết không?
Hoàng tử Chris

225

Ngoài ra, bạn chỉ có thể ngủ:

sleep(10)

Vì UITests chạy trong một quy trình khác, điều này hoạt động. Tôi không biết làm thế nào nó được khuyến khích, nhưng nó hoạt động.


2
Đôi khi chúng ta cần cách trì hoãn và không muốn nó gây ra thất bại! cảm ơn
Tai Le

13
Câu trả lời hay nhất tôi từng thấy :) Tôi sẽ thêm + 100 phiếu nếu tôi có thể :)
Bartłomiej Semańchot

8
Tôi thích NSThread.s ngủForTimeInterval (0.2) vì bạn có thể chỉ định độ trễ phụ thứ hai. (ngủ () lấy một tham số nguyên; chỉ có thể nhân bội số của một giây).
Graham Perks

5
@GrahamPerks, vâng, mặc dù cũng có:usleep
mxcl

3
Đó không phải là một đề xuất kém (bạn không hiểu cách thức hoạt động của UITesting), nhưng ngay cả khi đó là một đề xuất kém, đôi khi không có cách nào để tạo ra một kỳ vọng hoạt động (hệ thống cảnh báo cho bất kỳ ai?) Vì vậy đây là tất cả những gì bạn có.
mxcl

78

iOS 11 / Xcode 9

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

Đây là một sự thay thế tuyệt vời cho tất cả các triển khai tùy chỉnh trên trang web này!

Hãy chắc chắn để xem câu trả lời của tôi ở đây: https://stackoverflow.com/a/48937714/971329 . Ở đó tôi mô tả một giải pháp thay thế cho việc chờ đợi các yêu cầu sẽ giúp giảm đáng kể thời gian các bài kiểm tra của bạn đang chạy!


Cảm ơn @daidai tôi đã thay đổi văn bản :)
blackjacx

1
Đúng, đây vẫn là cách tiếp cận tôi sẽ sử dụng XCTestCasevà nó hoạt động như một bùa mê. Tôi không hiểu tại sao các cách tiếp cận như sleep(3)được bình chọn rất cao ở đây vì nó kéo dài thời gian thử nghiệm một cách giả tạo và thực sự không có lựa chọn nào khi bộ thử nghiệm của bạn phát triển.
blackjacx

Trên thực tế, nó yêu cầu Xcode 9, nhưng cũng hoạt động trên các thiết bị / trình giả lập chạy iOS 10 ;-)
d4Rk

Vâng, tôi đã viết nó trên tiêu đề ở trên. Nhưng bây giờ hầu hết mọi người đã nâng cấp lên ít nhất Xcode 9 ;-)
blackjacx

77

Xcode 9 đã giới thiệu các thủ thuật mới với XCTWaiter

Trường hợp thử nghiệm chờ đợi rõ ràng

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

Đại biểu bồi bàn để kiểm tra

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

Kết quả lớp bồi bàn

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
}

sử dụng mẫu

Trước Xcode 9

Mục tiêu 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];
        }
    }];
}

SỬ DỤNG

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

Nhanh

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

SỬ DỤNG

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

hoặc là

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

NGUỒN


1
tìm kiếm thêm một số hình minh họa liên quan đến ví dụ xcode9 ở trên
lần thứ


1
Thử nghiệm. Hoạt động như một lá bùa! Cảm ơn!
Dawid Koncewicz

32

Kể từ Xcode 8.3, chúng tôi có thể sử dụng 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
}

Một mẹo khác là viết một waithàm, tín dụng gửi cho John Sundell vì đã cho tôi xem

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

và sử dụng nó như

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

Dựa trên câu trả lời của @ Ted , tôi đã sử dụng tiện ích mở rộng này:

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

}

Bạn có thể sử dụng nó như thế này

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

Nó cũng cho phép chờ đợi một phần tử biến mất hoặc bất kỳ thuộc tính nào khác thay đổi (bằng cách sử dụng khối thích hợp)

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

+1 rất hay và nó sử dụng vị từ khối mà tôi nghĩ là tốt hơn rất nhiều vì đôi khi các biểu thức vị ngữ chuẩn không hoạt động với tôi, ví dụ như khi chờ một số thuộc tính trên XCUIElements, v.v.
lawicko

10

Biên tập:

Tôi thực sự đã nhận ra rằng trong Xcode 7b4, kiểm tra giao diện người dùng hiện có expectationForPredicate:evaluatedWithObject:handler:

Nguyên:

Một cách khác là quay vòng chạy trong một khoảng thời gian đã đặt. Thực sự chỉ hữu ích nếu bạn biết bạn sẽ cần bao nhiêu thời gian (ước tính)

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

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

Điều này không siêu hữu ích nếu bạn cần kiểm tra một số điều kiện để tiếp tục kiểm tra. Để chạy kiểm tra có điều kiện, sử dụng một whilevòng lặp.


Điều này rõ ràng và rất hữu ích đối với tôi, đặc biệt là, ví dụ như chờ khởi chạy ứng dụng, yêu cầu dữ liệu được tải sẵn và thực hiện đăng nhập / đăng xuất. Cảm ơn bạn.
felixwcf

4

Đoạn mã sau chỉ hoạt động với 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];
}

Chỉ cần thực hiện cuộc gọi đến chức năng này như được đưa ra dưới đây.

[self wait: 10];

Lỗi -> bị bắt "NSIternalInconsistencyException", "Vi phạm API - cuộc gọi được thực hiện để chờ mà không có bất kỳ mong đợi nào được đặt ra."
FlowUI. SimpleUITesting.com

@ iOSCalWikipatchthecode.com, Bạn đã tìm thấy giải pháp thay thế cho điều đó chưa?
Tối đa

@Max bạn có thể sử dụng bất kỳ của những người khác trên trang này?
FlowUI. SimpleUITesting.com

@ iOSCalWikipatchthecode.com Không, tôi chỉ cần một số độ trễ mà không có bất kỳ yếu tố nào để kiểm tra. Vì vậy, tôi cần thay thế điều này.
Tối đa

@Max tôi đã sử dụng câu trả lời được chọn trên trang này. Nó làm việc cho tôi. Có lẽ bạn có thể hỏi họ những gì cụ thể bạn đang tìm kiếm.
FlowUI. SimpleUITesting.com

4

Trong trường hợp của tôi sleeptạo ra tác dụng phụ vì vậy tôi đã sử dụngwait

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

0

Theo API cho XCUIE bổ sung .existscó thể được sử dụng để kiểm tra xem một truy vấn có tồn tại hay không vì vậy cú pháp sau đây có thể hữu ích trong một số trường hợp!

let app = XCUIApplication()
app.launch()

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

Nếu bạn tự tin rằng kỳ vọng của bạn sẽ được đáp ứng, cuối cùng bạn có thể thử chạy nó. Cần lưu ý rằng sự cố có thể tốt hơn nếu chờ đợi quá lâu trong trường hợp đó waitForExpectationsWithTimeout(_,handler:_)từ bài đăng của @Joe Masilotti nên được sử dụng.


0

giấc ngủ sẽ chặn các chủ đề

"Không có xử lý vòng lặp chạy xảy ra trong khi luồng bị chặn."

bạn có thể sử dụng WaitForExistence

let app = XCUIApplication()
app.launch()

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

0

Điều này sẽ tạo ra độ trễ mà không đặt luồng vào chế độ ngủ hoặc đưa ra lỗi khi hết thời gian:

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

Bởi vì sự kỳ vọng bị đảo ngược, nó sẽ hết thời gian lặng lẽ.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.