Reactで開発しているアプリが大きくなってきてそろそろテストが欲しくなってきたので、End to Endの形でテストをするためにPhantomJSを使ってみた話です。

環境

RailsアプリのフロントエンドにReactを突っ込んでいるという構成なので、一般的なRails + feature spec内でjavascriptを動かしつつ、circleciでテストが通ったら自動でデプロイされるという形にしています。

  • Ubuntu 14.04
  • Ruby 2.2.1
  • Rails 4.2.1
  • React 0.13.3(npm)
  • PhantomJS 2.0.1(development)

テストのサンプル

普通のjavascriptありのfeature specです。

# spec/features/top_page_spec.rb

RSpec.feature 'top page', type: :feature, js: true do
  context 'as non-signed in user' do
    it 'should see hoge' do
      visit '/'
      expect(page).to have_content 'hoge'
    end
  end

  context 'as signed in user' do
    it 'should see fuga' do
      user_sign_in
      visit '/'
      expect(page).to have_content 'fuga'
    end
  end
end

def user_sign_in
  # sign in code
end

詳細なセットアップはこちらをかなり参考にさせていただきました。

SPONSERD LINK

PhantomJSのインストール

何はともあれrspecでjavascriptを動かすためにPhantomJSを入れます。1.x系は公式にバイナリを配ってるんですが、最新の2.x系はまだありません。理由は依存関係が複雑になったからとのこと・・・

2系が必要かどうかもよくわからないので、まずは簡単に入れられる方を入れてみます。

PhantomJS 1.9.8

こちらを参考に1.9.8をインストールしてテストを実行してみたところ、下記のエラーで怒られました。

TypeError: 'undefined' is not a function (evaluating 'ReactElementValidator.createElement.bind(
      null,
      type
    )')

調べてみると、このbindという関数がPhantomJSの1系には実装されていないとのこと。こちらの記事で解決策が載っていたので、この通りGemfileに

source 'https://rails-assets.org' do
  gem 'rails-assets-bind-polyfill', '~> 1.0'
end

を追記してbundle installすると、なぜかローカルでbin/rakeの実行時にエラーが出てしまいました。

/home/muratayusuke/.rbenv/versions/2.2.1/lib/ruby/2.2.0/rubygems/request_set/lockfile.rb:520:in `pinned_requirement': undefined method `version' for nil:NilClass (NoMethodError)
        from /home/muratayusuke/.rbenv/versions/2.2.1/lib/ruby/2.2.0/rubygems/request_set/lockfile.rb:301:in `parse_DEPENDENCIES'
        from /home/muratayusuke/.rbenv/versions/2.2.1/lib/ruby/2.2.0/rubygems/request_set/lockfile.rb:254:in `parse'
        from /home/muratayusuke/.rbenv/versions/2.2.1/lib/ruby/2.2.0/rubygems/request_set.rb:279:in `load_gemdeps'
        from /home/muratayusuke/.rbenv/versions/2.2.1/lib/ruby/2.2.0/rubygems.rb:1055:in `use_gemdeps'
        from /home/muratayusuke/.rbenv/versions/2.2.1/lib/ruby/2.2.0/rubygems.rb:1244:in `<top (required)>'
        from <internal:gem_prelude>:1:in `require'
        from <internal:gem_prelude>:1:in `<compiled>'

これが全然解決できなかったので、仕方なくPhantomJSの2系にトライすることにしました。

(追記)

@nissheeから、rails-assets-es5-shimを使ったら1.9.8でも動いたとの情報を教えてもらいました。これでよかった・・・

PhantomJS 2.0.1

公式のダウンロードページを見ると、Linuxはバイナリないからソースからビルドしてねとのことだったので、ドキュメント通りにビルドすることに。これがまあメモリ食うわ時間かかるわでめっちゃ大変だったので、ビルドしてる間にcircleciの方を調べてみました。

ここにPhantomJSの対応バージョンが書いてあったのですが、なんと1.9.8しか書いてない・・・ということでサポートに2系の対応予定ありますか?って問い合わせたところ、1時間もしないうちに下記の回答がありました。

Hi Yusuke,
We have a dynamically linked PhantomJS binary that some customers have reported good success with but others have run into some difficulty so we haven’t released it generally.
You can install it via the following circle.yml commands

dependencies:
  pre:
    - sudo apt-get update; sudo apt-get install libicu52
    - curl --output /home/ubuntu/bin/phantomjs-2.0.1-linux-x86_64-dynamic https://s3.amazonaws.com/circle-support-bucket/phantomjs/phantomjs-2.0.1-linux-x86_64-dynamic
    - chmod a+x /home/ubuntu/bin/phantomjs-2.0.1-linux-x86_64-dynamic
    - sudo ln -s --force /home/ubuntu/bin/phantomjs-2.0.1-linux-x86_64-dynamic /usr/local/bin/phantomjs

Cheers,
–Alexey

要は、まだ問題あるから公式にはリリースしてないけど一応こうやってインストールしたらだいたい使えるはずっていう感じですね。

まじかよ!2系のバイナリあるやん!っていう感じで、他で見つけたバイナリは動かなかったんですがこいつは無事ローカルでもcircleci上でも動いてくれました。

ということでせっかく調べたので、とりあえずitamaeのレシピにして公開しときました。

itamae-plugin-recipe-phantomjs

元々ソースビルドするからと思って作り始めたので苦労の後がありますが、ソースの方は結局時間かかりすぎて最後まで動くのを見届けてません・・・・

バイナリもUbuntu 14.04でしか動かしてないので、他の環境への対応はプルリクいただけると嬉しいです。まあ2系がパッケージで配布されるようになったら出番なくなりそうな気はしますが。