QQ communication group: 815453846 Discord: https://discord.gg/PbJhnZJKDd
I haven’t maintained this project for a while (maybe two years), but recently I needed to re-study Android native automation. Of course, I also investigated Appium. After comparing it, I found that the running speed of the uiautomator2 project is really good. It’s fast, from detecting elements to clicking, it’s all in milliseconds, and the code is easier to understand. I really didn't expect that I had written such a magical project before. How could such a good project let it gather dust? It needs to be reorganized and some junk code should be cleaned up. So the project version has been upgraded from 2.xx to 3.xx
Users who are still using version 2.xx can take a look at 2to3 before deciding whether to upgrade to 3.xx (I personally highly recommend upgrading)
After all, version 2 to 3 is a major version upgrade, and many functions have been deleted. The first thing to delete is atx-agent, and secondly there are a bunch of atx-agent related functions. Deprecated functions such as init.
Version numbers of various dependent libraries
UiAutomator is a Java library provided by Google for Android automation testing, based on the Accessibility service. It is very powerful and can test third-party apps, obtain any control attribute of any APP on the screen, and perform any operations on it, but it has two disadvantages: 1. The test script can only use Java language 2. The test script It must be packaged into a jar or apk package and uploaded to the device to run.
We hope that the test logic can be written in Python and can control the mobile phone while running on the computer. I would like to thank Xiaocong He (@xiaocong) very much for realizing this idea (see xiaocong/uiautomator). The principle is to run an http rpc service on the mobile phone, open up the functions in uiautomator, and then use these http The interface is encapsulated into a Python library. Because the xiaocong/uiautomator
library has not been updated for a long time. So we directly forked a version. In order to make it easier to distinguish, we added 2 openatx/uiautomator2 at the end. I also forked a copy of the corresponding Android package source code, openatx/android-uiautomator-server.
In addition to fixing bugs in the original library, many new features have also been added. It mainly includes the following parts:
Let me explain here first, because many people often ask that openatx/uiautomator2 does not support iOS testing. If you need iOS automated testing, you can go to this library openatx/facebook-wda.
PS: This library
https://github.com/NeteaseGame/ATXIt is no longer under maintenance, please replace it as soon as possible.
Here is a quick reference QUICK REFERENCE GUIDE suitable for those who have already started. Comments are welcome.
First prepare one (not two) Android phone with开发者选项
turned on, connect it to the computer, and make sure you can see the connected device by executing adb devices
.
Run pip3 install -U uiautomator2
to install uiautomator2
Run python
from the command line to open the python interactive window. Then enter the following command into the window.
import uiautomator2 as u2
d = u2 . connect () # connect to device
print ( d . info )
When you see output similar to the one below, you can officially start using our library. Because this library has too many functions and there is a lot of content behind it, you need to read it slowly....
{'currentPackageName': 'net.oneplus.launcher', 'displayHeight': 1920, 'displayRotation': 0, 'displaySizeDpX': 411, 'displaySizeDpY': 731, 'displayWidth': 1080, 'productName': 'OnePlus5', '
screenOn': True, 'sdkInt': 27, 'naturalOrientation': True}
In addition, in order to maintain stability, you need to enable the floating window permission of小黄车
. Reference article py-uiautomator2 makes services available for a long time through floating windows
It usually succeeds, but there may be surprises. You can join the QQ group to report problems (the group number is at the top). There are many big guys in the group who can help you solve problems.
Thank you to all our sponsors! ?
Empty
Recommendation of excellent articles (welcome at me in QQ group for feedback)
成都-测试只会一点点
Installation
Connect to a device
Command line
Global settings
App management
UI automation
Contributors
LICENSE
Install uiautomator2
pip install -U uiautomator2
Test whether the installation is successful uiautomator2 --help
UI Inspector
pip install uiautodev
# 启动
uiauto.dev
Open https://uiauto.dev in the browser to view the interface structure of the current device.
uiauto.dev
uiauto.dev is a project independent of uiautomator2, used to view the layer structure. It is a reconstructed version of the old project editor. There may be a fee in the future (the price is definitely worth the money) to support the continued maintenance of the current project. If you are interested, you can join the group for discussion (including making requests) QQ group 536481989
use serialno to connect device eg. 123456f
(seen from adb devices
)
import uiautomator2 as u2
d = u2 . connect ( '123456f' ) # alias for u2.connect_usb('123456f')
print ( d . info )
Serial can be passed through env-var ANDROID_SERIAL
# export ANDROID_SERIAL=123456f
d = u2 . connect ()
$device_ip
represents the IP address of the device
If you need to specify the device, you need to pass in --serial
such as python3 -m uiautomator2 --serial bff1234 <SubCommand>
, SubCommand is a subcommand (screenshot, current, etc.)
1.0.3 Added:
python3 -m uiautomator2
equals touiautomator2
screenshot: screenshot
$ uiautomator2 screenshot screenshot.jpg
current: Get the current package name and 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 # 停止一个app
$ uiautomator2 stop --all # 停止所有的app
doctor:
$ uiautomator2 doctor
[I 2024-04-25 19:53:36,288 __main__:101 pid:15596] uiautomator2 is OK
When python quit, the UiAutomation service also quit.
Print out the HTTP request information behind the code
> >> 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
Set element search waiting time (default 20s)
d . implicitly_wait ( 10.0 ) # 也可以通过d.settings['wait_timeout'] = 10.0 修改
d ( text = "Settings" ). click () # if Settings button not show in 10s, UiObjectNotFoundError will raised
print ( "wait timeout" , d . implicitly_wait ()) # get default implicit wait
This function will have influence on click
, long_click
, drag_to
, get_text
, set_text
, clear_text
, etc.
This part showcases how to perform app management
We only support installing an APK from a URL
d . app_install ( 'http://some-domain.com/some.apk' )
# 默认的这种方法是先通过atx-agent解析apk包的mainActivity,然后调用am start -n $package/$activity启动
d . app_start ( "com.example.hello_world" )
# 使用 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" )
# equivalent to `am force-stop`, thus you could lose data
d . app_stop ( "com.example.hello_world" )
# equivalent to `pm clear`
d . app_clear ( 'com.example.hello_world' )
# stop all
d . app_stop_all ()
# stop all app except for com.examples.demo
d . app_stop_all ( excludes = [ 'com.examples.demo' ])
d . app_info ( "com.examples.demo" )
# expect output
#{
# "mainActivity": "com.github.uiautomator.MainActivity",
# "label": "ATX",
# "versionName": "1.1.7",
# "versionCode": 1001007,
# "size":1760809
#}
# save app icon
img = d . app_icon ( "com.examples.demo" )
img . save ( "icon.png" )
d . app_list_running ()
# expect output
# ["com.xxxx.xxxx", "com.github.uiautomator", "xxxx"]
pid = d . app_wait ( "com.example.android" ) # 等待应用运行, return pid(int)
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 ) # 最长等待时间20s(默认)
Added in version 1.2.0
push a file to the device
# push to a folder
d . push ( "foo.txt" , "/sdcard/" )
# push and rename
d . push ( "foo.txt" , "/sdcard/bar.txt" )
# push fileobj
with open ( "foo.txt" , 'rb' ) as f :
d . push ( f , "/sdcard/" )
# push and change file access mode
d . push ( "foo.sh" , "/data/local/tmp/" , mode = 0o755 )
pull a file from the device
d . pull ( "/sdcard/tmp.txt" , "tmp.txt" )
# FileNotFoundError will raise if the file is not found on the device
d . pull ( "/sdcard/some-file-not-exists.txt" , "tmp.txt" )
# grant all the permissions
d . app_auto_grant_permissions ( "io.appium.android.apis" )
# open scheme
d . open_url ( "appname://appnamehost" )
# same as
# adb shell am start -a android.intent.action.VIEW -d "appname://appnamehost"
This part showcases how to perform common device operations:
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 ) # timeout 60s (Default)
# output: "/n", exit_code: 0
# Similar to command: adb shell pwd
# Since `shell` function return type is `namedtuple("ShellResponse", ("output", "exit_code"))`
# so we can do some tricks
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" ])
# output: "/....", exit_code: 0
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 represents an app lifecycle. Can be used to start app, detect app crash.
Launch and close app
sess = d . session ( "com.netease.cloudmusic" ) # start 网易云音乐
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
# launch app if not running, skip launch if already running
sess = d . session ( "com.netease.cloudmusic" , attach = True )
Detect app crash
# When app is still running
sess ( text = "Music" ). click () # operation goes normal
# If app crash or quit
sess ( text = "Music" ). click () # raise SessionBrokenError
# other function calls under session will raise SessionBrokenError too
# check if session is ok.
# Warning: function name may change in the future
sess . running () # True or False
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 ())
# device upright output example: (1080, 1920)
# device horizontal output example: (1920, 1080)
Get current app info. For some android devices, the output could be empty (see Output example 3 )
print ( d . app_current ())
# Output example 1: {'activity': '.Client', 'package': 'com.netease.example', 'pid': 23710}
# Output example 2: {'activity': '.Client', 'package': 'com.netease.example'}
# Output example 3: {'activity': None, 'package': None}
Wait activity
d . wait_activity ( ".ApiDemos" , timeout = 10 ) # default timeout 10.0 seconds
# Output: true of false
Get device serial number
print ( d . serial )
# output example: 74aAEDR428Z9
Get WLAN ip
print ( d . wlan_ip )
# output example: 10.0.0.1 or None
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}
Get of set clipboard content
Set pasteboard content or get content
clipboard/set_clipboard
d . clipboard = 'hello-world'
# or
d . set_clipboard ( 'hello-world' , 'label' )
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)
```
Turn on/off screen
d . screen_on () # turn on the screen
d . screen_off () # turn off the screen
Get current screen status
d . info . get ( 'screenOn' ) # require Android >= 4.4
Press hard/soft key
d . press ( "home" ) # press the home key, with key name
d . press ( "back" ) # press the back key, with key name
d . press ( 0x07 , 0x02 ) # press keycode 0x07('0') with META ALT(0x02)
These key names are currently supported:
You can find all key code definitions at Android KeyEvnet
Unlock screen
d . unlock ()
# This is equivalent to
# 1. press("power")
# 2. swipe from left-bottom to right-top
Click on the screen
d . click ( x , y )
Double click
d . double_click ( x , y )
d . double_click ( x , y , 0.1 ) # default duration between two click is 0.1s
Long click on the screen
d . long_click ( x , y )
d . long_click ( x , y , 0.5 ) # long click 0.5s (default)
Swipe
d . swipe ( sx , sy , ex , ey )
d . swipe ( sx , sy , ex , ey , 0.5 ) # swipe for 0.5s(default)
SwipeExt extension function
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 ) # 代码会vkk
# 还可以使用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 ) # 页面水平左翻
Drag
d . drag ( sx , sy , ex , ey )
d . drag ( sx , sy , ex , ey , 0.5 ) # swipe for 0.5s(default)
Swipe points
# swipe from point(x0, y0) to point(x1, y1) then to point(x2, y2)
# time will speed 0.2s bwtween two points
d . swipe_points ([( x0 , y0 ), ( x1 , y1 ), ( x2 , y2 )], 0.2 ))
It is mostly used to unlock the Jiugong pattern. The relative coordinates of each point can be obtained in advance (percentage is supported here). For more detailed usage, please refer to this post. Use u2 to unlock the Jiugong pattern.
Touch and drap (Beta)
This interface is a relatively low-level primitive interface. It doesn't feel perfect, but it is usable. Note: This place does not support percentages
d . touch . down ( 10 , 10 ) # 模拟按下
time . sleep ( .01 ) # down 和 move 之间的延迟,自己控制
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
Retrieve/Set device orientation
The possible orientations:
natural
or n
left
or l
right
or r
upsidedown
or u
(can not be set) # retrieve orientation. the output could be "natural" or "left" or "right" or "upsidedown"
orientation = d . orientation
# WARNING: not pass testing in my TT-M1
# set orientation and freeze rotation.
# notes: setting "upsidedown" requires Android>=4.3.
d . set_orientation ( 'l' ) # or "left"
d . set_orientation ( "l" ) # or "left"
d . set_orientation ( "r" ) # or "right"
d . set_orientation ( "n" ) # or "natural"
Freeze/Un-freeze rotation
# freeze rotation
d . freeze_rotation ()
# un-freeze rotation
d . freeze_rotation ( False )
Take screenshot
# take screenshot and save to a file on the computer, require Android>=4.2.
d . screenshot ( "home.jpg" )
# get PIL.Image formatted images. Naturally, you need pillow installed first
image = d . screenshot () # default format="pillow"
image . save ( "home.jpg" ) # or home.png. Currently, only png and jpg are supported
# get opencv formatted images. Naturally, you need numpy and cv2 installed first
import cv2
image = d . screenshot ( format = 'opencv' )
cv2 . imwrite ( 'home.jpg' , image )
# get raw jpeg data
imagebin = d . screenshot ( format = 'raw' )
open ( "some.jpg" , "wb" ). write ( imagebin )
Dump UI hierarchy
# get the UI hierarchy dump content
xml = d . dump_hierarchy ()
# compressed=True: include not import nodes
# pretty: format xml
# max_depth: limit xml depth, default 50
xml = d . dump_hierarchy ( compressed = False , pretty = False , max_depth = 50 )
Open notification or quick settings
d . open_notification ()
d . open_quick_settings ()
Selector is a handy mechanism to identify a specific UI object in the current window.
# Select the object with text 'Clock' and its className is 'android.widget.TextView'
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
# get the children or grandchildren
d ( className = "android.widget.ListView" ). child ( text = "Bluetooth" )
siblings