uiautomator简介

UiAutomator是Google提供的用来做安卓自动化测试的一个Java库,基于Accessibility服务。功能很强,可以对第三方App进行测试,获取屏幕上任意一个APP的任意一个控件属性,并对其进行任意操作,但有两个缺点:1. 测试脚本只能使用Java语言 2. 测试脚本要打包成jar或者apk包上传到设备上才能运行

uiautomator2是对uiautomator的改进,使其能够用Python编写,能够在电脑上运行的时候就控制手机,原理是在手机上运行了一个http rpc服务,将uiautomator中的功能开放出来,然后再将这些http接口封装成Python库

github地址:https://github.com/openatx/uiautomator2

uiautomator2 除了对原有的库的bug进行了修复,还增加了很多新的Feature。主要有以下部分:

  • 设备和开发机可以脱离数据线,通过WiFi互联(基于atx-agent)
  • 集成了openstf/minicap达到实时屏幕投频,以及实时截图
  • 集成了openstf/minitouch达到精确实时控制设备
  • 修复了xiaocong/uiautomator经常性退出的问题
  • 代码进行了重构和精简,方便维护
  • 实现了一个设备管理平台(也支持iOS) atxserver2
  • 扩充了toast获取和展示的功能

安装

安装 uiautomator2

pip install --upgrade --pre uiautomator2   # 因为uiautomator2仍在开发中,你必须添加——pre来安装开发版本

测试是否安装成功 uiautomator2 --help

安装 weditor (UI Inspector)

因为uiautomator是独占资源,所以当atx运行的时候uiautomatorviewer是不能用的,为了减少atx频繁的启停,可以使用基于浏览器技术的weditor UI查看器。https://github.com/openatx/weditor

安装方法(备注: 目前最新的稳定版为 0.1.0)

pip install -U weditor

安装 daemons to a device (Optional)

电脑连接上一个手机或多个手机, 确保adb已经添加到环境变量中,执行下面的命令会自动安装本库所需要的设备端程序:uiautomator-serveratx-agentopenstf/minicapopenstf/minitouch

# 初始化 所有的已经连接到电脑的设备
python -m uiautomator2 init

安装提示success即可

UiAutomator 常用命令

命令行常用命令

  • screenshot: 截图

    uiautomator2 screenshot screenshot.jpg  #图片存放在当前路径
  • current: 获取当前包名和activity

    $ uiautomator2 current
    {
    "package": "com.xueqiu.android",
    "activity": "com.xueqiu.android.stockmodule.stockdetail.StockDetailActivity"
    }
  • install: 安装apk

    $ uiautomator2 install  com.android.chrome_81.0.4044.117_404411700.apk
    [D 221119 15:09:15 __init__:1295] pm install -rt /data/local/tmp/_tmp.apk
    Installed None

    官方说不能使用,但我实测可以安装成功

  • uninstall: 卸载

    $ uiautomator2 uninstall <package-name> # 卸载一个包
    $ uiautomator2 uninstall <package-name-1> <package-name-2> # 卸载多个包
    $ uiautomator2 uninstall --all # 全部卸载
    $ uiautomator2 uninstall com.android.chrome
    Uninstall "com.android.chrome" OK
  • stop: 停止应用

    $ uiautomator2 stop com.example.app # 停止一个app
    $ uiautomator2 stop --all # 停止所有的app
    $ uiautomator2 stop com.xueqiu.android
    am force-stop "com.xueqiu.android"

设置超时时间

在假设客户端退出并结束uiautomator服务之前,等待来自客户端的新命令的时间(秒)(默认为3分钟)

d.set_new_command_timeout(300)  # 改为5分钟,单位为s

开启debug模式

d.debug = True
print(d.info)
15:37:11.340 $ curl -X POST -d '{"jsonrpc": "2.0", "id": "c78bf70891f2fb0b99b082a40faa6e76", "method": "deviceInfo", "params": {}}' 'http://127.0.0.1:56258/jsonrpc/0'
15:37:11.483 Response (142 ms) >>>
{"jsonrpc":"2.0","id":"c78bf70891f2fb0b99b082a40faa6e76","result":{"currentPackageName":"com.xueqiu.android","displayHeight":1872,"displayRotation":0,"displaySizeDpX":450,"displaySizeDpY":720,"displayWidth":1170,"productName":"cancro_x64","screenOn":true,"sdkInt":23,"naturalOrientation":true}}
<<< END
{'currentPackageName': 'com.xueqiu.android', 'displayHeight': 1872, 'displayRotation': 0, 'displaySizeDpX': 450, 'displaySizeDpY': 720, 'displayWidth': 1170, 'productName': 'cancro_x64', 'screenOn': True, 'sdkInt': 23, 'naturalOrientation': True}

设置Implicit wait

设置元素查找等待时间(默认20s)

d.implicitly_wait(10.0)   # 也可以通过d.settings['wait_timeout'] = 10.0 修改
d(text="Settings").click() # 如果元素在10秒内没有找到,报UiObjectNotFoundError print("wait timeout", d.implicitly_wait()) # 获取隐身等待时间

app 管理

安装APP

d.app_install('https://down11.zol.com.cn/liaotian/huoshan15.2.0w.apk')  # 支持从URL或本文文件安装APK
# [D 221119 15:58:54 __init__:1295] pm install -rt /data/local/tmp/_tmp.apk

启动APP

# 默认的这种方法是先通过atx-agent解析apk包的mainActivity,然后调用am start -n $package/$activity启动
d.app_start("com.xueqiu.android")
d.app_start("com.xueqiu.android" ,stop=True) # 启动应用前停止应用 # 使用 monkey -p com.example.hello_world -c android.intent.category.LAUNCHER 1 启动
# 这种方法有个副作用,它自动会将手机的旋转锁定给关掉
d.app_start("com.example.hello_world", use_monkey=True) # start with package name # 通过指定main activity的方式启动应用,等价于调用am start -n com.example.hello_world/.MainActivity
d.app_start("com.example.hello_world", ".MainActivity")

