【问题标题】:Delay/Wait in a test case of Xcode UI testingXcode UI 测试用例中的延迟/等待
【发布时间】:2015-09-19 20:50:57
【问题描述】:

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

有没有办法在 XCTestCase 中引起延迟或等待机制,然后再继续执行进一步的步骤?

没有合适的文档可用,我浏览了类的头文件。找不到与此相关的任何内容。

有什么想法/建议吗?

【问题讨论】:

  • 我认为NSThread.sleepForTimeInterval(1) 应该可以工作
  • 太棒了!这看起来很有效。但我不确定这是否是推荐的方法。我认为Apple应该提供更好的方法来做到这一点。可能需要提交雷达
  • 我其实真的觉得还可以,真的是最常用的方式来暂停当前线程一段时间。如果你想要更多的控制权,你也可以进入 GCD(dispatch_afterdispatch_queue 的东西)
  • @Kametrixom 不要勾选运行循环 - Apple 在 Beta 4 中引入了原生异步测试。有关详细信息,请参阅 my answer
  • Swift 4.0 --> Thread.sleep(forTimeInterval: 2)

标签: ios ios9 xcode-ui-testing xcode7-beta2 xctwaiter


【解决方案1】:

另外,你可以睡觉:

sleep(10)

由于 UITest 在另一个进程中运行,因此可以正常工作。我不知道它有多可取,但它确实有效。

【讨论】:

  • 有时我们需要延迟的方式,不希望它引发失败!谢谢
  • 我喜欢 NSThread.sleepForTimeInterval(0.2) 因为你可以指定亚秒级的延迟。 (sleep() 接受一个整数参数;只能是秒的倍数)。
  • @GrahamPerks,是的,虽然也有:usleep
  • 我希望有更好的答案,但如果您不想导致失败,这似乎是目前唯一的方法。
  • 这不是一个糟糕的建议(你不明白 UITesting 是如何工作的),但即使这是一个糟糕的建议,有时也没有办法制定一个可行的期望(系统提醒任何人?)所以这就是你所拥有的。
【解决方案2】:

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)

更多details about UI Testing可以在我的博客上找到。

【讨论】:

  • 不幸的是,我们无法接受超时发生并继续前进 - waitForExpectationsWithTimeout 将自动使您的测试失败,这非常不幸。
  • @Jedidja 实际上,这不会发生在 XCode 7.0.1 中。
  • @Bastian 嗯,很有趣;我将不得不重新检查。
  • 它对我不起作用。这是我的示例: let xButton = app.toolbars.buttons["X"] let exists = NSPredicate(format: "exists == 1") expectForPredicate(exists, evaluateWithObject: xButton, handler: nil) waitForExpectationsWithTimeout(10, handler:无)
  • app.launch() 似乎只是重新启动应用程序。有必要吗?
【解决方案3】:

iOS 11 / Xcode 9

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

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

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

【讨论】:

  • 谢谢@daidai 我改了文字:)
  • 是的,这仍然是我在使用 XCTestCase 时要采用的方法,它就像一个魅力。我不明白为什么像sleep(3) 这样的方法在这里得到如此高的评价,因为它人为地延长了测试时间,并且当你的测试套件增长时真的没有选择。
  • 实际上它需要 Xcode 9,但也适用于运行 iOS 10 的设备/模拟器 ;-)
  • 是的,我在上面的标题上写了。但现在大多数人应该至少升级到 Xcode 9 ;-)
  • 我的答案被评为如此高,因为我在九年前写了它,当时它是唯一的选择。在某些情况下,它仍然是唯一的选择。你并不总是有一个元素可以等待它的存在。我给了你一个赞成票,我希望这能让你的脾气冷静一点。
【解决方案4】:

Xcode 9XCTWaiter引入了新技巧

测试用例显式等待

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

Waiter 实例委托测试

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

Waiter 类返回结果

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
}

sample usage

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)

SOURCE

【讨论】:

【解决方案5】:

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

