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()

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

相關文章