おもしろwebサービス開発日記

Ruby や Rails を中心に、web技術について書いています

Capybara の README 意訳

注意

この訳はだいぶ古い(2011年7月時のREADME)です。最新版の訳をgithub上に載せたのでこちらをご覧ください。

はじめに

Rails のエンドツーエンドテスト用のデファクトスタンダードプラグイン Capybara の README 意訳です。いつもと比べて直訳成分多めです。

テスト関連はどうにも日本語の情報が少なくて、覚えるのが大変ですね><

概要

Capybara は Rack アプリ(Rails, Sinatra, Merb等)の統合テストを簡単にするのが目的です。Capybara は現実のユーザがウェブアプリとやりとりするのをシミュレートします。テスト用のドライバを選択できます。デフォルトでは Rack::Test と Selenium ドライバをビルトインでサポートしています。HtmlUnit, env.js は外部の gem としてサポートしています。

完全なリファレンスは こちら

インストール

sudo gem install capybara

OSX は libffi をインストールする必要があるかもしれません。MacPorts では下記のコマンドでインストールできます。

sudo port install libffi

開発

省略

Cucumber で Capybara を使う

省略

RSpec で Capybara を使う

もし Cucumber よりも RSpec を使いたい場合は、下記の一行を spec_helper.rb などに追加することで、RSpec のサポートを使えるように出来ます。

require 'capybara/rspec'

そうすると例えば下記のように spec が書けます

describe "the signup process", :type => :request do
  before :each do
    User.make(:email => 'user@example.com', :password => 'caplin')
  end

  it "signs me in" do
    within("#session") do
      fill_in 'Login', :with => 'user@example.com'
      fill_in 'Password', :with => 'password'
    end
    click_link 'Sign in'
  end
end

Capybara は :type => :request とタグ付けされた example group の中だけ使えます(もしくは Steak 用に :acceptaance でも使えます)

もし Rails でテストをしていて、rspec-rails gem を使っているなら、これらの :request の example group には見覚えがあるかもしれません。なぜなら、これらは RSpec バージョンの統合テストだからです。つまり、Capybara のヘルパに加えて、named route も同時に使えます。例えば visit edit_user_path(user) のように。spec/requests にファイルを置けば、 rspec-rails は自動に :type => :request のタグを付けてくれます。(実際には、spec/integration と spec/acceptance でもうまく動きます)

rspec-rails は :controller と :mailer の example group 中で Capybara を自動で include します。

RSpecメタデータ機能はドライバを切り替えるのに使えます。:js => true とすると javascript ドライバ、または :driver オプションで特定のドライバを使うことが出来ます。

describe 'some stuff which requires js', :js => true do
  it 'will use the default js driver'
  it 'will switch to one specific driver', :driver => :celerity
end

Capybara はビルトインのDSLを使うことで受け入れテストを書くことができます

feature "Signing up" do
  background do
    User.make(:email => 'user@example.com', :password => 'caplin')
  end

  scenario "Signing in with correct credentials" do
    within("#session") do
      fill_in 'Login', :with => 'user@example.com'
      fill_in 'Password', :with => 'caplin'
    end
    click_link 'Sign in'
  end
end

これは実際には、単なる request spec へのショートカットで、feature は describe ... :type => :request のエイリアスです。background は before 、 scenario は it/specify のエイリアスです。

Capybara ビルトインの RSpec サポートは RSpec 2.0 以降のみサポートしています。

Test::Unit で Capybara を使う

Test::Unit で Capybara を使うには、DSL を 使いたいテストのクラスで DSL を include する必要があります(Capybara のバージョンが 0.4.x までの場合は include Capybara、 それ以降は include Capybara::DSL とします)。もしテストのクラスが ActionDispatch::IntegrationTest を継承している場合はこのように書きます。

class ActionDispatch::IntegrationTest
  include Capybara::DSL
end

Test::Unit はメタデータによるドライバの切り替えをサポートしていません。setup や teardown メソッドを使うことでドライバを切り替えます。詳しくは "ドライバの選択" の章をご覧ください。

Ruby on Rails で Capybara を使う

Rails を使うなら、下記の行を加えると、自動で Rails アプリのテスト用の Capybara の設定をしてくれます。(訳注: 恐らく Rails で Test::Unit を使う場合の話だと思われます)

require 'capybara/rails'

Rack で Capybara を使う

Rails 以外の Rack アプリで Capybara を使うなら、Capybara.app に application class を設定します。

Capybara.app = MyRackApp

ドライバ

Capybara は複数のブラウザやドライバを同一のDSLで扱います。

ドライバの選択

