Ошибка тестирования пользовательского интерфейса - ни элемент, ни какой-либо его потомок не фокусируются на клавиатуре secureTextField

Это мой случай:

let passwordSecureTextField = app.secureTextFields["password"]
passwordSecureTextField.tap()
passwordSecureTextField.typeText("wrong_password") //here is an error

UI Testing Failure - Ни элемент, ни потомок не имеют фокусировки на клавиатуре. Элемент:

Что случилось? Это нормально работает нормально textFields, но проблемы возникают только с secureTextFields, Есть обходные пути?

29 ответов

Решение

Эта проблема причинила мне боль, но мне удалось найти правильное решение. В симуляторе убедитесь, что "Оборудование -> Клавиатура -> Подключить аппаратную клавиатуру" выключено.

Недавно мы нашли хак, чтобы сделать решение из принятого ответа постоянным. Чтобы отключить настройку симулятора: "Оборудование -> Клавиатура -> Подключить аппаратную клавиатуру" из командной строки, необходимо написать:

defaults write com.apple.iphonesimulator ConnectHardwareKeyboard 0

Это не повлияет на работающий симулятор - вам нужно перезапустить симулятор или запустить новый, чтобы настройки вступили в силу.

Я написал небольшое расширение (Swift), которое идеально подходит для меня. Вот код:

extension XCTestCase {

    func tapElementAndWaitForKeyboardToAppear(element: XCUIElement) {
        let keyboard = XCUIApplication().keyboards.element
        while (true) {
            element.tap()
            if keyboard.exists {
                break;
            }
            NSRunLoop.currentRunLoop().runUntilDate(NSDate(timeIntervalSinceNow: 0.5))
        }
    }
}

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

У Станислава правильная идея.

В командной среде вам нужно что-то, что будет работать автоматически. Я нашел исправление в моем блоге.

В основном вы просто вставляете:

UIPasteboard.generalPasteboard().string = "Their password"
let passwordSecureTextField = app.secureTextFields["password"]
passwordSecureTextField.pressForDuration(1.1)
app.menuItems["Paste"].tap()

Другая причина этой ошибки - если есть родительское представление текстового поля, в которое вы пытаетесь ввести текст, который установлен как элемент доступности (view.isAccessibilityElement = true). В этом случае XCTest не может получить дескриптор подпредставления для ввода текста и возвращает ошибку.

UI Testing Failure - Ни элемент, ни потомок не имеют фокусировки на клавиатуре.

Дело не в том, что ни один элемент не имеет фокуса (как вы часто можете видеть клавиатуру вверх и мигающий курсор в UITextField), просто ни один элемент не может достичь фокуса. Я столкнулся с этим при попытке ввести текст в UISearchBar. Сама панель поиска не является текстовым полем, при установке его в качестве элемента доступности доступ к базовому UITextField был заблокирован. Чтобы решить это, searchBar.accessibilityIdentifier = "My Identifier" был установлен на UISearchBar Тем не менее isAccessibilityElement не был установлен в true, После этого протестируйте код вида:

app.otherElements["My Identifier"].tap()
app.otherElements["My Identifier"].typeText("sample text")

Работает

В моем случае это Hardware -> Keyboard -> Connect Hardware Keyboard-> Отключение у меня не сработало.

Но когда я последовал

1) Hardware -> Keyboard -> Connect Hardware Keyboard-> Включено и запустите приложение
2)Hardware -> Keyboard -> Connect Hardware Keyboard-> Отключить.

Это сработало для меня

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

Оборудование / Клавиатура (отключить все)

После этого программное обеспечение клавиатуры не исчезнет, ​​и ваши тесты смогут набирать текст

Отключить аппаратное обеспечение

Это может помочь: я просто добавляю "касание" перед ошибкой; это все:)

[app.textFields[@"theTitle"] tap];
[app.textFields[@"theTitle"] typeText:@"kk"];

Используйте режим сна между запуском приложения и вводом данных в текстовые поля, например так:

sleep(2)

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

func pasteTextFieldText(app:XCUIApplication, element:XCUIElement, value:String, clearText:Bool) {
    // Get the password into the pasteboard buffer
    UIPasteboard.generalPasteboard().string = value

    // Bring up the popup menu on the password field
    element.tap()

    if clearText {
        element.buttons["Clear text"].tap()
    }

    element.doubleTap()

    // Tap the Paste button to input the password
    app.menuItems["Paste"].tap()
}

