python + uiautomator2 中文使用细则

python + uiautomator2 中文使用细则

一、安装

1.安装uiautomator2
#由于uiautomator2仍在开发中,因此您必须添加‘–pre’才能安装开发版本。

pip install --pre uiautomator2

#也可以从源代码安装

git clone https://github.com/openatx/uiautomator2
pip install -e uiautomator2

如果需要截屏,还要安装pillow

pip install pillow

2.安装设备守护进程
电脑连接上一个手机或多个手机, 确保adb已经添加到环境变量中,执行下面的命令会自动安装本库所需要的设备端程序:uiautomator-server 、atx-agent、openstf/minicap、openstf/minitouch
#初始化所有的已经连接到电脑的设备

python -m uiautomator2 init

有时候init也会出错,请参考手动Init
指南,安装提示success即可。
3.安装weditor
pip install -U weditor # 目前最新的稳定版为 0.1.0
Windows系统可以使用命令在桌面创建一个快捷方式 python -m weditor --shortcut
命令行启动 python -m weditor 会自动打开浏览器,输入设备的ip或者序列号,点击Connect即可。

二、使用指南

1. 连接设备
有3种连接方法:
• 通过WiFi
假如设备IP是10.0.0.1,你的电脑在同一网络

import uiautomator2 as u2

d = u2.connect('10.0.0.1') # alias for u2.connect_wifi('10.0.0.1')
print(d.info)

• 通过USB
假设设备编号为123456f(通过cmd:adb devices查询)

import uiautomator2 as u2
d = u2.connect('123456f') # alias for u2.connect_usb('123456f')
print(d.info)

• 通过ADB WiFi

import uiautomator2 as u2
d = u2.connect_adb_wifi("10.0.0.1:5555")

无参数调用u2.connect()函数, uiautomator2将从环境变量ANDROID_DEVICE_IP或ANDROID_SERIAL获取设备IP。如果这个环境变量为空,uiautomator将退回connect_usb,你需要确保只有一个设备连接到电脑。

2. 命令行使用
其中的$device_ip代表设备的ip地址。
如需指定设备需要传入—serial,如python3 -m uiautomator2 --serial bff1234 , SubCommand为子命令(init,或者screenshot等)
1.0.3 Added: python3 -m uiautomator2可以简写为uiautomator2


init: 为设备安装所需要的程序
uiautomator2 init 
If you need specify device to init, pass --serial <serial> 
python3 -m uiautomator2 init --serial your-device-serial

screenshot: 截图
$ python -m uiautomator2 screenshot screenshot.jpg
uninstall: 卸载
python -m uiautomator2 uninstall <package-name> # 卸载一个包
python -m uiautomator2 uninstall <package-name-1> <package-name-2> # 卸载多个包
python -m uiautomator2 uninstall --all # 全部卸载

install: 安装apk,apk通过URL给出 (暂时不能用)
clear-cache: 清空缓存 (废弃中,目前已经不需要改接口)
app-stop-all: 停止所有应用 (暂不能用)
healthcheck: 健康检查 (暂不能用)

三、API手册

1.全局设定
#设置每次单击UI后再次单击之间延迟1.5秒

d.click_post_delay = 1.5  # 默认无延迟

设置默认元素等待超时(秒)

d.wait_timeout = 30.0 # 默认20.0秒

2.检索设备信息

d.info

以下是可能的结果:

{ 
    u'displayRotation': 0,
    u'displaySizeDpY': 640,
    u'displaySizeDpX': 360,
    u'currentPackageName': u'com.android.launcher',
    u'productName': u'takju',
    u'displayWidth': 720,
    u'sdkInt': 18,
    u'displayHeight': 1184,
    u'naturalOrientation': True
}

3.键盘操作
打开/关闭屏幕

d.screen_on() # 开启 screen
d.screen_off() # 关闭screen

获取屏幕开/关状态

d.info.get('screenOn') # 要求android >= 4.4

按下硬/软键

