├── po ├── __init__.py ├── getback_strategy.py └── integration.py ├── public ├── __init__.py ├── InstallApp.py ├── login.py └── Analyzelog.py ├── AI_Monkey.egg-info ├── dependency_links.txt ├── top_level.txt ├── requires.txt ├── PKG-INFO └── SOURCES.txt ├── conf └── lib │ ├── aapt │ ├── aapt.exe │ ├── monkey.jar │ ├── framework.jar │ └── max.config ├── lib ├── Utils.pyc ├── __init__.pyc ├── adbUtils.pyc ├── package.pyc ├── __init__.py ├── package.py ├── Utils.py └── adbUtils.py ├── dist └── AI_Monkey-1.0-py2.7.egg ├── setup.py ├── README.md └── run.py /po/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /AI_Monkey.egg-info/dependency_links.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /AI_Monkey.egg-info/top_level.txt: -------------------------------------------------------------------------------- 1 | lib 2 | po 3 | public 4 | -------------------------------------------------------------------------------- /conf/lib/aapt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/q88535448/AiMonkey/HEAD/conf/lib/aapt -------------------------------------------------------------------------------- /lib/Utils.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/q88535448/AiMonkey/HEAD/lib/Utils.pyc -------------------------------------------------------------------------------- /conf/lib/aapt.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/q88535448/AiMonkey/HEAD/conf/lib/aapt.exe -------------------------------------------------------------------------------- /lib/__init__.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/q88535448/AiMonkey/HEAD/lib/__init__.pyc -------------------------------------------------------------------------------- /lib/adbUtils.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/q88535448/AiMonkey/HEAD/lib/adbUtils.pyc -------------------------------------------------------------------------------- /lib/package.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/q88535448/AiMonkey/HEAD/lib/package.pyc -------------------------------------------------------------------------------- /conf/lib/monkey.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/q88535448/AiMonkey/HEAD/conf/lib/monkey.jar -------------------------------------------------------------------------------- /conf/lib/framework.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/q88535448/AiMonkey/HEAD/conf/lib/framework.jar -------------------------------------------------------------------------------- /conf/lib/max.config: -------------------------------------------------------------------------------- 1 | max.startAfterNSecondsofsleep = 10000 2 | max.wakeupAfterNSecondsofsleep = 6000 3 | -------------------------------------------------------------------------------- /AI_Monkey.egg-info/requires.txt: -------------------------------------------------------------------------------- 1 | pyyaml 2 | Appium-Python-Client 3 | selenium 4 | termcolor 5 | uiautomator 6 | -------------------------------------------------------------------------------- /dist/AI_Monkey-1.0-py2.7.egg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/q88535448/AiMonkey/HEAD/dist/AI_Monkey-1.0-py2.7.egg -------------------------------------------------------------------------------- /lib/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __author__ = 'xiaqing' 3 | 4 | """ 5 | @author:xiaqing 6 | @time: 18/3/13 上午10:13 7 | """ -------------------------------------------------------------------------------- /AI_Monkey.egg-info/PKG-INFO: -------------------------------------------------------------------------------- 1 | Metadata-Version: 1.0 2 | Name: AI-Monkey 3 | Version: 1.0 4 | Summary: UNKNOWN 5 | Home-page: UNKNOWN 6 | Author: xaiqing 7 | Author-email: xiaqing@cmcm.cosudom 8 | License: MIT 9 | Description: UNKNOWN 10 | Platform: UNKNOWN 11 | -------------------------------------------------------------------------------- /AI_Monkey.egg-info/SOURCES.txt: -------------------------------------------------------------------------------- 1 | setup.py 2 | AI_Monkey.egg-info/PKG-INFO 3 | AI_Monkey.egg-info/SOURCES.txt 4 | AI_Monkey.egg-info/dependency_links.txt 5 | AI_Monkey.egg-info/requires.txt 6 | AI_Monkey.egg-info/top_level.txt 7 | lib/Utils.py 8 | lib/__init__.py 9 | lib/adbUtils.py 10 | lib/package.py 11 | po/__init__.py 12 | po/getback_strategy.py 13 | po/integration.py 14 | public/Analyzelog.py 15 | public/InstallApp.py 16 | public/__init__.py 17 | public/login.py -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __author__ = 'xiaqing' 3 | 4 | """ 5 | @author:xiaqing 6 | @time: 16/11/16 下午3:25 7 | """ 8 | try: 9 | from setuptools import setup, find_packages 10 | except ImportError: 11 | from distutils.core import setup, find_packages 12 | 13 | setup( 14 | name='AI_Monkey', 15 | keywords='', 16 | version=1.0, 17 | packages=find_packages(), 18 | url='', 19 | license='MIT', 20 | author='xaiqing', 21 | author_email='xiaqing@cmcm.cosudom', 22 | description='', 23 | install_requires=[ 24 | 'pyyaml', 'Appium-Python-Client', 'selenium', 'termcolor', 'uiautomator', 'click' 25 | ] 26 | ) -------------------------------------------------------------------------------- /public/InstallApp.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import lib.adbUtils 3 | import re 4 | import lib.Utils as U 5 | __author__ = 'xiaqing' 6 | 7 | """ 8 | @author:xiaqing 9 | @time: 16/11/15 下午1:44 10 | """ 11 | 12 | 13 | class InstallApp: 14 | def __init__(self, serial, package): 15 | self.package = package 16 | self.serial = serial 17 | 18 | def run_install(self): 19 | adb = lib.adbUtils.ADB(self.serial) 20 | api_level = adb.get_api_level() 21 | if adb.is_install(self.package.name): 22 | adb.remove_app(self.package.name) 23 | 24 | opts = "-g " if int(api_level) > 22 else "" # grant all runtime permissions for api>=21 25 | U.Logging.info("start install app for %s" % self.serial) 26 | #process = adb.adb("-s %s install -r %s '%s'" % (self.serial, opts, self.package.apk_path)) 27 | process = adb.adb('install -r %s "%s"' % (opts, self.package.apk_path)) 28 | stdout, stderr = process.communicate() 29 | fails = re.findall(r"Failure\s[[][\w|\W]+[]]", stdout) 30 | if fails: 31 | U.Logging.error(fails) 32 | 33 | if adb.is_install(self.package.name): 34 | return True 35 | else: 36 | return False 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AiMonkey 2 | ## 简介 3 | AI Monkey项目是封装了目前火热的测试工具[Maxim](https://github.com/zhangzhao4444/Maxim)。主要修改点有如下几个 4 | * 解决了该工具偶尔进入页面长时间无法返回的问题。提高遍历效率 5 | * 封装了工具启动命令,简化启动方式 6 | * 封装了部分产品的登录方法(仅支持google+登录,请先在手机配置好) 7 | * 支持多设备并行,设备id空格分割 8 | 9 | ## 环境要求 10 | * mac linux windows 11 | * python2.7 12 | * adb 13 | 14 | ## 快速开始 15 | * git clone https://github.com/q88535448/AiMonkey.git 16 | * cd AiMonkey 17 | * python setup.py install 18 | 19 | ## 启动命令 20 | 不提供安装路径(确保已安装好应用并完成登录等操作),特别注意:在安装apk的时候务必加上-g命令默认授权,示例 adb -s xxxx install -g xxxxx.apk 21 | * python run.py run_monkey -s xxxxx(设备id) -p com.xxxx.xxxx(包名) --runningminutes 10(执行时间) 22 | 23 | 提供apk路径 24 | * python run.py run_monkey -s xxxxx --apk apkPath(apk绝对路径) --runningminutes 10 25 | 26 | 多终端同时测试 27 | * python run.py run_monkey -s xxxxx&xxxx --apk apkPath(apk绝对路径) --runningminutes 10 28 | 29 | 查看全部命令 30 | * python2 run.py run_monkey --help 31 | * --throttle 默认执行间隔700毫秒,太快程序扛不住,可自行设置 32 | 33 | ## 遍历模式 34 | * 模式 DFS:--uiautomatordfs 深度遍历模式,按照层级点击,会重复点击 35 | * 模式 Mix:--uiautomatormix 直接使用底层accessibiltyserver获取界面接口 解析各控件,随机选取一个控件执行touch操作。同时与原monkey 其他操作按比例混合使用默认accessibilityserver action占比70%,其余各action分剩余的30%,accessibilityserver action占比可配置 --pct-uiautomatormix n 36 | * 从测试效果看,Mix模式会优于DFS模式,经验之谈。 37 | 38 | ## 执行结果 39 | * 如果出现崩溃或anr程序会自动把日志抓出来保存到根目录logs/crash-dump.log,没有这个文件说明没有发生crash或anr 40 | * logcat.log是标准的androidlog,monkeyout.log是工具执行过程中产生的日志加monkey本身日志。 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /po/getback_strategy.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | 4 | import lib.adbUtils 5 | from time import sleep 6 | import datetime 7 | import threading 8 | import lib.Utils as U 9 | 10 | BLACK_ACTIVITY = ['cm.security.main.MainActivity'] 11 | 12 | 13 | 14 | class r(threading.Thread): 15 | def __init__(self, serial, runningminutes, throttle, package, man_talk_event): 16 | threading.Thread.__init__(self) 17 | self.serial = serial 18 | self.runningminutes = runningminutes 19 | self.throttle = float(throttle) / 1000 20 | self.package = package 21 | self.stop_event = man_talk_event 22 | 23 | def run(self): 24 | # 等待遍历程序启动 25 | sleep(15) 26 | act_diff = "" 27 | act_num = 0 28 | activity_dic = {} 29 | time_start = datetime.datetime.now() 30 | adb = lib.adbUtils.ADB(self.serial) 31 | 32 | while True: 33 | if self.stop_event.is_set(): 34 | U.Logging.warn("get back thread is quiting") 35 | break 36 | try: 37 | activity = adb.get_current_activity() 38 | except Exception as e: 39 | U.Logging.error(e) 40 | continue 41 | U.Logging.debug(activity) 42 | 43 | if activity in activity_dic: 44 | activity_dic[activity] += 1 45 | else: 46 | activity_dic[activity] = 1 47 | 48 | time_finish = datetime.datetime.now() 49 | during = (time_finish - time_start).seconds 50 | 51 | # 实际执行时间大于runningminutes时间则退出线程 52 | # if during > self.runningminutes * 60: 53 | # break 54 | 55 | sleep(self.throttle) 56 | # 每隔10s进行一次判断如果处于同一个activity则back 57 | if during % 10 == 0: 58 | if act_diff == activity and not activity in BLACK_ACTIVITY: 59 | U.Logging.warn("10s activity 未发生改变,get back") 60 | adb.send_key_event("4") 61 | else: 62 | act_diff = activity 63 | 64 | for act in activity_dic: 65 | U.Logging.info('%s %s' % (act, activity_dic[act])) 66 | 67 | if during % 300 == 0 and during != 0: 68 | if act_num < len(activity_dic): 69 | act_num = len(activity_dic) 70 | else: 71 | if self.package: 72 | U.Logging.warn("当前已遍历activity:%s 5分钟未增长,返回首页" % len(activity_dic)) 73 | adb.start_activity('%s/%s' % (self.package.name, self.package.activity)) 74 | -------------------------------------------------------------------------------- /run.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import sys 3 | 4 | __author__ = 'xiaqing' 5 | 6 | """ 7 | @author:xiaqing 8 | @time: 18/3/13 下午3:55 9 | """ 10 | import signal 11 | import click 12 | from po import integration 13 | import public.Analyzelog as l 14 | import lib.Utils as U 15 | 16 | 17 | def sigint_handler(signum, frame): 18 | global is_sigint_up 19 | is_sigint_up = True 20 | print 'catched interrupt signal!' 21 | 22 | 23 | signal.signal(signal.SIGINT, sigint_handler) 24 | is_sigint_up = False 25 | 26 | 27 | @click.group() 28 | def cli(): 29 | pass 30 | 31 | 32 | @cli.command() 33 | @click.option('-s', default="", prompt=u'-s 请指定一个或多个测试设备', help=u"设备id,支持多个设备同时,&分割") 34 | @click.option('-p', default="", help=u"测试app的包名,-p与--apk至少要填写一个参数") 35 | @click.option('--apk', default="", help=u"测试app的apk绝对路径,不填写则直接开始测试,请确保已经安装好app并进行登录") 36 | @click.option('--mode', type=click.Choice(['uiautomatormix', 'uiautomatordfs']), default="uiautomatormix", 37 | help=u"uiautomatormix是随机点击控件加monkey模式,uiautomatordfs是遍历控件模式") 38 | @click.option('--runningminutes', default="1", help=u"执行时间") 39 | @click.option('--throttle', default="600", help=u"点击间隔") 40 | @click.option('--pctuiautomatormix', default="70", help=u"仅仅只有uiautomatormix模式需要填写uiautomator和monkey的比例") 41 | def run_monkey(s, p, apk, mode, runningminutes, throttle, pctuiautomatormix): 42 | processdic = {} 43 | try: 44 | l.ProjectLog.set_up() 45 | if p or apk: 46 | snlist = s.split('&') 47 | for serial in snlist: 48 | ir = integration.RunMonkey(serial, p, apk, mode, runningminutes, throttle, pctuiautomatormix) 49 | # 成功启动再记录对象 50 | if ir.run(): 51 | processdic[serial] = ir 52 | 53 | for serial in processdic: 54 | processdic[serial].process.wait() 55 | processdic[serial].dl.check() 56 | 57 | # 停止get back守护线程 58 | processdic[serial].man_talk_event.set() 59 | 60 | # 如果提供了apk路径就卸载app 61 | if apk: 62 | processdic[serial].adb.quit_app(processdic[serial].pkgname) 63 | processdic[serial].adb.remove_app(processdic[serial].pkgname) 64 | 65 | l.ProjectLog.tear_down() 66 | 67 | else: 68 | U.Logging.error(u"-p与--apk至少要填写一个参数") 69 | 70 | except Exception, e: 71 | # 异常退出保存log 72 | U.Logging.error(e) 73 | for serial in processdic: 74 | processdic[serial].dl.check() 75 | l.ProjectLog.tear_down() 76 | 77 | 78 | if __name__ == '__main__': 79 | cli(sys.argv[1:]) 80 | -------------------------------------------------------------------------------- /lib/package.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import subprocess 5 | import os 6 | import re 7 | import lib.Utils as U 8 | import platform 9 | 10 | # 判断系统类型,windows使用findstr,linux使用grep 11 | system = platform.system() 12 | if system is "Windows": 13 | find_util = "findstr" 14 | aapt = 'conf\\lib\\aapt.exe' 15 | else: 16 | find_util = "grep" 17 | aapt = 'conf/lib/aapt' 18 | 19 | class Package: 20 | def __init__(self, apk_path): 21 | self.apk_path = apk_path 22 | self.name = "" 23 | self.activity = "" 24 | self.version_code = "" 25 | self.boolpkg = self.__get_package() 26 | 27 | def __get_package(self): 28 | # 判断是否有apk,如果没有则说明下载异常返回false 29 | if self.apk_path.endswith("apk") and os.path.exists(self.apk_path): 30 | if self.__set_pkg_info(): 31 | return True 32 | else: 33 | return False 34 | else: 35 | return False 36 | 37 | 38 | def __set_pkg_info(self): 39 | # 获取文件名 40 | self.apk_name = os.path.basename(self.apk_path) 41 | aaptpath = os.path.join(os.path.abspath(os.path.join(os.getcwd())),aapt) 42 | 43 | # 获取包名 44 | cmd = '{} dump badging "{}" | {} package'.format(aaptpath, self.apk_path, find_util) 45 | process = U.cmd(cmd) 46 | stdout, stderr = process.communicate() 47 | if stdout is None: 48 | U.Logging.error("[pkg_info] time out: {}".format(cmd)) 49 | elif "ERROR" in stderr or "error" in stderr: 50 | U.Logging.error("[pkg_info] cannot execute: {}".format(cmd)) 51 | U.Logging.error("[pkg_info] result: {}".format(stderr)) 52 | else: 53 | try: 54 | package_name = re.findall(r"name='([a-zA-Z0-9.*]+)'", stdout) 55 | self.name = package_name[0] 56 | self.version_code = re.findall(r"versionCode='([0-9]+)'", stdout)[0] 57 | except Exception as e: 58 | U.Logging.error("[pkg_info] failed to regex package name from {}. {}".format(stdout, e)) 59 | # 获取启动Activity 60 | cmd = '{} dump badging "{}" | {} launchable-activity'.format(aaptpath, self.apk_path, find_util) 61 | process = U.cmd(cmd) 62 | stdout, stderr = process.communicate() 63 | if stdout is None: 64 | U.Logging.error("[pkg_info] time out: {}".format(cmd)) 65 | elif "ERROR" in stderr or "error" in stderr: 66 | U.Logging.error("[pkg_info] cannot execute: {}".format(cmd)) 67 | U.Logging.error("[pkg_info] result: {}".format(stderr)) 68 | else: 69 | try: 70 | activity_list = re.findall(r"name='(.+?)'", stdout) 71 | main_activity = "" 72 | for activity in activity_list: 73 | if not activity.startswith("com.squareup") and not activity.startswith("com.github"): 74 | main_activity = activity 75 | break 76 | self.activity = main_activity 77 | except Exception as e: 78 | U.Logging.error("[pkg_info] failed to regex main activity from {}. {}".format(stdout, e)) 79 | 80 | if self.name and self.activity: 81 | return True 82 | 83 | return False 84 | 85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /public/login.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from uiautomator import Device 4 | from lib.adbUtils import ADB 5 | import lib.Utils as U 6 | import time 7 | __author__ = 'xiaqing' 8 | 9 | """ 10 | @author:xiaqing 11 | @time: 16/11/15 下午1:44 12 | """ 13 | 14 | def click_premission(d): 15 | alerts = ['ALLOW', 'SKIP', 'OK', 'NEXT TIME','YES'] 16 | num = 3 17 | while num > 0: 18 | for alert in alerts: 19 | if d(text=alert).exists: 20 | d(text=alert).click() 21 | num -= 1 22 | 23 | class LoginApp: 24 | def __init__(self, serial, package): 25 | self.serial = serial 26 | self.package = package 27 | self.adb = ADB(serial) 28 | 29 | def login_app(self): 30 | d = Device('{}'.format(self.serial)) 31 | component = "%s/%s" % (self.package.name, self.package.activity) 32 | self.adb.start_activity(component) 33 | time.sleep(15) 34 | 35 | try: 36 | if self.package.name == "com.cmcm.shorts": 37 | click_premission(d) 38 | d(resourceId="com.cmcm.shorts:id/home_bottom_user").click() 39 | d(resourceId="com.cmcm.shorts:id/layout_login_second").click() 40 | click_premission(d) 41 | # d(text="Guoliang Ren").click() 42 | d(resourceId="com.google.android.gms:id/account_name").click() 43 | time.sleep(15) 44 | click_premission(d) 45 | d.press.home() 46 | elif self.package.name == "com.cmcm.live": 47 | click_premission(d) 48 | d(resourceId="com.cmcm.live:id/layout_login_fifth").click() 49 | d(resourceId="com.cmcm.live:id/id_google_plus").click() 50 | click_premission(d) 51 | # d(text="Guoliang Ren").click() 52 | d(resourceId="com.google.android.gms:id/account_name").click() 53 | time.sleep(20) 54 | click_premission(d) 55 | d.press.home() 56 | elif self.package.name == "panda.keyboard.emoji.theme": 57 | d.click(770, 2100) 58 | time.sleep(2) 59 | d.press.back() 60 | time.sleep(2) 61 | d(text=' Cheetah Keyboard ❤ ❤ ❤ ').click() 62 | d(text='OK').click() 63 | d(text='OK').click() 64 | time.sleep(5) 65 | d.click(770, 2100) 66 | d(text=' Cheetah Keyboard ❤ ❤ ❤ ').click() 67 | time.sleep(5) 68 | click_premission(d) 69 | d.press.back() 70 | d.press.back() 71 | d.press.back() 72 | elif self.package.name == 'com.ksmobile.launcher': 73 | self.adb.quit_app(self.package.name) 74 | time.sleep(3) 75 | self.adb.start_activity(component) 76 | time.sleep(3) 77 | self.adb.send_key_event(3) 78 | d(resourceId="android:id/title").click() 79 | time.sleep(3) 80 | d(text="CM Launcher").click() 81 | time.sleep(3) 82 | d(resourceId="android:id/button_always").click() 83 | else: 84 | U.Logging.info("Don't need login") 85 | 86 | self.adb.quit_app('com.github.uiautomator') 87 | return True 88 | except Exception as e: 89 | U.Logging.error(e) 90 | U.Logging.error("login failed please check") 91 | return False 92 | 93 | 94 | if __name__ == '__main__': 95 | import lib.package 96 | package = lib.package.Package('/Users/xiaqing/PycharmProjects/PrivateCloudAutotestPlatform/apks/Launcher/launcher_2018-03-09_17-59-42_r_v52210_official%28no_channel%29_resguard.apk') 97 | package.get_package() 98 | login = LoginApp('0623ea5a00609f1f', package) 99 | login.login_app() -------------------------------------------------------------------------------- /po/integration.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import threading 3 | 4 | from po import getback_strategy 5 | import lib.Utils as U 6 | import subprocess 7 | import public.Analyzelog as l 8 | import lib.package as pkg 9 | from public.InstallApp import InstallApp 10 | from public.login import LoginApp 11 | from lib.adbUtils import ADB 12 | from lib.adbUtils import system 13 | 14 | __author__ = 'xiaqing' 15 | 16 | """ 17 | @author:xiaqing 18 | @time: 16/11/15 下午1:44 19 | """ 20 | 21 | 22 | class RunMonkey(object): 23 | def __init__(self, serial, pkgname, apk_path, mode, runningminutes, throttle, pctuiautomatormix): 24 | self.serial = serial 25 | self.apk_path = apk_path 26 | self.mode = mode 27 | self.adb = ADB(serial) 28 | self.runningminutes = runningminutes 29 | self.throttle = throttle 30 | self.pctuiautomatormix = pctuiautomatormix 31 | self.process = None 32 | if apk_path: 33 | self.package = pkg.Package(self.apk_path) 34 | self.pkgname = self.package.name 35 | else: 36 | self.pkgname = pkgname 37 | self.package = None 38 | 39 | self.dl = l.DeviceLog(serial, self.pkgname) 40 | 41 | 42 | def __del__(self): 43 | # windows adb不会释放logc.log需要强制释放一下 44 | if system is "Windows": 45 | U.cmd("taskkill /im adb.exe /f") 46 | 47 | def __start_back_strategy(self): 48 | U.Logging.info("start the thread of getback_strategy") 49 | self.man_talk_event = threading.Event() 50 | test_run = getback_strategy.r(self.serial, int(self.runningminutes), self.throttle, self.package, self.man_talk_event) 51 | test_run.start() 52 | 53 | def __start_new_monkey(self): 54 | U.Logging.info("run the AI monkey cmd") 55 | if self.mode == '--uiautomatormix': 56 | cmd = 'adb -s %s shell "CLASSPATH=/sdcard/monkey.jar:/sdcard/framework.jar exec app_process /system/bin tv.panda.test.monkey.Monkey ' \ 57 | '-p %s --%s --running-minutes %s --pct-uiautomatormix %s --ignore-crashes --ignore-timeouts --throttle %s -v -v -v -v > /sdcard/monkeyout.txt 2>/sdcard/monkeyerr.txt" ' % ( 58 | self.serial, self.pkgname, self.mode, self.runningminutes, self.pctuiautomatormix, self.throttle) 59 | else: 60 | cmd = 'adb -s %s shell "CLASSPATH=/sdcard/monkey.jar:/sdcard/framework.jar exec app_process /system/bin tv.panda.test.monkey.Monkey ' \ 61 | '-p %s --%s --running-minutes %s --ignore-crashes --ignore-timeouts --throttle %s -v -v -v -v > /sdcard/monkeyout.txt 2>/sdcard/monkeyerr.txt"' % ( 62 | self.serial, self.pkgname, self.mode, self.runningminutes, self.throttle) 63 | U.Logging.info(cmd) 64 | process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 65 | 66 | U.Logging.info("waiting for 10s") 67 | return process 68 | 69 | def __initialization_arrangement(self): 70 | #初始化log 71 | U.Logging.info("init logs in device %s" % self.serial) 72 | self.dl.init() 73 | al = l.Al(self.serial) 74 | al.main(self.dl.log_path) 75 | 76 | # 推送必要的jar包和配置文件到手机 77 | U.Logging.info("push the monkey jars to %s" % self.serial) 78 | process = self.adb.adb('push conf/lib/framework.jar /sdcard/') 79 | stdout, stderr = process.communicate() 80 | if 'error' in stdout: 81 | U.Logging.error(stdout) 82 | return False 83 | self.adb.adb('push conf/lib/monkey.jar /sdcard/') 84 | self.adb.adb('push conf/lib/max.config /sdcard/') 85 | self.adb.adb('shell rm /sdcard/crash-dump.log') 86 | 87 | return True 88 | 89 | 90 | def __install_app(self): 91 | if self.apk_path: 92 | if self.package.boolpkg: 93 | install = InstallApp(self.serial, self.package) 94 | login = LoginApp(self.serial, self.package) 95 | return install.run_install() and login.login_app() 96 | else: 97 | U.Logging.error('get package name failed and skip') 98 | return False 99 | else: 100 | U.Logging.info("apk_path is null so start app only") 101 | return True 102 | 103 | """ 104 | 启动遍历程序过程 105 | 执行步骤: 106 | 1:安装应用 107 | 2:登录 108 | 3:开启back逻辑 109 | 4:执行遍历命令 110 | :return: 111 | """ 112 | 113 | def run(self): 114 | if self.__initialization_arrangement() and self.__install_app(): 115 | self.__start_back_strategy() 116 | self.process = self.__start_new_monkey() 117 | return True 118 | else: 119 | U.Logging.error("install failed skip other process") 120 | return False 121 | -------------------------------------------------------------------------------- /lib/Utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __author__ = 'joko' 3 | 4 | """ 5 | @author:joko 6 | @time: 16/11/8 下午2:52 7 | """ 8 | import time 9 | import subprocess 10 | import os 11 | import sys 12 | import ConfigParser 13 | import sqlite3 14 | import re 15 | 16 | 17 | def get_now_time(): 18 | return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time())) 19 | 20 | 21 | def sleep(s): 22 | return time.sleep(s) 23 | 24 | 25 | def cmd(cmd): 26 | return subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 27 | 28 | 29 | class ConfigIni(): 30 | def __init__(self): 31 | self.current_directory = os.path.split( 32 | os.path.realpath(sys.argv[0]))[0] 33 | self.path = os.path.split(__file__)[0].replace('lib','data/test_info.ini') 34 | self.cf = ConfigParser.ConfigParser() 35 | 36 | self.cf.read(self.path) 37 | 38 | def get_ini(self, title, value): 39 | return self.cf.get(title, value) 40 | 41 | def set_ini(self, title, value, text): 42 | self.cf.set(title, value, text) 43 | return self.cf.write(open(self.path, "wb")) 44 | 45 | def add_ini(self, title): 46 | self.cf.add_section(title) 47 | return self.cf.write(open(self.path)) 48 | 49 | def get_options(self, data): 50 | # 获取所有的section 51 | options = self.cf.options(data) 52 | return options 53 | 54 | 55 | class colour: 56 | @staticmethod 57 | def c(msg, colour): 58 | try: 59 | from termcolor import colored, cprint 60 | p = lambda x: cprint(x, '%s' % colour) 61 | return p(msg) 62 | except: 63 | print (msg) 64 | 65 | @staticmethod 66 | def show_verbose(msg): 67 | colour.c(msg, 'grey') 68 | 69 | @staticmethod 70 | def show_debug(msg): 71 | colour.c(msg, 'white') 72 | 73 | @staticmethod 74 | def show_info(msg): 75 | colour.c(msg, 'green') 76 | 77 | @staticmethod 78 | def show_warn(msg): 79 | colour.c(msg, 'yellow') 80 | 81 | @staticmethod 82 | def show_error(msg): 83 | colour.c(msg, 'red') 84 | 85 | 86 | class Logging: 87 | flag = True 88 | 89 | @staticmethod 90 | def error(msg): 91 | if Logging.flag == True: 92 | # print get_now_time() + " [Error]:" + "".join(msg) 93 | colour.show_error(get_now_time() + " [Error]:" + "".join(str(msg))) 94 | 95 | @staticmethod 96 | def warn(msg): 97 | if Logging.flag == True: 98 | colour.show_warn(get_now_time() + " [Warn]:" + "".join(msg)) 99 | 100 | @staticmethod 101 | def info(msg): 102 | if Logging.flag == True: 103 | colour.show_info(get_now_time() + " [Info]:" + "".join(msg)) 104 | 105 | @staticmethod 106 | def debug(msg): 107 | if Logging.flag == True: 108 | colour.show_debug(get_now_time() + " [Debug]:" + "".join(msg)) 109 | 110 | @staticmethod 111 | def success(msg): 112 | if Logging.flag == True: 113 | colour.show_verbose(get_now_time() + " [Success]:" + "".join(msg)) 114 | 115 | 116 | def l(): 117 | """ 118 | 打印log 119 | 文件名+函数名,return 120 | :return: 121 | """ 122 | 123 | def log(func): 124 | def wrapper(*args, **kwargs): 125 | t = func(*args, **kwargs) 126 | filename = str(sys.argv[0]).split('/')[-1].split('.')[0] 127 | Logging.success('{}:{}, return:{}'.format(filename, func.__name__, t)) 128 | return t 129 | 130 | return wrapper 131 | 132 | return log 133 | 134 | 135 | class Asql: 136 | def __init__(self, ): 137 | ini = ConfigIni() 138 | test_db_path = ini.get_ini('test_db', 'test_result') 139 | self.conn = sqlite3.connect(test_db_path) 140 | self.cursor = self.conn.cursor() 141 | self.__is_table() 142 | 143 | def execute(self, *args, **kwargs): 144 | """ 145 | 146 | :param args: 147 | :param kwargs: 148 | :return: 提交数据 149 | """ 150 | self.cursor.execute(*args, **kwargs) 151 | 152 | def close(self): 153 | self.cursor.close() 154 | self.conn.commit() 155 | self.conn.close() 156 | 157 | def __is_table(self): 158 | """ 159 | 判断表是否存在 160 | :return: 161 | """ 162 | self.cursor.execute("SELECT count(*) FROM sqlite_master WHERE type='table' AND name='test_results'") 163 | row = self.cursor.fetchone() 164 | if row[0] != 1: 165 | self.__built_table() 166 | 167 | def __built_table(self): 168 | """ 169 | 建表 170 | :return: 171 | """ 172 | self.execute(""" 173 | CREATE TABLE test_results 174 | ( 175 | case_id INTEGER PRIMARY KEY, 176 | case_name TEXT, 177 | device_name TEXT, 178 | cpu_list TEXT, 179 | mem_list TEXT, 180 | execution_status TEXT, 181 | created_time DATETIME DEFAULT (datetime('now', 'localtime')) 182 | );""") 183 | 184 | def insert_per(self, case_name, device_name, cpu_list, mem_list, execution_status, ): 185 | key = "(case_name,device_name,cpu_list,mem_list,execution_status,created_time)" 186 | values = "('{}','{}','{}','{}','{}','{}')" \ 187 | .format(case_name, device_name, cpu_list, mem_list, execution_status, get_now_time()) 188 | self.execute("INSERT INTO test_results {} VALUES {}".format(key, values)) 189 | 190 | def select_per(self, case_name, device_name): 191 | statement = "select * from test_results where " \ 192 | "case_name = '{}' " \ 193 | "and " \ 194 | "device_name = '{}' " \ 195 | "and " \ 196 | "execution_status = 1 " \ 197 | "order by created_time desc".format(case_name, device_name) 198 | self.cursor.execute(statement) 199 | row = self.cursor.fetchone() 200 | if row is not None: 201 | cpu = re.findall(r"\d+\.?\d*", row[3]) 202 | mem = re.findall(r"\d+\.?\d*", row[4]) 203 | return [int(i) for i in cpu], [int(i) for i in mem] 204 | else: 205 | return None 206 | 207 | 208 | if __name__ == '__main__': 209 | a = Asql() 210 | print (a.select_per('login1', 'sanxing')) 211 | a.close() 212 | -------------------------------------------------------------------------------- /public/Analyzelog.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import filecmp 4 | import re 5 | import shutil 6 | import datetime 7 | import os 8 | import lib.adbUtils 9 | import time 10 | import lib.Utils as U 11 | 12 | __author__ = 'xiaqing' 13 | 14 | """ 15 | @author:xiaqing 16 | @time: 16/11/15 下午1:44 17 | """ 18 | 19 | 20 | 21 | LOG_ROOT = "logs" 22 | HISTORY_ROOT = "history_logs" 23 | 24 | 25 | class Al: 26 | def __init__(self, device): 27 | self.device = device 28 | self.log_path = os.path.join(LOG_ROOT,"logcat.log") 29 | 30 | def _get_android_log(self, log_path): 31 | """ 32 | 33 | :return:清理当前设备缓存log,并且记录当前设备log 34 | """ 35 | 36 | adb = lib.adbUtils.ADB(self.device) 37 | adb.c_logcat() 38 | adb.logcat(log_path) 39 | 40 | def main(self, log_path): 41 | """ 42 | :return: 开启记录log 43 | """ 44 | return self._get_android_log(log_path) 45 | 46 | 47 | class ProjectLog: 48 | def __init__(self): 49 | self.log_root = LOG_ROOT 50 | #self.log_path = "{}/{}".format(LOG_ROOT, "log.txt") 51 | self.log_path = os.path.join(LOG_ROOT, "log.txt") 52 | self.history_root = HISTORY_ROOT 53 | 54 | @staticmethod 55 | def set_up(): 56 | # 清理之前的项目日志目录 57 | if os.path.exists(LOG_ROOT): 58 | shutil.rmtree(LOG_ROOT) 59 | # 创建新的项目日志目录 60 | os.makedirs(LOG_ROOT) 61 | 62 | # 初始化logging 63 | # logging.basicConfig( 64 | # level=logging.INFO, 65 | # format="%(asctime)s %(levelname)s\t[%(threadName)s] %(message)s", 66 | # datefmt="%Y-%m-%d %H:%M:%S", 67 | # filename=self.log_path, 68 | # filemode="w" 69 | # ) 70 | 71 | @staticmethod 72 | def tear_down(): 73 | # 创建历史结果目录 74 | if not os.path.exists(HISTORY_ROOT): 75 | os.makedirs(HISTORY_ROOT) 76 | # 将本次结果目录复制到历史结果目录 77 | now = datetime.datetime.now().strftime("%Y%m%d%H%M%S") 78 | dest_dir = os.path.join(HISTORY_ROOT, now) 79 | shutil.copytree(LOG_ROOT, dest_dir) 80 | 81 | 82 | class DeviceLog: 83 | def __init__(self, sn, pkgname): 84 | self.sn = sn 85 | self.pkgname = pkgname 86 | ##如果STF设备名称中有":",建立日志文件时windows会不支持,统一处理为不带特殊字符的 87 | self.device_root = os.path.join(LOG_ROOT, self.sn.replace(":","_")) 88 | self.anr_dir = os.path.join(self.device_root, "anr") 89 | self.crash_dir = os.path.join(self.device_root, "crash") 90 | self.dump_dir = os.path.join(self.device_root, "dumpsys") 91 | self.log_path = os.path.join(self.device_root, "logcat.log") 92 | self.monkeyout = os.path.join(self.device_root, "monkeyout.log") 93 | self.monkeyerr = os.path.join(self.device_root, "monkeyerr.log") 94 | self.crashlog = os.path.join(self.device_root, "dump-crash.log") 95 | 96 | # self.log_path = "/home/ren/monkey.log" 97 | 98 | def init(self): 99 | # 清理device之前的日志目录 100 | if os.path.exists(self.device_root): 101 | shutil.rmtree(self.device_root) 102 | # 创建device所需的日志目录 103 | os.makedirs(self.device_root) 104 | os.makedirs(self.anr_dir) 105 | os.makedirs(self.crash_dir) 106 | os.makedirs(self.dump_dir) 107 | 108 | def __get_logs(self): 109 | adb = lib.adbUtils.ADB(self.sn) 110 | adb.adb("pull /sdcard/monkeyout.txt %s" % self.monkeyout) 111 | adb.adb("pull /sdcard/monkeyerr.txt %s" % self.monkeyerr) 112 | adb.adb("pull /sdcard/crash-dump.log %s" % self.crashlog) 113 | time.sleep(5) 114 | 115 | @staticmethod 116 | def __remove_excess_traces(anr_info): 117 | # 获取PID 118 | pid = 0 119 | for line in anr_info: 120 | if line.startswith("PID: "): 121 | pid = re.findall(r"PID: (\d+)", line)[0] 122 | break 123 | # 获取anr traces起始、末尾行,对应pid的起始、末尾行: 124 | trace_start = 0 125 | trace_end = 0 126 | trace_pid_start = 0 127 | trace_pid_end = 0 128 | for i in range(len(anr_info)): 129 | line = anr_info[i] 130 | if "----- pid " in line and trace_start == 0: 131 | trace_start = i 132 | if "----- end " in line: 133 | trace_end = i 134 | if "----- pid %s " % pid in line and trace_pid_start == 0: 135 | trace_pid_start = i 136 | if "----- end %s " % pid in line and trace_pid_end == 0: 137 | trace_pid_end = i 138 | # 如果起始、末尾行有问题,则不处理 139 | if not (0 < trace_start <= trace_pid_start < trace_pid_end <= trace_end or 140 | (trace_pid_start == trace_pid_end == 0 < trace_start < trace_end)): 141 | return anr_info 142 | # 处理保留信息 143 | anr_store = [] 144 | for i in range(len(anr_info)): 145 | line = anr_info[i] 146 | if i < trace_start or i > trace_end or trace_pid_start <= i <= trace_pid_end: 147 | anr_store.append(line) 148 | return anr_store 149 | 150 | def check(self): 151 | #先取log在分析 152 | self.__get_logs() 153 | if not (os.path.exists(self.monkeyout) and os.path.exists(self.monkeyerr)): 154 | U.Logging.error("missing the log of %s" % self.monkeyout) 155 | return False 156 | 157 | time.sleep(3) 158 | with open(self.monkeyout, "r") as fp: 159 | # 判断文件行是否为anr或crash,如果是则做相关处理 160 | is_anr = 0 161 | is_crash = False 162 | # anr和crash计数 163 | anr_cnt = 0 164 | crash_cnt = 0 165 | # anr和crash信息 166 | anr_info = [] 167 | crash_info = [] 168 | # 逐行读取日志信息 169 | for line in fp: 170 | # ANR处理 171 | if line.startswith("// NOT RESPONDING: {} ".format(self.pkgname)): 172 | if is_anr == 0: 173 | anr_cnt += 1 174 | is_anr += 1 175 | if is_anr != 0: 176 | anr_info.append(line) 177 | if is_anr != 0 and line.strip() == "// meminfo status was 0": 178 | is_anr -= 1 179 | if is_anr == 0: 180 | # 去掉多余的traces 181 | anr_info = self.__remove_excess_traces(anr_info) 182 | # 存成文件 183 | filename = os.path.join(self.anr_dir, "anr_{}_{}.txt".format(self.sn, anr_cnt)) 184 | with open(filename, "w") as anr_fp: 185 | for anr_line in anr_info: 186 | anr_fp.write(anr_line) 187 | # 清空 188 | anr_info = [] 189 | # CRASH处理 190 | if line.startswith("// CRASH: {} ".format(self.pkgname)) or line.startswith( 191 | "// CRASH: {}:".format(self.pkgname)): 192 | is_crash = True 193 | crash_cnt += 1 194 | if is_crash: 195 | crash_info.append(line) 196 | if is_crash and line.strip() == "//": 197 | # 存成文件 198 | filename = os.path.join(self.crash_dir, "crash_{}_{}.txt".format(self.sn, crash_cnt)) 199 | with open(filename, "w") as crash_fp: 200 | for crash_line in crash_info[1:]: 201 | crash_fp.write(crash_line) 202 | U.Logging.error(crash_line) 203 | # 清空 204 | crash_info = [] 205 | is_crash = False 206 | 207 | with open(self.monkeyerr, "r") as fp: 208 | for line in fp: 209 | U.Logging.error(line) 210 | 211 | @staticmethod 212 | def __numerical_sort(value): 213 | numbers = re.compile(r"(\d+)") 214 | parts = numbers.split(value) 215 | parts[1::2] = map(int, parts[1::2]) 216 | return parts 217 | 218 | def get(self): 219 | # 获取anr、crash、dumpsys 220 | anr_fn_list = os.listdir(self.anr_dir) 221 | anr_fn_list = sorted(anr_fn_list, key=self.__numerical_sort, reverse=False) 222 | anr_cnt = len(anr_fn_list) 223 | crash_fn_list = os.listdir(self.crash_dir) 224 | crash_fn_list = sorted(crash_fn_list, key=self.__numerical_sort, reverse=False) 225 | crash_cnt = len(crash_fn_list) 226 | dumpsys_fn_list = os.listdir(self.dump_dir) 227 | # 将anr、crash、dumpsys写入附件list 228 | att_list = [] 229 | anr_dic = {} 230 | for fn_add in anr_fn_list: 231 | flag = True 232 | for fn in att_list: 233 | #if filecmp.cmp(self.anr_dir + "/" + fn_add, fn): 234 | if filecmp.cmp(os.path.join(self.anr_dir, fn_add), fn): 235 | fn_name = fn.split('/')[-1] 236 | anr_dic[fn_name] = anr_dic[fn_name] + 1 237 | flag = False 238 | break 239 | if flag: 240 | anr_dic[fn_add] = 1 241 | att_list.append(os.path.join(self.anr_dir, fn_add)) 242 | # for fn in anr_fn_list: 243 | # att_list.append(os.path.join(self.anr_dir, fn)) 244 | crash_dic = {} 245 | for fn_add in crash_fn_list: 246 | flag = True 247 | for fn in att_list: 248 | print filecmp.cmp(self.crash_dir + "/" + fn_add, fn) 249 | if filecmp.cmp(self.crash_dir + "/" + fn_add, fn): 250 | fn_name = fn.split('/')[-1] 251 | crash_dic[fn_name] = crash_dic[fn_name] + 1 252 | flag = False 253 | break 254 | if flag: 255 | crash_dic[fn_add] = 1 256 | 257 | f = open(os.path.join(self.crash_dir, fn_add), 'r') 258 | firstline = f.readline() 259 | f.close() 260 | pa = re.compile(r"leakcanary") 261 | if not pa.match(firstline): 262 | att_list.append(os.path.join(self.crash_dir, fn_add)) 263 | print crash_dic, att_list 264 | # for fn in crash_fn_list: 265 | # att_list.append(os.path.join(self.crash_dir, fn)) 266 | for fn in dumpsys_fn_list: 267 | att_list.append(os.path.join(self.dump_dir, fn)) 268 | # 返回anr_cnt、crash_cnt和att_list 269 | return anr_cnt, crash_cnt, att_list, anr_dic, crash_dic 270 | 271 | 272 | if __name__ == '__main__': 273 | log = DeviceLog('0623ea5a00609f1f') 274 | log.check("com.cmcm.shorts") -------------------------------------------------------------------------------- /lib/adbUtils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __author__ = 'joko' 3 | 4 | """ 5 | @author:joko 6 | @time: 16/11/8 下午2:52 7 | """ 8 | import platform 9 | import subprocess 10 | import re 11 | from time import sleep 12 | import time 13 | import os 14 | import random 15 | 16 | PATH = lambda p: os.path.abspath(p) 17 | 18 | # 判断系统类型,windows使用findstr,linux使用grep 19 | system = platform.system() 20 | if system is "Windows": 21 | find_util = "findstr" 22 | else: 23 | find_util = "grep" 24 | 25 | # 判断是否设置环境变量ANDROID_HOME 26 | # if "ANDROID_HOME" in os.environ: 27 | # if system == "Windows": 28 | # command = os.path.join( 29 | # os.environ["ANDROID_HOME"], 30 | # "platform-tools", 31 | # "adb.exe") 32 | # else: 33 | # command = os.path.join( 34 | # os.environ["ANDROID_HOME"], 35 | # "platform-tools", 36 | # "adb") 37 | # else: 38 | # raise EnvironmentError( 39 | # "Adb not found in $ANDROID_HOME path: %s." % 40 | # os.environ["ANDROID_HOME"]) 41 | 42 | 43 | 44 | class ADB(object): 45 | """ 46 | 单个设备,可不传入参数device_id 47 | """ 48 | 49 | def __init__(self, device_id=""): 50 | if device_id == "": 51 | self.device_id = "" 52 | else: 53 | self.device_id = "-s %s" % device_id 54 | 55 | def adb(self, args): 56 | cmd = "%s %s %s" % ('adb', self.device_id, str(args)) 57 | return subprocess.Popen( 58 | cmd, 59 | shell=True, 60 | stdout=subprocess.PIPE, 61 | stderr=subprocess.PIPE) 62 | 63 | def shell(self, args): 64 | cmd = "%s %s shell %s" % ('adb', self.device_id, str(args),) 65 | return subprocess.Popen( 66 | cmd, 67 | shell=True, 68 | stdout=subprocess.PIPE, 69 | stderr=subprocess.PIPE) 70 | 71 | def get_device_state(self): 72 | """ 73 | 获取设备状态: offline | bootloader | device 74 | """ 75 | return self.adb("get-state").stdout.read().strip() 76 | 77 | def get_device_id(self): 78 | """ 79 | 获取设备id号,return serialNo 80 | """ 81 | return self.adb("get-serialno").stdout.read().strip() 82 | 83 | def get_android_version(self): 84 | """ 85 | 获取设备中的Android版本号,如4.2.2 86 | """ 87 | return self.shell( 88 | "getprop ro.build.version.release").stdout.read().strip() 89 | 90 | def get_sdk_version(self): 91 | """ 92 | 获取设备SDK版本号 93 | """ 94 | return self.shell("getprop ro.build.version.sdk").stdout.read().strip() 95 | 96 | def get_device_model(self): 97 | """ 98 | 获取设备型号 99 | """ 100 | return self.shell("getprop ro.product.model").stdout.read().strip() 101 | 102 | def get_pid(self, package_name): 103 | """ 104 | 获取进程pid 105 | args: 106 | - packageName -: 应用包名 107 | usage: getPid("com.android.settings") 108 | """ 109 | if system is "Windows": 110 | pidinfo = self.shell( 111 | "ps | findstr %s$" % 112 | package_name).stdout.read() 113 | else: 114 | pidinfo = self.shell( 115 | "ps | %s -w %s" % 116 | (find_util, package_name)).stdout.read() 117 | 118 | if pidinfo == '': 119 | return "the process doesn't exist." 120 | 121 | pattern = re.compile(r"\d+") 122 | result = pidinfo.split(" ") 123 | result.remove(result[0]) 124 | 125 | return pattern.findall(" ".join(result))[0] 126 | 127 | def kill_process(self, pid): 128 | """ 129 | 杀死应用进程 130 | args: 131 | - pid -: 进程pid值 132 | usage: killProcess(154) 133 | 注:杀死系统应用进程需要root权限 134 | """ 135 | if self.shell("kill %s" % 136 | str(pid)).stdout.read().split(": ")[-1] == "": 137 | return "kill success" 138 | else: 139 | return self.shell("kill %s" % 140 | str(pid)).stdout.read().split(": ")[-1] 141 | 142 | def quit_app(self, package_name): 143 | """ 144 | 退出app,类似于kill掉进程 145 | usage: quitApp("com.android.settings") 146 | """ 147 | self.shell("am force-stop %s" % package_name) 148 | 149 | # def get_focused_package_and_activity(self): 150 | # """ 151 | # 获取当前应用界面的包名和Activity,返回的字符串格式为:packageName/activityName 152 | # """ 153 | # pattern = re.compile(r"[a-zA-Z0-9.]+/.[a-zA-Z0-9.]+") 154 | # out = self.shell( 155 | # "dumpsys window w | %s \/ | %s name=" % 156 | # (find_util, find_util)).stdout.read().strip() 157 | # 158 | # return pattern.findall(out)[0] 159 | 160 | def get_focused_package_and_activity(self): 161 | """ 162 | 获取当前应用界面的包名和Activity,返回的字符串格式为:packageName/activityName 163 | """ 164 | apilevel = int(self.get_api_level()) 165 | if apilevel < 26: 166 | out = self.shell( 167 | "dumpsys activity activities | %s mFocusedActivity" % 168 | find_util).stdout.read().strip().split(' ')[3] 169 | else: 170 | out = self.shell( 171 | "dumpsys activity activities | %s ResumedActivity" % 172 | find_util).stdout.read().strip().split(' ')[3] 173 | return out 174 | 175 | def get_current_package_name(self): 176 | """ 177 | 获取当前运行的应用的包名 178 | """ 179 | return self.get_focused_package_and_activity().split("/")[0] 180 | 181 | def get_current_activity(self): 182 | """ 183 | 获取当前运行应用的activity 184 | """ 185 | return self.get_focused_package_and_activity().split("/")[-1] 186 | 187 | def get_battery_level(self): 188 | """ 189 | 获取电池电量 190 | """ 191 | level = self.shell("dumpsys battery | %s level" % 192 | find_util).stdout.read().split(": ")[-1] 193 | 194 | return int(level) 195 | 196 | def get_backstage_services(self, page_name): 197 | """ 198 | 199 | :return: 指定应用后台运行的services 200 | """ 201 | services_list = [] 202 | for line in self.shell( 203 | 'dumpsys activity services %s' % 204 | page_name).stdout.readlines(): 205 | if line.strip().startswith('intent'): 206 | service_name = line.strip().split('=')[-1].split('}')[0] 207 | if service_name not in services_list: 208 | services_list.append(service_name) 209 | 210 | return services_list 211 | 212 | def get_current_backstage_services(self): 213 | """ 214 | 215 | :return: 当前应用后台运行的services 216 | """ 217 | package = self.get_current_package_name() 218 | return self.get_backstage_services(package) 219 | 220 | def get_battery_status(self): 221 | """ 222 | 获取电池充电状态 223 | BATTERY_STATUS_UNKNOWN:未知状态 224 | BATTERY_STATUS_CHARGING: 充电状态 225 | BATTERY_STATUS_DISCHARGING: 放电状态 226 | BATTERY_STATUS_NOT_CHARGING:未充电 227 | BATTERY_STATUS_FULL: 充电已满 228 | """ 229 | status_dict = {1: "BATTERY_STATUS_UNKNOWN", 230 | 2: "BATTERY_STATUS_CHARGING", 231 | 3: "BATTERY_STATUS_DISCHARGING", 232 | 4: "BATTERY_STATUS_NOT_CHARGING", 233 | 5: "BATTERY_STATUS_FULL"} 234 | status = self.shell("dumpsys battery | %s status" % 235 | find_util).stdout.read().split(": ")[-1] 236 | 237 | return status_dict[int(status)] 238 | 239 | def get_battery_temp(self): 240 | """ 241 | 获取电池温度 242 | """ 243 | temp = self.shell("dumpsys battery | %s temperature" % 244 | find_util).stdout.read().split(": ")[-1] 245 | 246 | return int(temp) / 10.0 247 | 248 | def get_screen_resolution(self): 249 | """ 250 | 获取设备屏幕分辨率,return (width, high) 251 | """ 252 | pattern = re.compile(r"\d+") 253 | out = self.shell( 254 | "dumpsys display | %s DisplayDeviceInfo" % 255 | find_util).stdout.read() 256 | display = pattern.findall(out) 257 | 258 | return int(display[0]), int(display[1]) 259 | 260 | def reboot(self): 261 | """ 262 | 重启设备 263 | """ 264 | self.adb("reboot") 265 | 266 | def fast_boot(self): 267 | """ 268 | 进入fastboot模式 269 | """ 270 | self.adb("reboot bootloader") 271 | 272 | def get_system_app_list(self): 273 | """ 274 | 获取设备中安装的系统应用包名列表 275 | """ 276 | sysApp = [] 277 | for packages in self.shell("pm list packages -s").stdout.readlines(): 278 | sysApp.append(packages.split(":")[-1].splitlines()[0]) 279 | 280 | return sysApp 281 | 282 | def get_third_app_list(self): 283 | """ 284 | 获取设备中安装的第三方应用包名列表 285 | """ 286 | thirdApp = [] 287 | for packages in self.shell("pm list packages -3").stdout.readlines(): 288 | thirdApp.append(packages.split(":")[-1].splitlines()[0]) 289 | 290 | return thirdApp 291 | 292 | def get_matching_app_list(self, keyword): 293 | """ 294 | 模糊查询与keyword匹配的应用包名列表 295 | usage: getMatchingAppList("qq") 296 | """ 297 | matApp = [] 298 | for packages in self.shell( 299 | "pm list packages %s" % 300 | keyword).stdout.readlines(): 301 | matApp.append(packages.split(":")[-1].splitlines()[0]) 302 | 303 | return matApp 304 | 305 | def get_app_start_total_time(self, component): 306 | """ 307 | 获取启动应用所花时间 308 | usage: getAppStartTotalTime("com.android.settings/.Settings") 309 | """ 310 | time = self.shell("am start -W %s | %s TotalTime" % 311 | (component, find_util)).stdout.read().split(": ")[-1] 312 | return int(time) 313 | 314 | def get_api_level(self): 315 | apilevel = self.shell('getprop ro.build.version.sdk').stdout.read().strip() 316 | return apilevel 317 | 318 | def install_app(self, app_file): 319 | """ 320 | 安装app,app名字不能含中文字符 321 | args: 322 | - appFile -: app路径 323 | usage: install("/Users/joko/Downloads/1.apk") 324 | INSTALL_FAILED_ALREADY_EXISTS 应用已经存在,或卸载了但没卸载干净 adb install 时使用 -r 参数,或者先 adb uninstall 再安装 325 | INSTALL_FAILED_INVALID_APK 无效的 APK 文件 326 | INSTALL_FAILED_INVALID_URI 无效的 APK 文件名 确保 APK 文件名里无中文 327 | INSTALL_FAILED_INSUFFICIENT_STORAGE 空间不足 清理空间 328 | INSTALL_FAILED_DUPLICATE_PACKAGE 已经存在同名程序 329 | INSTALL_FAILED_NO_SHARED_USER 请求的共享用户不存在 330 | INSTALL_FAILED_UPDATE_INCOMPATIBLE 以前安装过同名应用,但卸载时数据没有移除 先 adb uninstall 再安装 331 | INSTALL_FAILED_SHARED_USER_INCOMPATIBLE 请求的共享用户存在但签名不一致 332 | INSTALL_FAILED_MISSING_SHARED_LIBRARY 安装包使用了设备上不可用的共享库 333 | INSTALL_FAILED_REPLACE_COULDNT_DELETE 替换时无法删除 334 | INSTALL_FAILED_DEXOPT dex 优化验证失败或空间不足 335 | INSTALL_FAILED_OLDER_SDK 设备系统版本低于应用要求 336 | INSTALL_FAILED_CONFLICTING_PROVIDER 设备里已经存在与应用里同名的 content provider 337 | INSTALL_FAILED_NEWER_SDK 设备系统版本高于应用要求 338 | INSTALL_FAILED_TEST_ONLY 应用是 test-only 的,但安装时没有指定 -t 参数 339 | INSTALL_FAILED_CPU_ABI_INCOMPATIBLE 包含不兼容设备 CPU 应用程序二进制接口的 native code 340 | INSTALL_FAILED_MISSING_FEATURE 应用使用了设备不可用的功能 341 | INSTALL_FAILED_CONTAINER_ERROR sdcard 访问失败 确认 sdcard 可用,或者安装到内置存储 342 | INSTALL_FAILED_INVALID_INSTALL_LOCATION 不能安装到指定位置 切换安装位置,添加或删除 -s 参数 343 | INSTALL_FAILED_MEDIA_UNAVAILABLE 安装位置不可用 一般为 sdcard,确认 sdcard 可用或安装到内置存储 344 | INSTALL_FAILED_VERIFICATION_TIMEOUT 验证安装包超时 345 | INSTALL_FAILED_VERIFICATION_FAILURE 验证安装包失败 346 | INSTALL_FAILED_PACKAGE_CHANGED 应用与调用程序期望的不一致 347 | INSTALL_FAILED_UID_CHANGED 以前安装过该应用,与本次分配的 UID 不一致 清除以前安装过的残留文件 348 | INSTALL_FAILED_VERSION_DOWNGRADE 已经安装了该应用更高版本 使用 -d 参数 349 | INSTALL_FAILED_PERMISSION_MODEL_DOWNGRADE 已安装 target SDK 支持运行时权限的同名应用,要安装的版本不支持运行时权限 350 | INSTALL_PARSE_FAILED_NOT_APK 指定路径不是文件,或不是以 .apk 结尾 351 | INSTALL_PARSE_FAILED_BAD_MANIFEST 无法解析的 AndroidManifest.xml 文件 352 | INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION 解析器遇到异常 353 | INSTALL_PARSE_FAILED_NO_CERTIFICATES 安装包没有签名 354 | INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES 已安装该应用,且签名与 APK 文件不一致 先卸载设备上的该应用,再安装 355 | INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING 解析 APK 文件时遇到 CertificateEncodingException 356 | INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME manifest 文件里没有或者使用了无效的包名 357 | INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID manifest 文件里指定了无效的共享用户 ID 358 | INSTALL_PARSE_FAILED_MANIFEST_MALFORMED 解析 manifest 文件时遇到结构性错误 359 | INSTALL_PARSE_FAILED_MANIFEST_EMPTY 在 manifest 文件里找不到找可操作标签(instrumentation 或 application) 360 | INSTALL_FAILED_INTERNAL_ERROR 因系统问题安装失败 361 | INSTALL_FAILED_USER_RESTRICTED 用户被限制安装应用 362 | INSTALL_FAILED_DUPLICATE_PERMISSION 应用尝试定义一个已经存在的权限名称 363 | INSTALL_FAILED_NO_MATCHING_ABIS 应用包含设备的应用程序二进制接口不支持的 native code 364 | INSTALL_CANCELED_BY_USER 应用安装需要在设备上确认,但未操作设备或点了取消 在设备上同意安装 365 | INSTALL_FAILED_ACWF_INCOMPATIBLE 应用程序与设备不兼容 366 | does not contain AndroidManifest.xml 无效的 APK 文件 367 | is not a valid zip file 无效的 APK 文件 368 | Offline 设备未连接成功 先将设备与 adb 连接成功 369 | unauthorized 设备未授权允许调试 370 | error: device not found 没有连接成功的设备 先将设备与 adb 连接成功 371 | protocol failure 设备已断开连接 先将设备与 adb 连接成功 372 | Unknown option: -s Android 2.2 以下不支持安装到 sdcard 不使用 -s 参数 373 | No space left on devicerm 空间不足 清理空间 374 | Permission denied ... sdcard ... sdcard 不可用 375 | """ 376 | # for line in self.adb("install -r %s" % app_file).stdout.readlines(): 377 | # if 'Failure' in line: 378 | # print line.strip() 379 | return self.adb("install -r %s" % app_file) 380 | 381 | def is_install(self, packageName): 382 | """ 383 | 判断应用是否安装,已安装返回True,否则返回False 384 | usage: isInstall("com.example.apidemo") 385 | """ 386 | if self.get_matching_app_list(packageName): 387 | return True 388 | else: 389 | return False 390 | 391 | def remove_app(self, packageName): 392 | """ 393 | 卸载应用 394 | args: 395 | - packageName -:应用包名,非apk名 396 | """ 397 | return self.adb("uninstall %s" % packageName) 398 | 399 | def clear_app_data(self, packageName): 400 | """ 401 | 清除应用用户数据 402 | usage: clearAppData("com.android.contacts") 403 | """ 404 | if "Success" in self.shell( 405 | "pm clear %s" % 406 | packageName).stdout.read().splitlines(): 407 | return "clear user data success " 408 | else: 409 | return "make sure package exist" 410 | 411 | def reset_current_app(self): 412 | """ 413 | 重置当前应用 414 | """ 415 | packageName = self.get_current_package_name() 416 | component = self.get_focused_package_and_activity() 417 | self.clear_app_data(packageName) 418 | self.start_activity(component) 419 | 420 | def get_app_install_path(self, path_name): 421 | """ 422 | 获取第三方应用安装地址 423 | :return: 424 | """ 425 | t = self.shell("pm path %s" % path_name).stdout.readlines() 426 | return ''.join(t).strip().split(':')[1] 427 | 428 | def pull_install_app(self, save_path): 429 | """ 430 | 获取当前Android设备第三方应用包,并且pull到本地 431 | :param save_path: 存放路径 432 | :return: 433 | """ 434 | for app_package_name in self.get_third_app_list(): 435 | install_app_path = self.get_app_install_path(app_package_name) 436 | self.pull(install_app_path, save_path + '/' + app_package_name + '.apk') 437 | 438 | def start_activity(self, component): 439 | """ 440 | 启动一个Activity 441 | usage: startActivity(component = "com.android.settinrs/.Settings") 442 | """ 443 | self.shell("am start -n %s" % component) 444 | 445 | def restart_activity(self, component): 446 | """ 447 | 启动一个Activity 448 | usage: startActivity(component = "com.android.settinrs/.Settings") 449 | """ 450 | self.shell("am start -R 1 %s" % component) 451 | 452 | def start_web_page(self, url): 453 | """ 454 | 使用系统默认浏览器打开一个网页 455 | usage: startWebpage("http://www.baidu.com") 456 | """ 457 | self.shell("am start -a android.intent.action.VIEW -d %s" % url) 458 | 459 | def call_phone(self, number): 460 | """ 461 | 启动拨号器拨打电话 462 | usage: callPhone(10086) 463 | """ 464 | self.shell( 465 | "am start -a android.intent.action.CALL -d tel:%s" % 466 | str(number)) 467 | 468 | def send_key_event(self, keycode): 469 | """ 470 | 发送一个按键事件 471 | args: 472 | - keycode -: 473 | http://developer.android.com/reference/android/view/KeyEvent.html 474 | usage: sendKeyEvent(keycode.HOME) 475 | """ 476 | self.shell("input keyevent %s" % str(keycode)) 477 | sleep(0.5) 478 | 479 | def long_press_key(self, keycode): 480 | """ 481 | 发送一个按键长按事件,Android 4.4以上 482 | usage: longPressKey(keycode.HOME) 483 | """ 484 | self.shell("input keyevent --longpress %s" % str(keycode)) 485 | sleep(0.5) 486 | 487 | def touch(self, e=None, x=None, y=None): 488 | """ 489 | 触摸事件 490 | usage: touch(e), touch(x=0.5,y=0.5) 491 | """ 492 | width, high = self.get_screen_resolution() 493 | if (e is not None): 494 | x = e[0] 495 | y = e[1] 496 | if (0 < x < 1): 497 | x = x * width 498 | if (0 < y < 1): 499 | y = y * high 500 | 501 | self.shell("input tap %s %s" % (str(x), str(y))) 502 | sleep(0.5) 503 | 504 | def get_focused_package_xml(self, save_path): 505 | file_name = random.randint(10, 99) 506 | self.shell( 507 | 'uiautomator dump /data/local/tmp/{}.xml'.format(file_name)).communicate() 508 | self.adb('pull /data/local/tmp/{}.xml {}'.format(file_name, 509 | save_path)).communicate() 510 | 511 | def touch_by_element(self, element): 512 | """ 513 | 点击元素 514 | usage: touchByElement(Element().findElementByName(u"计算器")) 515 | """ 516 | self.shell("input tap %s %s" % (str(element[0]), str(element[1]))) 517 | sleep(0.5) 518 | 519 | def touch_by_ratio(self, ratioWidth, ratioHigh): 520 | """ 521 | 通过比例发送触摸事件 522 | args: 523 | - ratioWidth -:width占比, 0 %s&' % (log_path)) 859 | 860 | def get_cpu_version(self): 861 | """ 862 | 获取cpu基带版本 863 | :return: arm64-v8a 864 | """ 865 | t = self.shell( 866 | "getprop ro.product.cpu.abi | tr -d '\r'").stdout.readlines() 867 | return ''.join(t).strip() 868 | 869 | def pull(self, remote_file, local_file): 870 | """ 871 | 872 | :param remote_file: 拉取文件地址 873 | :param local_file: 存放文件地址 874 | :return: 875 | """ 876 | return self.adb('pull %s %s' % (remote_file, local_file)) 877 | 878 | def rm(self, remote_file): 879 | """ 880 | 881 | :param remote_file: 删除文件地址 882 | :return: 883 | """ 884 | return self.shell(remote_file) 885 | 886 | def rm_minicap_jpg(self, remote_file): 887 | """ 888 | 889 | :param remote_file: 删除minicap图片缓存 890 | :return: 891 | """ 892 | self.rm('rm -r /data/local/tmp/%s.jpg' % (remote_file)) 893 | 894 | def get_disk(self): 895 | """ 896 | 获取手机磁盘信息 897 | :return: Used:用户占用,Free:剩余空间 898 | """ 899 | for s in self.shell('df').stdout.readlines(): 900 | if '/mnt/shell/emulated' in s or '/storage/sdcard0' in s: 901 | lst = [] 902 | for i in s.split(' '): 903 | if i: 904 | lst.append(i) 905 | return 'Used:%s,Free:%s' % (lst[2], lst[3]) 906 | 907 | def get_dmesg(self): 908 | """ 909 | 910 | :return:内核日志 911 | """ 912 | t = self.shell("dmesg").stdout.readlines() 913 | return ''.join(t).strip() 914 | 915 | def get_device_name(self): 916 | """ 917 | 918 | :return: 设备名 :SM-G9006W 919 | """ 920 | t = self.shell("getprop ro.product.model").stdout.readlines() 921 | return ''.join(t).strip() 922 | 923 | def get_battery(self): 924 | """ 925 | 926 | :return:全部电量相关信息 927 | """ 928 | t = self.shell("dumpsys battery").stdout.readlines() 929 | return ''.join(t).strip() 930 | 931 | def get_wm_density(self): 932 | """ 933 | 屏幕密度 934 | :return:Physical density: 480 935 | """ 936 | t = self.shell("wm density").stdout.readlines() 937 | return ''.join(t).strip() 938 | 939 | def get_window_displays(self): 940 | """ 941 | 942 | :return:显示屏参数 943 | """ 944 | t = self.shell("dumpsys window displays").stdout.readlines() 945 | return ''.join(t).strip() 946 | 947 | def get_mac_address(self): 948 | """ 949 | 950 | :return:mac地址 951 | """ 952 | t = self.shell("cat /sys/class/net/wlan0/address").stdout.readlines() 953 | return ''.join(t).strip() 954 | 955 | def get_cpu_info_all(self): 956 | """ 957 | 958 | :return:cpu全部信息 959 | """ 960 | t = self.shell("cat /proc/cpuinfo").stdout.readlines() 961 | return ''.join(t).strip() 962 | 963 | def get_cpu_mem_all(self): 964 | """ 965 | 966 | :return:内存全部信息 967 | """ 968 | t = self.shell("cat /proc/meminfo").stdout.readlines() 969 | return ''.join(t).strip() 970 | 971 | def get_sys_all(self): 972 | """ 973 | 974 | :return:设备全部信息 975 | """ 976 | t = self.shell("cat /system/build.prop").stdout.readlines() 977 | return ''.join(t).strip() 978 | 979 | def get_ps(self): 980 | """ 981 | 982 | :return:设备全部进程信息 983 | """ 984 | t = self.shell("ps").stdout.readlines() 985 | return ''.join(t).strip() 986 | 987 | def get_cpu_mem_info(self): 988 | """ 989 | 990 | :return:当前设备cpu与内存全部信息 991 | """ 992 | t = self.shell("top -n 1 -d 0.5").stdout.readlines() 993 | return ''.join(t).strip() 994 | 995 | def get_phone_ime(self): 996 | """ 997 | 998 | :return:获取设备已安装的输入法包名 999 | """ 1000 | ime_list = [ime.strip() for ime in self.shell("ime list -s").stdout.readlines()] 1001 | return ime_list 1002 | 1003 | def set_phone_ime(self, arg): 1004 | """ 1005 | 1006 | :return: 更改手机输入法 1007 | """ 1008 | self.shell("ime set %s" % arg) 1009 | 1010 | 1011 | if __name__ == "__main__": 1012 | A = ADB() 1013 | print A.get_focused_package_and_activity() 1014 | --------------------------------------------------------------------------------