ATX uiautomator2 README 学习

连接设备

有多种方式可以连接

  • wifi 首先要保证你的手机(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
    usb通过设备号连接,可以通过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")

# Equals to
# + Shell: adb connect 10.0.0.1:5555
# + Python: u2.connect_usb("10.0.0.1:5555")

全局设置

里面包含一些u2全局设置的属性

  • Http debug
>>> 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
  • 默认等待超时时间
d.implicitly_wait(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

将会作用在这些方法中 clicklong_clickdrag_toget_text, set_textclear_text,等.

app管理

  • 安装
d.app_install('http://some-domain.com/some.apk')
  • 唤起app
# 默认的这种方法是先通过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")
  • 停止app
# 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')
  • 停止所有运行的app
# stop all
d.app_stop_all()
# stop all app except for com.examples.demo
d.app_stop_all(excludes=['com.examples.demo'])
  • 获取app信息
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")
  • 获取所有正在运行的app
d.app_list_running()
# expect output
# ["com.xxxx.xxxx", "com.github.uiautomator", "xxxx"]
  • 等待app运行
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(默认)
  • 传文件到设备
# 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)
  • 从设备拉取一个文件到电脑
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")
  • 设备健康检查
d.healthcheck()

常见api使用

介绍如何操作设备

shell命令

  • 具有超时保护的shell 命令(默认60s)
output, exit_code = d.shell("pwd", timeout=60) # timeout 60s (Default)
# output: "/ ", 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

该 adb shell会阻塞,直到有结果活超时,不适合长时间使用

  • 长期运行的shell
r = d.shell("logcat", stream=True)
# r: requests.models.Response
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("Read:", line.decode('utf-8'))
finally:
r.close() # this method must be called

r.close() 时,命令将会被终止

会话

会话代表应用程序生命周期。 可用于启动应用程序,检测应用程序崩溃。

  • 唤起和关闭app

    sess = d.session("com.netease.cloudmusic") # start 网易云音乐
    sess.close() # 停止网易云音乐
    sess.restart() # 冷启动网易云音乐
  • 使用pythonwith

with d.session("com.netease.cloudmusic") as sess:
sess(text="Play").click()
  • 连接运行的app
# launch app if not running, skip launch if already running
#如果app没有运行将会唤起,运行则会连接
sess = d.session("com.netease.cloudmusic", attach=True)

# raise SessionBrokenError if not running
# 异常在app不在运行的时候
sess = d.session("com.netease.cloudmusic", attach=True, strict=True)

  • 检测app崩溃
# When app is still running
sess(text="Music").click() # operation goes normal

# If app crash or quit
#异常在app不在运行的时候
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

获取设备信息

  • 基本信息 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
}
  • 设备的详细信息 d.device_info
{'udid': '3578298f-b4:0b:44:e6:1f:90-OD103',
'version': '7.1.1',
'serial': '3578298f',
'brand': 'SMARTISAN',
'model': 'OD103',
'hwaddr': 'b4:0b:44:e6:1f:90',
'port': 7912,
'sdk': 25,
'agentVersion': 'dev',
'display': {'width': 1080, 'height': 1920},
'battery': {'acPowered': False,
'usbPowered': False,
'wirelessPowered': False,
'status': 3,
'health': 0,
'present': True,
'level': 99,
'scale': 100,
'voltage': 4316,
'temperature': 272,
'technology': 'Li-ion'},
'memory': {'total': 3690280, 'around': '4 GB'},
'cpu': {'cores': 8, 'hardware': 'Qualcomm Technologies, Inc MSM8953Pro'},
'presenceChangedAt': '0001-01-01T00:00:00Z',
'