暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

提高Android自动化测试稳定性的方法(三)

岛哥手记 2021-07-02
752

阅读本文大约需要1分钟。

点击👆小卡片,回复 “合集” 获取系统性的学习笔记和测试开发技能图谱


背景

在之前的一篇文章《移动端UI自动化过程中的难点及应对策略》中,我们提到在Android自动化测试执行过程中经常会遇到一些非预期的系统弹框,我们可以通过无障碍服务来实现智能点击处理,但是通常这个服务只能手动到设置中开启,今天就跟大家分享一下如何实现一个自定义的无障碍服务以及如何自动化的开启它。


实现自定义的无障碍服务

自定义一个服务继承自AccessibilityService

package com.android.jarvis.accessibility


import android.accessibilityservice.AccessibilityService
import android.accessibilityservice.AccessibilityServiceInfo
import android.util.Log
import android.view.KeyEvent
import android.view.accessibility.AccessibilityEvent
import android.view.accessibility.AccessibilityNodeInfo


class JarvisAccessibilityService : AccessibilityService() {
public override fun onServiceConnected() {
Log.i(TAG, "onServiceConnected: ")
val accessibilityServiceInfo = AccessibilityServiceInfo()
accessibilityServiceInfo.packageNames = null // 监听所有应用
accessibilityServiceInfo.eventTypes = AccessibilityEvent.TYPES_ALL_MASK //监听哪些行为
accessibilityServiceInfo.feedbackType = AccessibilityServiceInfo.FEEDBACK_ALL_MASK //反馈
accessibilityServiceInfo.notificationTimeout = 200
serviceInfo = accessibilityServiceInfo
}


override fun onAccessibilityEvent(event: AccessibilityEvent) {
traverseNode(event.source)
processAccessibilityEvent(event)
}


private fun processAccessibilityEvent(event: AccessibilityEvent) {
processBlockingNotification(event)
if (rootInActiveWindow == null) {
Log.i(TAG, "AccessibilityNodeInfo = null")
return
}
traverseNode(rootInActiveWindow)
}


private fun processBlockingNotification(event: AccessibilityEvent) {
val node = event.source
if (node != null) {
if (findblockingUI("是否允许 USB 调试?", node)) {
findAndPerformCheck("始终允许使用这台计算机进行调试", node)
findAndPerformAction("确定", node)
}
if (findblockingUI("是否允许USB调试?", node)) {
findAndPerformCheck("始终允许使用这台计算机进行调试", node)
findAndPerformAction("确定", node)
}
if (findblockingUI("允许USB调试吗?", node)) {
findAndPerformCheck("一律允许使用这台计算机进行调试", node)
findAndPerformAction("确定", node)
}
if (findblockingUI("允许 USB 调试吗?", node)) {
findAndPerformCheck("一律允许使用这台计算机进行调试", node)
findAndPerformAction("允许", node)
}
if (findblockingUI("允许 USB 调试吗?", node)) {
findAndPerformCheck("一律允许使用这台计算机进行调试", node)
findAndPerformAction("确定", node)
            }
}
}


private fun findblockingUI(text: String, source: AccessibilityNodeInfo): Boolean {
val nodes = source.findAccessibilityNodeInfosByText(text)
if (nodes == null || nodes.isEmpty()) {
return false
}
Log.d(TAG, "findblockingUI $text")
return true
}


public override fun onKeyEvent(event: KeyEvent): Boolean {
return true
}


override fun onInterrupt() {
Log.e(TAG, "服务被Interrupt")
}


private fun traverseNode(node: AccessibilityNodeInfo?) {
if (node != null) {
val count = node.childCount
if (count > 0) {
for (i in 0 until count) {
traverseNode(node.getChild(i))
}
return
}
val clickable = node.isClickable
val text = node.text
val pkgName = node.packageName
if (!"com.miui.home".contentEquals(pkgName)) {
Log.i(
TAG,
"pkg:" + pkgName as Any + " Node:" + text as Any + " clickable:" + clickable
)
}
}
}


private fun findAndPerformAction(text: String, source: AccessibilityNodeInfo?): Int {
if (source == null) {
return 0
}
val nodes = source.findAccessibilityNodeInfosByText(text)
var count = 0
if (nodes != null && !nodes.isEmpty()) {
for (i in nodes.indices) {
if (performActionClick(nodes[i], text)) {
count++
}
}
}
return count
}


private fun performActionClick(node: AccessibilityNodeInfo?, text: String): Boolean {
if (node == null) {
return false
}
if (!isButton(node) && !isTextView(node) && !isView(node)) {
return false
}
node.performAction(16)
return true
}


private fun findAndPerformCheck(text: String, source: AccessibilityNodeInfo?) {
if (source != null) {
val nodes = source.findAccessibilityNodeInfosByText(text)
if (nodes != null && !nodes.isEmpty()) {
for (i in nodes.indices) {
Log.d(TAG, "performCheck $text")
performActionCheck(nodes[i])
}
}
}
}


private fun performActionCheck(node: AccessibilityNodeInfo?) {
if (node != null && isCheckBox(node) && !node.isChecked) {
node.performAction(16)
}
}


private fun isButton(node: AccessibilityNodeInfo): Boolean {
return node.className == "android.widget.Button" || node.className == "amigo.widget.AmigoButton"
}


private fun isTextView(node: AccessibilityNodeInfo): Boolean {
return node.className == "android.widget.TextView"
}


private fun isView(node: AccessibilityNodeInfo): Boolean {
return node.className == "android.widget.View"
}


private fun isCheckBox(node: AccessibilityNodeInfo): Boolean {
return node.className == "android.widget.CheckBox"
}


private fun isCheckedTextView(node: AccessibilityNodeInfo): Boolean {
return node.className == "android.widget.CheckedTextView"
}


companion object {
private const val TAG = "JarvisAccessibility"
}
}
复制

