霍格沃茲測試學院是 python-uiautomator2 金牌贊助商,跟著開源項目作者學測試開發實戰,文末加群。
一、背景簡介Google 官方提供了一個 Android 自動化測試工具(Java 庫),基於 Accessibility 服務,功能很強,可以對第三方 App 進行測試,獲取屏幕上任意一個 App 的任意一個控制項屬性,並對其進行任意操作,但有兩個缺點:
測試腳本只能使用 Java 語言;
測試腳本要打包成 jar 或者 apk 包上傳到設備上才能運行;
實際工作中,我們希望測試邏輯能夠用 Python 編寫,能夠在電腦上運行的時候就控制手機。所以基於這個目的開發了 python-uiautomator2 自動化測試開源工具,其封裝了谷歌自帶的 uiautomator2 測試框架,可以運行在支持 Python 的任一系統上,目前版本為 V2.10.2。
GitHub 開源地址:
https://github.com/openatx/uiautomator2
如圖所示,python-uiautomator2 主要分為兩個部分,python 客戶端,行動裝置
整個過程:
在行動裝置上安裝 atx-agent(守護進程),隨後 atx-agent 啟動 uiautomator2 服務(默認 7912 埠)進行監聽;
在 PC 上編寫測試腳本並執行(相當於發送 HTTP 請求到行動裝置的 server 端);
行動裝置通過 WIFI 或 USB 接收到 PC 上發來的 HTTP 請求,執行制定的操作;
三、安裝與啟動3.1 安裝 uiautomator2使用 pip 安裝
pip install -U uiautomator2
安裝完成後,使用如下 python 代碼查看環境是事配置成功
說明:後文中所有代碼都需要導入 uiautomator2 庫,為了簡化我使用 u2 代替,d 代表 driver
import uiautomator2 as u2
# 連接並啟動
d = u2.connect()
print(d.info)
能正確列印出設備的信息則表示安裝成功
注意:需要安裝 adb 工具,並配置到系統環境變量,才能操作手機。
安裝有問題可以到 issue 列表查詢:
https://github.com/openatx/uiautomator2/wiki/Common-issues
weditor 是一款基於瀏覽器的 UI 查看器,用來幫助我們查看 UI 元素定位。
因為 uiautomator 是獨佔資源,所以當 atx 運行的時候 uiautomatorviewer 是不能用的,為了減少 atx 頻繁的啟停,就需要用到此工具
使用 pip 安裝
pip install -U weditor
查看安裝是否成功
weditor --help
出現如下信息表示安裝成功
運行 weditor
python -m weditor
#或者直接在命令行運行
weditor
d(定位方式 = 定位值)
#例:
element = d(text='Phone')
#這裡返回的是一個列表,當沒找到元素時,不會報錯,只會返回一個長度為 0 的列表
#當找到多個元素時,會返回多個元素的列表,需要加下標再定位
element[0].click()
#獲取元素個數
print(element.count)
ui2 支持 android 中 UiSelector 類中的所有定位方式,詳細可以在這個網址查看 https://developer.android.com/reference/android/support/test/uiautomator/UiSelector
整體內容如下 , 所有的屬性可以通過 weditor 查看到
名稱描述texttext 是指定文本的元素textContainstext 中包含有指定文本的元素textMatchestext 符合指定正則的元素textStartsWithtext 以指定文本開頭的元素classNameclassName 是指定類名的元素classNameMatchesclassName 類名符合指定正則的元素descriptiondescription 是指定文本的元素descriptionContainsdescription 中包含有指定文本的元素descriptionMatchesdescription 符合指定正則的元素descriptionStartsWithdescription 以指定文本開頭的元素checkable可檢查的元素,參數為 True,Falsechecked已選中的元素,通常用於複選框,參數為 True,Falseclickable可點擊的元素,參數為 True,FalselongClickable可長按的元素,參數為 True,Falsescrollable可滾動的元素,參數為 True,Falseenabled已激活的元素,參數為 True,Falsefocusable可聚焦的元素,參數為 True,Falsefocused獲得了焦點的元素,參數為 True,Falseselected當前選中的元素,參數為 True,FalsepackageNamepackageName 為指定包名的元素packageNameMatchespackageName 為符合正則的元素resourceIdresourceId 為指定內容的元素resourceIdMatchesresourceId 為符合指定正則的元素4.3 子元素和兄弟定位子元素定位
child()
#查找類名為 android.widget.ListView 下的 Bluetooth 元素
d(className="android.widget.ListView").child(text="Bluetooth")
# 下面這兩種方式定位有點不準確,不建議使用
d(className="android.widget.ListView")\
.child_by_text("Bluetooth",allow_scroll_search=True)
d(className="android.widget.ListView").child_by_description("Bluetooth")
兄弟元素定位
sibling()
#查找與 google 同一級別,類名為 android.widget.ImageView 的元素
d(text="Google").sibling(className="android.widget.ImageView")
鏈式調用
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),# 選擇 A 左邊的 B
d(A).right(B),# 選擇 A 右邊的 B
d(A).up(B), #選擇 A 上邊的 B
d(A).down(B),# 選擇 A 下邊的 B
#選擇 WIFI 右邊的開關按鈕
d(text='Wi‑Fi').right(resourceId='android:id/widget_frame')
表格標註有 @property 裝飾的類屬性方法,均為下方示例方式
d(test="Settings").exists
方法
描述返回值備註exists()判斷元素是否存在True,Flase@propertyinfo()返回元素的所有信息字典@propertyget_text()返回元素文本字符串exists 其它使用方法:
d.exists(text='Wi‑Fi',timeout=5)
info() 輸出信息:
{
"bounds": {
"bottom": 407,
"left": 216,
"right": 323,
"top": 342
},
"childCount": 0,
"className": "android.widget.TextView",
"contentDescription": null,
"packageName": "com.android.settings",
"resourceName": "android:id/title",
"text": "Wi‑Fi",
"visibleBounds": {
"bottom": 407,
"left": 216,
"right": 323,
"top": 342
},
"checkable": false,
"checked": false,
"clickable": false,
"enabled": true,
"focusable": false,
"focused": false,
"longClickable": false,
"scrollable": false,
"selected": false
}
可以通過上方信息分別獲取元素的所有屬性
4.6 XPATH 定位因為 Java uiautoamtor 中默認是不支持 xpath,這是屬於 ui2 的擴展功能,速度會相比其它定位方式慢一些
在 xpath 定位中,ui2 中的 description 定位需要替換為 content-desc,resourceId 需要替換為 resource-id
使用方法
# 只會返回一個元素,如果找不到元素,則會報 XPathElementNotFoundError 錯誤
# 如果找到多個元素,默認會返回第 0 個
d.xpath('//*[@resource-id="com.android.launcher3:id/icon"]')
# 如果返回的元素有多個,需要使用 all() 方法返回列表
# 使用 all 方法,當未找到元素時,不會報錯,會返回一個空列表
d.xpath('//*[@resource-id="com.android.launcher3:id/icon"]').all()
d(text='Settings').click()
#單擊直到元素消失 , 超時時間 10,點擊間隔 1
d(text='Settings').click_gone(maxretry=10, interval=1.0)
d(text='Settings').long_click()
Android<4.3 時不能使用拖動
# 在 0.25S 內將 Setting 拖動至 Clock 上,拖動元素的中心位置
# duration 默認為 0.5, 實際拖動的時間會比設置的要高
d(text="Settings").drag_to(text="Clock", duration=0.25)
# 拖動 settings 到屏幕的某個點上
d(text="Settings").drag_to(877,733, duration=0.25)
#兩個點之間的拖動 , 從點 1 拖動至點 2
d.drag(x1,y1,x2,y2)
滑動有兩個,一個是在 driver 上操作,一個是在元素上操作
元素上操作
從元素的中心向元素邊緣滑動
# 在 Setings 上向上滑動。steps 默認為 10
# 1 步約為 5 毫秒,因此 20 步約為 0.1 s
d(text="Settings").swipe("up", steps=20)
driver 上操作
即對整個屏幕操作
# 實現下滑操作
x,y = d.window_size()
x1 = x / 2
y1 = y * 0.1
y2 = y * 0.9
d.swipe(x1,y1,x1,y2)
driver 滑動的擴展方法,可以直接實現滑動,不需要再自己封裝定位點
# 支持前後左右的滑動
# "left", "right", "up", "down"
# 下滑操作
d.swipe_ext("down")
android>4.3
對元素操作
d(text='Settings').gesture(start1,start2,end1,end2,)
# 放大操作
d(text='Settings').gesture((525,960),(613,1121),(135,622),(882,1540))
封裝好的放大縮小操作
# 縮小
d(text="Settings").pinch_in()
# 放大
d(text="Settings").pinch_out()
# 等待元素出現
d(text="Settings").wait(timeout=3.0)
# 等待元素消失,返回 True False,timout 默認為全局設置的等待時間
d(text='Settings').wait_gone(timeout=20)
設置 scrollable 屬性為 True;
滾動類型:horiz 為水平,vert 為垂直;
滾動方向:
forward 向前
backward 向後
toBeginning 滾動至開始
toEnd 滾動至最後
to 滾動直接某個元素出現
所有方法均返回 Bool 值;
# 垂直滾動到頁面頂部 / 橫向滾動到最左側
d(scrollable=True).scroll.toBeginning()
d(scrollable=True).scroll.horiz.toBeginning()
# 垂直滾動到頁面最底部 / 橫向滾動到最右側
d(scrollable=True).scroll.toEnd()
d(scrollable=True).scroll.horiz.toEnd()
# 垂直向後滾動到指定位置 / 橫向向右滾動到指定位置
d(scrollable=True).scroll.to(description=" 指定位置 ")
d(scrollable=True).scroll.horiz.to(description=" 指定位置 ")
# 垂直向前滾動(橫向同理)
d(scrollable=True).scroll.forward()
# 垂直向前滾動到指定位置(橫向同理)
d(scrollable=True).scroll.forward.to(description=" 指定位置 ")
# 滾動直到 System 元素出現
d(scrollable=True).scroll.to(text="System")
Take screenshot of widget
im = d(text="Settings").screenshot()
im.save("settings.jpg")
5.8.1 輸入自定義文本
# 使用 adb 廣播的方式輸入
d.send_keys('hello')
# 清空輸入框
d.clear_text()
5.8.2 輸入按鍵
兩種方法
# 發送回車
d.press('enter')
# 第二種
d.keyevent('enter')
目前 press 支持的按鍵如下
"""
press key via name or key code. Supported key name includes:
home, back, left, right, up, down, center, menu, search, enter,
delete(or del), recent(recent apps), volume_up, volume_down,
volume_mute, camera, power.
"""
keyevent 是通過 「adb shell input keyevent」 方式輸入,支持按鍵更加豐富
更多詳細的按鍵信息 https://developer.android.com/reference/android/view/KeyEvent.html
5.8.3 輸入法切換
# 切換成 ui2 的輸入法,這裡會隱藏掉系統原本的輸入法 , 默認是使用系統輸入法
# 當傳入 False 時會使用系統默認輸入法,默認為 Fasle
d.set_fastinput_ime(True)
# 查看當前輸入法
d.current_ime()
#返回值
('com.github.uiautomator/.FastInputIME', True)
5.8.4 模擬輸入法功能
可以模擬的功能有 go ,search ,send ,next, done ,previous。
如果使用 press 輸入按鍵無效,可以嘗試使用此方法輸入
# 搜索功能
d.send_action("search")
# 獲取 toast, 當沒有找到 toast 消息時,返回 default 內容
d.toast.get_message(timout=5,default='no toast')
# 清空 toast 緩存
d.toast.reset()
使用 wather 進行界面的監控,可以用來實現跳過測試過程中的彈框
當啟動 wather 時,會新建一個線程進行監控
可以添加多個 watcher
用法
# 註冊監控 , 當界面內出現有 allow 字樣時,點擊 allow
d.watcher.when('allow').click()
# 移除 allow 的監控
d.watcher.remove("allow")
# 移除所有的監控
d.watcher.remove()
# 開始後臺監控
d.watcher.start()
d.watcher.start(2.0) # 默認監控間隔 2.0s
# 強制運行所有監控
d.watcher.run()
# 停止監控
d.watcher.stop()
# 停止並移除所有的監控,常用於初始化
d.watcher.reset()
2.11.0 版本 新增了一個 watch_context 方法 , 寫法相比 watcher 更簡潔,官方推薦使用此方法來實現監控,目前只支持 click() 這一種方法。
wct = d.watch_context()
# 監控 ALLOW
wct.when("ALLOW").click()
# 監控 OK
wct.when('OK').click()
# 開啟彈窗監控,並等待界面穩定(兩個彈窗檢查周期內沒有彈窗代表穩定)
wct.wait_stable()
#其它實現代碼
# 停止監控
wct.stop()
這裡可以用來實現圖案解鎖
使用 touch 類
# 模擬按下不放手
touch.down(x,y)
# 停住 3S
touch.sleep(x,y)
# 模擬移動
touch.move(x,y)
# 模擬放開
touch.up(x,y)
#實現長按 , 同一個點按下休眠 5S 後抬起
d.touch.down(252,1151).sleep(5).up(252,1151)
# 實現四點的圖案解鎖,目前只支持坐標點
d.touch.down(252,1151).move(559,1431).move(804,1674).move(558,1666).up(558,1666)
d.screenshot('test.png')
這個感覺是比較有用的一個功能,可以在測試用例開始時錄製,結束時停止錄製,然後如果測試 fail。則上傳到測試報告,完美復原操作現場,具體原理後面再去研究。
首先需要下載依賴,官方推薦使用鏡像下載:
pip3 install -U "uiautomator2[image]" -i https://pypi.doubanio.com/simple
執行錄製:
# 啟動錄製,默認幀率為 20
d.screenrecord('test.mp4')
# 其它操作
time.sleep(10)
#停止錄製,只有停止錄製了才能看到視頻
d.screenrecord.stop()
下載與錄製視頻同一套依賴。
這個功能是首先手動截取需要點擊目標的圖片,然後 ui2 在界面中去匹配這個圖片,目前我嘗試了精確試不是很高,誤點率非常高,不建議使用。
# 點擊
d.image.click('test.png')
# 匹配圖片,返回相似度和坐標
# {'similarity': 0.9314796328544617, 'point': [99, 630]}
d.image.match('test.png')
d.app_current()
#返回當前界面的包名,activity 及 pid
{
"package": "com.xueqiu.android",
"activity": ".common.MainActivity",
"pid": 23007
}
可以從本地路徑及 url 下載安裝 APP,此方法無返回值,當安裝失敗時,會拋出 RuntimeError 異常
# 本地路徑安裝
d.app_install('test.apk')
# url 安裝
d.app_install('http://s.toutiao.com/UsMYE/')
默認當應用在運行狀態執行 start 時不會關閉應用,而是繼續保持當前界面。
如果需要消除前面的啟動狀態,則需要加 stop=True 參數。
# 通過包名啟動
d.app_start("com.xueqiu.android",stop=True)
#源碼說明
def app_start(self, package_name: str,
activity: Optional[str]=None,
wait: bool = False,
stop: bool=False,
use_monkey: bool=False):
""" Launch application
Args:
package_name (str): package name
activity (str): app activity
stop (bool): Stop app before starting the activity. (require activity)
use_monkey (bool): use monkey command to start app when activity is not given
wait (bool): wait until app started. default False
"""
stop 和 clear 的區別是結束應用使用的命令不同
stop 使用的是 「am force-stop」
clear 使用的是 「pm clear」
# 通過包名結束單個應用
d.app_stop("com.xueqiu.android")
d.app_clear('com.xueqiu.android')
# 結束所有應用 , 除了 excludes 參數列表中的應用包名
# 如果不傳參,則會只保留兩個依賴服務應用
# 會返回一個結束應用的包名列表
d.app_stop_all(excludes=['com.xueqiu.android'])
d.app_info('com.xueqiu.android')
#輸出
{
"packageName": "com.xueqiu.android",
"mainActivity": "com.xueqiu.android.common.splash.SplashActivity",
"label": " 雪球 ",
"versionName": "12.6.1",
"versionCode": 257,
"size": 72597243
}
img = d.app_icon('com.xueqiu.android')
img.save('icon.png')
# 等待此應用變為當前應用,返回 pid,超時未啟動成功則返回 0
# front 為 true 表示等待 app 成為當前 app,
# 默認為 false,表示只要後臺有這個應用的進程就會返回 PID
d.app_wait('com.xueqiu.android',60,front=True)
# 卸載成功返回 true, 沒有此包或者卸載失敗返回 False
d.app_uninstall('com.xueqiu.android')
# 卸載所有自己安裝的第三方應用 , 返回卸載 app 的包名列表
# excludes 表示不卸載的列表
# verbose 為 true 則會列印卸載信息
d.app_uninstall_all(excludes=[],verbose=True)
卸載全部應用返回的包名列表並一定是卸載成功了,最好使用 verbose=true 列印一下信息,這樣可以查看到是否卸載成功
uninstalling com.xueqiu.android OK
uninstalling com.android.cts.verifier FAIL
或者可以修改一下源碼,使其只輸出成功的包名,注釋的為增加的代碼,未注釋的是源碼
def app_uninstall_all(self, excludes=[], verbose=False):
""" Uninstall all apps """
our_apps = ['com.github.uiautomator', 'com.github.uiautomator.test']
output, _ = self.shell(['pm', 'list', 'packages', '-3'])
pkgs = re.findall(r'package:([^\s]+)', output)
pkgs = set(pkgs).difference(our_apps + excludes)
pkgs = list(pkgs)
# 增加一個卸載成功的列表
#sucess_list = []
for pkg_name in pkgs:
if verbose:
print("uninstalling", pkg_name, " ", end="", flush=True)
ok = self.app_uninstall(pkg_name)
if verbose:
print("OK" if ok else "FAIL")
# 增加如下語句,當成功則將包名加入 list
#if ok:
# sucess_list.append(pkg_name)
# 返回成功的列表
# return sucess_list
return pkgs
#當 PC 只連接了一個設備時,可以使用此種方式
d = u2.connect()
#返回的是 Device 類 , 此類繼承方式如下
class Device(_Device, _AppMixIn, _PluginMixIn, _InputMethodMixIn, _DeprecatedMixIn):
""" Device object """
# for compatible with old code
Session = Device
connect() 可以使用如下其它方式進行連接
#當 PC 與設備在同一網段時,可以使用 IP 地址和埠號通過 WIFI 連接,無需連接 USB 線
connect("10.0.0.1:7912")
connect("10.0.0.1") # use default 7912 port
connect("http://10.0.0.1")
connect("http://10.0.0.1:7912")
#多個設備時,使用設備號指定哪一個設備
connect("cff1123ea") # adb device serial number
8.2.1 獲取 driver 信息
d.info
#輸出
{
"currentPackageName": "com.android.systemui",
"displayHeight": 2097,
"displayRotation": 0,
"displaySizeDpX": 360,
"displaySizeDpY": 780,
"displayWidth": 1080,
"productName": "freedom_turbo_XL",
"screenOn": true,
"sdkInt": 29,
"naturalOrientation": true
}
8.2.2 獲取設備信息
會輸出測試設備的所有信息,包括電池,CPU,內存等
d.device_info
#輸出
{
"udid": "61c90e6a-ba:1b:ba:46:91:0e-freedom_turbo_XL",
"version": "10",
"serial": "61c90e6a",
"brand": "Schok",
"model": "freedom turbo XL",
"hwaddr": "ba:1b:ba:46:91:0e",
"port": 7912,
"sdk": 29,
"agentVersion": "0.9.4",
"display": {
"width": 1080,
"height": 2340
},
"battery": {
"acPowered": false,
"usbPowered": true,
"wirelessPowered": false,
"status": 2,
"health": 2,
"present": true,
"level": 98,
"scale": 100,
"voltage": 4400,
"temperature": 292,
"technology": "Li-ion"
},
"memory": {
"total": 5795832,
"around": "6 GB"
},
"cpu": {
"cores": 8,
"hardware": "Qualcomm Technologies, Inc SDM665"
},
"arch": "",
"owner": null,
"presenceChangedAt": "0001-01-01T00:00:00Z",
"usingBeganAt": "0001-01-01T00:00:00Z",
"product": null,
"provider": null
}
8.2.3 獲取屏幕解析度
# 返回(寬,高)元組
d.window_size()
# 例 解析度為 1080*1920
# 手機豎屏狀態返回 (1080,1920)
# 橫屏狀態返回 (1920,1080)
8.2.4 獲取 IP 地址
# 返回 ip 地址字符串,如果沒有則返回 None
d.wlan_ip
8.3.1 使用 settings 設置
查看 settings 默認設置
d.settings
#輸出
{
#點擊後的延遲,(0,3)表示元素點擊前等待 0 秒,點擊後等待 3S 再執行後續操作
'operation_delay': (0, 3),
# opretion_delay 生效的方法,默認為 click 和 swipe
# 可以增加 press,send_keys,long_click 等方式
'operation_delay_methods': ['click', 'swipe'],
# 默認等待時間,相當於 appium 的隱式等待
'wait_timeout': 20.0,
# xpath 日誌
'xpath_debug': False
}
修改默認設置,只需要修改 settings 字典即可
#修改延遲為操作前延遲 2S 操作後延遲 4.5S
d.settings['operation_delay'] = (2,4.5)
#修改延遲生效方法
d.settings['operation_delay_methods'] = {'click','press','send_keys'}
# 修改默認等待
d.settings['wait_timeout'] = 10
8.3.2 使用方法或者屬性設置
# 默認值 60s,
d.HTTP_TIMEOUT = 60
# 僅當 TMQ=true 時有效,支持通過環境變量 WAIT_FOR_DEVICE_TIMEOUT 設置
d.WAIT_FOR_DEVICE_TIMEOUT = 70
# 打不到元素時,等待 10 後再報異常
d.implicitly_wait(10.0)
d.debug = True
d.info
#輸出
15:52:04.736 $ curl -X POST -d '{"jsonrpc": "2.0", "id": "0eed6e063989e5844feba578399e6ff8", "method": "deviceInfo", "params": {}}' 'http://localhost:51046/jsonrpc/0'
15:52:04.816 Response (79 ms) >>>
{"jsonrpc":"2.0","id":"0eed6e063989e5844feba578399e6ff8","result":{"currentPackageName":"com.android.systemui","displayHeight":2097,"displayRotation":0,"displaySizeDpX":360,"displaySizeDpY":780,"displayWidth":1080,"productName":"freedom_turbo_XL","screenOn":true,"sdkInt":29,"naturalOrientation":true}}
<<< END
# 相當於 time.sleep(10)
d.sleep(10)
# 亮屏
d.screen_on()
# 滅屏
d.screen_off()
# 設置屏幕方向
d.set_orientation(value)
# 獲取當前屏幕方向
d.orientation
value 值參考,只要是元組中的任一一個值就可以。
# 正常豎屏
(0, "natural", "n", 0),
# 往左橫屏,相當於手機屏幕順時針旋轉 90 度
# 現實中如果要達到此效果,需要將手機逆時針旋轉 90 度
(1, "left", "l", 90),
# 倒置,這個需要看手機系統是否支持 , 倒過來顯示
(2, "upsidedown", "u", 180),
# 往右橫屏,調整與往左相反,屏幕順時針旋轉 270 度
(3, "right", "r", 270))
打開通知欄
d.open_notification()
打開快速設置
d.open_quick_settings()
8.7.1 導入文件
# 如果是目錄,這裡 "/sdcrad/" 最後一個斜槓一定要加,否則會報錯
d.push("test.txt","/sdcrad/")
d.push("test.txt","/sdcrad/test.txt")
8.7.2 導出文件
d.pull('/sdcard/test.txt','text.txt')
使用 shell 方法執行
8.8.1 執行非阻塞命令
output 返回的是一個整體的字符串,如果需要抽取值,需要對 output 進行解析提取處理
# 返回輸出和退出碼,正常為 0,異常為 1
output,exit_code = d.shell(["ls","-l"],timeout=60)
8.8.2 執行阻塞命令(持續執行的命令)
# 返回一個命令的數據流 output 為 requests.models.Response
output = d.shell('logcat',stream=True)
try:
# 按行讀取,iter_lines 為迭代響應數據,一次一行
for line in output.iter_lines():
print(line.decode('utf8'))
finally:
output.close()
源碼描述
def shell(self, cmdargs: Union[str, List[str]], stream=False, timeout=60):
"""
Run adb shell command with arguments and return its output. Require atx-agent >=0.3.3
Args:
cmdargs: str or list, example: "ls -l" or ["ls", "-l"]
timeout: seconds of command run, works on when stream is False
stream: bool used for long running process.
Returns:
(output, exit_code) when stream is False
requests.Response when stream is True, you have to close it after using
Raises:
RuntimeError
For atx-agent is not support return exit code now.
When command got something wrong, exit_code is always 1, otherwise exit_code is always 0
"""
因為有 atx-agent 的存在,Uiautomator 會被一直守護著,如果退出了就會被重新啟動起來。但是 Uiautomator 又是霸道的,一旦它在運行,手機上的輔助功能、電腦上的 uiautomatorviewer 就都不能用了,除非關掉該框架本身的 uiautomator
使用代碼停止
d.service("uiautomator").stop()
手動停止
直接打開 ATX APP(init 成功後,就會安裝上),點擊關閉 UIAutomator
以上,歡迎大家一起交流探討。
推薦學習
推薦霍格沃茲出品 《測試開發實戰進階》課程,資深測試架構師、開源項目作者親授 BAT 大廠前沿最佳實踐。4 個月 20+ 項目實戰強化訓練,帶你一站式掌握 BAT 測試開發工程師必備核心技能(對標阿里P6+,年薪50W+)!學員直推 BAT 名企測試經理,普遍漲薪 50%+!
🔥14 期開課中🔥