关闭APP

d.app_stop("com.xueqiu.android")
# 相当于`am force-stop`,因此你可能会丢失数据 d.app_clear("com.xueqiu.android")
# 相当于 `pm clear`

关闭所有正则运行的APP

d.app_stop_all()  # 停止所有APP

d.app_stop_all(excludes=['com.xueqiu.android']) # 停止除了com.xueqiu.android之外的所有APP

获取APP信息

print(d.app_info('com.xueqiu.android'))
# {'packageName': 'com.xueqiu.android', 'mainActivity': 'com.xueqiu.android.common.splash.SplashActivity', 'label': '雪球股票', 'versionName': '12.18.1', 'versionCode': 280, 'size': 88270498} img = d.app_icon("com.xueqiu.android") # 获取APP图标
img.save('xueqiu.jpg')

获取正在运行的APP

print(d.app_list_running())
# ['com.github.uiautomator', 'com.android.systemui', 'com.xueqiu.android']

等待应用程序运行

pid = d.app_wait('com.xueqiu.android')   #等待APP运行,返回PID(不会自动运行APP)
if pid:
print(f"com.xueqiu.android 运行成功")
else:
print("运行失败")
d.app_wait("com.example.android", front=True) # 等待应用前台运行
d.app_wait("com.example.android", timeout=20.0) # 最长等待时间20s(默认)

上传下载文件

上传文件

d.push('xueqiu.jpg','/sdcard/')   # 上传文件

d.push('xueqiu.jpg','/sdcard/xue.jpg')  # 上传文件并改名

with open("xueqiu.jpg", 'rb') as f:
d.push(f, "/sdcard/") # 以文件对象方式上传 d.push("xueqiu.jpg", "/data/local/tmp/", mode=0o755) # 上传并更改文件访问模式

下载文件

d.pull('/data/local/tmp/xueqiu.mp4','xueqiu.mp4')

指定APP打开链接

d.open_url("https://www.baidu.com")
d.open_url("taobao://taobao.com") # open Taobao app
d.open_url("appname://appnamehost")

执行shell命令

d.shell("pwd", timeout=60)  # 设置超时时间,默认60s

print(d.shell("pwd")) # ShellResponse(output='/\n', exit_code=0)

output = d.shell("pwd").output  # 命令执行结果
exit_code = d.shell("pwd").exit_code # 命令是否正确执行 d.shell(["ls", "-l"]) # 长命令以列表方式输入

当执行需要长期输出的命令时,需加stream=True,否则代码讲一直在执行,直到超时失败

import time
r = d.shell("logcat",stream=True) deadline = time.time() + 10 # run maxium 10s
try:
for line in r.iter_lines(): # r.iter_lines(chunk_size=512, decode_unicode=None, delimiter=None)
if time.time() > deadline:
break
print(line.decode("utf-8"))
finally:
r.close() # 执行此命令后终止任务

Session

Session 代表一个应用程序生命周期。可以用来启动应用程序,检测应用程序崩溃

目前session方法以停用,预计在3.0版本中再启用

启动和关闭APP

sess = d.session('com.xueqiu.android') # 启动雪球APP
sess.app_stop('com.xueqiu.android') # 停止

基本操作

基本命令

print(d.info)  # 设备基本信息
# {'currentPackageName': 'com.mumu.launcher', 'displayHeight': 1872, 'displayRotation': 0, 'displaySizeDpX': 450, 'displaySizeDpY': 720, 'displayWidth': 1170, 'productName': 'cancro_x64', 'screenOn': True, 'sdkInt': 23, 'naturalOrientation': True}
print(d.window_size()) # 屏幕大小
print(d.app_current()) # 当前APP基本信息 {'package': 'com.xueqiu.android', 'activity': 'com.xueqiu.android.main.view.MainActivity'}
print(d.wait_activity("com.xueqiu.android.main.view.MainActivity", timeout=10)) # 等待当前页面活动 Output: true of false
print(d.serial) # 当前设置名称 与adb devices 中设置名称一致
print(d.wlan_ip) # 获取设置IP地址
print(d.device_info) # 获取设备详细信息 d.set_clipboard('text', 'label') # 设置粘贴板内容
print(d.clipboard) # 获取粘贴板内容

屏幕操作

d.screen_on() # 打开屏幕
d.screen_off() # 关闭屏幕
print(d.info.get('screenOn')) # 获取当前屏幕状态 d.press("home") # 按home键
d.press("back") # 按返回键
d.press(0x07, 0x02) # press keycode 0x07('0') with META ALT(0x02)
d.unlock() # 解锁屏幕
d.click(100, 200) # 点击坐标点
d.double_click(100,200,0.1) # 双击默认情况下,两次点击的间隔时间为0.1s
d.long_click(100, 200, 0.5) # 长按,默认按0.5s
d.long_click(0.5, 0.5) # 长时间点击屏幕中央 注:点击、滑动、拖动操作支持百分比
d.swipe(580, 1400, 580, 400, 0.5) # 滑动, 默认滑动速度0.5s

SwipeExt 扩展功能