【讨论】:

    【解决方案6】:

    基于@Ted's answer,我使用了这个扩展:

    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."
                    let location = XCTSourceCodeLocation(filePath: file, lineNumber: line)
                    let issue = XCTIssue(type: .assertionFailure, compactDescription: message, detailedDescription: nil, sourceCodeContext: .init(location: location), associatedError: nil, attachments: [])
                    self.record(issue)
                }
            }
        }
    
    }
    

    你可以这样使用它

    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 上的某些属性等时。
    • 对于 Xcode 13.1 只需更改行:UInt = #line => 行:Int = #line。存在编译器错误,基本上需要此修复
    【解决方案7】:

    编辑:

    实际上我刚刚想到,在 Xcode 7b4 中,UI 测试现在已经 expectationForPredicate:evaluatedWithObject:handler:

    原文:

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

    对象-C: [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow: &lt;&lt;time to wait in seconds&gt;&gt;]]

    斯威夫特: NSRunLoop.currentRunLoop().runMode(NSDefaultRunLoopMode, beforeDate: NSDate(timeIntervalSinceNow: &lt;&lt;time to wait in seconds&gt;&gt;))

    如果您需要测试某些条件才能继续测试,这并不是很有用。要运行条件检查,请使用 while 循环。

    【讨论】:

    • 这很干净,对我来说非常有用,尤其是例如等待应用启动、请求预加载数据和登录/注销等。谢谢。
    【解决方案8】:

    这将造成延迟,而不会使线程进入睡眠状态或在超时时抛出错误:

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

    因为期望反转,所以会悄悄超时。

    【讨论】:

      【解决方案9】:

      在我的例子中,sleep 产生了副作用,所以我使用了wait

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

      【讨论】:

        【解决方案10】:

        在我目前的公司,我们的做法是创建一个 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 和 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)
        

        然后您可以包装该方法,其中match 值始终为true,这是我发现最常见的用例。

        用法

        app.buttons["my_button"].wait(until: \.exists)
        app.buttons["my_button"].wait(until: \.isHittable)
        

        我写了一篇关于它的帖子,并在那里获得了完整的扩展文件:https://sourcediving.com/clean-waiting-in-xcuitest-43bab495230f

        【讨论】:

          【解决方案11】:

          以下代码仅适用于 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];
          

          【讨论】:

          • 错误 --> 捕获“NSInternalInconsistencyException”、“API 违规 - 调用等待而没有设置任何期望。”
          • @iOSCalendarpatchthecode.com,你找到替代解决方案了吗?
          • @Max 您可以使用此页面上的任何其他内容吗?
          • @iOSCalendarpatchthecode.com 不,我只需要一些延迟,无需检查任何元素。所以我需要这个的替代品。
          • @Max 我在此页面上使用了选定的答案。它对我有用。也许你可以问问他们你具体在找什么。
          【解决方案12】:

          sleep 会阻塞线程

          “线程被阻塞时不进行运行循环处理。”

          你可以使用 waitForExistence

          let app = XCUIApplication()
          app.launch()
          
          if let label = app.staticTexts["Hello, world!"] {
          label.waitForExistence(timeout: 5)
          }
          

          【讨论】:

            【解决方案13】:

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

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

            如果您确信最终会满足您的期望,您可以尝试运行此程序。应该注意的是,如果等待时间过长,崩溃可能更可取,在这种情况下,应该使用来自@Joe Masilotti 的帖子中的waitForExpectationsWithTimeout(_,handler:_)

            【讨论】:

              【解决方案14】:
                 let app = XCUIApplication()
                  app.launch()
              
                   //Find the button in the UI 
                  let SettingsButton =
                      app.navigationBars["HomeView"].buttons["Settings"]
                  XCTAssertTrue(settingButton.waitForExistence(timeout: 10))
              

              【讨论】:

                猜你喜欢
                • 2015-11-25
                • 1970-01-01
                • 2015-10-14
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多