https://github.com/xiaocong/uiautomator
這個Python庫是基於Android自帶的uiautomator測試框架的一個python封包。適用於Android 4.1以上版本,需要通過adb連接Android設備。
from uiautomator import device as d
d . screen.on()
d(text="Clock").click()
安裝
pip install uiautomator
前置條件
安裝 Android的SDK,設置Android_home環境以正確的路徑。
啟動 adb 並且通過usb數據線將電腦與Android設備相連接。
設置Android設備,在開發者選項中允許未知來源應用安裝。
導入uiautomator
如果僅有一台設備或在 Android_serial 環境變量中配置。
from uiautomator import device as d
通過設備的設備ID來確定設備對象:
from uiautomator import Device
d=Device('014E05DE0F02000E')
通過其他計算機的端口及連接運行adb服務
雖然 adb 支持sdk 4.3的 -a選項 ,但是現在它有問題。在所有非本地服務上啟動adb監聽服務使用
adb -a -P 5037 fork-server server &
from uiautomator import Device
d=Device('014E05DE0F02000E', adb_server_host='192.168.1.68', adb_server_port=5037)
注:在下面的例子中,我們使用d代表Android設備對象。
基本API用法
此部分通過一些簡單的示例顯示設備的正常操作。
檢索設備信息
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.screen.on()
#關閉屏幕
d.screen.off()
替代方法是:
#喚醒設備
d.wakeup()
#睡眠設備,一樣關閉屏幕。
d.sleep()
按硬/軟鍵
#按home鍵
d.press.home()
#按返回鍵
d.press.back()
#正常的方式按返回鍵
d.press( “back”)
#按下鍵碼0×07(‘0’)與ALT (0X02)
d.press(0x07,0X02)
目前下列按鍵支持上述方法
home
back
left
right
home
back
left
right
up
down
center
menu
search
enter
delete(or del)
recent(recent apps)
volume_up
volume_down
volume_mute
camera
power
down
center
menu
search
enter
delete(or del)
recent(recent apps)
volume_up
volume_down
volume_mute
camera
power
你可以找到所有的鍵值定義在Android KeyEvent連接如下:
developer.android.com/reference/android/view/KeyEvent.html
在設備上模擬手勢交互
#點擊屏幕
# click (x, y) on screen
d.click(x, y)
#長按屏幕
# long click (x, y) on screen
d.long_click(x, y)
滑動
# swipe from (sx, sy) to (ex, ey)
#從sx,sy坐標滑動至ex,ey坐標
d.swipe(sx, sy, ex, ey)
# swipe from (sx, sy) to (ex, ey) with 10 steps
d.swipe(sx, sy, ex, ey, steps=10)
拖動
# drag from (sx, sy) to (ex, ey)
d.drag(sx, sy, ex, ey)
# drag from (sx, sy) to (ex, ey) with 10 steps
d.drag(sx, sy, ex, ey, steps=10)
設備屏幕的事件
有以下幾種屬性:
natural 或者 n 代替
left 或者 l 代替
right 或者 r 代替
upsidedown或 u(不能設定)
#獲取orientation(方向),可能是上述中的任意一種
orientation = d.orientation
#設置定向和凍結旋轉。
#說明:"upsidedown"不能用於Android 4.3 以前的版本
d.orientation="l"
d.orientation="r"
d.orientation="n"
鎖定/解除旋轉
#鎖定旋轉
d.freeze rotation()
#解除旋轉鎖定
d.freeze_rotation(False)
屏幕截圖
獲取屏幕截圖並且將其存本機地址中,命名為home.png,不能被用於Android 4.2以前的版本
d.screenshot("home.png")
將屏幕結構儲存(就是使用uiautomatorviewer看到的那個結構,以xml格式保存)
#將當前屏幕結構保存在本機並命名為"heierarchy.xml"
d.dump("hierarchy.xml")
#或者將存儲值作為結果返回
xml=d.dump()
打開通知消息欄或快速設置
#打開通知消息欄,不能用於Android 4.3以前的版本
d.notification()
#打開快速設置欄,不能用於Android 4.3以前的版本
d.open.quick_settings()
等待空閑或者窗口刷新
# 等待當前窗口空閑
d.wait.idle()
#等待直到窗口發生刷新事件
d.wait.update()
監視器
You can register watcher to perform some actions when a selector can not find a match.
當選擇器無法找到匹配時,您可以注冊觀察器來執行一些操作。
注冊監視器
當一個選擇器找不到匹配時,uiautomator 會運行全部已經注冊的觀察者
條件匹配時點擊目標
d.watcher("AUTO_FC_WHEN_ANR").when(text="ANR").when(text="Wait") \
.click(text="Force Close")
# d.watcher(name) ## creates a new named watcher.
(創建一個新的監視器,並將其命名為“name”)
# .when(condition) ## the UiSelector condition of the watcher.
(為監視器添加添加一個Uiselector條件)
# .click(target) ## perform click action on the target UiSelector.
(執行對目標Uiselector點擊動作)
條件匹配時按下按鍵
d.watcher("AUTO_FC_WHEN_ANR").when(text="ANR").when(text="Wait") \
.press.back.home()
# Alternative way to define it as below
(可以將其定義為以下方法)
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.
(為監視器添加一個Uiselector條件)
# .press.<keyname>.....<keyname>.() ## press keys one by one in sequence.
(按順序依次按下key)
# Alternative way defining key sequence is press(<keybname>, ..., <keyname>)
(定義另一種鍵值序列的方法是‘press(<keybname>, ..., <keyname>)’)
檢查監視器是否觸發
一個監視器觸發意味着這個監視器所有條件都匹配並且監視器運行。
d.watcher("watcher_name").triggered
# true in case of the specified watcher triggered, else false
(監視器被觸發為‘真’,反之則為‘假’)
刪除監視器命名
# remove the watcher
(刪除監視器)
d.watcher("watcher_name").remove()
列出所有的監視器
d.watchers
# a list of all registered wachers' names
(一個已注冊的監視器名稱列表)
檢查是否有監視器被觸發
d.watchers.triggered
# true in case of any watcher triggered
(在任何監視器被觸發的情況下)
重置所有已觸發的監視器
# reset all triggered watchers, after that, d.watchers.triggered will be false.
(重置所有已觸發的監視器,並且將d.watchers.triggered 參數變為false)
d.watchers.reset()
刪除監視器
# remove all registered watchers
(刪除所有已觸發的監視器)
d.watchers.remove()
# remove the named watcher, same as d.watcher("watcher_name").remove()
(刪除特定名稱的監視器,與d.watcher("watcher_name").remove() 相同)
d.watchers.remove("watcher_name")
強制運行所有的監視器
# force to run all registered watchers
(強制運行所有已注冊的監視器)
d.watchers.run()
處理程序
處理程序的功能與Watcher相同,只是它實現了我們的Android uiautomator。
處理程序和觀察程序之間最不同的用法是,處理程序可以使用自定義的回調函數
def fc_close(device):
if device(text='Force Close').exists:
device(text='Force Close').click()
return True
# return True means to break the loop of handler callback functions.
(返回True來中斷處理程序回調函數的循環)
# turn on the handler callback function(打開處理程序返回函數)
d.handlers.on(fc_close)
# turn off the handler callback function(關閉處理程序返回函數)
d.handlers.off(fc_close)
選擇器
選擇器是標識當前窗口中的特定UI對象
# To seleted the object ,text is 'Clock' and its className is 'android.widget.TextView'
(對於選擇器對象,文字是“Clock”和它的類名是“Android.widget.TextView”)
d(text='Clock', className='android.widget.TextView')
選擇器支持以下參數。請參閱UiSelector DOC 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
子對象和同級UI對象
子對象
# get the child or grandchild(得到子類或復子類)
d(className="android.widget.ListView").child(text="Bluetooth")
同級對象
# get sibling or child of sibling(得到同級對象或子類)
d(text="Google").sibling(className="android.widget.ImageView")
子類文本或描述或實例
# get the child match className="android.widget.LinearLayout"
(獲取子類匹配的 className="android.widget.LinerarLayout")
# and also it or its child or grandchild contains text "Bluetooth"
(而且它或它的子類和復子類包含文本“Bluetooth”)
d(className="android.widget.ListView", resourceId="android:id/list") \
.child_by_text("Bluetooth", className="android.widget.LinearLayout")
# allow scroll search to get the child
(允許滾動搜索獲得子類)
d(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_text相同的描述說明。
child_by_instance查找一個目標類中子類中特定的UI元素,用於沒有滾動可見視圖上執行。
詳情請參閱以下鏈接:
UiScrollable, getChildByDescription, getChildByText, getChildByInstance
UiCollection, getChildByDescription, getChildByText, getChildByInstance
上面的方法支持鏈接調用,例如對於下面的層次結構
<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>
我們要點擊文本“Wi-Fi”右側的開關打開/打開Wi-Fi。因為有幾個開關,幾乎相同的屬性,所以我們不能使用類似d(className=”android.widget.Switch”)選擇的UI對象。相反,我們可以使用下面的代碼來選擇它。
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。
d(A).left(B),意味着在左側選擇B。
d(A).right(B),表示選擇A右側的B.
d(A).up(B),表示選擇B以上的A.
d(A).down(B),表示在A下選擇B.
所以對於上面的情況,我們可以寫代碼:
## select "switch" on the right side of "Wi-Fi" (選擇無線網絡連接右側的開關) d(text="Wi‑Fi").right(className="android.widget.Switch").click()
多個實例
有時,屏幕可能包含多個視圖與相同的例如文本,那么你將不得不使用選擇器中的“實例”屬性,
如下所示:
d(text="Add new", instance=0)
# which means the first instance with text "Add new"
(這意味着第一個實例是文字“Add new”)
但是,uiautomator提供了類似的方法來使用它。
# get the count of views with text "Add new" on current screen
(統計當前界面中使用文本“Add new”)
d(text="Add new").count
# same as count property
(統計相同的屬性)
len(d(text="Add new"))
# get the instance via index
(通過索引獲取實例)
d(text="Add new")[0]
d(text="Add new")[1]
...
# iterator
(迭代器)
for view in d(text="Add new"):
view.info # ...
注意:當您使用選擇器像一個列表時,你必須確保屏幕保持不變,否則你可能會得到UI未找到錯誤。
獲取所選的ui對象狀態及其信息
檢查特定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").clear_text() # clear the text(清除文本信息)
d(text="Settings").set_text("My text...") # set the text(設置文本信息)
對選中的ui對象執行單擊操作
點擊特定的ui對象
# click on the center of the specific ui object
(點擊特定的UI對象的中心)
d(text="Settings").click0()
# click on the bottomright corner of the specific ui object
(點擊具體的的UI對象的右下角)
d(text="Settings").click.bottomright()
# click on the topleft corner of the specific ui object
(點擊具體的UI對象的左上角)
d(text="Settings").click.topleft()
# click and wait until the new window update
(等待更新后點擊)
d(text="Settings").click.wait()
長時間點擊特定的ui對象
# long click on the center of the specific ui object
(長按特定的UI對象的中心)
d(text="Settings").long_click()
# long click on the bottomright corner of the specific ui object
(長按特定的UI對象的右下角)
d(text="Settings").long_click.bottomright()
# long click on the topleft corner of the specific ui object
(長按特定UI對象的左上角)
d(text="Settings").long_click.topleft()
針對特定UI對象
# notes : drag can not be set until Android 4.3.
(注:不能用於Android4.3以下的版本)
# drag the ui object to point (x, y)
(拖拽UI對象至點X,Y)
d(text="Settings").drag.to(x, y, steps=100)
# drag the ui object to another ui object(center)
(拖拽某一UI對象到另外一個UI對象(中心))
d(text="Settings").drag.to(text="Clock", steps=50)
從UI對象的中心滑動到UI對象的邊緣
滑動支持4個方向:
left
right
top
bottom
d(text="Settings").swipe.right() d(text="Settings").swipe.left(steps=10) d(text="Settings").swipe.up(steps=10) d(text="Settings").swipe.down()
模擬兩點同時移動
d(text="Settings").gesture((sx1, sy1), (sx2, sy2)) \
.to((ex1, ey1), (ex2, ey2))
模擬兩點在特定的UI對象的上的操作
In,從邊到中心
Out,從中心到邊緣
# notes : pinch can not be set until Android 4.3. (注:不能用於Android4.3以下的版本) # from edge to center. here is "In" not "in" (從邊緣到中心這里使用的是“In”不是“in”) d(text="Settings").pinch.In(percent=100, steps=10) # from center to edge (從中心到邊緣) d(text="Settings").pinch.Out()
同時三點手勢模擬
d().gestureM((sx1, sy1), (sx2, sy2),(sx3, sy3)) \
.to((ex1, ey1), (ex2, ey2),(ex3,ey3))
d().gestureM((100,200),(300,200),(600,200),(100,600),(300,600),(600,900))
等待特定ui對象出現或消失
# wait until the ui object appears
(等待UI對象出現)
d(text="Settings").wait.exists(timeout=3000)
# wait until the ui object gone
(等待UI對象消失)
d(text="Settings").wait.gone(timeout=1000)
對特定的UI對象執行拋出操作(可滾動)
可能的屬性
horiz or vert
(水平或者是垂直的)
forward or backward or toBeginning or toEnd
(向前或向后,去開始位置或者去結束位置)
# fling forward(default) vertically(default)
(向前拋出(默認),在垂直方向(默認))
d(scrollable=True).fling()
# fling forward horizentally
(向前拋出,在水平方向)
d(scrollable=True).fling.horiz.forward()
# fling backward vertically
(向后拋出,在垂直方向)
d(scrollable=True).fling.vert.backward()
# fling to beginning horizentally
(向開始位置拋出,在水平方向)
d(scrollable=True).fling.horiz.toBeginning(max_swipes=1000)
# fling to end vertically
(向結束方向拋出,在垂直方向)
d(scrollable=True).fling.toEnd()
在特定的UI對象上滾動(可滾動)
horiz or vert
(水平或者是垂直的)
forward or backward or toBeginning or toEnd,or to
(向前或向后,去開始位、結束位和特定位置)
# scroll forward(default) vertically(default)
(向前滾動(默認),在垂直方向(默認))
d(scrollable=True).scroll(steps=10)
# scroll forward horizentally
(向前滾動,在水平方向)
d(scrollable=True).scroll.horiz.forward(steps=100)
# scroll backward vertically
(向后滾動,在垂直方向)
d(scrollable=True).scroll.vert.backward()
# scroll to beginning horizentally
(向開始位置滾動,在垂直方向)
d(scrollable=True).scroll.horiz.toBeginning(steps=100, max_swipes=1000)
# scroll to end vertically
(向結束方向滾動,在垂直方向)
d(scrollable=True).scroll.toEnd()
# scroll forward vertically until specific ui object appears
(向前滾動,直到特定的UI對象出現)
d(scrollable=True).scroll.to(text="Security")
Contribution
Fork the repo, and clone to your computer.
Checkout a new branch from develop branch
Install requirements: pip install -r requirements.txt
Make your changes, and update tests. Don't forget adding your name at the end of 'Contributors' section
Pass all tests and your code must be covered: tox.
Commit your changes and submit pull request to develop branch.
貢獻者
Xiaocong He (@xiaocong)
Yuanyuan Zou (@yuanyuan)
Qian Jin (@QianJin2013)
Xu Jingjie (@xiscoxu)
Xia Mingyuan (@mingyuan-xia)
問題和討論
如果您有任何錯誤報告或問題,煩請提交到 github issues。
備注:
Android的uiautomator適用於Android 4.1及以上版本,所以在使用它之前,請確保你的設備是Android4.1 +。
有些方法僅工作在Android 4.2 / 4.3,所以你最好在使用之前詳細閱讀uiautomator的Java文檔。
該模塊采用uiautomator-jsonrpc服務器作為后台程序與設備進行通信。
該模塊僅在python2.7 / 3.2 / 3.3 / pypy上測試。
FAQ
1.無法啟動JSONRPC服務器: raise IOError(“RPC server not started!”)
它可能是由網絡,設備或環境引起的。因此,當您遇到此問題,請按照以下步驟,嘗試手動啟動JSONRPC服務器。
1)從下載jar文件uiautomator jsonrpc服務器。
2)Adb將下載的jar文件推送到 /data/local/tmp/
3)通過命令啟動jsonrpc服務器:
adb shell uiautomator runtest bundle.jar uiautomator-stub.jar -c com.github.uiautomatorstub.Stub
4)adb將本地端口轉發到設備端口:
adb forward tcp:9008 tcp:9008
5)檢查jsonrpc服務器是否正常
如果你看到類似以下這樣的信息:
{“jsonrpc”:”2.0”,”id”:1,”result”:
{“currentPackageName”:”android”,”displayHeight”:1280,”displayRotation”:0,”displaySizeDpX”:0,”displaySizeDpY”:0,
“displayWidth”:720,”productName”:”falcon”,”sdkInt”:17,”naturalOrientation”:true}}
則表示服務器已啟動。
如果你可以手動啟動jsonrpc服務器,但你的腳本總是提示IOError(“RPC server not started!”),
請提交問題 github issues。
錯誤 httplib.BadStatusLine: ”
JsonRPC服務器需要訪問設備上的臨時目錄,但在一些底層設備上,它有可能拋出一些錯誤,在訪問臨時文件沒有連接SD卡。因此,如果您遇到錯誤,請插入SD卡,然后重試。