整机Monkey测试

背景

  稳定性测试时,需要跑整机monkey,实际测试中发现:monkey打开应用时,无法点击“权限弹窗”,导致monkey无法进入应用首页。手动打开每个应用授权,非常耗时,基于此背景开发该工具。

一键停止Monkey批处理

@ECHO off  
@REM 无限循环的标签  

ECHO wait-for-device...
adb wait-for-device
ECHO Device connected.
echo.
:LOOP
@REM 拿到Monkey进程id
adb shell "kill -9 `ps -ef | grep com.android.commands.monkey | grep -v 'grep' | awk '{print $2}'`"
@REM 清除时钟的闹钟
adb shell pm clear com.android.deskclock
ECHO Monkey hat stopped.
echo.
PAUSE
GOTO LOOP  
@ECHO on

整机Monkey测试源码

# -*- coding: utf-8 -*-
# @Time    : 2026/4/9 14:00
# @FileName: 整机monkey.py
# @Author: 1159533975@qq.com
# @Software: PyCharm
import os
import re
import subprocess
import sys
import time
from concurrent.futures import ThreadPoolExecutor

# 黑名单,monkey测试不跑的包
black_list = [
    # MTK log
    "com.debug.loggerui",
    "com.mediatek.engineermode",
    # 展讯 log
    "com.sprd.logmanager",
    "com.sprd.engineermode",
    # RK log
    "com.incar.rkylog",
    # 全志 log
    "com.softwinner.awlogsettings",
    # MTK非框架行动定位服务
    "com.mediatek.gnss.nonframeworklbs"
]

# 创建adb shell会话, 大大提升adb shell性能
class ADB_SHELL:
    def __init__(self):
        """
        简介:
            使用subprocess开启一个adb shell子线程并保持会话状态
            在频繁使用adb shell命令时,可节省启动shell的时间
        参数:
            :param device_no: 设备号
        返回:
            无
        """
        command = 'adb shell'
        process = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, bufsize=0, creationflags=subprocess.CREATE_NO_WINDOW)
        self.process = process

    def execute(self, command, output=False) -> list:
        """
        简介:
            执行adb shell命令
        参数:
            :param command: 命令
            :param output: 是否将输出结果打印在控制台
        返回:
            返回以行分隔、list类型的输出结果结果
        """
        # 防止在shell里再执行adb shell
        if command.startswith('adb shell'):
            command = command.replace('adb shell ', '', 1)

        process = self.process
        result = []

        process.stdin.write((command + '\n').encode('utf-8'))
        process.stdin.flush()

        # 发送标记命令,用于识别命令结束
        marker = "echo __COMMAND_FINISHED__"
        self.process.stdin.write((marker + '\n').encode('utf-8'))
        self.process.stdin.flush()

        while True:
            # 检查进程是否已结束
            if process.poll() is not None:
                break

            # 读取输出行
            line_bytes = process.stdout.readline()
            if not line_bytes:  # 无字节流,休眠
                time.sleep(0.05)
                continue
            # 容错解码:优先 UTF-8(安卓默认),失败则 GBK(Windows 兜底)
            try:
                line = line_bytes.decode('utf-8').strip()
            except UnicodeDecodeError:
                line = line_bytes.decode('gbk', errors='ignore').strip()

            if '__COMMAND_FINISHED__' in line:
                break

            if line:
                if output:
                    print(line)
                result.append(line)
        return result

    def exit(self):
        process = self.process
        process.stdin.close()
        process.terminate()
        process.wait()

# 等待设备连接
def wait_for_device():
    # 等待设备连接
    sys.stdout.write(f'\r等待设备连接...')
    sys.stdout.flush()
    # print(f'等待设备连接...')
    shell("adb wait-for-device")
    sys.stdout.write(f'\r')
    sys.stdout.flush()
    # 当前设备项目名和安卓版本号
    print(f"已连接设备: {get_device_name()}")

def shell(command, device=None, output: bool = False):
    """执行shell命令,返回 {'code': 状态码, 'output': 输出, 'error': 错误}"""
    if 'adb' == command[:3] and device is not None:
        command = 'adb -s ' + device + ' ' + command.split('adb ', 1)[1]
    sp = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
    result, error = sp.communicate(timeout=120)  # 基础超时控制(避免卡死)
    output_str = result.decode('utf-8', errors='ignore').strip()
    error_str = error.decode('utf-8', errors='ignore').strip()
    if output:
        print(output_str if sp.returncode == 0 else error_str)
    return {'code': sp.returncode, 'output': output_str, 'error': error_str}

