Xcode UI测试的测试用例中的延迟/等待


182

我正在尝试使用Xcode 7 beta 2中提供的新UI测试编写测试用例。该应用程序有一个登录屏幕,在该屏幕上可以调用服务器进行登录。由于这是异步操作,因此存在延迟。

在进行进一步的步骤之前,是否有办法在XCTestCase中引起延迟或等待机制?

没有适当的文档,我浏览了这些类的Header文件。找不到与此相关的任何内容。

有什么想法/建议吗?


13
我认为NSThread.sleepForTimeInterval(1)应该有效
Kametrixom

大!看起来好像可行。但是我不确定这是否是推荐的方法。我认为苹果应该提供一种更好的方法。可能必须提交雷达
Tejas HS

我实际上真的认为还可以,这实际上是将当前线程暂停一定时间的最常用方法。如果你想要更多的控制,你也可以进入GCD(的dispatch_afterdispatch_queue东西)
Kametrixom

@Kametrixom不要打勾运行循环-Apple在Beta 4中引入了本机异步测试。有关详细信息,请参见我的答案
乔·马西洛蒂

2
Swift 4.0-> Thread.sleep(forTimeInterval:2)
uplearnedu.com

Answers:


168

Xcode 7 Beta 4中引入了异步UI测试。要等待带有文本“ 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测试的更多详细信息,请参见我的博客。


19
不幸的是,没有办法接受超时发生并继续前进-这waitForExpectationsWithTimeout将自动使您的测试失败,这是非常不幸的。
Jedidja

@Jedidja实际上,使用XCode 7.0.1不会发生这种情况。
巴斯蒂安,2015年

@Bastian Hmm有趣;我将不得不重新检查。
Jedidja

1
这对我不起作用。这是我的示例:let xButton = app.toolbars.buttons [“ X”] let exist = NSPredicate(格式:“ exists == 1”) nil)
emoleumassi

app.launch()似乎只是重新启动应用程序。有必要吗?
克里斯·普林斯

224

此外,您可以睡觉:

sleep(10)

由于UITest在另一个进程中运行,因此可以正常工作。我不知道这是多么可取,但可以。


2
有时候我们需要延迟的方式,不想让它失败!谢谢
Tai Le

13
我所见过的最好的答案:)我想补充+ 100票如果我能:)
巴特洛梅耶Semańczyk

8
我喜欢NSThread.sleepForTimeInterval(0.2),因为您可以指定亚秒级延迟。(sleep()采用整数参数;只能是一秒的倍数)。
Graham Perks

5
@GrahamPerks,是的,尽管还有:usleep
mxcl

3
这不是一个糟糕的建议(您不了解UITesting的工作原理),但是即使这是一个糟糕的建议,有时也无法制定出有效的期望(系统会警告任何人?),所以这就是您的全部。
mxcl

78

iOS 11 / Xcode 9

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

这是该网站上所有自定义实现的绝佳替代品!

请确保在这里查看我的答案:https : //stackoverflow.com/a/48937714/971329。我在那里描述了一种等待请求的替代方法,它将大大减少测试的运行时间!


谢谢@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之前

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

要么

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

资源


1
寻找有关上述xcode9示例的更多插图
rd_


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函数,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)
}

11

基于@Ted的答案,我使用了以下扩展名:

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中,UI测试现在具有 expectationForPredicate:evaluatedWithObject:handler:

原版的:

另一种方法是将运行循环旋转一定的时间。仅当您知道需要等待多少(估计)时间时,它才有用

对象: [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow: <<time to wait in seconds>>]]

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

如果您需要测试某些条件以继续测试,则此功能不是超级有用。要运行条件检查,请使用while循环。


这很干净,对我来说非常有用,尤其是例如等待应用程序启动,请求预加载的数据以及执行登录/注销操作。谢谢。
felixwcf

4

以下代码仅适用于ObjectiveC。

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

错误->捕获到“ NSInternalInconsistencyException”,“ API违反-调用以等待而未设置任何期望”。
FlowUI。SimpleUITesting.com

@ iOSCalendarpatchthecode.com,您是否找到了替代解决方案?
最多

@Max您可以使用此页面上的其他任何内容吗?
FlowUI。SimpleUITesting.com

@ iOSCalendarpatchthecode.com不,我只需要一些延迟就可以检查任何内容。因此,我需要替代方法。
最多

@Max我在此页面上使用了选定的答案。它为我工作。也许您可以问他们想要什么。
FlowUI。SimpleUITesting.com

4

就我而言,它sleep产生了副作用,所以我使用了wait

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

0

根据XCUIElement的API,.exists可用于检查查询是否存在,因此以下语法在某些情况下可能有用!

let app = XCUIApplication()
app.launch()

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

如果您有信心最终可以实现期望,则可以尝试运行此程序。应该注意的是,如果等待时间太长,最好使用崩溃,在这种情况下waitForExpectationsWithTimeout(_,handler:_),应使用@Joe Masilotti的帖子。


0

睡眠会阻塞线程

“在线程被阻塞时,不会发生运行循环处理。”

您可以使用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)

因为期望是反向的,所以它将安静地超时。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.