Tunda / Tunggu dalam kasus pengujian pengujian Xcode UI

182

Saya mencoba untuk menulis kasus uji menggunakan Pengujian UI baru yang tersedia di Xcode 7 beta 2. Aplikasi ini memiliki layar login di mana ia membuat panggilan ke server untuk login. Ada penundaan yang terkait dengan ini karena ini merupakan operasi yang tidak sinkron.

Apakah ada cara untuk menyebabkan penundaan atau menunggu mekanisme di XCTestCase sebelum melanjutkan ke langkah selanjutnya?

Tidak ada dokumentasi yang tepat tersedia dan saya membaca file Header kelas. Tidak dapat menemukan apa pun yang terkait dengan ini.

Ada ide / saran?

Tejas HS
sumber
13
Saya pikir NSThread.sleepForTimeInterval(1)harus bekerja
Kametrixom
Bagus! Ini sepertinya berfungsi. Tetapi saya tidak yakin apakah itu cara yang disarankan untuk melakukannya. Saya pikir Apple harus memberikan cara yang lebih baik untuk melakukannya. Mungkin harus mengajukan Radar
Tejas HS
Sebenarnya saya benar-benar berpikir tidak apa-apa, itu benar-benar cara paling umum untuk menjeda utas saat ini untuk waktu tertentu. Jika Anda ingin lebih banyak kontrol, Anda juga bisa masuk ke GCD (The dispatch_after, dispatch_queuestuff)
Kametrixom
@Kametrixom Jangan centang run loop - Apple memperkenalkan pengujian asinkron asli dalam Beta 4. Lihat jawaban saya untuk detailnya.
Joe Masilotti
2
Swift 4.0 -> Thread.sleep (forTimeInterval: 2)
uplearnedu.com

Jawaban:

168

Asynchronous UI Testing diperkenalkan di Xcode 7 Beta 4. Untuk menunggu label dengan teks "Halo, dunia!" untuk muncul Anda dapat melakukan hal berikut:

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)

Rincian lebih lanjut tentang Pengujian UI dapat ditemukan di blog saya.

Joe Masilotti
sumber
19
Sayangnya tidak ada cara untuk menerima bahwa batas waktu terjadi dan terus berjalan - waitForExpectationsWithTimeoutsecara otomatis akan gagal dalam ujian Anda yang sangat disayangkan.
Jedidja
@Jedidja Sebenarnya, ini tidak terjadi pada saya dengan XCode 7.0.1.
Bastian
@Bastian Hmm menarik; Saya harus memeriksa ulang ini.
Jedidja
1
itu tidak bekerja untuk saya. Berikut adalah contoh saya: biarkan xButton = app.toolbars.buttons ["X"] biarkan exist = NSPredicate (format: "exist == 1") ekspektasiForPredicate (ada, dievaluasiWithObject: xButton, handler: nil) waitForExpectationsWithTimeout (10, handler: nil)
emoleumassi
The app.launch()tampaknya hanya meluncurkan aplikasi. Apakah itu perlu?
Chris Prince
225

Selain itu, Anda bisa tidur:

sleep(10)

Karena UITests berjalan dalam proses lain, ini berfungsi. Saya tidak tahu bagaimana itu disarankan, tetapi berhasil.

mxcl
sumber
2
Beberapa waktu kita perlu cara untuk menunda dan tidak ingin itu menimbulkan kegagalan! terima kasih
Tai Le
13
Jawaban terbaik yang pernah saya lihat :) Saya akan menambahkan + 100 suara Jika saya bisa :)
Bartłomiej Semańczyk
8
Saya suka NSThread.sleepForTimeInterval (0.2) karena Anda dapat menentukan penundaan sub-detik. (sleep () mengambil parameter integer; hanya beberapa detik yang dimungkinkan).
Graham Perks
5
@GrahamPerks, ya, meskipun ada juga:usleep
mxcl
3
Ini bukan saran yang buruk (Anda tidak mengerti cara kerja UITesting), tetapi bahkan jika itu adalah saran yang buruk, kadang-kadang tidak ada cara untuk membuat harapan yang berhasil (sistem memberi peringatan kepada siapa pun?) Jadi hanya ini yang Anda miliki.
mxcl
78

iOS 11 / Xcode 9

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

Ini adalah pengganti yang bagus untuk semua implementasi kustom di situs ini!

Pastikan untuk melihat jawaban saya di sini: https://stackoverflow.com/a/48937714/971329 . Di sana saya menjelaskan alternatif untuk menunggu permintaan yang akan sangat mengurangi waktu tes Anda berjalan!

