介面進階

本章節為您介紹更高級的自動化介面。您可以透過這些介面完成各種細緻的操作。本章內容較多,如果您是第一次接觸,我們建議您耐心地看完每個部分。

小撇步

在編寫自動化程式碼時,您可以直接在遠端桌面的右側終端機輸入命令 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)

輸出或者傳回的值是一個區域資訊(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-righttop-rightbottom-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)

或者如果您知道存在多個匹配元素,想獲得指定的第 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
方向指示說明
Direction.DIR_UP向上滑動
Direction.DIR_LEFT向左滑動
Direction.DIR_DOWN向下滑動
Direction.DIR_RIGHT向右滑動

快速滑動

快速滑動類似於人快速滑動的行為,此操作會快速地滑動螢幕,適合模擬快速瀏覽類的操作。以下範例從上往下滑動螢幕,範例中選擇器為空,您仍然需要根據實際情況選擇是否填寫選擇器。

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="购物"))

#########
# 查找同級或者子級元素
#########
# 有時會有一些重複元素或無明顯特徵的元素,很難定位。
# 這時你可以透過尋找子級/同級元素的方法來縮小查找範圍。
# 子級元素,例如:一個聊天登入框,裡面的輸入框即為登入框的子級元素。
# 同級元素,例如:聊天輸入框裡的使用者名稱和密碼框為同級元素(正常情況下)。
form = d(resourceId="login_form")
form.child(index=1)
# 這將取得 login_form 下索引為 1 的子元素。
form.child(index=1).sibling()
# 你也可以這樣來找與 login_form 同級的找回密碼按鈕
# (其實已經可以透過字串判斷,就不需要這樣做了,這裡只是示範)。
form.sibling(textContains="找回密码")
# 它們本身就是一個元素,你可以對其執行任何元素操作。


# 其他:一直向下/左右滑動,直到滑動到底。
# 因為並不一定能滑動到底或偵測到滑動到底,
# 所以 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)

#########
# scroll:較機械的滑動
#########
step = 60
max_swipes = 32
# 從上往下滑動(步數為 step)
d().scroll_from_top_to_bottom(step)
# 從下往上滑動
d().scroll_from_bottom_to_top(step)
# 從左往右滑動
d().scroll_from_left_to_right(step)
# 從右往左滑動
d().scroll_from_right_to_left(step)

# 其他:一直向下/左右滑動,直到滑動到底。
# 同上文 fling 描述。
d().scroll_from_top_to_bottom_to_end(max_swipes, step)
d().scroll_from_bottom_to_top_to_end(max_swipes, step)
d().scroll_from_left_to_right_to_end(max_swipes, step)
d().scroll_from_right_to_left_to_end(max_swipes, step)