UI 测试失败-元素或任何子代都没有将键盘焦点放在 secureTextField 上

这是我的案子:

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

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

什么是错误的? 这是工作良好的正常 textFields,但问题只出现与 secureTextFields。有什么解决办法?

43017 次浏览

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

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

XCTAssertFalse(passwordSecureTextField.exists);

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

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

[app.textFields[@"theTitle"] tap];
[app.textFields[@"theTitle"] typeText:@"kk"];
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()
}

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

我需要做模拟器 > 重置内容和设置在模拟器菜单栏,使这个开始为我工作。

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

Stanislav 的想法是对的。

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

基本上你只需要粘贴:

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

我已经写了一个小的扩展(斯威夫特) ,这对我来说是完美的。 密码如下:

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

其主要思想是在显示键盘之前不断点击一个元素(文本字段)。

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

defaults write com.apple.iphonesimulator ConnectHardwareKeyboard 0

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

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

记录案件,无论你想,键盘或没有键盘附加。 但是在玩游戏之前做以下测试。

下面的选项(连接硬件键盘)在播放测试时应取消选中。

enter image description here

我的问题和泰德一样。实际上,如果在登录字段之后点击密码字段,硬件知识库打开,软件键盘在第二次点击字段时会自动关闭,而且它不是特定于 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 测试目标,您可能希望在开发目标中添加类似的脚本,以便在开发期间重新启用 HW 键盘)。 您应该在构建阶段中添加 Run Script阶段,并像下面这样使用它: osascript Path/To/Script/script_name.applescript

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

硬件/键盘(全部禁用)

之后,键盘软件将不会解散,您的测试可以输入文本

Disable Hardware

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

sleep(2)

在我的情况下,我每次都会得到这个错误,只有这个解决方案帮助我解决了这个问题。

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

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

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

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

此错误的另一个原因是,您试图在其中输入设置为可访问性元素(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")

工程

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

另一个答案,但对我们来说,问题是视图太接近另一个视图,手势识别器在它上面。我们发现我们需要至少20个像素的视图(在我们下面的例子中)。实际上15个没用,20个甚至更多。我承认这很奇怪,但是我们有一些正在工作的 UITextView 和一些没有工作的 UITextView,它们都在同一个父级和相同的其他定位(当然还有变量名)下。键盘打开或关闭或其他什么都没有区别。可访问性显示了字段。我们重启了电脑。我们做了干净的建筑。新鲜来源的检查。

解决这个问题的方法是增加1秒钟的睡眠:

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

在我的情况下,这个 Hardware -> Keyboard -> Connect Hardware Keyboard-> 关闭不适合我。

但是当我跟着

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

这招对我很管用

在输入输出中不需要打开/关闭键盘。 不要为 secureTextField 使用.typeText,只要使用

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

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

我遇到了这个问题,并能够修复它在我的情况下,采取的解决方案由 @ AlexDenisov张贴,并添加到我的预先行动的 快跑测试

Pre-Action Run Script

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

首先,创建一个名为 Disable-hardware-keyboard.sh的 shell 脚本,其内容如下:

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. 转到: Test > Pre-actions
  3. 点击“ +”符号 > “新建运行脚本操作”添加新脚本。
  4. 重要提示: 在“提供构建设置”下拉菜单中选择你的主应用目标,而不是 UI 测试目标。
  5. 现在在下面的文本区域中添加以下脚本。

测试脚本 > 预演:

#!/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 测试。模拟器重新启动,禁用硬件键盘。而且测试的键盘交互工作正常。:)

对我有用:

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

长话短说: 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

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

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

明确地说,我手动禁用-启用-禁用模拟器设置中的硬件键盘,设置 defaults write com.apple.iphonesimulator ConnectHardwareKeyboard 0那么擦除的内容和设置。

下面是一个 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:

Test : 未能合成事件: 元素或任何子代都没有 键盘焦点

解决方案: 再次点击

发现第二个 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 字段的到达。

快速扩展:

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,然后点击

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

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:
    emailTextField.tap() // add this line before typeText
    

emailTextField.typeText("your text")