AppCrawler 自動遍歷測試實踐(三):動手實操與常見問題匯總
上兩篇文章介紹了自動遍歷的測試需求、工具選擇和 AppCrawler 的環(huán)境安裝、啟動及配置文件字段基本含義,這里將以實際案例更加細(xì)致的說明配置文件的用法和一些特殊場景的處理。
下面我們繼續(xù)之前的例子,在雪球搜索框輸入搜索內(nèi)容后的頁面開始:
testcase:設(shè)置測試用例,輸入 alibaba 后,點選"阿里巴巴"
yaml 寫法如下:
testcase:
?name: "XueQiuTestDemo AppCrawler"
?steps:
?- { xpath: "//*[contains(@resource-id,'image_cancel')]", action: click }
?- xpath: home_search
? ?action: click
?- xpath: search_input_text
? ?action: alibaba
?- { xpath: 阿里巴巴, action: click }
selectedList:遍歷范圍設(shè)定
接上一步點選"阿里巴巴"后到達(dá)如下界面:
我們先看demo配置文件中的原始寫法,如下:
selectedList:
- given: []
?when: null
?then: []
?xpath: "//*[contains(name(), 'Button')]"
?action: null
?actions: []
?times: 0
- given: []
?when: null
?then: []
?xpath: "//*[contains(name(), 'Text') and @clickable='true' and string-length(@text)<10]"
?action: null
?actions: []
?times: 0
- given: []
?when: null
?then: []
?xpath: "//*[@clickable='true']/*[contains(name(), 'Text') and string-length(@text)<10]"
?action: null
?actions: []
?times: 0
- given: []
?when: null
?then: []
?xpath: "//*[contains(name(), 'Image') and @clickable='true']"
?action: null
?actions: []
?times: 0
- given: []
?when: null
?then: []
?xpath: "//*[@clickable='true']/*[contains(name(), 'Image')]"
?action: null
?actions: []
?times: 0
- given: []
?when: null
?then: []
?xpath: "//*[contains(name(), 'Image') and @name!='']"
?action: null
?actions: []
?times: 0
- given: []
?when: null
?then: []
?xpath: "//*[contains(name(), 'Text') and @name!='' and string-length(@label)<10]"
?action: null
?actions: []
?times: 0
原始文件中將所有可點擊的控件類型都包括了進(jìn)去,再加上了部分 text 長度的限制 現(xiàn)在我們按照自己平常的簡便寫法重新編寫,先設(shè)置所有 clickable 等于 true 的控件進(jìn)行點擊:
selectedList:
- { xpath: "//*[@clickable='true']", action: click }
blackList:黑名單,將不想要被點擊的元素加入黑名單中
配置文件原始寫法如下,表示將帶有2位數(shù)字的排除在外,可能是App中包含了很對關(guān)于股價展示的,不需要挨個點擊:
blackList:
- given: []
?when: null
?then: []
?xpath: ".*[0-9]{2}.*"
?action: null
?actions: []
?times: 0
我們現(xiàn)在希望不要點擊到叉號?和取消按鈕,否則會跳出此頁面,那么就可以把其加入黑名單中,如下:

blackList:
- xpath: ".*[0-9]{2}.*"
- xpath: //*[@resource-id='action_delete_text']
- xpath: //*[@resource-id='action_close']
firstList: 優(yōu)先被遍歷
這里我們設(shè)置讓text包含"股票"的優(yōu)先遍歷
firstList:
- { xpath: "//*[contains(@text,'股票')]", action: click }
lastList:最后被點擊
在頁面中有很多標(biāo)簽頁(例如綜合、股票、用戶、組合):

