在 XCUITests 中,如何等待两个 ui 元素中的任何一个存在

In XCUITests, how to wait for existence of either of two ui elements

查看 XCTWaiter().wait(...) 我相信我们可以使用此代码等待 多重期望 变为真

let notHittablePredicate = NSPredicate(format: "hittable == false")
let myExpectation = XCTNSPredicateExpectation(predicate: notHittablePredicate, object: element)
let result = XCTWaiter().wait(for: [myExpectation], timeout: timeout)
//for takes array of expectations

但这在所提供的期望中使用了 like AND。有没有办法在提供的期望中进行 OR。

就像我在登录时有一个用例,点击提交后,我想等待两个元素之一。第一个元素是 "You are already logged in on another device. If you continue any unsaved data on your other device will be lost?"。第二个元素是登录后的主屏幕。所以任何人都可以出现。目前我首先等待第一个元素直到超时发生,然后等待第二个元素。但是我想在这里优化时间,只要两个元素中的任何一个存在==true就继续。然后我将检查 element1 是否存在,然后点击是,然后等待主屏幕,否则只需断言 element2 存在。

如果问题中有不清楚的地方,请发表评论。谢谢

http://masilotti.com/ui-testing-tdd/ 启发,您不必依赖 XCTWaiter。您可以简单地 运行 一个循环并测试其中一个是否存在。

/// Waits for either of the two elements to exist (i.e. for scenarios where you might have
/// conditional UI logic and aren't sure which will show)
///
/// - Parameters:
///   - elementA: The first element to check for
///   - elementB: The second, or fallback, element to check for
/// - Returns: the element that existed
@discardableResult
func waitForEitherElementToExist(_ elementA: XCUIElement, _ elementB: XCUIElement) -> XCUIElement? {
    let startTime = NSDate.timeIntervalSinceReferenceDate
    while (!elementA.exists && !elementB.exists) { // while neither element exists
        if (NSDate.timeIntervalSinceReferenceDate - startTime > 5.0) {
            XCTFail("Timed out waiting for either element to exist.")
            break
        }
        sleep(1)
    }

    if elementA.exists { return elementA }
    if elementB.exists { return elementB }
    return nil
}

那么你可以这样做:

let foundElement = waitForEitherElementToExist(elementA, elementB)
if foundElement == elementA {
    // e.g. if it's a button, tap it
} else {
    // element B was found
}

lagoman 的回答绝对正确而且很棒。不过我需要等待超过 2 个可能的元素,所以我调整了他的代码以支持 ArrayXCUIElement 而不是仅仅两个。

@discardableResult
func waitForAnyElement(_ elements: [XCUIElement], timeout: TimeInterval) -> XCUIElement? {
    var returnValue: XCUIElement?
    let startTime = Date()
    
    while Date().timeIntervalSince(startTime) < timeout {
        if let elementFound = elements.first(where: { [=10=].exists }) {
            returnValue = elementFound
            break
        }
        sleep(1)
    }
    return returnValue
}

可以像

一样使用
let element1 = app.tabBars.buttons["Home"]
let element2 = app.buttons["Submit"]
let element3 = app.staticTexts["Greetings"]
foundElement = waitForAnyElement([element1, element2, element3], timeout: 5)

// do whatever checks you may want
if foundElement == element1 {
     // code
}

NSPredicate 也支持 OR 谓词。

例如,我写了这样的东西来确保我的应用程序在我开始尝试在 UI 测试中与之交互之前完全完成启动。这是在检查应用程序中是否存在各种地标,我知道这些地标在启动后的每个可能的启动状态上都是唯一存在的。

extension XCTestCase {
  func waitForLaunchToFinish(app: XCUIApplication) {
    let loginScreenPredicate = NSPredicate { _, _ in
      app.logInButton.exists
    }

    let tabBarPredicate = NSPredicate { _, _ in
      app.tabBar.exists
    }

    let helpButtonPredicate = NSPredicate { _, _ in
      app.helpButton.exists
    }

    let predicate = NSCompoundPredicate(
      orPredicateWithSubpredicates: [
        loginScreenPredicate,
        tabBarPredicate,
        helpButtonPredicate,
      ]
    )

    let finishedLaunchingExpectation = expectation(for: predicate, evaluatedWith: nil, handler: nil)
    wait(for: [finishedLaunchingExpectation], timeout: 30)
  }
}

在控制台中进行测试时 运行 有一系列重复检查是否存在我要检查的各种按钮,每次检查之间的时间长短不一。

t = 13.76s Wait for com.myapp.name to idle

t = 18.15s Checking existence of "My Tab Bar" Button

t = 18.88s Checking existence of "Help" Button

t = 20.98s Checking existence of "Log In" Button

t = 22.99s Checking existence of "My Tab Bar" Button

t = 23.39s Checking existence of "Help" Button

t = 26.05s Checking existence of "Log In" Button

t = 32.51s Checking existence of "My Tab Bar" Button

t = 16.49s Checking existence of "Log In" Button

瞧,现在我可以同时进行,而不是单独等待每个元素。

这当然非常灵活,因为您可以根据需要添加任意数量的元素。如果您想要 OR 和 AND 谓词的组合,您也可以使用 NSCompoundPredicate 来实现。这可以很容易地改编成一个更通用的函数,它接受一个元素数组,如下所示:

func wait(for elements: XCUIElement...) { … }

甚至可以传递一个参数来控制它是使用 OR 还是 AND。

嘿,其他适合我们的选择。我也希望能帮助到别人。

 XCTAssert(
            app.staticTexts["Hello Stack"]
                .waitForExistence(timeout: 10) || app.staticTexts["Hi Stack"]
                .waitForExistence(timeout: 10)
        )