デフォルトでは、Capybara は :rack_test ドライバを使います。rack_test は速いけど JavaScript をサポートしていません。デフォルトのドライバを selenium に変えたい場合は下記のようにします

Capybara.default_driver = :selenium

しかし、もし RSpec か Cucumber を使っているなら、rack_test をデフォルトドライバとして使わないようにして、 :js => true または @javascript と印を付けたところだけ js が利用可能なドライバを使うことが出来ます。js をテストするデフォルトは :selenimum ドライバです。Capybara.javascript_deiver を変更することで、js 用のデフォルトドライバを変更できます。

また、一時的にドライバを変更したい場合は下記のようにします。(Before/setup や After/teardown ブロック中などでよく使われます)

Capybara.current_driver = :culerity  # 一時的に変更
... tests ...
Capybara.use_default_driver  # デフォルトドライバに戻す

ドライバの変更は新しいセッションを作ります。テストの途中ではドライバの変更は出来ません。

RackTest

RackTest は Capybara のデフォルトドライバです。これは pure Ruby で書かれていて、JavaScript はサポートしていません。Rack Test ドライバは直接 Rack のインタフェースに作用するので、テスト用のサーバーを立ち上げる必要はありません。つまり Rack アプリでなければ、このドライバを使うことは出来ないということです。Rack Test はリモートのアプリのテストにも使えません。capybara-mechanize はリモートのサーバをテストするドライバです。

RackTest はこのように設定することが出来ます。

Capybara.register_driver :rack_test do |app|
  Capybara::RackTest::Driver.new(app, :browser => :chrome)
end

詳しくは "ドライバを設定、追加する" のセクションを見てください。

Selenium

現在、Capybara は Selenium 2.0 をサポートしており、Selenium RC はサポートしていません。Firefox がインストールされていた場合、設定は全てすんでおり、すぐに Selenium を使い始めることが出来ます。

Capybara はデフォルトで Ajax リクエストがページに対して作用し終わるのを待ちます。:resynchronize を false にすることでこの振る舞いをオフに出来ます。詳しくは configuring drivers のセクションを読んでください。

Selenium は transactional fixture をサポートしていません。Transaction Fixtures の章を読んでください。

HtmlUnit

外部の gem としてメンテナンスされている、HtmlUnit を使うドライバが 3つあります。

Akephalos
今のところ HtmlUnit を使うドライバとしては一番かもしれません
Celerity
JRuby 上でのみ動きます。なので JRuby で celerity gem をインストールする必要があります。(jruby -S gem install celerity)
Culerity
celerityを上記のようにインストールして、JRubyがパスにあるか確認してください。Culerity は現在 Ruby 1.9 では動かないようです。

HtmlUnit は transactional fixtures をサポートしていません。Transactional Fixtures の章を見てください。

env.js

capybara-envjs ドライバは env-js gem を使ってブラウザを使わずに JavaScript を実行します。下記のようにインストールします

gem install capybara-envjs

さらなる情報を得たい場合は、上記のリンクを見てください。envjs gem は現在 Ruby 1.8.7 のみサポートしています。

Envjs は transactional fixtures をサポートしていません。Transactional Fixtures の章を見てください。

DSL

Capybara の DSL は Webrat からインスパイアしています。多くのケースで後方互換性を保っている一方、重要な違いもいくつかあります。Webrat とは異なり、Capybara は 全ての検索で case sensitive です。これは、 Capybara は case insensivity をサポートしていない XPath をヘビーに使っているためです。

Navigating

他のページに遷移するメソッドとして visit が使えます

visit('/projects')
visit(post_comments_path(post))

visit メソッドは引数を一つだけ取り、リクエストはいつも GET です。

テスト用に、カレントパスを取得することが出来ます。

current_path.should == post_comments_path(post)

Clicking links and buttons

フルのリファレンスはこちら: Capybara::Node::Actions

リンクやボタンを押すことが出来ます。Capybara は自動でリダイレクトに対応し、ボタンに関連するフォームをサブミットします。

click_link('id-of-link')
click_link('Link Text')
click_button('Save')
click_on('Link Text')  # リンクとボタンどちらかをクリック
click_on('Button Value')

Interacting with forms

フルのリファレンスはこちら: Capybara::Node::Actions

フォーム要素を操作するツールがたくさんあります

fill_in('First Name', :with => 'John')
fill_in('Password', :with => 'Seekrit')
fill_in('Description', :with => 'Really Long Text...')
choose('A Radio Button')
check('A Checkbox')
uncheck('A Checkbox')
attach_file('Image', '/path/to/image.jpg')
select('Option', :from => 'Select Box')

Querying

