我正在尝试使用Xcode 7 beta 2中提供的新UI测试编写一个测试用例。该应用程序有一个登录屏幕,它调用服务器登录。由于这是一个异步操作,因此存在与此相关的延迟。
在进行进一步的步骤之前,是否有一种方法可以在XCTestCase中引起延迟或等待机制?
没有合适的文档,我浏览了类的头文件。找不到任何与此相关的信息。
意见/建议吗?
我正在尝试使用Xcode 7 beta 2中提供的新UI测试编写一个测试用例。该应用程序有一个登录屏幕,它调用服务器登录。由于这是一个异步操作,因此存在与此相关的延迟。
在进行进一步的步骤之前,是否有一种方法可以在XCTestCase中引起延迟或等待机制?
没有合适的文档,我浏览了类的头文件。找不到任何与此相关的信息。
意见/建议吗?
当前回答
编辑:
我突然想到在Xcode 7b4中,UI测试现在有 expectationForPredicate: evaluatedWithObject:处理程序:
原:
另一种方法是在一段时间内旋转运行循环。只有当你知道你需要等待多长时间时才有用
Obj-C: [NSRunLoop currentRunLoop] runMode: nsdefaultrunopmode befordate:[NSDate datetimeintervalcenow: << <time wait in sec >>]
迅速: NSRunLoop.currentRunLoop().runMode(nsdefuultrunloopmode, beforeDate: NSDate(timeIntervalSinceNow: <<time to wait in seconds>>)))
如果您需要测试某些条件才能继续测试,那么这并不是非常有用。要运行条件检查,请使用while循环。
其他回答
此外,你可以只睡觉:
sleep(10)
因为uitest在另一个进程中运行,所以这是可行的。我不知道这有多明智,但确实有效。
从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
}
另一个技巧是写一个等待函数,感谢John 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)
}
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之前
Objective - 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];
}
}];
}
使用
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)
or
let element = app.staticTexts["Name of your element"]
self.waitForElementToAppear(element, timeout: 10)
源
根据XCUIElement的API, .exists可以用来检查查询是否存在,所以下面的语法在某些情况下是有用的!
let app = XCUIApplication()
app.launch()
let label = app.staticTexts["Hello, world!"]
while !label.exists {
sleep(1)
}
如果你有信心你的期望最终会得到满足,你可以试着运行这个。应该注意的是,如果等待太长,崩溃可能是可取的,在这种情况下,应该使用@Joe Masilotti的帖子中的waitForExpectationsWithTimeout(_,handler:_)。
在我目前的公司,我们是这样做的:创建一个XCUIElement表达式期望(以创建一个通用的等待方法)。我们以以下方式来确保它是可维护的(有很多不同的期望,并且不想为此创建很多方法/特定的谓词。
斯威夫特5
基本方法
该表达式用于形成一个动态谓词值。我们可以从谓词创建xctnspredicateexpect,然后将其传递给XCTWaiter以显式等待。如果结果不是已完成,则失败并返回一条可选消息。
@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 })
Keypaths
然后将其包装在一个方法中,其中keyPath和match值构成表达式。
@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)
然后可以包装该方法,其中匹配值对于我发现的最常见的用例始终为真。
使用
app.buttons["my_button"].wait(until: \.exists)
app.buttons["my_button"].wait(until: \.isHittable)
我写了一篇关于它的文章,并获得了完整的扩展文件:https://sourcediving.com/clean-waiting-in-xcuitest-43bab495230f