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


74

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

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测试。如何解决这个问题才更普遍?


我无法重现您遇到的失败。我添加了相同的扩展名,将模拟器的语言切换为波兰语,并验证了“Usún”键是否已被点击。重新启动/重置模拟器似乎对该Toggle software keyboard设置没有任何影响。文本视图中是否还有其他东西可以隐藏/关闭键盘?
Joe Masilotti 2015年

重启系统后,也许我已经Toggle software keyboard重置了。这不是模拟器的默认设置(而且我不知道是否可以更改)。在您可以从测试(或测试方案)级别修复语言和软件键盘设置之前,这种方法或其他方法是不可靠的。
TomaszBąk2015年

我自己的评论给了我一个主意,我在“方案”>“选项”>“应用程序语言”中找到了语言设置。键盘问题仍未解决。
TomaszBąk2015年

真好!这样可以解决故障吗?
Joe Masilotti 2015年

修复了仅本地化的删除键名称问题。
TomaszBąk2015年

Answers:


150

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

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")


20
您可以通过以下方式以更实用的方式创建“删除字符串”:let deleteString = stringValue.characters.map { _ in "\u{8}" }.joinWithSeparator("")
TomaszBąk2015年

1
太棒了!我不知道他们已经joinWithSeparator在Swift 2中添加了。谢谢!
菲利普斯湾(Bay Phillips)

8
对于swift4,请使用XCUIKeyboardKey.delete.rawValue
Daniel

18
对于Swift 4,您可以使用let deleteString = String(repeating: XCUIKeyboardKey.delete.rawValue, count: stringValue.characters.count)
Stasel,

14
对于Swift 4.2:let deleteString = String(repeating: XCUIKeyboardKey.delete.rawValue, count: stringValue.count)
Rody Davis

20

由于您已在问题的注释中解决了本地化的删除键名称问题,因此我假设您可以通过将其称为“删除”来访问删除键。

下面的代码将使您能够可靠地删除字段的内容:

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,用户可以立即使用将该文本字段清空;

打开情节提要,然后选择文本字段,在属性检查器下找到“清除按钮”并将其设置为所需的选项(例如,始终可见)。

清除按钮选择

现在,用户可以在文本字段右侧的十字架上轻按一下以清除字段:

清除按钮

或在您的UI测试中:

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

我喜欢带有清晰按钮的想法,但我仍在寻找一种在测试中强制使用软件键盘的方法。好像涉及到while循环-每个抽头之后都有延迟,因此对于长字符串来说,这会使测试变慢。
TomaszBąk2015年

1
+1是!使用编写测试的过程来改进软件。也许它不能完全满足OP的要求,但这恰恰是遇到类似问题时我需要的指针。
安迪·莫蒂默

3
这使我在Xcode 7.2中
遇到

1
改变应用程序的行为方式来进行测试是不可或缺的恕我直言。@ bay.philips的答案似乎更加合法。
hris.to

2
我是在假设您要针对实际用例编写测试的情况下编写此代码的,而不仅仅是在测试中做随机的事情。利用您可以获得的每一点反馈来改善可用性,从而为用户带来更好的应用程序。
马丁·霍尔斯

15

这将适用于textfield和textview

对于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)
    }
}

对于SWIFT 4SWIFT 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)
    }
}

更新XCODE 9

一个苹果错误,如果文本字段为空,则value和placeholderValue相等

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

4
哇,您可以预测到Swift 99之前吗?
丹尼尔(Daniel)

@丹尼尔·哈哈(Daniel haha​​)只是因为我们不得不将其更改为快速2、3、4而感到恼火。–
Ted

@SujitBaranwal好的,将检查发生了什么更改
Ted

这段代码可以在我的模拟器和设备上正常运行,但是当我在浏览器堆栈(基于云的平台)上对它进行远程测试时,第一个保护声明仅从那里返回。这意味着我的文本字段内的文本不会被识别为字符串。我确定文本在那里,因为也记录了测试用例。有什么帮助吗?
nr5

13

我发现以下解决方案:

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

当您点击并按住文本字段时,将打开菜单,您可以在其中点击“全选”按钮。之后,您所要做的就是通过键盘上的“删除”按钮删除该文本,或者仅输入新文本。它将覆盖旧的。


2
您还可以myTextView.doubleTap()用来调出菜单,这可能会更快一些
Hudson

2
这并不总是可行的,因为有时长按可以在文本字段中突出显示一个单词,这导致“全选”按钮不可见
Tadej Magajna

7

Xcode 9,Swift 4

尝试了上面的解决方案,但由于点击时有些怪异的行为而无法使用-它将光标移动到了文本字段的开头,或文本中的某个随机点。我使用的方法是这里描述的@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")

4

因此,我尚未找到任何好的解决方案:/

而且我不喜欢与语言环境相关的解决方案,例如上面带有显式“清除文本”查找的方法。

因此,我进行了检查,然后尝试在文本字段中找到清除按钮,除非您有带有多个按钮的自定义文本字段,否则它会很好

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

    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
    }

3

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

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

看起来像是四次点击是每个苹果
马克·格里尔

但是,有时我发现我需要事先执行.tap()来设置焦点。这是我的代码段。
Mark Gerrior

let toSelectWord = 2 // Select a word: Double-tap the word with one finger. let toSelectSentence = 3 // Select a sentence: Triple-tap the sentence with one finger. let toSelectParagraph = 4 // Select a paragraph: Quadruple-tap with one finger. app.textFields["sd.Title"].tap() app.textFields["sd.Title"].tap(withNumberOfTaps: toSelectParagraph, numberOfTouches: 1) app.textFields["sd.Title"].typeText("Old Task")
Mark Gerrior

1
只要withNumberOfTaps: 2是足够的。
Boris Y.

2

这样做无需依赖虚拟键盘即可删除文本框中的当前字符串值。

//在此变量中读取文本框的值let textInTextField:String =

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

这个解决方案的好处是,不管模拟器是否带有虚拟键盘,它都可以正常工作。


1

现在,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)
    }
}

@eonist-禁用硬件键盘,我认为它将起作用。请参阅:stackoverflow.com/a/34095158/238166
Inti,

1

对于仍在使用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

0

我很难获得上述解决方案来解决我遇到的类似问题:光标将自己置于文本之前,然后从那里向后工作。另外,我想在删除之前检查文本字段中是否包含文本。这是我通过扩展https://stackoverflow.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)
    }

0

我是使用iOS进行UI测试的新手,但我可以使用此简单的解决方法清除文本字段。使用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...
}

0

我使用了@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")

0

迅捷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)
    }

}

0

您可以doubleTap用来选择所有文本并输入新文本来替换:

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")
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.