UIAutomator测试框架介绍

news2024/11/20 15:37:01

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 --helpimage

安装 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

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

  • 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版本中再启用image

启动和关闭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>

image

选择器支持以下参数。详细信息请参考 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()) # 是否在运行

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/76898.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

软件测试面试笔试习题参考,你都会了吗?

目录 一、 简答题和应用题&#xff1a; 二、 填空题&#xff1a; 三、 判断题&#xff1a; 四、 选择题&#xff1a; 总结 重点&#xff1a;配套学习资料和视频教学 一、 简答题和应用题&#xff1a; 1. 什么是软件测试&#xff1f; 2. 比较软件测试过程和软件开发过程&a…

基于JavaScript中AES和MD5加密,以及简单二维码的生成

一、加密技术 1、对称加密&#xff1a;单密钥加密。一个密钥可以用来加密也可以用来解密 —- AES 2、非对称加密&#xff1a;有两把密码&#xff0c;公钥(用于加密)&#xff0c;私钥(用于解密) 3、摘要算法&#xff1a;把任意长度的输入&#xff0c;根据算法生成一串固定长度…

有环链表入口问题

有环链表入口问题 当快慢指针相遇时&#xff0c;我们可以判断到链表中有环&#xff0c;这时重新设定一个新指针指向链表的起点&#xff0c;且步长与慢指针一样为1&#xff0c;则慢指针与“新”指针相遇的地方就是环的入口。 图片来源:黑马程序员 证明: 设a为起点位置&#xff…

百变郁锦香,开创新典范,深化全球战略布局成就国际高端酒店品质之选

随着消费需求的不断升级&#xff0c;酒店消费场景也进行着多元化的发展&#xff0c;城市高端度假品牌正积极溯源消费需求&#xff0c;寻得品牌文化延伸的可靠路径。同时&#xff0c;各大酒店品牌也加快在市场布局的脚步&#xff0c;希望通过布局城市核心区域获得可持续发展的更…

Python绘制正二十面体

文章目录正二十面体的顶点绘制棱绘制面正二十面体的顶点 正20面体的12个顶点刚好可以分为三组&#xff0c;每一组都是一个符合黄金分割比例的长方形&#xff0c;而且这三个长方形是互相正交的。 所以&#xff0c;想绘制一个正二十面体是比较容易的 import numpy as np from …

大环配体配合物1407166-70-4,NODA-GA-NHS ester,NODA-GA-NHS 酯

●外观以及性质&#xff1a; NODA-GA-NHS ester产物呈固体或粘性液体&#xff0c;取决于PEG分子量&#xff0c;一般为白色固体&#xff0c;双功能大环化合物&#xff0c;大环配体配合物是指由多齿配体与环骨架上的O、N、P、S等多个配位原子形成的环配合物。 NODA-GA-NHS ester …

前端面试题2022-CSS篇

关于前端面试的题&#xff0c;最近整理了一些干货&#xff0c;经常被问到的一些问题&#xff0c;出现频率比较高的问题&#xff0c;如有不足之处&#xff0c;请高调指出&#xff0c;&#xff08;⭐代表难度&#xff0c;星星越多越难&#xff0c;以次类推&#xff09;&#xff0…

如何自动备份指定文件扩展名的文件?

关于文件扩展名 文件扩展名&#xff0c;一个点后跟几个字母&#xff0c;例如“.doc”或“.jpg”&#xff0c;构成计算机文档名称的结尾。保存文档时&#xff0c;请务必在单击“保存”之前输入文档名称和文件扩展名。 自动备份具有特定文件扩展名的文件 随着计算机的使用&…

第十章 鲁棒性检查(中)

文章目录10.5 时钟门控检查(Clock Gating Checks)高电平时钟门控(Active-High Clock Gating)10.5 时钟门控检查(Clock Gating Checks) 当一个门控信号&#xff08;gating signal&#xff09;可以控制逻辑单元中时钟信号&#xff08;clock signal&#xff09;的路径时&#xff…

最近面试遇到的面试题

先挖坑&#xff0c;有空再填坑&#xff01; 迭代器 生成器 常用高阶函数 MySQL 建索引几大原则 浅拷贝 深拷贝 区别 实现 Linux 进程名字 查看进程号 Linux 杀死一个进程 python Linux shell 脚本 数据库 主从复制 配置 nginx 权限设置 python 去除空行 pandas 保留最后一个空…

