UI 测试删除文本字段中的文本

UI Test deleting text in text field

在我的测试中,我有一个带有预先存在的文本的文本字段。我想删除内容并输入新的字符串。

let textField = app.textFields
textField.tap()
// delete "Old value"
textField.typeText("New value")

当使用硬件键盘删除字符串时,没有为我生成任何记录。用软件键盘做同样的事情后,我得到:

let key = app.keys["Usuń"] // Polish name for the key
key.tap()
key.tap() 
... // x times

app.keys["Usuń"].pressForDuration(1.5)

我担心我的测试依赖于语言,所以我为我支持的语言创建了这样的东西:

extension XCUIElementQuery {
    var deleteKey: XCUIElement {
        get {
            // Polish name for the key
            if self["Usuń"].exists {
                return self["Usuń"]
            } else {
                return self["Delete"]
            }
        }
    }
}

代码看起来更好:

app.keys.deleteKey.pressForDuration(1.5)

但它非常脆弱。从模拟器退出后 Toggle software keyboard 被重置,我的测试失败了。我的解决方案不适用于 CI 测试。这个要怎么解决才能更通用?

由于您在问题的评论中修复了本地化的删除键名称问题,我假设您只需调用它就可以访问删除键 "Delete"。

下面的代码将允许您可靠地删除字段的内容:

while (textField.value as! String).characters.count > 0 {
    app.keys["Delete"].tap()
}

或Swift 4+:

while !(textView.value as! String).isEmpty {
    app.keys["Delete"].tap()
}

但与此同时,您的问题可能表明需要更优雅地解决此问题以提高应用的可用性。在文本字段上,您还可以添加一个 Clear button,用户可以使用它立即清空文本字段;

打开故事板和 select 文本字段,在属性检查器下找到 "Clear button" 并将其设置为所需的选项(例如始终可见)。

现在用户只需点击文本字段右侧的叉号即可清除该字段:

或者在你的 UI 测试中:

textField.buttons["Clear text"].tap()

我写了一个扩展方法来为我做这个,它非常快:

extension XCUIElement {
    /**
     Removes any current text in the field before typing in the new value
     - Parameter text: the text to enter into the field
     */
    func clearAndEnterText(text: String) {
        guard let stringValue = self.value as? String else {
            XCTFail("Tried to clear and enter text into a non string value")
            return
        }

        self.tap()

        let deleteString = String(repeating: XCUIKeyboardKey.delete.rawValue, count: stringValue.count)

        self.typeText(deleteString)
        self.typeText(text)
    }
}

这很容易使用:app.textFields["Email"].clearAndEnterText("newemail@domain.com")

所以,我还没有找到任何好的解决方案:/

而且我不喜欢依赖区域设置的解决方案,就像上面带有显式 "Clear text" 查找的解决方案。

所以,我进行了类型检查,然后试图在文本字段中找到清除按钮 它运作良好,除非你有一个按钮以上的自定义文本字段

我现在最好的是(我没有带有更多按钮的自定义文本字段):

    class func clearTextField(textField : XCUIElement!) -> Bool {

        guard textField.elementType != .TextField else {
            return false
        }

        let TextFieldClearButton = textField.buttons.elementBoundByIndex(0)

        guard TextFieldClearButton.exists else {
            return false
        }

        TextFieldClearButton.tap()

        return true
    }

我找到了以下解决方案:

let myTextView = app.textViews["some_selector"]
myTextView.pressForDuration(1.2)
app.menuItems["Select All"].tap()
app.typeText("New text you want to enter") 
// or use app.keys["delete"].tap() if you have keyboard enabled

当您点击并按住文本字段时,它会打开菜单,您可以在其中点击 "Select all" 按钮。之后,您只需使用键盘上的 "delete" 按钮删除该文本或输入新文本即可。它将覆盖旧的。

我在使用上述解决方案来解决我遇到的类似问题时遇到了一些困难:光标会把自己放在文本之前,然后从那里向后工作。此外,我想在删除之前检查文本字段中是否包含文本。这是我的解决方案,灵感来自 https://whosebug.com/users/482361/bay-phillips 的扩展名。我应该注意,点击删除键可能需要很长时间,可以用 .pressForDuration

代替
func clearAndEnterText(element: XCUIElement, text: String) -> Void
    {
        guard let stringValue = element.value as? String else {
            XCTFail("Tried to clear and enter text into a non string value")
            return
        }

        element.tap()

        guard stringValue.characters.count > 0 else
        {
            app.typeText(text)
            return
        }

       for _ in stringValue.characters
        {
            app.keys["delete"].tap()
        }
        app.typeText(text)
    }

我是 UI 使用 iOS 进行测试的新手,但我能够通过这个简单的解决方法清除文本字段。与 Xcode8 合作并计划尽快重构它:

func testLoginWithCorrectUsernamePassword() {
      //Usually this will be completed by Xcode
    let app = XCUIApplication()
      //Set the text field as a constant
    let usernameTextField = app.textFields["User name"]
      //Set the delete key to a constant
    let deleteKey = app.keys["delete"]
      //Tap the username text field to toggle the keyboard
    usernameTextField.tap()
      //Set the time to clear the field.  generally 4 seconds works
    deleteKey.press(forDuration: 4.0);
      //Enter your code below...
}

这将适用于文本字段和文本视图

对于SWIFT 3

