Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

Rails: システムテストのドライバをSeleniumからPlaywrightに差し替えたら安定した(翻訳)

概要

元サイトの許諾を得て翻訳・公開いたします。

日本語タイトルは内容に即したものにしました。

microsoft/playwright - GitHub

  • 追記(2024/11/25): 以下の記事もどうぞ。

Rails: VS CodeのdevcontainerとGitHub ActionsでPlaywrightを使う

Rails: システムテストのドライバをSeleniumからPlaywrightに差し替えたら安定した(翻訳)

先週、DHHが「システムテストの導入は失敗だった」と宣言したとき、私の最初の心の声は「まあそうだろうね」でした。UIテストはもろいうえに、テスト件数がある程度以上になると、アプリが動作しているという確信をもたらしてくれるUIテストのありがたみよりも、UIテストをメンテナンスするコストの方が上回ってしまう可能性があります。

しかし、私の次なる心の声は「考えてみれば、Turboに強く依存した複雑なUIに対して書いたスモークテストは、何かと失敗だらけだった」でした。TurboがDOMの大半を完全に置き換えたことで、システムテストでタイミングの問題が多数発生していたようでした。Capybara(これはSeleniumの下で動作します)が見つけた要素がたちまち陳腐化することが頻繁に起きているようです。

そして私の最後の心の声は「しょうもないSeleniumに14年以上も付き合わされて、もううんざりだ。テストを書き直さずにSeleniumを消し去れないものだろうか?」そういうわけで、ずっと堅牢そうなPlaywright用のCapybaraアダプタを探し始めました。

そして(こうしてわざわざ記事を書いていることからおわかりかと思いますが)、そういうアダプタが見つかったのです。しかもちゃんと動きました!おかげでだいぶマシな状況になりました。

そういうわけで、Railsのシステムテストで使われるSeleniumをPlaywrightに差し替える方法の完全なガイドを以下に紹介します。

🔗 ステップ1: インストール

YusukeIwaki/capybara-playwright-driver - GitHub

Gemfileからselenium-webdriver gemを削除して、代わりにcapybara-playwright-driver gemをその場所に追加します。

 group :test do
   gem "capybara"
   gem "capybara-playwright-driver"
 end

YusukeIwaki/playwright-ruby-client - GitHub

これによって、playwright-ruby-clientもインストールされます。これはplaywrightの特定のnpmパッケージと互換性があるので、正しいバージョンがインストールされていることを確認しておく必要があります。幸い、PlaywrightのRubyクライアントにあるPlaywright::COMPATIBLE_PLAYWRIGHT_VERSION定数を使えば、互換性のあるバージョンが使われるようになります。

さらに、playwright installコマンドを実行すれば、テスト用のブラウザ(デフォルトではchromium、webkit、firefox)をプラットフォーム依存のキャッシュディレクトリにダウンロードします。PlaywrightのRubyクライアントは、これらのブラウザを自動的に検出します。

以上を自動化するために、自分のプロジェクトのscript/setupに置いたシェルスクリプトの、bundle installyarn installの直後に以下を追加しました。これで、プロジェクトをセットアップするときに誰でも必要に応じてPlaywrightをインストール・更新できるようになります。

export PLAYWRIGHT_CLI_VERSION=$(bundle exec ruby -e 'require "playwright"; puts Playwright::COMPATIBLE_PLAYWRIGHT_VERSION.strip')
yarn add -D "playwright@$PLAYWRIGHT_CLI_VERSION"
yarn run playwright install

上のコマンドは2回以上実行しても安全です。playwrightが最新で、かつブラウザがキャッシュ済みであれば、0.5秒未満で完了します。

🔗 ステップ2: テストのセットアップ

うすうすお気づきかと思いますが、Playwrightの設定は、seleniumを完全に消し去ることから始まります。
おそらくtest/application_system_test_case.rbファイルは以下のようになっているはずです。

require "test_helper"

class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
  # 以下と、Seleniumに関連する他のすべてを削除する
  driven_by :selenium, using: :chrome, screen_size: [1400, 1400]
end

お望みであれば、ここをdriven_by :playwrightに書き換えるだけで十分(?)かもしれませんが、私の場合は、どのブラウザでテストを実行するか、およびヘッドレスブラウザにするかどうかをENVフラグで制御したいので、私のtest/application_system_test_case.rbファイルは以下のような感じにしました。

require "test_helper"

Capybara.register_driver :my_playwright do |app|
  Capybara::Playwright::Driver.new(app,
    browser_type: ENV["PLAYWRIGHT_BROWSER"]&.to_sym || :chromium,
    headless: (false unless ENV["CI"] || ENV["PLAYWRIGHT_HEADLESS"]))
end

class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
  driven_by :my_playwright
end

