Capybara で Chrome DevTools Protocol を使って DOM 読み込み完了前に JavaScript を実行する
はじめに
一般的に、Webアプリケーションでは、JavaScript は DOM の読み込み完了を示す DOMContentLoaded
イベント発火後に実行することが多いと思います。
しかし、E2E テストではセットアップとしての JavaScript を DOMContentLoaded
より先に実行したい場面もあります。Capybara では #execute_script
で JavaScript を実行できますが、この場合は DOMContentLoaded
の後に実行されます。
この課題を解決するため、この記事では Chrome DevTools Protocol(CDP)の Page.addScriptToEvaluateOnNewDocument
を使用して、DOMContentLoaded
より先に JavaScript を実行する方法を紹介します。
Chrome DevTools Protocol(CDP)とは
Chrome DevTools Protocol(CDP)は、Google Chrome 等の Chromium ベースのブラウザのデバッグ用のプロトコルです。
CDP を使うことで、Chrome ブラウザの Developer Tools で可能な操作をプログラムで実行できるようになります。
CDP は Playwright や Puppeteer など、数多くのツールで使われています。Ruby 方面では、Capybara のドライバーである Cuprite で、CDP に基づいて動作する Ferrum が使われています。
※ 今回は Rails 7.1 で Capybara のデフォルトのドライバーである Selenium WebDriver を使った方法を紹介します。
Page.addScriptToEvaluateOnNewDocument
とは
Page.addScriptToEvaluateOnNewDocument
は CDP が提供するメソッドの1つで、ブラウザが新しいドキュメントを読み込む前、つまり DOM が構築されるよりも前に、指定した JavaScript を実行できるようになります。
このメソッドを使うことで、DOMContentLoaded
イベントの発火前に任意の JavaScript が実行可能になります。
Capybara で CDP を実行する方法
Selenium WebDriver に CDP を実行するメソッド #execute_cdp
があるため、Capybara のドライバーとして Selenium WebDriver を使うことで、間接的に CDP を実行できるようになります。
なお、ブラウザとして CDP が実行可能なもの(Chrome 等)を使う必要があります。
使用例
次のコードは、Capybara と Selenium WebDriver を組み合わせて Page.addScriptToEvaluateOnNewDocument
を使い、以下のタイミングで console.log
を実行するための JavaScript を追加しています。
-
Page.addScriptToEvaluateOnNewDocument
で追加された JavaScript の実行時 -
DOMContentLoaded
イベント発火時
なお、比較用に、 Capybara.visit
の前後で Capybara.execute_script
で console.log
を実行しています。
require 'bundler/inline'
gemfile do
source 'https://rubygems.org'
gem 'capybara', require: 'capybara/dsl' # 記事執筆時点の最新バージョン 3.40.0 を使用
gem 'selenium-webdriver' # 同様にバージョン 4.19.0 を使用
end
# driver として Selenium、ブラウザ として Chrome を指定
Capybara.current_driver = :selenium_chrome
chrome_driver = Capybara.page.driver.browser
# => #<Selenium::WebDriver::Chrome::Driver:0x7f8a9cc561d591d8 browser=:chrome>
# CDP の Page.addScriptToEvaluateOnNewDocument を使って console にログ出力する JavaScript を追加
chrome_driver.execute_cdp('Page.addScriptToEvaluateOnNewDocument', source: <<~JavaScript)
console.log('Page.addScriptToEvaluateOnNewDocument で追加したスクリプト')
document.addEventListener('DOMContentLoaded', () => console.log('DOMContentLoaded イベント発火'))
JavaScript
# 検証のため、visit 前後に execute_script で console にログを出力
Capybara.execute_script('console.log("Capybara.visit 前の Capybara.execute_script")')
Capybara.visit 'https://example.com/'
Capybara.execute_script('console.log("Capybara.visit 後の Capybara.execute_script")')
# Chrome の console に出力されたログを確認するための待機時間
sleep 10
実行結果
上記のコードを実行して Chrome の console を確認すると、以下の順でログが出力されています。
Page.addScriptToEvaluateOnNewDocument で追加したスクリプト
DOMContentLoaded イベント発火
Capybara.visit 後の Capybara.execute_script
この結果から、Page.addScriptToEvaluateOnNewDocument
で追加した JavaScript はたしかに DOMContentLoaded
より先に実行されていることがわかります。
Page.addScriptToEvaluateOnNewDocument
を使う際の注意点
E2E テストで Page.addScriptToEvaluateOnNewDocument
で追加された JavaScript は、その後のテストでも実行されるため、後処理として Page.removeScriptToEvaluateOnNewDocument
で削除する必要があります。
以下は、Rails 7.1 のデフォルトである Minitest を使った E2E テスト(システムテスト)での例です。
class UsersTest < ApplicationSystemTestCase
setup do
@script_id = Capybara.page.driver.browser.execute_cdp('Page.addScriptToEvaluateOnNewDocument', source: 'console.log("hoge")')
end
teardown do
if defined?(@script_id)
Capybara.page.driver.browser.execute_cdp('Page.removeScriptToEvaluateOnNewDocument', **@script_id)
end
end
...
まとめ
Capybara と Selenium WebDriver を組み合わせて CDP の Page.addScriptToEvaluateOnNewDocument
を使い、ドキュメントの読み込み前、すなわち DOMContentLoaded
イベント発火前に JavaScript を実行する方法を紹介しました。
この技術は、自動テストにおいて JavaScript で扱うデータをモックしたい場合や、ページの初期状態をカスタマイズしたい場合に活用できると思います。
CDPには他にもさまざまな機能があり、 Chrome の Developer Tools を使った操作が自動テストで実行できるようになります。CDP のドキュメントに目を通すと、役立つメソッドが見つかるかもしれません。
Discussion