PlaywrightをRSpec, Capybara, Seleniumと組み合わせて使い、traceを出力する

環境

確認したのは以下のバージョン

Playwrightを使いたい

なんで?

  • Selenium と Capybara を使うテストで謎のエラーが起きるのがつらい
  • Ruby と組み合わせたいので Cypress など JS 専用のものは避けなければいけない
    • Puppeteer も同じ理由で候補から外れていたんだがいつの間にか YusukeIwaki/puppeteer-ruby: A Ruby port of Puppeteer なんてものができていた
    • ただ Puppeteer チームが Playwright 作ってたんじゃなかったっけ?という理解

個人的にいちばん大きいのは

Fast and reliable end-to-end testing for modern web apps | Playwright より

Resilient • No flaky tests

Auto-wait. Playwright waits for elements to be actionable prior to performing actions. It also has a rich set of introspection events. The combination of the two eliminates the need for artificial timeouts - the primary cause of flaky tests.

Web-first assertions. Playwright assertions are created specifically for the dynamic web. Checks are automatically retried until the necessary conditions are met.

Tracing. Configure test retry strategy, capture execution trace, videos, screenshots to eliminate flakes.

この辺り。特に tracing は嬉しい。今までせいぜい画像や録画しかなかったが、ブラウザ上で起きていることをそのまま後で確認できるのは大きい。

基本的な仕組み

実現しているのは playwright - npm で、ブラウザも playwright 管理のものを使う。

Playwright 公式で対応していない言語は Playwright Client に使いたい言語でラッパーを用意する形になるようだ。

混乱しやすいdriverという用語

  • Playwright driver
    • Playwright server が実際にブラウザを操作するもの
  • Capybara driver
    • Capybara から見たブラウザを操作するライブラリ。実際には RackTest だったり Selenium (という名の WebDriver)だったり Playwright だったりする
  • WebDriver
    • ブラウザを操作する規格 cf. WebDriver

Rubyで使いたい

必要なもの

playwright client/server を実現しているのはあくまで npm のパッケージなので、gem だけでなく npm も必要。

基本的な使い方はこんな感じ。

browser = Playwright.create(
  playwright_cli_executable_path: "./node_modules/.bin/playwright"
).chromium.launch # ここにchromiumが挟まるのイヤだなぁ
page = browser.new_page
page.goto "<URL>"

公式のサンプルは全部ブロックの中に入れてしまっていて扱いにくいが、別に全部ブロックの中に入れなければいけないわけではない。(ブロックを与えなければいけないものもある)

※ 2024-08 時点で確認したところ、npm で playwright をインストールしていない場合は npx コマンド経由で自動的に playwright を呼び出す仕様になっているので、万全の Node.js 環境が動いているなら別途 npm をインストールしなくても問題なく動く。もしコンテナ環境など一部のバイナリしか存在しない場合はうまく動かないので注意が必要。

traceを使いたい

Tracing | Playwright

tracing をコントロールするメソッド群は BrowserContext が持っている

ので、上の基本のコードにちょっと手を加えて

browser = Playwright.create(
  playwright_cli_executable_path: "./node_modules/.bin/playwright"
).chromium.launch
context = browser.new_context # <-
context.tracing.start(*opts)  # <-
page = context.new_page
page.goto "<URL>"

browser と page の間に context が必要になる。オプションは確認しておくこと。どうもデフォルトでは screenshot は生成されないっぽい。

stop するまで記録されるので、テストケースごとに記録する場合はテストスイートの before / after, setup / teardown などにこれらのコードを記述する。

このエントリではのちほど RSpec と組み合わせる例を挙げる。

Capybaraと組み合わせたい

YusukeIwaki/capybara-playwright-driver: Playwright driver for Capybara

capybara driver を登録すると使えるようになる。最小限はこんな感じ。1

Capybara.register_driver(:playwright) do |app|
  Capybara::Playwright::Driver.new(app)
end

あとは

Capybara.default_driver = :playwright

なり

Capybara.current_driver = :playwright

なりセットしたのちの動作が Playwright 経由になる。この辺りの切り替えの話は他の driver を使っている場合と同じ。

参考

Capybara があると既存のテスト資産の移行の際にはだいぶ助かるのだが、実は Capybara DSL が flaky テストの原因という話がある。

