- バックエンド / リーダー候補
- PdM
- Webエンジニア(シニア)
- Other occupations (19)
- Development
- Business
RailsのブラウザテストでJavaScript側のエラーを出力する (RSpec + Selenium + ChromeDriver)
Photo by David Pupaza on Unsplash
Railsのブラウザテストでブラウザ側のエラーを表示するようにしたため、その方法を残しておきます。
(内部向け: この変更は #65868 で適用されました。)
はじめに
ブラウザテストではユーザーからみた振舞いを調べるため、テストの成否はブラウザの外側 (RailsであればRuby側) からのクエリによって判定されるのが一般的です。
一方で、テストが失敗した場合、その途中でブラウザの内側 (JavaScript側) で発生したエラーが原因になっていることは少なくありません。この場合はブラウザの内側で発生したエラーが追加情報として表示されていることが望ましいですが、今回使っている環境ではデフォルトではそのような機能はないようでした。
そこで、テストの設定をいじって、ブラウザ側のエラーを表示する機能を自分たちで追加しました。
環境
今回の設定は以下の環境を前提にしています。別の環境ではそのままでは動きません。
- Capybara + RSpec-Rails による feature spec 環境
- Selenium + ChromeDriver によるブラウザテスト環境
設定
spec/rails_helper.rb に以下の設定を追加しました。
RSpec.configure do |config|
# ...
# RSpecに報告するためのエラー定義。
# 本来はこの位置で定義するのは行儀が悪いが、コードとしてのまとまりを優先してここに書いている
class ::JavaScriptError < StandardError; end
# 各テストケースの終了後にフックする (ただし、feature specのみ)
config.after(type: :feature) do |example|
# テストが成功していれば何もしない。
# また、このコードはSelenium + ChromeDriverでのみ動くため、それ以外の環境は無視している。
# (ChromeDriver 以外でも動くかもしれないが、未確認)
if example.exception && page.driver.is_a?(Capybara::Selenium::Driver) && page.driver.browser.is_a?(Selenium::WebDriver::Chrome::Driver)
# Selenium Driverのログのうち、ブラウザ由来のものを取り出す
page.driver.browser.manage.logs.get(:browser).each do |entry|
# エラーレベルのログのみ抽出する
# (SeleniumのAPIを見る限りではSEVERE, WARNING, INFO, DEBUGの4種類のようなので、 ERROR の分岐は余計かもしれない)
if entry.level == "SEVERE" || entry.level == "ERROR"
# 得られたログをエラーとしてRSpecに報告する。
# このset_exceptionはRSpecのプライベートAPIで、複数のエラーが報告された場合はまとめてMultipleExceptionErrorにしてくれる。
example.set_exception(JavaScriptError.new(entry.message))
end
end
end
end
# ...
end
注意
スタックトレースはエラーメッセージに含まれていますが、ソースマップは適用されていないようです。手元にNode.jsとsource-mapパッケージがあれば、だいたい以下の手順で元の位置を復元できます。
> const { SourceMapConsumer } = require("source-map");
> const smc = new SourceMapConsumer(fs.readFileSync("path/to/source.js.map", "utf-8"));
> smc.originalPositionFor({ line: 2, column: 12345 })
これらの変換を自動的に行うという方法も考えられますが、当面はエラーが出るだけでも十分だと判断して実装していません。