d.press("home") # 按下home键
d.press("back") # 按下back键的常规方式
d.press(0x07, 0x02) # 按下编码 0x07('0') 和 0x02(META ALT)

当前支持的按键

o home
o back
o left
o right
o up
o down
o center
o menu
o search
o enter
o delete ( or del)
o recent (recent apps)
o volume_up
o volume_down
o volume_mute
o camera
o power

您可以在Android KeyEvnet
上找到所有按键代码定义。

#解锁屏幕
d.unlock()

#1. 启动 activity: com.github.uiautomator.ACTION_IDENTIFY
2. 按下 “home”
4.设备的手势交互
点击屏幕

d.click(x, y)

长按屏幕

d.long_click(x, y)
d.long_click(x, y, 0.5) # long click 0.5s (default)

滑动

d.swipe(sx, sy, ex, ey)
d.swipe(sx, sy, ex, ey, 0.5) # swipe for 0.5s(default)

拖动

d.drag(sx, sy, ex, ey)
d.drag(sx, sy, ex, ey, 0.5) # swipe for 0.5s(default)
注意: click, swipe, drag 支撑百分比位置。例:
d.long_click(0.5, 0.5) #长按屏幕中心

5. 屏幕操作
检索/设置方向
可用的方向有:
natural 或 n
left 或l
right 或r
upsidedown 或u (不可设置)
#检索方向,可以是"natural" 或"left" 或"right" 或"upsidedown"

orientation = d.orientation
#警告:未在我的TT-M1通过测试
#设定orientation(方向) 和 冻结旋转.
d.set_orientation("n") # 或"natural"
d.set_orientation("l") # 或"left"
d.set_orientation("r") # 或"right"
d.set_orientation("n") # or "natural"

冻结/解冻旋转

#冻结旋转
d.freeze_rotation()

#取消冻结旋转
d.freeze_rotation(False)

截图
#截图并保存到本地文件“home.jpg”.

d.screenshot("home.jpg")
#获取PIL.Image 格式,需要先安装pillow
image = d.screenshot()
image.save("home.jpg") # 或 home.png

#获取opencv 格式, 需要先安装numpy 和cv2
import cv2
image = d.screenshot(format='opencv')
cv2.imwrite('home.jpg', image)

转储窗口层次结构

 #获取转储的内容(unicode).
xml = d.dump_hierarchy()

打开通知或快速设置

d.open_notification()
d.open_quick_settings()

6.推送和提取文件
将文件推送到设备

#推送到一个文件夹 
d.push("foo.txt", "/sdcard/")
#推送并重命名
d.push("foo.txt", "/sdcard/bar.txt")
#推送fileobj
with open("foo.txt", 'rb') as f:
    d.push(f, "/sdcard/")
#推送并修改文件模式
d.push("foo.sh", "/data/local/tmp/", mode=0o755)
从设备中提取文件
d.pull("/sdcard/tmp.txt", "tmp.txt")
#设备中没有文件会引发FileNotFoundErr
d.pull("/sdcard/some-file-not-exists.txt", "tmp.txt")

7.APP管理
包括APP安装,启动和停止

APP安装
目前仅支持从url安装。

d.app_install(' HTTP://some-domain.com/some.apk ')

APP启动

d.app_start("com.example.hello_world") # 以package name启动

APP停止

#执行强制停止
d.app_stop("com.example.hello_world") 

#执行应用清除
d.app_clear('com.example.hello_world')

停止所有APP的运行

# 停止所有 
d.app_stop_all()
#停止除com.examples.demo以外的所有应用程序 
d.app_stop_all(excludes=['com.examples.demo'])

8.选择器Selecor
选择器用于识别当前窗口中的特定ui对象.

#要选text是'Clock',className 是'android.widget.TextView' 的元素
d(text='Clock', className='android.widget.TextView')

选择器支持以下参数. 详见UiSelecor java doc
.
• 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

获取儿孙和同级UI对象
儿孙级

#获取 child 或 grandchild(递归获取)
d(className="android.widget.ListView").child(text="Bluetooth")

