介面進階¶
本章節為您介紹更進階的自動化介面。您可以透過這些介面完成各種細緻的操作。本章內容較多,如果您是第一次接觸,我們建議您耐心地看完每個部分。
小撇步
編寫自動化程式碼時,您可以直接在遠端桌面的右側終端機輸入命令 lamda,並在裡面執行以下測試程式碼,或者自行進行元素選取、點擊測試等,這樣可以加快編寫及驗證的速度。
取得元素¶
您在基礎知識或前文中可能已經對它有一些了解。您需要透過選擇器查找到相關的元素才能進行操作。您應該也看到了在哪裡可以獲得選擇器參數。接下來的介紹將圍繞這個元素展開。您可以在這張圖片的右側看到「同意」這個元素的相關資訊。

注意
您在左側介面直接點擊的元素可能並不是實際元素,因為它可能與其他元素大小及位置重疊。通常情況下,多個位置或大小重疊的元素會顯示在右側資訊欄內,您可以上下捲動查看哪個才是真正需要的。您也可以透過在左側選擇介面按下 TAB 鍵的方式手動遍歷所有元素。
對於以上元素,我們取得它一般是透過 text。使用 text 的條件是當前介面沒有另一個元素的 text 也是「同意」,這是最簡便的方法。其次您也可以選用 resourceId,不過您需要注意這裡的 resourceId 代表的並不是唯一 ID,它代表的是資源 ID,而一個介面內可能包含很多個相同資源 ID 的元素。其他一些欄位如 packageName、checkable 等一般不常用,但如果 text、resourceId、description 等都沒有,可以嘗試使用這些欄位。我們可以透過以下幾種方式取得這個元素。
element = d(text="同意")
element = d(text="同意", resourceId="com.tencent.news:id/btm_first_agree")
element = d(resourceId="com.tencent.news:id/btm_first_agree")
元素點擊¶
呼叫如下介面進行一次普通的元素點擊操作,上下文中會實現手動點擊「同意」的效果。
element.click()
如果您需要指定點擊元素的位置,可以在呼叫點擊介面時指定 corner 參數。例如,Corner.COR_CENTER 代表點擊該元素中心點,您也可以點擊它的左上角或右下角(Corner.COR_BOTTOMRIGHT)。
element.click_exists(corner=Corner.COR_TOPLEFT)
在該元素上執行長按操作,不存在則拋出例外。該介面也支援 corner,但無法指定長按時間。
element.long_click()
元素存在則點擊,如果元素不存在,呼叫此介面不會引發例外。該介面同樣支援 corner。
element.click_exists()
>>> element.click_exists()
True
是否存在¶
很多情況下,在進行進一步操作之前,需要對元素的存在性進行檢查。否則後續流程可能會出現例外,甚至在錯誤的介面執行錯誤的操作。此時您可以使用如下介面進行存在性判斷。
element.exists()
元素資訊¶
某些情況下,您可能想要取得元素的部分資訊,例如元素座標、區域資訊,或元素上的文字、描述等字串資訊。您可以透過如下介面來讀取元素資訊。
element.info()
對於我們上述的測試元素,輸出的資訊如下。
>>> info = element.info()
>>> print (info)
bounds { ... }
className: "android.widget.TextView"
clickable: true
enabled: true
focusable: true
packageName: "com.tencent.news"
resourceName: "com.tencent.news:id/btn_first_agree"
text: "\345\220\214\346\204\217"
visibleBounds { ... }
提示
您可能發現上面列印的資訊中缺少某些欄位,比如 description 等。這種情況通常表示該欄位值為空或為 false,您仍然可以透過屬性正常存取相關欄位以取得其值。
可以看到這個資訊有些複雜,這是 protobuf 預設的列印格式。您可以直接透過存取對應的屬性來列印出其實際的值。比如想要讀取元素 text 的值,您可以直接像下面這樣使用。
>>> info = element.info()
>>> print (info.text)
同意
當然,裡面還有一些元素區域座標相關的資訊,您也可以存取它們。比如您現在想要取得該元素對應的區域資訊,您可以像以下方式列印區域資訊,也可以將其儲存為變數供後續操作使用。
>>> info = element.info()
>>> print (info.bounds)
top: 947
left: 338
bottom: 997
right: 743
輸出或者回傳的值是一個區域資訊(Bounds),您會發現這也是某些截圖介面會使用的參數。您可以將該參數傳遞給截圖介面,實現對元素的單獨截圖。不過我們已為您封裝了更簡便的方法。
您可能也想要取得元素的寬度和高度來計算偏移量,例如計算其他元素的相對偏移。您可以使用:
>>> info = element.info()
>>> print (info.bounds.width, info.bounds.height)
484 138
或者取得該元素的中心點或角點,如左上角、右下角等。以下介面通常回傳 Point 物件,您也可以從 Point 物件中取得對應的 X 和 Y 裝置螢幕座標。
>>> info = element.info()
>>> print (info.bounds.center())
x: 792
y: 1908
>>> print (info.bounds.center().x)
792
以下呼叫用來取得元素的角點座標,範例取得了元素左上角的座標,除此之外還支援取得 bottom-right、top-right、bottom-left 等共四個角的座標。
>>> info = element.info()
>>> print (info.bounds.corner("top-left"))
x: 550
y: 1839
>>> print (info.bounds.corner("top-left").x)
550
元素遍歷¶
您也可以遍歷選擇器選擇到的所有元素。正常情況下,當前上下文中該選擇器可能僅匹配到一個元素。如果您想測試遍歷,請選擇會匹配多個元素的選擇器。您可以直接在選擇器上使用 for 迴圈或其他方式進行遍歷。
for i in element: print (i.info())
或者如果您知道存在多個匹配元素,想獲得指定的第 N 個匹配元素,可以使用如下方式取得。
element_3rd = element.get(3)
元素計數¶
通常情況下您不會直接用到此介面。以下呼叫可以取得您當前選擇器匹配到的元素個數。
>>> element.count()
1
元素截圖¶
我們支援元素級別的截圖,可以單獨截下元素的圖像而無需全螢幕截圖再行裁剪。
element.screenshot(quality=60)
您可以在截圖後直接使用 getvalue() 方法取得截圖的二進位資料,或者直接將其傳入 PIL.Image。
>>> element.screenshot(quality=60).getvalue()
b'\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x00\x00\x01\x00\x01\x00\x00\xff\xe2\x02(ICC_PROFILE\x00\x01\x01\x00\x00\x02\x18\x00\x00\x00\x00\x02\x10\x00\x00mntrRGB XYZ \x00\x00...
或者如果您無需繼續處理,也可以選擇直接將截圖儲存到本地檔案中。
>>> element.screenshot(quality=60).save("image.png")
等待元素¶
某些情況下,您可能需要判斷當前頁面是否載入完畢。通常可以透過判斷相關元素是否已經出現來判斷頁面是否載入完成。以下範例會等待「同意」元素出現,最長等待時間為 10 秒。
提示
這裡的等待時長是毫秒,所以 10 秒鐘需要 *1000,10 秒 = 10000 毫秒。
element.wait_for_exists(10*1000)
>>> element.wait_for_exists(10*1000)
True
此外,我們還支援等待元素消失,即等待元素從介面消失。
element.wait_until_gone(10*1000)
>>> element.wait_until_gone(10*1000)
False
文字輸入¶
文字輸入是一個需要注意的地方。我們不可能往一個按鈕上輸入文字,因為那是按鈕。現在我們重新選取一個輸入框元素來介紹,這個元素的基本資訊如下所示。

