uiautomator2
QQ交流群: 815453846
Discord: https://discord.gg/PbJhnZJKDd
有段时间没有维护这个项目了(可能有两年了),但是最近工作需要又重新研究一下Android原生自动化,当然又调研了Appium,对比下来一看,发现uiautomator2这个项目的运行速度是真的好快,从检测元素到点击,都是毫秒级的,代码也比较好理解。真是没想到以前竟然写出了这么神奇的项目,这么好的项目怎么能让它落灰呢,得好好整一整,一些垃圾代码清理清理。所以项目版本从2.x.x升级到了3.x.x
还在用2.x.x版本的用户,可以先看一下2to3 再决定是否要升级3.x.x (我个人还是非常建议升级的)
2到3毕竟是大版本升级,很多的函数删掉了。首先删掉的就是atx-agent,其次还有一堆atx-agent相关的函数。废弃的功能比如init.
各种依赖库的版本号
UiAutomator是Google提供的用来做安卓自动化测试的一个Java库,基于Accessibility服务。功能很强,可以对第三方App进行测试,获取屏幕上任意一个APP的任意一个控件属性,并对其进行任意操作,但有两个缺点:1. 测试脚本只能使用Java语言 2. 测试脚本要打包成jar或者apk包上传到设备上才能运行。
我们希望测试逻辑能够用Python编写,能够在电脑上运行的时候就控制手机。这里要非常感谢 Xiaocong He (@xiaocong),他将这个想法实现了出来(见xiaocong/uiautomator),原理是在手机上运行了一个http rpc服务,将uiautomator中的功能开放出来,然后再将这些http接口封装成Python库。
因为xiaocong/uiautomator
这个库,已经很久不见更新。所以我们直接fork了一个版本,为了方便做区分我们就在后面加了个2 openatx/uiautomator2,对应的Android包源码我也fork了一份,openatx/android-uiautomator-server
除了对原有的库的bug进行了修复,还增加了很多新的Feature。主要有以下部分:
这里要先说明下,因为经常有很多人问 openatx/uiautomator2 并不支持iOS测试,需要iOS自动化测试,可以转到这个库 openatx/facebook-wda。
PS: 这个库 https://github.com/NeteaseGame/ATX 目前已经不维护了,请尽快更换。
这里有一份快速参考,适合已经入门的人 QUICK REFERENCE GUIDE,欢迎多提意见。
Requirements
- Android版本 4.4+
- Python 3.8+
QUICK START
先准备一台(不要两台)开启了开发者选项
的安卓手机,连接上电脑,确保执行adb devices
可以看到连接上的设备。
运行pip3 install -U uiautomator2
安装uiautomator2
命令行运行python
打开python交互窗口。然后将下面的命令输入到窗口中。
import uiautomator2 as u2
d = u2.connect()
print(d.info)
这时看到类似下面的输出,就可以正式开始用我们这个库了。因为这个库功能太多,后面还有很多的内容,需要慢慢去看 ....
{'currentPackageName': 'net.oneplus.launcher', 'displayHeight': 1920, 'displayRotation': 0, 'displaySizeDpX': 411, 'displaySizeDpY': 731, 'displayWidth': 1080, 'productName': 'OnePlus5', '
screenOn': True, 'sdkInt': 27, 'naturalOrientation': True}
另外为了保持稳定,还需要开启小黄车
的悬浮窗权限。参考文章 py-uiautomator2通过悬浮窗让服务长时间可用
一般情况下都会成功,不过也可能会有意外。可以加QQ群反馈问题(群号在最上面),群里有很多大佬可以帮你解决问题。
Thank you to all our sponsors! ✨🍰✨
Empty
Article Recommended
优秀文章推荐 (欢迎QQ群里at我反馈)
相关项目
- 基于adb协议与Android进行交互的库 adbutils
- uiauto.dev 用于查看UI层级结构,类似于uiautomatorviewer(用于替代之前写的weditor),用于查看UI层级结构
- 设备管理平台,设备多了就会用到 atxserver2 (寻找项目维护人员)
atx-agent 运行在设备上的驻守程序,go开发,用于保活设备上相关的服务weditor 类似于uiautomatorviewer,专门为本项目开发的辅助编辑器(这个暂不维护了
Installation
Connect to a device
Command line
Global settings
App management
UI automation
Contributors
LICENSE
Installation
-
Install uiautomator2
pip install -U uiautomator2
测试是否安装成功 uiautomator2 --help
-
UI Inspector
pip install uiautodev
uiauto.dev
浏览器打开 https://uiauto.dev 查看当前设备的界面结构。
uiauto.dev
uiauto.dev 是一个独立与uiautomator2之外的一个项目,用于查看图层结构的。属于旧版项目weditor的重构版本,后续也许会收费(价格肯定物超所值),来支持当前这个项目继续维护下去。感兴趣的可以加群讨论(也包含提需求) QQ群 536481989
Connect to a device
use serialno to connect device eg. 123456f
(seen from adb devices
)
import uiautomator2 as u2
d = u2.connect('123456f')
print(d.info)
Serial can be passed through env-var ANDROID_SERIAL
d = u2.connect()
Command line
其中的$device_ip
代表设备的ip地址
如需指定设备需要传入--serial
如 python3 -m uiautomator2 --serial bff1234 <SubCommand>
, SubCommand为子命令(screenshot, current 等)
1.0.3 Added: python3 -m uiautomator2
equals to uiautomator2
-
screenshot: 截图
$ uiautomator2 screenshot screenshot.jpg
-
current: 获取当前包名和activity
$ uiautomator2 current
{
"package": "com.android.browser",
"activity": "com.uc.browser.InnerUCMobile",
"pid": 28478
}
-
uninstall: Uninstall app
$ uiautomator2 uninstall <package-name>
$ uiautomator2 uninstall <package-name-1> <package-name-2>
$ uiautomator2 uninstall --all
-
stop: Stop app
$ uiautomator2 stop com.example.app
$ uiautomator2 stop --all
-
doctor:
$ uiautomator2 doctor
[I 2024-04-25 19:53:36,288 __main__:101 pid:15596] uiautomator2 is OK
API Documents
New command timeout (Removed)
When python quit, the UiAutomation service also quit.
Debug HTTP requests
打印出代码背后的HTTP请求信息
>>> d.debug = True
>>> d.info
12:32:47.182 $ curl -X POST -d '{"jsonrpc": "2.0", "id": "b80d3a488580be1f3e9cb3e926175310", "method": "deviceInfo", "params": {}}' 'http://127.0.0.1:54179/jsonrpc/0'
12:32:47.225 Response >>>
{"jsonrpc":"2.0","id":"b80d3a488580be1f3e9cb3e926175310","result":{"currentPackageName":"com.android.mms","displayHeight":1920,"displayRotation":0,"displaySizeDpX":360,"displaySizeDpY":640,"displayWidth":1080,"productName"
:"odin","screenOn":true,"sdkInt":25,"naturalOrientation":true}}
<<< END
Implicit wait
设置元素查找等待时间(默认20s)
d.implicitly_wait(10.0)
d(text="Settings").click()
print("wait timeout", d.implicitly_wait())
This function will have influence on click
, long_click
, drag_to
, get_text
, set_text
, clear_text
, etc.
App management
This part showcases how to perform app management
Install an app
We only support installing an APK from a URL
d.app_install('http://some-domain.com/some.apk')
Launch an app
d.app_start("com.example.hello_world")
d.app_start("com.example.hello_world", use_monkey=True)
d.app_start("com.example.hello_world", ".MainActivity")
Stop an app
d.app_stop("com.example.hello_world")
d.app_clear('com.example.hello_world')
Stop all running apps
d.app_stop_all()
d.app_stop_all(excludes=['com.examples.demo'])
Get app info
d.app_info("com.examples.demo")
img = d.app_icon("com.examples.demo")
img.save("icon.png")
List all running apps
d.app_list_running()
Wait until app running
pid = d.app_wait("com.example.android")
if not pid:
print("com.example.android is not running")
else:
print("com.example.android pid is %d" % pid)
d.app_wait("com.example.android", front=True)
d.app_wait("com.example.android", timeout=20.0)
Added in version 1.2.0
Push and pull files
-
push a file to the device
d.push("foo.txt", "/sdcard/")
d.push("foo.txt", "/sdcard/bar.txt")
with open("foo.txt", 'rb') as f:
d.push(f, "/sdcard/")
d.push("foo.sh", "/data/local/tmp/", mode=0o755)
-
pull a file from the device
d.pull("/sdcard/tmp.txt", "tmp.txt")
d.pull("/sdcard/some-file-not-exists.txt", "tmp.txt")
Other app operations
d.app_auto_grant_permissions("io.appium.android.apis")
d.open_url("appname://appnamehost")
Basic API Usages
This part showcases how to perform common device operations:
Shell commands
-
Run a short-lived shell command with a timeout protection. (Default timeout 60s)
Note: timeout support require atx-agent >=0.3.3
adb_shell
function is deprecated. Use shell
instead.
Simple usage
output, exit_code = d.shell("pwd", timeout=60)
output = d.shell("pwd").output
exit_code = d.shell("pwd").exit_code
The first argument can be list. for example
output, exit_code = d.shell(["ls", "-l"])
This returns a string for stdout merged with stderr.
If the command is a blocking command, shell
will also block until the command is completed or the timeout kicks in. No partial output will be received during the execution of the command. This API is not suitable for long-running commands. The shell command given runs in a similar environment of adb shell
, which has a Linux permission level of adb
or shell
(higher than an app permission).
-
Run a long-running shell command (Removed)
Session
Session represent an app lifecycle. Can be used to start app, detect app crash.
-
Launch and close app
sess = d.session("com.netease.cloudmusic")
sess.close()
sess.restart()
-
Use python with
to launch and close app
with d.session("com.netease.cloudmusic") as sess:
sess(text="Play").click()
-
Attach to the running app
sess = d.session("com.netease.cloudmusic", attach=True)
-
Detect app crash
sess(text="Music").click()
sess(text="Music").click()
sess.running()
Retrieve the device info
Get basic information
d.info
Below is a possible output:
{'currentPackageName': 'com.android.systemui',
'displayHeight': 1560,
'displayRotation': 0,
'displaySizeDpX': 360,
'displaySizeDpY': 780,
'displayWidth': 720,
'naturalOrientation': True,
'productName': 'ELE-AL00',
'screenOn': True,
'sdkInt': 29}
Get window size
print(d.window_size())
Get current app info. For some android devices, the output could be empty (see Output example 3)
print(d.app_current())
Wait activity
d.wait_activity(".ApiDemos", timeout=10)
Get device serial number
print(d.serial)
Get WLAN ip
print(d.wlan_ip)
Get detailed device info d.device_info
device_info
print(d.device_info)
Below is a possible output:
{'arch': 'arm64-v8a',
'brand': 'google',
'model': 'sdk_gphone64_arm64',
'sdk': 34,
'serial': 'EMULATOR34X1X19X0',
'version': 14}
Clipboard
Get of set clipboard content
设置粘贴板内容或获取内容
Get clipboard content
get clipboard requires IME(com.github.uiautomator/.AdbKeyboard) call d.set_input_ime()
before using it.
```python
# get clipboard content
print(d.clipboard)
```
Key Events
-
Turn on/off screen
d.screen_on()
d.screen_off()
-
Get current screen status
d.info.get('screenOn')
-
Press hard/soft key
d.press("home")
d.press("back")
d.press(0x07, 0x02)
-
These key names are currently supported:
- home
- back
- left
- right
- up
- down
- center
- menu
- search
- enter
- delete ( or del)
- recent (recent apps)
- volume_up
- volume_down
- volume_mute
- camera
- power
You can find all key code definitions at Android KeyEvnet
Gesture interaction with the device
-
Click on the screen
d.click(x, y)
-
Double click
d.double_click(x, y)
d.double_click(x, y, 0.1)
-
Long click on the screen
d.long_click(x, y)
d.long_click(x, y, 0.5)
-
Swipe
d.swipe(sx, sy, ex, ey)
d.swipe(sx, sy, ex, ey, 0.5)
-
SwipeExt 扩展功能
d.swipe_ext("right")
d.swipe_ext("right", scale=0.9)
d.swipe_ext("right", box=(0, 0, 100, 100))
d.swipe_ext("up", scale=0.8)
from uiautomator2 import Direction
d.swipe_ext(Direction.FORWARD)
d.swipe_ext(Direction.BACKWARD)
d.swipe_ext(Direction.HORIZ_FORWARD)
d.swipe_ext(Direction.HORIZ_BACKWARD)
-
Drag
d.drag(sx, sy, ex, ey)
d.drag(sx, sy, ex, ey, 0.5)
-
Swipe points
d.swipe_points([(x0, y0), (x1, y1), (x2, y2)], 0.2))
多用于九宫格解锁,提前获取到每个点的相对坐标(这里支持百分比),
更详细的使用参考这个帖子 使用u2实现九宫图案解锁
-
Touch and drap (Beta)
这个接口属于比较底层的原始接口,感觉并不完善,不过凑合能用。注:这个地方并不支持百分比
d.touch.down(10, 10)
time.sleep(.01)
d.touch.move(15, 15)
d.touch.up(10, 10)
Note: click, swipe, drag operations support percentage position values. Example:
d.long_click(0.5, 0.5)
means long click center of screen
Screen-related
-
Retrieve/Set device orientation
The possible orientations:
natural
or n
left
or l
right
or r
upsidedown
or u
(can not be set)
orientation = d.orientation
d.set_orientation('l')
d.set_orientation("l")
d.set_orientation("r")
d.set_orientation("n")
-
Freeze/Un-freeze rotation
d.freeze_rotation()
d.freeze_rotation(False)
-
Take screenshot
d.screenshot("home.jpg")
image = d.screenshot()
image.save("home.jpg")
import cv2
image = d.screenshot(format='opencv')
cv2.imwrite('home.jpg', image)
imagebin = d.screenshot(format='raw')
open("some.jpg", "wb").write(imagebin)
-
Dump UI hierarchy
xml = d.dump_hierarchy()
xml = d.dump_hierarchy(compressed=False, pretty=False, max_depth=50)
-
Open notification or quick settings
d.open_notification()
d.open_quick_settings()
Selector
Selector is a handy mechanism to identify a specific UI object in the current window.
d(text='Clock', className='android.widget.TextView')
Selector supports below parameters. Refer to UiSelector Java doc for detailed information.
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
Children and siblings
-
children
d(className="android.widget.ListView").child(text="Bluetooth")
-
siblings
d(text="Google").sibling(className="android.widget.ImageView")
-
children by text or description or instance
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"
)
-
child_by_description
is to find children whose grandchildren have
the specified description, other parameters being similar to child_by_text
.
-
child_by_instance
is to find children with has a child UI element anywhere
within its sub hierarchy that is at the instance specified. It is performed
on visible views without scrolling.
See below links for detailed information:
- UiScrollable,
getChildByDescription
, getChildByText
, getChildByInstance
- UiCollection,
getChildByDescription
, getChildByText
, getChildByInstance
Above methods support chained invoking, e.g. for below hierarchy
<node index="0" text="" resource-id="android:id/list" class="android.widget.ListView" ...>
<node index="0" text="WIRELESS & NETWORKS" 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>
To click the switch widget right to the TextView 'Wi‑Fi', we need to select the switch widgets first. However, according to the UI hierarchy, more than one switch widgets exist and have almost the same properties. Selecting by className will not work. Alternatively, the below selecting strategy would help:
d(className="android.widget.ListView", resourceId="android:id/list") \
.child_by_text("Wi‑Fi", className="android.widget.LinearLayout") \
.child(className="android.widget.Switch") \
.click()
-
relative positioning
Also we can use the relative positioning methods to get the view: left
, right
, top
, bottom
.
d(A).left(B)
, selects B on the left side of A.d(A).right(B)
, selects B on the right side of A.d(A).up(B)
, selects B above A.d(A).down(B)
, selects B under A.
So for above cases, we can alternatively select it with:
d(text="Wi‑Fi").right(className="android.widget.Switch").click()
-
Multiple instances
Sometimes the screen may contain multiple views with the same properties, e.g. text, then you will
have to use the "instance" property in the selector to pick one of qualifying instances, like below:
d(text="Add new", instance=0)
In addition, uiautomator2 provides a list-like API (similar to jQuery):
d(text="Add new").count
len(d(text="Add new"))
d(text="Add new")[0]
d(text="Add new")[1]
...
for view in d(text="Add new"):
view.info
Notes: when using selectors in a code block that walk through the result list, you must ensure that the UI elements on the screen
keep unchanged. Otherwise, when Element-Not-Found error could occur when iterating through the list.
Get the selected ui object status and its information
-
Check if the specific UI object exists
d(text="Settings").exists
d.exists(text="Settings")
d(text="Settings").exists(timeout=3)
-
Retrieve the info of the specific UI object
d(text="Settings").info
Below is a possible output:
{ 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
}
-
Get/Set/Clear text of an editable field (e.g., EditText widgets)
d(text="Settings").get_text()
d(text="Settings").set_text("My text...")
d(text="Settings").clear_text()
-
Get Widget center point
x, y = d(text="Settings").center()
-
Take screenshot of widget
im = d(text="Settings").screenshot()
im.save("settings.jpg")
Perform the click action on the selected UI object
-
Perform click on the specific object
d(text="Settings").click()
d(text="Settings").click(timeout=10)
d(text="Settings").click(offset=(0.5, 0.5))
d(text="Settings").click(offset=(0, 0))
d(text="Settings").click(offset=(1, 1))
clicked = d(text='Skip').click_exists(timeout=10.0)
is_gone = d(text="Skip").click_gone(maxretry=10, interval=1.0)
-
Perform long click on the specific UI object
d(text="Settings").long_click()
Gesture actions for the specific UI object
-
Drag the UI object towards another point or another UI object
d(text="Settings").drag_to(x, y, duration=0.5)
d(text="Settings").drag_to(text="Clock", duration=0.25)
-
Swipe from the center of the UI object to its edge
Swipe supports 4 directions:
d(text="Settings").swipe("right")
d(text="Settings").swipe("left", steps=10)
d(text="Settings").swipe("up", steps=20)
d(text="Settings").swipe("down", steps=20)
-
Two-point gesture from one point to another
d(text="Settings").gesture((sx1, sy1), (sx2, sy2), (ex1, ey1), (ex2, ey2))
-
Two-point gesture on the specific UI object
Supports two gestures:
In
, from edge to centerOut
, from center to edge
d(text="Settings").pinch_in(percent=100, steps=10)
d(text="Settings").pinch_out()
-
Wait until the specific UI appears or disappears
d(text="Settings").wait(timeout=3.0)
d(text="Settings").wait_gone(timeout=1.0)
The default timeout is 20s. see global settings for more details
-
Perform fling on the specific ui object(scrollable)
Possible properties:
horiz
or vert
forward
or backward
or toBeginning
or toEnd
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()
-
Perform scroll on the specific ui object(scrollable)
Possible properties:
horiz
or vert
forward
or backward
or toBeginning
or toEnd
, or to
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()
d(scrollable=True).scroll.to(text="Security")
WatchContext
目前的这个watch_context是用threading启动的,每2s检查一次
目前还只有click这一种触发操作
with d.watch_context() as ctx:
ctx.when("^立即(下载|更新)").when("取消").click()
ctx.when("同意").click()
ctx.when("确定").click()
ctx.wait_stable()
ctx.when("仲夏之夜").call(lambda d: d.press("back"))
ctx.when("确定").call(lambda el: el.click())
with d.watch_context(builtin=True) as ctx:
ctx.when("@tb:id/jview_view").when('//*[@content-desc="图片"]').click()
另外一种写法
ctx = d.watch_context()
ctx.when("设置").click()
ctx.wait_stable()
ctx.close()
Watcher
更推荐用WatchContext 写法更简洁一些
You can register watchers to perform some actions when a selector does not find a match.
2.0.0之前使用的是 uiautomator-jar库中提供的[Watcher]((http://developer.android.com/tools/help/uiautomator/UiWatcher.html)方法,但在实践中发现一旦uiautomator连接失败重启了,所有的watcher配置都是丢失,这肯定是无法接受的。
所以目前采用了后台运行了一个线程的方法(依赖threading库),然后每隔一段时间dump一次hierarchy,匹配到元素之后执行相应的操作。
用法举例
注册监控
d.watcher.when("安装").click()
d.watcher("ANR").when(xpath="ANR").when("Force Close").click()
d.watcher.when("抢红包").press("back")
d.watcher.when("//*[@text = 'Out of memory']").call(lambda d: d.shell('am force-stop com.im.qq'))
def click_callback(d: u2.Device):
d.xpath("确定").click()
d.xpath("继续").click()
d.watcher.start()
监控操作
d.watcher.remove("ANR")
d.watcher.remove()
d.watcher.start()
d.watcher.start(2.0)
d.watcher.run()
d.watcher.stop()
d.watcher.reset()
另外文档还是有很多没有写,推荐直接去看源码watcher.py
Global settings
u2.HTTP_TIMEOUT = 60
其他的配置,目前已大部分集中到 d.settings
中,根据后期的需求配置可能会有增减。
print(d.settings)
{'operation_delay': (0, 0),
'operation_delay_methods': ['click', 'swipe'],
'wait_timeout': 20.0}
d.settings['operation_delay'] = (.5, 1)
d.settings['operation_delay_methods'] = ['click', 'swipe', 'drag', 'press']
d.settings['wait_timeout'] = 20.0
d.settings['max_depth'] = 50
对于随着版本升级,设置过期的配置时,会提示Deprecated,但是不会抛异常。
>>> d.settings['click_before_delay'] = 1
[W 200514 14:55:59 settings:72] d.settings[click_before_delay] deprecated: Use operation_delay instead
uiautomator恢复方式设置
细心的你可能发现,实际上手机安装了两个APK,一个在前台可见(小黄车)。一个包名为com.github.uiautomator.test
在后台不可见。这两个apk使用同一个证书签名的。
不可见的应用实际上是一个测试包,包含有所有的测试代码,核心的测试服务也是通过其启动的。
但是运行的时候,系统却需要那个小黄车一直在运行(在后台运行也可以)。一旦小黄车应用被杀,后台运行的测试服务也很快的会被杀掉。就算什么也不做,应用应用在后台,也会很快被系统回收掉。(这里希望高手指点一下,如何才能不依赖小黄车应用,感觉理论上是可以的,但是目前我还不会)。
让小黄车在后台运行有两种方式,一种启动应用后,放到后台(默认)。另外通过am startservice
启动一个后台服务也行。
通过 d.settings["uiautomator_runtest_app_background"] = True
可以调整该行为。True代表启动应用,False代表启动服务。
UiAutomator中的超时设置(隐藏方法)
>> d.jsonrpc.getConfigurator()
{'actionAcknowledgmentTimeout': 500,
'keyInjectionDelay': 0,
'scrollAcknowledgmentTimeout': 200,
'waitForIdleTimeout': 0,
'waitForSelectorTimeout': 0}
>> d.jsonrpc.setConfigurator({"waitForIdleTimeout": 100})
{'actionAcknowledgmentTimeout': 500,
'keyInjectionDelay': 0,
'scrollAcknowledgmentTimeout': 200,
'waitForIdleTimeout': 100,
'waitForSelectorTimeout': 0}
为了防止客户端程序响应超时,waitForIdleTimeout
和waitForSelectorTimeout
目前已改为0
Refs: Google uiautomator Configurator
Input method
这种方法通常用于不知道控件的情况下的输入。
d.send_keys("你好123abcEFG")
d.send_keys("你好123abcEFG", clear=True)
d.clear_text()
d.send_action()
print(d.current_ime())
更多参考: IME_ACTION_CODE
Toast
print(d.last_toast)
d.clear_toast()
Fixed in version 3.2.0
XPath
Java uiautoamtor中默认是不支持xpath的,所以这里属于扩展的一个功能。速度不是这么的快。
For example: 其中一个节点的内容
<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
常见用法
d.xpath("//android.widget.TextView").wait(10.0)
d.xpath("//*[@content-desc='分享']").click()
if d.xpath("//android.widget.TextView[contains(@text, 'Se')]").exists:
print("exists")
for elem in d.xpath("//android.widget.TextView").all():
print("Text:", elem.text)
print("Attrib:", elem.attrib)
print("Position:", elem.center())
点击查看其他XPath常见用法
Screenrecord (Deprecated)
视频录制(废弃),使用scrcpy来代替吧
这里没有使用手机中自带的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文件才能打开
录制的时候也可以指定fps(当前是20),这个值是率低于minicap输出图片的速度,感觉已经很好了,不建议你修改。
Enable uiautomator2 logger
from uiautomator2 import enable_pretty_logging
enable_pretty_logging()
Or
logger = logging.getLogger("uiautomator2")
# setup logger
Stop UiAutomator
Python程序退出了,UiAutomation就退出了。
不过也可以通过接口的方法停止服务
d.stop_uiautomator()
Google UiAutomator 2.0和1.x的区别
https://www.cnblogs.com/insist8089/p/6898181.html
- 新增接口:UiObject2、Until、By、BySelector
- 引入方式:2.0中,com.android.uiautomator.core.* 引入方式被废弃。改为android.support.test.uiautomator
- 构建系统:Maven 和/或 Ant(1.x);Gradle(2.0)
- 产生的测试包的形式:从zip /jar(1.x) 到 apk(2.0)
- 在本地环境以adb命令运行UIAutomator测试,启动方式的差别:
adb shell uiautomator runtest UiTest.jar -c package.name.ClassName(1.x)
adb shell am instrument -e class com.example.app.MyTest
com.example.app.test/android.support.test.runner.AndroidJUnitRunner(2.0) - 能否使用Android服务及接口? 1.x
不能;2.0能。 - og输出? 使用System.out.print输出流回显至执行端(1.x); 输出至Logcat(2.0)
- 执行?测试用例无需继承于任何父类,方法名不限,使用注解 Annotation进行(2.0); 需要继承UiAutomatorTestCase,测试方法需要以test开头(1.x)
依赖项目
Contributors
Other contributors
其他优秀的项目
排名有先后,欢迎补充
LICENSE
MIT