前言

因为公司项目所集成的APP和其内嵌的网页可能需要进行自动化测试,几乎立马就构思出了这套使用Android无障碍服务来进行测试的这套测试方案。在去年上半年时,终于抽空对该方案进行了实践并证实了其可行性。通过使用Android无障碍服务,可以实现获取页面元素、控制和获取界面操作等众多操作,对于自动化APP测试来说已经完全足够了,其功能和权限之丰富也需要开发人员特别注意其申请的权限和获取的隐私信息处理方式,保障用户的隐私安全。

安卓无障碍服务概览

安卓无障碍服务是从安卓1.6(API 4)开始出现,并经过安卓4.0(API 14)进行了增强,使用它的主要目的是为了根据用户的操作手势或语音来方便残障人士来管理和操控手机上的UI界面。由于它能够获取屏幕上的UI界面和进行操作,许多自动化执行APP也都使用这项功能来实现。

创建无障碍服务

由于需要使用无障碍服务来对APP中的界面元素进行操控和测试,需要创建相应的无障碍服务和进行配置,如配置需要操控的APP包名、需要获取的用户操作、需要在界面上执行的操作、描述信息、接受操作事件的时间间隔等。

申请权限

<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
<queries>
<package android:name="package_name" />
</queries>

配置无障碍服务

<service
android:name=".TestService"
android:exported="true"
android:foregroundServiceType="location"
android:label="@string/app_name"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService" />
</intent-filter>

<meta-data
android:name="android.accessibilityservice"
android:resource="@xml/accessibility_service_config" />
</service>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:accessibilityEventTypes="typeAllMask"
android:accessibilityFeedbackType="feedbackAllMask"
android:accessibilityFlags="flagDefault|flagRequestEnhancedWebAccessibility"
android:canPerformGestures="true"
android:canRetrieveWindowContent="true"
android:description="@string/accessibility_description"
android:notificationTimeout="1000"
android:packageNames="package_name" />

创建无障碍服务

class TestService : AccessibilityService() {
override fun onAccessibilityEvent(event: AccessibilityEvent?) {
}
}

检测并提示开启无障碍服务

if (!Settings.Secure.getString(contentResolver, Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES).contains(TestService::class.java.canonicalName as CharSequence)) {
Toast.makeText(this, "请先开启测试辅助功能!", Toast.LENGTH_LONG).show()
startActivity(Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS))
} else {
Toast.makeText(this, "正在进行测试!", Toast.LENGTH_LONG).show()
startService(Intent(applicationContext, TestService::class.java))
}

界面元素的定位方式

对界面中的元素进行定位并在无障碍服务中进行使用,可以用Android Studio自带的UiAutomator工具来完成这项任务。

通过UiAutomator定位元素

启动UiAutomator

  1. 使用UiAutomator之前,必须先在电脑中安装JDK8,建议从Oracle提供的官方下载地址下载并安装:https://www.oracle.com/cn/java/technologies/downloads/#java8-windows
  2. 安卓手机连接电脑并允许USB调试
  3. 打开目录C:\Users\user_name\AppData\Local\Android\Sdk\tools\bin,打开uiautomatorviewer.bat即可启动UiAutomator

获取屏幕快照并定位元素

通过点击第二个按钮来获取当前显示的屏幕快照,当成功抓取屏幕快照后将显示界面的层次结构和截图,点击需要定位的元素,右下角的resource-id就是该元素在该页面上的唯一定位id。

在代码中定位元素

通过UiAutomator获取了界面元素的id之后,即可在无障碍服务中进行使用来定位和获取屏幕中的指定元素,可以通过View中显示的文本或id来进行元素定位。

代码 用途
rootInActiveWindow.findAccessibilityNodeInfosByText(text) 通过View中显示的文本进行定位
rootInActiveWindow.findAccessibilityNodeInfosByViewId(id) 通过界面元素的id进行定位

获取元素属性和模拟手势操作

定位完元素,主要是为了获取元素的属性或进行模拟用户的手势操作,如点击、输入文本、滑动。

代码 用途
element.performAction(AccessibilityNodeInfo.ACTION_CLICK) 点击元素
element.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT,Bundle().apply {putString(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE,text)}) 输入文本
dispatchGesture(GestureDescription.Builder().addStroke(GestureDescription.StrokeDescription(Path().apply {moveTo(1000f, 1000f)lineTo(0f, 1000f)}, 200, 200)).addStroke(GestureDescription.StrokeDescription(Path().apply { moveTo(1000f, 1000f) lineTo(0f, 1000f)}, 200, 200)).build(), object : GestureResultCallback() {}, null) 滑动操作

获取和定位WebView中的元素

如需获取和定位WebView中的元素,则需声明android:accessibilityFlags=”flagRequestEnhancedWebAccessibility”。由于在WebView中的网页内容无法直接通过文本或id进行定位,在定位时需使用遍历整个WebView才能查找成功。

 private fun getNode(webviewNode: AccessibilityNodeInfo, text: String): AccessibilityNodeInfo {
for (i in 0..webviewNode.childCount) {
if (webviewNode.getChild(i).text == text) {
return webviewNode.getChild(i)
}
}
}

小结

通过使用安卓无障碍服务,成功获取了界面数据并且进行了界面操控,该方案在2022.06.11成功验证了其可行性,并进行了小量试验。通过这次试验,基本了解如何通过安卓无障碍服务获取界面元素和进行界面操控,能够实现检测页面元素以及自动触发相应的界面操作,可以使用无障碍服务对需要重复多次的操作和繁琐操作进行自动化改造并自动执行。