打造一款快速高效且高度可复用的 android 自动化测试工具
主页入口 请点我https://github.com/zhangzhao4444/Maxim文章源自玩技e族-https://www.playezu.com/240341.html
文章源自玩技e族-https://www.playezu.com/240341.html
文章源自玩技e族-https://www.playezu.com/240341.html
优势
- 高速点击,每秒 10-15 action!
- 多平台兼容! 同时兼容 Android 5-9
- 轻量极简!
如何使用
- adb push framework.jar monkey.jar 文件到 /sdcard
执行
adbshell CLASSPATH=/sdcard/monkey.jar:/sdcard/framework.jar exec app_process /system/bin tv.panda.test.monkey.Monkey-p com.panda.videoliveplatform --uiautomatormix --running-minutes 60文章源自玩技e族-https://www.playezu.com/240341.html脱机运行(#53 楼)文章源自玩技e族-https://www.playezu.com/240341.html
参数说明
- tv.panda.test.monkey.Monkey 主调入口 无需修改
- -p com.panda.videoliveplatform 待测 appid
策略模式文章源自玩技e族-https://www.playezu.com/240341.html
--uiautomatormix 混合模式(70% 控件解析随机点击,其余 30% 按原 Monkey 事件概率分布)
--pct-uiautomatormix n 可自定义混合模式中控件解析事件概率
--uiautomatordfs DFS 深度遍历算法(优化版)(注 Android5 不支持 dfs)
--uiautomatortroy Troy 模式 见(#74 楼)
非以上两种为原始 Monkey 策略文章源自玩技e族-https://www.playezu.com/240341.html执行时长
--running-minutes 60 执行 60 分钟 monkey文章源自玩技e族-https://www.playezu.com/240341.html场景细粒度控制
--act-whitelist-file/sdcard/awl.strings 自定义 Activity 白名单
例:文章源自玩技e族-https://www.playezu.com/240341.htmlcom.panda.videoliveplatform.activity.WelcomeActivity com.panda.videoliveplatform.activity.SplashWakeActivity com.panda.videoliveplatform.activity.MainFragmentActivity com.panda.videoliveplatform.activity.LiveRoomActivity
锁定跳转只可进入其中的某个 Activity
--act-blacklist-file 同上文章源自玩技e族-https://www.playezu.com/240341.html其他参数及用法同原始 Monkey
特性简介
a. 速度快 每秒 10-15 个 Action 事件
界面控件解析算法通过改造底层 framework,直接使用 AccessibilityNodeInfo 并优化减化其调度流程,解析速度控制在 50ms 内,可对界面变化做快速反应。
{
val clickable = ArrayList<AccessibilityNodeInfo>()
collectClickable(clickable, root) //递归算法生成node树结构
......
val node = clickable[random.nextInt(count)]
val nodeRect = Rect()
node.getBoundsInScreen(nodeRect) //选取其中某个node的 rect ,随机点击其中某point
generateClickEventAt(nodeRect, 0L)
}
b. Android 全平台兼容
兼容 Android5,6,7,8,p 各系列。通过反射原理动态解析各平台 Api 差异,使用一套逻辑兼容全系列。
{
Class<?> clazz = mAm.getClass();
String name = "setActivityController"; //存在差异的api
Method method = findMethod(clazz, name, android.app.IActivityController.class); //反射动态search api
if (method != null) {
......
}
method = findMethod(clazz, name, android.app.IActivityController.class, boolean.class);
if (method != null) {
......
}
c. 防跳出
控件解析时获取进程推栈 Top Activity,按非白即黑立即执行切回。各事件执行时按特有逻辑屏蔽掉状态栏,防止误操作。
{
......
cn = this.mDevice.mAm.getTasks(1, 0).get(0).topActivity //取top activity
if (cn != null) currentPackage = cn.getPackageName()
if (!MonkeyUtils.packageFilter.isPackageValid(currentPackage!!)) { //判断非白即黑
this.mQ.clear()
this.generateActivity()
......
}
d. 防休眠
休眠时自动检测并唤醒屏幕。
{
......
if (!this.mPM!!.isInteractive) { //判断处于休眠中
......
val i = Runtime.getRuntime().exec(arrayOf("input", "keyevent", "26")).waitFor() //唤醒设备
......
}
e. 熔断机制
当事件按某个特有模式固定执行一段时间时则自动触发熔断开始自拉活,防止假死。如重复点击同一位置 n 秒。
private fun isBlocked(): Boolean{
val jam = true
......
val last = actionsHistory.last()
for (i in 2..30){ //重复点击n次 触发熔断
if (mVerbose > 1) Logger.log("last action is ${actionsHistory.size-1} code = $last, action${actionsHistory.size-i} code = ${actionsHistory[actionsHistory.size-i]}")
if (last != actionsHistory[actionsHistory.size-i]){
return false
}
}
//其他熔断
.....
}
f. 场景细粒度
引入 Activity 黑白名单,可控制限定在某些场景内。如只测试某几个相关页面。
private inner class ActivityController : IActivityController.Stub() {
override fun activityStarting(intent: Intent, pkg: String): Boolean {
var allowActivity = true
if (MonkeyUtils.activityFilter.hasValidActivitys()){
val currentActivity: String? = try {intent.component.className} catch (e:Exception){null}
if (currentActivity != null){
allowActivity = MonkeyUtils.activityFilter.checkEnteringActivity(currentActivity) //通过filter的才允许跳转
}
Logger.log("// Activity : ${currentActivity} in Intent")
}
......
}
......
}
g. 随机自动输入
检测当遇到可输入模式时,按设定(ape.string)或随机输入键盘事件。如输入 666 或 2333 弹幕
随机输入 需要提前安装 adbkeyboard
https://github.com/senzhk/ADBKeyBoard
{
......
val inputMethodVisbleHight = this.mDevice.mIMM!!.getInputMethodWindowVisibleHeight() //存在键盘输入栏
if (inputMethodVisbleHight <= 0) return
//注入键盘事件
try { generateRandomKeyEvents() }catch (e : Exception){ }
......
}
h. 崩溃堆栈自动保存
当崩溃(crash、oom)发生时自动抓取,并存于/sdcard/crash-dump.log
{
override fun appCrashed(processName: String, pid: Int, shortMsg: String, longMsg: String, timeMillis: Long, stackTrace: String): Boolean {
......
writeDumpLog("// CRASH: $processName (pid $pid) (dump time: $dateStr)")
writeDumpLog("// Build Time: ${Build.TIME}")
writeDumpLog("// ${stackTrace.replace("n", "n// ")}")
....
}
todo
- 特殊事件序列 - 已完成
- 引入 GT 性能测试
- 控件黑名单 - 已完成
- xposed 模式 --见 xmonkey
- Ai 算法 模式
** 其他 **
扫盲贴: https://testerhome.com/topics/11884
常见问题排查
小米手机未开启 “开发者选项” -> "USB 调试(安全设置)允许通过 usb 调试修改权限或模拟点击"。
开启后 ok 。报错 log 如下图accessibilityservice 被其他驱动占用 agent 未注销。如使用过 atx 未执行 unregister。
执行 unregister 后 OK。报错 log 如下图,或无 log
或厂商修改造成 dex 无法 load ,maxim 启动后就退出。实际什么都未执行。无 log 输出。 logcat 如下图。此问题暂时待查
4.运行 maxim 无任何 log,启动后就退出。需检查/sdcard/是否存在 monkey.jar ,framework.jar。部分机型发现 adb push 过去 monkey.jar 自动被更名成 monkey. 导致无法运行。
5.vivo7.1 运行 maxim 报错,需要关闭锁屏和开启 usb 模拟点击 ,然后就可以跑 monkey 了。报错如下:
update
#40 楼
支持特殊事件序列 max.xpath.actions
如何查看特殊事件 log #79 楼
#44 楼 #52 楼
支持屏蔽黑控件或黑区域 max.widget.black
#57 楼
支持截图,dump xml
崩溃回溯式截图 #92 楼
#74 楼
Troy 模式
#169 楼
Monkeyapi
未知地区 200F
大神,开源把
未知地区 199F
经常跑着跑着整个手机卡住了,背景黑了,但是桌面 app 还能看到,就是点不了咋回事
未知地区 198F
请问这个 unregister 具体是怎么弄呀
未知地区 197F
这个进程可以用什么命令直接关掉吗?
未知地区 196F
accessibilityservice 被其他进程占用中,可能是 appiumuiautomatoratx 等等,需要关闭后运行 max
未知地区 195F
@zhangzhao_lenovo 大神,这个错误是什么原因啊,一跑起来就停了,SM-G9600 的手机
java.lang.IllegalStateException: UiAutomationService android.accessibilityservice.IAccessibilityServiceClient$Stub$Proxy@2c0907falready registered!
at android.os.Parcel.createException(Parcel.java:1974)
at android.os.Parcel.readException(Parcel.java:1934)
at android.os.Parcel.readException(Parcel.java:1884)
at android.view.accessibility.IAccessibilityManager$Stub$Proxy.registerUiTestAutomationService(IAccessibilityManager.java:696)
at android.app.UiAutomationConnection.registerUiTestAutomationServiceLocked(UiAutomationConnection.java:399)
at android.app.UiAutomationConnection.connect(UiAutomationConnection.java:97)
at android.app.UiAutomation.connect(UiAutomation.java:234)
at android.app.UiAutomation.connect(UiAutomation.java:210)
at tv.panda.test.monkey.MonkeySourceRandomUiAutomatorMix.connect(MonkeySourceRandomUiAutomatorMix.kt:123)
at tv.panda.test.monkey.MonkeySourceRandomUiAutomatorMix.(MonkeySourceRandomUiAutomatorMix.kt:142)
at tv.panda.test.monkey.Monkey.run(Monkey.kt:558)
at tv.panda.test.monkey.Monkey$Companion.main(Monkey.kt:1422)
at tv.panda.test.monkey.Monkey.main(Unknown Source:2)
at com.android.internal.os.RuntimeInit.nativeFinishInit(Native Method)
at com.android.internal.os.RuntimeInit.main(RuntimeInit.java:341)
Caused by: android.os.RemoteException: Remote stack trace:
at com.android.server.accessibility.UiAutomationManager.registerUiTestAutomationServiceLocked(UiAutomationManager.java:88)
at com.android.server.accessibility.AccessibilityManagerService.registerUiTestAutomationService(AccessibilityManagerService.java:1354)
at android.view.accessibility.IAccessibilityManager$Stub.onTransact(IAccessibilityManager.java:159)
at android.os.Binder.execTransact(Binder.java:739)
java.lang.IllegalStateException: UiAutomation not connected!
at android.app.UiAutomation.throwIfNotConnectedLocked(UiAutomation.java:1112)
at android.app.UiAutomation.getWindows(UiAutomation.java:474)
at tv.panda.test.monkey.MonkeySourceRandomUiAutomatorMix.getWindowRoots$monkey_release(MonkeySourceRandomUiAutomatorMix.kt:685)
at tv.panda.test.monkey.MonkeySourceRandomUiAutomatorMix.collectClickable(MonkeySourceRandomUiAutomatorMix.kt:670)
at tv.panda.test.monkey.MonkeySourceRandomUiAutomatorMix.generatePointerEvent(MonkeySourceRandomUiAutomatorMix.kt:457)
at tv.panda.test.monkey.MonkeySourceRandomUiAutomatorMix.generateEvents(MonkeySourceRandomUiAutomatorMix.kt:850)
at tv.panda.test.monkey.MonkeySourceRandomUiAutomatorMix.getNextEvent(MonkeySourceRandomUiAutomatorMix.kt:92)
at tv.panda.test.monkey.Monkey.runMonkeyCycles(Monkey.kt:1155)
at tv.panda.test.monkey.Monkey.run(Monkey.kt:605)
at tv.panda.test.monkey.Monkey$Companion.main(Monkey.kt:1422)
at tv.panda.test.monkey.Monkey.main(Unknown Source:2)
at com.android.internal.os.RuntimeInit.nativeFinishInit(Native Method)
at com.android.internal.os.RuntimeInit.main(RuntimeInit.java:341)
java.util.NoSuchElementException
at java.util.LinkedList.getFirst(LinkedList.java:244)
at tv.panda.test.monkey.MonkeySourceRandomUiAutomatorMix.getNextEvent(MonkeySourceRandomUiAutomatorMix.kt:95)
at tv.panda.test.monkey.Monkey.runMonkeyCycles(Monkey.kt:1155)
at tv.panda.test.monkey.Monkey.run(Monkey.kt:605)
at tv.panda.test.monkey.Monkey$Companion.main(Monkey.kt:1422)
at tv.panda.test.monkey.Monkey.main(Unknown Source:2)
at com.android.internal.os.RuntimeInit.nativeFinishInit(Native Method)
at com.android.internal.os.RuntimeInit.main(RuntimeInit.java:341)
未知地区 194F
求问:第一次用该工具,如何设置精准化 monkey 脚本,可以一直只跑登录退出操作?
未知地区 193F
问题:
1、跑 app 的过程中重复退出 app,是什么原因导致的?
2、配置了 max.xpath.actions,但是第二次进入页面的时候不会执行 actions 里面的
未知地区 192F
我也遇到了一样的问题,测试机型是 MEIZU MX5,android 5.1.想了解下后面你们是如何解决的呢
未知地区 191F
请教个问题,想用 shell 脚本自动化执行 maxim,有一步需要安装 apk,但是会有安装权限弹窗,怎么自动化点掉,有好的办法吗?
查了下资料,Android 自带的 UIAutomator 框架可以满足条件,但是提供的 api 得用 java。。用 java 写这么个操作感觉有点笨重,除了 java,还有其他法子不?