连接设备
有多种方式可以连接
- wifi 首先要保证你的手机(10.0.0.1)和电脑在同一个局域网内
import uiautomator2 as u2d = 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 u2d = u2.connect('123456f') # alias for u2.connect_usb('123456f')print(d.info)
- adb wifi
import uiautomator2 as u2d = 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.info12: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 raisedprint("wait timeout", d.implicitly_wait()) # get default implicit wait
将会作用在这些方法中 click
, long_click
, drag_to
, get_tex
t, set_text
, clear_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/.MainActivityd.app_start("com.example.hello_world", ".MainActivity")
- 停止app
# equivalent to `am force-stop`, thus you could lose datad.app_stop("com.example.hello_world") # equivalent to `pm clear`d.app_clear('com.example.hello_world')
- 停止所有运行的app
# stop alld.app_stop_all()# stop all app except for com.examples.demod.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 iconimg = 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 folderd.push("foo.txt", "/sdcard/")# push and renamed.push("foo.txt", "/sdcard/bar.txt")# push fileobjwith open("foo.txt", 'rb') as f: d.push(f, "/sdcard/")# push and change file access moded.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 deviced.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: "/\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 tricksoutput = d.shell("pwd").outputexit_code = d.shell("pwd").exit_code# The first argument can be list. for exampleoutput, exit_code = d.shell(["ls", "-l"])# output: "/....", exit_code: 0
该 adb shell
会阻塞,直到有结果活超时,不适合长时间使用
- 长期运行的shell
r = d.shell("logcat", stream=True)# r: requests.models.Responsedeadline = time.time() + 10 # run maxium 10stry: 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() # 冷启动网易云音乐
-
使用python
with
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 runningsess(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 futuresess.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', 'usingBeganAt': '0001-01-01T00:00:00Z'}
-
屏幕大小
print(d.window_size())# device upright output example: (1080, 1920)# device horizontal output example: (1920, 1080)
-
运行的app
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}
- 等待
activity
d.wait_activity(".ApiDemos", timeout=10) # default timeout 10.0 seconds# Output: true of false
- 获取设备号
print(d.serial)# output example: 74aAEDR428Z9
- 局域网ip
print(d.wlan_ip)# output example: 10.0.0.1
键盘事件
- 打开/关闭屏幕
d.screen_on() # turn on the screend.screen_off() # turn off the screen
- 获取屏幕状态
d.info.get('screenOn') # require Android >= 4.4
- 点击按键
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)
- 当前支持以下键名:
- home
- back
- left
- right
- up
- down
- center
- menu
- search
- enter
- delete ( or del)
- recent (recent apps)
- volume_up
- volume_down
- volume_mute
- camera
- power
更多key code定义参考Android KeyEvent
- 解锁
python d.unlock() # This is equivalent to # 1. launch activity: com.github.uiautomator.ACTION_IDENTIFY # 2. press the "home" key
与设备的手势交互
-
单击
d.click(x, y)
-
双击
d.double_click(x, y)d.double_click(x, y, 0.1) # default duration between two click is 0.1s
-
长按
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.swipe_ext("right") # 屏幕右滑,4选1 "left", "right", "up", "bottom"d.swipe_ext("right", scale=0.9) # 默认0.9, 滑动距离为屏幕宽度的90%d.swipe_ext("right", box=(0, 0, 100, 100)) # 在 (0,0) -> (100, 100) 这个区域做滑动
* 拖拽```pythond.drag(sx, sy, ex, ey)d.drag(sx, sy, ex, ey, 0.5) # swipe for 0.5s(default)
-
滑动解锁
# swipe from point(x0, y0) to point(x1, y1) then to point(x2, y2)# time will speed 0.2s bwtween two pointsd.swipe_points([(x0, y0), (x1, y1), (x2, y2)], 0.2))
多用于九宫格解锁,提前获取到每个点的相对坐标(这里支持百分比), 更详细的使用参考这个帖子 使用u2实现九宫图案解锁
-
按着拖动
这个接口属于比较底层的原始接口,感觉并不完善,不过凑合能用。注:这个地方并不支持百分比
d.touch.down(10, 10) # 模拟按下time.sleep(.01) # down 和 move 之间的延迟,自己控制d.touch.move(15, 15) # 模拟移动d.touch.up() # 模拟抬起
注: click
, swipe
, drag
这几个操作支持百分比
屏幕相关
- 设置屏幕方向
- 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 rotationd.freeze_rotation()# un-freeze rotationd.freeze_rotation(False)
- 截屏
# 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 firstimage = 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 firstimport cv2image = d.screenshot(format='opencv')cv2.imwrite('home.jpg', image)# get raw jpeg dataimagebin = d.screenshot(format='raw')open("some.jpg", "wb").write(imagebin)
- dump 视图结构
# get the UI hierarchy dump content (unicoded).xml = d.dump_hierarchy()
- 打开通知和设置
d.open_notification()d.open_quick_settings()
选择器
选择器是一种灵活的机制用户标识一个特殊的ui对象
# Select the object with text 'Clock' and its className is 'android.widget.TextView'd(text='Clock', className='android.widget.TextView')
注: 可以使用d.info
输出定位的控件信息,来确认是否正确
选择器支持以下参数,更多参考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
子和兄弟控件(Children and siblings)
- children
# get the children or grandchildrend(className="android.widget.ListView").child(text="Bluetooth")
- siblings (兄弟)
# get siblingsd(text="Google").sibling(className="android.widget.ImageView")
text
description
instance
定位子view
# get the child matching the condition className="android.widget.LinearLayout"# and also its children or grandchildren with text "Bluetooth"d(className="android.widget.ListView", resourceId="android:id/list") \ .child_by_text("Bluetooth", className="android.widget.LinearLayout")# get children by allowing scroll searchd(className="android.widget.ListView", resourceId="android:id/list") \ .child_by_text( "Bluetooth", allow_scroll_search=True, className="android.widget.LinearLayout" )
child_by_description
child_by_instance
和 child_by_instance
差不多,只是条件换了,但是都是查找屏幕内的view
例子dom信息如下:
<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>
这里有一个例子来说明如何使用
注: 不同版本的settings可能实现不一样,例如6.0的就不是ListView
d(className="android.widget.ListView", resourceId="android:id/list") \ .child_by_text("Wi‑Fi", className="android.widget.LinearLayout") \ .child(className="android.widget.Switch") \ .click()
-
相对位置
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.
## select "switch" on the right side of "Wi‑Fi"d(text="Wi‑Fi").right(className="android.widget.Switch").click()
- 多个实例
有时候界面上会有多个view具有相同的属性,例如text
,这时候你可以使用instance
来选择你需要的实例
d(text="Add new", instance=0) # which means the first instance with text "Add new"
更多api信息 list-like API (similar to jQuery):
# get the count of views with text "Add new" on current screend(text="Add new").count# same as count propertylen(d(text="Add new"))# get the instance via indexd(text="Add new")[0]d(text="Add new")[1]...# iteratorfor view in d(text="Add new"): view.info # ...
注:在遍历list的时候要保证界面是不变化的,不然发生错误
ui对象状态和信息
- 检查ui是否存在
d(text="Settings").exists # True if exists, else Falsed.exists(text="Settings") # alias of above property.# advanced usaged(text="Settings").exists(timeout=3) # wait Settings appear in 3s, same as .wait(3)
- 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}
- 获取/设置/清空 输入框
d(text="Settings").get_text() # get widget textd(text="Settings").set_text("My text...") # set the textd(text="Settings").clear_text() # clear the text
- 获取控件中心点
x, y = d(text="Settings").center()# x, y = d(text="Settings").center(offset=(0, 0)) # left-top x, y
点击选中的ui对象
- 单击
# click on the center of the specific ui objectd(text="Settings").click()# wait element to appear for at most 10 seconds and then clickd(text="Settings").click(timeout=10)# click with offset(x_offset, y_offset)# click_x = x_offset * width + x_left_top# click_y = y_offset * height + y_left_topd(text="Settings").click(offset=(0.5, 0.5)) # Default centerd(text="Settings").click(offset=(0, 0)) # click left-topd(text="Settings").click(offset=(1, 1)) # click right-bottom# click when exists in 10s, default timeout 0sclicked = d(text='Skip').click_exists(timeout=10.0)# click until element gone, return boolis_gone = d(text="Skip").click_gone(maxretry=10, interval=1.0) # maxretry default 10, interval default 1.0
- 长按
# long click on the center of the specific UI objectd(text="Settings").long_click()
手势操作
- 拖动
# notes : drag can not be used for Android<4.3.# drag the UI object to a screen point (x, y), in 0.5 secondd(text="Settings").drag_to(x, y, duration=0.5)# drag the UI object to (the center position of) another UI object, in 0.25 secondd(text="Settings").drag_to(text="Clock", duration=0.25)
-
滑动
支持四个方向- left
- right
- top
- bottom
d(text="Settings").swipe("right")d(text="Settings").swipe("left", steps=10)d(text="Settings").swipe("up", steps=20) # 1 steps is about 5ms, so 20 steps is about 0.1sd(text="Settings").swipe("down", steps=20)
-
双指滑动
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
# notes : pinch can not be set until Android 4.3.# from edge to center. here is "In" not "in"d(text="Settings").pinch_in(percent=100, steps=10)# from center to edged(text="Settings").pinch_out()
-
等待ui出现活消失
# wait until the ui object appearsd(text="Settings").wait(timeout=3.0) # return bool# wait until the ui object goned(text="Settings").wait_gone(timeout=1.0)
-
fling指定的ui(scrollable)
horiz
orvert
(横竖)forward
orbackward
ortoBeginning
ortoEnd
(前后)
# fling forward(default) vertically(default) d(scrollable=True).fling()# fling forward horizontallyd(scrollable=True).fling.horiz.forward()# fling backward verticallyd(scrollable=True).fling.vert.backward()# fling to beginning horizontallyd(scrollable=True).fling.horiz.toBeginning(max_swipes=1000)# fling to end verticallyd(scrollable=True).fling.toEnd()
- 滑动指定的ui对象(scrollable)
Possible properties:
horiz
orvert
forward
orbackward
ortoBeginning
ortoEnd
, orto
# scroll forward(default) vertically(default)d(scrollable=True).scroll(steps=10)# scroll forward horizontallyd(scrollable=True).scroll.horiz.forward(steps=100)# scroll backward verticallyd(scrollable=True).scroll.vert.backward()# scroll to beginning horizontallyd(scrollable=True).scroll.horiz.toBeginning(steps=100, max_swipes=1000)# scroll to end verticallyd(scrollable=True).scroll.toEnd()# scroll forward vertically until specific ui object appearsd(scrollable=True).scroll.to(text="Security")
观察者
当选择器找不到指定对象的时候,你可以注册观察者来执行一些操作
- 注册
d.watcher("AUTO_FC_WHEN_ANR").when(text="ANR").when(text="Wait") \ .click(text="Force Close")# d.watcher(name) ## creates a new named watcher.# .when(condition) ## the UiSelector condition of the watcher.# .click(target) ## perform click action on the target UiSelector.#d.watcher("ALERT").when(text="OK").click()# Same asd.watcher("ALERT").when(text="OK").click(text="OK")# 多条件d.watcher("AUTO_FC_WHEN_ANR").when(text="ANR").when(text="Wait") \ .press("back", "home")# d.watcher(name) ## creates a new named watcher.# .when(condition) ## the UiSelector condition of the watcher.# .press(<keyname>, ..., <keyname>.() ## press keys one by one in sequence.
- 检查是否触发
d.watcher("watcher_name").triggered# true in case of the specified watcher triggered, else false
- 移除
# remove the watcherd.watcher("watcher_name").remove()
- 列出所有观察者
d.watchers# a list of all registered watchers
- 检查是否触发,任意一个
d.watchers.triggered# true in case of any watcher triggered
- 重置
# reset all triggered watchers, after that, d.watchers.triggered will be false.d.watchers.reset()
- 移除
# remove all registered watchersd.watchers.remove()# remove the named watcher, same as d.watcher("watcher_name").remove()d.watchers.remove("watcher_name")
- 强制运行所有
# force to run all registered watchersd.watchers.run()
另外文档还是有很多没有写,推荐直接去看源码init.py
现在推荐使用这种观察者来处理弹窗等
d.xpath.when(“服务条款%”).when(“同意”).click() d.xpath.when(“立即体验”).click() d.xpath.when(“跳过”).click()d.xpath.run_watchers() # 运行一次弹窗检查 d.xpath.watch_background(2.0)
全局设置
# set delay 1.5s after each UI click and click, 点击延迟d.click_post_delay = 1.5 # default no delay# set default element wait timeout (seconds) 默认超时时间d.wait_timeout = 30.0 # default 20.0
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}
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_CODE。send_action
先broadcast命令发送给输入法操作IME_ACTION_CODE
,由输入法完成后续跟EditText的通信。(原理我不太清楚,有了解的,提issue告诉我)
Toast
Show Toast
d.toast.show("Hello world")d.toast.show("Hello world", 1.0) # show for 1.0s, default 1.0s
Get Toast
# [Args]# 5.0: max wait timeout. Default 10.0# 10.0: cache time. return cache toast if already toast already show up in recent 10 seconds. Default 10.0 (Maybe change in the furture)# "default message": return if no toast finally get. Default Noned.toast.get_message(5.0, 10.0, "default message")# common usageassert "Short message" in d.toast.get_message(5.0, default="")# clear cached toastd.toast.reset()# Now d.toast.get_message(0) is None
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-descresourceId -> resource-id
常见用法
# wait exists 10sd.xpath("//android.widget.TextView").wait(10.0)# find and clickd.xpath("//*[@content-desc='分享']").click()# check existsif d.xpath("//android.widget.TextView[contains(@text, 'Se')]").exists: print("exists")# get all text-view text, attrib and center pointfor 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常见用法
See also: https://github.com/openatx/uiautomator2/blob/master/uiautomator2/ext/xpath/README.md
uiautomator2 是一个超级好的项目,希望大家一起参与多提pr