Rails, RSpec でブラウザテストというと System Spec, Feature Spec などが挙げられます。バックエンド、フロンテエンド両方を End-to-End でテストするには便利ですが、複雑さ故に、たまに落ちるテストになってしまったり、落ちたときのデバッグが大変という難しさがあります。
Qiita でも主要な機能 (エディタ、記事ページなど) をテストするのに使っているのですが、CI で時折よくわからない理由で落ち、 更に何故落ちたかの再現や調査が難しい というのが結構つらいポイントでした
今回 Qiita で RSpec で利用するブラウザ自動化ツールを Selenium から Playwright を使うように置き換えたら、かなりデバッグが行いやすくなりました
移行自体も Cabybara 用の Driver を使うことで、大きく書き換えることなく移行することが出来ました。
Rails のブラウザテストでの Playwright はどう使えるか、移行してどうだったかまとめます。
Playwright の Trace Viewer がデバッグに非常に便利
Playwright は、ブラウザの自動化を行うためのツール兼 JS 用テストフレームワークであり、 Selenium と比較して後発のツールで、Microsoftによって開発されています。
後発のツールだけあって、様々な機能が豊富なのですが、個人的に着目したのは、 Trace Viewer という機能です。
Playwright ではテスト中のブラウザの操作などをファイルに記録し、 Trace Viewer からその記録を再生することが出来ます。
時系列ごとの操作内容やスクリーンショットはもちろん、ネットワークのリクエストやレスポンス、コンソールログなど、かなり多くの情報を確認することが可能です。
ネットワークリクエストごとの Request, Response もそれぞれ確認できるので、 途中で API リクエストがちゃんと行えてるか、なども確認ができます。
Capybara Driver で Rails, RSpec 上で Playwright を動かすのも簡単
Selenium から別のツールに移行するにあたって、懸念となるのは移行の手間です。が、Playwright 向けに Ruby クライアントと Capybara driver が開発されており、従来の System Spec, Feature Spec の書き方からほぼ変わらずにテストを書くことが出来ます。
Rails 7.1 で Playwright 用の設定が組み込まれた
また、 Rails 7.1 では System Test で、Playwright 用の設定が組み込まれるようになり、導入のハードルが非常に低くなりました。
必要な Gem (capybara-playwright-driver) を追加した上で、 以下のような設定を書くだけで Playwright を使うことが可能です。
RSpec.describe "Todos", type: :system do
before do
driven_by(:playwright)
end
# ...
end
すでに Rails で利用する設定のサンプルも書いてくださっている例もあります。
※ Rails 7.1 にまだ出来ていなくても、以下の設定を書けば導入することが可能です。
Qiita においての Playwright の導入はどうだったか
実際に、今回 Qiita のブラウザテストを Playwright を使うように設定しました。
Capybara driver を使うことで、Qiita では大体のテストは書き換えを行わずに移行を行えました。
一部 Selenium 用の Capybara driver と Playwright 用の Capybara driver で挙動が異なるケースはありましたが、数自体は多くなかったのと、その過程のデバッグも、 Trace Viewer を使うことで、
- 時間ごとの画面の変化を見れるので、どう失敗したかの流れを追えたり、様々なイベントとの時系列的な関連も検証できる
- Console の出力などから JavaScript コード内のエラーやログを確認出来る
- ブラウザからのネットワークリクエストを見れるので、API 呼び出しがうまくいっているかのチェックが行える
など、デバッグも非常にやりやすかったです。
導入にあたって、いくつか気づいたポイントもあったのでその辺をまとめていきます。
テストが落ちたときに自動で Trace を保存したら捗った
今回の導入に当たって、失敗したテストは自動で Trace を保存するように Hook を書きました。
before do |example|
Capybara.current_session.driver.start_tracing(title: example.metadata[:full_description], screenshots: true, snapshots: true)
end
after do |example|
if failed?(example)
save_dir = Capybara.save_path.presence || "tmp/traces"
Capybara.current_session.driver.stop_tracing(path: File.join(save_dir, "#{example.metadata[:full_description]}.zip"))
else
Capybara.current_session.driver.stop_tracing
end
end
def failed?(example)
failure_notifier = ::RSpec::Support.failure_notifier
if defined?(::RSpec::Expectations::FailureAggregator) && failure_notifier.is_a?(::RSpec::Expectations::FailureAggregator)
# rspec-expectations may aggregate multiple failures into a single exception.
# https://github.com/rspec/rspec-expectations/blob/v3.13.0/lib/rspec/expectations/failure_aggregator.rb#L22-L44
example.exception || failure_notifier.failures.any? || failure_notifier.other_errors.any?
else
example.exception
end
end
こういう Hook を書き、さらに CI の完了時に Artifact として保存するようにして、 失敗したテストのデバッグがしやすくなりました
Capybara 経由だと Action 内容が分かりにくくなる
ちょっと悲しいポイントとして、今回の設定だと Capybara を経由して、 Playwright を扱うことになるので、 直接 Playwright を扱う場合に比べて、 Action の内容が分かりにくくなるという点があります。
直接 Playwright の API を呼び出した場合が、以下のように何やってるかわかりやすい Action になります。
同じ操作を Capybara を介して行った場合、以下のように、 Actions がわかりにくいものになってしまいます。
確かに、Capybara 経由の場合だと少し Action 周りの情報が分かりにくくなるのですが、Console ログやネットワークリクエストのログなどから、十分デバッグに必要な情報を集めることは出来るかな、と思いました。
以下のように、Playwright の API を使うコードにも出来るので、許容範囲かなと思っています。
Capybara.current_session.driver.with_playwright_page do |playwright_page|
playwright_page.get_by_label("Title").fill("buy a coffee")
playwright_page.get_by_role("button", name: 'Create Todo').click
expect(playwright_page.get_by_text("Todo was successfully created.").wait_for).to be_present
expect(playwright_page.get_by_text("buy a coffee").wait_for).to be_present
end
おわりに
Rails でのブラウザテストを Playwright で動かすの、導入も難しくなく、デバッグも行いやすくなるので、とてもおすすめです