monkeyrunner
三、monkeyrunner复杂的功能开始学习
(1)获取APK文件中ID的两种方式
Monkeyrunner的环境已经搭建完成,现在对Monkeyrunner做一个简介。
Monkeyrunner工具提供了一套API让用户/测试人员来调用,调用这些api可以控制一个Android设备或模拟器,而不需要了解对应的源码。
有了Monkeyrunner,我们可以编写Python脚本来控制apk包的安装和卸载、启动APP、向app发送各种动作事件、截取图片并保存。
除此之外,MonkeyRunner是Google提供的一个基于坐标点的Android黑盒自动化测试工具。所以,要使用Monkeyrunner进行自动化测试,首先,要了解Monkeyrunner中获取坐标点的方式。
本文中,我们主要介绍两种获取坐标点的方式。一种是通过MonkeyRecorder获取坐标;另一种是通过HierarchyViewer工具获取控件ID。
一、控件坐标获取
1.Pointer location获取坐标
先说一个比较简单的获取坐标的方式,是通过模拟器中的设置-开发者选项,找到“指针位置”的选项,勾选上。如下图所示。
勾选后,模拟器的最顶部则显示坐标,比如点击模拟器上的任一应用,最顶部显示X、Y的值即该应用的坐标;同理,如果想要获取任一应用中的任一位置的坐标,也可用此方法。
- MonkeyRecorder获取坐标
下面就MonkeyRecorder获取坐标的方式,进行演示。MonkeyRecorder是一个比较好用的获取坐标的工具,它是用来获取真机或模拟器上坐标的工具,当我们点击真机或模拟器上的空间时,就能显示真机或模拟器上的点击点的坐标。
(1)MonkeyRecorder的启动
a.终端USB调成开发者模式
b.电脑安装手机驱动
手机连接成功后,打开cmd窗口,输入adb devices查看已连接真机或模拟器设备的名称,我们这里仍以模拟器为代表。
之后,在cmd窗口,输入monkeyrunner后,启动Monkeyrunner。做以下操作:导入MonkeyRecorder包、连接模拟器设备、以MonkeyRecorder方式启动模拟器,并依次输入
如下命令:
from com.android.monkeyrunner import MonkeyRunner,MonkeyDevice
from com.android.monkeyrunner.recorder import MonkeyRecorder as recorder
device=MonkeyRunner.waitForConnection()
recorder.start(device)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
到此,MonkeyRecorder正式启动。截图如下。
2)MonkeyRecorder的使用
我们这里只是使用MonkeyRecorder来记录坐标,获取坐标的方式很简单。比如qq的登录界面,点击“登录”按钮,右侧就会显示该按钮的坐标;同样,点击账号输入框或密码输入框,右侧同样会显示坐标。这个坐标就是我们需要获得的坐标。
同时,MonkeyRecorder中的界面是同模拟器页面保持一致的,在MonkeyRecorder中触发任一操作,模拟器上会有相应的触发。如果两者没有保持一致,则点击MonkeyRecorder右上角的Refresh display即可刷新页面。
3.控件坐标之Monkeyrunner脚本演示
我们将下面一段Monkeyrunner脚本写到一个test.py文件中,然后运行test.py文件,查看模拟器或真机上是不是做相应的操作。
from com.android.monkeyrunner import MonkeyRunner,MonkeyDevice
device=MonkeyRunner.waitforConnection()
#启动activity(这里启动qq)
device.startActivity(component="com.tencent.mobileqq/.activity.SplashActivity")
#登录界面,点击账号输入框
device.touch(60,300,'DOWN_AND_UP')
#输入qq账号
device.type('3469191693')
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
二、控件ID获取
通过控件ID实现自动化脚本的运行,就性能而言,会比控件坐标的实现差一些;但是对于不同分辨率的设备都通用,不需要动态变换坐标。控件ID的获取主要是通过HierarchyViewer。下面就HierarchyViewer从打开方式和使用两方面进行讲解。
1.HierarchyViewer的打开方式
HierarchyViewer的打开方式有两种:一种是Eclipse中打开HierarchyView视图,另外一种是命令行中执行sdk/tools/hierarchyviewer.BAT。
HierarchyViewer默认只能在非加密设备使用,例如工程机,工程平板或者模拟器。如果要在手机上使用HierarchyViewer,你需要在你的应用中添加一个开源库View Server。链接地址:https://github.com/romainguy/ViewServer。该篇文章中有讲解如何启动真机View Server,大家如果有兴趣,可参考:https://dup2.org/node/1538。
方式一:连接您的真机设备,或打开模拟器,在eclipse中, 依次选择Window-Open Perspective-Other,在Other中,选择HierarchyView视图,即可打开。
方式二:连接您的真机设备或打开模拟器,运行cmd窗口,进入到sdk/tools目录下,输入命令hierarchyviewer.bat,运行hierarchyviewer。
或者直接在sdk/tools目录下,找到hierarchyviewer.bat,双击运行。
下面讲解利用HierarchyViewer获取控件ID的方法。
2.HierarchyViewer获取控件ID
HierarchyViewer启动后,首先会看到的第一个窗口显示了设备和模拟器的列表。点击左边的箭头,就会展开当前设备或模拟器的Activity对象列表。列表中显示了设备或模拟器上,UI当前可视的所有Activity对象。这些对象按照它们的Android组件名称列出来。列表中的内容包含应用的Activity对象和系统的Activity对象。
当模拟器activity画面变更后,点击refresh可以加载新的页面布局信息。
从列表中选择你的activity名称,双击,或点击菜单栏的Load View Hierarchy按钮,进入View Hierarchy窗口,查看它的view层次结构;或者点击Inspect Screenshot按钮,进入Pixel Perfect窗口,从而查看UI的一个放大图像。我们这里点击进入View Hierarchy窗口。
可以从下图中看到模拟器此activity的画面布局信息,左边部分是hierarchy通过树形结构展示的布局形式,右下角是模拟器上当前页面的UI布局信息。
通过滚动鼠标,可以放大每个树节点;拖拽鼠标,移动树形结构布局。双击树节点可以展示单独的UI部分。从下图中,可以看到,id/btn_login即为登录按钮的ID。依次类推,可以查看其它控件ID。
注:对于列表、或者弹出框则无法直接通过点击ID操作成功,需要计算ID的坐标
(2)Monkeyrunner之控件ID不存在或重复
我们在用monkeyrunner进行Android自动化时,通过获取坐标点或控件ID进行一系列操作。由于使用坐标点时,屏幕分辨率一旦更改,则代码中用到坐标的地方都要修改,这样导致代码的复用率较低。因此,我们多采用控件ID操作(注:控件ID需要在模拟器中使用,对于绝大多数真机不适用)。
但是,某些控件的ID是不存在的或重复存在,那么,遇到这种情况,我们怎样继续使用控件ID进行自动化测呢?
例如,下图中,我想要获取最右侧红框中的id/tv,但是,大家会发现,和它并列的也有重复的控件id值。现在我们就讲述一下这种情况(控件ID不存在同样处理)。
我们从这个控件树的节点角度来思考如何获得控件的引用。我们可以看到在上图hierarchy viewer中的每个控件所对应的框形中,右下角都有一个数字。其实这个数字就是该控件在同级兄弟节点中的索引值,我们知道这个索引值后,就可以根据parentView.children[index]属性来获取任意父节点所对应的子节点的对象引用。其中的parentView可以是树形图中有效ID的任意父节点(父节点要保证唯一有效),然后利用python函数的可变参数列表特性来传入所需控件的索引列表即可构造出得到任意节点引用的字符串,从而得到其引用。
核心代码如下,把如下代码加入自己的python脚本中,直接调用该函数即可。
#定义获取重复或不存在控件id,寻找子节点函数
def getChildView(parentId, *childSeq):
hierarchyViewer = device.getHierarchyViewer()
childView="hierarchyViewer.findViewById('" + parentId +"')"
for index in childSeq:
childView += ('.children[' + str(index) + ']')
print childView
return eval(childView)
#获取id的文本
def getText(view):
if view != None:
return (view.namedProperties.get('text:mText').value)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
有了以上代码之后,我们可以获取上图中的id/tv,方法如下:
getChildView(‘id/province_list’,5,0,0)
其中结合上图可知,getChildView的第一个参数即:有效且唯一的父节点
参数二、三依次为要获取的控件ID的父节点的父节点
注:用到的父节点即图中的id/province_list,有效且唯一的值。当前的父节点右下角的角标,不需要在getChildView函数中显示。
这样,通过以上函数,再结合Hierarchyviewer图形,我们获取到了重复的控件ID。
由于Hierarchyviewer看起来不是特别方便,这里再推荐一款和Hierarchyviewer类似功能的工具:uiautomatorviewer(存储在sdk\tools中,双击打开即可)
由上图中,uiautomatorviewer每个控件前面的数字即相当于Hierarchyviewer的角标,我们同样可以获取到目标ID的最终有效且唯一的父节点,从而调用函数getChildView(‘id/province_list’,5,0,0)
获取到了不存在或重复的控件ID后,我们可以通过其坐标,进行点击操作。
首先,定义一个“获取指定按钮坐标”的函数
def getBtnPoint(btn):
print btn
point = device.getHierarchyViewer().getabsoluteCenterOfView(btn);
return point
- 1
- 2
- 3
- 4
然后通过坐标,实现点击操作,例如:
askView = getChildView('id/tabs',1)
askPpoint = getBtnPoint(askView)
device.touch(askPpoint.x,askPpoint.y,'DOWN_AND_UP')
- 1
- 2
- 3
到这里,我们介绍完了处理控件ID不存在或重复时的方法,自己实践一把,就会更能体会
Hierarchyviewer/uiautomatorviewer+getChildView()获取不存在或重复控件ID的用法。
(3)录制脚本与回放脚本
录制脚本,recorder.py:
01.from com.android.monkeyrunner import MonkeyRunner as mr
02.from com.android.monkeyrunner.recorder import MonkeyRecorder as recorder
03.device = mr.waitForConnection()
04.recorder.start(device)
- 1
- 2
- 3
- 4
- 5
回放脚本,recorder_playback.py:
01.import sys
02.from com.android.monkeyrunner import MonkeyRunner
03.CMD_MAP = {
04. 'TOUCH': lambda dev, arg: dev.touch(**arg),
05. 'DRAG': lambda dev, arg: dev.drag(**arg),
06. 'PRESS': lambda dev, arg: dev.press(**arg),
07. 'TYPE': lambda dev, arg: dev.type(**arg),
08. 'WAIT': lambda dev, arg: MonkeyRunner.sleep(**arg)
09. }
10.
11.# Process a single file for the specified device.
12.def process_file(fp, device):
13. for line in fp:
14. (cmd, rest) = line.split('|')
15. try:
16. # Parse the pydict
17. rest = eval(rest)
18. except:
19. print 'unable to parse options'
20. continue
21.
22. if cmd not in CMD_MAP:
23. print 'unknown command: ' + cmd
24. continue
25.
26. CMD_MAP[cmd](device, rest)
27.
28.
29.def main():
30. file = sys.argv[1]
31. fp = open(file, 'r')
32.
33. device = MonkeyRunner.waitForConnection()
34.
35. process_file(fp, device)
36. fp.close();
37.
38.if __name__ == '__main__':
39. main()
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
录制操作:
在cmd下输入monkeyrunner recorder.py,将打开下面的窗口,该窗口的功能:
1、可以自动显示手机当前的界面
2、自动刷新手机的最新状态
3、点击手机界面即可对手机进行操作,同时会反应到真机,而且会在右侧插入操作脚本
4:、wait: 用来插入下一次操作的时间间隔,点击后即可设置时间,单位是秒
Press a Button:用来确定需要点击的按钮,包括menu、home、search,以及对按钮的press、down、up属性
Type Something:用来输入内容到输入框
Fling:用来进行拖动操作,可以向上、下、左、右,以及操作的范围
Export Actions:用来导出脚本
Refresh Display:用来刷新手机界面,估计只有在断开手机后,重新连接时才会用到
5.在左侧手机视图上做的任何操作,在右侧都会进行脚本录制
上述实例录制的操作脚本如下:
TOUCH|{‘x’:187,’y’:356,’type’:’downAndUp’,}
PRESS|{‘name’:’MENU’,’type’:’downAndUp’,
WAIT|{‘seconds’:5.0,}
DRAG|{‘start’:(192,128),’end’:(192,640),’duration’:1.0,’steps’:10,}
回放操作:
回放:使用命令monkeyrunner recorder_playback.py record_test.py,其中record_test.py中是我们录制的脚本,这里需要使用绝对路径,即回放脚本和录制操作脚本都必须在Monkeyrunner.bat所在目录。
PS: 录制后的脚本可以进行二次更改,而且每一步操作需要有时间间隔,以保证测试的正确性
(4)MonkeyRunner常用事件
这里给大家罗列下Monkeyrunner的常用事件,基本上这些事件都能够满足我们日常写用例的需求了。
#monkeyrunner导入模块
from com.android.monkeyrunner import MonkeyRunner, MonkeyDevice, MonkeyImage
#monkeyrunner连接设备
device = MonkeyRunner.waitForConnection()
if not device:
print "Please connect a device to start!"
else:
print "Start "
#monkeyrunner启动一个Activity
componentname="com.ss.android.article.news/.activity.SplashActivity"
device.startActivity(component=componentName)
#monkeyrunner按键
发送指定键的关键事件: device.press(参数1:键码, 参数2:触摸事件类型)
参数1:常用键内容
按下HOME键 device.press('KEYCODE_HOME', MonkeyDevice.DOWN_AND_UP)
按下BACK键 device.press('KEYCODE_BACK', MonkeyDevice.DOWN_AND_UP)
按下下导航键 device.press('KEYCODE_DPAD_DOWN', MonkeyDevice.DOWN_AND_UP)
按下上导航键 device.press('KEYCODE_DPAD_UP', MonkeyDevice.DOWN_AND_UP)
按下OK键 device.press('KEYCODE_DPAD_CENTER', MonkeyDevice.DOWN_AND_UP)
按下左导航键 device.press('KEYCODE_DPAD_LEFT', MonkeyDevice.DOWN_AND_UP)
按下右导航键 device.press('KEYCODE_DPAD_RIGHT', MonkeyDevice.DOWN_AND_UP)
相应的按键对应名称:
menu键:KEYCODE_MENU
home键:KEYCODE_HOME
back键:KEYCODE_BACK
search键:KEYCODE_SEARCH
call键:KEYCODE_CALL
end键:KEYCODE_ENDCALL
上音量键:KEYCODE_VOLUME_UP
下音量键:KEYCODE_VOLUME_DOWN
power键:KEYCODE_POWER
camera键:KEYCODE_CAMERA
#monkeyrunner卸载包
device.removePackage ('com.example.android.notepad')
print ('卸载成功')
#monkeyrunner安装包
device.installPackage('ApiDemos.apk')
print ('安装成功')
#monkeyrunner单击控件
方式1:device.touch(507,72,"DOWN_AND_UP")
方式2:easy_device.touch(By.id('id/qingchu'),device.DOWN_AND_UP)
用后者需要导入
from com.android.chimpchat.hierarchyviewer import HierarchyViewer #根据ID找到ViewNode,对viewnode的一些操作等
from com.android.monkeyrunner.easy import EasyMonkeyDevice #提供了根据ID进行访问方法touch、drag等
from com.android.monkeyrunner.easy import By #根据ID返回PyObject的方法
from com.android.hierarchyviewerlib.models import ViewNode as vn #代表一个控件,可获取控件属性
#monkeyrunner长按控件
方式1:device.touch(507,72,"DOWN_AND_UP")
device.touch(507,72,MonkeyDevice.DOWN)
MonkeyRunner.sleep(1)
device.touch(507,72,MonkeyDevice.UP)
方式2:
easy_device.touch(By.id('id/qingchu'),,MonkeyDevice.DOWN)
MonkeyRunner.sleep(1)
easy_device.touch(By.id('id/qingchu'),MonkeyDevice.UP)
用后者需要导入
from com.android.chimpchat.hierarchyviewer import HierarchyViewer #根据ID找到ViewNode,对viewnode的一些操作等
from com.android.monkeyrunner.easy import EasyMonkeyDevice #提供了根据ID进行访问方法touch、drag等
from com.android.monkeyrunner.easy import By #根据ID返回PyObject的方法
from com.android.hierarchyviewerlib.models import ViewNode as vn #代表一个控件,可获取控件属性
#monkeyrunner滑动屏幕
for i in range(1,70):
device.drag((250,110),(250,850),0.1,10)
MonkeyRunner.sleep(1)
#monkeyrunner延时
MonkeyRunner.sleep(3)
#monkeyrunner截图
result = device.takeSnapshot()
result.writeToFile('C:\\Users\\Martin\\Desktop\\test.png','png')
#monkeyrunner截图对比
result1.sameAs(result0,1.0)
#monkeyrunner局部图片(前两个值是左上角左边,后两个值是右下角减左上角的坐标。)
pic0= result0.getSubImage((4,41,400,700))
#monkeyrunner重启设备
device.reboot()
#monkeyrunner单击电源键,熄灭屏幕
device.press('KEYCODE_POWER',MonkeyDevice.DOWN_AND_UP)
#monkeyrunner唤醒屏幕
device.wake()
#monkeyrunner输入文本
Cotent='1234'
device.type(Cotent)
相关阅读
from com.android.monkeyrunner import MonkeyRunner as mr from com.android.monkeyrunner import MonkeyDevice as md from com