capybara-webkit を headless(画面なし・Xなし)で動かした

どうも皆さんこんにちは、GW返上で頑張る babie でございます。日中にキャッキャウフフ行楽してる奴は殺人光線を浴びて死ぬ。

Rails のインテグレーションテストで一般的となった Capybara ですが、JavaScript のテストには選択肢が色々あります。envjs, selenium, akephalos, celerity, culerity などなどです。迷いますねー。
ですが、当方、Ruby 1.9.2 p136 on VPS の関係のため、

  • envjs ―― 依存してる johnson が Ruby 1.9 系列ではコンパイルできないので×
  • selenium ―― X ごっそり入れるのイヤなので×
  • akephalos ―― 依存してる HTMLUnitjQuery 1.2 までしか対応してないので×
  • celerity ―― JRuby 専用なので×
  • culerity ―― Ruby 1.8 でしか動かないので×

まぁ、どうしましょう!全然使えませんわお姉さま!そこで id:secondlife お姉さまが、微妙に推薦している、capybara-webkit を試すことにしました。

capybara-webkit のページ に行くと、Xvfb なる単語が見えるではありませんか!Xvfb は仮想フレームバッファというもので、画面を実際に表示させることなしにメモリ上に展開することができるやつです。Gnome とか入れたくない VPS に嬉しいですね。これで勝つる!

では、Xvfb と capybara-webkit をコンパイルするためのパッケージを導入します。

$ sudo aptitude install xvfb libqt4-dev

capybara-webkit は QtWebkit を利用しています。へー。


それでは、Gemfile に書いていきましょうか、っと、待ったー!!えー、諸般の事情により、Capybara を自分でパッケージ化します。というのは、Capybara.javascript_driver = :webkit としておけば、describe や it に :js => true がついてる奴だけ capybara-webkit を使ってくれるのですが、Capybara の 2011-04-30 現在の stable 版である 0.4.1.2 では動かず、edge でしか動かないのです!じゃあ、Gemfile で :git => "..." 使えよ、ってことになるのですが、なぜか bundler が git で導入される 1.0.0.beta1 を capybara-webkit が要求する "~> 0.4.1" と認めてくれないのです。ナンタルチア!bundler め……ぐぬぬ……というわけで、Capybara 0.4.1.3 というオリジナルバージョンを作ります。

$ cd ~/dev/
$ git clone git://github.com/jnicklas/capybara.git
$ cd capybara
$ vi Gemfile
#gem 'xpath', :path => 'xpath' # コメントアウト

$ gem install xpath
$ bundle install
$ vi Gemfile
gem 'xpath', :path => 'xpath' # コメントアウトを解除

$ bundle update
$ vi lib/capybara/version.rb
module Capybara
  VERSION = '0.4.1.3' # ここ変えるだけ
end
$ gem build capybara.gemspec
$ gem install ./capybara-0.4.1.3.gem

さぁ、これで準備が整いました。gem list すれば Capybara 0.4.1.3 がインストールされてると思います。途中で Gemfile の xpath をコメントアウトしたりしてるのは、rubygems.org から bundler がうまく取ってきてくれないんですよねぇ。なぜか。ま、いいか。


よっし、Rails に戻るぞ。

$ vi Gemfile
group :development, :test do
...
  gem "capybara", "0.4.1.3"
  gem "capybara-webkit", "0.2.0"
  gem "launchy", "0.4.0"
  gem "headless", "0.1.0"
..
end

おや、ここで話に出てきてないものが2つありますね。launchy は capybara の save_and_open_page というメソッドを実行するために必要なものです(実は今回の設定ではどっちにしても使えないのですが)。headless は、Xvfb の起動と終了が Ruby から簡単にできるパッケージです。headless 使わない場合は、/etc/init.d/ に起動・終了スクリプト書いたりしないといけないんですが、これを使うとテスト走らす時だけ呼び出せるので便利っす。

$ bundle install

Using Capybara 0.4.1.3 とか出て無事にインストールできると思います。