Запишите случай, как хотите, с клавиатурой или без клавиатуры. Но перед игрой сделайте следующее.

Этот следующий параметр (подключить аппаратную клавиатуру) должен быть отключен во время воспроизведения теста.

введите описание изображения здесь

Работает для меня:

      extension XCUIElement {
    func typeTextAlt(_ text: String) {
        // Solution for `Neither element nor any descendant has keyboard focus.`
        if !(self.value(forKey: "hasKeyboardFocus") as? Bool ?? false) {
            XCUIDevice.shared.press(XCUIDevice.Button.home)
            XCUIApplication().activate()
        }
        self.typeText(text)
    }
}

[Повторно Bartłomiej Semańczyk комментарий Бартломея Bartłomiej Semańczyk, потому что он решил проблему для меня]

Мне нужно было выполнить Simulator > Reset Contents and Settings в строке меню симулятора, чтобы это начало работать для меня.

Я столкнулся с этой проблемой и смог исправить ее в своем сценарии, взяв решение, опубликованное @AlexDenisov, и добавив его в свои предварительные действия для запуска и тестирования.

ht tps://stackru.com/images/39ee0150a669bc8db86ce894caafd51ca5886fe0.png

Приведенные выше ответы решили это для меня при запуске тестов пользовательского интерфейса, когда пользовательский интерфейс симулятора находится на переднем плане (когда вы могли видеть изменения в окне симулятора). Однако это не сработало, когда они запускались в фоновом режиме (например, как их запускал fastlane).

Я должен был пойти в Simulator > Device > Erase All Content and Settings...

Для ясности, я вручную отключил-включено-отключил аппаратную клавиатуру в настройках симулятора, установил , а затем удалили содержимое и настройки.

Следующее - из сценария приложения Swift 5.4, SwiftUI, которое не прошло бы тестирование пользовательского интерфейса.

      app.cells.firstMatch.tap()
// transition from list view to detail view for item
app.textFields
    .matching(identifier: "balance_textedit")
    .element.tap()
app.textFields
    .matching(identifier: "balance_textedit")
    .element.typeText("7620") // :FAIL:

Тест: не удалось синтезировать событие: ни у элемента, ни у потомка нет фокуса клавиатуры.

Решение: нажмите еще раз

Второй tap() было обнаружено, что прошло.

      app.cells.firstMatch.tap()
// transition from list view to detail view for item
app.textFields
    .matching(identifier: "balance_textedit")
    .element.tap()
app.textFields
    .matching(identifier: "balance_textedit")
    .element.tap()
app.textFields
    .matching(identifier: "balance_textedit")
    .element.typeText("7620") // :PASS:

Тем не мение, .doubleTap() а также tap(withNumberOfTaps: 2, numberOfTouches: 1) все равно не получится.

Решение: дождитесь текстового поля, затем коснитесь

Иногда для перехода к просмотру необходимо явно дождаться прибытия TextEdit. Приведенный ниже подход ожидает прибытия целевого поля TextEdit.

Расширение XCTestCase.swift:

      extension XCTestCase {
    func awaitArrival(element: XCUIElement, timeout: TimeInterval) {
        let predicate = NSPredicate(format: "exists == 1")
        expectation(for: predicate, evaluatedWith: element) 
        waitForExpectations(timeout: timeout)
    }

    func awaitDeparture(element: XCUIElement, timeout: TimeInterval) {
        let predicate = NSPredicate(format: "exists == 0")
        expectation(for: predicate, evaluatedWith: element) 
        waitForExpectations(timeout: timeout)
    }
}

Пример использования:

      app.cells.firstMatch.tap()
// transition from list view to detail view for item
awaitArrival(
    element: app.textFields.matching(identifier: "balance_textedit").element, 
    timeout: 5)
app.textFields
    .matching(identifier: "balance_textedit")
    .element.tap()
app.textFields
    .matching(identifier: "balance_textedit")
    .element.typeText("7620") // :PASS:

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

