「當生活看似輕鬆大道時,危險正敲你的門」——Grateful Dead,《Uncle John’s Band》
(關於選擇這句歌詞的說明:我請Claude幫我找一段符合主題的Grateful Dead歌詞,但搜尋「dead lyrics」會觸發內容過濾政策,導致API錯誤。最後只好自己挑選這句。)
我獨自開發Zabriskie,沒有團隊、沒有投資者,只有我一人在臥室裡打造一款社群應用,因為我認為網路需要更好的聚合場所。關於打造「產品」的第一課是:如果它不在App Store,就不存在。早期用戶喜歡網頁版,但不會每天使用,因為它不是「應用程式」,彷彿不存在一樣。因此我必須同時發佈三個平台版本——網頁版用於快速迭代與測試,iOS與Android則是人們真正使用的地方。
問題是我只有一個人,無法維護三套獨立程式碼。解決方案是使用Capacitor:它將我已經建立的React網頁應用包裝成原生外殼——Android用WebView,iOS用WKWebView——讓相同程式碼可在各平台執行。搭配後端驅動的UI架構(後端以JSON傳送畫面佈局,客戶端負責渲染),我能在不需等待App Store審核的情況下推送更新。單一程式碼庫,三個平台,一位開發者,這是唯一可行的方式。
但Capacitor讓你陷入測試的無人地帶。Playwright無法進入原生外殼——它不再是瀏覽器分頁,而是應用程式。原生測試框架如XCTest和Espresso無法操作內容——因為內容是WebView內的HTML,而非原生UI元件。你對網頁工具來說太原生,對原生工具又太網頁。本文中所有測試方法都是因為這個落差而生。
Zabriskie在三平台運行。網頁版由Playwright測試,擁有150多個端對端測試,每次推送都執行。但行動應用完全沒有自動化QA、視覺回歸檢查,也無法確定客戶端是否正確渲染,除非人工逐頁點擊。我決定教Claude操作兩個行動平台,截圖、分析問題,並自動提交錯誤報告。
Android花了90分鐘,iOS則超過六小時。這差異說明了2026年行動自動化工具的現況。
第一個挑戰是連線問題。在Android模擬器中,localhost指向模擬器本身,而非主機Mac。當Capacitor應用嘗試連接localhost:3000或8080時,無法取得資料。解決方法是使用adb reverse,但每次模擬器重啟都要重新執行。
真正的突破是發現Capacitor應用運行於Android WebView,而WebView提供Chrome DevTools Protocol(CDP)連接埠。你可以找到它,轉發到本地端口,從而獲得完整的程式化控制權:
透過CDP,認證只需一條WebSocket訊息——將JWT注入localStorage並導向feed頁面。導航也是一條訊息——設定window.location.href。無需猜座標、無需UI互動、無需與鍵盤或對話框搏鬥。這是Playwright和Puppeteer使用的相同協議,只是連接的是Android WebView,而非桌面瀏覽器。
結合adb shell screencap截圖,我寫了一個Python腳本,約90秒內掃描應用的25個畫面。包括登陸、登入、四個feed、貼文詳情、個人檔案、演出中心、內容創建表單、目錄、對戰、錯誤論壇、日記、徽章、巡迴團隊等。每張截圖都會被分析視覺問題:版面破損、錯誤訊息、缺圖、空白畫面、狀態列重疊。
當掃描發現問題時,會以zabriskie_bot身份認證,上傳截圖至S3,並在正式論壇提交格式化的錯誤報告。標題格式為[Android QA] Shows Hub: RSVP按鈕覆蓋場地文字——清楚標示來自自動化及受影響畫面。它也知道預期狀態:非成員看到「Forbidden」不是錯誤,空白頭像圈不是錯誤,個人設定中「Preview」文字是已知的美觀問題。
整個流程每天早上8:47定時執行。第一次完整掃描結果良好:25個畫面,0個嚴重問題,2個輕微美觀提醒。若有人隔夜改動破壞畫面,錯誤報告會在大家喝咖啡前送出。
我以為iOS會簡單些,同一款應用、同樣畫面,模擬器就在Mac上。但接下來是我遇過最荒謬的除錯過程之一——不是因為技術複雜,而是iOS模擬器充滿了許多小限制,單獨看都合理,但合起來卻是惡夢。
第一個想法是用深層連結處理器,產生JWT,透過simctl openurl開啟URL,跳過登入表單。嘗試四次,四種不同失敗模式——原生包過期、設定指向正式環境、JWT密鑰錯誤、Vite開發伺服器監聽IPv6但模擬器嘗試IPv4。完全無法登入。
貼上也不行。Cmd+V被模擬器攔截。用simctl pbcopy設定iOS剪貼簿會出現亂碼。macOS剪貼簿與iOS剪貼簿是兩套系統。
解決方法是修改後端登入處理:將WHERE email = $1改為WHERE email = $1 OR username = $1,將表單輸入類型從type="email"改為type="text",並建立一個已知密碼的測試用戶。現在我可以輸入「qatest」而不需@符號。這是為了繞過鍵盤限制的後端修改。
登入後,iOS會顯示「是否允許發送通知」的對話框,由UIKit渲染,而非WebView。原生iOS對話框無法被任何macOS模擬輸入方式關閉。
我嘗試用AppleScript點擊100多個座標格子、用cliclick點擊所有可能座標、Python Quartz CGEvent滑鼠事件、按Return和Enter、在輔助功能樹中尋找按鈕(無法取得)、simctl privacy grant(iOS 26不支援通知)、simctl ui alert accept(不存在)。
對話框依然存在,阻擋應用操作。
正確流程是:卸載應用、寫入TCC權限、重啟SpringBoard、重新安裝應用、啟動、登入。只有這樣對話框才不會出現。
應用有一個浮動導覽列,右上角有三個泡泡按鈕——Z標誌、頭像和+號,分別打開垂直下拉選單。為了測試25個畫面,我需要點擊特定下拉選項。我有CSS提供的座標,數學計算也正確,但每種方法都有不同失敗模式。
AppleScript點擊使用macOS視窗座標,需要考慮視窗位置、裝置螢幕群組偏移、模擬器縮放模式(點精確、像素精確、適合螢幕)及工具列是否顯示。第一次掃描準確率42%。
Facebook的idb以裝置邏輯點(390x844)發送點擊,不需轉換。對主導覽按鈕較好,但下拉選項座標略有偏差——點擊會關閉下拉選單或穿透z-index點擊到背後內容。第二次掃描準確率57%。
突破是使用ios-simulator-mcp工具的ui_describe_point功能。輸入任意座標,它會回傳輔助功能標籤、角色與框架。
我以48pt間隔探測每個下拉選項。Y座標正確,但X錯誤——+號下拉選項在x=258,而非x=269。11點誤差導致點擊都落錯欄位。確認座標並等待1.5秒下拉動畫後,掃描達到100%成功率。
成功組合是:先用ui_describe_point探索,再用idb ui tap執行。先量測UI,再點擊。不要猜座標,要測量。
對比鮮明。Android認證流程:
iOS認證流程:卸載應用、寫入TCC資料庫、重啟SpringBoard、重新安裝應用、啟動、等待5秒、點擊登入按鈕特定座標、等待、點擊Email欄位、用AppleScript輸入「qatest」、按Tab、輸入「qatest123」、按Return、等待、祈禱。
Apple的WKWebView不支援Chrome DevTools Protocol。Safari Web Inspector使用專有二進位協議,只有Safari能解析。ios-webkit-debug-proxy只支援真實USB裝置。safaridriver連接macOS Safari,不是模擬器WebView。
Android給你WebSocket,說「這是瀏覽器,隨你操作」。iOS給你一扇鎖著的門和一張紙條寫著「請用Xcode」。
在Android成功後,iOS完成前發生了一件事,顯示另一種失敗——不是平台限制,而是代理紀律問題。
Railway部署因Go版本不符失敗。我的本地Go自動升級到1.26,導致go.mod要求1.25,而Dockerfile仍用golang:1.24-alpine。兩個檔案修正。
Claude在git工作樹中操作——一個乾淨、隔離的repo副本,適合這種精準修改。但它卻cd到主repo,裡面有十多個無關改動。它將所有未提交檔案加入暫存區,一併提交Go版本修正,推送並開PR。PR包含QA登入端點、錯誤論壇更新、iOS模擬器解決方案、E2E測試設定變更、推播程式碼及三個新技能檔案,與Go版本無關。
PR被自動合併,還沒來得及關閉。
錯誤合併導致測試套件中重複宣告變數、函式。意外包含的改動是將表單placeholder從"Email"改為"Email or Username",破壞所有使用page.fill('input[placeholder="Email"]')的認證E2E測試。目錄測試斷言itemCount > 50只在本地資料庫通過,CI資料有限。
為了修正兩個檔案改動,我做了四次後續提交,分三個PR。前兩次沒先跑測試,結果失敗。第三次先跑測試,通過。三輪「推送祈禱」後,才做了應該是第一步的事:先跑測試、讀輸出、修正錯誤、驗證再推送。這是我每次除錯都堅持的規則——先看日誌,再推理,但自己卻忽略了。
現在兩平台都有運作的QA技能。每天早上,Android模擬器與iOS模擬器啟動,掃描25個畫面,分析截圖,並自動提交錯誤報告。三平台皆測試且自動回報。
經驗不斷相互印證:
CDP勝過點擊。若能用瀏覽器自身的除錯協議,別與座標系統搏鬥。Android免費提供,iOS沒有,所有替代方案都增加脆弱性。
量測,不要猜測。讓輔助功能API幫助定位,是與先看日誌同理的原則。別假設知道按鈕在哪,問系統。
待在工作樹。隔離只有在尊重邊界時有效。稍微「看一下」就可能一不小心提交十多個無關檔案。
推送前先跑測試。三輪推送祈禱後才做該做的事。知道規則與遵守規則的差距,是浪費的提交數。
蘋果,如果你們看到這篇文章:請為模擬器WebView開放CDP或WebDriver。開發工具對人類很好用,但AI嘗試時幾乎無用。