当 运行 使用并行测试同时进行所有测试时,输入文本字段不稳定

Inputting textfields is flaky when running all tests at once using parallel testing

我正在尝试使用 XCUITest 进行 UI 测试。目前我在将文本输入 UITextField 时遇到了一些问题。这是我在 Robot class 中使用键盘输入文本的内容:

func typeTextToTextField(_ element: XCUIElement, text: String, timeout: TimeInterval = timeInterval,
                     file: StaticString, line: UInt) {
    guard assertExists(element, timeout: timeout, file: file, line: line),
        element.isHittable else {
            return
    }

    element.tap()
    sleep(2)
    app.activate()
    element.typeText("\(text)\n")
    sleep(2)
}

我试过这些步骤才变成这样:

  1. 只有 element.tap()element.typeText()
  2. 添加守卫等待存在(assertExists()块)
  3. 添加了 sleep() 等待键盘出现几秒钟。
  4. 添加了 app.activate() 因为显然有时查询花费的时间太长。关于这里错误的回答说我应该先调用 app.activate()。

如果我运行一次使用并行测试进行所有测试,一些使用它的测试会工作,但有些会失败。有些会正确显示键盘并正确键入文本,而另一些则无法显示键盘并显示此错误 Failed to synthesize event: Neither element nor any descendant has keyboard focus.。但是当我 运行 一次测试一个时,它们都会是绿色的并且可以完美地工作。

因此,接下来,我尝试通过粘贴文本而不是键入来更改输入方式。那也行不通。这是代码:

func pasteToTextField(_ element: XCUIElement, text: String, timeout: TimeInterval = timeInterval,
                      file: StaticString, line: UInt) {
    app.activate()
    guard assertExists(element, timeout: timeout, file: file, line: line),
        element.isHittable else {
            return
    }

    UIPasteboard.general.string = text
    app.activate()
    element.tap()
    element.doubleTap()
    element.press(forDuration: 1.2)
    tap(pasteMenuItem, timeout: timeout, file: file, line: line)
    sleep(5)
}

所以我做到了:

  1. 只有element.tap()tap(pasteMenuItem, timeout: timeout, file: file, line: line)(与app.menuItems["Paste"].tap()相同)
  2. 添加了 element.doubleTap()
  3. 添加了element.press()

使用粘贴功能时,有时“粘贴”菜单项不显示并出现此错误:failed - Element: "Paste" MenuItem does not exist!。但有时它工作得很好。

所以,我现在束手无策了。如何将文本输入到文本字段中,无论是 运行 所有测试还是 运行 单个测试用例,它都肯定能在所有条件下正常工作?

提前致谢。

PS:它在我的 CI 机器上工作得很好。真奇怪。

你可以试试这个

extension XCUIElement {
    func clearAndEnterText(_ text: String, app: XCUIApplication) {
        let currentClipboard = UIPasteboard.general.string ?? ""

        waitForElementToBecomeHittable(timeout: .small)

        guard let stringValue = value as? String else {
            return XCTFail("Tried to clear and enter text into a non string value")
        }

        if stringValue == text {
            return
        }

        if stringValue.isNotEmpty {
            if app.isKeyboardKeysAvaliable(key: XCUIKeyboardKey.delete.rawValue) {
                let deleteString = stringValue.map { _ in XCUIKeyboardKey.delete.rawValue }.joined()
                typeText(deleteString)
            } else {
                let selectAllButton = app.menuItems.element(predicate: .label(Comparison.containsAny, "Select All")).firstMatch
                if !selectAllButton.waitForExistence(timeout: .small) {
                    press(forDuration: 1.1)
                }

                if selectAllButton.waitForExistence(timeout: .small) {
                    selectAllButton.tapElement()
                }
            }
        }

        if value(forKey: "hasKeyboardFocus") as? Bool ?? false {
            typeText(text)
        } else {
            UIPasteboard.general.string = text
            let pasteButton = app.menuItems.element(predicate: .label(Comparison.containsAny, "Paste"))

            if !pasteButton.waitForExistence(timeout: .small) {
                tap()
                press(forDuration: 2.1)
            }

            if pasteButton.waitForExistence(timeout: .small) {
                 pasteButton.tapElement()
            }
        }

        // Dismiss tooltip if it is still displayed
        if app.menuItems.element.waitForExistence(timeout: .small) {
            app.tap()
        }

        waitForElementToBecomeHittable(timeout: .small)

        UIPasteboard.general.string = currentClipboard
    }
}

注意:您需要导入此文件 https://github.com/ZhipingYang/Einstein/blob/600854f9b6f93bb3694deddb3fb6edbae0f67f74/Class/UITest/Model/EasyPredicate.swift,而且我已将名称从 EasyPredicate 更改为 Predicate

extension XCUIApplication {
    func isKeyboardKeysAvaliable(key: String) -> Bool {
        let keyboard = keyboards.element(boundBy: 0)
        if key.contains(all: "next") {
            return keyboard.buttons[key].exists
        } else {
            return keyboard.keys[key].exists
        }
    }
}

extension XCUIElement {
    @discardableResult
    func waitForElementToBecomeHittable(timeout: Timeout) -> Bool {
        return waitForExistence(timeout: timeout) && isHittable
    }