Capybara + Turnip を Docker内部のSelenium hubで実行させる

Nexus6 買っちゃいました。何やってんだ自分とちょっと思わなくはないですが、まぁちょっと早いクリスマスプレゼント（自分への）ってことで。

そんなことはどうでも良いのですが、最近現場でも、画面の複雑さが増すに従ってデグレが発生する頻度が上がるようになってきました。
客先としても、「デグレなんとかなんない？」と言われたので、とりあえず「時間といいマシンください」とは言いましたが、それだけじゃ足りないので、Seleniumの導入を行いたいです。

が、Seleniumを単純に導入すると言っても、環境の用意がまず大変です。

- テスト環境と別の環境を用意する？
- データはどうやって投入する？
- Windows機は使うのか？

とかとか色々ありますが、とりあえず動き出さないとならなさそうになったところで、こんな記事を見かけました。

<http://gongo.hatenablog.com/entry/2014/12/02/002148>

Docker の中で Selenium hubを動作させる、というやり方をすれば、必要最小限の労力でFirefoxとか確認ができるってことですね。これっきゃないかな、と思いましたが、いきなり実践投入はあれなので、ちょっとVagrant上で試してみました

## 簡単な構成

Host -> Vagrant -> Guest -> Docker -> Selenium hub + slaves

こんな感じです。間がかなり挟まっているので、パフォーマンスにはあまり期待しないでおきます。

## Vagrantfile の設定
Docker についての設定とかは前述のページをまるぱく・・・ゲフンゲフン、参考にさせていただくとして、Vagrantの設定が必要ですんで、 `vagrant init` したところで記述していきましょう。

ちなみに今回はゲストとしてUbuntu 14.04を使いました。ここで別に辛いものを利用する必要もないので。

Vagrantfileはこんな感じになりました。

```ruby
    Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
      config.vm.box = "ubuntu/trusty64"
    
      # ホストオンリーネットワークを使わない場合はこっちの設定も必要
      # config.vm.network "forwarded_port", guest: 4444, host: 4444
    
      # Create a private network, which allows host-only access to the machine
      # using a specific IP.
      config.vm.network "private_network", ip: "192.168.33.10"
    
      # Create a public network, which generally matched to bridged network.
      # Bridged networks make the machine appear as another physical device on
      # your network.
      # config.vm.network "public_network"
    
      # If true, then any SSH connections made will enable agent forwarding.
      # Default value: false
      # config.ssh.forward_agent = true
    
      # Share an additional folder to the guest VM. The first argument is
      # the path on the host to the actual folder. The second argument is
      # the path on the guest to mount the folder. And the optional third
      # argument is a set of non-required options.
      # config.vm.synced_folder "../data", "/vagrant_data"
    
       config.vm.provider "virtualbox" do |vb|
         vb.customize ["modifyvm", :id, "--memory", "2048", "--cpus", "2", "--ioapic", "on"]
       end
    
      config.vm.provision "shell", path: "provision.sh"
    end
```

若干ポイントとしては、あえてホストオンリーネットワークを定義しておくことで、localhost上のポートと被る心配を減らしています。Docker上にSelenium hubを構築する関係上、Docker上にコンテナが配置されて初めてポートが決定されるので、ホストオンリーネットワークの方が多分楽です。

また、内部で（とりあえず）5ノード起動するので、メモリとCPUはある程度多めにしておきました。ノードが一個とかなら、多分1GBくらいで大丈夫です。


## provisioning の設定

