提高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
相关文章
|
3月前
|
运维 Prometheus 监控
如何在测试环境中保持操作系统、浏览器版本和服务器配置的稳定性和一致性?
如何在测试环境中保持操作系统、浏览器版本和服务器配置的稳定性和一致性?
|
4月前
|
机器学习/深度学习 人工智能 监控
提升软件质量的关键路径:高效测试策略与实践在软件开发的宇宙中,每一行代码都如同星辰般璀璨,而将这些星辰编织成星系的过程,则依赖于严谨而高效的测试策略。本文将引领读者探索软件测试的奥秘,揭示如何通过精心设计的测试方案,不仅提升软件的性能与稳定性,还能加速产品上市的步伐,最终实现质量与效率的双重飞跃。
在软件工程的浩瀚星海中,测试不仅是发现缺陷的放大镜,更是保障软件质量的坚固防线。本文旨在探讨一种高效且创新的软件测试策略框架,它融合了传统方法的精髓与现代技术的突破,旨在为软件开发团队提供一套系统化、可执行性强的测试指引。我们将从测试规划的起点出发,沿着测试设计、执行、反馈再到持续优化的轨迹,逐步展开论述。每一步都强调实用性与前瞻性相结合,确保测试活动能够紧跟软件开发的步伐,及时适应变化,有效应对各种挑战。
|
22天前
|
Dart 前端开发 Android开发
【02】写一个注册页面以及配置打包选项打包安卓apk测试—开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
【02】写一个注册页面以及配置打包选项打包安卓apk测试—开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
25 1
【02】写一个注册页面以及配置打包选项打包安卓apk测试—开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
|
29天前
|
消息中间件 监控 小程序
电竞陪玩系统架构优化设计,陪玩app如何提升系统稳定性,陪玩小程序平台的测试与监控
电竞陪玩系统架构涵盖前端(React/Vue)、后端(Spring Boot/php)、数据库(MySQL/MongoDB)、实时通信(WebSocket)及其他组件(Redis、RabbitMQ、Nginx)。通过模块化设计、微服务架构和云计算技术优化,提升系统性能与可靠性。同时,加强全面测试、实时监控及故障管理,确保系统稳定运行。
|
3月前
|
数据库连接 Go 数据库
Go语言中的错误注入与防御编程。错误注入通过模拟网络故障、数据库错误等,测试系统稳定性
本文探讨了Go语言中的错误注入与防御编程。错误注入通过模拟网络故障、数据库错误等,测试系统稳定性;防御编程则强调在编码时考虑各种错误情况,确保程序健壮性。文章详细介绍了这两种技术在Go语言中的实现方法及其重要性,旨在提升软件质量和可靠性。
54 1
|
4月前
|
存储 监控 网络协议
服务器压力测试是一种评估系统在极端条件下的表现和稳定性的技术
【10月更文挑战第11天】服务器压力测试是一种评估系统在极端条件下的表现和稳定性的技术
212 32
|
3月前
|
存储 监控 前端开发
如何确保测试脚本的稳定性和可靠性?
确保测试脚本的稳定性和可靠性是保证性能测试结果准确有效的关键
|
4月前
|
监控 Devops 持续交付
掌握 GitOps:实现 DevOps 自动化的现代方法
【10月更文挑战第19天】GitOps 是一种基于 Git 仓库管理应用配置和集群状态的现代化 DevOps 方法,通过自动化工具实现声明式配置和持续部署。本文介绍了 GitOps 的核心概念、优势、挑战及实施的最佳实践,帮助团队提高部署效率和系统可靠性。
|
4月前
|
运维 监控 安全
构建高效运维体系:从监控到自动化的全面指南在当今数字化时代,运维作为保障系统稳定性和效率的重要环节,其重要性不言而喻。本文将深入探讨如何构建一个高效的运维体系,从监控系统的搭建到自动化运维的实施,旨在为读者提供一套完整的解决方案。
本文详细介绍了高效运维体系的构建过程,包括监控系统的选择与部署、日志分析的方法、性能优化的策略以及自动化运维工具的应用。通过对这些关键环节的深入剖析,帮助运维人员提升系统的可靠性和响应速度,降低人工干预成本,实现业务的快速发展和稳定运行。
|
4月前
|
测试技术 Python
自动化测试项目学习笔记(三):Unittest加载测试用例的四种方法
本文介绍了使用Python的unittest框架来加载测试用例的四种方法,包括通过测试用例类、模块、路径和逐条加载测试用例。
133 0
自动化测试项目学习笔记(三):Unittest加载测试用例的四种方法