d.swipe_ext("right") # 手指右滑,4选1 "left", "right", "up", "down"
d.swipe_ext("right", scale=0.9) # 默认0.9, 滑动距离为屏幕宽度的90%
d.swipe_ext("right", box=(0, 0, 100, 100)) # 在 (0,0) -> (100, 100) 这个区域做滑动
# # 实践发现上滑或下滑的时候,从中点开始滑动成功率会高一些
d.swipe_ext("up", scale=0.8) # 还可以使用Direction作为参数
from uiautomator2 import Direction d.swipe_ext(Direction.FORWARD) # 页面下翻, 等价于 d.swipe_ext("up"), 只是更好理解
d.swipe_ext(Direction.BACKWARD) # 页面上翻
d.swipe_ext(Direction.HORIZ_FORWARD) # 页面水平右翻
d.swipe_ext(Direction.HORIZ_BACKWARD) # 页面水平左翻 d.drag(288, 880, 800, 880) # 拖动
d.drag(288, 880, 800, 880, 0.5) # 设置拖动时间0.5s ,默认0.5s # 点击和移动
# d.swipe_points([(x0, y0), (x1, y1), (x2, y2)], 0.2) # 从点(x0, y0)到点(x1, y1)再到点(x2, y2),两点之间的时间速度是0.2秒
d.swipe_points([(300, 700), (700, 700), (700, 1400)], 0.2) # 点击和移动
# 这个接口属于比较底层的原始接口,感觉并不完善,不过凑合能用。注:这个地方并不支持百分比
d.touch.down(10, 10) # 模拟按下
time.sleep(.01) # down 和 move 之间的延迟,自己控制
d.touch.move(15, 15) # 模拟移动
d.touch.up() # 模拟抬起

屏幕设置


# 旋转屏幕 注,我用模拟器没有测试通过
d.set_orientation('l') # or "left"
d.set_orientation("u") # or "upsidedown"
d.set_orientation("r") # or "right"
d.set_orientation("n") # or "natural" d.freeze_rotation() # 冻结旋转
d.freeze_rotation(False) # 解除冻结 # 屏幕截图
d.screenshot("home.jpg") # 屏幕截图 # image方法保存截图
image = d.screenshot()
image.save("home.jpg") # opencv 方法保存解脱
import cv2
image = d.screenshot(format='opencv')
cv2.imwrite('home.jpg', image) # open 方法保存截图
imagebin = d.screenshot(format='raw')
open("some.jpg", "wb").write(imagebin) # 获取DOM树
xml = d.dump_hierarchy()
print(xml) d.open_notification() # 打开通知消息
d.open_quick_settings() # 打开快捷设置

选择器

选择器是一种方便的机制,可以在当前窗口中标识特定的UI对象。

演示用例:

<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
<hierarchy rotation="0">
<node index="0" text="" resource-id="" class="android.widget.FrameLayout" package="com.android.systemui" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[0,0][1170,62]">
<node index="1" text="" resource-id="com.android.systemui:id/status_bar" class="android.widget.FrameLayout" package="com.android.systemui" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[0,0][1170,62]">
<node index="0" text="" resource-id="com.android.systemui:id/status_bar_contents" class="android.widget.LinearLayout" package="com.android.systemui" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[0,0][1170,62]">
<node index="0" text="" resource-id="com.android.systemui:id/notification_icon_area" class="android.widget.FrameLayout" package="com.android.systemui" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[16,0][1007,62]">
<node index="0" text="" resource-id="com.android.systemui:id/notification_icon_area_inner" class="android.widget.LinearLayout" package="com.android.systemui" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[16,0][1007,62]">
<node index="0" text="" resource-id="com.android.systemui:id/notificationIcons" class="android.widget.LinearLayout" package="com.android.systemui" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[16,0][946,62]">
<node index="0" text="" resource-id="" class="android.widget.ImageView" package="com.android.systemui" content-desc="UIAutomator service started" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[25,9][69,53]" />
</node>
</node>
</node>
<node index="1" text="" resource-id="com.android.systemui:id/system_icon_area" class="android.widget.LinearLayout" package="com.android.systemui" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[1007,0][1149,62]">
<node index="0" text="" resource-id="com.android.systemui:id/system_icons" class="android.widget.LinearLayout" package="com.android.systemui" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[1007,0][1079,62]">
<node index="1" text="" resource-id="com.android.systemui:id/signal_cluster" class="android.widget.LinearLayout" package="com.android.systemui" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[1014,9][1079,53]">
<node index="0" text="" resource-id="com.android.systemui:id/wifi_combo" class="android.widget.FrameLayout" package="com.android.systemui" content-desc="WLAN 信号满格。" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[1014,9][1061,53]">
<node index="1" text="" resource-id="com.android.systemui:id/wifi_signal" class="android.widget.ImageView" package="com.android.systemui" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[1014,9][1061,53]" />
</node>
</node>
</node>
<node index="1" text="1:31" resource-id="com.android.systemui:id/clock" class="android.widget.TextView" package="com.android.systemui" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[1079,0][1149,62]" />
</node>
</node>
</node>
<node index="2" text="" resource-id="com.android.systemui:id/scrim_behind" class="android.view.View" package="com.android.systemui" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[0,0][1170,62]" />
<node index="3" text="" resource-id="com.android.systemui:id/panel_holder" class="android.widget.FrameLayout" package="com.android.systemui" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[0,0][1170,62]" />
<node index="4" text="" resource-id="com.android.systemui:id/scrim_in_front" class="android.view.View" package="com.android.systemui" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[0,0][1170,62]" />
</node>
<node index="0" text="" resource-id="" class="android.widget.FrameLayout" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[0,0][1170,1872]">
<node index="0" text="" resource-id="android:id/decor_content_parent" class="android.view.ViewGroup" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[0,0][1170,1872]">
<node index="0" text="" resource-id="android:id/action_bar_container" class="android.widget.FrameLayout" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[0,62][1170,208]">
<node index="0" text="" resource-id="android:id/action_bar" class="android.view.ViewGroup" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[0,62][1170,208]">
<node index="0" text="设置" resource-id="" class="android.widget.TextView" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[41,100][145,170]" />
<node index="1" text="" resource-id="" class="android.widget.LinearLayout" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[1045,62][1170,208]">
<node index="0" text="" resource-id="com.android.settings:id/search" class="android.widget.TextView" package="com.android.settings" content-desc="搜索设置" checkable="false" checked="false" clickable="true" enabled="true" focusable="true" focused="false" scrollable="false" long-clickable="true" password="false" selected="false" visible-to-user="true" bounds="[1045,72][1170,197]" />
</node>
</node>
</node>
<node index="1" text="" resource-id="android:id/content" class="android.widget.FrameLayout" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[0,208][1170,1872]">
<node index="0" text="" resource-id="com.android.settings:id/main_content" class="android.widget.FrameLayout" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[0,208][1170,1872]">
<node index="0" text="" resource-id="com.android.settings:id/dashboard" class="android.widget.ScrollView" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="true" focused="false" scrollable="true" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[0,208][1170,1872]">
<node index="0" text="" resource-id="com.android.settings:id/dashboard_container" class="android.widget.LinearLayout" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[0,208][1170,1872]">
<node index="0" text="" resource-id="com.android.settings:id/category" class="android.widget.LinearLayout" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[0,208][1170,894]">
<node index="0" text="无线和网络" resource-id="com.android.settings:id/category_title" class="android.widget.TextView" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[0,208][1170,333]" />
<node index="1" text="" resource-id="com.android.settings:id/category_content" class="android.view.ViewGroup" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[0,333][1170,894]">
<node index="0" text="" resource-id="" class="android.widget.FrameLayout" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="true" enabled="true" focusable="true" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[0,333][1170,520]">
<node index="0" text="" resource-id="" class="android.widget.LinearLayout" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[0,333][1170,520]">
<node index="0" text="" resource-id="com.android.settings:id/icon" class="android.widget.ImageView" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[42,395][104,457]" />
<node index="1" text="" resource-id="" class="android.widget.LinearLayout" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[187,333][1170,520]">
<node index="0" text="" resource-id="" class="android.widget.LinearLayout" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[187,333][1170,517]">
<node index="0" text="" resource-id="" class="android.widget.RelativeLayout" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[187,396][304,453]">
<node index="0" text="WLAN" resource-id="com.android.settings:id/title" class="android.widget.TextView" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[187,396][304,453]" />
</node>
</node>
<node index="1" text="" resource-id="com.android.settings:id/tile_divider" class="android.view.View" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[187,517][1170,520]" />
</node>
</node>
</node>
<node index="1" text="" resource-id="" class="android.widget.FrameLayout" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="true" enabled="true" focusable="true" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[0,520][1170,707]">
<node index="0" text="" resource-id="" class="android.widget.LinearLayout" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[0,520][1170,707]">
<node index="0" text="" resource-id="com.android.settings:id/icon" class="android.widget.ImageView" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[42,582][104,644]" />
<node index="1" text="" resource-id="" class="android.widget.LinearLayout" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[187,520][1170,707]">
<node index="0" text="" resource-id="" class="android.widget.LinearLayout" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[187,520][1170,704]">
<node index="0" text="" resource-id="" class="android.widget.RelativeLayout" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[187,583][271,640]">
<node index="0" text="蓝牙" resource-id="com.android.settings:id/title" class="android.widget.TextView" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[187,583][271,640]" />
</node>
</node>
<node index="1" text="" resource-id="com.android.settings:id/tile_divider" class="android.view.View" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[187,704][1170,707]" />
</node>
</node>
</node>
<node index="2" text="" resource-id="" class="android.widget.FrameLayout" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="true" enabled="true" focusable="true" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[0,707][1170,894]">
<node index="0" text="" resource-id="" class="android.widget.LinearLayout" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[0,707][1170,894]">
<node index="0" text="" resource-id="com.android.settings:id/icon" class="android.widget.ImageView" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[42,769][104,831]" />
<node index="1" text="" resource-id="" class="android.widget.LinearLayout" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[187,707][1170,894]">
<node index="0" text="" resource-id="" class="android.widget.LinearLayout" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[187,707][1170,894]">
<node index="0" text="" resource-id="" class="android.widget.RelativeLayout" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[187,772][271,829]">
<node index="0" text="更多" resource-id="com.android.settings:id/title" class="android.widget.TextView" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[187,772][271,829]" />
</node>
</node>
</node>
</node>
</node>
</node>
</node>
<node index="1" text="" resource-id="com.android.settings:id/category" class="android.widget.LinearLayout" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[0,915][1170,1872]">
<node index="0" text="设备" resource-id="com.android.settings:id/category_title" class="android.widget.TextView" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[0,915][1170,1040]" />
<node index="1" text="" resource-id="com.android.settings:id/category_content" class="android.view.ViewGroup" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[0,1040][1170,1872]">
<node index="0" text="" resource-id="" class="android.widget.FrameLayout" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="true" enabled="true" focusable="true" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[0,1040][1170,1227]">
<node index="0" text="" resource-id="" class="android.widget.LinearLayout" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[0,1040][1170,1227]">
<node index="0" text="" resource-id="com.android.settings:id/icon" class="android.widget.ImageView" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[42,1102][104,1164]" />
<node index="1" text="" resource-id="" class="android.widget.LinearLayout" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[187,1040][1170,1227]">
<node index="0" text="" resource-id="" class="android.widget.LinearLayout" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[187,1040][1170,1224]">
<node index="0" text="" resource-id="" class="android.widget.RelativeLayout" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[187,1103][271,1160]">
<node index="0" text="显示" resource-id="com.android.settings:id/title" class="android.widget.TextView" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[187,1103][271,1160]" />
</node>
</node>
<node index="1" text="" resource-id="com.android.settings:id/tile_divider" class="android.view.View" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[187,1224][1170,1227]" />
</node>
</node>
</node>
<node index="1" text="" resource-id="" class="android.widget.FrameLayout" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="true" enabled="true" focusable="true" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[0,1227][1170,1414]">
<node index="0" text="" resource-id="" class="android.widget.LinearLayout" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[0,1227][1170,1414]">
<node index="0" text="" resource-id="com.android.settings:id/icon" class="android.widget.ImageView" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[42,1289][104,1351]" />
<node index="1" text="" resource-id="" class="android.widget.LinearLayout" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[187,1227][1170,1414]">
<node index="0" text="" resource-id="" class="android.widget.LinearLayout" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[187,1227][1170,1411]">
<node index="0" text="" resource-id="" class="android.widget.RelativeLayout" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[187,1290][439,1347]">
<node index="0" text="提示音和通知" resource-id="com.android.settings:id/title" class="android.widget.TextView" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[187,1290][439,1347]" />
</node>
</node>
<node index="1" text="" resource-id="com.android.settings:id/tile_divider" class="android.view.View" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[187,1411][1170,1414]" />
</node>
</node>
</node>
<node index="2" text="" resource-id="" class="android.widget.FrameLayout" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="true" enabled="true" focusable="true" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[0,1414][1170,1601]">
<node index="0" text="" resource-id="" class="android.widget.LinearLayout" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[0,1414][1170,1601]">
<node index="0" text="" resource-id="com.android.settings:id/icon" class="android.widget.ImageView" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[42,1476][104,1538]" />
<node index="1" text="" resource-id="" class="android.widget.LinearLayout" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[187,1414][1170,1601]">
<node index="0" text="" resource-id="" class="android.widget.LinearLayout" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[187,1414][1170,1598]">
<node index="0" text="" resource-id="" class="android.widget.RelativeLayout" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[187,1477][271,1534]">
<node index="0" text="应用" resource-id="com.android.settings:id/title" class="android.widget.TextView" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[187,1477][271,1534]" />
</node>
</node>
<node index="1" text="" resource-id="com.android.settings:id/tile_divider" class="android.view.View" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[187,1598][1170,1601]" />
</node>
</node>
</node>
<node index="3" text="" resource-id="" class="android.widget.FrameLayout" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="true" enabled="true" focusable="true" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[0,1601][1170,1788]">
<node index="0" text="" resource-id="" class="android.widget.LinearLayout" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[0,1601][1170,1788]">
<node index="0" text="" resource-id="com.android.settings:id/icon" class="android.widget.ImageView" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[42,1663][104,1725]" />
<node index="1" text="" resource-id="" class="android.widget.LinearLayout" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[187,1601][1170,1788]">
<node index="0" text="" resource-id="" class="android.widget.LinearLayout" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[187,1601][1170,1785]">
<node index="0" text="" resource-id="" class="android.widget.RelativeLayout" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[187,1664][397,1721]">
<node index="0" text="应用兼容性" resource-id="com.android.settings:id/title" class="android.widget.TextView" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[187,1664][397,1721]" />
</node>
</node>
<node index="1" text="" resource-id="com.android.settings:id/tile_divider" class="android.view.View" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[187,1785][1170,1788]" />
</node>
</node>
</node>
<node index="4" text="" resource-id="" class="android.widget.FrameLayout" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="true" enabled="true" focusable="true" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[0,1788][1170,1872]">
<node index="0" text="" resource-id="" class="android.widget.LinearLayout" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[0,1788][1170,1872]">
<node index="0" text="" resource-id="com.android.settings:id/icon" class="android.widget.ImageView" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[42,1850][104,1872]" />
<node index="1" text="" resource-id="" class="android.widget.LinearLayout" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[187,1788][1170,1872]">
<node index="0" text="" resource-id="" class="android.widget.LinearLayout" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[187,1788][1170,1872]">
<node index="0" text="" resource-id="" class="android.widget.RelativeLayout" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[187,1851][485,1872]">
<node index="0" text="存储设备和 USB" resource-id="com.android.settings:id/title" class="android.widget.TextView" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[187,1851][485,1872]" />
</node>
</node>
</node>
</node>
</node>
</node>
</node>
</node>
</node>
</node>
</node>
</node>
</node>
</hierarchy>