每個標(biāo)簽頁下面對應(yīng)著很多控件需要被操作,可是在當(dāng)前頁面下的控件未被遍歷完的時候就有可能會點擊到其他標(biāo)簽頁中了,我們希望的是在一個標(biāo)簽頁下完全遍歷結(jié)束后最后再點擊標(biāo)簽控件,這個就可以借助lastList來完成,讓元素在點進(jìn)標(biāo)簽頁后的內(nèi)容為最后遍歷
lastList:
- { xpath: "//*[contains(@resource-id,'ti_tab_indicator')]//*", action: click }
backButton: 當(dāng)所有元素都被點擊后默認(rèn)后退控件定位
AppCrawler是不知道后退按鈕是哪一個的,這個可能會造成的一種情況是,當(dāng)我們進(jìn)入一個頁面時,還沒有對這個頁面完全遍歷就點到了后退按鈕,這樣就會造成測試不充分
因此我們可以給它設(shè)置一個默認(rèn)的后退按鈕,使所有事件完成后再 back
backButton:
- { xpath: "//*[contains(@resource-id,'action_back')]", action: click }
maxDepth: 遍歷的最大深度
有時候我們的頁面層次可能很深,每次遍歷測試的需求可能不同,有時候可能需要在短時間內(nèi)測試主要常用界面的功能,有時候可能需要全面的測試,所以測試的深度就不相同,我們可以依靠 maxDepth 來進(jìn)行需求定制,這里以遍歷 2 層深度為例:
maxDepth: 2
findBy:定位方式的選擇
findBy 可以設(shè)置定位方式,有 default、android、id、xpth 方式可選,默認(rèn)狀態(tài)會自動判斷是否是要 Android 定位或者 iOS 定位。當(dāng)我們的定位很精準(zhǔn)的時候,用默認(rèn)的 default 速度會快一點;若是定位符寫的不是很精準(zhǔn),在切換到 Android 定位的時候可能找不到,這個時候就可以嘗試將其設(shè)置為 Xpath方式定位。
findBy: "xpath"
defineUrl = ListString:用來確定url的元素定位 xpath,他的 text 會被取出當(dāng)做 url 因素;就是說如果想要當(dāng)前的頁面布局與某個控件之間有層級關(guān)系,給定一個標(biāo)記控件,以此來區(qū)分不同的界面(語言的描述怎么樣都有點晦澀,還是結(jié)合下面的示例來理解吧。。。)
有時候我們會遇見這種情況:設(shè)置了 clickable 未 true 的控件都被遍歷,可是運行時發(fā)現(xiàn)很多控件都沒有被遍歷到,一般這種情況有一下兩種原因:
元素屬性 clickable 本身就為 false 或者它的父節(jié)點等都為 false,這樣自然是無法遍歷到的。
還有一種情況是同屬性的控件在兩個tag頁面都存在,在其中一個tag頁遍歷一遍之后,再到下一個tag頁中就會默認(rèn)已經(jīng)遍歷,不會再進(jìn)行遍歷,如下這種:
在“股票”和“用戶”tag頁中,“加自選”和“關(guān)注”控件的clickable及id屬性一樣。


他們所屬的頁面屬性也一樣,所以會被看做是同一個頁面下的同一個控件:


如上這種情況肯定不是我們想要的,我們想要它在股票和用戶頁都分別進(jìn)行遍歷,更好的覆蓋測試,那么就要借助于 defineUrl 了;
1)按照上面的介紹,我們首先要找一個標(biāo)志控件,用來做頁面的區(qū)分,那么我們首先想到的就是從“股票”和“用戶”這兩個 tag 標(biāo)簽屬性上來找,遺憾的是最終發(fā)現(xiàn)這兩個控件的屬性全都一毛一樣:

2)接著我們就必須從 tag 頁內(nèi)部來找標(biāo)志控件了,我們發(fā)現(xiàn)在“股票”和“用戶”頁中搜索出來的結(jié)果名稱的 id 是不同的:


3)上面介紹過了 defineUrl 是取的 text 屬性值作為標(biāo)志區(qū)分,所以這里取股票頁的第一個元素“阿里巴巴”和用戶頁的第二個元素“阿里巴巴四十大盜”, 具體 yaml 寫法如下:
defineUrl:
- (//*[contains(@resource-id, "stockName")])[1]
- (//*[contains(@resource-id, "user_name")])[2]
缺點:上面的做法雖然解決了頁面區(qū)分的問題,但是有一個缺點就是我們定義了遍歷的深度,然而使用 defineUrl 之后將每個標(biāo)志符在的頁面都視為一個新的 activity,因此遍歷深度就會從這里開始重新計算
4)繼續(xù)解決上述的缺點,我們可以在 clickable 之前指定所屬的頁面,當(dāng)判斷不在此頁面后就會自動跳回
selectedList:
- { xpath: "//*[@resource-id='com.xueqiu.android:id/ll_search_result']//*[@clickable='true']//*", action: click }
5)另外我們之前在 selectList 中寫了 clickable=true, 而 clickable=true 通常只是布局元素,布局元素一般是沒有任何屬性的,不知道控件里包含什么,這樣在截圖和生成報告的時候就會造成不精準(zhǔn),截圖中的步驟框就很可能選擇錯誤,對我們定位分析問題造成困擾;
所以我們要繼續(xù)往下找標(biāo)志符,以 Text 作為定位標(biāo)志符:
selectedList:
- { xpath: "//*[@resource-id='com.xueqiu.android:id/ll_search_result']//*[@clickable='true']//*[contains(@class,'Text')]", action: click }
用 Text 作為標(biāo)志符以后所有的 Text 屬性都會遍歷一遍,還可以進(jìn)一步優(yōu)化,使用id非空作為判定條件,并且通常研發(fā)將控件設(shè)置 id 的話很可能此控件有關(guān)鍵的作用
selectedList:
- { xpath: "//*[@resource-id='com.xueqiu.android:id/ll_search_result']//*[@clickable='true']//*[@resource-id!='']", action: click }
6)按照上面的寫法又引發(fā)了新的問題,就是 id 不為空的時候,我們的 tag 控件無法被選中了,因為 tag 控件的 id 正好為空:

因此我們又需要對 selectedList 進(jìn)行修改,單獨增加一條判定條件用來過濾出 tag 控件;我們注意到它們同屬一片有 id 的區(qū)域,并且各自自身有 text:

修改后的selectedList如下:
selectedList:
- { xpath: "//*[@clickable='true']//*[contains(@class,'Text')]", action: click }
- { xpath: "//*[contains(@resource-id, 'ti_tab_indicator')]//*[contains(@class,'Text')]",
tagLimitMax:最大的點擊次數(shù)
有時候頁面中可能會有多個相同類型的控件,這些控件之間可能只是展示的信息不同,其他功能屬性都一直,那么為了保證測試效率可以只設(shè)置讓它被點擊少數(shù)次或者一次,通過 tagLimitMax 設(shè)置即可。
tagLimitMax: 1
缺點:這個設(shè)置是一個全局的,一旦設(shè)置,那么所有的同類型的控件都只會被點擊一次,但是像上個例子中的 4 個tag標(biāo)簽控件雖然是同類型的,但是每一個都需要被點擊一次,這樣顯然就不符合我們的需求了,這個時候就需要 tagLimit 參數(shù)了
tagLimit:自定義控件類型的點擊次數(shù)
tagLimit:
- xpath: //*[contains(@resource-id, 'ti_tab_indicator')]//*[contains(@class,'Text')]
?action: click
?times: 4
triggerActions:觸發(fā)器,特定條件觸發(fā)執(zhí)行動作的設(shè)置 這個參數(shù)是一個非常有用的參數(shù),比如我們可能會遇到如下的情況
廣告、升級彈框在測試過程中突然出現(xiàn)
某些動作需要輸入
某些動作需要特定次數(shù)的操作
這樣每次出現(xiàn)彈框都會被處理
測試中途碰到了賬號密碼輸入框需要輸入的可以提前在triggerActions中設(shè)置
triggerActions:
- xpath: //*[contains(@resource-id,'image_cancel')]
? action: click
? times: 1
App 運行比較慢,容易超時怎么辦? 答:AppCrawler 默認(rèn)每次操作時會等待 500ms; 通過 triggeraction 來解決需要等待的條件,xpath 為進(jìn)度條,action 為 sleep 1s。
tagLimit 會限制同屬性但不同層級的元素嗎? 答:tagLimit 限制的是相同的父節(jié)點層級,不管屬性,是看布局的層級。
如何防止遍歷的時候不小心跳到別的應(yīng)用?跳到別的應(yīng)用后怎么回來? 答:會自動跳轉(zhuǎn)回來的。除非設(shè)置了 App 的白名單
頁面需要在當(dāng)前頁不?;瑒蛹虞d測試 答:遍歷完當(dāng)前頁后用 afterpage 參數(shù)設(shè)置滑動
firstList 和 lastList 可以寫多個表達(dá)式嗎?他們是如何執(zhí)行的? 答:順序是這樣排列的
app 運行比較慢,容易超時怎么辦? 答:AppCrawler 默認(rèn)每次操作時會等待 500ms;通過 triggeraction 來解決需要等待的條件,xpath 為進(jìn)度條,action 為 sleep 1s
tagLimit 會限制同屬性但不同層級的元素嗎? 答:tagLimit 限制的是相同的父節(jié)點層級,不管屬性,是看布局的層級
如何防止遍歷的時候不小心跳到別的應(yīng)用?跳到別的應(yīng)用后怎么回來? 答:會自動跳轉(zhuǎn)回來的。除非設(shè)置了 App 的白名單
頁面需要在當(dāng)前頁不?;瑒蛹虞d測試 答:遍歷完當(dāng)前頁后用 afterpage 參數(shù)設(shè)置滑動
firstList 和 lastList 可以寫多個表達(dá)式嗎?他們是如何執(zhí)行的? 答:順序是這樣排列的
firstList[0]
firstList[1]
排除lastList firstList之后剩下的元素
lastList[0]
lastList[1]
backbutton
Appclawer ==>maxDepth:這個層級是如何定義的? 答:maxDepth 可以從 log 中看到,AppCrawler.log 中有一個 Stack 的輸出,里面默認(rèn)保存的是所有 activity 的棧記錄。
Main Main->UserProfile Main->UserProfile->Login Main maxDepth是判斷這個堆棧最長的長度,一旦超過就回退