provision.shでは、Docker本体と [fig](http://www.fig.sh/install.html) というツールをインストールします。最近はこういったプロビジョニングはAnsibleで書くのが好みですが、今回そんな重くないので、普通にシェルで書きます。

ところで、UbuntuへのDockerインストールは、Ubuntuがメンテナンスしているdocker.ioというパッケージか、Docker自体がメンテナンスしている奴のどっちかで基本的に導入しみあす。今回は、docker.ioのバージョンがかなり古かったため、Dockerがメンテナンスしているパッケージにしときました。

```bash
#!/usr/bin/env bash
    
apt-get update -y
apt-get install -y language-pack-ja python-pip
apt-get install apt-transport-https
apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 36A1D7869245C8950F966E92D8576A8BA88D21E9
sh -c "echo deb https://get.docker.com/ubuntu docker main\
> /etc/apt/sources.list.d/docker.list"
apt-get update -y
apt-get install -y lxc-docker

pip install -U fig
```

fig用のymlも書いておきます。中身は前述のサイトそのまんまです。

```yml
hub:
  image: selenium/hub
  ports:
    - "4444"

node:
  image: selenium/node-firefox
  expose:
    - "5555"
  links:
    - hub
```

## インスタンスの起動〜figでコンテナのセットアップ

```shell
$ vagrant up
$ vagrant ssh
$ cd /vagrant && sudo fig up -d hub
```

こんな感じで。最初はダウンロードとかがあるので、多分10分くらいは待ちます。

さて、起動が終わったら、接続の確認をします。参考サイトでは、Mac上でos2dockerを利用していて、こっちは生のVagrantですが、ぶっちゃけ確認方法は一緒です。

`sudo docker port vagrant_hub_1`
で表示されるポートに対して、http://<private_network>:<上のポート>/grid/console でアクセスすると、Selenium Gridの画面が表示されるはずです。

問題なければここで fig scale node=5 で指定したとおり、ちゃんと５つのnodeが表示されているはず。

## Turnip + Capybaraを利用する
さて、本題その２です。

これらは、実際にはEC2とかGCEとかのインスタンス内で実行する形なので、Vagrant内で実行させる形になります。

本当はプロビジョニングするときにrbenvとかも書けばよかったんですが、とりあえずは割愛ということで。

## 必要なGemのインストール

以下のようなGemfileを用意して、 `bundle install` します。ちなみに今回は rspec 経由で実行させます。

```
source 'https://rubygems.org'

gem 'rspec'
gem 'turnip
gem 'capybara'
gem 'selenium-webdriver'
```

## turnip_helper.rb

turnipが読み込むヘルパーを作っておきます。実際にはこのファイルは /vagrant/spec/に置きます。

```ruby
# coding: utf-8
require 'turnip'
require 'turnip/capybara'
require 'turnip/rspec'
require 'capybara'

#各種driverの設定
Capybara.register_driver :selenium do |app|
  Capybara::Selenium::Driver.new(app, :browser => :firefox, :url => 'http://localhost:<hubのポート>/wd/hub')
end

Capybara.configure do |config|
  config.default_driver = :selenium
  config.javascript_driver = :selenium
  config.ignore_hidden_elements = true
  config.default_wait_time = 30
end

Dir.glob("spec/steps/**/*steps.rb") { |f| load f, true }
```

ここでのミソは、hubへのアクセスの書き方でしょうか。実際には、docker port した結果を環境変数か何かで渡しておいて、それを解析してやったほうが良さそうな気はしてます。

そしたらあとはfeatureとstepsを書いていくだけです。

```
# encoding: utf-8
# language: ja

@common
機能: sample
  sample for Kabypara and Turnip on selenium on docker.

  シナリオ: Googleを訪問できれば成功
    もし googleを訪問
    ならば 成功
```

```ruby
# encoding: utf-8

steps_for :common do
  step 'googleを訪問' do 
    url = "http://www.google.com"
    visit url
  end

  step '成功' do
    expect(page).to have_content("検索")
  end
end
```

この辺りはTurnipのページを参考に書いていけばいいんじゃないかと思います。

あと忘れてはいけないのが、公式ページには書いてあるのですが、.rspecというファイルを作成して、中に一行

```
-r turnip/rspec
```

って書いておく必要があります。

実際に確認するときは、普通に

bundle exec rspec

で、確認できます。ちなみに性能ですが、上の例だと単純にGoogleにアクセスしてるだけですが、おおよそ5秒程度かかります。まぁ、Vagrant内で実行しているので、実際にネイティブなDocker内で起動させればそれなり・・・なのかもしれません。

## 最後に
*Turnip* と *Tunrip* の間違いで動かなくてあれー？ってなったのは私だけじゃないはず。

## 参考サイト

参考というかほとんど丸パクリして組み合わせただけというか。有用な記事を公開してくださってありがとうございます！

<http://qiita.com/moriyaman/items/af2a0264adbaaa0d2029>
<https://github.com/jnicklas/turnip>
<https://github.com/willnet/capybara-readme-ja>
<http://gongo.hatenablog.com/entry/2014/12/02/002148>