94

In my test I have a text field with a pre-existing text. I want to delete the content and type a new string.

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

When deleting string with hardware keyboard Recording generated for me nothing. After doing the same with software keyboard I got:

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

or

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

I was worried that my test is language-dependent so I have created something like this for my supported languages:

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

It looks nicer in code:

app.keys.deleteKey.pressForDuration(1.5)

but it is very fragile. After quitting from Simulator Toggle software keyboard was reset and I've got a failing test. My solution doesn't work well with CI testing. How can this be solved to be more universal?

Tomasz Bąk
  • 6,124
  • 3
  • 34
  • 48
  • I can't reproduce the failure you are experiencing. I added the same extension, switched my simulator's language to Polish, and verified that the "Usún" key is getting tapped. Restarting/resetting the simulator doesn't seem to have any affect on the `Toggle software keyboard` setting. Is there something else in the text view that could be hiding/dismissing the keyboard? – Joe Masilotti Sep 28 '15 at 12:21
  • Maybe after restarting system I've got `Toggle software keyboard` reset. It is not default for the simulator (and I don't know if this can be changed). This way or the other my method isn't reliable until you can fix language and software keyboard settings from the level of the test (or testing scheme). – Tomasz Bąk Sep 28 '15 at 12:58
  • My own comment gave me an idea and I have found language setting in Scheme > Options > Application Language. Keyboard issue still remains unresolved. – Tomasz Bąk Sep 28 '15 at 13:02
  • Nice! Does that fix the failure? – Joe Masilotti Sep 28 '15 at 13:03
  • Fixed only localized delete key name problem. – Tomasz Bąk Sep 28 '15 at 14:15

18 Answers18

173

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

Stephen Ostermiller
  • 23,933
  • 14
  • 88
  • 109
Bay Phillips
  • 2,015
  • 1
  • 13
  • 7
  • 20
    You can create "delete string" in more functional fashion with: `let deleteString = stringValue.characters.map { _ in "\u{8}" }.joinWithSeparator("")` – Tomasz Bąk Oct 06 '15 at 10:42
  • 1
    Oh awesome! I didn't know they had added `joinWithSeparator` in Swift 2. Thanks! – Bay Phillips Oct 06 '15 at 11:44
  • 1
    Consider incorporating functional approach into your answer for better readability. – Tomasz Bąk Oct 08 '15 at 12:56
  • For longer text I have encouraged problem with Connect Hardware Keyboard set on. Please consider adding small bash script before running tests, then this method works like a charm! http://stackoverflow.com/a/34812979/3158204 – Apan Feb 23 '16 at 15:20
  • 1
    Where does `\u{8}` come from? I replaced this with `deleteString += XCUIKeyboardKeyDelete` (which evaluates to `\u{7f}`). – Kyle Apr 13 '16 at 23:24
  • @Kyle it's the UTF8 decimal value for Backspace: http://www.w3schools.com/charsets/ref_utf_basic_latin.asp . But I didn't know about `XCUIKeyboardKeyDelete` which is neat! – Bay Phillips Apr 14 '16 at 16:13
  • Works great for text fields that have prediction / spelling check disabled. Otherwise you might have to deal with spelling suggestion alerts as characters get deleted. – Litome Apr 18 '17 at 14:22
  • 1
    joinWithSeparator was changed in Swift 3 `let deleteString = stringValue.characters.map { _ in XCUIKeyboardKeyDelete }.joined(separator: "")` – Trevis Thomas Jul 30 '17 at 21:01
  • I am unsure why but no matter what I try, this doesn't seem to delete the entire text in the text field even when I inspect it and see the same number of delete characters as there are characters to delete. It's seemingly random and deletes different amounts of characters each iteration. – Aaron Aug 03 '17 at 23:36
  • 8
    For swift4, use XCUIKeyboardKey.delete.rawValue – Daniel Sep 06 '17 at 21:53
  • 18
    For Swift 4, you can just use `let deleteString = String(repeating: XCUIKeyboardKey.delete.rawValue, count: stringValue.characters.count)` – Stasel Oct 09 '17 at 13:26
  • @Stasel, Daniel XCUIKeyboardKey does not have a delete member. In addition this will not work because the usage of characters is deprecated and xcode will show an error preventing you from running your app. – sceiler Nov 13 '17 at 13:54
  • joinedWithSeparator was renamed to joined(separator:"") – Bruno Muniz Jan 15 '18 at 10:56
  • https://stackoverflow.com/a/47427715/3040446 in Xcode 9 for empty fields, there is a bug where value == placeholderValue. – Ted May 02 '18 at 10:36
  • Late to the party, but if you want to go functional, the least introsive would be reduce... ```stringValue.reduce("") { current, _ in current + XCUIKeyboardKey.delete.rawValue }``` – Nuno Gonçalves May 06 '18 at 07:58
  • 16
    For Swift 4.2: `let deleteString = String(repeating: XCUIKeyboardKey.delete.rawValue, count: stringValue.count)` – Rody Davis Jul 13 '18 at 13:57
  • This doesn't seem to work if the software keyboard is not showing (which is the case after resetting a simulator) – SeanR Dec 02 '19 at 00:41
  • 7
    On iPhone X this script is broken, it's not selecting all the text. – Pedro Paulo Amorim Feb 04 '20 at 17:16
  • Thanks, you made my day (Swift 5) – David Ansermot Sep 02 '20 at 15:35
  • If you select your `TextField` based on its content then this will fail because the second `typeText()` will re-evaluate the query and fail to find it (since it was just cleared out). To solve that you can just combine these into a single action: `typeText("\(deleteString)\(text)")` – Jeroen Vannevel Dec 14 '21 at 12:29
  • 1
    Only solution that works for me for long text on iOS 16 & Xcode 14: https://stackoverflow.com/a/73847504/2650622 – Agost Biro Sep 25 '22 at 20:03