extension XCUIElement {
    func clearText() {
        guard let stringValue = self.value as? String else {
            return
        }

        var deleteString = String()
        for _ in stringValue {
            deleteString += XCUIKeyboardKeyDelete
        }
        self.typeText(deleteString)
    }
}

for SWIFT 4, SWIFT 5

extension XCUIElement {
    func clearText() {
        guard let stringValue = self.value as? String else {
            return
        }

        var deleteString = String()
        for _ in stringValue {
            deleteString += XCUIKeyboardKey.delete.rawValue
        }
        typeText(deleteString)
    }
}

更新XCODE9

an apple bug 如果文本字段为空,则值和占位符值相等

extension XCUIElement {
    func clearText() {
        guard let stringValue = self.value as? String else {
            return
        }
        // workaround for apple bug
        if let placeholderString = self.placeholderValue, placeholderString == stringValue {
            return
        }

        var deleteString = String()
        for _ in stringValue {
            deleteString += XCUIKeyboardKey.delete.rawValue
        }
        typeText(deleteString)
    }
}

Xcode9,Swift4

尝试了上述解决方案,但 none 由于点击时出现了一些奇怪的行为 - 它将光标移动到文本字段的开头或文本中的某个随机点。我使用的方法是@oliverfrost 描述的 ,但我添加了一些方法来解决这些问题并将其组合成一个简洁的扩展。我希望它对某人有用。

extension XCUIElement {
    func clearText(andReplaceWith newText:String? = nil) {
        tap()
        tap() //When there is some text, its parts can be selected on the first tap, the second tap clears the selection
        press(forDuration: 1.0)
        let selectAll = XCUIApplication().menuItems["Select All"]
        //For empty fields there will be no "Select All", so we need to check
        if selectAll.waitForExistence(timeout: 0.5), selectAll.exists {
            selectAll.tap()
            typeText(String(XCUIKeyboardKey.delete.rawValue))
        }
        if let newVal = newText { typeText(newVal) }
    }
}

用法:

let app = XCUIApplication()
//Just clear text
app.textFields["field1"].clearText() 
//Replace text    
app.secureTextFields["field2"].clearText(andReplaceWith: "Some Other Text")

这样做可以在不依赖虚拟键盘的情况下删除文本框中的当前字符串值。

//在这个变量中读取你的文本框的值 让 textInTextField:String =

  let characterCount: Int = textInTextField.count
  for _ in 0..<characterCount {
    textFields[0].typeText(XCUIKeyboardKey.delete.rawValue)
  }

此解决方案的优点在于,无论模拟器是否具有虚拟键盘,它都能正常工作。

现在 swift 4.2 也许你应该试试下面的代码:

extension XCUIElement {
    /**
     Removes any current text in the field before typing in the new value
     - Parameter text: the text to enter into the field
     */
    func clearAndEnterText(text: String) {
        guard let stringValue = self.value as? String else {
            XCTFail("Tried to clear and enter text into a non string value")
            return
        }

        self.tap()
        for _ in 0..<stringValue.count {
            self.typeText(XCUIKeyboardKey.delete.rawValue)
        }

        self.typeText(text)
    }
}

我使用了@oliverfrost 描述的内容,但它不适用于 IPhone XR,我为了自己的使用对它做了一些改动,变成了这个

extension XCUIElement {
func clearText(andReplaceWith newText:String? = nil) {
    tap()
    tap() //When there is some text, its parts can be selected on the first tap, the second tap clears the selection
    press(forDuration: 1.0)
    let select = XCUIApplication().menuItems["Select"]
    //For empty fields there will be no "Select All", so we need to check
    if select.waitForExistence(timeout: 0.5), select.exists {
        select.tap()
        typeText(String(XCUIKeyboardKey.delete.rawValue))
    }
    if let newVal = newText { typeText(newVal) }
}
}

正如@zysoft 所说,您可以像这样使用它:

let app = XCUIApplication()
//Just clear text
app.textFields["field1"].clearText() 
//Replace text    
app.secureTextFields["field2"].clearText(andReplaceWith: "Some Other Text")

致仍在使用的人Objective-C

@implementation XCUIElement (Extensions)

-(void)clearText{
    if (!self){
        return;
    }
    if (![self.value isKindOfClass:[NSString class]]){
        return;
    }
    NSString* stringValue = (NSString*)self.value;
    for (int i=0; i<stringValue.length ; i++) {
        [self typeText:XCUIKeyboardKeyDelete];
    }
}

@end

Swift 5

基于@Bay Phillips 的回答,

extension XCUIElement {

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

        self.tap()

        let deleteString = stringValue.map { _ in "\u{8}" }.joined(separator: "")

        self.typeText(deleteString)
        self.typeText(text)
    }

}

我找到了一个使用 iOS 功能的解决方案,该功能通过点击几次来选择整个文本字段。然后,我们通过键入任何字符或按删除键(如果启用键盘)来删除文本字段。

let myTextView = app.textViews["some_selector"]
myTextView.tap(withNumberOfTaps: 2, numberOfTouches: 1)
app.typeText("New text you want to enter") 
// or use app.keys["delete"].tap() if you have keyboard enabled

您可以使用 doubleTap 来 select 所有文本并键入新文本来替换:

extension XCUIElement {
  func typeNewText(_ text: String) {
    if let existingText = value as? String, !existingText.isEmpty {
      if existingText != text {
        doubleTap()
      } else {
        return
      }
    }

    typeText(text)
  }
}

用法:

textField.typeNewText("New Text")