pyadb关于python操作adb的资料
3.最后adb命令由于是android的原生操作命令,支持实现的功能非常多。这里举几个pyapp里实现的功能例子:获取,修改手机当前使用的输入法(adb shell ime list),获取当前手机界面的活动activity(adb shell dumpsys activity activities),安装,卸载,启动app,点击,划屏,长按,硬件输入,截屏等。
所以最后总结一下,这里只是说明adb命令更适合我开发pyapp测试框架,而不是说appium不好。针对只是单纯的使用python开发自动化脚本的同学来说,appium更为简单些。
1.3 python如何调用adb命令
Python中执行cmd命令可以用到os和subprocess两个模块。区别在于os是阻塞式的,subprocess是非阻塞式的,所以我们使用subprocess是比较适合的。接下来我先举一个查询连接设备的命令来看看python中怎么样的写法。用到的命令为 adb devices。
import subprocess
order='adb devices' #获取连接设备
pi= subprocess.Popen(order,shell=True,stdout=subprocess.PIPE)
print pi.stdout.read() #打印结果
实际打印结果,可以看到当前电脑连接了三台设备。这里需要再说明一下adb devices 命令的结果返回是一次性的,所以我们用read方法读取数据是没有问题的,然而adb命令里还有一些是实时返回结果的,比如输出手机日志的命令logcat,结果会不断的打印出来当前的设备操作日志信息内容,这种类型的命令我们在python中如果需要获取打印结果,如果还是用read方法的话,等待结果的返回时间会非常长,这里我们就要换一种方法读取结果,写法如下。
import subprocess
order='adb logcat'
pi= subprocess.Popen(order,shell=True,stdout=subprocess.PIPE)
for i in iter(pi.stdout.readline,'b'):
print i
这样的打印效果,如同cmd里操作一致,实时的打印出日志信息。这里我们就用到了readline方法,其实这种写法类似我们读取文件,单行读取和全部内容读取。因为目前pyapp的框架已经基本写完了,所以有了写这篇文章的想法,分享一些python在处理adb命令上的一些心得,就目前来看python在调用adb命令上区别主要就是这两点,最终目的是我们找到需要的功能命令获取结果数据,然后再去通过python处理这些返回数据,实现自动化测试的目的。大家要用好adb命令,还要注意一点的是每条命令的各种参数的搭配使用,比如pyapp的实现是支持多设备连接的,那么我们在针对某个手机进行adb命令操作时,就需要带上-s 加设备号,表示操作的具体设备否则命令会报错。比如我们针对一个设备去进行点击操作,命令的写法应该是这样:adb -s 49dsd4554wdsa shell input tap 600 900,其中‘49dsd4554wdsa’是设备号,‘600 900’点击屏幕坐标。所以可以看到增加了-s之后就可以很方便的同时操作多台设备。
至于adb的相关的命令,本篇文章不会再过多的介绍,因为网上有很多相关的帖子博客都有介绍adb命令的,大家可以自己查阅然后通过python去操作实现。而后续我还要将花一篇的文章的时间去介绍一下我在编写pyapp框架时,遇到的一些adb命令需要注意的地方。可能大家在网上查阅一些adb命令后,尝试这自己去执行一下,发现“诶,怎么不起作用?”,这里可能就要注意一些命令的参数搭配或者是自己所使用的设备具体的情况来定,因为目前市面上的安卓手机设备种类非常多,需要做一些适配。这里如果大家在每条的命令后加一句 –h 或是-help,可以查询一下该条命令的相关使用帮助信息。比如我们在cmd中输入 adb shell input –help,会返回命令相关的使用方法,我这里大概讲解一下,大家遇到其他的命令也可以自己看明白每条命令的用法了。在Usage这行具体告诉我们input后面跟的写法,[]中括号里的内容表示可以带也可以不带,<>尖括号就是必须带上的内容。然后接下来给我们了sources的一些常见可用的用法,这里有看到trackball(轨迹球),joystick(手柄),mouse(鼠标)等等,表示模拟输入的源硬件的意思,而最后是具体的命令,有text(输入文本),keyevent(硬件操作),tap(点击)等等,这里还需要注意在具体的命令后还需要跟上具体的命令参数,怎么理解呢?比如我们是要操作tap命令,点击屏幕,那么我们肯定要告诉设备我们要点击的位置(坐标),所以在tap后把我们点击的坐标x,y传进去。
1.2 那些需要注意的adb命令
1.2.1 “adb shell input ……”
这条命令,在测试过程中也是经常用到的,它后面可以跟 tap,text,swipe,进行点击屏幕,输入文本,滑屏的操作,具体在python中使用按照命令格式执行也不会出现什么问题。但是如果我们要长按某个元素,从而实现具体功能呢?下面给出实现代码。
def long_press(dev,data,hold_time):
action='adb -s '+dev+' shell input touchscreen swipe '+'%d'%data[0][0]+' '+'%d'%(data[0][1])+' '+'%d'%data[-1][0]+' '+'%d'%(data[-1][1])+' '+hold_time
print action
pi= subprocess.Popen(action,shell=True,stdout=subprocess.PIPE)
long_press('4d0041b1be98b01f',[[540,716],[545,718]],'1000')
可以看到我定义的long_press方法,action里用到的仍然是swipe命令,大家知道swipe是滑动屏幕的操作,那么如果我们在传递滑动范围坐标的时候,设定的滑动范围非常小,那么是不是就是间接的达到了长按某一个区域的目的,然后配合一个整个命令的执行时间的参数,是不是就完美解决了长按这个动作。
我们看一看完整的adb命令:
adb -s 4d0041b1be98b01f shell input touchscreen swipe 540 716 545 718 1000
解释:-s 后跟设备号 ,swipe 先传移动坐标范围“540 716 545 718”,然后1000是长按的时间,1000的单位是毫秒(注意)。
大家不要以为这条命令就这样介绍结束了,还没有。按道理来说input后面的source源在swipe命令里默认就是touchscreen,所以一般我们在写这条命令时是可以省略touchscreen的,但是实际我在编写pyapp框架的时候(由于pyapp设计需要支持多台设备),拿了不同型号品牌(分辨率)的手机做适配,毕竟框架要照顾的范围要必须广要有一定的普适性。我发现,在pyapp的僚机模式下(就是一台主设备操控多台附属设备欢迎关入群”631466916“,获取最新pyswat和pyapp程序),主设备的长按操作在有些副设备上实现不成功,开始我也是在命令里省略了touchscreen。开始也想不到什么原因,因为命令来说也不复杂不存在写错的情况,毕竟有些副设备还是能正确响应动作的,后来我把shell input命令的帮助结果打印出来研究了一下(见上一篇文中有截图),在swipe命令的参数传递中说明了touchscreen是缺省,默认属性,所以正常来说我们是不用在命令中明确指定该参数值的,然而我也是瞎猫碰到死耗子,试着在命令里加入了touchscreen,最后执行结果每一台副设备在长按命令的执行上都成功了。
1.2.2 adb命令如何输入中文?
adb命令里进行输入文本输入‘adb -s 设备号shell input text 输入的内容’。在原生的adb命令里是不支持中文输入的,所以我们在测试的时候只能输入英文字符。然而实际我们在做app测试的时候避免不了需要输入中文字符的情况,这里给大家介绍一种曲线救国的办法。利用“ADBKeyBoard”输入法来进行中文的输入,通过广播的方式达到输入中文字符,具体命令:adb shell am broadcast -a ADB_INPUT_TEXT --es msg “内容”。
1.2.3 启动应用
在appium的应用中每次只能开启一个app,而如果用adb命令的话就灵活许多,输入:adb shell am start –n package名/.activity名,这里的package名和activity名和appium中配置的一致。比如我们启动计算器程序,对应的命令就是“adb shell am start -n com.android.calculator2/.Calculator”。这里要再提醒大家一点,APP的package名和activity名一定要找对特别是activity名,具体的找发大家可以参看我的《python自动化测试应用-第2篇(APP测试)--Appium初识篇》里边讲解了具体的查找方法。当然大家也是可以根据adb命令去进行查找,在pyapp测试框架中我就是根据adb命令进行名称的查找启动应用的。这里我讲解一下思路,利用命令adb shell pm list packages -3,将手机中安装的第三方app列举出来,然后通过时间比对找到新安装的app从而确定名称。其实大家只要有了思路可以在百度中查找对应的命令即可。
1.2.4 只会发短信,那么查看短信呢?不会你就out了!
网上你可能找到如何发短信,打电话的相关adb命令的介绍,其实原理也就是1.2.3中介绍的,还是启动对应的应用程序来实现。比如发短息:adb shell am start -a android.intent.action.SENDTO -d smsto:发送号码 --es sms_body 短信内容。那么如何读取一条短信内容呢?
在编写pyapp框架的过程中为了实现app的验证码自动填写功能,着实费了一番功夫。目前大部分的注册登录都是可以用动态短信验证码来进行操作的,当然app开发者本身是可以实现在收到短信后读取验证码进行自动填写,遇到没有这种功能的app我们当然也可以按照此原理去实现。由于安卓手机的所有短信都是存储在数据库中,那么我们只要找到短信的这个数据库文件,自然就可以轻松的通过python的数据库操作读取到短信内容了。'/data/data/com.android.providers.telephony/databases/mmssms.db'这个路径下“mmssms.db”文件就是保存短信内容的数据库文件,那么剩下的工作自然就是数据库的读操作了,用正则表达式匹配到验证码即可,最后通过adb的input命令写入到app中即可。这里还需要注意的是,操作“mmssms.db”文件需要root权限,所以你要想在pyapp框架里使用这个功能必须是使用root过的手机设备。
1.2.5 adb命令su权限如何使用?
什么是adb命令的su权限,举个简单的例子,比如说你要访问手机的内存/data/system路径下的文件。在cmd命令窗中你需要执行三步:
1. 在cmd窗体中输入命令:adb shell回车
2. 输入su回车
可以看到命令符由$变成了#符号。
3. 输入cd /data/system
这样就完成了/data/system路径的访问,如果遇到那些需要权限的文件你没有执行su的话,可能就会给你返回一句Permission denied(权限拒绝)。
好了,说了这么多上面都是在cmd窗口中去分步执行的,那么在python中怎么去实现呢?如果按照之前讲解的方式在python脚本中分别执行这三条命令,肯定是不行的。因为程序执行一次adb命令,就会建立一个独立的进程,所以我们在脚本中不能按顺序执行三次adb命令,这样是达不到效果的。我们必须让一条adb命令一次完成所有的步骤的执行才对,那么我们这条命令该怎么写呢?简单暴力点,像这样:adb shell su cd data\system
肯定是不行的,不过大致意思是对的,只是具体写法上要按照正确的格式在su后面加上-c就行了。如:adb shell su –c cd “data\system”。我们看看这条命令,注意以后需要su权限的adb命令都可以这样写,-c后面跟着具体的操作命令即可。
1.2.6 uiautomatorviewer 和 hierarchyviewer傻傻分不清楚?
大家在做android自动化测试时,必定会需要知道界面元素/控件的相关属性,如id,class等,这时在androidsdk的tool工具中就会用到uiautomatorviewer 和 hierarchyviewer。这两个工具都可以很直观的方便大家去查找界面元素,而这样只是单纯的人工借助工具去查看,既然是要做自动化测试,必然我们要去通过代码编程去代替手工操作了。在pyapp框架中,我也是利用uiautomatorviewer来捕获app界面的从而获取对应的元素/控件。接下来我们就来看看python是如何做到的。相对来说uiautomatorviewer的实现更容易些,一条adb命令就可以了。
order='adb -s device shell uiautomator dump'
运行成功后会返回:
“UI hierchary dumped to: /storage/emulated/legacy/window_dump.xml“
结果很直观了,这个window_dump.xml里就是整个界面的布局层级信息,从中我们就可以获取到各个元素/控件的属性信息。如果利用浏览器打开xml文件大家也可以直观的看到界面的层级关系,如果大家只关心具体的元素控件,我们可以在命令后面加上—compressed,这样获取的xml就会清爽许多。
我们接着看看怎么在python中利用hierarchyviewer实现元素/控件获取,首先我们要知道要实现hierarchyviewer的元素获取我们要打开手机的View Server服务,并与其进行socket通信,从而获取到元素/控件信息。那么接下来我们将通过几条不同的adb命令来准备好与View Server进行通信的环境。
第一步:
通过“adb shell service call window 3”命令得到返回值Result: Parcel(00000000 00000001 '........')或者Result: Parcel(00000000 00000000 '........')如果是00000001表示View Server是开启的,反之我们就要开启View Server服务。
第二步:
如果View Server没有打开,我们就通过“adb shell service call window 1 i32 4939”命令打开。然后再通过第一步的命令确认是否开启了View Server。
第三步:
当我们开启了服务后,需要再将手机的4939端口映射到电脑的4939端口,这样就可以进行socket通信了。命令为“adb forward tcp:4939 tcp:4939”
以上这三步完成后,接下来我们就可以在python脚本中与View Server建立socket通信了。由于已经有服务端View Server,我们只需要用python实现客户端的socket代码即可。
import socket#导入socket模块
sock=socket.socket(socket.AF_INET,socket.SOCK_STREAM)#建立TCP连接
sock.connect(('127.0.0.1',4939))#连接服务端地址及端口并建立连接
sock.send('list')#发送list命令向服务端
sock.recv(1024)#接收返回结果
这里就不去讲解python的socket用法了,重点讲解一下这里的list指令。当我们建立好与View Server的通信后,就可以发送不同的指令向服务端,获取信息了,这里的list意思就是获取当前手机活动界面的信息。如下图
有了这个信息,我们就可以继续发送dump命令去获取具体某个界面的详细层级信息了。举个例子比如我们要打印出上图中第二行
“432b8608 com.miui.home/com.miui.home.launcher.Launcher”
的界面信息,在我们发送内容中应该这样写sock.send(“dump 432b8608”),获取结果如下图。由于内容会比较多,建议大家在代码中将返回值写入txt文件中,方便查看。具体的内容中每一段表示一个元素,里边详细的列举了元素的各种属性信息,如id,class,坐标等。
至此,我们了解到通过发送list,dump两个命令可以进行元素信息的获取。另外还有一个capture命令可以获取到元素在界面中的截图,这里就不再赘述了。至于到底是用uiautomatorviewer 还是 hierarchyviewer去实现元素的获取,决定权就在你的手上了。