概述
Bee 是由有贊 QA 開發的 UI 自動化工具,並以此實現了 web 端和 wap 端的核心業務的自動化。旨在簡化開源工具提供的介面,方便 UI 自動化測試用例的設計。
Bee 整個框架是基於 selenium 和 selenide 設計的。框架對 selenium 和 selenide 提供的介面進行了二次封裝以滿足日常的用例設計,二次封裝後的介面解決了一些元素載入,元素定位解析等問題,可以讓用例設計變得更加簡便。 Bee 能支援 Web 和 Wap 頁面的元素定位以及操作,其中 Selenide 主要支援 Web 頁面的元素操作,Selenium 支援 Wap 頁面的元素操作。
Bee 為什麼會採用 Selenide+Selenium 的模式。原因一,其實框架設計的初衷是想全部依賴 Selenide 來完成 Web 和 Wap 的自動化,Selenide 對於作者來說是一個全新的開源框架,很想窺探一二; 原因二,Selenium 可無縫接入。在實踐過程中發現 Selenide 還不能支援 Wap 頁面,滿足不了日常的測試需求,好在框架可以很容易的嵌入 Selenium 從而實現了 Wap 頁面的自動化,也正是 Selenide 和 selenium 有這個特性,所以在框架設計初期才敢放心的嘗試採用Selenide;原因三,在實踐中的切身體會 Selenide 對頁面元素的處理會比 Selenium 平滑的多,因為 Selenide 其本身也是對 Selenium 的一個二次封裝,對 Selenium 的介面也做了很多的優化。
Bee 目前支援的環境為:mac、chrome,整個框架支援可擴充套件。
對於 Selenide 和 Selenium 的原理不在本文中贅述,大家可以到網上學習瞭解。 Bee 開源地址:beeyz,歡迎交流。
用例設計
按照實際的業務流程呼叫對應介面來實現 WEB-UI 自動化測試用例。case 層可呼叫 service 層和 pageObject 層的介面,pageObject 是對每一個頁面元素的一個封裝,service 是對一個常用的業務模組功能的封裝。比如一個營銷秒殺的測試用例,需要依賴登入、建立商品,這兩個業務功能就可以直接呼叫 service 中的介面。秒殺活動的建立就可以呼叫 pageObject 中的介面,然後按照秒殺的業務流程,在測試用例中把這些介面串起來就形成了一個 UI 自動化測試用例,詳細細節接下去會舉例說明。設計用例的靈活度取決於 pageObject 封裝的顆粒度,顆粒度越小越容易在用例層設計出新流程的測試用例。用例層使用了 testng,可按照實際的需求靈活設計一個測試用例。推薦在封裝 pageObject 介面的時候,顆粒度定義的越小越好,方便用例的擴充套件和維護。pageObject 封裝的介面就相當於一個原子,原子粒度越小越方便組裝成新的用例。相反如果粒度太粗維護上會不太方便。參考程式碼:
截圖是一個秒殺用例。建立活動之前,需要登入有贊微商城後臺,登入操作已封裝到 loginService,直接呼叫 service 層的介面,不需要在意這個步驟的細節;登入之後要指定一個商品參與秒殺活動,普通商品建立已封裝到 goodsService,直接呼叫 service 層的介面,不需要在意這個步驟的細節;接著是建立秒殺活動,建立秒殺活動的所有業務步驟都封裝到 seckillPage,這就是個 pageObject 的實現,也是用例設計中最主要的環節。最後把這幾個步驟串起來就形成了一個秒殺活動的測試用例。用例結構清晰,組裝簡單。
框架介紹
1、工程結構
整個工程基於 selenide & selenium,採用 pageObject 模式搭建起來。技術結構:selenide+selenium+testng+reportng+spring。下面對工程中的幾個重要模組做介紹。
1.1 dataprovider — 資料層
為了實現測試資料和測試用例分離而採取的一種方法,資料模型在 model 中定義,具體的測試資料則在 dataprovider 中初始化。
1.2 driver — 介面層
對 web 頁面所有元素的操作都是在這裡定義介面並實現的。driver 對 selenide&selenium 提供的介面做了二次封裝,對外提供封裝後的介面。common 實現了一些和介面相關的公共方法,比如模擬鍵盤按鈕等,目前 common 封裝的方法不多,大多功能都可以通過 selenide&selenium 實現。driver 層對開源工具介面做了二次封裝,想要驅動一個瀏覽器還有一個必不可少的工具 —— 瀏覽器驅動,這個驅動放在 resources 裡,驅動的版本必須與被測瀏覽器版本相匹配。
1.3 listeners — 監聽器
為了提高框架本身的容錯能力監聽一些事件。目前實現了:1. 監聽用例測試結果,可對不同的測試結果監聽器做不同的處理;2. 失敗測試用例重試的監聽,一個 fail 的用例最多可重試3次。
1.4 model — 資料模型
為了實現測試資料和測試用例分離而採取的一種方法,具體的測試資料在 dataprovider 中初始化。可以對一個業務流程中需要測試資料的元素在一個 model 中定義出來,方便管理和程式碼閱讀。
1.5 pageObject — 業務層
pageObject 模式,介面形式封裝每一個頁面需要用到的元素,實現上只要做兩步:確定元素的定位方式;呼叫 driver 中對應的操作介面。driver 的介面實現包含了一定的容錯能力,但並不是全面的,有些頁面獨特性或者元件的獨特性單純呼叫 driver 的介面並不能保證測試用例的穩定性,此時就需要在 pageObject 的介面實現中加入一些容錯演算法,確保用例穩定性。 實際操作的經驗是 pageObject 對元素封裝的顆粒度越小,在用例設計層設計測試用例就越靈活,可以像組裝工具那樣組裝出一個新的測試用例。參考程式碼:
1.6 service — 提供業務功能
一個業務流程很多時候依賴其他業務模組功能,為了方便設計一個測試用例,也為了避免重複造輪子,service 層就提供了一些常用的業務功能,比如登入、建立商品等。依賴方只需要在 service 層呼叫即可。
2、功能優化
Bee 對 selenide&selenium 做二次封裝的同時也對介面做了些優化,框架的初衷是使設計一個 UI 用例儘可能的易設計、易讀、易維護。
2.1 介面優化
直接呼叫 selenide 或者 selenium 的介面經常會遇到些令人頭疼的問題,比如網路問題使頁面 loading 太慢,需要操作的元素還沒展示出來,這種情況就會經常報元素找不到的 error,用例執行失敗,但實際上這種報錯不是一個 bug,測試結果是無效的。為了提高誤報率 driver 層介面實現了等待元素載入的功能,使用的關鍵介面:Selenide.$(elementLocator).waitUntil(Condition.appears, timeout)。參考程式碼:
`/**
* 檢查元素載入情況
* @param elementLocator
* @param timeout
* @return
*/
private boolean checkElementLoad(By elementLocator, long timeout){
try {
Selenide.$(elementLocator).waitUntil(Condition.appears, timeout);
return true;
}catch (Exception ex){
throw new RuntimeException(ex);
}
}
/**
* 如果沒有找到元素拋null異常
* @param element
* @param timeout
* @param elementType
* @return
*/
private By isElementLoaded(String element, long timeout,String ...elementType){
By elementLocator = null;
int count = 4;
long partTimeout = timeout/count;
for(int i=0; i<count; i++) {
elementLocator = waitingForElementLoad(element, partTimeout, elementType);
if(null != elementLocator){
break;
}else if (null == elementLocator && (count-1) == i) {//元素為null丟擲異常
log.error("Web頁面元素:{} 無法定位",element);
}
}
return elementLocator;
}`
複製程式碼
概述中提到過 selenide 本身就是對 selenium 的一個二次封裝,所以 selenide 對元素的操作會比 selenium 平滑的多。在頁面載入方面 selenide 本身有做優化,再在 click、input 等操作介面中加入 waitUntil 的判斷可最大限度的等待一個元素的載入從而提高測試用例的穩定性。
2.2 元素定位統一入口
接觸過 UI 自動化用例設計的同學會比較清楚,想通過 selenide&selenium 操作一個元素,其中必不可少的就是對元素定位的描述,通俗的講就是要通知介面在當前頁面操作哪個位置上的元素。定位一個元素的方法很多,常用的有 id,name,css,xpath 等,對應不同的定位方法 selenide&selenium 在處理上也給出了不同介面。這從維護角度上來考慮顯然不是最好的。最好的做法就是用例設計者只管元素定位和操作事件的呼叫,而事件實現上走了哪種渠道最好是無感知,無需維護的。對此框架封裝了一個方法供 driver 呼叫,主要功能就是解析描述元素的字串自動判斷是 id、css 還是 xpath。
2.3 失敗測試用例重試
網路原因等不確定因素會導致測試用例失敗,這種外部因素導致的失敗一般都會認為它是無效的,為了提高測試報告的可信度,增加了失敗用例重試的機制。具體做法是實現一個用例測試結果的監聽器,當監聽器監聽到一個 fail 的結果,會觸發重試,失敗用例最多重試 3 次。
3、元素定位
UI自動化用例其實可以分成兩部分,1. 定位元素;2. 呼叫介面操作該元素。其中定位一個元素的方法很多,常用的有 id,name,css,xpath。實際設計中選擇哪種定位方法一般會在維護角度上考慮的會多一些,因為現在的伺服器效能配置等都很優秀,所以跑一個WEB-UI用例可以不用考慮效能問題。從維護成本上考慮會優先選擇 id、name,其次 css,最後用 xpath。
我們並不能保證每一個 web 系統的所有元素都能給你提供一個唯一 id 或者唯一的 name,當然如果能和前端開發達成合作這就是一件很美好的事情了,一般情況下我們都需要面對沒有 id 和 name 這兩個屬性的情況。這時我們就可以使用 css 樣式,很多時候 css 樣式是能滿足我們的定位需求。當然在這些都不提供給我們的情況下就只能選擇 xpath,使用 xpath 的優點 1. 易獲取,主流瀏覽器只要開啟“檢視”就可以通過 copy 輕鬆獲取到;2. 頁面上的元素都可以用 xpath 來描述;缺點,不穩定,大量使用的話會給用例維護產生很大的負擔。 xpath 一般只要前端在頁面上做一下小調整用例就必須重新維護,在不得不使用 xpath 的情況下為了減少今後的維護量可對 xpath 做一些優化,可以減少 xpath 的路徑長度提高穩定性。以下是實踐過程中最長用到的幾種型別:
- 依靠自己的屬性文字定位,如 //input[@value=‘XXXXX’]
- 包含指示性字元,如 //input[contains(text(),’指示性字元’)]
- 巧妙使用 descendant,如 //*[@id=‘app-container']/descendant::input
CI整合
用例設計完成之後就可以加入整合建設,讓UI自動化用例在整合環境中發揮作用。測試報告展示使用 reportng。jenkins 的外掛可以很好的把 report 呈現出來,所以 reportng + jenkins 是一個很不錯的組合。
搭建的步驟:- 搭建一個 jenkins。
- 一臺用於跑 UI 自動化用例的伺服器。
- 將伺服器配置成 jenkins 的一個節點。
- jenkins 建立 job,job 中需要使用的外掛包含 Git、Excute shell、Editable Email Notification、Publish HTML reports。其中 editable email notification,支援郵件提醒,是個很不錯的外掛。支援 html report 格式,附件功能。
常見報錯
使用 Bee 過程中經常會遇到些問題,這裡做下總結方便 debug。
- 某些頁面不滾動。有時候一屏展示不了所有的元素。理論上 selenide 或者 selenium 在一個頁面中查詢一個元素是可以自動執行滾屏,但有些時候滾屏會失效,此時就需要在測試用例中實現滾屏查詢元素。 解決方法:void scrollToElement(String element,String …elementType)
- 有些輸入框不能被 input 介面正常操作。實踐過程中在日曆控制元件中遇到過,元素定位什麼的都對,但就是不能正常被操作。 解決方法:void triggerInput(String element,String …elementType),該介面起到一個觸發的作用,實際操作中遇到類似的情況可以把它當做一種嘗試。
- 按鈕不能被 click 介面正常操作。button 元素定位完全正確。且在“檢查”視窗中看到的也是 button 屬性。
<button type="button" class="zent-btn-primary zent-btn-large zent-btn">確定</button>
解決方法:呼叫介面 void clickByText(String text) - 發現 selenide 或者 selenium 的某些介面不能 work 了,此時最大的可能就是瀏覽器升級了。 解決方法:升級瀏覽器驅動
- 元素不可見。有一種元素能在頁面上正常展示但對於工具來說它是不可見的,這是因為在一般情況下元素可見需要滿足以下幾個條件:visibility!=hidden ; display!=none; opacity!=0; height、width都大於0;對於 input 標籤,沒有 hidden 屬性。如截圖就是 opacity=0 的例項。
解決方法:呼叫介面 void clickByJs(String element,String ... elementType)
結束語
Bee 是在開源工具的基礎上做了些優化,目前為止 Bee 更多的是在 driver 層做了些努力,資料層、業務層以及用例層的解決方案還有很大的提升空間。實現一個 WEB-UI 自動化用例主流的方法有錄製和程式碼實現這兩種,其實兩種方法各有優劣。
Bee 還不完美,後期還需繼續努力。感謝一直以來支援 Bee 開發的小夥伴,有你有贊,有你有 Bee。