😸

Capybara で Chrome DevTools Protocol を使って DOM 読み込み完了前に JavaScript を実行する

2024/04/22に公開

はじめに

一般的に、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 は PlaywrightPuppeteer など、数多くのツールで使われています。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_scriptconsole.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 より先に実行されていることがわかります。

E2E テストで Page.addScriptToEvaluateOnNewDocument を使う際の注意点

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 のドキュメントに目を通すと、役立つメソッドが見つかるかもしれません。

GitHubで編集を提案

Discussion