Capybara + Selenium WebDriver による E2E テストを実行しているとき Net::ReadTimeout が発生することがある。
Net::ReadTimeout
--- Caused by: ---
IO::EAGAINWaitReadable:
Resource temporarily unavailable - read would block
これへの対応として、
- (1) タイムアウトをのばす
- (2) リトライする
- (3) そもそもタイムアウトに引っ掛かるような処理を見直す
があると思う。(3) については各位がんばりましょうということで、ここでは (1) (2) について書く。
環境
- Rails 4.2.5(がんばる...)
- capybara 2.6.2
- selenium-webdriver 3.8.0
- Google Chrome 63.0.3239.84
- ChromeDriver 2.34.522932
タイムアウトをのばす
https://github.com/SeleniumHQ/selenium/wiki/Ruby-Bindings#internal-timeouts のとおり
client = Selenium::WebDriver::Remote::Http::Default.new
client.read_timeout = 120 # seconds
driver = Selenium::WebDriver.for :remote, http_client: client
みたいなことをやれば良いのだけれど、これをみても、なるほどわからん、という人はいると思うので、もう少し周辺も含めた設定例を書いておく。
ヘッドレス Chrome を使った例
PhantomJS が最新の JS / CSS へ追随しなくなって、E2E テストで使うブラウザーを Chrome へ乗り換えた人も多いと思うので、ヘッドレス Chrome の設定例を書いておく。
# spec/support/capybara.rb
require 'capybara/rails'
require 'capybara/rspec'
require 'selenium-webdriver'
client = Selenium::WebDriver::Remote::Http::Default.new
client.read_timeout = 120 # instead of the default 60
Capybara.register_driver :selenium do |app|
Capybara::Selenium::Driver.new(
app,
browser: :chrome,
desired_capabilities: Selenium::WebDriver::Remote::Capabilities.chrome(
chrome_options: {
args: ['headless', 'window-size=1280,768'],
},
),
http_client: client,
)
end
Capybara.javascript_driver = :selenium
ポイント
-
timeout=
メソッドは deprecated-
client.read_timeout = xxx
としている箇所、少し古いドキュメントだとclient.timeout = xxx
と書かれているものもあるけど、timeout= メソッドは deprecated になっている - Selenium の Wiki については 先ほど 中の人に直してもらえた(感謝)
-
- ヘッドレス Chrome の
disable-gpu
オプションは不要- 本件のメインの内容ではないけれど
-
ヘッドレス Chrome ことはじめ | Web | Google Developers によると、Chrome 59 の時点では
disable-gpu
が暫定的なフラグとして必要だったようだが、少なくとも Chrome 63 ではこのフラグを付けなくても正常に動く
- ヘッドレス Chrome を RSpec + Capybara で使うことについては下記が分かりやすい
リトライする
rspec-retry という便利な Gem があるのでオススメ。
設定例
使い方は公式の README に分かりやすく書いているので特に困らないと思うけれど、参考までに 転職ドラフト で使っている設定を載せておく。
# spec/support/retry.rb
require 'rspec/retry'
RSpec.configure do |config|
config.verbose_retry = true
config.display_try_failure_messages = true
# run retry only on features
config.around :each, :js do |example|
# 公式では 3 となっているけれど、日本には三度目の正直という言葉があるし、
# 3回やってもこけるテストは根本的に対応したほうが良いと考えた
example.run_with_retry retry: 2
end
# callback to be run between retries
config.retry_callback = proc do |example|
# run some additional clean up task - can be filtered by example metadata
Capybara.reset! if example.metadata[:js]
end
end
ポイント
config.exceptions_to_retry = [Net::ReadTimeout]
とやって、リトライする対象のエラーを指定する方法もある(詳細は公式 README 参照)が、JavaScript を利用した E2E テストは経験上なんやかんやでこけるときがあるので特にエラーを限定しなくて良いのかなと判断した。