同级

#获取 sibling 或 child of sibling
d(text="Google").sibling(className="android.widget.ImageView")

通过text或description或instance,获取儿孙级对象

#获取儿孙中符合类名"android.widget.LinearLayout",text包含"Bluetooth"
d(className="android.widget.ListView", resourceId="android:id/list")\ .child_by_text("Bluetooth", className="android.widget.LinearLayout")
#允许页面滚动搜索获得儿孙对象
d(className="android.widget.ListView", resourceId="android:id/list") \
 .child_by_text(
    "Bluetooth",
    allow_scroll_search=True,
    className="android.widget.LinearLayout"
  )

o child_by_description 用于获取儿孙中包含指定描述的对象, 其余和child_by_text相同.
o child_by_instance 用于获取屏幕上可见的儿孙对象中的特定实例.
详细信息,请参见以下链接:
o UiScrollable
, getChildByDescription, getChildByText, getChildByInstance
o UiCollection
, getChildByDescription, getChildByText, getChildByInstance
上面的方法支持链式调用,例如,对于以下层次结构

<node index="0" text="" resource-id="android:id/list" class="android.widget.ListView" ...>
  <node index="0" text="WIRELESS & NETW或KS" resource-id="" class="android.widget.TextView" .../>
  <node index="1" text="" resource-id="" class="android.widget.LinearLayout" ...>
    <node index="1" text="" resource-id="" class="android.widget.RelativeLayout" ...>
      <node index="0" text="Wi Fi" resource-id="android:id/title" class="android.widget.TextView" .../>
    </node>
    <node index="2" text="ON" resource-id="com.android.settings:id/switchWidget" class="android.widget.Switch" .../>
  </node>
  ...
</node>
System Setting

我们要单击“Wi-Fi”右侧的开关来打开Wi-Fi。由于几个开关具有几乎相同的属性,因此我们不能使用像
d(className=“android.widget.Switch”)来选择对象,可以使用下面的代码来选。

d(className="android.widget.ListView", resourceId="android:id/list")\
  .child_by_text("Wi Fi", className="android.widget.LinearLayout")\
  .child(className="android.widget.Switch")\
  .click()

相对位置
我们可以用相对位置的方法来获取当前视图中的对象: left, right, top, bottom.
o d(A).left(B), 表示选择A左侧的B.
o d(A).right(B), 表示选择A右侧的B.
o d(A).up(B), 表示选择A上方的B.
o d(A).down(B), 表示选择在A下面的B.
因此,对于“Wi-Fi”开关,我们可以编写如下代码:

#选择"Wi Fi"右侧的"switch" 
d(text="Wi Fi").right(className="android.widget.Switch").click()

多个实例
有时屏幕上有包含相同特点(例如文本)的多个对象,那么您将不得不在选择器中使用“instance”属性,如下所示:

d(text="Add new", instance=0)  # 获取第一个带有文本“Add new”的元素对象

uiautomator也提供了类似列表的方法来处理类似的元素对象.

#获取当前屏幕上带有文本“Add new”的元素的总数
d(text="Add new").count

#len函数与count属性功能相同

len(d(text="Add new"))

#通过index获取元素实例

d(text="Add new")[0]
d(text="Add new")[1]
#迭代
for view in d(text="Add new"):
    view.info  # ...

注意: 使用选择器(如列表)时,必须确保屏幕保持不变,否则可能会出现ui not found error.

获取ui对象的状态和信息
检查指定ui对象是否存在

d(text="Settings").exists # 如果存在,则为True ,否则为 False
d.exists(text="Settings") # 以上属性的别名.

检索指定ui对象的信息

d(text="Settings").info

以下是可能的结果:

{ u'contentDescription': u'',
u'checked': False,
u'scrollable': False,
u'text': u'Settings',
u'packageName': u'com.android.launcher',
u'selected': False,
u'enabled': True,
u'bounds': {u'top': 385,
            u'right': 360,
            u'bottom': 585,
            u'left': 200},
u'className': u'android.widget.TextView',
u'focused': False,
u'focusable': True,
u'clickable': True,
u'chileCount': 0,
u'longClickable': True,
u'visibleBounds': {u'top': 385,
                    u'right': 360,
                    u'bottom': 585,
                    u'left': 200},
u'checkable': False
}