フルのリファレンスはこちら: Capybara::Node::Matchers

Capybara はページに特定の要素が存在しているかを調べることが出来るオプションを豊富に持っており、かつそれらの要素の操作もできます。

page.has_selector?('table tr')
page.has_selector?(:xpath, '//table/tr')
page.has_no_selector?(:content)

page.has_xpath?('//table/tr')
page.has_css?('table tr.foo')
page.has_content?('foo')

RSpec のマッチャとして使うことも出来ます

page.should have_selector('table tr')
page.should have_selector(:xpath, '//table/tr')
page.should have_no_selector(:content)

page.should have_xpath('//table/tr')
page.should have_css('table tr.foo')
page.should have_content('foo')
page.should have_no_content('foo')

page_should_not have xpath よりも page.should have_no_xpath を使うべきです。詳しくは Ajax の章をご覧ください。

もしうまくいかない時は、page.html を使うと生HTMLを扱うことが出来ます。

page.html.should match /<span>.../i

Finding

フルのリファレンスはこちら: Capybara::Node::Finders

特定の要素を探して操作することが出来ます。

find_field('First Name').value
find_link('Hello').visible?
find_button('Send').click

find(:xpath, "//table/tr").click
find("#overlay").find("h1").click
all('a').each { |a| a[:href] }

find は、Ajax の章で説明されているように、要素がページに表示されるのを待ちます。もし要素が現れなければエラーを吐きます。

これらの要素は Capybara の DSL を全て使え、ページの箇所を特定して DSL の作用する場所を制限することが出来ます。

find('#navigation').click_link('Home')
find('#navigation').should have_button('Sign out')

Scoping

Capybara は form 操作やリンクやボタンのクリックなどの特定のアクションを、ページの特定のエリア内で行うように制限することが可能です。within メソッドを使うことでそれができ、オプションでセレクタの種類を特定することが出来ます。

within("li#employee") do
  fill_in 'Name', :with => 'Jimmy'
end

within(:xpath, "//li[@id='employee']") do
  fill_in 'Name', :with => 'Jimmy'
end

fieldset や table 用の特別なメソッドがあります。fieldset は legend タグ内の id かテキスト、table は caption タグの id かテキストを見ます。

within_fieldset('Employee') do
  fill_in 'Name', :with => 'Jimmy'
end

within_table('Employee') do
  fill_in 'Name', :with => 'Jimmy'
end

Scripting

ドライバがサポートしていれば、簡単に JavaScript を実行できます。

page.execute_script("$('body').empty()")

簡単な式であれば、script の結果を受け取ることが出来ます。複雑な式だとうまくいかないかもしれません。

result = page.evaluate_script('4 + 4');

Debugging

下記のメソッドで、現在の状況をスナップショットとして撮って見れます。便利です。

save_and_open_page

Transactional fixture

Transactional Fixture はデフォルトの Rack::Test のみで使えますが、それ以外の Selenium 等のドライバでは使えません。Cucumber はそのあたり自動で面倒を見てくれますが、 Test::Unit や RSpec では、database_cleaner gem を使う必要があるかもしれません。詳しくはこのあたりを見てください。またはここここに解決用のコードがあります。

Ajax

非同期の Javascript を扱う時に、まだページに存在していないされていないDOM要素を操作したい場面に出くわすことがあるかもしれません。Capybara はDOM要素が現れるのを待つことでうまく対処しています。

下記のような操作をしたとします。

click_link('foo')
click_link('bar')
page.should have_content('baz')

foo をクリックしたときに非同期の処理(例:Ajaxリクエスト)が走り、完了したら bar リンクがページに加えられるとした場合、bar がまだ出現していなければ bar のクリックで失敗します。しかし Capybara は短い時間で再度リンクを探します。次の行でも同じようにbazを探します。

待つ時間を変更することが出来ます。(デフォルトは2秒)

Capybara.default_wait_time = 5

この振る舞いがあるので、下記の二行は等価ではありません。常に have_no_xpath の方を使うようにしてください。

page.should_not have_xpath('a')
page.should have_no_xpath('a')

page.should_not have_xpath('a') の方は非同期の操作を待ちません。

サポートしていないテストフレームワークで Capybara の DSL を使う

Capybara::DSL を include することで、どんなコンテキストでも Capybara のDSLを使うことが出来ます。

require 'capybara'
require 'capybara/dsl'

Capybara.default_driver = :culerity

module MyModule
  include Capybara::DSL

  def login!
    within("//form[@id='session']") do
      fill_in 'Login', :with => 'user@example.com'
      fill_in 'Password', :with => 'password'
    end
    click_link 'Sign in'
  end
end

リモートサーバを呼ぶ