# 获取设备名称
def get_device_name():
    return shell('adb shell getprop ro.product.model')['output'].strip()

# 设置息屏时长
def set_screen_off_timeout(duration=9999999):
    shell(f'adb shell settings put system screen_off_timeout {duration}')


# 获取所有可运行应用包名
def get_all_runnable_packages():
    """
    简介:
        获取所有可运行的应用包名
    返回:
        :return: 可运行的应用包名列表
    """
    runnable_packages = set()
    pattern = re.compile(r'packageName=(.+)')
    adb_shell = ADB_SHELL()

    output_list = adb_shell.execute('cmd package query-activities -a android.intent.action.MAIN -c android.intent.category.LAUNCHER | grep packageName=')
    for line in output_list:
        match = pattern.search(line)
        if match:
            runnable_packages.add(match.group(1))

    adb_shell.exit()
    return list(runnable_packages)

# 授予应用权限
def grant_authority(package):
    """
    简介:
        授予应用权限
    参数:
        :param package: 应用包名
    返回:
        :return: 本次授权的权限列表
    """
    adb_shell = ADB_SHELL()
    pattern = re.compile(r'(android\.permission\..+):')

    ungranted_permissions_output = adb_shell.execute(f'dumpsys package {package} | grep granted=false')

    ungranted_permissions_list = set()
    for permission in ungranted_permissions_output:
        match = pattern.search(permission)
        if match:
            ungranted_permissions_list.add(match.group(1))

    ungranted_permissions_list = list(ungranted_permissions_list)

    for permission in ungranted_permissions_list:
        adb_shell.execute(f'pm grant {package} {permission}')

    adb_shell.exit()

    return ungranted_permissions_list

# 授予应用权限
def grant_all_apps_permissions(thread_max_workers=5):
    """
    简介:
        授予所有应用权限
    参数:
        :param thread_max_workers: 线程池子线程数量(子线程过多可能导致电脑死机)
    返回:
        :return: 授权的应用包名列表
    """
    all_packages_list = get_all_runnable_packages()
    print(f"已获取所有可运行应用包名,数量为:{len(all_packages_list)}")
    with ThreadPoolExecutor(max_workers=thread_max_workers) as executor:
        for package in all_packages_list:
            executor.submit(grant_authority, package)
    return all_packages_list

# 写入黑名单
def write_to_file():
    file_path = f'{os.path.dirname(os.path.realpath(__file__))}\\blacklist.txt'
    with open(file_path, "w+", encoding="utf-8") as f:
        for package in black_list:
            f.write(f"{package}\n")
    return file_path

def run_all_apps_monkey(test_time=10000000):
    if shell(f"adb push {write_to_file()} /sdcard/")["code"] != 0:
        raise print("monkey test blacklist push fail!")
    else:
        monkey_command = f'adb shell "monkey ' \
                         f'--pkg-blacklist-file /sdcard/blacklist.txt ' \
                         f'--throttle 500 ' \
                         f'--randomize-throttle ' \
                         f'--ignore-crashes ' \
                         f'--ignore-timeouts ' \
                         f'--ignore-security-exceptions ' \
                         f'--ignore-native-crashes ' \
                         f'--pct-touch 40 ' \
                         f'--pct-motion 30 ' \
                         f'--pct-trackball 5 ' \
                         f'--pct-syskeys 5 ' \
                         f'--pct-appswitch 5 ' \
                         f'--pct-rotation 5 ' \
                         f'-v -v -v {test_time} ' \
                         f'1>/sdcard/monkeyInfo.txt 2>/sdcard/monkeyError.txt &"'
        shell(monkey_command)
        result = shell(monkey_command)
        if result['code'] == 0:
            print('Monkey命令已发送!')
        else:
            print('Monkey命令发送失败!')
            print(result['error'])

if __name__ == '__main__':
    # 等待设备连接
    wait_for_device()
    # 应用授权
    grant_all_apps_permissions()
    # 运行所有应用monkey
    run_all_apps_monkey()
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容