设置/清除可编辑字段的text

d(text="Settings").clear_text()  # 清除文本
d(text="Settings").set_text("My text...")  # 设置文本

对指定ui对象执行click
单击指定ui object

#点击UI对象的中心
d(text="Settings").click()
#最长等待(元素显示)10秒,并单击(默认值),超时没有显示会报错
d(text="Settings").click(timeout=10)
#click别名,键盘操作的短名
d(text="Settings").tap()
#不等element show
d(text="Settings").tap_nowait()
长按指定ui对象
#长按指定ui object
d(text="Settings").long_click()

指定ui object手势动作
将对象拖到定点或另一个对象

#注意:在Android 4.3之前无法设置拖动.
#拖动ui object到point (x, y)
d(text="Settings").drag_to(x, y, duration=0.5)
#拖动ui object 到另一个ui object(中心)
d(text="Settings").drag_to(text="Clock", duration=0.25)

两点手势

d(text="Settings").gesture((sx1, sy1), (sx2, sy2), (ex1, ey1), (ex2, ey2))  # s起点,e终点

指定ui object两点手势
支持两种手势:
o In, 从边缘到中心
o Out, 从中心到边缘

#注意:在Android 4.3之前无法设置缩放.
#从边缘到中心。
d(text="Settings").pinch_in(percent=100, steps=10)
#从中心到边缘
d(text="Settings").pinch_out()

等待指定的ui对象出现或消失

#等待ui对象出现
d(text="Settings").wait(timeout=3.0) # return bool
#等待ui object消失
d(text="Settings").wait_gone(timeout=1.0)

默认超时20s. 详见"1.全局设定"

对(可滚动)ui对象执行fling
Fling(飞滑)一般用于水平或垂直翻页,可能的属性:
o horiz 或 vert
o forward 或 backward 或 toBeginning 或 toEnd

#fling默认垂直向前 
d(scrollable=True).fling()
#fling水平向前
d(scrollable=True).fling.horiz.forward()
#fling垂直向后
d(scrollable=True).fling.vert.backward()
 垂直fling到开始
d(scrollable=True).fling.h或iz.toBeginning(max_swipes=1000)
 垂直fling到末尾
d(scrollable=True).fling.toEnd()

对(可滚动)ui对象执行scroll
可能的属性:
o horiz 或 vert
o forward 或 backward 或 toBeginning 或 toEnd, 或 to

#scroll 默认垂直向前
d(scrollable=True).scroll(steps=10)
#scroll水平向前
d(scrollable=True).scroll.h或iz.forward(steps=100)
#scroll垂直向后
d(scrollable=True).scroll.vert.backward()
#水平scroll到开始
d(scrollable=True).scroll.h或iz.toBeginning(steps=100, max_swipes=1000)
#垂直scroll到末尾
d(scrollable=True).scroll.toEnd()
#scroll 向前垂直,直到出现指定ui object 
d(scrollable=True).scroll.to(text="Security")

9.触发器(Watcher)

你可以注册触发器来执行选择器匹配不到的ui对象动作。

注册有名触发器(Watcher)
当选择器找不到匹配项时,uiautomator将运行所有已注册的触发器.

条件匹配时点击目标

d.watcher("AUTO_FC_WHEN_ANR").when(text="ANR").when(text="Wait") \
                             .click(text="Force Close")
#d.watcher(name) # 创建并命名一个触发器.
#d.when(condition)  # 触发条件.
#d.click(target)  # 对目标对象执行click 动作.
件匹配时按键
d.watcher("AUTO_FC_WHEN_ANR").when(text="ANR").when(text="Wait") \
                             .press("back", "home")