見ておわかりのように、この設定ではChromiumとUIを用いてテストを実行します。PLAYWRIGHT_BROWSER環境変数を"webkit""firefox"に変更すれば、起動するブラウザを変更できます。また、(どのCIサービスでもやっているように)ドライバをヘッドレスで実行するかどうかもCI環境変数やPLAYWRIGHT_HEADLESS環境変数で指定できます。

🔗 ステップ3: CIをセットアップする

私が使っているCIはGitHub Actionsで、以下が確実に行われるようにしたいと思いました。

  1. Playwrightが正しくインストールされること
  2. ビルドで使ったブラウザだけがインストールされること
    (自分の場合はChromium)
  3. 以後の実行でもブラウザがキャッシュされること

これを実現するために、CIのワークフローYAMLのyarn install行の直後に、以下の3つのステップを追加しました。

- name: Cache Playwright Chromium browser
  id: playwright-cache
  uses: actions/cache@v4
  with:
    path: ~/.cache/ms-playwright
    key: playwright-browsers-${{ runner.os }}-${{ hashFiles('yarn.lock') }}

- name: Install Playwright Chromium browser (with deps)
  if: steps.playwright-cache.outputs.cache-hit != 'true'
  run: yarn run playwright install --with-deps chromium

- name: Install Playwright Chromium browser deps
  if: steps.playwright-cache.outputs.cache-hit == 'true'
  run: yarn run playwright install-deps chromium

ご想像のとおり、キャッシュにヒットすればChromiumのインストールは省略されますが、Playwrightで必要なサポートツールを利用可能にするためのplaywright install-depsコマンドの実行は、引き続き必要になります。

このセットアップでは、アクション実行のたびにセットアップのオーバーヘッドが20秒ほど追加されます。素晴らしいとは言えませんが、我慢できる範囲です。

🔗 ステップ4: テストを修正する

Playwrightに乗り換えたときに、すべてのテストが魔法のようにうまくいくことはあまり期待していなかったのですが、遭遇した問題が非常に少なかった点は実に見事でした。

私が踏んだ問題は、どれもささいなものばかりです。

  • Seleniumでは、XML要素のテキストノードが空行をそのまま返しますが、Playwrightでは空行を削除します。このため、Atomフィードのアサーションを1件調整する必要がありました。
  • Seleniumドライバは、accept_confirmにブロックを渡さずに呼び出して文字列を返すことが許されますが、CapybaraのAPIでは、確認プロンプトを表示するアクションは、必ずaccept_confirmに渡したブロック内で呼び出される必要があります(Playwrightでも同様のことが期待されます)。
  • PlaywrightのCapybaraアダプタは、致命的でない多くのエラーをrescueしてくれますが、修復しようのないエラーのメッセージまでputsで出力してしまいます。そこで大急ぎで残念なヘルパーをこしらえて、それらのエラーの一方を抑制し、コンソール出力が散らからないようにしなければなりませんでした。

これ以外の問題が発生した場合は、アダプタのドキュメントが機能や制約を理解するうえで非常に役立ちます。

🔗 ステップ5: テストの安定性が非常に高まったことに気づいて小躍りする

私はすぐに、テストの安定性が劇的に向上したことに気づきました。全体の実行時間はほぼ同じでしたが、スイート全体を実行したときの失敗率がSeleniumで30%だったのに対し、Playwrightでは5%を下回ったのです。

さらにうれしいことに、同じ呼び出しが2〜3箇所ある場合に失敗の予測が立てられることに気づきました。このおかげで問題の再現やデバッグがずっとやりやすくなりました。問題を引き起こした個別のテストを微調整して解決するまで1時間もかからなくなり、200回連続実行しても1度も失敗しなかったのです。

この「安定感が高まった」感覚は、思った以上に大きなことです。
Selenuimだったときのパフォーマンスは不安定で、同じ問題を根本的に解決しようとしたときは2か月間を費やしてもまるで進捗が出なかったというのに、Playwrightでは同じ問題を午後半日ですべて解決できたのです。従来のとても信用のおけない不安定なビルドとおさらばして、アプリが壊れているかどうかを確実に教えてくれる信頼性の高いビルドに引っ越せたのです。

🔗 さて、これでおしまいでしょうか?

私はまだPlaywright初心者なので、今後きっと他の問題を踏むこともあるでしょう。しかし、サードパーティが実用的なアダプタを実装できるようなドライバAPIの抽象化に成功したCapybaraと、Playwright RubyクライアントとCapybaraドライバを構築してくれたYusuke Iwakiには敬意を表さなければなりません。「初めて試してみたら何もかもがほぼ問題なく動いた」という、オープンソースではめったにない素敵な経験を得られました。

ともあれ、DHHの「システムテスト導入は失敗」宣言を覆すほどではないかもしれませんが、皆さんもSeleniumからPlaywrightに乗り換えてみれば、テスト自体が失敗する頻度が下がるかもしれませんよ。

関連記事

Rails: VS CodeのdevcontainerとGitHub ActionsでPlaywrightを使う

Railsでブラウザテストを「正しく」行う方法(翻訳)


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。