Airtest (poco 框架) 元素定位實戰

MarvinWu發表於2020-08-25

對於UI自動化測試來說,處理好元素定位問題對於提高測試設計效率顯得尤為重要,本文以美團APP選擇商品頁面為例,介紹在使用Airtest(Poco框架)進行元素定位時可能遇到的問題以及相應的處理方式。

目標頁面如下所示:

假設頁面上的藥品是我們的測試資料,現在需要點選“+”圖示,新增指定藥品到購物車。
通過Airtest IDE檢視“+”圖示元素屬性:

Path from root node: [0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 4, 0, 0]
Payload details:
type : android.widget.ImageView
name : com.sankuai.meituan:id/img_foodCount_add_fix
enabled : True
visible : True
resourceId : b'com.sankuai.meituan:id/img_foodCount_add_fix'
zOrders : {'global': 0, 'local': 1}
package : b'com.sankuai.meituan'
anchorPoint : [0.5, 0.5]
dismissable : False
checkable : False
scale : [1, 1]
boundsInParent : [0.10277777777777777, 0.04625]
focusable : False
touchable : True
longClickable : False
size : [0.10277777777777777, 0.04625]
pos : [0.5259259259259259, 0.37583333333333335]
focused : False
checked : False
editalbe : False
selected : False
scrollable : False

很明顯,我們可以通過name屬性獲得頁面上所有的“+”號圖示構成列表,然後根據索引選擇需要點選的目標,但是這種做法存在不足,隨著測試資料的變化、頁面佈局的變化,我們不知道自己點的是哪件商品,當然也會影響到後續對於測試資料的校驗。
所以,現在新的需求是新增指定的商品到購物車,下文以“[精華]正柴胡飲顆粒5g*10袋”這件商品為例。

首先,我們得知道Poco框架支援通過text屬性精確找到元素,程式碼如下:

item_text_obj = poco(text="[精華]正柴胡飲顆粒5g*10袋")  # 找到待測商品名稱對應的元素

找到的元素在Airtest UI樹中展示如下:

同時,可以找到待點選的“+”號圖示在UI樹中所處的位置:

通過觀察UI樹,可以得出目標元素(target_obj)與商品文字元素(item_text_obj)之間的對應關係:
商品文字元素的父節點的第五個子節點的第一個子節點的子節點就是我們要點選的目標元素。
對應的完整Poco程式碼如下:

item_text_obj = poco(text="[精華]正柴胡飲顆粒5g*10袋")  # 找到待測商品名稱對應的元素
foo_parent = item_text_obj.parent()
foo_childs = foo_parent.child()
target_obj = foo_childs[4].child()[0].child()[0]
target_obj.click()

這段程式碼放到Airtest執行可以通過,但是很遺憾,並不會點選到加號目標,而是進入到商品詳情頁面。
通過列印target_obj屬性便可發現問題出在哪裡:

日誌顯示,根據總結的規律找到的元素name是“com.sankuai.meituan:id/txt_stickyfoodList_adapter_food_price_fix”,而之前我們已經知道期望點選的元素name是“com.sankuai.meituan:id/img_foodCount_add_fix”,回到Airtest中的UI樹,不難找到實際點選的元素位置:

這裡其實揭示了Poco框架一個令人相當不爽的問題,對於任一元素的子元素,我們並不能很輕易地確定其正確的索引。
甚至存在這種可能,對於A商品,目標元素索引是1,換成B商品,索引有可能又變成0了,有興趣的可以自己慢慢嘗試。

# 索引錯誤會導致找不到元素或點錯元素
# target_obj = foo_childs[4].child()[0].child()[0]
target_obj = foo_childs[4].child()[1].child()[0] # 這才是正確的索引
target_obj.click()

問題已經找到,最後說說解法。
解法一:

item_text_obj = poco(text="[精華]正柴胡飲顆粒5g*10袋")  # 找到待測商品名稱對應的元素
foo_parent = item_text_obj.parent()
target_obj = foo_parent.offspring('com.sankuai.meituan:id/img_foodCount_add_fix')

Poco找子元素除了child方法還有offspring方法,支援找到指定name的後代元素(注意與子元素的區別),如果目標元素的name可以唯一確定元素,建議使用這種方式處理。
解法二:

def get_child_by_index(element_p, structure, index):
"""
根據可見的元素位置關係選擇子元素
:param element_p: 父節點
:param structure: l表示元素佈局為左右結構,v表示上下結構,
:param index: 序號,按從左往右,從上往下順序取
:return:
"""

temp_list = []
element_list = []
for element_c in element_p.child(): # 遍歷子節點
temp_list.append({'element': element_c, 'pos': element_c.attr('pos')}) # 將元素以及其位置POS屬性構造成字典以後再存入列表
if structure == 'l': # 左右結構
temp_list.sort(key=lambda e: e['pos'][0], reverse=False) # 呼叫列表sort排序方法,排序的key是字典的POS鍵值的下標為0的值,即橫座標
elif structure == 'v': # 上下結構
temp_list.sort(key=lambda e: e['pos'][1], reverse=False) # 呼叫列表sort排序方法,排序的key是字典的POS鍵值的下標為1的值,即縱座標
for element in temp_list:
element_list.append(element['element'])
return element_list[index]

item_text_obj = poco(text="[精華]正柴胡飲顆粒5g*10袋") # 找到待測商品名稱對應的元素
foo_parent = item_text_obj.parent()
foo1 = get_child_by_index(foo_parent, 'v',4) # 從上往下分別是圖片,名稱,銷售額等資訊
foo2 = get_child_by_index(foo1, 'l', 1) # 從左往右分別是價格資訊、加號按鈕
target_obj= foo2.child()
target_obj.click()

解法二的思路已經寫在註釋裡了,全文完。

相關文章