iOS UI自动化测试与辅助功能:元素隔离的最佳实践
2024-12-25 06:27:51
自动化测试元素的暴露与辅助功能元素的隔离
在 UIKit 开发中,有时需要为自动化测试暴露某些视图元素,但这些元素又无需对辅助功能(例如 VoiceOver)可见。这种需求很常见,本文探讨如何实现这种目标,并分析常见错误及其解决方案。
问题
在自动化测试中,往往需要通过 accessibilityIdentifier
来定位元素,以便进行交互或断言。而对于辅助功能,可能需要使用 accessibilityLabel
提供对用户的,或完全忽略某些元素,避免 VoiceOver 朗读。问题在于,当对父视图设置了 isAccessibilityElement = false
时,如何准确暴露子视图元素给自动化测试,并且不会干扰辅助功能的正常运作。
以 UIStackView
作为示例,当希望将 UIStackView
下的特定子视图作为自动化元素暴露出来时,常常会遇到一些挑战。特别是想要区分哪些是自动化测试的专用标识符(identifier),哪些又是提供给辅助功能的标签(label),区分并准确配置是一个难题。例如:给容器视图 dataDisclosureView
设置 isAccessibilityElement = false
, 但需要把其中 descriptionView
与 requestButton
作为 automationElement 暴露出来,还需要保留 descriptionView
与 requestButton
accessibilitylabel。同时给其他元素(titleLabel,descriptionLabel等) 添加 accessibilityIdentifier
用来测试使用。
常见的错误
isAccessibilityElement
滥用: 将isAccessibilityElement
设置为false
后,它的子视图通常会被辅助功能直接忽略。这并非总是期望的行为,尤其是在嵌套视图的情况下。- **
accessibilityElements
配置错误:**accessibilityElements
用于指定父视图的辅助功能子视图。如果配置不当,可能会导致 VoiceOver 无法正常识别到目标元素。 - ** 误解
automationElements
的用途:**automationElements
在 iOS 17 及更高版本引入,旨在专门暴露给自动化测试的元素。它的使用应该与accessibilityElements
相协调,而非冲突。 - 尝试直接使用
accessibilityIdentifier
进行自动化: 对于自动化测试,最主要的元素定位依据就是accessibilityIdentifier
。若在视图结构未配置清晰的情况下使用该属性,无法找到该元素,即便其父级视图允许访问的情况下,该标识符依然会被忽略。
解决方案:合理运用属性
为了同时满足自动化测试和辅助功能的需求,应分别使用 accessibilityIdentifier
与 automationElements
,且在 isAccessibilityElement
、accessibilityLabel
、accessibilityElements
之间合理配置。
方案一:automationElements
优先
针对 iOS 17 及更高版本,优先使用 automationElements
,可以将要自动化测试的元素直接暴露,并且在容器元素本身设置 isAccessibilityElement
为 false
, 容器的辅助功能和测试功能被清晰地区分开。 对于之前的 accessibilityIdentifier
可以依然使用。
操作步骤:
- 保持父容器视图的
isAccessibilityElement
为false
。 - 配置需要自动化测试的子视图的
accessibilityIdentifier
。 - 设置父容器的
automationElements
属性为子视图列表。 - 配置子视图的
accessibilityLabel
用以支持辅助功能。
@IBOutlet weak var dataDisclosureView: UIStackView! // Main ContainerView
@IBOutlet private weak var titleLabel: UILabel! {
didSet {
titleLabel.text = "Hello"
titleLabel.accessibilityIdentifier = "test_titleLabel"
}
}
@IBOutlet private weak var descriptionLabel: UILabel! {
didSet {
descriptionLabel.text = "World"
descriptionLabel.accessibilityIdentifier = "test_descriptionLabel"
}
}
@IBOutlet weak var descriptionView: UIStackView! { // sub container view
didSet {
descriptionView.isAccessibilityElement = true
descriptionView.accessibilityLabel = "Hello"
descriptionView.accessibilityIdentifier = "test_hello"
}
}
@IBOutlet private weak var requestButton: UIButton! {
didSet {
requestButton.isAccessibilityElement = true
requestButton.accessibilityLabel = "Request Button"
requestButton.accessibilityIdentifier = "test_button"
}
}
override func viewDidLoad() {
super.viewDidLoad()
dataDisclosureView.isAccessibilityElement = false
dataDisclosureView.accessibilityElements = [ descriptionView ?? "" ]
if #available(iOS 17.0, *) {
dataDisclosureView.automationElements = [ descriptionView ?? "",
requestButton ?? ""]
} else {
// Fallback on earlier versions
}
let requestButtonAction = UIAccessibilityCustomAction(name: "start",
target: self,
selector: #selector( request))
dataDisclosureView.accessibilityCustomActions = [ requestButtonAction ]
}
原理: 此配置使得自动化测试框架可以利用 dataDisclosureView
的 automationElements
定位到子视图 descriptionView
和 requestButton
,通过 accessibilityIdentifier
定位到 titleLabel
与 descriptionLabel
,同时辅助功能可以通过 descriptionView
和 requestButton
的 accessibilityLabel
来进行朗读。此方法在iOS 17 以上运行正常。
方案二:低于 iOS 17 的兼容方案
针对iOS 17 以下版本,由于不支持 automationElements
, 将辅助功能与自动化测试暴露到同一层面:
- 父视图设置
isAccessibilityElement
为true
。 - 需要提供辅助功能的元素正常设置
accessibilityLabel
,不需要的就无需配置,并且设置好自动化测试需要使用accessibilityIdentifier
。 - 对父视图使用
accessibilityElements
将子视图暴露出来,以便accessibilityIdentifier
可以使用, 同时可以使用accessibilityLabel
。
@IBOutlet weak var dataDisclosureView: UIStackView! // Main ContainerView
@IBOutlet private weak var titleLabel: UILabel! {
didSet {
titleLabel.text = "Hello"
titleLabel.accessibilityIdentifier = "test_titleLabel"
}
}
@IBOutlet private weak var descriptionLabel: UILabel! {
didSet {
descriptionLabel.text = "World"
descriptionLabel.accessibilityIdentifier = "test_descriptionLabel"
}
}
@IBOutlet weak var descriptionView: UIStackView! { // sub container view
didSet {
descriptionView.isAccessibilityElement = true
descriptionView.accessibilityLabel = "Hello"
descriptionView.accessibilityIdentifier = "test_hello"
}
}
@IBOutlet private weak var requestButton: UIButton! {
didSet {
requestButton.isAccessibilityElement = true
requestButton.accessibilityLabel = "Request Button"
requestButton.accessibilityIdentifier = "test_button"
}
}
override func viewDidLoad() {
super.viewDidLoad()
dataDisclosureView.isAccessibilityElement = true
dataDisclosureView.accessibilityElements = [ titleLabel,descriptionLabel, descriptionView, requestButton ]
let requestButtonAction = UIAccessibilityCustomAction(name: "start",
target: self,
selector: #selector( request))
dataDisclosureView.accessibilityCustomActions = [ requestButtonAction ]
}
原理: 此配置方法为较低版本提供了对等解决方案, 使自动化测试框架能够利用 accessibilityElements
定位所有需要的元素,从而进行UI测试; 与此同时, 具有 accessibilityLabel
的 descriptionView
和 requestButton
将可以正常辅助功能操作,titleLabel
, descriptionLabel
和 accessibilityLabel
无辅助功能的需求,所以不会影响辅助功能的正常运作。
注意事项
- 自动化测试元素ID设置需要使用清晰,语义化的命名方式, 并且做好相应的维护,在自动化测试脚本中使用匹配的方式访问UI控件。
- 辅助功能应该为盲人和视觉受损的人群考虑,所以元素的暴露以及对应标签需要谨慎处理。
- 确保对每一个元素的使用方法了解充分,才能做到灵活配置。
总而言之,通过细致的配置和对不同属性作用的深入理解,可以有效区分自动化测试所需的元素,和为辅助功能使用的标签,达到更好的测试和用户体验效果。