工程實務

在雲端 Mac 驗證 WKWebView 雙向橋接契約

在雲端 Mac 驗證 WKWebView 雙向橋接契約

同一套 iOS 頁面在本機執行正常,放進遠端建置工作後,卻偶爾收不到網頁傳來的「已就緒」訊息。先別把這類失敗歸咎於雲端 Mac 的網路:如果頁面與原生程式碼透過 WKWebView 橋接,載入時序、訊息欄位和處理常式的生命週期都可能是原因。將一份本機 HTML 隨儲存庫提交,就能把雙向通訊變成不依賴外部頁面的契約測試。

先固定橋接契約

訂出最小協定:網頁向原生端傳送 {kind: "ready", version: 1};原生端向網頁傳送 {kind: "sync", value: "ok"}。前者透過 window.webkit.messageHandlers.bridge.postMessage,後者透過 evaluateJavaScript。測試應分別證明訊息確實送達、欄位型別正確,而不只是證明頁面能顯示。

將以下內容儲存為測試 target 可讀取的 BridgeFixture.html,並確認它已加入該 target 的資源清單:

<!doctype html>
<html lang="en">
<meta charset="utf-8">
<body>
<script>
window.bridgeReceive = function (message) {
  if (message.kind !== "sync" || typeof message.value !== "string") {
    throw new Error("Invalid native message");
  }
  document.body.dataset.received = message.value;
};
window.webkit.messageHandlers.bridge.postMessage({
  kind: "ready",
  version: 1
});
</script>
</body>
</html>

dataset.received 是測試的觀察點,不是正式環境協定的一部分。正式程式碼與測試頁面應共用同一份欄位定義;變更欄位時同步更新雙方,並讓不相容的變更在測試中明確失敗。不要靠等待固定秒數來猜測頁面何時載入完成。

在模擬器中驗證兩個方向

測試程式碼建立 WKUserContentController,以 bridge 為名稱註冊 WKScriptMessageHandler,再將控制器放入 WKWebViewConfiguration。從測試套件讀取 HTML 後,呼叫 loadHTMLString(_:baseURL:)。收到 ready 時,先確認 message.body 是字典,再檢查 kind 是字串、version 是數字且值為 1;遇到不認識的訊息,斷言應失敗。

原生端傳送訊息前,須等待 WKNavigationDelegate 收到 webView(_:didFinish:)。接著用 JSONSerialization 將待傳送的字典編碼為 JSON 文字,組成 window.bridgeReceive(...) 並呼叫 evaluateJavaScript。不要直接把使用者輸入插入腳本字串:引號和換行會改變腳本的意義。呼叫完成後,再讀取 document.body.dataset.received,斷言結果為 ok。

loadHTMLString 返回只表示開始載入;即使網頁已送出 ready,也不該用這個返回時間點取代導覽完成事件。為兩個方向各設定一個 XCTest expectation,才能分別指出哪一段沒有回應。

載入失敗與腳本執行錯誤也應有明確的失敗處理。為 expectation 設定明確的逾時時間,並在失敗輸出中區分「頁面未完成載入」、「原生端未收到訊息」和「網頁未接受原生端訊息」,否則 CI 只會留下籠統的逾時結果。

將測試接入雲端建置

先在 ArmMesh 雲端 Mac 的控制台確認目前可選的配置,選定節點後固定 Xcode 版本與模擬器執行階段。用 xcrun simctl list devices available 查找可用裝置,將選定模擬器的 UUID 寫入工作配置;不要依賴裝置名稱的模糊比對。將以下命令中的專案、scheme 和 UUID 換成儲存庫中的實際值:

xcodebuild test \
  -project Client.xcodeproj \
  -scheme Client \
  -destination 'platform=iOS Simulator,id=<SIMULATOR_UUID>' \
  -only-testing:ClientTests/BridgeContractTests \
  -resultBundlePath "$PWD/BridgeContract.xcresult"

執行前,確認 scheme 已共享、測試 target 包含 HTML 資源,且結果路徑尚不存在;xcodebuild 不會將既有的結果套件當成空目錄覆寫。失敗時保留 .xcresult 與建置日誌,記錄 Xcode 版本和模擬器 UUID,以便在相同環境中重新執行。

這項測試不需要遠端網頁,因此網路故障不應改變測試結論。另設一項使用實際頁面的整合測試,才能區分「橋接契約失效」與「頁面部署或請求失敗」。

處理時序與清理陷阱

如果頁面在處理常式註冊前執行 postMessage,第一則訊息就可能遺失。因此應先完成配置與註冊,再建立並載入 WebView。不要在測試中重複使用仍帶有舊處理常式的控制器;測試案例結束時呼叫 removeScriptMessageHandler(forName: "bridge"),並釋放 WebView 對導覽委派物件的參照。

也要測試拒絕路徑:讓測試頁面傳送缺少 version 的訊息,以及將 version 寫成字串的訊息,確認原生程式碼不會將其視為有效的 ready。若正式環境協定包含請求編號,回覆必須依編號配對,不能讓上一輪請求的延遲訊息完成本輪 expectation。涉及使用者內容時,測試日誌只記錄欄位結構與去識別化後的錯誤資訊。

最後進行驗收:先單獨執行這項測試,再將它放進完整測試套件;連續執行時不應出現重複回呼,換用另一台相同版本的模擬器也應得到相同的斷言結果。至此,本機測試頁面負責穩定的介面契約,實際頁面測試負責部署後的整合行為,兩類失敗各有明確歸屬。

常見問題

為何不直接使用正式網站作為測試頁面?

網站更新與網路狀態會改變測試輸入。先用本機 HTML 固定契約,再另外驗證正式頁面的整合。

原生程式應在何時呼叫頁面函式?

等 WKNavigationDelegate 收到 didFinish 後再呼叫 evaluateJavaScript;loadHTMLString 傳回不代表頁面已就緒。

獨享 Apple Silicon

將固定規格的雲端 Mac 整合至你的工作流程

可按日、週、月或季租用獨享實體節點,以固定硬體資源執行建置、自動化與 MLX 推論工作。

查看可租用規格