團隊在雲端 Mac 上執行 iOS 回歸測試時,常會讓 App 依序開啟兩個網頁工作階段:先登入工作空間 A,再切換到工作空間 B。如果 B 讀到了 A 的 Cookie,畫面可能仍能正常載入,直到使用者切換身分才顯露問題。與其只檢查「頁面能否開啟」,不如先把網站資料儲存區的邊界寫成可重複執行的測試。
先釐清要隔離什麼
WKWebView 透過 WKWebViewConfiguration.websiteDataStore 選擇網站資料儲存區。WKWebsiteDataStore.default() 對應持久性資料;每次呼叫 WKWebsiteDataStore.nonPersistent() 都可建立一個暫存儲存區實例。本文先驗證兩個獨立暫存儲存區之間的 Cookie 隔離,不把這項結果當成整個登入流程已經安全的證明。
建立測試前,先找出 App 程式碼中建立 Web 視圖的位置,記錄每個視圖綁定的儲存區。如果產品要求同一身分的多個頁面共用工作階段,就不該誤用彼此獨立的暫存儲存區;如果要求切換身分後重新開始,則須確認新視圖沒有繼續參照舊儲存區。把「哪些頁面共用、哪些頁面分開」列成表格,比事後猜測 Cookie 從何而來更可靠。
WKProcessPool不是這裡的驗收對象。不要只憑程序池是否相同,就斷言網站資料已共用或隔離;應檢查實際綁定的websiteDataStore。
撰寫不依賴外網的儲存區測試
在 App 的單元測試 target 中引入 WebKit 和 XCTest。以下測試只操作 Cookie 儲存區,不發出網路請求,因此不需要測試伺服器,也不受頁面載入速度影響。test.invalid 是範例 Cookie 的網域,不作為服務位址使用。
import WebKit
import XCTest
final class WebsiteDataIsolationTests: XCTestCase {
func testEphemeralStoresDoNotShareCookie() {
let storeA = WKWebsiteDataStore.nonPersistent()
let storeB = WKWebsiteDataStore.nonPersistent()
let done = expectation(description: "Both stores checked")
guard let cookie = HTTPCookie(properties: [
.domain: "test.invalid",
.path: "/",
.name: "isolation_probe",
.value: "only_a"
]) else {
XCTFail("Could not create probe cookie")
return
}
storeA.httpCookieStore.setCookie(cookie) {
storeA.httpCookieStore.getAllCookies { cookiesA in
XCTAssertTrue(cookiesA.contains {
$0.name == "isolation_probe" && $0.value == "only_a"
})
storeB.httpCookieStore.getAllCookies { cookiesB in
XCTAssertFalse(cookiesB.contains {
$0.name == "isolation_probe"
})
done.fulfill()
}
}
}
wait(for: [done], timeout: 5)
}
}
先確認 A 能讀到剛寫入的值,再確認 B 讀不到同名 Cookie。這個順序很重要:如果寫入尚未完成就讀取 B,即使兩處錯誤地共用了資料,測試也可能「通過」。逾時表示回呼鏈未完成,應將其視為失敗並調查原因,而不是無止境地調高逾時時間。
將儲存區接回實際頁面
儲存層測試通過後,再檢查 App 如何建立視圖。必須在初始化 WKWebView 之前設定配置;事後修改另一份配置,不會替已建立的視圖更換儲存區。可以在頁面測試的準備程式碼中明確傳入目標實例:
let sessionStore = WKWebsiteDataStore.nonPersistent()
let configuration = WKWebViewConfiguration()
configuration.websiteDataStore = sessionStore
let webView = WKWebView(frame: .zero, configuration: configuration)
接下來的整合測試應載入受控的測試頁面,讓頁面執行登入、讀取狀態、切換身分和登出,再觀察請求與頁面顯示。儲存區 API 能證明測試 Cookie 在兩個實例之間不可見,卻無法單獨證明伺服器端工作階段已失效,也無法證明 App 沒有在原生層將權杖重新注入頁面。如果測試需要真實請求,應使用團隊自行維護的受控服務,並分開記錄網路故障與工作階段斷言。
持久性儲存區是另一條測試線
不要根據 nonPersistent() 的通過結果推斷登出流程正確。如果 App 使用 default(),登出測試應檢查對應持久性儲存區中的相關網站資料是否依產品規則處理,並重新建立 Web 視圖再測一次。清理前先限定要刪除的資料範圍;不要為了讓測試通過,就不加區分地刪除裝置上其他頁面可能仍需使用的紀錄。
在雲端 Mac 上維持結果可重現
讓同一個測試 target 在開發桌面與自動化工作中執行,並分別保留測試結果。執行前固定專案要求的 Xcode、模擬器系統版本和測試配置;這裡不預設某個系統版本一定適用。每個測試方法都重新建立暫存儲存區,並為測試 Cookie 使用專用名稱,避免不同測試案例互相沿用狀態。
如果出現「單獨執行通過、整套執行失敗」,先查三件事:測試是否不知不覺改用了 default();兩個 Web 視圖是否重用了同一個儲存區實例;非同步寫入、頁面導覽與斷言是否依完成回呼排序。不要先靠增加等待秒數掩蓋問題。需要回溯時,保存失敗案例名稱、使用的 Xcode 與模擬器版本,以及測試結果檔案;不要在日誌中輸出真實的工作階段值。
交付前的檢查項目
| 檢查項目 | 通過標準 |
|---|---|
| 儲存區歸屬 | 每個 Web 視圖的 websiteDataStore 與預期工作階段一致 |
| 暫存隔離 | A 寫入的測試 Cookie 在獨立的 B 中不可見 |
| 回呼順序 | 寫入完成後再讀取 A,最後讀取 B |
| 登出驗證 | 持久性資料與伺服器端工作階段分別依產品規則檢查 |
| 失敗證據 | 記錄環境與斷言,不記錄真實憑證 |
這套測試最適合作為邊界測試:先證明本機資料沒有在暫存儲存區之間流動,再用頁面層級的案例驗證身分切換與登出。分開記錄這兩層結論,失敗時才知道該查儲存區綁定、非同步時序,還是 App 本身的工作階段邏輯。
常見問題
暫存 WKWebsiteDataStore 會清除持久 Cookie 嗎?
不會。nonPersistent() 不會清理 default();登出流程必須針對實際使用的持久資料庫另行驗證。
分開 WKProcessPool 就能隔離 Cookie 嗎?
不能僅憑行程池判斷。請檢查各個網頁檢視綁定的 WKWebsiteDataStore,必要時再測試實際網頁請求。
需要一台可遠端連線的獨享實體 Mac?
比較 M4 Start、M4 Work 與 M4 Pro 的規格、節點和計費週期,選擇最符合需求的方案。