配置

在res/xml目录下新建accessibility_service_config.xml文件,如下:

<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:description="@string/accessibility_service_description"
android:accessibilityEventTypes="typeViewClicked|typeViewLongClicked|typeViewSelected|typeViewFocused|typeViewTextChanged|typeWindowStateChanged|typeNotificationStateChanged|typeViewHoverEnter|typeViewHoverExit|typeTouchExplorationGestureStart|typeTouchExplorationGestureEnd|typeWindowContentChanged|typeViewScrolled|typeViewTextSelectionChanged|typeAllMask"
android:accessibilityFlags="flagDefault"
android:accessibilityFeedbackType="feedbackGeneric"
android:canRetrieveWindowContent="true"
    android:notificationTimeout="200" />
复制

在AndroidManifest.xml中注册服务

        <service
android:name=".accessibility.JarvisAccessibilityService"
android:label="智能辅助服务"
android:enabled="true"
android:exported="true"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
<intent-filter android:priority="2147483647">
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
<action android:name="android.accessibilityservice.AccessibilityService" />
</intent-filter>


<meta-data
android:name="android.accessibilityservice"
android:resource="@xml/accessibility_service_config" />
</service>
复制

自动开启无障碍服务

可以通过执行下面的命令就可以自动开启指定的无障碍服务:

adb shell content call --uri content://settings/secure --method PUT_secure --arg enabled_accessibility_services  --extra _user:i:0 --extra value:s:com.android.jarvis/com.android.jarvis.accessibility.JarvisAccessibilityService
adb shell content call --uri content://settings/secure --method PUT_secure --arg accessibility_enabled --extra _user:i:0 --extra value:s:1
adb shell settings put secure enabled_accessibility_services com.android.jarvis/com.android.jarvis.accessibility.JarvisAccessibilityService
adb shell settings put secure accessibility_enabled 1
复制

推荐阅读:

提高Android自动化测试稳定性的方法(二)

提高Android自动化测试稳定性的方法(一)

让Android自动化辅助应用成为设备所有者(二)

让Android自动化辅助APP成为设备所有者(一)

Android自动化辅助APP保活配置


想要明白些道理,遇见些有趣的事 —— 离岛

文章转载自岛哥手记,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论