【问题标题】:UI Testing Failure - Neither element nor any descendant has keyboard focus on secureTextFieldUI 测试失败 - 元素和任何后代都没有键盘焦点在 secureTextField
【发布时间】:2015-11-18 00:37:57
【问题描述】:

这是我的情况:

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

UI 测试失败 - 元素和任何后代都没有键盘焦点。元素:

怎么了?这对于普通的textFields 来说效果很好,但只有secureTextFields 才会出现问题。有什么解决方法吗?

【问题讨论】:

标签: ios xcode swift ios9 xcode-ui-testing


【解决方案1】:

您的第一行只是一个查询定义,这并不意味着passwordSecureTextField 确实存在。

您的第二行将动态执行查询并尝试(重新)将查询绑定到 UI 元素。您应该在其上放置一个断点并验证是否找到了一个且只有一个元素。或者只使用断言:

XCTAssertFalse(passwordSecureTextField.exists);

否则看起来没问题,tap 应该强制键盘可见,然后typeText 应该可以正常工作。错误日志应该会告诉你更多信息。

【讨论】:

  • passwordSecureTextField 存在。我解决了这个问题,但是清理内存,然后再次重写这些行。很奇怪,但它奏效了。
  • 很高兴听到它已修复!我昨天在 beta 6 中遇到了类似的问题 :)
  • beta 6?:-) 真的吗?我只有 5 个:)
  • 您一直在忙于编码 :) Beta 6 没有为我记录 tap,花了一些时间才弄清楚。良好的调试实践!
  • 也许你知道答案:stackoverflow.com/questions/32219015/…?
【解决方案2】:

这可能会有所帮助:我只是在错误之前添加了一个“点击”操作;就是这样:)

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

【讨论】:

  • 这绝对不适用于安全密码文本字段。
  • 这适用于我的安全密码文本字段。很好的解决方案。
【解决方案3】:
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()
}

