一、环境要求
python 3.6+
android 4.4+
二、介绍
uiautomator2 是一个可以使用python对android设备进行ui自动化的库。其底层基于google uiautomator,google提供的uiautomator库可以获取屏幕上任意一个app的任意一个控件属性,并对其进行任意操作。
三、库地址
github地址:
https://github.com/openatx/uiautomator2
https://github.com/openatx/uiautomator2/blob/master/readme.md
四、安装
1、安装uiautomator2
1 |
pip install --pre uiautomator2 |
2 |
pip install pillow (如果需要截图,可安装这个库) |
2、设备安装atx-agent
首先设备连接到pc,并能够adb devices发现该设备。
执行下面的命令会自动安装本库所需要的设备端程序:uiautomator-server,atx-agent,openstf / minicap,openstf / minitouch
1 |
# init就是所有usb连接电脑的手机上都安装uiautomator2 |
2 |
python -m uiautomator2 init |
4 |
# 指定手机安装uiautomator2, 用 --mirror |
5 |
python -m uiautomator2 init --mirror --serial $serial |
8 |
python -m uiautomator2 init --mirror |
最后提示success,代表atx-agent初始化成功。
3、安装weditor
有了这个,方便我们快速的识别手机上的元素,方便写代码
安装好之后,就可以在命令行运行 weditor --help 确认是否安装成功了。
windows系统可以使用命令在桌面创建一个快捷方式:
在windows cmd中执行上述命令后,会在桌面上创建一个快捷方式,如下图:

启动方法:
方法1.命令行直接输入 weditor 会自动打开浏览器,输入设备的ip或者序列号,点击connect即可;
方法2.桌面上双击weditor快捷方式即可;
方法3.命令行中执行 python -m weditor
启动后如下图:

