Appium 搭配 pytest 完整實務:從 fixture 設計到 CI 執行
#Appium #pytest #CI/CD #自動化測試 #GitHub Actions #行動測試
Appium 搭配 pytest 完整實務:從 fixture 設計到 CI 執行 Appium 測試最難的不是讓第一個測試跑起來,而是三個月後維護一個 50 個測試案例的專案時,還能快速加測試、快速找問題、CI 上跑起來不是每次都賭運氣。 這篇整理我在實際行動 App 自動化測試專案裡用 pytest + Appium 累積的做法——包含哪些設計決策是踩過才知道要這樣做的,不只是「複製貼上能跑」的教學。 為什麼選 pytest,不用 unittest? Appium 官方文件的範例大多用 unittest,初學者很容易就跟著走。切換到 pytest 的理由很簡單: fixture 的 scope 控制是關鍵。 unittest 的 setUp 每個 test 都跑一次,沒辦法輕鬆做「整個測試 session 只建立一次 driver」或「同一個 class 共用一個 app session」。pytest 的 function、class、module、session scope 讓這件事可以精確控制。 conftest.py 讓設定不需要 import。 driver 初始化、裝置設定、測試資料——全部放在 conftest,測試檔直接用 fixture 名稱,不用每個檔都 import。 marker 讓測試分類有彈性。 我現在的專案有 30 幾個 marker——功能分類、優先級、平台限制、用戶身份。CI 可以依情境選擇要跑哪一組。 專案結構:不要把所有東西塞進一個 conftest.py 新手第一版通常是這樣: 這樣幾個月後會變成一個沒人敢動的檔案。 我現在用的結構: conftest.py 只做一件事: fixture 的實作拆到各自的檔案,conftest 只負責宣告。這樣不同功能的 fixture 可以獨立修改,不會互相影響。 devices.yml:裝置設定不寫死在 code 裡 很多教學的 capabilities 是寫死在 conftest.py 裡的: 這在真實專案會爆炸:不同環境要用不同裝置、CI 和本機不同、多台裝置並行要不同 port。 解法是用 YAML 設定檔管理裝置: driver fixture 讀這個 YAML,或從 CLI 參數接收裝置資訊: 本機跑特定裝置: CI 跑全部:不帶參數,讀 devices.yml 全部裝置。 Marker 設計:不只是功能分類 初期的 marker 設計通常只有功能分類: 隨著專案成長,我加了幾個對 CI 很實用的分類: 實際執行範例: flaky marker 是我覺得最實用的一個:不穩定的測試先不刪,加上 marker 隔離,避免它影響整體 CI 結果,等時間處理。 自動重試:Appium 測試的標準配置 Appium 測試在真實環境有一個繞不開的問題:偶發失敗。不是 bug,是裝置抖動、網路延遲、UI 渲染時序。 pytest rerunfailures 是這個問題的標準答案: 一次重試,失敗後等 3 秒再跑。 這個設定的背後有取捨:重試讓 CI 穩定,但也可能讓真正的 bug 被掩蓋(跑兩次才失敗,QA 可能以為是環境問題)。 我的做法:CI 上開 reruns=1,但同時監控「重試後才通過」的測試數量。如果某個測試一直需要重試,那就是真的問題,不是環境問題——加進追蹤清單。 Base Page:比你想的要複雜 教學文章的 BasePage 通常只有 wait + click。真實專案的 BasePage 隨著時間會長出很多東西: 截圖整合 :每個重要步驟截圖記錄,失敗時自動截圖上傳。 多種 locator 策略 :用字典管理 locator 類型映射,避免在測試裡寫死 AppiumBy.XPATH: Log 整合 :每個 Page Object 有自己的 logger,不用 print: 我的 base page 現在有 300+ 行,包含圖片比對(OpenCV)、手勢操作(swipe/pinch)、截圖管理。這不是一開始就有的,是跑過幾百個測試案例後慢慢長出來的。 報告系統:HTML 報告 vs ReportPortal 小專案用 pytest html 夠了: 跑完打開 HTML,清楚看到哪些過、哪些失敗、截圖都在。 當測試量增加、多台裝置並行、需要跨 release 比較趨勢時,企業級的選擇是 ReportPortal 。 ReportPortal 是開源的測試報告平台,可以自己架設(Docker Compose 幾行搞定)。優點: 每次 CI 的結果自動累積,可以看「這個測試案例過去 30 次的穩定率」 多台裝置的結果合併到同一個 Launch,不用看 4 個獨立 HTML 可以標記哪些失敗是「known issue」,過濾掉雜訊 整合方式:安裝 pytest reportportal,在 pytest.ini 加設定: rp skip connection errors = True 這行很重要——ReportPortal 掛掉不應該讓測試跟著失敗。 GitHub Actions:self hosted runner 是關鍵 這是很多人卡住的地方:GitHub 的 hosted runner(雲端機器)沒有實體 Android/iOS 裝置,只能跑模擬器。 如果你的測試需要真實裝置,必須用 self hosted runner 。(CI 工具怎麼選、self hosted runner 的取捨,可參考 GitHub Actions vs Jenkins:CI 工具怎麼選。) self hosted runner 實際架設 :在你的 Mac 上跑幾行指令,GitHub repo Settings → Actions → Runners → Add Runner 裡有完整步驟。讓 Mac 作為 runner 在背景持續跑,有 CI 觸發就接任務。 我的設置:一台 MacBook Pro 連 Android 裝置(GP 版),一台 Mac mini 連 iOS 裝置和另一台 Android(CN 版)。CI 根據 APK 類型把 job 分派到不同的 runner。 多台裝置並行:matrix 策略 每台裝置跑一個獨立的 job,同時進行。fail fast: false 讓一台裝置失敗不影響其他台繼續跑。 關鍵是每個 job 要有自己的 Appium server port,不然多個 job 同時在同一台機器上跑會打架: 幾個減少 CI 時間的細節 行動測試的 CI 通常比 web 慢——模擬器啟動、app 安裝、UI 渲染都吃時間。下面是 Appium 專案特有的幾個調整;更通用的 CI 平行化策略,另一篇有完整整理:CI 測試跑太慢?平行化策略與最佳化。 關掉不必要的 log 。第三方 library 的 DEBUG log 會讓 CI 慢很多——Appium client、Selenium、urllib3 每個請求都在記東西: 這個調整讓我的專案 CI 時間減少了大約 15%。 uv 取代 pip 。在 CI 上安裝 Python 依賴,uv pip install 比 pip install 快 5~10 倍。requirements.txt 幾十個套件,從 2 分鐘降到 20 秒。 依賴快取 。把 venv 快取起來,lock file 沒變就跳過安裝: 常見問題 Q:conftest.py 和 fixtures/ 目錄的 fixture,pytest 都找得到嗎? A:需要在 conftest.py 裡用 pytest plugins 明確宣告。每個 fixtures/ 裡的 .py 檔案作為模組名稱(不帶路徑分隔符)加進去,例如 "fixtures.driver"。pytest 掃到 conftest.py 時,會自動載入所有宣告的 plugin。 Q:多台裝置並行的時候,測試資料會衝突嗎? A:取決於測試設計。如果每台裝置用獨立的測試帳號(不共用),通常沒問題。我的做法是依裝置 UDID 映射到不同的測試帳號,透過環境變數注入。同一個帳號被兩個測試同時操作是最常見的衝突來源。 Q: reruns 會不會讓 CI 時間加倍? A:只有失敗的測試才會重試,穩定通過的測試不受影響。如果大部分測試都穩定,整體時間增加很有限。如果每次 CI 都有很多重試,那是要解決測試穩定性問題,不是調整重試次數。 Q:ReportPortal 和 Allure 哪個比較好? A:Allure 設定簡單、報告漂亮,適合小型專案或個人。ReportPortal 需要自己架設伺服器,但多個 launch 的歷史資料、失敗趨勢分析、跨裝置比較是 Allure 做不到的。裝置超過 2 台、測試量超過 100 個時,ReportPortal 的管理優勢才開始明顯。 參考資料 Appium — Official Documentation — Appium 官方文件,含 Session 管理與 Driver 設定 pytest — Fixtures Documentation — pytest fixture 機制完整說明 pytest — conftest.py — conftest.py 跨檔案共享 fixture 指南 Appium — Desired Capabilities — Appium Capabilities 設定參考 Selenium Grid — Parallel Testing — 平行測試架構,可配合 Appium 使用