通常、Capybara は Rack アプリをテストすることを期待します。しかし app_host を設定すると、インターネット上のウェブサーバと会話することができるようになります。

Capybara.current_driver = :selenium
Capybara.app_host = 'http://www.google.com'
...
visit('/')

デフォルトのドライバ(:rack_test)はリモートサーバをサポートしていません。サポートしているドライバでは、どんな URL でも直接 visit することが出来ます。

visit('http://www.google.com')

Capybara はデフォルトでは rack アプリを自動でブートしようとします。リモートアプリを使っていて Capybara の rack サーバをオフにしたい場合は下記のようにします

Capybara.run_server = false

session を手動で使う

Session を手動でインスタンス化して使うことが出来ます。

require 'capybara'

session = Capybara::Session.new(:culerity, my_rack_app)
session.within("//form[@id='session']") do
  session.fill_in 'Login', :with => 'user@example.com'
  session.fill_in 'Password', :with => 'password'
end
session.click_link 'Sign in'

XPath CSS and selectors

Capybara はデフォルトでは CSS を使います。セレクタから推測するようなことはしません。XPath を使いたい場合は、下記のようにすることで使えます

within(:xpath, '//ul/li') { ... }
find(:xpath, '//ul/li').text
find(:xpath, '//li[contains(.//a[@href = "#"]/text(), "foo")]').value

デフォルトのセレクタXPath に変更することも出来ます。

Capybara.default_selector = :xpath
find('//ul/li').text

Capybara はカスタムセレクタを作成して使うことも出来ます。同じような種類のセレクタをよく使う場合に役立ちます。

Capybara.add_selector(:id) do
  xpath { |id| XPath.descendant[XPath.attr(:id) == id.to_s] }
end

Capybara.add_selector(:row) do
  xpath { |num| ".//tbody/tr[#{num}]" }
end

xpath メソッドに与えられたブロックは必ず XPath の表現を文字列もしくは XPath gem で作られたもので返さないといけません。上記のようにカスタムセレクタを設定した場合、下記のようにセレクタを使うことが出来ます。

find(:id, 'post_123')
find(:row, 3)

XPath // の罠

XPath での // は特別で、あなたが思ってるような意味ではないかもしれません。通常の認識とは違い、// は "ドキュメント全体のどこか" であって "今のコンテキスト内でのどこか" ではありません。例えば

page.find(:xpath, '//body').all(:xpath, '//script')

これは全ての script タグを body の中から探すと思うかもしれません。でも実際には、これはドキュメント全体の script タグを探します。 .// は "カレントノードの配下" を表します。

page.find(:xpath, '//body').all(:xpath, './/script')

within で同じようにやるとこうなります

within(:xpath, '//body') do
  page.find(:xpath, './/script')
  within(:xpath, './/table/tbody') do
    ...
  end
end

ドライバを設定、追加する

Capybara はドライバを切り替えることが簡単にできます。また、ドライバを設定するためのAPIが用意されているし、独自のドライバを追加することが出来ます。下記は selenium ドライバに chrome を使わせる方法です

Capybara.register_driver :selenium do |app|
  Capybara::Selenium::Driver.new(app, :browser => :chrome)
end

違う名前を付けることで、ブラウザを使い分けることが簡単にできます。

Capybara.register_driver :selenium_chrome do |app|
  Capybara::Selenium::Driver.new(app, :browser => :chrome)
end

ブロックの戻り値は Capybara::Driver::Base の記述する API に従うべきです。Capybara::Driver::Base を継承している必要はありません。gem はこの API を、自分独自のドライバを Capybara に加えるために使えます。

selenium wikiにはドライバ設定の情報が詳しく書かれています。

その他

  • session と request にアクセスするのはテストからは無理です。response へのアクセスは限定されています。いくつかのドライバは response header と HTTP status code にアクセスできますが、アクセスできないドライバ(例: selenium)もあります。
  • Rails の統合テストを使っていないので、Rails の特定のもの(例: controller)へアクセスすることは出来ません。
  • 現在時刻に依存したフィーチャをうまく動かすためにモック化すると問題が起こることがあります。capybara の Ajax のタイミングは system の時間を使っているせいで、failure な時にCapybara はタイムアウトせずにハングってしまいます。時間を止める系のプラグインよりも、時間を移動させる系のプラグイン(例: timecop)でよく起こります。
  • Rack::Test を使っているときは、URLで visit しているかどうかチェックするべきです。たとえば、session は posts_path と posts_url で別々になります。もし Action Mailer の中でURLを使っているとしたら、default_url_options を Rails のデフォルトの www.example.com に設定するべきです。