五、应用及操作
调用uiautomator2的过程
配置手机设备参数,设置具体操作的是哪一台手机
抓取手机上应用的控件,制定对应的控件来进行操作
对抓取到的控件进行操作,比如点击、填写参数等。
设备连接方法,有两种:
python-uiautomator2连接手机的方式有两种,一种是通过wifi,另外一种是通过usb。两种方法各有优缺点。
wifi最便利的地方要数可以不用连接数据线,usb则可以用在pc和手机网络不在一个网段用不了的情况。
(1)通过wifi,假设设备ip 192.168.0.107和您的pc在同一网络中
1 |
import uiautomator2 as u2 |
2 |
d = u2.connect('192.168.0.107') |
(2)通过usb, 假设设备序列是123456789f
1 |
import uiautomator2 as u2 |
2 |
d = u2.connect('123456789f') # usb链接设备。或者u2.connect_usb('123456f') |
3 |
#d = u2.connect_usb() 或者 d = u2.connect() ,当前只有一个设备时可以用这个 |
在没有参数的情况下调用u2.connect(), uiautomator2将从环境变量android_device_ip获取设备ip。如果这个环境变量是空的,uiautomator将返回connect_usb,您需要确保只有一个设备连接到计算机。
检查并维持设备端守护进程处于运行状态:
打开调试开关:
安装应用,只能从url安装:
启动应用:
1 |
d.app_start('com.eg.android.alipaygphone') #引号内为包名称,这里为支付宝 |
停止应用:
1 |
#相当于'am force-stop'强制停止应用 |
2 |
d.app_stop('com.eg.android.alipaygphone') |
5 |
d.app_clear('com.eg.android.alipaygphone') |
停止所有正在运行的应用程序:
4 |
# 停止所有应用程序,除了com.examples.demo |
5 |
d.app_stop_all(excludes=['com.examples.demo']) |
跳过弹窗,禁止弹窗:
1 |
d.disable_popups() # 自动跳过弹出窗口 |
2 |
d.disable_popups(false) # 禁用自动跳过弹出窗 |
获取设备信息:
05 |
print(d.window_size()) |
06 |
# 设备垂直输出示例: (1080, 1920) |
07 |
# 设备水平输出示例: (1920, 1080) |
09 |
# 获取当前应用程序信息。对于某些android设备,输出可以为空 |
10 |
print(d.current_app()) |
获取应用信息:
01 |
d.app_info("com.eg.android.alipaygphone") |
05 |
"packagename": "com.eg.android.alipaygphone", |
06 |
"mainactivity": "com.eg.android.alipaygphone.alipaylogin", |
08 |
"versionname": "10.2.13.9020", |
14 |
img = d.app_icon("com.eg.android.alipaygphone") |
推拉文件:
(1)将文件推送到设备
2 |
d.push("foo.txt", "/sdcard/") |
4 |
d.push("foo.txt", "/sdcard/bar.txt") |
6 |
with open("foo.txt", 'rb') as f: |
9 |
d.push("foo.sh", "/data/local/tmp/", mode=0o755) |
(2)从设备中拉出一个文件
1 |
d.pull("/sdcard/tmp.txt", "tmp.txt") |
3 |
# 如果在设备上找不到文件,filenotfounderror将引发 |
4 |
d.pull("/sdcard/some-file-not-exists.txt", "tmp.txt") |
关键事件:
(1)打开/关闭屏幕
(2)获取当前屏幕状态
1 |
d.info.get('screenon') # 需要 android> = 4.4 |
(3)硬键盘和软键盘操作
01 |
d.press("home") # 点击home键 |
02 |
d.press("back") # 点击back键 |
03 |
d.press("left") # 点击左键 |
04 |
d.press("right") # 点击右键 |
06 |
d.press("down") # 点击下键 |
07 |
d.press("center") # 点击选中 |
08 |
d.press("menu") # 点击menu按键 |
09 |
d.press("search") # 点击搜索按键 |
10 |
d.press("enter") # 点击enter键 |
11 |
d.press("delete") # 点击删除按键 |
12 |
d.press("recent") # 点击近期活动按键 |
13 |
d.press("volume_up") # 音量+ |
14 |
d.press("volume_down") # 音量- |
15 |
d.press("volume_mute") # 静音 |
16 |
d.press("camera") # 相机 |
(4)解锁屏幕
3 |
# 1. 发射活动:com.github.uiautomator.action_identify |
手势与设备的交互:
02 |
d.click(x,y) # x,y为点击坐标 |
06 |
d.double_click(x,y,0.1) # 默认两个单击之间间隔时间为0.1秒 |
10 |
d.long_click(x,y,0.5) # 长按0.5秒(默认) |
13 |
d.swipe(sx, sy, ex, ey) |
14 |
d.swipe(sx, sy, ex, ey, 0.5) #滑动0.5s(default) |
17 |
d.drag(sx, sy, ex, ey) |
18 |
d.drag(sx, sy, ex, ey, 0.5)#拖动0.5s(default) |
19 |
# 滑动点 多用于九宫格解锁,提前获取到每个点的相对坐标(这里支持百分比) |
21 |
# 从点(x0, y0)滑到点(x1, y1)再滑到点(x2, y2) |
23 |
d.swipe((x0, y0), (x1, y1), (x2, y2), 0.2) |
24 |
# 注意:单击,滑动,拖动操作支持百分比位置值。例: |
25 |
d.long_click(0.5, 0.5) 表示长按屏幕中心 |
xpath:
03 |
# 检索方向。输出可以是 "natural" or "left" or "right" or "upsidedown" |
06 |
d.set_orientation("l") # or "left" |
07 |
d.set_orientation("r") # or "right" |
08 |
d.set_orientation("n") # or "natural" |
11 |
d.freeze_rotation() # 冻结旋转 |
12 |
d.freeze_rotation(false) # 开启旋转 |
14 |
########## 截图 ############ |
15 |
# 截图并保存到电脑上的一个文件中,需要android>=4.2。 |
16 |
d.screenshot("home.jpg") |
18 |
# 得到pil.image格式的图像. 但你必须先安装pillow |
19 |
image = d.screenshot() # default format="pillow" |
20 |
image.save("home.jpg") # 或'home.png',目前只支持png 和 jpg格式的图像 |
22 |
# 得到opencv的格式图像。当然,你需要numpy和cv2安装第一个 |
24 |
image = d.screenshot(format='opencv') |
25 |
cv2.imwrite('home.jpg', image) |
28 |
imagebin = d.screenshot(format='raw') |
29 |
open("some.jpg", "wb").write(imagebin) |
31 |
############################# |
34 |
# get the ui hierarchy dump content (unicoded).(获取ui层次结构转储内容) |
39 |
d.open_notification() #下拉打开通知栏 |
40 |
d.open_quick_settings() #下拉打开快速设置栏 |
43 |
d(text="settings").exists # 返回布尔值,如果存在则为true,否则为false |
44 |
d.exists(text="settings") # 另一种写法 |
46 |
d(text="settings").exists(timeout=3) # 等待'settings'在3秒钟出现 |
49 |
d(text="settings").info |
51 |
# 获取/设置/清除可编辑字段的文本(例如edittext小部件) |
52 |
d(text="settings").get_text() #得到文本小部件 |
53 |
d(text="settings").set_text("my text...") #设置文本 |
54 |
d(text="settings").clear_text() #清除文本 |
57 |
d(text="settings").center() |
58 |
#d(text="settings").center(offset=(0, 0)) # 基准位置左前 |
ui对象有五种定位方式:
01 |
# text、resourceid、description、classname、xpath、坐标 |
05 |
d(text="settings").click() |
06 |
d(text="settings", classname="android.widget.textview").click() |
09 |
d(resourceid="com.ruguoapp.jike:id/tv_title", classname="android.widget.textview").click() |
12 |
d(description="设置").click() |
13 |
d(description="设置", classname="android.widget.textview").click() |
16 |
d(classname="android.widget.textview").click() |
19 |
d.xpath("//android.widget.framelayout[@index='0']/android.widget.linearlayout[@index='0']").click() |
25 |
d(text="settings").click(timeout=10) |
27 |
d(text='skip').click_exists(timeout=10.0) |
29 |
d(text="skip").click_gone(maxretry=10, interval=1.0) # maxretry默认值10,interval默认值1.0 |
31 |
d(text="settings").click(offset=(0.5, 0.5)) # 点击中心位置,同d(text="settings").click() |
32 |
d(text="settings").click(offset=(0, 0)) # 点击左前位置 |
33 |
d(text="settings").click(offset=(1, 1)) # 点击右下 |
36 |
d(text="设置").double_click() # 双击特定ui对象的中心 |
37 |
d.double_click(x, y, 0.1) # 两次单击之间的默认持续时间为0.1秒 |
41 |
d(text="settings").long_click() |
42 |
d.long_click(x, y, 0.5) # 长按坐标位置0.5s默认 |
45 |
# android<4.3不能使用drag. |
46 |
# 在0.5秒内将ui对象拖到屏幕点(x, y) |
47 |
d(text="settings").drag_to(x, y, duration=0.5) |
49 |
# 将ui对象拖到另一个ui对象的中心位置,时间为0.25秒 |
50 |
d(text="settings").drag_to(text="clock", duration=0.25) |
常见用法:
02 |
d.xpath("//android.widget.textview").wait(10.0) |
05 |
d.xpath("//*[@content-desc='分享']").click() |
08 |
if d.xpath("//android.widget.textview[contains(@text, 'se')]").exists: |
12 |
for elem in d.xpath("//android.widget.textview").all(): |
13 |
print("text:", elem.text) |
16 |
for elem in d.xpath("//android.widget.textview").all(): |
17 |
print("attrib:", elem.attrib) |
21 |
for elem in d.xpath("//android.widget.textview").all(): |
22 |
print("position:", elem.center()) |
28 |
# resource-id包含login字符 |
29 |
//*[contains(@resource-id, 'login')] |
32 |
//android.widget.button[contains(@text, '账号') or contains(@text, '帐号')] |
35 |
(//android.widget.imageview)[2] |
38 |
(//android.widget.imageview)[last()] |
40 |
# classname包含imageview |
41 |
//*[contains(name(), "imageview")] |