注意
取得輸入框元素有一些需要注意的事項:請注意,在取得輸入框元素時,您的輸入法必須處於彈出狀態,然後再查找相關元素,並且建議仔細查找,否則您取得的可能並不是真正的輸入框。
提示
在自動化流程中,讓輸入法處於彈出狀態僅僅需要您在程式碼中先行點擊父容器中顯示的輸入框即可。
對於上面這個輸入框,我們可以呼叫如下介面往其中輸入「你好世界」這個字串。您也可以輸入英文或其他 Unicode 字串,只需如下使用即可在框內輸入文字。
>>> element = d(text="搜尋感興趣的內容")
>>> element.set_text("你好世界")
True
如果您想要取得該輸入框當前顯示的文字內容,可以這樣呼叫。
注意
請注意,這裡我們更換了選擇器。初始選擇器使用了 text 屬性,但輸入文字後,元素內容發生變化,導致原選擇器不再匹配,因此我們更換了另一個選擇器。選擇合適的選擇器很重要,但此範例僅作演示,姑且如此。
>>> element = d(className="android.widget.EditText")
>>> element.get_text()
'你好世界'
您也可以清空當前輸入的內容。通常輸入文字時會自動清空原有文字,但您也可以手動清空。
提示
連續使用按鍵介面循環按下 BACKSPACE 鍵也可以實現類似的效果。
>>> element = d(className="android.widget.EditText")
>>> element.clear_text_field( )
True
備註
極端情況下,有些地方無法正常使用此介面輸入文字,我們正在支援。
普通滑動¶
使用如下介面進行介面的滑動操作,例如列表的上下滑動翻頁。以下呼叫實現向上滑動,step 值越大,滑動速度越慢,比較適合精度要求較高的滑動。
注意
在簡單情況下,此操作無需提供選擇器參數。如果遇到無法滑動的情況,請自行設定選擇器條件為適合的元素,例如具有 scrollable 屬性的元素或列表的第一級容器。
d().swipe(direction=Direction.DIR_UP, step=32)
>>> element = d(resourceId="com.tencent.news:id/important_list_content")
>>> element.swipe(direction=Direction.DIR_UP, step=32)
True
快速滑動¶
快速滑動類似於人快速滑動的行為,此操作會快速地滑動螢幕,適合模擬快速瀏覽類的操作。以下範例從上往下滑動螢幕,範例中選擇器為空,您仍然需要根據實際情況選擇是否填寫選擇器。
d().fling_from_top_to_bottom()
從下往上的快速滑動:
d().fling_from_bottom_to_top()
從左往右的快速滑動:
d().fling_from_left_to_right()
從右往左的快速滑動:
d().fling_from_right_to_left()
注意
在簡單情況下,此操作無需提供選擇器參數。如果遇到無法滑動的情況,請自行設定選擇器條件為適合的元素,例如具有 scrollable 屬性的元素或列表的第一級容器。
>>> element = d(resourceId="com.tencent.news:id/important_list_content")
>>> element.fling_from_bottom_to_top()
True
元素拖曳¶
將元素拖曳到另一元素所在位置(如將 App 圖示拖入資料夾)。
element.drag_to(Selector(text="購物")) # 拖到目標元素所在位置
子層級與同級查詢¶
重複或無明顯特徵的元素,可先定位父容器,再用 child 取子元素、sibling 取同級元素縮小範圍。
form = d(resourceId="login_form") # 定位父容器
form.child().get(1) # 取 form 下第一個子元素
form.sibling(textContains="找回密碼") # 取與 form 同級的元素,文字包含「找回密碼」
# 以下是一個稍微複雜的查詢
# 取得匹配 resourceId=com.example.com:id/resource 的第一個結果,選擇他的 resourceId=com.example.com:id/abc 的子節點,並在這個子節點下查詢 description 包含「一天內」的元素,輸出其資訊。
d(resourceId="com.example.com:id/resource").get(0).child(resourceId="com.example.com:id/abc").child(descriptionContains="一天內").info()
對於如下範例佈局資訊,您可以使用下面的查詢方式來精確選中到該元素。
d(resourceId="com.zhiliaoapp.musically:id/bxa").child().get(3).child().get(1).info()
提示
大部分情況下您不需要使用如此精確的 child sibling 查詢語句,只需要一句 d(text="Continue with Google") 即可,除了實在無法透過文字定位的情況。
快速滑動至邊界¶
持續快速滑動直至無法繼續;未必能檢測到已到底,須指定 max_swipes。
d().fling_from_top_to_bottom_to_end(max_swipes=32) # 向下快速滑至邊界
d().fling_from_bottom_to_top_to_end(max_swipes=32) # 向上
d().fling_from_left_to_right_to_end(max_swipes=32) # 向右
d().fling_from_right_to_left_to_end(max_swipes=32) # 向左
等速滑動¶
按固定步長 step 滑動,比 swipe 更機械,適合需要穩定步進的場景。
d().scroll_from_top_to_bottom(step=60) # 向下
d().scroll_from_bottom_to_top(step=60) # 向上
d().scroll_from_left_to_right(step=60) # 向右
d().scroll_from_right_to_left(step=60) # 向左
等速滑動至邊界¶
與快速滑動至邊界類似,以等速滑動代替 fling,同樣須指定 max_swipes 與 step。
d().scroll_from_top_to_bottom_to_end(max_swipes=32, step=60) # 向下滑至邊界
d().scroll_from_bottom_to_top_to_end(max_swipes=32, step=60) # 向上
d().scroll_from_left_to_right_to_end(max_swipes=32, step=60) # 向右
d().scroll_from_right_to_left_to_end(max_swipes=32, step=60) # 向左