26

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)
    }
}
Ted
  • 22,696
  • 11
  • 95
  • 109
  • 4
    Wow, you can predict until Swift 99? – Daniel Mar 20 '18 at 13:31
  • @Daniel haha was just annoyed that we had to change it for swift 2, 3, 4. – Ted Mar 21 '18 at 08:38
  • @SujitBaranwal ok will check whats changed – Ted Jun 05 '18 at 12:32
  • This code works fine on my simulator and device but when I test the same on browserstack which is a cloud based platform for testing on real devices remotely, the first guard statement returns from there only. Meaning the text inside my textfield isn't getting recognized as string. I am sure the text is there because the test cases are recorded as well. Any help ? – nr5 May 02 '20 at 03:13
21

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()
Pedro Paulo Amorim
  • 1,838
  • 2
  • 27
  • 50
Martijn Hols
  • 1,470
  • 1
  • 10
  • 21
  • I like the idea with clear button, but I'm still looking a way to force the software keyboard in tests. As if it comes to while loop - every tap has delay after it, so it makes tests slow for long strings. – Tomasz Bąk Sep 29 '15 at 10:59
  • 1
    +1 Yes! Use the process of writing the tests to improve the software. Maybe it didn't quite meet the OP's requirements but it's exactly the pointer I needed when I hit a similar issue. – Andy Mortimer Oct 29 '15 at 21:32
  • 3
    This gives me "UI Testing Failure - No matches found for "Delete" Key" in Xcode 7.2 – Oded Feb 19 '16 at 03:18
  • 1
    Changing app behaviour in flavour to it's testing is a no-no IMHO. @bay.philips answer seems way more legit for this purpose. – hris.to Apr 08 '16 at 12:22
  • 2
    I wrote this under the assumption you're writing tests for actual use-cases instead of just to do random things in your tests. Using every bit of feedback you can get to improve usability leads to better apps for users. – Martijn Hols Apr 08 '16 at 12:33
  • I agree that you can find things in testing that help you improve the app, and if that's the case, do it. However, don't change the app just to get a test to work (because that change could break something else in your app). – dustinrwh Jun 01 '16 at 21:54
  • I like this approach thanks. Worth noting that currently (Xcode 10.1) the key you need is `delete` (no leading uppercase) and the keyboard must be visible on the device. – ChrisH Mar 06 '19 at 23:58
  • There's a potential issue with the first example: if the cursor lands not at the end of the text field then it will delete to the start of the field but there will still be characters after it – the while loop break condition will never be met, and you'll be on the train to Infinite Loop City. – adamjansch Apr 28 '20 at 14:37