//XCUIApplication().scrollViews.otherElements.staticTexts["Email"] the locator for the element
RegistrationScreenStep1of2.emailTextField.tap()
let keys = app.keys
 keys["p"].tap() //type the keys that you need
 
 //If you stored your data somewhere and need to access that string //you can cats your string to an array and then pass the index //number to key[]
 
 let newUserEmail = Array(newPatient.email())
 let password = Array(newPatient.password)
 
 //When you cast your string to an array the elements of the array //are Character so you would need to cast them into string otherwise //Xcode will compain. 
 
 let keys = app.keys
     keys[String(newUserEmail[0])].tap()
     keys[String(newUserEmail[1])].tap()
     keys[String(newUserEmail[2])].tap()
     keys[String(newUserEmail[3])].tap()
     keys[String(newUserEmail[4])].tap()
     keys[String(newUserEmail[5])].tap()       

          emailTextField.tap() // add this line before typeText
    
    emailTextField.typeText("your text")

Другой ответ, но для нас проблема заключалась в том, что представление было слишком близко к другому представлению, что распознаватель жестов на нем. Мы обнаружили, что нам нужно, чтобы изображение было на расстоянии не менее 20 пикселей (в нашем случае ниже). Буквально 15 не сработало, а 20 и более сработало. Это странно, я признаю, но у нас было несколько UITextViews, которые работали, и некоторые, которые не работали, и все находились под одним и тем же родителем и имели идентичное другое позиционирование (и, конечно, имена переменных). Клавиатура включена или выключена или что-то не имеет значения. Доступность показала поля. Мы перезапустили наши компьютеры. Мы сделали чистые сборки. Свежий источник проверки.

Нет необходимости включать / выключать клавиатуру при вводе / выводе. Не используйте.typeText для secureTextField, просто используйте

app.keys["p"].tap()
app.keys["a"].tap()
app.keys["s"].tap()
app.keys["s"].tap()

Бонус: слышен щелчок клавиатуры:)

Проблема для меня была такой же, как и для Теда. На самом деле, если поле пароля вводится после того, как поле входа в систему и аппаратное КБ включены, программная клавиатура будет отключаться при втором нажатии поля, и это не относится к тестам пользовательского интерфейса.

После некоторого времени возни с AppleScript вот что я придумал (улучшения приветствуются):

tell application "Simulator" activate tell application "System Events" try tell process "Simulator" tell menu bar 1 tell menu bar item "Hardware" tell menu "Hardware" tell menu item "Keyboard" tell menu "Keyboard" set menuItem to menu item "Connect Hardware Keyboard" tell menu item "Connect Hardware Keyboard" set checkboxStatus to value of attribute "AXMenuItemMarkChar" of menuItem if checkboxStatus is equal to "✓" then click end if end tell end tell end tell end tell end tell end tell end tell on error tell application "System Preferences" activate set securityPane to pane id "com.apple.preference.security" tell securityPane to reveal anchor "Privacy_Accessibility" display dialog "Xcode needs Universal access to disable hardware keyboard during tests(otherwise tests may fail because of focus issues)" end tell end try end tell end tell

Создайте файл сценария с приведенным выше кодом и добавьте его к необходимым целям (возможно, цель - только для тестов пользовательского интерфейса, вы можете добавить аналогичный сценарий к целям разработки, чтобы повторно включить HW-клавиатуру во время разработки). Вы должны добавить Run Script этап на этапе сборки и использовать его так:osascript Path/To/Script/script_name.applescript

Мы столкнулись с той же ошибкой при установке accessibilityIdentifier значение для пользовательского представления (UIStackView подкласс), содержащий UIControl подвиды. В этом случае XCTest не удалось получить фокус клавиатуры для дочерних элементов.

Наше решение было просто удалить accessibilityIdentifier от нашего родительского взгляда и установить accessibilityIdentifier для подпредставлений через выделенные свойства.

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

Ваша первая строка - это просто определение запроса, что не означает, что passwordSecureTextField на самом деле существует.

Ваша вторая строка будет динамически выполнять запрос и попытаться (повторно) связать запрос с элементом пользовательского интерфейса. Вы должны установить точку останова и убедиться, что найден один и только один элемент. Или просто используйте assert:

XCTAssertFalse(passwordSecureTextField.exists);

В противном случае это выглядит хорошо, tap должен заставить клавиатуру быть видимой typeText должен просто работать. Журнал ошибок должен рассказать вам больше информации.

Короче говоря: ввод с аппаратной клавиатуры в iOS Simulator уже давно не работает.