选择器支持以下参数。详细信息请参考 UiSelector Java文档。

  • 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

父子节点和兄弟节点选择

父子节点

# 后去子节点或孙子节点
d(className="android.widget.RelativeLayout").child(text="蓝牙") d(className="android.widget.FrameLayout", resourceId="android:id/content") \
.child_by_text("蓝牙", className="android.widget.TextView").click() # 通过允许滚动搜索获取子节点
d(className="android.widget.FrameLayout", resourceId="android:id/content") \
.child_by_text(
"安全",
allow_scroll_search=True,
className="android.widget.TextView"
).click()

兄弟节点

d(text="无线和网络").sibling(className="android.view.ViewGroup")

相对定位

我们还可以使用相对定位方法来获取元素 left, right, top, bottom.

  • d(A).left(B), 在A的左侧选择B
  • d(A).right(B), 在A的右侧选择B.
  • d(A).up(B), 在A的上部选择B
  • d(A).down(B), 在A是下部选择B

so:

d(text="更多").up(text="蓝牙").click()

多元素(实例)选择

有时屏幕可能包含多个具有相同属性的元素,例如文本,使用选择器中的“instance”属性来选择一个符合条件的实例

d(resourceId="com.android.settings:id/title",instance=0).click()

此外,uiautomator2提供了一个类似列表的方法:

print(d(resourceId="com.android.settings:id/title").count)  # 获取元素个数,也可以用 len(d(text="Add new")) 代替
# 通过下标选择第几个元素
d(resourceId="com.android.settings:id/title")[0].click()
d(resourceId="com.android.settings:id/title")[1].click() for view in d(resourceId="com.android.settings:id/title"):
print(view.info)

判断元素状态及信息

判断元素是否存在

print(d(text="蓝牙").exists)  # True
print(d.exists(text="蓝牙")) # True 两种方法一样 print(d(text="蓝牙").exists(timeout=3)) # True 设置超时时间

获取特定UI对象的信息

print(d(text="蓝牙").info)
# {'bounds': {'bottom': 640, 'left': 187, 'right': 271, 'top': 583}, 'childCount': 0, 'className': 'android.widget.TextView', 'contentDescription': None, 'packageName': 'com.android.settings', 'resourceName': 'com.android.settings:id/title', 'text': '蓝牙', 'visibleBounds': {'bottom': 640, 'left': 187, 'right': 271, 'top': 583}, 'checkable': False, 'checked': False, 'clickable': False, 'enabled': True, 'focusable': False, 'focused': False, 'longClickable': False, 'scrollable': False, 'selected': False}

获取/设置/清除可编辑字段的文本

print(d(text="Settings").get_text()) # 获取可编辑字段值
d(text="Settings").set_text("My text...") # 设置/修改值
d(text="My text...").clear_text() # 清除值

获取元素位置

# 获取元素中心点
x, y = d(text="蓝牙").center()
print(x, y) # 239.0 135.0
x, y = d(text="蓝牙").center(offset=(0, 0)) # 左上角坐标值
print(x, y) # 187 583
# 对获取到的元素截图
im = d(text="蓝牙").screenshot()
im.save("蓝牙.jpg")

扩展选择器 XPath

Java uiautoamtor中默认是不支持xpath的,所以这里属于扩展的一个功能。速度不是这么的快

常见用法

# wait exists 10s
d.xpath("//android.widget.TextView").wait(10.0)
# find and click
d.xpath("//*[@content-desc='分享']").click()
# check exists
if d.xpath("//android.widget.TextView[contains(@text, 'Se')]").exists:
print("exists")
# get all text-view text, attrib and center point
for elem in d.xpath("//android.widget.TextView").all():
print("Text:", elem.text)
# Dictionary eg:
# {'index': '1', 'text': '999+', 'resource-id': 'com.netease.cloudmusic:id/qb', 'package': 'com.netease.cloudmusic', 'content-desc': '', 'checkable': 'false', 'checked': 'false', 'clickable': 'false', 'enabled': 'true', 'focusable': 'false', 'focused': 'false','scrollable': 'false', 'long-clickable': 'false', 'password': 'false', 'selected': 'false', 'visible-to-user': 'true', 'bounds': '[661,1444][718,1478]'}
print("Attrib:", elem.attrib)
# Coordinate eg: (100, 200)
print("Position:", elem.center())

元素操作

# 单击
d(text="蓝牙").click() # 单击,等待超时时间为10s
d(text="蓝牙").click(timeout=10) # 点击偏移量(x_offset, y_offset)
# click_x = x_offset * width + x_left_top
# click_y = y_offset * height + y_left_top
d(text="蓝牙").click(offset=(0.5, 0.5)) # 默认值为中间
d(text="蓝牙").click(offset=(0, 0)) # 点击左上角
d(text="蓝牙").click(offset=(1, 1)) # 点击右下角 # 单击,元素不存在不报错,返回布尔值,默认超时时间为0秒
clicked = d(text='蓝牙').click_exists(timeout=10.0)
print(clicked) # 点击直到元素消失,返回布尔值
is_gone = d(text="蓝牙").click_gone(maxretry=10, interval=1.0) # 最大重试默认值为10,间隔默认值为1.0
# 长按
d(text="蓝牙").long_click()

针对特定UI对象的手势操作

元素拖拽

# # 拖拽特定元素到相应坐标,拖动时间0.5s
# d(text="蓝牙").drag_to(988, 610, duration=0.5)
# # 拖拽特定元素到另一个元素位置,拖拽时间0.25s
# d(text="蓝牙").drag_to(text="更多", duration=0.25)

元素移动

# # 元素滑动
# d(text="蓝牙").swipe("right") # 元素右滑
# d(text="蓝牙").swipe("left", steps=10) # 元素左滑
# d(text="蓝牙").swipe("up", steps=20) # 元素上滑滑 1 steps 大约 5ms, 因此 20 steps 大约 0.1s
# d(text="蓝牙").swipe("down", steps=20) # 元素下滑
# # 元素手势
# 从一个点到另一个点的两点手势
# d(text="蓝牙").gesture((988, 410), (988, 910), (588, 610), (288, 310)) # 注:测试中发现 text="蓝牙" 不起作用
# d().gesture(startPoint1, startPoint2, endPoint1, endPoint2, steps) # # 元素移动
# # 从元素的边缘到用心,移动百分之50,一定时间10ms
# d(text="无线和网络").pinch_in(percent=50, steps=10)
# # 从元素中心移到边缘
# d(text="无线和网络").pinch_out()

元素等待

# # 查找等待元素,等待超时时间为3s(默认20s),返回布尔值
# print(d(text="蓝牙").wait(timeout=3.0)) # True
# # 等待元素消失,等待时间1s
# print(d(text="蓝牙").wait_gone(timeout=1.0)) # False

在特定的ui对象上滑动页面

# # 页面滑动(手指滑动后松开)
# # 垂直向前(手指向上)滑动
# d(scrollable=True).fling()
# # # 水平向前(手指向左)滑动
# d(scrollable=True).fling.horiz.forward()
# # 垂直向后(手指像下)滑动
# d(scrollable=True).fling.vert.backward()
# # 水平滑动到最开始的地方
# d(scrollable=True).fling.horiz.toBeginning(max_swipes=1000)
# # 垂直滑动到尾部
# d(scrollable=True).fling.toEnd()

