返回

iOS UI自动化测试与辅助功能:元素隔离的最佳实践

IOS

自动化测试元素的暴露与辅助功能元素的隔离

在 UIKit 开发中,有时需要为自动化测试暴露某些视图元素,但这些元素又无需对辅助功能(例如 VoiceOver)可见。这种需求很常见,本文探讨如何实现这种目标,并分析常见错误及其解决方案。

问题

在自动化测试中,往往需要通过 accessibilityIdentifier 来定位元素,以便进行交互或断言。而对于辅助功能,可能需要使用 accessibilityLabel 提供对用户的,或完全忽略某些元素,避免 VoiceOver 朗读。问题在于,当对父视图设置了 isAccessibilityElement = false 时,如何准确暴露子视图元素给自动化测试,并且不会干扰辅助功能的正常运作。

UIStackView 作为示例,当希望将 UIStackView 下的特定子视图作为自动化元素暴露出来时,常常会遇到一些挑战。特别是想要区分哪些是自动化测试的专用标识符(identifier),哪些又是提供给辅助功能的标签(label),区分并准确配置是一个难题。例如:给容器视图 dataDisclosureView 设置 isAccessibilityElement = false, 但需要把其中 descriptionViewrequestButton 作为 automationElement 暴露出来,还需要保留 descriptionViewrequestButton accessibilitylabel。同时给其他元素(titleLabel,descriptionLabel等) 添加 accessibilityIdentifier 用来测试使用。

常见的错误

  1. isAccessibilityElement 滥用:isAccessibilityElement 设置为 false 后,它的子视图通常会被辅助功能直接忽略。这并非总是期望的行为,尤其是在嵌套视图的情况下。
  2. ** accessibilityElements 配置错误:** accessibilityElements 用于指定父视图的辅助功能子视图。如果配置不当,可能会导致 VoiceOver 无法正常识别到目标元素。
  3. ** 误解 automationElements 的用途:** automationElements 在 iOS 17 及更高版本引入,旨在专门暴露给自动化测试的元素。它的使用应该与 accessibilityElements 相协调,而非冲突。
  4. 尝试直接使用 accessibilityIdentifier 进行自动化: 对于自动化测试,最主要的元素定位依据就是 accessibilityIdentifier。若在视图结构未配置清晰的情况下使用该属性,无法找到该元素,即便其父级视图允许访问的情况下,该标识符依然会被忽略。

解决方案:合理运用属性

为了同时满足自动化测试和辅助功能的需求,应分别使用 accessibilityIdentifierautomationElements ,且在 isAccessibilityElementaccessibilityLabelaccessibilityElements之间合理配置。

方案一:automationElements 优先

针对 iOS 17 及更高版本,优先使用 automationElements,可以将要自动化测试的元素直接暴露,并且在容器元素本身设置 isAccessibilityElementfalse, 容器的辅助功能和测试功能被清晰地区分开。 对于之前的 accessibilityIdentifier 可以依然使用。

操作步骤:

  1. 保持父容器视图的 isAccessibilityElementfalse
  2. 配置需要自动化测试的子视图的 accessibilityIdentifier
  3. 设置父容器的 automationElements 属性为子视图列表。
  4. 配置子视图的 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 ]
    }

原理: 此配置使得自动化测试框架可以利用 dataDisclosureViewautomationElements 定位到子视图 descriptionViewrequestButton ,通过 accessibilityIdentifier 定位到 titleLabeldescriptionLabel ,同时辅助功能可以通过 descriptionViewrequestButtonaccessibilityLabel 来进行朗读。此方法在iOS 17 以上运行正常。

方案二:低于 iOS 17 的兼容方案

针对iOS 17 以下版本,由于不支持 automationElements , 将辅助功能与自动化测试暴露到同一层面:

  1. 父视图设置isAccessibilityElementtrue
  2. 需要提供辅助功能的元素正常设置accessibilityLabel,不需要的就无需配置,并且设置好自动化测试需要使用accessibilityIdentifier
  3. 对父视图使用 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测试; 与此同时, 具有 accessibilityLabeldescriptionViewrequestButton 将可以正常辅助功能操作,titleLabel, descriptionLabelaccessibilityLabel 无辅助功能的需求,所以不会影响辅助功能的正常运作。

注意事项

  • 自动化测试元素ID设置需要使用清晰,语义化的命名方式, 并且做好相应的维护,在自动化测试脚本中使用匹配的方式访问UI控件。
  • 辅助功能应该为盲人和视觉受损的人群考虑,所以元素的暴露以及对应标签需要谨慎处理。
  • 确保对每一个元素的使用方法了解充分,才能做到灵活配置。

总而言之,通过细致的配置和对不同属性作用的深入理解,可以有效区分自动化测试所需的元素,和为辅助功能使用的标签,达到更好的测试和用户体验效果。