15

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.

Sebastian Lenartowicz
  • 4,695
  • 4
  • 28
  • 39
oliverfrost21
  • 259
  • 1
  • 3
  • 9
  • 3
    you can also use `myTextView.doubleTap()` to bring up the menu, which might be a little faster – Hudson Jun 26 '17 at 15:54
  • 2
    This doesn't always work as sometimes long pressing can highlight a word inside the text field and this results in "Select all" button not being visible – Tadej Magajna Dec 11 '19 at 14:29
8

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")
zysoft
  • 2,268
  • 1
  • 16
  • 21
8

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")
Honghao Z
  • 1,419
  • 22
  • 29
  • 2
    Best solution so far – Toma Mar 03 '21 at 17:04
  • 1
    This won't work if the text has punctuation that prevents a double-tap from selecting the entire string, such as a UUID like `0ac8c806-3f43-4d00-bc73-56073142950a`--depending on where the insertion point is (and as far as I can tell, this is nondeterministic), only a segment between `-`s will be selected. – NRitH Aug 10 '21 at 14:26
4

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
    }
Oleg Shanyuk
  • 1,296
  • 2
  • 15
  • 26
4

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
Tadej Magajna
  • 2,765
  • 1
  • 25
  • 41
  • Looks like a quadruple-tap is the magic number per [Apple](https://support.apple.com/guide/iphone/type-and-edit-text-iph3c50f96e/ios) – Mark Gerrior May 01 '20 at 20:56
  • But, I sometimes find I need to do a .tap() beforehand to set focus. Here's my code snippet. – Mark Gerrior May 01 '20 at 21:17
  • 1
    `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 May 01 '20 at 21:20
  • 1
    Just `withNumberOfTaps: 2` is sufficient. – Boris Y. May 14 '20 at 10:07
  • I found a triple tap works best. Double tap only selects one word. I've found it to be a bit flakey but so far so good with triple tap. – Michael Vescovo Feb 11 '22 at 11:50
4

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

Agost Biro
  • 2,709
  • 1
  • 20
  • 33
2

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.

Dilip Agheda
  • 2,447
  • 1
  • 11
  • 15
2

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)
    }
}
Alfredo Luco G
  • 876
  • 7
  • 18
  • @eonist - disable the hardware keyboard and I think it'll work. See: https://stackoverflow.com/a/34095158/238166 – Inti Oct 25 '19 at 14:00
2

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
Skander Fathallah
  • 503
  • 1
  • 5
  • 10
0

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)
    }
Community
  • 1
  • 1
0

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

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")
Hamish
  • 1,685
  • 22
  • 37
0

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

}
Hatim
  • 1,516
  • 1
  • 18
  • 30
0

This is my solution based on Aghost Biro's answer. It adds a maxAttempts parameter so that the test will fail instead of infinitely loop if it hasn't cleared in a number of times attempting to. (Implying something went wrong or the clearing logic needs to be updated such as for a new iOS version). If there is a specific textfield that needs a lot of clearing for some reason. Then the maxAttempts parameter can be increased.

extension XCUIElement {
    func clearValue(maxAttempts: Int = 3) {
        for attempt in 0...maxAttempts {
            if attempt == maxAttempts {
                XCTFail("Couldn't clear value of element: \(self)")
                break // In case XCTestCase.continueAfterFailure == true
            }
            // If the text field is empty, then value returns the placeholderValue. So if they are equal then it can be considered cleared.
            if let value = value as? String, !value.isEmpty && value != placeholderValue {
                // Move the cursor to the end of the text field
                let lowerRightCorner = coordinate(withNormalizedOffset: CGVector(dx: 0.9, dy: 0.8))
                lowerRightCorner.tap()
                typeText(NSString().padding(toLength: value.count, withPad: XCUIKeyboardKey.delete.rawValue, startingAt: 0))
            } else {
                break
            }
        }
    }
}
Jonathan.
  • 53,997
  • 54
  • 186
  • 290
-1

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()
grabz
  • 69
  • 6