テストシミュレータで漏れた WKWebView ナビゲーションをキャンセル
テスト実行時に実際のネットワークアクセスが発生し、共有 WKProcessPool がブロックされて CI が数分間停止していた問題を、ナビゲーションを即時 cancel することで解消し、テスト全体の実行時間を大幅に短縮しました。
背景
WebViewNavigationSimulator の decidePolicyFor が常に decisionHandler(.allow) を呼び出していたため、WKWebView が外部 URL(例: https://example.com)へ実際に遷移し、共有 WKProcessPool がロックされてテストが 12 分以上停滞していました。この挙動は CI での全テストスイート(208 件)で合計約 1018 秒の実行時間を引き起こしていました。
この問題は外部ネットワークアクセスが不要なテストシナリオでも発生しており、テストコード側でナビゲーションアクションのみ取得すれば十分という設計趣旨と乖離していました。実際に CI が長時間ブロックされていた事実は、PR のベンチマーク表でも「Before」側の数秒から数百秒へと跳ね上がっている点で裏付けられます。
技術的な変更
WebViewNavigationSimulator.decidePolicyFor 内で、decisionHandler に対する呼び出しロジックを分岐させ、キャプチャしたナビゲーションアクションが取得できた時点で decisionHandler(.cancel) を実行するようにしました。これにより実際のページ遷移は発生せず、WKProcessPool のロックも回避されます。
@@
- decisionHandler(.allow)
+ // Capture the navigation action without performing the real navigation.
+ // Canceling prevents external URL loads that would block the shared WKProcessPool.
+ decisionHandler(.cancel)
continuation?.resume(returning: navigationAction)
continuation = nil
同時に、intermediate なナビゲーション(例: loadHTMLString による初期ロード)だけは decisionHandler(.allow) で許可し、ページが正しくロードされるように条件分岐を追加しました。また、テスト終了時に残っているロードを確実に停止させるため、stopLoading() メソッドを実装し、tearDown で呼び出すように変更しました。
@@
func stopLoading() {
- webView.stopLoading()
- webView.navigationDelegate = nil
+ // Ensure no pending navigation remains after a test completes.
+ webView.stopLoading()
+ webView.navigationDelegate = nil
}
@@
- webNavigationSimulator = nil
+ webNavigationSimulator.stopLoading()
+ webNavigationSimulator = nil
@@
- webNavigationSimulator = nil
+ webNavigationSimulator?.stopLoading()
+ webNavigationSimulator = nil
設計判断
実際の外部ナビゲーションを許可する代わりに即座に cancel する方針は、テストが外部環境に依存しないことを保証しつつ、WKProcessPool の競合を防止する最小限の変更と言えます。ナビゲーションをキャンセルしても WKNavigationAction オブジェクトは取得できるため、テストロジック自体は変更不要です。
stopLoading() の導入は、テスト終了後に残っているロードやデリゲート参照を明示的に解放する安全策です。これによりメモリリークや予期しないコールバックが起こるリスクを低減し、テストフレームワークのクリーンアップ手順を一元化しました。
この設計は、API の互換性 を保ちつつ、テスト実行速度を劇的に改善するという二重の目的を達成しています。既存のテストコードは改変不要で、追加したメソッドは内部使用に留められています。
まとめ
WebViewNavigationSimulator が外部 URL への実際の遷移を防ぎ、テストスイート全体の実行時間を約 95% 短縮しました。decisionHandler(.cancel) と stopLoading() の組み合わせにより、共有 WKProcessPool のロックを回避しつつ、テスト環境のクリーンアップも強化されています。これらの変更は既存テストコードへの影響を最小化し、今後のテスト拡張でも同様のアプローチが適用可能です。