寫出第一個 pytest + Appium 測試

#pytest #Appium #App 自動化 #QA 實戰

我在這套框架寫的第一支測試是登入。 登入幾乎是每個人的第一支:流程短、結果明確,而且後面大部分測試都得先過這關。這篇就用登入當例子,從最小的 conftest.py 開始,寫出第一個有意義的 assert。 pytest 的最小結構 pytest 有三個你需要先認識的概念: test function (以 test 開頭的函式就是一個測試)、 assert (斷言預期結果)、 fixture (注入測試所需的資源)。 driver 是「需要在測試前建立、測試後關掉」的資源,這正是 fixture 要處理的事。把 driver 的建立與清理放進 conftest.py,所有測試共用,不用每個 test function 自己寫 try/finally: yield 是關鍵:yield 之前是 setup,之後是 teardown。不管測試過或失敗,pytest 都保證會執行 drv.quit(),不會留下殭屍 session。 這裡的 fixture 每個測試都會重開一次 session,先求好懂。Appium session 啟動很貴,實際專案裡我是整輪測試共用一個 session,測試之間的狀態靠 teardown 收拾,這個取捨在 fixture 那篇再細講。 第一個測試 有了 fixture,test function 只要宣告一個跟 fixture 同名的參數,pytest 就會自動注入,這叫 dependency injection : AppiumBy.ACCESSIBILITY ID 對應 Android 的 content desc、iOS 的 accessibilityIdentifier,是跨平台最穩的定位方式。這個測試做了三件事:輸入帳號、點登入、確認首頁標題出現。assert 失敗時,pytest 會明確告訴你是元件找不到,還是 is displayed() 回傳了 False,不會靜默通過。 跑測試: 看到 PASSED,代表 session 建立成功、元件找到、斷言通過。看到 FAILED 也不用沮喪,測試本來就是要在該紅的時候紅。 開發中我不會每次都跑完整設定。我另外包了一支快跑腳本:關掉 HTML 報表、關掉失敗重跑、加上 x 第一個失敗就停。改一行跑一次,回饋越快,越願意常跑。正式驗證再用完整設定跑一次,commit 之前一定要在本機跑過。 為什麼這樣還不夠 第一,locator 散在測試裡。 "username"、"login btn" 直接寫在 test function 裡,App 改一個 accessibility id 就要全文搜尋。解法是 Page Object Model(POM),後面的章節會把它們集中到 Page class。 第二,沒有等待。 find element 找不到就立刻丟 NoSuchElementException,不會等頁面載入或動畫結束。真實 App 有非同步渲染和網路延遲,硬找就會 flaky。解法是 Explicit Wait,用 WebDriverWait 等條件成立,而不是 time.sleep。 這兩個問題在範例裡看不出來,因為範例的 App 永遠不會改版。 我那支登入測試後來長成什麼樣 範例是三行。實際上,登入是我整個專案裡最難維護的區塊之一。 Page Object 越長越大。 登入要處理的東西遠比「輸入帳密、按登入」多:訪客、各種帳號身分、彈窗、登出再登入。這個檔案後來和另一個核心頁面並列專案裡最大的兩個 Page Object。 帳號系統整個換了技術。 某一版起,帳號相關畫面換了一套新的 UI 框架重寫,原本的登入腳本全部要重新驗。這也是我後來診斷 locator 壞掉時,第一步先看近期 commit 的原因。 iOS 的輸入框不是 TextField。 換了框架之後,iOS 上的輸入框類型變成 XCUIElementTypeTextView,照 TextField 去找就找不到。 提示文字互相包含。 「密碼」和「確認密碼」兩個欄位,用提示文字模糊比對時,找「密碼」會同時命中兩個。最後改成用精確的 locator 指定欄位,不靠提示文字。 點了不一定有點到。 新框架的畫面偶爾會丟失觸控,按鈕點下去沒反應,測試卻以為點過了。所以高風險的點擊之後,我一定會驗證「下一個畫面的元素有沒有出現」,沒出現就再點一次。 這個系列後面的篇幅,大多在處理同一件事:App 改版之後,測試還能不能跑。