Задержка / ожидание в тестовом случае тестирования Xcode UI

182

Я пытаюсь написать контрольный пример, используя новый UI Testing, доступный в Xcode 7 beta 2. В приложении есть экран входа в систему, где он вызывает сервер для входа в систему. С этим связана задержка, так как это асинхронная операция.

Есть ли способ вызвать механизм задержки или ожидания в XCTestCase, прежде чем перейти к дальнейшим шагам?

Нет доступной документации, и я просмотрел файлы заголовков классов. Не смог найти ничего связанного с этим.

Есть идеи / предложения?

Tejas HS
источник
13
Я думаю, что NSThread.sleepForTimeInterval(1)должно работать
Kametrixom
Большой! Похоже, это работает. Но я не уверен, что это рекомендуемый способ сделать это. Я думаю, что Apple должна дать лучший способ сделать это. Возможно, придется подать радар
Tejas HS
Я действительно думаю, что все в порядке, это действительно самый распространенный способ приостановить текущий поток на определенное время. Если вы хотите больше контроля, вы также можете попасть в GCD (The dispatch_after, dispatch_queueвещи)
Kametrixom
@ Kametrixom Не отмечайте цикл выполнения - Apple представила собственное асинхронное тестирование в бета-версии 4. Подробности см. В моем ответе .
Джо Масилотти
2
Swift 4.0 -> Thread.sleep (forTimeInterval: 2)
uplearnedu.com

Ответы:

168

Асинхронное тестирование пользовательского интерфейса было введено в Xcode 7 Beta 4. Ожидать метку с текстом «Hello, world!» чтобы появиться вы можете сделать следующее:

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)

Более подробную информацию о UI Testing можно найти в моем блоге.

Джо Масилотти
источник
19
К сожалению, нет никакого способа признать, что истекло время ожидания, и двигаться дальше - waitForExpectationsWithTimeoutавтоматически провалит ваш тест, что весьма печально.
Джедиджа,
@Jedidja На самом деле, это не происходит для меня с XCode 7.0.1.
Бастиан
@ Бастиан Хмм интересно; Мне придется перепроверить это.
Джедиджа
1
это не работает для меня. Вот мой пример: let xButton = app.toolbars.buttons ["X"] let there = NSPredicate (format: "exist == 1") hopeationForPredicate (Существует, оцененныйWithObject: xButton, обработчик: nil) waitForExpectationsWithTimeout (10, обработчик: ноль)
emoleumassi
app.launch(), Кажется, просто перезапустить приложение. Это необходимо?
Крис Принц
225

Кроме того, вы можете просто спать:

sleep(10)

Поскольку UITests запускаются в другом процессе, это работает. Я не знаю, насколько это целесообразно, но это работает.

mxcl
источник
2
Некоторое время нам нужен способ отсрочить, и мы не хотим, чтобы он провалился! спасибо
Тай Ле
13
Лучший ответ, который я когда-либо видел :) Я бы добавил + 100 голосов, если бы мог :)
Bartłomiej Semańczyk
8
Мне нравится NSThread.sleepForTimeInterval (0.2), так как вы можете указать задержки в секунду. (sleep () принимает целочисленный параметр; возможны только кратные секунды).
Грэм Перкс
5
@GrahamPerks, да, хотя есть также:usleep
mxcl
3
Это не плохое предложение (вы не понимаете, как работает UITesting), но даже если это было плохое предложение, иногда нет никакого способа создать ожидание, которое работает (система предупреждает кого-нибудь?), Так что это все, что у вас есть.
mxcl
78

iOS 11 / Xcode 9

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

Это отличная замена для всех пользовательских реализаций на этом сайте!

Обязательно посмотрите мой ответ здесь: https://stackoverflow.com/a/48937714/971329 . Там я опишу альтернативу ожидания запросов, которая значительно сократит время ваших тестов!

blackjacx
источник
Спасибо @daidai, я изменил текст :)
blackjacx
1
Да, это тот подход, который я использую при использовании, XCTestCaseи он работает как шарм. Я не понимаю, почему такие подходы sleep(3)так высоко оцениваются, потому что это увеличивает время тестирования искусственно и на самом деле не подходит при расширении набора тестов.
blackjacx,
На самом деле для этого требуется Xcode 9, но он работает на устройствах / симуляторах под управлением iOS 10 ;-)
d4Rk
Да, я написал это в заголовке выше. Но теперь большинству людей следовало бы перейти хотя бы на Xcode 9 ;-)
blackjacx
77

