Задержка / ожидание в тестовом случае тестирования Xcode UI
Я пытаюсь написать контрольный пример с использованием нового UI Testing, доступного в Xcode 7 beta 2. В приложении есть экран входа в систему, где он вызывает сервер для входа в систему. С этим связана задержка, так как это асинхронная операция.
Есть ли способ вызвать механизм задержки или ожидания в XCTestCase, прежде чем перейти к дальнейшим действиям?
Нет доступной документации, и я просмотрел файлы заголовков классов. Не смог найти ничего связанного с этим.
Есть идеи / предложения?
14 ответов
Асинхронное тестирование пользовательского интерфейса было введено в 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)
Более подробную информацию о тестировании пользовательского интерфейса можно найти в моем блоге.
Кроме того, вы можете просто спать:
sleep(10)
Поскольку UITests запускаются в другом процессе, это работает. Я не знаю, насколько это целесообразно, но это работает.
iOS 11 / Xcode 9
<#yourElement#>.waitForExistence(timeout: 5)
Это отличная замена для всех пользовательских реализаций на этом сайте!
Обязательно посмотрите мой ответ здесь: /questions/12842507/asinhronnoe-testirovanie-polzovatelskogo-interfejsa-v-xcode-s-swift/12842513#12842513. Там я опишу альтернативу ожидания запросов, которая значительно сократит время ваших тестов!
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)
Начиная с 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
функция, кредит идет к Джону Sundell для показа это мне
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)
}
Это создаст задержку без перевода потока в спящий режим или выдачи ошибки по таймауту:
let delayExpectation = XCTestExpectation()
delayExpectation.isInverted = true
wait(for: [delayExpectation], timeout: 5)
Поскольку ожидание инвертировано, тайм-аут будет незаметен.
Основываясь на ответе @ Теда, я использовал это расширение:
extension XCTestCase {
// Based on https://stackru.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
В моем случае sleep
создал побочный эффект, поэтому я использовал
XCTWaiter.wait(for: [XCTestExpectation(description: "Hello World!")], timeout: 2.0)
Редактировать:
На самом деле мне пришло в голову, что в 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
петля.
Как мы это делаем в моей текущей компании, мы создаем ожидание выражения XCUIElement (для создания универсального метода ожидания). Мы делаем это следующим образом, чтобы убедиться, что его можно поддерживать (много разнообразия ожиданий, и мы не хотим создавать для этого множество методов/конкретных предикатов.
Свифт 5
Базовый метод
Выражение используется для формирования значения динамического предиката. Мы можем создать
XCTNSPredicateExpectation
из предикатов, которые мы затем переходим в
XCTWaiter
явно ждать. Если результат был иным, чем
completed
, то мы терпит неудачу с необязательным сообщением.
@discardableResult
func wait(
until expression: @escaping (XCUIElement) -> Bool,
timeout: TimeInterval = 15,
message: @autoclosure () -> String = "",
file: StaticString = #file,
line: UInt = #line
) -> Self {
if expression(self) {
return self
}
let predicate = NSPredicate { _, _ in
expression(self)
}
let expectation = XCTNSPredicateExpectation(predicate: predicate, object: nil)
let result = XCTWaiter().wait(for: [expectation], timeout: timeout)
if result != .completed {
XCTFail(
message().isEmpty ? "expectation not matched after waiting" : message(),
file: file,
line: line
)
}
return self
}
Применение
app.buttons["my_button"].wait(until: { $0.exists })
app.buttons["my_button"].wait(until: { $0.isHittable })
Ключевые пути
Затем мы оборачиваем это в метод, в котором keyPath и значение формируют выражение.
@discardableResult
func wait<Value: Equatable>(
until keyPath: KeyPath<XCUIElement, Value>,
matches match: Value,
timeout: TimeInterval = 15,
message: @autoclosure () -> String = "",
file: StaticString = #file,
line: UInt = #line
) -> Self {
wait(
until: { $0[keyPath: keyPath] == match },
timeout: timeout,
message: message,
file: file,
line: line
)
}
Применение
app.buttons["my_button"].wait(until: \.exists, matches: true)
app.buttons["my_button"].wait(until: \.isHittable, matches: false)
Затем вы можете обернуть этот метод, где
match
значение всегда
true
для варианта использования, который я нашел наиболее распространенным.
Применение
app.buttons["my_button"].wait(until: \.exists)
app.buttons["my_button"].wait(until: \.isHittable)
Я написал об этом пост и получил там файл с полным расширением: https://sourcediving.com/clean-waiting-in-xcuitest-43bab495230f
Следующий код работает только с 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];
let app = XCUIApplication()
app.launch()
//Find the button in the UI
let SettingsButton =
app.navigationBars["HomeView"].buttons["Settings"]
XCTAssertTrue(settingButton.waitForExistence(timeout: 10))
Согласно API для XCUIElement .exists
может использоваться для проверки, существует ли запрос или нет, поэтому в некоторых случаях может быть полезен следующий синтаксис!
let app = XCUIApplication()
app.launch()
let label = app.staticTexts["Hello, world!"]
while !label.exists {
sleep(1)
}
Если вы уверены, что ваши ожидания оправдаются, вы можете попробовать выполнить это. Следует отметить, что сбой может быть предпочтительным, если ожидание слишком долго, и в этом случае waitForExpectationsWithTimeout(_,handler:_)
из сообщения @Joe Masilotti следует использовать.
Сон заблокирует поток
"Обработка цикла выполнения не выполняется, пока поток заблокирован".
вы можете использовать waitForExistence
let app = XCUIApplication()
app.launch()
if let label = app.staticTexts["Hello, world!"] {
label.waitForExistence(timeout: 5)
}