项目经常遇到混淆打包、安卓版本兼容带来的崩溃问题,还有少部分代码引起的崩溃问题。
人工验证即费时也枯燥,故琢磨有没办法可以快速的验证这些问题,最重要的是快、最重要的是快、最重要的是快
目前只验证崩溃问题,而且初步只是验证启动 Activity 的崩溃问题,后续增加自动遍历页面所有控件的方法,亦可考虑增加 UI 版本 diff 对比
实现了,亲测有效,只是还没系统的整合在一起。
只要有 Activity 挂了,robotium 就会挂,检测到挂了就导出日志
怎么检测有没挂?
Activity 需要传参才能打开的怎么办?
有办法,源码传什么参数,robotium 就传什么参数过去
无参数的情况
Class <?> LoginClass;
LoginClass = Class.forName("Activity路径");
Intent intent = new Intent(getActivity(), LoginClass);
getActivity().startActivity(intent);
有参数的情况
Class <?> GroupMemberClass;
GroupMemberClass = Class.forName("Activity路径");
Intent intent = new Intent(getActivity(), GroupMemberClass);
intent.putExtra("GroupId", 77);
getActivity().startActivity(intent);
传参怎么管理?
配置文件以 Activity 为节点,通过字典的形式配置好,一劳永逸,后面只需少量改动,如页面变动等等
能有多快?
预测 5 分钟内可完成 Activity 的遍历
#!/usr/bin/evn python
# -*- coding:utf-8 -*-
# FileName activities.py
# Author: HeyNiu
# Created Time: 2016/9/18
"""
获取项目所有Activity
"""
import re
import os
import utils.adbtools
import utils.consts
class Activities(object):
def __init__(self, apk_path, manifest_path):
"""
初始化
:param apk_path: apk文件路径
:param manifest_path: AndroidManifest.xml 路径
"""
self.apk_path = apk_path
self.manifest_path = manifest_path
self.dump_stream = utils.adbtools.AdbTools().dump_apk(apk_path).readlines()
self.manifest_stream = self.__read_file()
self.__init_data()
def __init_data(self):
"""
初始化apk基本信息
:return:
"""
for i in self.dump_stream:
if 'package' in i:
reg = re.compile("name='(.+?)'")
utils.consts.PACKAGE = re.findall(reg, i)[0] # 包名
reg = re.compile("versionCode='(.+?)'")
utils.consts.VERSION_CODE = re.findall(reg, i)[0] # build版本
reg = re.compile("versionName='(.+?)'")
utils.consts.VERSION_NAME = re.findall(reg, i)[0] # 版本号
if 'launchable-activity' in i:
reg = re.compile("name='(.+?)'")
utils.consts.LAUNCHER_ACTIVITY = re.findall(reg, i)[0] # 启动Activity
def __read_file(self):
"""
读取AndroidManifest.xml
:return:
"""
if os.path.exists(self.manifest_path):
return open(self.manifest_path, encoding='utf-8').read()
raise FileNotFoundError('AndroidManifest.xml not found.')
def __match_activities(self):
"""
匹配出所有Activity
:return:
"""
reg = re.compile("<activity\s(.*)")
l = re.findall(reg, self.manifest_stream)
regex = re.compile(r'android:name="(.+?)"')
return ('%s/%s%s' % (utils.consts.PACKAGE, utils.consts.PACKAGE, re.findall(regex, i)[0]) for i in l)
def ignore__activities(self, ignore_activities):
"""
排除忽略的Activities
:return:
"""
all_activities = self.__match_activities()
activities = list(all_activities)
for activity in activities:
for i in ignore_activities:
if i in activity:
activities.remove(activity)
return activities
if __name__ == '__main__':
pass
#!/usr/bin/evn python
# -*- coding:utf-8 -*-
# FileName resign.py
# Author: HeyNiu
# Created Time: 2016/9/19
"""
重签名apk
"""
import os
import utils.errors
class Resign(object):
def __init__(self, path):
"""
初始化
:param path: apk文件路径
"""
self.path = path
self.__check_environment()
apk_path = self.__check_apk_path()
self.oldapk = apk_path
self.newapk = apk_path[::-1].split('.', 1)[1][::-1] + '_debug.apk'
@staticmethod
def __check_environment():
"""
环境变量检测
:return:
"""
if "ANDROID_HOME" not in os.environ:
raise EnvironmentError("ANDROID_HOME PATH NOT FOUND.\nPlease set the environment variable.")
if "7Z_HOME" not in os.environ:
raise EnvironmentError("7Z_HOME PATH NOT FOUND.\nPlease set the environment variable.\n"
"Don't installed 7-zip? click the url download.\n"
"http://www.7-zip.org/")
# 检查build-tools是否添加到环境变量中
# 需要用到里面的zipalign命令
l = os.environ['PATH'].split(';')
build_tools = False
for i in l:
if 'build-tools' in i:
build_tools = True
if not build_tools:
raise EnvironmentError("ANDROID_HOME BUILD-TOOLS COMMAND NOT FOUND.\nPlease set the environment variable.")
def __check_apk_path(self):
"""
检查path是否合法apk
:return:
"""
if not self.path.endswith('.apk'):
raise utils.errors.InvalidApkFile('无效apk文件! %s' % (self.path,))
return self.path
def __make_file(self):
"""
apk文件改名zip,并处理掉原签名
:return:
"""
temp = self.oldapk[::-1].split('.', 1)[1][::-1].split('\\')[-1].split()[0]
zip_filename = '%s.zip' % (temp,)
apk_filename = '%s.apk' % (temp,)
zip_path = os.path.join(self.oldapk[::-1].split('\\', 1)[-1][::-1], zip_filename)
os.system('ren %s %s' % (self.oldapk.split()[0], zip_filename))
os.system('7z d %s META-INF' % zip_path)
os.system('ren %s %s' % (zip_path, apk_filename))
def resign(self):
"""
重签名apk
:return:
"""
self.__make_file()
local_keystore = os.path.join(os.path.expanduser('~'), '.android\debug.keystore')
if not os.path.exists(local_keystore):
raise utils.errors.KeyStoreNotFound('请确保签名文件存在%s' % (local_keystore,))
os.system('jarsigner -digestalg SHA1 -sigalg MD5withRSA -keystore %s\.android\debug.keystore -storepass android '
'-keypass android %s androiddebugkey' % (os.path.expanduser('~'), self.oldapk))
os.system('zipalign 4 %s %s' % (self.oldapk, self.newapk))
if __name__ == '__main__':
pass
#!/usr/bin/evn python
# -*- coding:utf-8 -*-
# FileName adbtools.py
# Author: HeyNiu
# Created Time: 2016/9/19
"""
adb 工具类
"""
import os
import platform
import re
import time
class AdbTools(object):
def __init__(self, device_id=''):
self.system = platform.system()
self.find = ''
self.command = ''
self.device_id = device_id
self.__get_find()
self.__check_adb()
self.__connection_devices()
def __get_find(self):
"""
判断系统类型,windows使用findstr,linux使用grep
:return:
"""
if self.system is "Windows":
self.find = "findstr"
else:
self.find = "grep"
def __check_adb(self):
"""
检查adb
判断是否设置环境变量ANDROID_HOME
:return:
"""
if "ANDROID_HOME" in os.environ:
if self.system == "Windows":
path = os.path.join(os.environ["ANDROID_HOME"], "platform-tools", "adb.exe")
if os.path.exists(path):
self.command = path
else:
raise EnvironmentError(
"Adb not found in $ANDROID_HOME path: %s." % os.environ["ANDROID_HOME"])
else:
path = os.path.join(os.environ["ANDROID_HOME"], "platform-tools", "adb")
if os.path.exists(path):
self.command = path
else:
raise EnvironmentError(
"Adb not found in $ANDROID_HOME path: %s." % os.environ["ANDROID_HOME"])
else:
raise EnvironmentError(
"Adb not found in $ANDROID_HOME path: %s." % os.environ["ANDROID_HOME"])
def __connection_devices(self):
"""
连接指定设备,单个设备可不传device_id
:return:
"""
if self.device_id == "":
return
self.device_id = "-s %s" % self.device_id
def adb(self, args):
"""
执行adb命令
:param args:参数
:return:
"""
cmd = "%s %s %s" % (self.command, self.device_id, str(args))
return os.popen(cmd)
def shell(self, args):
"""
执行adb shell命令
:param args:参数
:return:
"""
cmd = "%s %s shell %s" % (self.command, self.device_id, str(args))
return os.popen(cmd)
def get_devices(self):
"""
获取设备列表
:return:
"""
l = self.adb('devices').readlines()
return (i.split()[0] for i in l if 'devices' not in i and len(i) > 5)
def get_package(self):
"""
获取当前运行app包名
:return:
"""
result = self.shell('dumpsys window w | %s \/ | %s name=' % (self.find, self.find)).read()
reg = re.compile(r'name=(.+?)/')
return re.findall(reg, result)[0]
def get_pid(self, package_name):
"""
获取pid
:return:
"""
if self.system is "Windows":
pid_command = self.shell("ps | %s %s$" % (self.find, package_name)).read()
else:
pid_command = self.shell("ps | %s -w %s" % (self.find, package_name)).read()
if pid_command == '':
return "the process doesn't exist."
req = re.compile(r"\d+")
result = str(pid_command).split()
result.remove(result[0])
return req.findall(" ".join(result))[0]
def get_uid(self, pid):
"""
获取uid
:param pid:
:return:
"""
result = self.shell("cat /proc/%s/status" % pid).readlines()
for i in result:
if 'uid' in i.lower():
return i.split()[1]
@staticmethod
def dump_apk(path):
"""
dump apk文件
:param path: apk路径
:return:
"""
# 检查build-tools是否添加到环境变量中
# 需要用到里面的aapt命令
l = os.environ['PATH'].split(';')
build_tools = False
for i in l:
if 'build-tools' in i:
build_tools = True
if not build_tools:
raise EnvironmentError("ANDROID_HOME BUILD-TOOLS COMMAND NOT FOUND.\nPlease set the environment variable.")
return os.popen('aapt dump badging %s' % (path,))
def uiautomator_dump(self):
"""
获取屏幕uiautomator xml文件
:return:
"""
return self.shell('uiautomator dump').read().split()[-1]
def pull(self, source, target):
"""
从手机端拉取文件到电脑端
:return:
"""
self.adb('pull %s %s' % (source, target))
def remove(self, path):
"""
从手机端删除文件
:return:
"""
self.shell('rm %s' % (path,))
def clear_app_data(self, package):
"""
清理应用数据
:return:
"""
self.shell('pm clear %s' % (package,))
def install(self, path):
"""
安装apk文件
:return:
"""
# adb install 安装错误常见列表
errors = {'INSTALL_FAILED_ALREADY_EXISTS': '程序已经存在',
'INSTALL_FAILED_INVALID_APK': '无效的APK',
'INSTALL_FAILED_INVALID_URI': '无效的链接',
'INSTALL_FAILED_INSUFFICIENT_STORAGE': '没有足够的存储空间',
'INSTALL_FAILED_DUPLICATE_PACKAGE': '已存在同名程序',
'INSTALL_FAILED_NO_SHARED_USER': '要求的共享用户不存在',
'INSTALL_FAILED_UPDATE_INCOMPATIBLE': '版本不能共存',
'INSTALL_FAILED_SHARED_USER_INCOMPATIBLE': '需求的共享用户签名错误',
'INSTALL_FAILED_MISSING_SHARED_LIBRARY': '需求的共享库已丢失',
'INSTALL_FAILED_REPLACE_COULDNT_DELETE': '需求的共享库无效',
'INSTALL_FAILED_DEXOPT': 'dex优化验证失败',
'INSTALL_FAILED_OLDER_SDK': '系统版本过旧',
'INSTALL_FAILED_CONFLICTING_PROVIDER': '存在同名的内容提供者',
'INSTALL_FAILED_NEWER_SDK': '系统版本过新',
'INSTALL_FAILED_TEST_ONLY': '调用者不被允许测试的测试程序',
'INSTALL_FAILED_CPU_ABI_INCOMPATIBLE': '包含的本机代码不兼容',
'CPU_ABIINSTALL_FAILED_MISSING_FEATURE': '使用了一个无效的特性',
'INSTALL_FAILED_CONTAINER_ERROR': 'SD卡访问失败',
'INSTALL_FAILED_INVALID_INSTALL_LOCATION': '无效的安装路径',
'INSTALL_FAILED_MEDIA_UNAVAILABLE': 'SD卡不存在',
'INSTALL_FAILED_INTERNAL_ERROR': '系统问题导致安装失败',
'DEFAULT': '未知错误'
}
print('Installing...')
l = self.adb('install %s' % (path,)).read()
if 'Success' in l:
print('Install Success')
if 'Failure' in l:
reg = re.compile('\\[(.+?)\\]')
key = re.findall(reg, l)[0]
print('Install Failure >> %s' % (errors[key],))
def uninstall(self, package):
"""
卸载apk
:param package: 包名
:return:
"""
print('Uninstalling...')
l = self.adb('uninstall %s' % (package,)).read()
print(l)
def screenshot(self, target_path=''):
"""
手机截图
:param target_path: 目标路径
:return:
"""
format_time = self.timestamp('%Y%m%d%H%M%S')
self.shell('screencap -p /sdcard/%s.png' % (format_time,))
time.sleep(1)
if target_path == '':
self.adb('pull /sdcard/%s.png %s' % (format_time, os.path.expanduser('~')))
else:
self.adb('pull /sdcard/%s.png %s' % (format_time, target_path))
self.shell('rm /sdcard/%s.png' % (format_time,))
@staticmethod
def timestamp(format_time):
"""
获取当前时间
:return:
"""
return time.strftime(format_time, time.localtime(time.time()))
if __name__ == '__main__':
pass
开源吗?
当然开源,后面完成后,我会上传到 github