声明:本文是Python-UiAutomator知识的梳理,更多更新信息,请参阅Xiaocong的github(https://github.com/xiaocong/uiautomator#uiautomator)。
十分感谢Xiaocong等大侠编写并分享这套Python-UiAutomator的库。
本文主要介绍如下内容,
一, UiAutomator是什么鬼?
二,Python-UiAutomator又是什么鬼?
三, Python-UiAutomator API介绍
四,重要查看工具:UiAutomatorViewer
五,Python编辑器推荐:Geany
让我们怀着一颗好奇的心,逐个看看我们刚刚列出的以上几条吧。
一, UiAutomator是什么鬼?
UiAutomator是google为Android平台开发的跨程序的测试类库(考点:跨程序),可以在Android Studio中用Java Script开发(很久很久以前,大家也用Eclipse开发)。
它支持Android 4.1以及更新的版本,低版本Android不能使用UiAutomator(现实中,很少有产品在使用Android 4及以下的版本了,所以影响很小)。
UiAutomator, 顾名思义主要是针对UI的自动化测试,不仅可以测试用户自己开发的程序,也可以测试系统自带的系统程序,如Setting等。
它支持”黑盒测试“,无需得到要测试程序的代码,就可以测试(这还得多亏了UiAutomatorViewer,这个稍后再讲)。
想获得更多的关于UiAutomator的信息,请移步至Android官网https://developer.android.com/training/testing/ui-automator.html
二,Python-UiAutomator又是什么鬼?
用过Android Studio/Eclipse开发UiAutomator的小伙伴都有体会,UiAutomaor虽然是个好东西,但是不好用。
聪明的程序员,对,就像Xiaocong那样的,为Python和UiAutomator架了一座桥,就是我们今天谈的Python-UiAutomator库,从而力保代码的简洁和优雅。
下面,先来一段Python-UiAutomator代码示例,
from uiautomator import device as d
d.screen.on()
d(text="Clock").click()
上面代码的作用是点亮屏幕,然后启动Clock程序。代码简洁利索,十分高效。
那么,如何安装Python-UiAutomator库呢?命令如下,十分简单
$ pip install uiautomator
如何将Pyhon-UiAutomator库导入自己的代码中呢?分一下几种情况,
1. 如果只有一台Android机器,使用下面的代码即可,
from uiautomator import device as d
2. 如果电脑连接多台Android机器,需要指定Serial Number,可以用下面的代码
from uiautomator import Device
d = Device('014E05DE0F02000E')
3. 如果Android机器连接在另外一条PC主机,则需要指定PC主机的地址和端口,代码如下,
from uiautomator import Device
d = Device('014E05DE0F02000E', adb_server_host='192.168.1.68', adb_server_port=5037)
关于xiaocong同学的大作,大家可以拜访他的github https:// github.com/xiaocong/uiautomator#uiautomator
(猜想Xiaocong应该和我们一样是黄皮肤黑眼睛滴:))
三, Python-UiAutomator API介绍
又该画重点了,关于API的介绍是必考噢,切记切记(高中某老师十分喜欢用这个词)
1. 先看看常用的基本API
1.1 获取机器的信息
代码如下,
d.info
下面是可能的结果(因机器不同而异),
{u'displayRotation': 0,
u'displaySizeDpY': 640,
u'displaySizeDpX': 360,
u'screenOn': True,
u'currentPackageName': u'com.android.launcher3',
u'productName': u'DeviceX',
u'displayWidth': 720,
u'sdkInt': 25,
u'displayHeight': 1280,
u'naturalOrientation': True}
1.2 屏幕相关的操作
开关屏幕,代码如下
# Turn on screen
d.screen.on()
# Turn off screen
d.screen.off()
效果等同如下代码,
# wakeup the device
d.wakeup()
# sleep the device, same as turning off the screen.
d.sleep()
查看屏幕是否点亮,代码如下,
if d.screen == "on": # of d.screen != "off"
# do something in case of screen on
pass
if d.screen == "off": # of d.screen != "on"
# do something in case of screen off
pass
1.3 按(软/硬)键操作
先上一小段代码如下,
# press home key
d.press.home()
# press back key
d.press.back()
# the normal way to press back key
d.press("back")
# press keycode 0x07('0') with META ALT(0x02) on
d.press(0x07, 0x02)
还支持如下按键的操作,
home
back
left
right
up
down
center
menu
search
enter
recent(recent apps)
volume_up
volume_down
volume_mute
camera
power
更多按键定义,请移步至Android官网
AndroidKeyEvent
1.4 手势相关的存在,包括 短按/长按/滑动/拖拽
短按操作,
# click (x, y) on screen
d.click(x, y)
长按操作,
# long click (x, y) on screen
d.long_click(x, y)
滑动操作,
# swipe from (sx, sy) to (ex, ey)
d.swipe(sx, sy, ex, ey)
# swipe from (sx, sy) to (ex, ey) with 10 steps
d.swipe(sx, sy, ex, ey, steps=10)
拖拽操作,
# drag from (sx, sy) to (ex, ey)
d.drag(sx, sy, ex, ey)
# drag from (sx, sy) to (ex, ey) with 10 steps
d.drag(sx, sy, ex, ey, steps=10)
1.5 屏幕相关的操作
获取并设置屏幕的旋转方向,
# retrieve orientation, it may be "natural" or "left" or "right" or "upsidedown"
orientation = d.orientation
# set orientation and freeze rotation.
# notes: "upsidedown" can not be set until Android 4.3.
d.orientation = "l" # or "left"
d.orientation = "r" # or "right"
d.orientation = "n" # or "natural"
冻结/解冻旋转功能,
# freeze rotation
d.freeze_rotation()
# un-freeze rotation
d.freeze_rotation(False)
屏幕截图,
# take screenshot and save to local file "home.png", can not work until Android 4.2.
d.screenshot("home.png")
获取屏幕层级(hierachy)XML
# dump the widown hierarchy and save to local file "hierarchy.xml"
d.dump("hierarchy.xml")
# or get the dumped content(unicode) from return.
xml = d.dump()
打开通知栏或快速设置栏,
# open notification, can not work until Android 4.3.
d.open.notification()
# open quick settings, can not work until Android 4.3.
d.open.quick_settings()
等待窗口休眠或更新,
# wait for current window to idle
d.wait.idle()
# wait until window update event occurs
d.wait.update()
2. Watcher
功能:开启守护进程,当符合条件时,完成特点的动作,如点击按钮等。
2.1 注册Watcher
示例一,
d.watcher("AUTO_FC_WHEN_ANR").when(text="ANR").when(text="Wait") \
.click(text="Force Close")
# d.watcher(name) ## creates a new named watcher.
# .when(condition) ## the UiSelector condition of the watcher.
# .click(target) ## perform click action on the target UiSelector.
示例二,
d.watcher("AUTO_FC_WHEN_ANR").when(text="ANR").when(text="Wait") \
.press.back.home()
# Alternative way to define it as below
d.watcher("AUTO_FC_WHEN_ANR").when(text="ANR").when(text="Wait") \
.press("back", "home")
# d.watcher(name) ## creates a new named watcher.
# .when(condition) ## the UiSelector condition of the watcher.
# .press.<keyname>.....<keyname>.() ## press keys one by one in sequence.
# Alternavie way defining key sequence is press(<keybname>, ..., <keyname>)
2.2 查看某个Watcher是否被触发
d.watcher("watcher_name").triggered
# true in case of the specified watcher triggered, else false
2.3 移除某个Watcher
# remove the watcher
d.watcher("watcher_name").remove()
2.4 列出所有已经注册的Watcher
d.watchers
# a list of all registered wachers' names
2.5 查看是否有任何一个Watcher曾经被触发
d.watchers.triggered
# true in case of any watcher triggered
2.6 复位所有已经被触发的Watcher
# reset all triggered watchers, after that, d.watchers.triggered will be false.
d.watchers.reset()
2.7 移除Watcher
# remove all registered watchers
d.watchers.remove()
# remove the named watcher, same as d.watcher("watcher_name").remove()
d.watchers.remove("watcher_name")
2.8 强制运行所有已经注册的Watcher
# force to run all registered watchers
d.watchers.run()
3. Handler
和Watcher类似,但可以呼叫回调函数,示例代码如下,
def fc_close(device):
if device(text='Force Close').exists:
device(text='Force Close').click()
return True # return True means to break the loop of handler callback functions.
# turn on the handler callback function
d.handlers.on(fc_close)
# turn off the handler callback function
d.handlers.off(fc_close)
4. 选择器(Selector)
选择器(Selector)是用来识别当前屏幕的对象,它可以通过对象的下列属性来识别,
- text, textContains, textMatches, textStartsWith
- className, classNameMatches
- description, descriptionContains, descriptionMatches, descriptionStartsWith
- checkable, checked, clickable, longClickable
- scrollable, enabled,focusable, focused, selected
- packageName, packageNameMatches
- resourceId, resourceIdMatches
- index, instance
示例代码如下,
# To seleted the object ,text is 'Clock' and its className is 'android.widget.TextView'
d(text='Clock', className='android.widget.TextView')
4.1 通过父子关系选择
# get the child or grandchild
d(className="android.widget.ListView").child(text="Bluetooth")
4.2 通过宗族关系选择
# get sibling or child of sibling
d(text="Google").sibling(className="android.widget.ImageView")
4.3 通过相对位置选择,支持left/right/up/down,
示例代码如下,
## select "switch" on the right side of "Wi‑Fi"
d(text="Wi‑Fi").right(className="android.widget.Switch").click()
4.4 检查当前屏幕UI对象是否存在
d(text="Settings").exists # True if exists, else False
d.exists(text="Settings") # alias of above property.
4.5 得到UI对象的信息
d(text="Settings").info
4.6 设置/清理可编辑文本的内容,
d(text="Settings").clear_text() # clear the text
d(text="Settings").set_text("My text...") # set the text
4.7 对UI对象点击
# click on the center of the specific ui object
d(text="Settings").click()
# click on the bottomright corner of the specific ui object
d(text="Settings").click.bottomright()
# click on the topleft corner of the specific ui object
d(text="Settings").click.topleft()
# click and wait until the new window update
d(text="Settings").click.wait()
4.8 对UI对象长按点击
# long click on the center of the specific ui object
d(text="Settings").long_click()
# long click on the bottomright corner of the specific ui object
d(text="Settings").long_click.bottomright()
# long click on the topleft corner of the specific ui object
d(text="Settings").long_click.topleft()
4.9 拖到UI对象到另外一个坐标或者另外一个UI对象
# notes : drag can not be set until Android 4.3.
# drag the ui object to point (x, y)
d(text="Settings").drag.to(x, y, steps=100)
# drag the ui object to another ui object(center)
d(text="Settings").drag.to(text="Clock", steps=50)
4.10 滑动(swipe),支持四个方向,left/right/up/down,
d(text="Settings").swipe.right()
d(text="Settings").swipe.left(steps=10)
d(text="Settings").swipe.up(steps=10)
d(text="Settings").swipe.down()
4.11 双指操作,
d(text="Settings").gesture((sx1, sy1), (sx2, sy2)) \
.to((ex1, ey1), (ex2, ey2))
4.12 双指操作,两手指往里捏(In),两手指往外捏(Out)
# notes : pinch can not be set until Android 4.3.
# from edge to center. here is "In" not "in"
d(text="Settings").pinch.In(percent=100, steps=10)
# from center to edge
d(text="Settings").pinch.Out()
4.13 三指手势
d().gestureM((sx1, sy1), (sx2, sy2),(sx3, sy3)) \
.to((ex1, ey1), (ex2, ey2),(ex3,ey3))
d().gestureM((100,200),(300,200),(600,200),(100,600),(300,600),(600,900))
4.14 等待UI对象的出现或者消失
# wait until the ui object appears
d(text="Settings").wait.exists(timeout=3000)
# wait until the ui object gone
d(text="Settings").wait.gone(timeout=1000)
4.15 滑动操作,支持水平和垂直滑动,
# fling forward(default) vertically(default)
d(scrollable=True).fling()
# fling forward horizentally
d(scrollable=True).fling.horiz.forward()
# fling backward vertically
d(scrollable=True).fling.vert.backward()
# fling to beginning horizentally
d(scrollable=True).fling.horiz.toBeginning(max_swipes=1000)
# fling to end vertically
d(scrollable=True).fling.toEnd()
4.16 滚到操作,支持水平和垂直滚动,××支持滚动到指定的UI对象××(必考,切记)
# scroll forward(default) vertically(default)
d(scrollable=True).scroll(steps=10)
# scroll forward horizentally
d(scrollable=True).scroll.horiz.forward(steps=100)
# scroll backward vertically
d(scrollable=True).scroll.vert.backward()
# scroll to beginning horizentally
d(scrollable=True).scroll.horiz.toBeginning(steps=100, max_swipes=1000)
# scroll to end vertically
d(scrollable=True).scroll.toEnd()
# scroll forward vertically until specific ui object appears
d(scrollable=True).scroll.to(text="Security")
四,重要查看工具:UiAutomatorViewer
本文开始有讲到,UiAutomator是黑盒测试,即使没有待测程序的源文件,依然可以进行自动化测试。
程序员没有通天的本领,不可能看看一个待测程序马上就像魔术师一样变出测试代码,所以,我们需要神器----UiAutomatorViewer。
有了它,我们可以把当前屏幕下所有的UI对象看的清清楚楚(穿墙看物)。
这个工具一般放在路径Sdk/tools/bin/uiautomatorviewer(Windows下加后缀.bat)
它有3部分组成,
1. 左边是当前屏幕截图,我们可以选中某个UI对象
2. 右上边是获得的当前层级(Hierarchy)资料,xml格式
3. 右下边是当前选中UI对象的属性
注意:只有当我们点击菜单栏地二个或者第三个图标(带机器人的)的时候,才会获取屏幕的截图和层级(Hierarchy)资料。
五,Python编辑器推荐:Geany
Windows下,比较推荐Geany编辑器,可以很方便完成编辑和编译。
下面简单介绍Geany的Python配置,
1. 点击“New (with Template)” ,打开文件 “main.py”
2. 点击“Build”,选择 “Set Build Commands”
3. 在"Compile"和“Execute”的栏位中,添加完整的python路径名。点击“OK”保存设置,至此设置完成。
4. 点击菜单栏“Execute”图标,运行Python代码,测试设置是否成功。
问&答:
问题一:下面示例中adb_server_host代表另外一台PC Host还是Android Device?
from uiautomator import Device
d = Device('014E05DE0F02000E', adb_server_host='192.168.1.68', adb_server_port=5037)
答:下面示例中的adb_server_host代表的是Android Device. PC Host 和Android
Device必须在同一个无线网络下,并且要设置Android Device端的TCP port才可以使用。
问题二:是否可以使用下面的代码实现模拟同时按下2个物理键的功能,比如 Power button + Volume Down?
# press home key
d.press.home()
# press back key
d.press.back()
# the normal way to press back key
d.press("back")
# press keycode 0x07('0') with META ALT(0x02) on
d.press(0x07, 0x02)
答:不可以,因为这样需要同时在d.press()中同时送出两个keycode且需要
指定duration, 目前的API没有这个功能。
问题三:关于Watcher,如果已注册的代码被执行过一次后,还会被自动触发吗?
d.watcher("AUTO_FC_WHEN_ANR").when(text="ANR").when(text="Wait") \
.click(text="Force Close")
# d.watcher(name) ## creates a new named watcher.
# .when(condition) ## the UiSelector condition of the watcher.
# .click(target) ## perform click action on the target UiSelector.
答:经过代码测试和网上查询案例,发现Python-UiAutomation的Watcher功能
有bug,已经注册的Wather不会自动触发。如果要实现一直被触发的功能,需要另外
运行一个线程不断呼叫d.watchers.run()。
本文结束!