Дело в том, что -[UIApplicationSupport _accessibilityHardwareKeyboardActive] частный метод от com.apple.UIKit.axbundle иногда возвращается NO хотя он должен вернуться YES (когда вид ввода текста firstResponder, а курсор мигает, что буквально означает, что аппаратная клавиатура активна).

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

          
#if TARGET_OS_SIMULATOR
__attribute__((constructor))
static void Fix_UIApplicationAccessibility__accessibilityHardwareKeyboardActive(void) {
    {
        static id observer = nil;

        observer = [[NSNotificationCenter defaultCenter] addObserverForName:NSBundleDidLoadNotification
                                                          object:nil
                                                           queue:nil
                                                      usingBlock:^(NSNotification * _Nonnull note) {
            NSBundle *bundle = note.object;
            if ([bundle.bundleIdentifier isEqualToString:@"com.apple.UIKit.axbundle"]) {
                Class cls = objc_lookUpClass("UIApplicationAccessibility");
                SEL sel = sel_getUid("_accessibilityHardwareKeyboardActive");
                Method m = class_getInstanceMethod(cls, sel);
                method_setImplementation(m, imp_implementationWithBlock(^BOOL(UIApplication *app) {
                    return ![[app valueForKey:@"_accessibilitySoftwareKeyboardActive"] boolValue];
                }));
            }
        }];
    }
}
#endif

Наконец, я написал сценарий, который редактирует файл.plist симулятора и устанавливает ConnectHardwareKeyboardзначение false для выбранного симулятора. Вы правильно поняли, он изменяет свойство для специально выбранного симулятора в словаре DevicePreferences, а не редактирует глобальное свойство.

Сначала создайте сценарий оболочки с именем disable-hardware-keyboard.sh со следующим содержимым. Вы можете разместить его в "YourProject/xyzUITests/Scripts/":

echo "Script: Set ConnectHardwareKeyboard to false for given Simulator UDID"

if [[ $1 != *-*-*-*-* ]]; then
    echo "Pass device udid as first argument."
    exit 1
else
    DEVICE_ID=$1
fi

DEVICE_PREFERENCES_VALUE='<dict><key>ConnectHardwareKeyboard</key><false/></dict>'
killall Simulator # kill restart the simulator to make the plist changes picked up
defaults write com.apple.iphonesimulator DevicePreferences -dict-add $DEVICE_ID $DEVICE_PREFERENCES_VALUE
open -a Simulator # IMPORTANT

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

  1. Отредактируйте схему Xcode (или определенную схему тестов пользовательского интерфейса, если она у вас есть)
  2. Перейдите в: Тест> Предварительные действия
  3. Добавьте новый сценарий, нажав символ "+"> "Новое действие сценария выполнения".
  4. Важно: в раскрывающемся списке "Предоставить настройки сборки из" выберите цель основного приложения, а не цель тестов пользовательского интерфейса.
  5. Теперь добавьте следующий скрипт в текстовую область ниже.

Скрипт внутри Test>Pre-actions:

#!/bin/sh
# $PROJECT_DIR is path to your source project. This is provided when we select "Provide build settings from" to "AppTarget"
# $TARGET_DEVICE_IDENTIFIER is the UDID of the selected simulator
sh $PROJECT_DIR/xyzUITests/Scripts/disable-hardware-keyboard.sh $TARGET_DEVICE_IDENTIFIER

# In order to see output of above script, append following with it:
#  | tee ~/Desktop/ui-test-scheme-prescript.txt

Пора это проверить:

  1. Симулятор запуска
  2. Включите для него аппаратную клавиатуру
  3. Запустите любой тест пользовательского интерфейса с помощью клавиатуры. Наблюдайте, как симулятор перезапускается и аппаратная клавиатура отключена. И взаимодействие теста с клавиатурой работает нормально.:)

Что исправило эту проблему для меня, так это добавив 1 секунду сна:

let textField = app.textFields["identifier"]
textField.tap()
sleep(1)
textField.typeText(text)

Была такая же проблема с Securetextfields. Вариант подключения оборудования в моем симуляторе был, но все же столкнулся с проблемой. Наконец, это сработало для меня (Swift 3):

 let enterPasswordSecureTextField = app.secureTextFields["Enter Password"]
    enterPasswordSecureTextField.tap()
    enterPasswordSecureTextField.typeText("12345678")

Убедитесь, что вы отключили «Подключить аппаратную клавиатуру» из ввода-вывода в симуляторе для XCode 12.

Другие вопросы по тегам