では、capybara-webkit の設定をば。
spec/spec_helper.rb

require "capybara/rails"
require "capybara/rspec"
...
RSpec.configure do |config|
  config.before(:all) do
    #@headless = Headless.new
    #@headless.start
    Capybara.javascript_driver = :webkit
  end
 
  config.after(:all) do
    #Capybara.use_default_driver
    #@headless.destroy
  end
...
end

require "capybara/rails" だけでいいんちゃう?require "capybara/rspec" 要らないんちゃう?と訝る向きもあろうかと思いますが、:js => true を検知するコードは、capybara/rspec に入っているので要るのです。今んとこ。

Headless がコメントアウトされてるのはですねー、はじめここで起動・終了してたのですが、なんか Xvfb が期待しないところで終了してしまって「webkit_server: Fatal IO error: client killed」ってメッセージが出たりするので、後述の .autotest で起動・終了するようにしました。

Capybara.use_default_driver もコメントアウトしてありますが、Capybara.current_driver をいじくってたときの名残です。

Capybara.javascript_driver = :webkit で decribe や it で :js => true と設定したものにだけ、capybara-webkit が使えるようになります。Capybara 0.4.1.2 で Capybara.current_driver = :webkit してみたりもしたんですけど、"Rack application timed out during boot" とか言ってうまく動かないんすよねー。JavaScript テストだけに使うってことで。


spec/requests/home_spec.rb

require 'spec_helper'
describe "Home" do
  describe "GET /home" do
 
    it "supports JavaScript", :js => true do
      visit root_path

      #STDOUT.puts Capybara.current_driver
      #STDOUT.puts %Q|1 + 1 = #{page.evaluate_script("1 + 1")}|

      click_link "test js"

      #STDOUT.puts page.body

      page.should have_content("js works")
    end
 
  end
end

コメントアウトに苦労が忍ばれますね。ここでは root_path(home#index)にアクセスして、"test js" って書いてあるリンクをクリックしたら、ページ中に "js works" って文字列が表示されるというスペックです。

ビューはこんな感じ:
app/views/home/index.html.erb

<%= link_to_function "test js", "$(this).html('js works')" %>

あ、jQuery 使ってます。他のライブラリの人は適当に変えてください。

コントローラーとルーティングは省略していいよね? def index; end と root :to => "home#index" です。


ほいで!スペックを実行する前に!環境変数をセットしてください。

$ export DISPLAY=:99

というのは、Headless はデフォルト DISPLAY :99 で Xvfb を開くからなんですねー。.zshenv なんかに書いちゃった方がいいでしょう。共有サーバーで開発してる人は、開発者毎に DISPLAY 環境変数の番号をずらすといいですよ。

間違っても、横着して、

$ DISPLAY=:99 rspec spec/requests/home_spec.rb

なんてしちゃダメです。というのは、capybara-webkit の中で、webkit_server というバイナリを実行しているのですが、その実行時にこの環境変数が渡っていないと通信ができないからです。ハマりましたよ、ええ。


んで、プロジェクトディレクトリに .autotest ファイルを作ります。autotest 使ってますよね?使ってない人は開発の前後に別ターミナルの irb で手動で Headless の start/stop を実行すれば良いです。要は、Xvfb サーバーの起動と終了をしています。
.autotest

require 'headless'
Autotest.add_hook :initialize do |at|
  @headless = Headless.new(:display => ENV["DISPLAY"].delete(":").to_i)
  @headless.start
end
Autotest.add_hook :reset do |at|
  @headless.destroy
  @headless = Headless.new(:display => ENV["DISPLAY"].delete(":").to_i)
  @headless.start
end
Autotest.add_hook :quit do |at|
  @headless.destroy
end

はい、これで、autotest で実行できます。

`include Capybara` is deprecated please use `include Capybara::DSL` instead.

ってウォーニングが出ますが、capybara-webkit が最新の capybara に対応してないだけで動くのでとりあえず無視しましょう。1.0.0 が出た暁には対応されるでしょう。


長い旅路だった。2日ハマりました………