在特定的ui对象上滚动页面

# # 页面滚动(手指一直在屏幕上)
# # 垂直向前(手指向上)滚动
# d(scrollable=True).scroll(steps=10)
# # 水平向前(手指向左)滚动
# d(scrollable=True).scroll.horiz.forward(steps=100)
# # 垂直向后(手指像下)滚动
# d(scrollable=True).scroll.vert.backward()
# # 水平滚动到最开始的地方
# d(scrollable=True).scroll.horiz.toBeginning(steps=100, max_swipes=1000)
# # 垂直滚动到尾部
# d(scrollable=True).scroll.toEnd()
# # 垂直向前滚动,直到特定的UI对象出现
# d(scrollable=True).scroll.to(text="Security")

上下文监控


with d.watch_context() as ctx:
ctx.when("^立即(下载|更新)").when("取消").click() # 当同时出现 (立即安装 或 立即取消)和 取消 按钮的时候,点击取消
ctx.when("同意").click()
ctx.when("确定").click()
# 上面三行代码是立即执行完的,不会有什么等待 ctx.wait_stable() # 开启弹窗监控,并等待界面稳定(两个弹窗检查周期内没有弹窗代表稳定) # 使用call函数来触发函数回调
# call 支持两个参数,d和el,不区分参数位置,可以不传参,如果传参变量名不能写错
# eg: 当有元素匹配仲夏之夜,点击返回按钮
ctx.when("仲夏之夜").call(lambda d: d.press("back"))
ctx.when("确定").call(lambda el: el.click()) # 其他操作 # 为了方便也可以使用代码中默认的弹窗监控逻辑
# 下面是目前内置的默认逻辑,可以加群at群主,增加新的逻辑,或者直接提pr
# when("继续使用").click()
# when("移入管控").when("取消").click()
# when("^立即(下载|更新)").when("取消").click()
# when("同意").click()
# when("^(好的|确定)").click()
with d.watch_context(builtin=True) as ctx:
# 在已有的基础上增加
ctx.when("@tb:id/jview_view").when('//*[@content-desc="图片"]').click() # 其他脚本逻辑

全局设置

d.HTTP_TIMEOUT = 60 # 默认值60s, http默认请求超时时间

# 当设备掉线时,等待设备在线时长,仅当TMQ=true时有效,支持通过环境变量 WAIT_FOR_DEVICE_TIMEOUT 设置
d.WAIT_FOR_DEVICE_TIMEOUT = 70

其他的配置,目前已大部分集中到 d.settings 中

# 配置点击前延时0.5s,点击后延时1s
d.settings['operation_delay'] = (.5, 1)
# 修改延迟生效的方法
# 其中 double_click, long_click 都对应click
d.settings['operation_delay_methods'] = ['click', 'swipe', 'drag', 'press'] d.settings['xpath_debug'] = True # 开启xpath插件的调试日志
d.settings['wait_timeout'] = 20.0 # 默认控件等待时间(原生操作,xpath插件的等待时间) print(d.settings)

UiAutomator中的超时设置(隐藏方法)

print( d.jsonrpc.getConfigurator() )
d.jsonrpc.setConfigurator({"waitForIdleTimeout": 100})

Input method (没测试过)

这种方法通常用于不知道控件的情况下的输入。第一步需要切换输入法,然后发送adb广播命令,具体使用方法如下

d.set_fastinput_ime(True) # 切换成FastInputIME输入法
d.send_keys("你好123abcEFG") # adb广播输入
d.clear_text() # 清除输入框所有内容(Require android-uiautomator.apk version >= 1.0.7)
d.set_fastinput_ime(False) # 切换成正常的输入法
d.send_action("search") # 模拟输入法的搜索

send_action 说明

该函数可以使用的参数有 go search send next done previous

什么时候该使用这个函数呢?

有些时候在EditText中输入完内容之后,调用press("search") or press("enter")发现并没有什么反应。 这个时候就需要send_action函数了,这里用到了只有输入法才能用的IME_ACTION_CODEsend_actionbroadcast命令发送给输入法操作IME_ACTION_CODE,由输入法完成后续跟EditText的通信。

消息提示框toast

显示toast

print(d.toast.show("Hello world"))   # True
d.toast.show("Hello world", 5.0) # 显示5s,默认1s

获取 toast

# (Args)
# 5.0:最大等待超时时间。默认的10.0
# 10.0:缓存时间。如果toast已经在最近10秒内显示,则返回缓存toast。默认10.0(可能在将来更改)
# "默认消息":如果最终没有得到toast则返回。默认没有
print(d.toast.get_message(5.0, 10.0, "default message"))

一般用法

assert "Short message" in d.toast.get_message(5.0, default="")

清除缓存的toast

d.toast.reset()  # Now d.toast.get_message(0) is None

视频录制

这里没有使用手机中自带的screenrecord命令,是通过获取手机图片合成视频的方法,所以需要安装一些其他的依赖,如imageio, imageio-ffmpeg, numpy等 因为有些依赖比较大,推荐使用镜像安装。直接运行下面的命令即可。

pip3 install -U "uiautomator2[image]" -i https://pypi.doubanio.com/simple

使用方法:

d.screenrecord('output.mp4')

time.sleep(10)
# or do something else d.screenrecord.stop() # 停止录制后,output.mp4文件才能打开

图像匹配

安装依赖:

pip3 install -U "uiautomator2[image]" -i https://pypi.doubanio.com/simple
imdata = "home.jpg" # 也可以是URL, PIL.Image或OpenCV打开的图像

