Flutter UI自動化測試技術方案選型與探索

語言: CN / TW / HK

作者:小匠

Flutter頁面無法直接使用Native測試工具定位元素,給自動化測試帶來很多不便。雖然Google官方推出了Flutter driver 和 Integration test,但是在實際使用中存在以下問題:

  • 不適用於混合棧APP,雖然appium中有相關的driver,但是無法切換環境;
  • 元素定位能力相對薄弱;
  • 依賴於VMService,需要構建Profile或Debug包。

基於以上因素,我們並沒有直接使用Google官方推出的工具,而是選擇基於Native測試工具去擴充套件Flutter頁面的測試能力。本文對Flutter driver 和Integration test的原理和實現進行了分析,同時簡單介紹閒魚在UI自動化測試的嘗試方案。

Flutter driver

最早接觸flutter自動化測試時,先嚐試使用appium框架去驅動APP,當我們使用inspect功能去dump頁面元素時發現很多元素會被合併成一個區域塊,然後點選的時候只能通過xpath定位,想定位到某些具體的元素會比較困難,並且xpath其實是容易改變的,程式碼可維護效能力差。

因為上述原因,我們開始調研Flutter官方提供的測試工具——flutter driver。一開始使用該框架的時候發現它只能適用於純Flutter應用,對於混合棧應用並不適應,但是它底層提供的元素定位能力或許對我們有用,於是我們對它的原始碼進行了剖析,該框架的原理圖1如下所示。

圖1 flutter driver原理圖

整個框架的流程互動比較簡單,測試指令碼在執行時,首先利用FlutterDriver.connect()來連線VMService獲取相關的isolate,之後通過websocket來傳輸操作過程以及資料獲取。其中測試指令碼側的所有操作都是被序列化為json字串通過websocket傳遞給ioslate來轉換為命令在APP側執行,例如我們想要獲取某個元件的文字內容,其最終生成的json結構體如下:

{
"jsonrpc":"2.0",
"id":5,
"method":"ext.flutter.driver",
"params":{
"finderType":"ByValueKey",
"keyValueString":"counter",
"keyValueType":"String",
"command":"get_text",
"isolateId":"isolates/4374098363448227"
    }
}

瞭解上述原理後,就可以通過構造協議格式,在任何語言、測試框架下都能夠去驅動flutter測試,所以我們對這個協議進行了封裝,使用Python進行驅動,這樣可以在使用uiautomator2和facebook-wda的基礎上來測試flutter頁面,以滿足flutter混合棧應用的測試需求。最終的實現程式碼demo如下。

from flutter_driver.finder import FlutterFinder
from flutter_driver.flutter_driver import FlutterDriver
import uiautomator2 as u2

if __name__ == "__main__":
    d = u2.connect()
    driver = FlutterDriver(d)
if pageFlutter is True:  # 如果是flutter,則使用flutter driver進行驅動
        driver.connect("com.it592.flutter_app")
        finder = FlutterFinder.by_value_key("input")
        driver.tap(finder)
        time.sleep(1)
        print(driver.getText(FlutterFinder.by_value_key("counter")))
else:
        d(text="increase").click()

我們嘗試使用該套框架,發現其實flutter driver底層提供的能力相對比較薄弱,並不能完全滿足我們的需求,主要問題如下:

  • 不能批量操作元素,一旦finder定位到的元素超過1個時,就會丟擲異常;
  • 很多時候開發同學不寫key,元素定位也沒那麼方便;
  • 因為flutter沒有inspect工具dump元素,所以只能利用結合原始碼去寫指令碼,程式碼維護成本比較高;
  • 官方已經放棄維護該專案,所以後續估計也不會有新功能支援。

integration_test

前面提到,flutter官方放棄維護Flutter driver,並推出新的測試框架integration_test,那麼這個框架會不會對混合棧應用予以支援呢,事實上試用了之後發現事情並沒有我們想的那麼美妙。在官方文件裡有這麼一句話“該軟體包可在裝置和模擬器上對Flutter程式碼進行自驅動測試”。

integration_test底層的元素操作和定位還是基於flutter_test去驅動的,其優勢主要如下:

  • 測試指令碼可以使用各種Flutter的API;
  • 打包ipa、apk後就能在 Firebase Test Lab等裝置群上執行測試,不需要額外驅動;
  • integration_test的每個頁面之間測試無關聯,可以實現單個頁面級別的測試。

但是由於底層元素定位和Flutter driver的是一致的,所以Flutter driver存在的問題依舊存在,同時還存在其他侷限問題:

  • 測試指令碼打包到APP中,每次修改指令碼都需要重新打包;
  • 對端到端測試不夠友好,需要額外函式來等待資料載入完畢;
  • 不適合全鏈路級別的頁面測試;
  • 可擴充套件性弱。

基於以上問題,不滿足我們的使用需求,所以我們只是做了簡單預研,並沒有深入瞭解和應用。

閒魚UI自動化測試方案

學習Flutter官方推出的相關測試框架之後,我們開始思考閒魚UI自動化到底要怎麼走?是站在官方的肩膀上去造輪子還是複用現有的原生自動化測試能力去擴充套件Flutter測試能力。在綜合考慮投入成本以及測試指令碼的維護難度後,我們選擇使用影象處理技術來擴充原生自動化框架對Flutter頁面的測試能力支援,整個測試方案架構如圖2所示。

圖2 閒魚UI自動化測試方案架構

Flutter的元素不是完全不能被uiautomator2和facebook-wda識別,所以編寫測試指令碼時只需要處理不能被識別的元素即可。對於有name、label以及xpath不易改變的元素定位,我們優先使用原生定位能力進行定位操作,其他元素則直接使用影象處理技術進行定位操作。

在處理無法使用原生能力定位的元素時,我們優先使用ocr文字匹配來進行定位,準確率較高,不容易受解析度的影響,對於純圖片則通過圖片查詢的方式進行定位。對於一些常見的元素控制元件例如商品卡片、價格、icon、頭像等,我們構建一個訓練集,使用影象分類來判斷元素的型別,從而實現常用控制元件的定位。

UI自動化面臨最大的問題就是——隨著版本的迭代,測試指令碼也需要進行不斷迭代。所以在方案選型和指令碼編寫過程中需要考慮到指令碼的健壯性以及可維護性。我們在實際指令碼開發中將頁面元素封裝到單獨的類中,並與測試邏輯分離,從而保證後期元素迭代時只需要修改對應的頁面元素即可,減少維護成本。

圖3 指令碼分層結構

閒魚效能自動化測試的相關UI操作已經使用該方案,在指令碼編寫時,並不需要區分當前頁面是什麼型別。我們的指令碼已經穩定執行500+次,成功率超過98%。

總結

圖4 方案對比

從圖4可以看出,無論是flutter driver還是integration test對混合棧的支援不夠成熟,但是flutter driver可以進行一些擴充套件,對於純Flutter應用而言,採用該方案能夠基本滿足測試需求,而integration test相對沒有那麼成熟,對於混合棧應用的測試,可能還是需要考慮混合棧的場景切換成本,使用一些ocr技術去做一些擴充可能成本更低,收益更大。

致謝

感謝SLM、TMQ提供的很多底層能力支援,讓我們可以專心的做業務。

關注我們,每週 3 篇移動技術實踐&乾貨給你思考!