深入浅出 Swift 中的 some、any 关键字以及主关联类型(primary associated types)

问题现象 从 Swift 5.1 开始,Apple 陆续引入 some、any 关键字,并且从 Swift 5.7 开始对 any 关键字的用法做了增强,并且引入了主关联类型(primary associated types)的概念。 那么它们到底是什么意思?使用它们又能如何改进我们书写代码的范儿呢? 在本篇博文中,您将学…

自动依据你的数据库生成SQL练习题及答案,宝藏软件鉴赏

原创软件不易&#xff0c;方面多点赞、收藏、加关注鼓励&#xff01;后续领更多好用功能&#xff01; 引言 各位好&#xff0c;相信看见这篇文章的朋友&#xff0c;应该也去体验过了chatGPT了吧~&#xff0c;确实chatGPT拉近了我们与未来科技的距离&#xff0c;所有别人火也是…

电脑技巧:Microsoft Edge浏览器技巧介绍

目录 1、导入浏览器数据 2、Edge边栏 3、实用的扩展功能 4、创建集锦列表 4.1 集锦的作用 4.2 使用方法 5、查找优惠券 6、Edge效率省电模式 Microsoft Edge是Windows10操作系统预装的一款非常棒的浏览器和之前的IE不同&#xff0c;采用了谷歌浏览器内核&#xff0c;相…

项目的成功标准如何衡量?【一杯咖啡谈项目】

每个项目经理都想自己的项目能够成功&#xff0c;但是&#xff0c;什么才叫项目的成功&#xff0c;成功的定义是什么&#xff1f;项目成功标准是什么呢&#xff1f; 很多看似失败的项目其实是成功的&#xff0c;很多看似成功的项目其实是失败的。研究项目成功的标准&#xff0…

Chrome 扩展插件:如何开始一个插件的开发

欢迎点击查看个人站首发原文&#xff0c;访问个人站获取更多插件编程知识。 Chrome扩展插件基础是基于前端htmljs开发&#xff0c;然后通过官方提供的项目结构进行开发&#xff0c;需要注意的是&#xff0c;目前Chrome已经支持Manifest V3&#xff0c;FireFox仅仅支持Manifest …

【大数据入门核心技术-Impala】(一)Impala简介

目录 一、Impala介绍 二、Impala优势 三、Impala主要功能 一、Impala介绍 Impala是Cloudera公司主导开发的新型查询系统&#xff0c;它提供SQL语义&#xff0c;能查询存储在Hadoop的HDFS和HBase中的PB级大数据。已有的Hive系统虽然也提供了SQL语义&#xff0c;但由于Hive底层…

代码详细教程+文档+PPT+源码等]SSM框架美妆商城全套|电商购物计算机专业毕业论文java毕业设计网站

&#x1f496;&#x1f496;更多项目资源&#xff0c;最下方联系我们✨✨✨✨✨✨ 目录 Java项目介绍 资料获取 Java项目介绍 计算机毕业设计java毕设之SSM美妆商城项目源码_哔哩哔哩_bilibili项目资料网址: http://itzygogogo.com软件下载地址:http://itzygogogo.com/itsz…

[附源码]Python计算机毕业设计SSM基于框架的在线健康系统设计与实现(程序+LW)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

DRF和vue跨域问题的解决

跨域不一定都会有跨域问题&#xff1a; 因为跨域问题是浏览器对于ajax请求的一种安全限制&#xff1a;一个页面发起的ajax请求&#xff0c;只能是与当前页域名相同的路径&#xff0c;这能有效的阻止跨站攻击。因此&#xff0c;跨域问题 是针对ajax的一种限制。 但是这却给我们…

Birdboot第二天 Request Response

目录 V4 HttpServletRequest保存请求内容 1.此类专门处理请求 把获取请求的readline()和拆分代码移动过来 2.ClientHandler客户端处理器实例化HttpServletRequest 3. 拆分的信息从局部变量 变成属性 4.建方法 解析请求行 消息头 消息正文 V5 发送响应 1.在客户端处理器 …