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

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

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 测试中不能很好地工作。如何才能解决这个问题,使其更具普遍性?

49272 次浏览

Since you fixed your localized delete key name problem in the comments of your questions, I'll assume you can access the delete key by just calling it "Delete".

The code below will allow you to reliably delete the contents of your field:

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

or Swift 4+:

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

But at the same time, your issue might indicate the need to solve this more elegantly to improve the usability of your app. On the text field you can also add a Clear button with which a user can immediately empty the text field;

Open the storyboard and select the text field, under the attributes inspector find "Clear button" and set it to the desired option (e.g. is always visible).

Clear button selection

Now users can clear the field with a simple tap on the cross at the right of the text field:

Clear button

Or in your UI test:

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

I wrote an extension method to do this for me and it's pretty fast:

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

This is then used pretty easily: app.textFields["Email"].clearAndEnterText("newemail@domain.example")

So, I didn't found any good solution yet :/

And I don't like locale dependent solutions, like above with explicit "Clear text" lookup.

So, I do type check, then trying to find clear button in the text field It works well unless you have custom text field with more that one button

My best now is (I have no custom text fields with more buttons):

    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
}

I found following solution:

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

When you tap and hold on the text field it opens menu where you can tap on "Select all" button. After that all you need is to remove that text with "delete" button on the keyboard or just enter new text. It will overwrite the old one.

I had some difficulty getting the above solutions to work for a similar problem I was having: The curser would place itself before text and then work backwards from there. Additionally, I wanted to check that the textfield had text in it before deleting. Here's my solution inspired by the extension https://stackoverflow.com/users/482361/bay-phillips wrote. I should note that the tapping the delete key can take a long time, and it can be substituted with .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)
}

I am new to UI testing with iOS but I was able to clear text fields with this simple workaround. Working with Xcode8 and plan on refactoring this soon:

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

this will work for textfield and textview

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

UPDATE XCODE 9

There is an apple bug where if the textfield is empty, value and placeholderValue are equal

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

Xcode 9, Swift 4

Tried the solutions above, but none worked due to some weird behavior on tap - it moved the cursor to either beginning of the text field, or at some random point in text. The approach I used is what @oliverfrost described here, but I've added some touches to work around the issues and combine it in a neat extension. I hope it can be useful for someone.

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

Usage:

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

Do this to delete the current string value in a text box without relying on virtual keyboard.

//read the value of your text box in this variable let textInTextField:String =

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

good thing about this solution is that it works regardless of simulator has virtual keyboard or not.

Now in swift 4.2 maybe you should try the following code:

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

I used what @oliverfrost described but it wasn't working on IPhone XR, I changed it a little for my own use, to this

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

and as @zysoft said, you can use it like:

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

For those who are still using 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

based on @Bay Phillips answer,

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


}

I found a solution that uses an iOS feature that selects the the entirety of the text fields by tapping it several times. We then delete the text field by typing any character or by pressing delete (if keyboard is enabled).

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

You can use doubleTap to select all text and type new text to replace:

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


typeText(text)
}
}

Usage:

textField.typeNewText("New Text")

I know in the original question this was marked as "fragile", but nowadays this seems to be the easiest solution:

myTextView.press(forDuration: 1.2)
app.menuItems["Select All"].tap()
app.menuItems["Cut"].tap()

This is the only solution that works for me reliably with text that may be partially hidden in the text view due to its length. Tested on iOS 16 and Xcode 14.

extension XCUIElement {
/// Removes any current text in the field before typing in the new value and submitting
/// Based on: https://stackoverflow.com/a/32894080
func clear() {
if self.value as? String == nil {
XCTFail("Tried to clear and enter text into a non string value")
return
}


// Repeatedly delete text as long as there is something in the text field.
// This is required to clear text that does not fit in to the textfield and is partially hidden initally.
// Important to check for placeholder value, otherwise it gets into an infinite loop.
while let stringValue = self.value as? String, !stringValue.isEmpty, stringValue != self.placeholderValue {
// Move the cursor to the end of the text field
let lowerRightCorner = self.coordinate(withNormalizedOffset: CGVector(dx: 0.9, dy: 0.9))
lowerRightCorner.tap()
let delete = String(repeating: XCUIKeyboardKey.delete.rawValue, count: stringValue.count)
self.typeText(delete)
}
}


func clearAndEnterText(text: String) {
self.clear()
// new line at end submits
self.typeText("\(text)\n")
}
}