Systemテストのドライバ事前読み込みでブラウザバイナリを明示的に設定

rails/rails

Rails のシステムテストで、Selenium 4.45 で削除された options.binary の自動設定を復元し、Chrome バイナリが見つからないエラーを解消します。

背景

Selenium 4.45 では DriverFinder がオプションオブジェクトを変更して options.binary を設定していた副作用が削除されました。その結果、Rails のシステムテストがドライバパスだけを取得し、ブラウザバイナリが未設定のまま Selenium が起動しようとするため、SessionNotCreatedError: cannot find Chrome binary が発生します。この regression は、Chrome が PATH に無く Selenium Manager のみがブラウザを提供している環境で顕在化します。同様の報告は SeleniumHQ の Issue #17698 でも取り上げられています。Rails では Browser#preload がこの副作用に依存していたため、バージョン更新でテストが失敗する状態になっていました。

Browser#preload の目的は、並列実行時のレースコンディションを防ぐためにドライバパスを事前に解決しキャッシュすることです。従来は DriverFinder.new(...).driver_path の呼び出しにより、内部で options.binary が暗黙的に設定されていました。副作用が除去されたことで、ドライバは取得できてもブラウザパスは残らず、テスト実行が止まっていました。この状況を解消するために、本 PR は resolve_driver_path に明示的なバイナリ設定を追加します。

技術的な変更

resolve_driver_path では Selenium 4.20.0 以降で DriverFinder のインスタンスを生成し、取得した driver_pathbrowser_path をそれぞれ Service.driver_pathoptions.binary に代入するよう変更されました。まず driver_finder = ::Selenium::WebDriver::DriverFinder.new(options, namespace::Service.new) でインスタンス化し、namespace::Service.driver_path = driver_finder.driver_path でドライバパスを設定します。続いて options.binary = driver_finder.browser_path if driver_finder.browser_path? により、ブラウザバイナリが利用可能な場合のみ明示的に設定されます。このロジックは Selenium バージョンに依存せず、Gem::Version の比較で分岐させているため、既存のコードフローを保持しつつ新機能を提供します。

@@
-            namespace::Service.driver_path = ::Selenium::WebDriver::DriverFinder.new(options, namespace::Service.new).driver_path
+            driver_finder = ::Selenium::WebDriver::DriverFinder.new(options, namespace::Service.new)
+            namespace::Service.driver_path = driver_finder.driver_path
+            options.binary = driver_finder.browser_path if driver_finder.browser_path?

テストスイートには preloads browser's driver_path and binary with DriverFinder という新しいケースが追加され、DriverFinder が返す driver_pathbrowser_path が正しく設定されることを検証しています。スタブで Selenium::WebDriver::SeleniumManager.binary_paths{ "driver_path" => "chromedriver", "browser_path" => "chrome" } に置き換え、ActionDispatch::SystemTesting::Driver を生成して実際の Service.driver_pathbrowser.options.binary を取得します。アサーションでは chromedriverService.driver_path に、chrome がブラウザオプションの binary に設定されていることを確認します。既存の driver_path が設定済みの場合は上書きされないことを assert_no_changes で確認し、後方互換性が保たれていることを示しています。

@@
-    test "preloads browser's driver_path with DriverFinder if a path isn't already specified" do
+    test "preloads browser's driver_path and binary with DriverFinder if a path isn't already specified" do
@@
-    assert_equal found_executable, ::Selenium::WebDriver::Chrome::Service.driver_path
+    assert_equal "chromedriver", ::Selenium::WebDriver::Chrome::Service.driver_path
+    assert_equal "chrome", driver.instance_variable_get(:@browser).options.binary

設計判断

本修正の核心は、DriverFinder の副作用に依存せず、返却値を用いて設定を明示的に行う設計に変更した点です。副作用は内部実装の変更に脆弱であり、今回の Selenium 4.45 の変更で問題が顕在化したため、直接代入に置き換えることで安定性が向上します。resolve_driver_path 内のロジックは数行の追加で済むため、呼び出し元や公開インターフェースを変更する必要がなく、利用者への影響は最小限です。また、options.binary が既に設定されているケースでは条件分岐で上書きを防ぐため、既存環境の動作を維持します。

Selenium のバージョンチェック (Gem::Version.new(::Selenium::WebDriver::VERSION) >= Gem::Version.new("4.20.0")) により、古いバージョンでも従来の DriverFinder.path 呼び出しを保持しています。この分岐は既存コードの動作を保ちつつ、新しいバイナリ設定ロジックを安全に導入できるトレードオフです。結果として、現在サポートしている Selenium バージョン範囲内での動作が保証され、テストの追加により変更が意図した通りに機能することが CI で検証されています。

まとめ

Browser#preload が Selenium 4.45 の変更に追随し、ドライバパスとブラウザバイナリの両方を明示的に設定するようになったため、Chrome が PATH に無くてもシステムテストが正常に起動します。副作用依存を排除した設計により、既存のテスト環境への影響は最小限で、互換性が保たれています。

記事メタデータ

Generated by:
gpt-oss-120b for DiffDaily
LLM Trace:
5d090297

この記事はAIによって自動生成されています。内容の正確性については、必ずソースコードやPRを確認してください。

品質レビュー結果

Review Status:
リトライ後承認
Review Count:
2回 (改善を経て承認)
Reviewed by:
gpt-oss-120b for DiffDaily

Review Criteria:

記事構成 ✓ PASS

Title, Context, Technical Detailの存在と明確さ

リード文、背景(Context)、技術的な変更(Technical Detail)、設計判断(任意)、まとめ(Conclusion)がすべて揃い、構成が明確です。

カスタムMarkdown構文 ✓ PASS

シンタックスハイライト・GitHubリンク記法の正確性

ファイル名付きシンタックスハイライトが正しい形式で使用され、PRリンクも # + 数字でリンク化されています。

対象読者への適合性 ✓ PASS

エンジニア向けの適切な技術レベルと表現

Rails のシステムテストに詳しいエンジニア向けの内容で、初心者向けの余計な説明はありません。

パラグラフ・ライティング ✓ PASS

トピックセンテンス・1段落1トピック・段落長

各セクションが総論→各論→結論の流れになっており、段落はトピックセンテンスで始まり 1 段落1トピック、長さも適切です。

Diff内容との照合 ✓ PASS

コードブロックとDiff内容の一致

記事中のコードブロックは PR の Diff に含まれる変更箇所と一致しており、抜粋は重要な変更点を正確に示しています。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

DriverFinder、options.binary、Service.driver_path などの用語が正しく使用されています。

説明の技術的正確性 ✓ PASS

技術的主張の正確性と論理性

技術的な説明は PR の Motivation と Detail に基づき、論理的かつ正確です。

事実の突合 ✓ PASS

PR情報による主張の裏付け(ハルシネーション検出)

PR の情報に基づかない推測や外部知識は含まれておらず、全ての主張に根拠があります。

数値・固有名詞の確認 ✓ PASS

PR番号・コミットID・バージョン等の正確性

PR 番号 #57816 などの数値は正確です。

タイトル・説明との一致 ✓ PASS

記事タイトル・説明とPR内容の一致

記事タイトルは PR タイトルの内容を日本語で正確に表現しています。

外部知識の正確性 ✓ PASS

PRに記載のない外部知識(LTS、サポート状況など)の不使用

LTS やリリース日程等、PR に無い外部情報は記載されていません。

時間表現の正確性 ✓ PASS

時間表現がPR情報と一致しているか

時間表現の歪曲はなく、PR の記述と一致しています。