「Capybara、お前はただサーバーを起動してくれたらいいんだ!」って思ったときの対処法
で書いたように、RailsのSystem specなどで使うCapybaraは精度が良くない。
- 「頼むから、Capybara DSLなんて使わないで、ネイティブのPuppeteerやPlaywrightでブラウザの自動操作をさせてくれ!」
- 「Capybara、お前はただテスト用にRailsサーバーを起動してくれたらええんや!」
って思ったときに、どうすればいいか調べました。
CapybaraがHTTPサーバーを起動するように、ドライバ登録をする
「RailsアプリケーションをRackに乗せて、空いているポートを自動で見つけて、Puma起動する」というCapybaraがやっている仕事を自前で再実装するのは結構たいへんなので、ここだけ利用したい。
ただ、Capybara::Serverはどんな条件でも起動するわけではない。Capybaraドライバが needs_server?
メソッドでtrueを返したときに限って起動するような実装になっている。
@server = if config.run_server && @app && driver.needs_server?
server_options = { port: config.server_port, host: config.server_host, reportable_errors: config.server_errors }
server_options[:extra_middleware] = [Capybara::Server::AnimationDisabler] if config.disable_animation
Capybara::Server.new(@app, **server_options).boot
end
needs_server? はデフォルトではfalseが返り、Seleniumドライバなどブラウザを介して試験するようなドライバではtrueが返るように定義されている。
ただ、今回はべつに自動操作はCapybaraに任せるつもりはない。
class NullDriver < Capybara::Driver::Base
def needs_server?
true
end
end
Capybara.server = :puma, { Silent: false }
Capybara.register_driver(:null) { NullDriver.new }
Capybara.current_driver = :null
こんな感じで、needs_server? だけ定義したドライバをCapybaraに登録すればいいのだ。
Capybara::DSLを勝手にincludeしないようにする
rspec-rails
と capybara
の2つのGemはかなり密結合で、Gemfileに gem 'capybara'
って書くだけで、Capybara::DSLを勝手にロードしようとする。
- rspec-railsは、Capybaraがあれば
require 'capybara/rspec'
やrequire 'capybara/rails'
などを勝手にincludeする - Capybaraは('capybara/rspec' の中で定義されているが)、 spec/features, spec/system 配下のファイルに対して、Capybara::DSLを勝手にincludeする
勝手にCapybara::DSLをincludeさせないためには、spec/features, spec/system 以外のディレクトリ(たとえば、spec/integration など)にシステムテストのスクリプトを置くのが手っ取り早そうだ。
サンプル
例えば、 puppeteer-rubyをブラウザ操作部分に使うなら、こんな感じでaroundフックを使ってやればいい。
around do |example|
Capybara.current_driver = :null
@server_base_url = Capybara.current_session.server.base_url
Puppeteer.launch(channel: 'chrome', headless: false) do |browser|
@puppeteer_page = browser.new_page
example.run
end
Capybara.use_default_driver
end
let(:base_url) { @server_base_url }
let(:page) { @puppeteer_page }
it 'can browse' do
page.goto("#{base_url}/test")
page.wait_for_selector('input', visible: true)
page.type_text('input', 'hoge')
page.keyboard.press('Enter')
expect(page.eval_on_selector('#content', 'el => el.textContent')).to include('hoge')
end
Capybara.current_session
を初めて参照したときにCapybaraはHTTPサーバーを起動するので、Puppeteer.launch(ブラウザ起動)よりも前に server_base_url = Capybara.current_session.server.base_url
でしれっと current_sessionを参照しているのがポイント。
つづく...
続編かきました。「もうCapybaraなんて使わない!」って方法ですw
Discussion