【问题标题】:UI Test deleting text in text fieldUI 测试删除文本字段中的文本
【发布时间】:2015-12-25 15:06:36
【问题描述】:

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

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 设置没有任何影响。文本视图中是否还有其他内容可以隐藏/关闭键盘?
  • 也许在重启系统后我得到了Toggle software keyboard 重置。这不是模拟器的默认设置(我不知道这是否可以更改)。在您可以从测试(或测试方案)级别修复语言和软件键盘设置之前,这种或其他我的方法都不可靠。
  • 我自己的评论给了我一个想法,我在 Scheme > Options > Application Language 中找到了语言设置。键盘问题仍未解决。
  • 不错!这能解决故障吗?
  • 修复了仅本地化删除键名问题。

标签: swift uitextfield xcode7 uikeyboard xcode-ui-testing


【解决方案1】:

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

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

【讨论】:

  • 您可以通过以下方式以更实用的方式创建“删除字符串”:let deleteString = stringValue.characters.map { _ in "\u{8}" }.joinWithSeparator("")
  • 对于 swift4,使用 XCUIKeyboardKey.delete.rawValue
  • 对于 Swift 4,你可以使用 let deleteString = String(repeating: XCUIKeyboardKey.delete.rawValue, count: stringValue.characters.count)
  • 对于 Swift 4.2:let deleteString = String(repeating: XCUIKeyboardKey.delete.rawValue, count: stringValue.count)
  • 在 iPhone X 上这个脚本坏了,它没有选择所有的文本。
【解决方案2】:

由于您在问题的 cmets 中修复了本地化删除键名称问题,我假设您可以通过将删除键称为“删除”来访问删除键。

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

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 循环 - 每次点击之后都有延迟,所以它会使长字符串的测试变慢。
  • +1 是的!使用编写测试的过程来改进软件。也许它不太符合 OP 的要求,但它正是我遇到类似问题时需要的指针。
  • 这给了我“UI 测试失败 - 在 Xcode 7.2 中找不到“删除”键的匹配项”
  • 改变应用程序的行为以适应它的测试是一个禁忌恕我直言。 @bay.philips 的答案似乎更合法。
  • 我写这篇文章是假设您正在为实际用例编写测试,而不是仅仅在测试中做随机的事情。利用您可以获得的每一点反馈来提高可用性,从而为用户带来更好的应用程序。
【解决方案3】:

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

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

an apple bug,如果文本字段为空,则 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)
    }
}

【讨论】:

  • 哇,你能预测到 Swift 99 吗?
  • @Daniel haha​​ 只是对我们不得不为 swift 2、3、4 更改它感到恼火。
  • @SujitBaranwal ok 会检查发生了什么变化
  • 这段代码在我的模拟器和设备上运行良好,但是当我在 browserstack 上进行测试时,browserstack 是一个基于云的平台,用于在真实设备上进行远程测试,第一个保护语句只从那里返回。这意味着我的文本字段中的文本没有被识别为字符串。我确信文本在那里,因为测试用例也被记录下来。有什么帮助吗?
【解决方案4】:

我找到了以下解决方案:

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

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

【讨论】:

  • 你也可以使用myTextView.doubleTap()调出菜单,可能会快一点
  • 这并不总是有效,因为有时长按会突出显示文本字段内的单词,这会导致“全选”按钮不可见
【解决方案5】:

Xcode 9、Swift 4

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

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

【讨论】:

    【解决方案6】:

    您可以使用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")
    

    【讨论】:

    • 迄今为止最好的解决方案
    • 如果文本中的标点符号会阻止双击选择整个字符串,例如 0ac8c806-3f43-4d00-bc73-56073142950a 之类的 UUID——取决于插入点的位置(以及据我所知,这是不确定的),只会选择-s 之间的一段。
    【解决方案7】:

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

    而且我不喜欢依赖于语言环境的解决方案,例如上面带有显式“明文”查找的解决方案。

    所以,我做类型检查,然后尝试在文本字段中找到清除按钮 除非您有带有多个按钮的自定义文本字段,否则它会很好地工作

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

        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
        }
    

    【讨论】:

      【解决方案8】:

      我找到了一个使用 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
      

      【讨论】:

      • 看起来像四连击是每个Apple的神奇数字
      • 但是,我有时发现我需要事先做一个 .tap() 来设置焦点。这是我的代码 sn-p。
      • 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")
      • withNumberOfTaps: 2 就足够了。
      【解决方案9】:

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

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

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

      这个解决方案的好处是无论模拟器是否有虚拟键盘,它都能正常工作。

      【讨论】:

        【解决方案10】:

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

        【讨论】:

        【解决方案11】:

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

        【讨论】:

          【解决方案12】:

          我在让上述解决方案解决我遇到的类似问题时遇到了一些困难:光标会将自己置于文本之前,然后从那里向后工作。此外,我想在删除之前检查文本字段中是否有文本。这是我的解决方案,灵感来自 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)
              }
          

          【讨论】:

            【解决方案13】:

            我是使用 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...
            }
            

            【讨论】:

              【解决方案14】:

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

              【讨论】:

                【解决方案15】:

                斯威夫特 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)
                    }
                
                }
                

                【讨论】:

                  猜你喜欢
                  • 2017-09-04
                  • 2015-01-09
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2011-01-02
                  • 1970-01-01
                  • 2016-08-15
                  • 1970-01-01
                  相关资源
                  最近更新 更多