blackjacx
sumber
Terima kasih @daidai saya mengubah teks :)
blackjacx
1
Ya ini masih pendekatan yang saya akan gunakan ketika menggunakan XCTestCasedan itu bekerja seperti pesona. Saya tidak mengerti mengapa pendekatan seperti sleep(3)dipilih begitu tinggi di sini karena memperpanjang waktu pengujian secara artifisial dan benar-benar tidak ada pilihan ketika test suite Anda tumbuh.
blackjacx
Sebenarnya ini membutuhkan Xcode 9, tetapi bekerja pada perangkat / simulator yang menjalankan iOS 10 juga ;-)
d4Rk
Ya saya menulis itu pada judul di atas. Tetapi sekarang kebanyakan orang seharusnya meningkatkan ke setidaknya Xcode 9 ;-)
blackjacx
77

Xcode 9 memperkenalkan trik baru dengan XCTWaiter

Test case menunggu secara eksplisit

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

Delegasi instance pelayan untuk menguji

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

Kelas pelayan mengembalikan hasil

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
}

penggunaan sampel

Sebelum Xcode 9

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

PEMAKAIAN

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

Cepat

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

PEMAKAIAN

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

atau

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

SUMBER

Ted
sumber
1
mencari beberapa ilustrasi lebih lanjut tentang contoh xcode9 di atas
rd_
1
Diuji. Bekerja seperti pesona! Terima kasih!
Dawid Koncewicz
32

Pada Xcode 8.3, kita dapat menggunakan 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
}

Trik lain adalah menulis waitfungsi, kredit diberikan kepada John Sundell karena menunjukkannya kepada saya

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

dan menggunakannya seperti

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)
}
onmyway133
sumber
11

Berdasarkan jawaban @ Ted , saya telah menggunakan ekstensi ini:

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

}

Anda bisa menggunakannya seperti ini

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

Ini juga memungkinkan untuk menunggu elemen menghilang, atau properti lainnya berubah (dengan menggunakan blok yang sesuai)

waitFor(object: element) { !$0.exists } // Wait for it to disappear
Ben Lings
sumber
+1 sangat cepat, dan menggunakan predikat blok yang menurut saya jauh lebih baik karena ekspresi predikat standar kadang-kadang tidak bekerja untuk saya, misalnya ketika menunggu beberapa properti di XCUIElements dll.
lawicko
10

Edit:

Sebenarnya baru saja terpikir oleh saya bahwa di Xcode 7b4, pengujian UI sekarang telah expectationForPredicate:evaluatedWithObject:handler:

Asli:

Cara lain adalah dengan memutar putaran proses untuk waktu yang ditentukan. Benar-benar hanya berguna jika Anda tahu berapa banyak (perkiraan) waktu yang harus Anda tunggu

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

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

Ini tidak sangat berguna jika Anda perlu menguji beberapa kondisi untuk melanjutkan tes Anda. Untuk menjalankan pemeriksaan bersyarat, gunakan satu whilelingkaran.

enmiller
sumber
Ini bersih dan sangat berguna bagi saya terutama misalnya menunggu peluncuran aplikasi, meminta data yang dimuat sebelumnya dan melakukan hal-hal masuk / keluar. Terima kasih.
felixwcf
4

Kode berikut hanya berfungsi dengan 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];
}

Cukup panggil fungsi ini seperti yang diberikan di bawah ini.

[self wait: 10];
arango_86
sumber
Kesalahan -> ketahuan "NSInternalInconsistencyException", "pelanggaran API - panggilan dibuat untuk menunggu tanpa harapan yang telah ditetapkan."
FlowUI. SimpleUITesting.com
@ iOSCalendarpatchthecode.com, Sudahkah Anda menemukan solusi alternatif untuk itu?
Maks
@ Max dapatkah Anda menggunakan yang lain di halaman ini?
FlowUI. SimpleUITesting.com
@ iOSCalendarpatchthecode.com Tidak, saya hanya perlu penundaan tanpa elemen untuk memeriksa. Jadi saya perlu alternatif ini.
Maks
@ Max saya menggunakan jawaban yang dipilih di halaman ini. Ini berhasil untuk saya. Mungkin Anda bisa bertanya kepada mereka apa yang Anda cari secara spesifik.
FlowUI. SimpleUITesting.com
4

Dalam kasus saya sleepbuat efek samping jadi saya gunakanwait

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

Menurut API untuk XCUIElement .existsdapat digunakan untuk memeriksa apakah ada pertanyaan atau tidak sehingga sintaks berikut dapat berguna dalam beberapa kasus!

let app = XCUIApplication()
app.launch()

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

Jika Anda yakin harapan Anda akan terpenuhi pada akhirnya Anda bisa mencoba menjalankannya. Perlu dicatat bahwa mogok mungkin lebih baik jika penantiannya terlalu lama dalam hal mana waitForExpectationsWithTimeout(_,handler:_)dari pos @ Jo Masilotti harus digunakan.

Reid
sumber
0

tidur akan memblokir utas

"Tidak ada proses run loop terjadi saat utas diblokir."

Anda dapat menggunakan waitForExistence

let app = XCUIApplication()
app.launch()

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

Ini akan membuat penundaan tanpa membuat utas tertidur atau membuat kesalahan pada batas waktu:

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

Karena harapannya terbalik, ia akan diam-diam habis.

Ken Murphy
sumber