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

简介: 在之前的一篇文章《移动端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
相关文章
|
2月前
|
监控 安全 Android开发
【新手必读】Airtest测试Android手机常见的设置问题
【新手必读】Airtest测试Android手机常见的设置问题
|
3月前
|
Android开发 Python
Python封装ADB获取Android设备wifi地址的方法
Python封装ADB获取Android设备wifi地址的方法
61 0
|
1天前
|
人工智能 Python
【Python实用技能】建议收藏:自动化实现网页内容转PDF并保存的方法探索(含代码,亲测可用)
【Python实用技能】建议收藏:自动化实现网页内容转PDF并保存的方法探索(含代码,亲测可用)
14 0
|
2天前
|
Linux Shell Android开发
自动化脚本之GPIO/LED相关适用于Android/Linux
自动化脚本之GPIO/LED相关适用于Android/Linux
13 0
|
2天前
|
Java API Android开发
Android 11 修改libcore Cipher AS测试
Android 11 修改libcore Cipher AS测试
13 1
|
22天前
|
Android开发
Android调用相机与相册的方法2
Android调用相机与相册的方法
17 0
|
1月前
|
存储 Android开发 C++
【Android 从入门到出门】第五章:使用DataStore存储数据和测试
【Android 从入门到出门】第五章:使用DataStore存储数据和测试
34 3
|
1月前
|
存储 SQL 数据库
【Android 从入门到出门】第六章:使用Room数据库并测试
【Android 从入门到出门】第六章:使用Room数据库并测试
29 4
|
2月前
|
监控 测试技术
APP的稳定性测试如何做?
APP的稳定性测试如何做?
|
2月前
|
Java 测试技术 API
安卓APP和iOS APP在测试上的区别是什么?
安卓APP和iOS APP在测试上的区别是什么?