169

这是我的情况:

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

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

怎么了?这在正常情况下工作得很好textFields,但问题只出现在secureTextFields. 任何解决方法?

4

27 回答 27

341

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

于 2015-12-04T18:34:04.883 回答
34

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

defaults write com.apple.iphonesimulator ConnectHardwareKeyboard 0

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

于 2016-01-15T14:14:33.800 回答
20

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

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

于 2016-01-15T10:29:52.170 回答
12

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

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

基本上你只需粘贴:

UIPasteboard.generalPasteboard().string = "Their password"
let passwordSecureTextField = app.secureTextFields["password"]
passwordSecureTextField.pressForDuration(1.1)
app.menuItems["Paste"].tap()
于 2016-01-13T16:40:57.607 回答
10

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

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

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

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

作品

于 2017-11-29T20:37:44.577 回答
7

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

硬件/键盘(全部禁用)

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

禁用硬件

于 2016-05-05T13:36:52.927 回答
7

就我而言,这Hardware -> Keyboard -> Connect Hardware Keyboard->禁用对我不起作用。

但是当我跟随

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

它对我有用

于 2020-04-02T13:05:32.607 回答
5
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()
}
于 2015-11-05T05:08:27.560 回答
5

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

sleep(2)

就我而言,我每次都遇到这个错误,只有这个解决方案才能帮助我。

于 2016-08-21T02:54:59.317 回答
4

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

[app.textFields[@"theTitle"] tap];
[app.textFields[@"theTitle"] typeText:@"kk"];
于 2015-10-06T19:39:31.163 回答
4

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

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

在此处输入图像描述

于 2016-03-29T09:37:12.670 回答
3

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

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

于 2015-11-28T23:19:08.517 回答
2

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

动作前运行脚本

于 2020-06-09T19:24:44.913 回答
2

为我工作:

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)
    }
}
于 2020-12-08T23:31:21.280 回答
2

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

我不得不去Simulator > Device > Erase All Content and Settings...

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

于 2021-06-22T20:34:25.950 回答
1

有时文本字段没有作为文本字段实现,或者它们被包装到另一个 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()       

于 2018-06-22T18:48:44.960 回答
1

以下来自 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,然后点击

使用waitForExistence(timeout:)提供的方法XCUIElement

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:
于 2021-09-05T18:11:12.307 回答
0

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

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

XCTAssertFalse(passwordSecureTextField.exists);

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

于 2015-08-26T06:20:38.387 回答
0

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

于 2016-02-04T06:34:46.650 回答
0

我的问题和 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

于 2016-04-14T03:37:46.000 回答
0

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

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

于 2017-07-25T14:29:55.473 回答
0

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

于 2018-11-10T13:16:10.300 回答
0

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

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

奖励:你得到键盘声音点击:)

于 2020-05-20T09:15:04.663 回答
0

最后,我编写了一个脚本来编辑模拟器的 .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>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. 使用键盘交互运行任何 UI 测试。观察模拟器重新启动并且硬件键盘被禁用。并且测试的键盘交互工作正常。:)
于 2020-07-16T07:49:53.580 回答
0

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

问题是即使它应该返回的-[UIApplicationSupport _accessibilityHardwareKeyboardActive]私有方法com.apple.UIKit.axbundle有时也会返回(当文本输入视图是,并且光标在闪烁时,这实际上意味着硬件键盘处于活动状态)。NOYESfirstResponder

将此代码放在应用程序中的任何位置,以使 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
于 2020-12-17T05:37:39.883 回答
-1

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

 let enterPasswordSecureTextField = app.secureTextFields["Enter Password"]
    enterPasswordSecureTextField.tap()
    enterPasswordSecureTextField.typeText("12345678")
于 2017-09-25T16:33:50.997 回答
-1

为我解决此问题的是添加 1 秒睡眠:

let textField = app.textFields["identifier"]
textField.tap()
sleep(1)
textField.typeText(text)
于 2019-03-28T15:42:24.053 回答