#d.watcher(name)  # 创建并命名一个触发器.
#d.when(condition)  # 触发条件.
#d .press(<keyname>, ..., <keyname>.()  # 依次按键.

检查指定名字的Watcher是否被触发过
一个Watcher被触发过, 意味着所有触发条件都匹配,Watcher程序已运行.

d.watcher("watcher_name").triggered  # 触发过为true ,否则为false

删除有名触发器

#删除触发器
d.watcher("watcher_name").remove()

列出所有触发器

d.watchers # 返回已注册触发器的列表

检查任意Watcher是否被触发过

d.watchers.triggered  # 任意触发器触发过为true ,否则为false

重置所有触发过的触发器

#重置所有触发过的触发器, 之后d.watchers.triggered 返回 False.
d.watchers.reset()

删除触发器

#删除所有注册的触发器
d.watchers.remove()
#删除指定触发器, 效果和d.watcher("watcher_name").remove()相同
d.watchers.remove("watcher_name")

强制运行所有触发器

#强制运行所有注册过的触发器
d.watchers.run()

另外本文还有很多没写,推荐直接去看源码init.py

10.输入法

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


d.set_fastinput_ime(True) # 切换成FastInputIME输入法
d.send_keys("你好123abcEFG") # adb广播输入
d.clear_text() # 清除输入框所有内容(要求 android-Uiautomator.apk version >= 1.0.7)
d.set_fastinput_ime(False) # 切换成正常的输入法

11.Toast消息
显示Toast消息

d.toast.show("Hello world")
d.toast.show("Hello world", 1.0) # show for 1.0s, default 1.0s

获取Toast

#[Args]
#5.0: 最大等待超时。默认的10.0
#10.0: 缓存时间。如果toast在最近10秒内已经出现,则返回缓存toast。默认10.0(将来可能会改变)
#"default message":如果最终没有得到toast则返回。默认没有
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

12.XPath定位
举例: 某节点内容

<android.widget.TextView
  index="2"
  text="05:19"
  resource-id="com.netease.cloudmusic:id/qf"
  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="[957,1602][1020,1636]" />

xpath定位和使用方法

有些属性的名字有修改需要注意

description -> content-desc
resourceId -> resource-id
常见用法

#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())

其他XPath常见用法,见: https://github.com/openatx/uiautomator2/blob/master/uiautomator2/ext/xpath/README.md
13.九宫格解锁
说了这么多,直接上代码吧。

import uiautomator2 as u2

u = u2.connect() # 手机连接到PC即可
u.swipe_points([(0.235, 0.456), (0.503, 0.449), (0.509, 0.601), (0.777, 0.603), (0.771, 0.763), (0.222, 0.75)], 0.2)

其中(0.235, 0.456) 代表 X(23.5%) Y(45.6%). 这里用绝对坐标也可以。
最后的 0.2 代表每一次滑动的时间。
14.常见问题
很多没写在这个地方的,都放到了这里 Common Issues

Stop UiAutomator
停止UiAutomator守护服务

https://github.com/openatx/uiautomator2/wiki/Common-issues

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

方法1:

直接打开uiautomator app(init成功后,就会安装上的),点击关闭UIAutomator

方法2:


d.service("uiautomator").stop()

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

ATX与Maxim共存AccessibilityService的方法

14.Uiautomator与Uiautomator2的区别
https://www.cnblogs.com/insist8089/p/6898181.html

四、常见问题

1.提示502错误
尝试手机连接PC,然后运行下面的命令

adb shell am instrument -w -r -e debug false -e class com.github.Uiautomator.stub.Stub
com.github.Uiautomator.test/android.support.test.runner.AndroidJUnitRunner

如果运行正常,启动测试之前增加一行代码
d.healthcheck()
如果报错,可能是缺少某个apk没有安装,使用下面的命令重新初始化
python -m Uiautomator2 init --reinstall
1.本文来自开源项目uiautomator2:https://github.com/openatx/uiautomator2
2.后续更新Python+uiautomator2手机UI自动化测试实战

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

车载testing

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值