UI Test deleting text in text field
SwiftUitextfieldXcode7UikeyboardXcode Ui-TestingSwift Problem Overview
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?
Swift Solutions
Solution 1 - Swift
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("[email protected]")
Solution 2 - Swift
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).
Now users can clear the field with a simple tap on the cross at the right of the text field:
Or in your UI test:
textField.buttons["Clear text"].tap()
Solution 3 - Swift
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)
}
}
Solution 4 - Swift
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.
Solution 5 - Swift
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")
Solution 6 - Swift
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")
Solution 7 - Swift
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
}
Solution 8 - Swift
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
Solution 9 - Swift
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.
Solution 10 - Swift
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)
}
}
Solution 11 - Swift
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
Solution 12 - Swift
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)
}
Solution 13 - Swift
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...
}
Solution 14 - Swift
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")
Solution 15 - Swift
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)
}
}