上期回顧:Airtest局部截圖+找圖、截屏另存為
以下基於
python3.8;airtestIDE1.2.13;airtest1.2.4;pocoui1.0.85
Airtest框架講的差不多了,本期開始講Poco框架,注意:Poco框架和Airtest框架很多API是同名的,但使用方法完全不一樣!!!一定不要搞混了,我初學時也經常搞混,這點一定要注意!
具體Poco框架和Airtest框架是什么關系,可以看之前文章:Airtest Project——UI自動化利器介紹
今天的主題是Poco框架的元素定位,在講之前我們先普及一個基礎知識,就是Poco框架腳本內容的編寫順序,新手經常會掉這個坑里。
Poco腳本編寫順序
在講之前不熟悉Poco的可以先看下歷史文章:
AirtestIDE實踐二:Poco框架試用
Airtest之使用Poco測試Android原生應用
Airtest之使用Poco測試iOS原生應用
正確的Poco腳本編寫順序應該是:連接設備-->打開應用&等待應用啟動完成-->初始化poco
新手不按這個順序的話,有可能出現各種異常報錯。
純代碼連接設備方法可以看之前的Airtest API精講之設備連接管理API集合
示例:
# -*- encoding=utf8 -*-
__author__ = "測試工程師小站"
from airtest.core.api import *
from poco.drivers.android.uiautomation import AndroidUiautomationPoco
# 連接設備、初始化日志路徑
auto_setup(__file__, logdir=True, devices=["Android:///"])
# 啟動計算器
start_app("com.miui.calculator")
sleep(3)
# 初始化安卓原生poco
poco = AndroidUiautomationPoco(use_airtest_input=True, screenshot_each_action=False)
# 依次點1+1=,這塊代碼可以使用IDE左下的錄制功能
poco("com.miui.calculator:id/digit_1").click()
poco("com.miui.calculator:id/op_add").click()
poco("com.miui.calculator:id/digit_1").click()
poco("com.miui.calculator:id/btn_equal_s").click()
# 獲取結果控件的文本並斷言,可以使用IDE左下的鎖定功能,並找到結果控件
r = poco("com.miui.calculator:id/result").get_text()
assert_equal("= 2", r, "結果=2")
Poco元素定位
Poco元素定位需要用到AirtestIDE中的'Poco輔助窗'功能,之前有介紹AirtestIDE基本功能(一)
AirtestIDE的錄制功能可自動生成元素定位代碼,但和所有自動化工具的錄制功能一樣,現在的工具還沒有那么智能,錄制出的定位代碼可能無法定位、代碼太長太冗余、兼容性不好(下個版本UI有一點小改動就導致定位失效)。所以錄制可以輔助,但一定還要熟練掌握定位基礎和技巧。
Poco官方將元素定位分為了:基本選擇器、相對選擇器、空間選擇器,以及通過正則表達式匹配。熟悉Selenium或Appium的朋友可以無縫上手。
基本選擇器
我們對元素定位的目標就是唯一,即使下個版本有一些UI改動,也能盡量不影響之前的定位代碼。像Selenium、Appium,如果開發配合,應該給所有元素一個唯一的id值,我們通過id即可唯一定位一個元素。在Poco中,'name'屬性就是類似於Selenium的'id'屬性的存在。
我們以安卓設置為例
一個經典的定位:
poco(name="android:id/title", text="語言與輸入法")
# 在poco中,如果不寫屬性名,則默認該屬性為name,所以也可以寫成
poco("android:id/title", text="語言與輸入法")
# 在這個例子中,設置中的所有文字選項的name都是"android:id/title"
# 但"text"屬性是唯一的,所以可以寫成更簡單的
poco(text="語言與輸入法")
上面就是一個簡單的定位的例子,一般情況下,如果開發配合,給"name"屬性都設為全局唯一名稱,那我們的定位工作將會非常簡單。如果受公司所限開發無法配合,一般"text"屬性也是唯一的。
我們定位的原則就是用最少的屬性全局唯一定位一個元素,優先用"name"、"text"單屬性定位,如果單屬性定位不唯一,就組合起來使用,仍不唯一時,可以繼續加入其他屬性,如"type"、"touchable"、"clickable"、"visible"等。
空間選擇器
空間選擇器就是有一組元素時,利用索引下標來確定唯一元素,下標是從0開始。
下來我們以Airtest官網提供的Unity的App為例
# 我們的目標是點擊鯊魚圖片元素
# 鯊魚是第1個'Image',即下標0
poco("Image")[0].click()
# 也可以和相對選擇器組合使用
node = poco("fish")[0] # 先定位到鯊魚的整個節點
# 上面那行也可以拆分寫成2行
# node_list = poco("fish")
# node = node_list[0]
shark = node.child("Image") # 再定位到其下的"Image"元素
shark.click()
但使用空間選擇器需要注意,有時我們App內的元素索引是會變的,比如上面例子中第1個(索引0)是鯊魚,假如我們點擊后,又多出來一個鯉魚,並且排在第1位,這時候鯊魚就變第2個了,所以還想再次點擊鯊魚的話,需要重新定位一次
poco("Image")[1].click()
相對選擇器
有時候,通過基本選擇器、空間選擇器無法定位到元素時,可以使用相對選擇器來定位。
上面我們已經例舉了一個相對選擇器的例子,即先定位到鯊魚的整個節點元素'fish',再定位他的子元素'Image'
Poco有如下相對選擇器:
-
child(name=None, **attrs)
返回第1個符合條件的子元素 -
children()
返回所有子元素 -
offspring(name=None, **attrs)
返回符合條件的子孫元素 -
parent()
返回父元素 -
sibling(name=None, **attrs)
返回符合條件的兄弟元素
還是以此圖為例:
# 返回playLocalPositioning的第1個子元素,即鯊魚的node元素'fish'
shark_node = poco("playLocalPositioning").child()
# 返回鯊魚的子元素name
shark_name = shark_node.child("name")
print(shark_name.get_text())
# 返回playLocalPositioning的所有子元素
fish_list = poco("playLocalPositioning").children()
shark_node = fish_list[0]
# 返回playLocalPositioning的所有符合條件的子孫元素(下一層)
fish_list = poco("playLocalPositioning").offspring("fish")
shark_node = fish_list[0]
# 返回playLocalPositioning的所有符合條件的子孫元素(下下一層)
fish_list = poco("playLocalPositioning").offspring("name")
shark_name = fish_list[0]
print(shark_name.get_text())
# 返回Canvas的所有符合條件的子孫元素(下下下下一層)
shark_name = poco("Canvas").offspring("name")
# 返回鯊魚文字的父節點
shark_node = poco(text="shark").parent()
# 返回鯊魚文字的兄弟節點圖片
shark_img = poco(text="shark").sibling("Image")
# 返回鯊魚node節點的兄弟節點
# 按理說應該是3個fish節點,但poco會加上其父節點playLocalPositioning,不知道是不是bug
fish_list = poco(text="shark").parent().sibling()
# 所以我們限定一下只要3個fish節點
fish_list = poco(text="shark").parent().sibling("fish")
# 通過鯊魚文字元素定位珍珠文字元素
pearl_node = poco(text="shark").parent().sibling("fish")[2]
pearl_name = pearl_node.child("name")
print(pearl_name.get_text())
# 打印所有圖片的名字文字
fish_list = poco("fish")
for fish in fish_list:
print(fish.child("name").get_text())
通過正則表達式定位元素
Poco元素的各個屬性,都是字符串,所以正則定位元素和Python中的字符串正則匹配是一樣的。不會正則的可以先去學Python正則。
我們以第1個例子安卓配置為例,我們要定位"語言與輸入法",但是不同的MIUI版本或是三星、華為手機該項名稱可能是不一樣的,比如:
MIUI10該元素的text="語言與輸入法"
MIUI11該元素的text="語言和輸入法"
三星該元素的text="輸入法"
華為該元素的text="輸入法設置"
為了同時兼容4台手機,我們可以通過if來判斷手機型號然后定位元素,很復雜而且要寫很多代碼,這時就可以使用正則定位元素
# 一行代碼同時兼容4台手機
poco(textMatches=".*輸入法.*")
或者有時name屬性是類似的,我們想定位所有類似元素。如有3個元素name分別是"color_red"、"color_green"、"color_yellow"、
# 返回3個顏色元素的一個list
color_list = poco(nameMatches="color_.*")
除了最常見的textMatches、nameMatches和typeMatches,其實大部分的屬性都可以用這種方式來傳遞正則表達式,只要能夠用 poco(xx=預期屬性值) 來選擇的控件,就可以用 poco(xxMatches=預期屬性值的正則表達式) 來進行匹配定位。
---------------------------------------------------------------------------------
關注微信公眾號即可在手機上查閱,並可接收更多測試分享~