【讨论】:

    【解决方案4】:

    [转发Bartłomiej Semańczyk的评论作为答案,因为它为我解决了问题]

    我需要在模拟器菜单栏中执行 Simulator > Reset Contents and Settings 以使其开始为我工作。

    【讨论】:

    • 重置模拟器对我也有帮助。有趣的是,这个问题只发生在模拟器中,而相同的测试(点击 texfield,然后输入字符)在设备上运行良好。
    【解决方案5】:

    这个问题给我带来了痛苦,但我设法找到了一个合适的解决方案。在模拟器中,确保I/O -> Keyboard -> Connect hardware keyboard 处于关闭状态。

    【讨论】:

    • 这已经解决了这个问题。但这是非常糟糕的解决方法,因为我们无法自动将其应用于 CI。
    • 如果您在 CI(Jenkins 等)上运行测试,您可以在运行测试之前在脚本中设置以下参数。 "默认写入 com.apple.iphonesimulator ConnectHardwareKeyboard 0"
    • 这并不能解决我的问题。我可以清楚地看到文本字段获得焦点,键盘向上,但测试框架永远无法使用 typeText: 方法输入文本
    • 在 Xcode 11 上为我工作,但取消选中 连接硬件键盘 可能还不够。还必须确保真正显示软件键盘(我实际上遇到过没有连接硬件键盘,也没有显示软件键盘的情况,即使 textField 有焦点,光标也在闪烁)。
    • 我也在 Xcode 11 上遇到了这个问题。本地修复,但如何在 CI 中设置?
    【解决方案6】:

    斯坦尼斯拉夫的想法是正确的。

    在团队环境中,您需要能够自动发挥作用的东西。我在我的博客上提出了一个修复 here

    基本上你只是粘贴:

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

    【讨论】:

    • 感谢分享您的解决方案。不幸的是,它现在不适合我。我有 Xcode 7.2 (7C68),你呢?查看我们为 CI 找到的其他解决方案:stackoverflow.com/questions/32184837/…
    【解决方案7】:

    我编写了一个小扩展 (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))
            }
        }
    }
    

    主要思想是在出现键盘之前不断点击元素(文本字段)。

    【讨论】:

    • 这对我现在不起作用。我有 Xcode 7.2 (7C68),你有什么版本?我们为 CI 找到的这个解决方案对我们有用:stackoverflow.com/a/34812979/598057
    • 再确认一次:此助手适用于安全密码文本字段,而不仅仅是常规文本字段?
    • 是的,它适用于两种类型的文本字段:常规和安全。
    • 这个答案的问题是它使用了硬编码的延迟。相反,请使用带有键盘元素的等待期望模型。
    【解决方案8】:

    最近我们发现了一种技巧,可以使已接受答案的解决方案持久化。要禁用模拟器设置:I/O -> Keyboard -> Connect hardware keyboard 从命令行应该写:

    defaults write com.apple.iphonesimulator ConnectHardwareKeyboard 0
    

    它不会影响正在运行的模拟器 - 您需要重新启动模拟器或启动一个新的模拟器才能使该设置生效。

    【讨论】:

    • 这并没有改变什么。
    • @netshark1000 可能会随着 Xcode 的较新版本而改变,我会检查一下。
    • 非常有帮助,我需要始终启用键盘的相反功能,但是非常感谢。我最终选择了 defaults write com.apple.iphonesimulator ConnectHardwareKeyboard -bool true ,但它的概念相同。
    • 我在构建阶段创建了一个脚本来执行此操作。但我需要杀死所有模拟器,所以我这样做了:killall "Simulator"; defaults write com.apple.iphonesimulator ConnectHardwareKeyboard 0;
    • 这似乎不再起作用了,是不是钥匙变了?? @AlexDenisov
    【解决方案9】:

    别搞砸了,发现问题的原因是您记录了您的应用程序将连接硬件键盘的测试时间,而您的自动测试时间模拟器只需要软件键盘。那么如何解决这个问题。只需在录制时间上使用软件键盘。你可以看到魔法。

    【讨论】:

      【解决方案10】:

      随心所欲的记录盒,键盘或不连接键盘。 但在进行测试之前,请执行以下操作。

      在进行测试时,应取消选中以下选项(连接硬件键盘)。

      【讨论】:

        【解决方案11】:

        我的问题和 Ted 一样。实际上,如果在登录字段和硬件 KB 开启后点击密码字段,软件键盘将在第二次字段点击时自行关闭,并且它不是特定于 UI 测试。

        在使用 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

        使用上面的代码创建一个脚本文件并将其添加到必要的目标(可能仅 UI 测试目标,您可能希望将类似的脚本添加到您的开发目标以在开发期间重新启用硬件键盘)。 您应该在构建阶段添加Run Script 阶段并像这样使用它: osascript Path/To/Script/script_name.applescript

        【讨论】:

          【解决方案12】:

          它发生在我身上很多次。 您必须在模拟器中禁用键盘硬件和与 OSX 相同的布局

          硬件/键盘(全部禁用)

          之后键盘软件不会关闭,您的测试可以输入文本

          【讨论】:

            【解决方案13】:

            在启动应用程序和在文本字段中输入数据之间使用睡眠,如下所示:

            sleep(2)
            

            在我的情况下,我每次都遇到这个错误,只有这个解决方案才能帮助我。

            【讨论】:

            • 问题是,如果您有大量的 UI 测试执行类似于初始登录的操作,这将为您的 CI 测试运行增加巨大的开销。
            【解决方案14】:

            在为包含 UIControl 子视图的自定义视图(UIStackView 子类)设置 accessibilityIdentifier 值时,我们遇到了同样的错误。在这种情况下,XCTest 无法获得后代元素的键盘焦点。

            我们的解决方案只是从父视图中删除accessibilityIdentifier,并通过专用属性为子视图设置accessibilityIdentifier

            【讨论】:

              【解决方案15】:

              Securetextfields 也有同样的问题。我的模拟器中的连接硬件选项是,但仍然遇到问题。最后,这对我有用(Swift 3):

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

              【讨论】:

              • 你的代码和问题中问的代码有什么区别。
              【解决方案16】:

              此错误的另一个原因是您尝试在其中输入设置为可访问性元素的文本的文本字段的父视图 (view.isAccessibilityElement = true)。在这种情况下,XCTest 无法获取子视图的句柄来输入文本并返回错误。

              UI 测试失败 - 元素和任何后代都没有键盘 重点。

              并不是没有元素有焦点(你经常可以在 UITextField 中看到键盘向上和闪烁的光标),只是它可以到达的元素没有焦点。尝试在 UISearchBar 中输入文本时遇到了这个问题。搜索栏本身不是文本字段,当将其设置为可访问性元素时,对底层 UITextField 的访问被阻止。为解决此问题,searchBar.accessibilityIdentifier = "My Identifier" 设置在 UISearchBar 上,但 isAccessibilityElement 未设置为 true。在此之后,测试表单的代码:

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

              作品

              【讨论】:

                【解决方案17】:

                有时文本字段没有实现为文本字段,或者它们被包装到另一个 UI 元素中并且不容易访问。 这是一个解决方法:

                //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()       

                【讨论】:

                  【解决方案18】:

                  另一个答案,但对我们来说,问题是视图太接近手势识别器在其上的另一个视图。我们发现我们需要距离至少 20 像素的视图(在我们下面的例子中)。从字面上看,15 个不起作用,20 个或更多起作用。我承认这很奇怪,但是我们有一些 UITextViews 正在工作,而一些 UITextViews 没有工作,而且它们都在同一个父级和相同的其他位置(当然还有变量名)下。键盘打开或关闭或任何没有区别。可访问性显示了这些字段。我们重新启动了计算机。我们做了干净的构建。新鲜来源结帐。

                  【讨论】:

                    【解决方案19】:

                    为我解决这个问题的方法是增加 1 秒的睡眠时间:

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

                    【讨论】:

                      【解决方案20】:

                      在我的情况下,Hardware -> Keyboard -> Connect Hardware Keyboard -> 禁用 对我不起作用。

                      但是当我关注

                      1) Hardware -> Keyboard -> Connect Hardware Keyboard -> 启用并运行应用程序
                      2) Hardware -> Keyboard -> Connect Hardware Keyboard -> 禁用

                      它对我有用

                      【讨论】:

                      • 为我工作,伙计,我讨厌 XCode 的这方面!
                      【解决方案21】:

                      无需在 I/O 中打开/关闭键盘。 不要将 .typeText 用于secureTextField,只需使用

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

                      奖励:你会听到键盘声音:)

                      【讨论】:

                        【解决方案22】:

                        我遇到了这个问题,并且能够通过采用@AlexDenisov 发布的解决方案并将其添加到我的 runtest 预操作中来解决它强>。

                        【讨论】:

                        • 没想到,点个赞吧!
                        【解决方案23】:

                        最后,我编写了一个脚本来编辑模拟器的 .plist 文件,并将所选模拟器的 ConnectHardwareKeyboard 属性设置为 false。您没听错,它会更改“DevicePreferences”字典中专门选择的模拟器的属性,而不是编辑全局属性。

                        首先,创建一个名为 disable-hardware-keyboard.sh 的 shell 脚本,其内容如下。您可以将其放在“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 方案(或 UI 测试特定方案,如果有的话)
                        2. 转到:测试 > 预操作
                        3. 点击“+”符号>“新建运行脚本动作”添加新脚本。
                        4. 重要提示:在“Provide build settings from”下拉菜单中选择您的主应用目标,而不是 UI 测试目标。
                        5. 现在在下面的文本区域中添加以下脚本。

                        Test 中的脚本>预操作:

                        #!/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. 使用键盘交互运行任何 UI 测试。 观察模拟器重新启动并且硬件键盘被禁用。并且测试的键盘交互工作正常。 :)

                        【讨论】:

                          【解决方案24】:

                          为我工作:

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

                          【讨论】:

                          • 会破坏不需要将应用程序置于后台的测试。但除此之外,它就像一个魅力。
                          【解决方案25】:

                          长话短说:iOS 模拟器中的硬件键盘输入似乎已经中断了很长时间。

                          问题是来自com.apple.UIKit.axbundle-[UIApplicationSupport _accessibilityHardwareKeyboardActive] 私有方法有时会返回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
                          

                          【讨论】:

                            【解决方案26】:

                            当模拟器 UI 处于前台时(当您可以在模拟器窗口中看到更改时)运行 UI 测试时,上述答案为我解决了这个问题。但是,在后台运行它们时它不起作用(例如,fastlane 就是这样运行它们的)。

                            我必须去Simulator &gt; Device &gt; Erase All Content and Settings...

                            为了清楚起见,我在模拟器设置中手动禁用-启用-禁用了硬件键盘,设置了defaults write com.apple.iphonesimulator ConnectHardwareKeyboard 0,然后删除了内容和设置。

                            【讨论】:

                              【解决方案27】:

                              以下内容来自一个 Swift 5.4 的 SwiftUI 应用场景,该场景会导致 UI 测试失败。

                              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) 仍然会失败。

                              解决方案:等待 TextField 到达,然后点击

                              有时视图转换需要明确等待 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:
                              

                              解决方案:waitForExistence,然后点击

                              使用@987654322@提供的@987654321@方法。

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

                              【讨论】:

                                猜你喜欢
                                • 2015-12-12
                                • 2016-04-20
                                • 2017-12-17
                                • 2017-01-19
                                • 2019-01-15
                                • 1970-01-01
                                • 1970-01-01
                                • 2020-03-22
                                • 2020-09-13
                                相关资源
                                最近更新 更多