UI Test удаление текста в текстовом поле

В моем тесте у меня есть текстовое поле с уже существующим текстом. Я хочу удалить содержимое и ввести новую строку.

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. Как это можно решить, чтобы стать более универсальным?

18 ответов

Решение

Я написал метод расширения, чтобы сделать это для меня, и это довольно быстро:

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 = stringValue.characters.map { _ in XCUIKeyboardKeyDelete }.joined(separator: "")

        self.typeText(deleteString)
        self.typeText(text)
    }
}

Это тогда используется довольно легко: app.textFields["Email"].clearAndEnterText("newemail@domain.com")

Это будет работать для 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 4 до SWIFT 99

extension XCUIElement {
    func clearText() {
        guard let stringValue = self.value as? String else {
            return
        }

        var deleteString = String()
        for _ in stringValue {
            deleteString += XCUIKeyboardKey.delete.rawValue
        }
        self.typeText(deleteString)
    }
}

ОБНОВЛЕНИЕ XCODE 9

Существует ошибка Apple, когда текстовое поле пустое, а 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
        }
        self.typeText(deleteString)
    }
}

Поскольку вы исправили проблему локализованного имени ключа удаления в комментариях к своим вопросам, я предполагаю, что вы можете получить доступ к ключу удаления, просто назвав его "Удалить".

Код ниже позволит вам надежно удалить содержимое вашего поля:

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

Но в то же время ваша проблема может указывать на необходимость более элегантного решения этой проблемы для повышения удобства использования вашего приложения. В текстовое поле вы также можете добавить Clear button с помощью которого пользователь может сразу очистить текстовое поле;

Откройте раскадровку и выберите текстовое поле, в инспекторе атрибутов найдите "Кнопка очистки" и установите для него нужный параметр (например, всегда видимый).

Очистить выбор кнопки

Теперь пользователи могут очистить поле простым нажатием на крестик справа от текстового поля:

Кнопка очистки

Или в вашем тесте пользовательского интерфейса:

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

Я нашел следующее решение:

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

Когда вы нажимаете и удерживаете текстовое поле, открывается меню, в котором вы можете нажать кнопку "Выбрать все". После этого все, что вам нужно, это удалить этот текст с помощью кнопки "удалить" на клавиатуре или просто ввести новый текст. Это перезапишет старый.

Вы можете использовать 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")

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

Я нашел решение, в котором используется функция iOS, которая выбирает все текстовые поля, нажимая на них несколько раз. Затем мы удаляем текстовое поле, набрав любой символ или нажав клавишу "Удалить" (если включена клавиатура).

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

Это единственное решение, которое надежно работает для меня с текстом, который может быть частично скрыт в текстовом представлении из-за его длины. Протестировано на iOS 16 и 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")
    }
}

Итак, я не нашел ни одного хорошего решения:/

И мне не нравятся решения, зависящие от локали, как выше с явным поиском "Чистый текст".

Итак, я делаю проверку типа, затем пытаюсь найти кнопку очистки в текстовом поле. Это работает хорошо, если у вас нет настраиваемого текстового поля с более чем одной кнопкой

Мой лучший сейчас (у меня нет пользовательских текстовых полей с большим количеством кнопок):

    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
    }

Сделайте это, чтобы удалить текущее строковое значение в текстовом поле, не полагаясь на виртуальную клавиатуру.

// читаем значение вашего текстового поля в этой переменной let textInTextField:String =

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

Хорошо, что это решение работает независимо от того, имеет симулятор виртуальную клавиатуру или нет.

Сейчас в 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)
    }
}

Для тех, кто все еще использует 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

Я использовал то, что описал @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")

Это мое решение, основанное на ответе Агоста Биро . Он добавляет параметр maxAttempts, чтобы тест завершился неудачно, а не зациклился на бесконечном цикле, если он не был очищен несколько раз при попытке. (Подразумевается, что что-то пошло не так или необходимо обновить логику очистки, например, для новой версии iOS). Если есть определенное текстовое поле, которое по какой-то причине требует значительной очистки. Затем параметр maxAttempts можно увеличить.

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

У меня были некоторые трудности с тем, чтобы описанные выше решения работали для решения аналогичной проблемы, с которой я столкнулся: курсор помещался перед текстом, а затем работал в обратном направлении. Кроме того, я хотел проверить, что текстовое поле содержало текст перед удалением. Вот мое решение, вдохновленное расширением 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)
    }

Я новичок в тестировании пользовательского интерфейса с iOS, но мне удалось очистить текстовые поля с помощью этого простого обходного пути. Работаем с 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...
}

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

}

Я знаю, что в исходном вопросе это было помечено как «хрупкое», но в настоящее время это кажется самым простым решением:

      myTextView.press(forDuration: 1.2)
app.menuItems["Select All"].tap()
app.menuItems["Cut"].tap()
Другие вопросы по тегам