※ リンクはいくつもあるが、全部 capybara driver 作者が書いている

RSpecと組み合わせたい

これは Capybara との組み合わせの話もコミになってしまうんだけど、RSpec + Capybara は driver の情報について

RSpec の before / after が Capybara の session と紐づく形で設定されている

capybara の capybara/rspec.rb に以下のような記述がある。

  config.after do
    if self.class.include?(Capybara::DSL)
      Capybara.reset_sessions!
      Capybara.use_default_driver
    end
  end

  config.before do |example|
    if self.class.include?(Capybara::DSL)
      Capybara.current_driver = Capybara.javascript_driver if example.metadata[:js]
      Capybara.current_driver = example.metadata[:driver] if example.metadata[:driver]
    end
  end

何の話かというと、ここでさっきの tracing の話に戻ってくる。

  • Capybara の driver の機能を使いたければ current_session の driver オブジェクトから呼び出す必要があり、特に trace の stop のリミットは after になる
  • around は before の前、after の後に影響するので tracing 関係のコードは around の中には書けない

結論としては以下のようなコード例になる。

Rails のブラウザテストを Playwright で動かすようにしたらデバッグが簡単になって捗った #Ruby - Qiita

before で start_tracing を、after で stop_tracing を呼び出すべし

Capybaraとの組み合わせを意図的に外す場合

上の Capybara の参考先に散々書かれているが、

実は Capybara DSL を利用するとせっかく Playwright を使っていても flaky になり得る。

そこで、意図的に Capybara を外すコードも利用できるようにしておくとよいのではないかと考えた。例えば以下のような感じ。

it "なにかのテスト", js: true, capybara: false do
end

it "なんらかのテスト", js: true, capybara: true do
end

設定はこんな感じ。

config.before do |example|
  # この中だけ playwright 環境とする
  if example.metadata[:js]
    if example.metadata[:capybara]
      Capybara.current_session.driver.start_tracing(*opts)
    else
      @context.tracing.start(*opts)
    end
  end
end

config.after do |example|
  if example.metadata[:js]
    if example.metadata[:capybara]
      Capybara.current_session.driver.stop_tracing
    else
      @context.tracing.stop
    end
  end
end

デフォルトをどっちに倒すかは分量次第だと思うので、既存のテスト資産がある場合には capybara: true がデフォルトになるようにしておくとよいかもしれない。

Seleniumと組み合わせたい

実は自分は Playwright は Selenium を置き換えてしまうんじゃないかと思っていたんだけど、Playwright はスコープをそこまで広げようと思っていないこと、Selenium も進化していることから、そういうことではないらしい。むしろ組み合わせて使うことができる。

Playwright はそもそもクライアントサーバモデルなのだが、Playwright client を Playwright server ではなく Selenium Grid に接続することでこれを実現する。

使い方は簡単。Playwright に対して環境変数

SELENIUM_REMOTE_URL

に接続先の URL をセットするだけ。実際の接続を行うのは npm の方の playwright なので、Ruby のコード内で

ENV["SELENIUM_REMOTE_URL"] = "<Selenium GridのURL>"

をセットすればオーケー。

この場合、ブラウザへの接続は playwright の責任範囲外なので、 playwright install を実行しておく必要はない。(playwright-ruby-client が npm の playwright を呼び出すのは変わらないので、この部分の準備は必要。)

もちろん Selenium Grid 側も相応の準備が必要で、例えば

SeleniumHQ/docker-selenium: Provides a simple way to run Selenium Grid with Chrome, Firefox, and Edge using Docker, making it easier to perform browser automation

を使う場合、環境変数

SE_NODE_ENABLE_CDP
SE_ENABLE_TRACING

辺りが関係してくるのだが、幸い

selenium/standalone-chrome - Docker Image | Docker Hub

などの standalone image ではデフォルトで有効になっているようで、何の追加設定もなしにただ Selenium Grid に繋ぐだけで trace を含めて使うことができた。

※ Selenium の CDP サポートは WebDriver BiDi 実装が進むまでの仮扱いっぽいが、これはブラウザ対応の進み具合にもよると予想できる。CDP を利用するか WebDriver BiDi を利用するかを我々ユーザー側は気にすることなく、今後もそれなりの期間この組み合わせは動作しそう。

  1. Chrome も出てきた当初はこういう設定をしていたなぁ 

More