d.image.match(imdata)
# 匹配待查找的图片,立刻返回一个结果
# 返回一个dict, eg: {"similarity": 0.9, "point": [200, 300]} d.image.click(imdata, timeout=20.0)
# 在20s的时间内调用match轮询查找图片,当similarity>0.9时,执行点击操作

Stop UiAutomator

停止UiAutomator守护服务

因为有atx-agent的存在,Uiautomator会被一直守护着,如果退出了就会被重新启动起来。但是Uiautomator又是霸道的,一旦它在运行,手机上的辅助功能、电脑上的uiautomatorviewer 就都不能用了,除非关掉该框架本身的uiautomator。下面就说下两种关闭方法

d.uiautomator.stop()

# d.uiautomator.start() # 启动
print(d.uiautomator.running()) # 是否在运行

UIAutomator测试框架介绍的更多相关文章

  1. UiAutomator自动化测试框架介绍

    UiAutomator自动化测试框架介绍 环境搭建 1         必要条件 1.1       JDK 1.2       SDK(API高于15) 1.3       Eclipse 2    ...

  2. [python 测试框架学习篇] 分享 uiautomator测试框架

    uiautomator测试框架 :https://testerhome.com/topics/4194

  3. python pytest测试框架介绍二

    在介绍一中简单介绍了pytest的安装和简单使用,接下来我们就要实际了解pytest了 一.pytest的用例发现规则 pytest可以在不同的函数.包中发现用例,发现的规则如下 文件名以test_开 ...

  4. spring boot应用测试框架介绍

    一.spring boot应用测试存在的问题 官方提供的测试框架spring-boot-test-starter,虽然提供了很多功能(junit.spring test.assertj.hamcres ...

  5. 【Tavern】1.tavern测试框架介绍

    目录 1. Tavern 和 Postman 2. 安装Python 3. 安装Tavern 4. 第一个测试用例 1. Tavern 和 Postman Tavern是一个用Python实现的接口自 ...

  6. 【转】Java学习---10个测试框架介绍

    [原文]https://www.toutiao.com/i6594302925458113027/ JAVA 程序员需要用到 10 个测试框架和库 Java 程序员需要用到十大单元测试和自动化集成测试 ...

  7. 【pytest系列】- pytest测试框架介绍与运行

    如果想从头学起pytest,可以去看看这个系列的文章! https://www.cnblogs.com/miki-peng/category/1960108.html 前言​ ​ 目前有两种纯测试的测 ...

  8. 『德不孤』Pytest框架 — 1、Pytest测试框架介绍

    目录 1.什么是单元测试框架 2.单元测试框架主要做什么 3.单元测试框架和自动化测试框架有什么关系 4.Pytest测试框架说明 5.Pytest框架和Unittest框架区别 (1)Unittes ...

  9. 菜鸟水平如何在Android Studio中添加uiautomator测试框架

    1.启动AS,弹出创建Android Studio项目 2.选择 "Start a new Android Studio project",输入 application name ...

  10. python pytest测试框架介绍三

    之前介绍了pytest以xUnit形式来写用例,下面来介绍pytest特有的方式来写用例 1.pytest fixture实例1 代码如下 from __future__ import print_f ...

随机推荐

  1. vivo 公司 Kubernetes 集群 Ingress 网关实践

    文章转载自:https://mp.weixin.qq.com/s/qPqrJ3un1peeWgG9xO2m-Q 背景 vivo 人工智能计算平台小组从 2018 年底开始建设 AI 计算平台至今,已经 ...

  2. 查看pod创建时使用yaml文件内容

    除了 kubectl describe pod 以外,另一种获取 Pod 额外信息(除了 kubectl get pod)的方法 是给 kubectl get pod 增加 -o yaml 输出格式参 ...

  3. Kibana:如何周期性地为 Dashboard 生成 PDF Report

    转载自:https://blog.csdn.net/UbuntuTouch/article/details/108449775 按照上面的方式填写.记得把之前的 URL 拷贝到 webhook 下的 ...

  4. Codeforces Round #442 (Div. 2) B. Nikita and string

    题意:求最长可以分a b a为三部分子串,a b a可以为空 思路在代码里 1 #include<cstdio> 2 #include<iostream> 3 #include ...

  5. python基础作业2

    目录 编写一个用户认证装饰器 利用有参装饰器编写多种用户登录校验策略 利用递归函数依次打印列表中每一个数据值 获取用户权限并校验用户登录 编写一个用户认证装饰器 """ ...

  6. python2与python区别汇总

    目录 输入与输出 range使用区别 字符编码区别 输入与输出 python2与python3中两个关键字的区别 python2中 input方法需要用户自己提前指定数据类型 写什么类型就是什么类型 ...

  7. Volatile介绍

    介绍 volatile 是 Java 虚拟机提供的轻量级的同步机制,它可以保证可见性(缓存一致性协议)和有序性(禁止指令重排序,也就是通过内存屏障来实现),但是不保证原子性. JMM 介绍 JMM 是 ...

  8. C++算法之旅、02 从木棒切割问题领悟二分法精髓

    172.木棒切割问题 https://sunnywhy.com/problem/172 题目描述 给出n根木棒的长度,现在希望通过切割它们来得到至少k段长度相等的木棒(长度必须是整数),问这些长度相等 ...

  9. SQL的表的连接Left Join / Right Join /inner join相关

    Left Join / Right Join /inner join相关关于左连接和右连接总结性的一句话:左连接where只影向右表,右连接where只影响左表.Left Joinselect * f ...

  10. 只能用于文本与图像数据?No!看TabTransformer对结构化业务数据精准建模

    作者:韩信子@ShowMeAI 深度学习实战系列:https://www.showmeai.tech/tutorials/42 TensorFlow 实战系列:https://www.showmeai ...