前言
我們在使用 uiautomator2 做UI自動化時,有時需要進(jìn)行多次滑動操作伍派,比如我進(jìn)入某個APP頁面定位某個元素诉植,如果該元素不存在晾腔,那么我可能會一直進(jìn)行上滑操作建车,如果定位到該元素就停止繼續(xù)滑動扩借。
一般的情況,如果頁面是固定不變的內(nèi)容缤至,那么我們可以嘗試并計算出大概需要滑動多少次才能到該元素的區(qū)域潮罪,這個時候我們直接把滑動次數(shù)寫死就好。
另一種情況领斥,如果頁面是經(jīng)常變化的內(nèi)容嫉到,這個時候我們就不知道到底需要滑動多少次,也許需要滑動到頁面底部月洛,才能判斷該元素是否存在何恶。
今天我們就學(xué)習(xí)下,如何在 uiautomator2 中判斷是否已滑動到頁面底部。
利用 dump_hierarchy() 獲取界面UI結(jié)構(gòu)
在 uiautomator2 中碎节,我們通過 dump_hierarchy() 方法獲取到當(dāng)前界面的UI結(jié)構(gòu)信息,同時該方法會把數(shù)據(jù)以 XML 形式的字符串進(jìn)行返回。
import uiautomator2 as u2
d = u2.connect()
res = d.dump_hierarchy()
print(res)
返回的形式大致如下纲堵,它反應(yīng)了當(dāng)前頁面的所有UI層次結(jié)構(gòu)關(guān)系茂附。
<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
<hierarchy rotation="0">
<node index="0" text="" resource-id="" class="android.widget.FrameLayout" package="com.sec.android.app.launcher" 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="[0,0][1080,1920]">
<node index="0" text="" resource-id="" class="android.widget.LinearLayout" package="com.sec.android.app.launcher" 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="[0,0][1080,1920]">
<node index="0" text="" resource-id="android:id/content" class="android.widget.FrameLayout" package="com.sec.android.app.launcher" 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="[0,0][1080,1920]">
<node index="0" text="" resource-id="com.sec.android.app.launcher:id/launcher" class="android.widget.FrameLayout" package="com.sec.android.app.launcher" 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="[0,0][1080,1920]">
<node index="0" text="" resource-id="com.sec.android.app.launcher:id/drag_layer" class="android.widget.FrameLayout" package="com.sec.android.app.launcher" 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="[0,0][1080,1920]">
<node index="0" text="" resource-id="com.sec.android.app.launcher:id/apps_view" class="android.widget.FrameLayout" package="com.sec.android.app.launcher" 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="[0,0][1080,1920]">
<node index="0" text="" resource-id="com.sec.android.app.launcher:id/apps_content" class="com.android.launcher3.allapps.view.AppsPagedView" package="com.sec.android.app.launcher" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="true" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[0,0][1080,1920]">
<node index="3" text="" resource-id="" class="android.view.ViewGroup" package="com.sec.android.app.launcher" 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="[23,225][1057,1833]">
<node index="0" text="" resource-id="" class="android.view.ViewGroup" package="com.sec.android.app.launcher" 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="[23,237][1057,1833]">
<node index="0" text="" resource-id="" class="android.widget.FrameLayout" package="com.sec.android.app.launcher" content-desc="ATX" checkable="false" checked="false" clickable="true" enabled="true" focusable="true" focused="false" scrollable="false" long-clickable="true" password="false" selected="false" visible-to-user="true" bounds="[23,237][274,549]">
<node index="0" text="" resource-id="com.sec.android.app.launcher:id/iconview_imageView" class="android.widget.ImageView" package="com.sec.android.app.launcher" 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="[67,256][229,418]" />
<node index="2" text="ATX" resource-id="com.sec.android.app.launcher:id/iconview_titleView" class="android.widget.TextView" package="com.sec.android.app.launcher" 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="[114,424][183,478]" />
</node>
</node>
</node>
</node>
<node index="1" text="" resource-id="com.sec.android.app.launcher:id/apps_searchbar_container" class="android.widget.LinearLayout" package="com.sec.android.app.launcher" 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="[0,99][1080,207]">
<node index="0" text="" resource-id="com.sec.android.app.launcher:id/app_search_wrapper" class="android.widget.RelativeLayout" package="com.sec.android.app.launcher" 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="[60,99][1020,207]">
<node index="0" text="" resource-id="" class="android.widget.LinearLayout" package="com.sec.android.app.launcher" 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="[60,99][1020,207]">
<node index="0" text="" resource-id="com.sec.android.app.launcher:id/app_search_edit_text_wrapper" class="android.widget.LinearLayout" package="com.sec.android.app.launcher" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="true" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[60,99][774,207]">
<node index="0" text=" 搜索手機(jī)" resource-id="com.sec.android.app.launcher:id/app_search_edit_text" class="android.widget.EditText" package="com.sec.android.app.launcher" content-desc="" checkable="false" checked="false" clickable="true" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="true" password="false" selected="false" visible-to-user="true" bounds="[72,99][774,207]" />
</node>
<node index="1" text="" resource-id="com.sec.android.app.launcher:id/voice_search_button" class="android.widget.FrameLayout" package="com.sec.android.app.launcher" 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="[774,99][894,207]">
<node index="0" text="" resource-id="com.sec.android.app.launcher:id/voice_search_icon_imageview" class="android.widget.ImageButton" package="com.sec.android.app.launcher" content-desc="語音搜索" checkable="false" checked="false" clickable="true" enabled="true" focusable="true" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[774,99][894,207]" />
</node>
<node index="2" text="" resource-id="com.sec.android.app.launcher:id/apps_search_divider" class="android.view.View" package="com.sec.android.app.launcher" 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="[915,129][918,177]" />
<node index="3" text="" resource-id="com.sec.android.app.launcher:id/more_button_search" class="android.widget.FrameLayout" package="com.sec.android.app.launcher" 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="[939,117][1011,189]">
<node index="0" text="" resource-id="com.sec.android.app.launcher:id/more_icon_imageview" class="android.widget.ImageButton" package="com.sec.android.app.launcher" content-desc="更多選項(xiàng)" checkable="false" checked="false" clickable="true" enabled="true" focusable="true" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[939,117][1011,189]" />
</node>
</node>
<node index="1" text="" resource-id="" class="android.widget.LinearLayout" package="com.sec.android.app.launcher" 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="[60,204][1020,207]" />
</node>
</node>
</node>
<node index="2" text="" resource-id="com.sec.android.app.launcher:id/apps_page_indicator" class="android.widget.LinearLayout" package="com.sec.android.app.launcher" content-desc="第 4 頁,共 4 頁。" 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="[424,1821][655,1872]">
<node NAF="true" index="0" text="" resource-id="" class="android.widget.FrameLayout" package="com.sec.android.app.launcher" content-desc="" checkable="false" checked="false" clickable="true" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[424,1821][475,1872]">
<node index="0" text="" resource-id="com.sec.android.app.launcher:id/inactive" class="android.widget.ImageView" package="com.sec.android.app.launcher" 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="[424,1821][475,1872]" />
</node>
<node NAF="true" index="1" text="" resource-id="" class="android.widget.FrameLayout" package="com.sec.android.app.launcher" content-desc="" checkable="false" checked="false" clickable="true" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[484,1821][535,1872]">
<node index="0" text="" resource-id="com.sec.android.app.launcher:id/inactive" class="android.widget.ImageView" package="com.sec.android.app.launcher" 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="[484,1821][535,1872]" />
</node>
<node NAF="true" index="2" text="" resource-id="" class="android.widget.FrameLayout" package="com.sec.android.app.launcher" content-desc="" checkable="false" checked="false" clickable="true" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[544,1821][595,1872]">
<node index="0" text="" resource-id="com.sec.android.app.launcher:id/inactive" class="android.widget.ImageView" package="com.sec.android.app.launcher" 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="[544,1821][595,1872]" />
</node>
<node NAF="true" index="3" text="" resource-id="" class="android.widget.FrameLayout" package="com.sec.android.app.launcher" content-desc="" checkable="false" checked="false" clickable="true" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[604,1821][655,1872]">
<node index="1" text="" resource-id="com.sec.android.app.launcher:id/active" class="android.widget.ImageView" package="com.sec.android.app.launcher" 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="[604,1821][655,1872]" />
</node>
</node>
</node>
</node>
</node>
</node>
</node>
</hierarchy>
判斷已經(jīng)滑動到頁面底部
我們可以利用 dump_hierarchy() 獲取界面UI結(jié)構(gòu)婿牍,那么需要怎么判斷當(dāng)前是否已滑動到底了呢慎菲?其實(shí)很簡單,我們只需要判斷每次滑動前的UI結(jié)構(gòu)信息,與滑動后的UI結(jié)構(gòu)信息一致,那么就可以看作是滑動到底部了稻轨。
import uiautomator2 as u2
import time
d = u2.connect()
while True:
# 獲取每次滑動前頁面下半部分的所有元素
page_content = d.dump_hierarchy()[(len(d.dump_hierarchy()) // 2):]
d.swipe_ext("up")
time.sleep(0.5)
# 獲取每次滑動后頁面下半部分的所有元素线欲,并與上一次滑動前的頁面元素對比,頁面元素沒有變化時跳出循環(huán)
new_page_content = d.dump_hierarchy()[(len(d.dump_hierarchy()) // 2):]
if new_page_content == page_content:
break
print("swipe end")