Xcode 9 представил новые трюки с XCTWaiter

Тестовый случай ждет явно

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

Делегаты экземпляра официанта для тестирования

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

Класс официанта возвращает результат

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
}

пример использования

До Xcode 9

Цель С

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

ИСПОЛЬЗОВАНИЕ

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

стриж

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

ИСПОЛЬЗОВАНИЕ

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

или

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

ИСТОЧНИК

Тед
источник
1
ищем еще иллюстрацию относительно приведенного выше примера
xcode9
Вы можете проверить shashikantjagtap.net/asynchronous-ios-testing-swift-xcwaiter
Тед
1
Проверено. Работает как шарм! Спасибо!
Давид Концевич
32

Начиная с Xcode 8.3, мы можем использовать 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
}

Еще один трюк - написать waitфункцию, спасибо Джону Санделлу за то, что он показал ее мне.

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

и использовать его как

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
источник
11

Основываясь на ответе @ Теда , я использовал это расширение:

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

}

Вы можете использовать это так

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

Это также позволяет ожидать исчезновения элемента или изменения любого другого свойства (используя соответствующий блок).

waitFor(object: element) { !$0.exists } // Wait for it to disappear
Бен Лингс
источник
+1 очень быстро, и он использует блочный предикат, который, я думаю, намного лучше, потому что стандартные выражения предикатов иногда не работают для меня, например, при ожидании некоторых свойств в XCUIElements и т. Д.
lawicko
10

Редактировать:

На самом деле мне пришло в голову, что в Xcode 7b4 тестирование пользовательского интерфейса теперь имеет expectationForPredicate:evaluatedWithObject:handler:

Оригинал:

Другой способ - вращать цикл выполнения в течение заданного промежутка времени. Действительно полезно, только если вы знаете, сколько (приблизительного) времени вам нужно ждать

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

Это не очень полезно, если вам нужно проверить некоторые условия, чтобы продолжить тест. Для запуска условных проверок используйте whileцикл.

enmiller
источник
Это чисто и очень полезно для меня, особенно, например, ожидание запуска приложения, запрос предварительно загруженных данных и выполнение входов / выходов из системы. Спасибо.
felixwcf
4

Следующий код работает только с 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];
}

Просто вызовите эту функцию, как указано ниже.

[self wait: 10];
arango_86
источник
Ошибка -> обнаружена «NSInternalInconsistencyException», «Нарушение API - вызов сделан для ожидания без каких-либо ожиданий».
FlowUI. SimpleUITesting.com
@ iOSCalendarpatchthecode.com, вы нашли альтернативное решение для этого?
Макс
@ Макс можете ли вы использовать другие на этой странице?
FlowUI. SimpleUITesting.com
@ iOSCalendarpatchthecode.com Нет, мне просто нужна задержка без элементов для проверки. Так что мне нужен альтернативный вариант.
Макс
@ Макс я использовал выбранный ответ на этой странице. Это сработало для меня. Может быть, вы можете спросить их, что конкретно вы ищете.
FlowUI. SimpleUITesting.com
4

В моем случае sleepсоздан побочный эффект, поэтому я использовалwait

let _ = XCTWaiter.wait(for: [XCTestExpectation(description: "Hello World!")], timeout: 2.0)
yoAlex5
источник
0

В соответствии с API для XCUIElement .existsможно использовать для проверки, существует ли запрос или нет, поэтому в некоторых случаях может быть полезен следующий синтаксис!

let app = XCUIApplication()
app.launch()

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

Если вы уверены, что ваши ожидания оправдаются, вы можете попробовать выполнить это. Следует отметить, что сбой может быть предпочтительным, если ожидание слишком велико, и в этом случае waitForExpectationsWithTimeout(_,handler:_)следует использовать пост @Joe Masilotti.

Reid
источник
0

сон заблокирует поток

Msgstr "Обработка цикла выполнения не происходит, пока поток заблокирован."

Вы можете использовать waitForExistence

let app = XCUIApplication()
app.launch()

if let label = app.staticTexts["Hello, world!"] {
label.waitForExistence(timeout: 5)
}
здравко здравкин
источник
0

Это создаст задержку, не переводя поток в спящий режим и не выдавая ошибку по таймауту:

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

Поскольку ожидание переворачивается, время ожидания истекает незаметно.

Кен Мерфи
источник