以前にこんな記事を書きました。
今回はこれの続きみたいなものです。 まだCircleCIでのテスト実行まで至らず、ローカルでテストが全部通ることを目標に調整中です。 featurespec以外のテストは完走したので、問題はfeaturespecのみ。 ということで、Capybaraの設定の見直しです。
エラーの傾向を見る
落ちたエラーを確認したところ、
- 画像をダウンロードしようとしてURLが間違っていてエラー
- ファイルのダウンロードが完了したことを検出できずにエラー
- ChromeのSession IDが不正
みたいなのが大半でした。
特に最後のが結構困ったもので、これが起きるとrspecの後処理が完走せずに次のテストに行ってしまって巻き込みエラーになっているみたいなので、どうしたものか、という状況です。
Capybaraの設定を見直す
Before
とりあえずBeforeのやつを貼ります。spec/support/capybara.rb
です。
WaitForDownload
モジュールは私が作っているファイルのダウンロードを待つためのモジュールです。
詳しくは私が書いたQiitaの記事を…。
Capybara.register_driver :selenium do |app| options = Selenium::WebDriver::Chrome::Options.new options.headless! options.add_argument '--disable-gpu' options.add_argument '--window-size=1680,1050' options.add_argument '--blink-settings=imagesEnabled=false' options.add_argument '--lang=ja' # options.add_argument '--no-sandbox' # options.add_argument '--no-zygote' driver = Capybara::Selenium::Driver.new(app, url: ENV.fetch('SELENIUM_DRIVER_URL'), browser: :remote, options: options, desired_capabilities: Selenium::WebDriver::Remote::Capabilities.chrome( login_prefs: { browser: 'ALL' }, loggingPrefs: { browser: 'ALL' }, ), ) bridge = driver.browser.send(:bridge) path = "session/#{bridge.session_id}/chromium/send_command" bridge.http.call(:post, path, cmd: 'Page.setDownloadBehavior', params: { behavior: 'allow', downloadPath: WaitForDownload::PATH, }) driver end Capybara.configure do |config| config.server_host = "test" # <= chromeから見た、テストを実行するイメージ名を指定 config.server_port = 9887 + ENV['TEST_ENV_NUMBER'].to_i config.app_host = "http://#{config.server_host}:#{config.server_port}" # <= Seleniumが接続するテストを実行するAppHostのURL config.javascript_driver = :selenium config.default_max_wait_time = ENV['CI'].present? ? 15 : 5 config.ignore_hidden_elements = true config.server = :puma, { Silent: true } end
画像のダウンロードをしない設定を調査
Capybaraの設定は特に変えていないにも関わらず、画像をダウンロードしようとするということは、リモートのChromeに設定が渡ってないってことか?と思い、調査開始。
すると、desired_capabilities
オプションで設定するっぽい感じの情報を見つけました。
これを参考にして、desired_capabilities
に対してcrhomeOptions
を定義してそちらにChromeに関するオプションの配列を渡したところ、動き始めました🎉
よりよい書き方を模索
しかし、Selenium::WebDriver::Chrome::Options
クラスがあるにも関わらず、オプションを配列にして渡すかね?と思って綺麗な書き方を模索するためにコードを読みました。
selenium/options.rb at master · SeleniumHQ/selenium · GitHub
すると、as_json
メソッドを発見。これを実行すると、goog:chromeOptions
というKeyを持つHashを作ってくれます(as_jsonとは?🤔)これを、desired_capabilities
に設定するようにしたところ、リモートのChromeにもいい感じに反映されました。👍👍👍
この時点でのregister_driverだけを書き出します。
Capybara.register_driver :selenium do |app| chrome_options = Selenium::WebDriver::Chrome::Options.new chrome_options.headless! %w( no-sandbox disable-gpu window-size=1440,900 disable-desktop-notifications disable-extensions blink-settings=imagesEnabled=false lang=ja ).each { |option| chrome_options.add_argument(option) } capabilities = Selenium::WebDriver::Remote::Capabilities.chrome(chrome_options.as_json) driver = Capybara::Selenium::Driver.new( app, url: ENV.fetch('SELENIUM_DRIVER_URL'), options: chrome_options, browser: :remote, desired_capabilities: capabilities, ) bridge = driver.browser.send(:bridge) path = "session/#{bridge.session_id}/chromium/send_command" bridge.http.call(:post, path, cmd: 'Page.setDownloadBehavior', params: { behavior: 'allow', downloadPath: WaitForDownload::PATH, }) driver end
ダウンロードしたファイルを検出したい
ファイルをダウンロードするまで待つ、という処理をしているのですが、rspecを実行しているコンテナとchromeが動いているコンテナは異なるため、ダウンロードしたファイルはchromeのコンテナ内に保存され、rspecのコンテナでは見つかりません。そりゃそうですね。そこで、docker-compose.ymlを修正して、ホストOSとファイル共有するようにします。chromeコンテナでダウンロードしたファイルをrspecのコンテナからも参照できるようにするわけです。
version: '3.3' services: # 略 chrome: image: selenium/standalone-chrome:latest ports: - '4444:4444' volumes: - ./tmp/download:/tmp/download # 略
これで、ダウンロードしたファイルを検出できるようになりました!
ダウンロードファイルのパス指定方法が変わったらしい
これを調べているときに、メドピアさんのブログを見かけて、ダウンロードのファイルパス指定方法が変わったことを知りました。
こっちのほうがわかりやすいので、変更しました。テストを実行しても問題なくファイルがダウンロードされていたのでOK。コードが簡潔になりました👍
この時点でのregister_driverを書き出します。
Capybara.register_driver :selenium do |app| chrome_options = Selenium::WebDriver::Chrome::Options.new chrome_options.headless! %w( no-sandbox disable-gpu window-size=1440,900 disable-desktop-notifications disable-extensions blink-settings=imagesEnabled=false lang=ja ).each { |option| chrome_options.add_argument(option) } # ダウンロードディレクトリを設定 chrome_options.add_preference(:download, default_directory: "/tmp/download") capabilities = Selenium::WebDriver::Remote::Capabilities.chrome(chrome_options.as_json) Capybara::Selenium::Driver.new( app, url: ENV.fetch('SELENIUM_DRIVER_URL'), options: chrome_options, browser: :remote, desired_capabilities: capabilities, ) end
bridge云々のコードがなくなってわかりやすくなりました。
並列化に対応したい
弊社ではparallel_testsを使っているので、ファイルのダウンロードが並列に行われても影響がないようにしたいので、ダウンロードパスを修正します。
# ダウンロードディレクトリを設定 chrome_options.add_preference(:download, default_directory: "/tmp/download/#{ENV.fetch('TEST_ENV_NUMBER', '1')}")
こうすることで、並列実行したら、
- /tmp/download/1
- /tmp/download/2
- /tmp/download/3
のようにディレクトリができるようになります。
併せて、wait_for_download.rbも修正しました。
module WaitForDownload PATH = Rails.root.join("tmp/download/#{ENV.fetch('TEST_ENV_NUMBER', '1')}") # 他は略 end
After
ということで、これがとりあえず今のところのspec/support/capybara.rb
の全コードになります。
Capybara.register_driver :selenium do |app| chrome_options = Selenium::WebDriver::Chrome::Options.new chrome_options.headless! %w( no-sandbox disable-gpu window-size=1440,900 disable-desktop-notifications disable-extensions blink-settings=imagesEnabled=false lang=ja ).each { |option| chrome_options.add_argument(option) } chrome_options.add_preference(:download, default_directory: "/tmp/download/#{ENV.fetch('TEST_ENV_NUMBER', '1')}") capabilities = Selenium::WebDriver::Remote::Capabilities.chrome(chrome_options.as_json) Capybara::Selenium::Driver.new( app, url: ENV.fetch('SELENIUM_DRIVER_URL'), options: chrome_options, browser: :remote, desired_capabilities: capabilities, ) end Capybara.configure do |config| config.server_host = "test" config.server_port = 9887 + ENV['TEST_ENV_NUMBER'].to_i config.app_host = "http://#{config.server_host}:#{config.server_port}" config.javascript_driver = :selenium config.default_max_wait_time = ENV['CI'].present? ? 15 : 5 config.ignore_hidden_elements = true config.server = :puma, { Silent: true } end
SessionIDが不正のやつは?🤔
まだ調査中です🥺 Chromeがメモリを使いすぎてクラッシュするみたいな話を見かけたので、window-sizeを調整したりもしているのですが、直りません。
window-size云々よりも、並列化で動かすことでメモリがなくなってクラッシュしている可能性が高いかなと推測しております。 まぁとりあえずシングルプロセスでの完走を目指します🏃♂️
追記:調査完了
Chromeへのメモリ割当方法が分かったので記事にしました。