Rails 5.1のSystemTestCaseを試してみた


## はじめに

Rails 5.1で導入されたSystemTestCaseを使ってみたので、その記録を残しておきます。
この記事ではユーザーマスタメンテ画面で、JavaScriptを使った郵便番号自動検索機能をテストします。

## Rails 5.1のSystemTestCaseとは？

いわゆるエンドツーエンド（E2E）テストを実行するためのテストケースクラスです。
このテストケースクラスを使うと、JavaScriptを利用する画面のテストが書けるようになります。

また、サードパーティgemを使う従来の方式ではDatabaseCleanerやDatabaseRewinderを使って、テストデータのトランザクション管理に関する設定が必要でした。
しかし、SystemTestCaseはフレームワーク内でトランザクション管理をしてくれるので、こうした設定も不要になります。

## 対象バージョン

この記事では以下のバージョンで動作確認しています。
Rails 5.1はベータ版なので、正式リリースの際は多少仕様が変わる可能性もあります。ご注意ください。

- Rails 5.1.0.beta1
- Ruby 2.4.0

## Rails 5.1.0.beta1のインストール

まず、Rails 5.1をインストールします。
ただし、Rails 5.1.0.beta1はインストールしようとすると次のようなエラーが出ました。

```
$ gem install rails -v 5.1.0.beta1
ERROR:  While executing gem ... (Gem::DependencyResolutionError)
    conflicting dependencies activesupport (= 3.0.0) and activesupport (= 5.1.0.beta1)
  Activated activesupport-5.1.0.beta1
  which does not match conflicting dependency (= 3.0.0)

  Conflicting dependency chains:
    rails (= 5.1.0.beta1), 5.1.0.beta1 activated, depends on
    activesupport (= 5.1.0.beta1), 5.1.0.beta1 activated

  versus:
    rails (= 5.1.0.beta1), 5.1.0.beta1 activated, depends on
    sprockets-rails (>= 2.0.0), 2.0.0 activated, depends on
    actionpack (>= 3.0), 3.0.0 activated, depends on
    activesupport (= 3.0.0)
```

このエラーが出たら次のようなコマンドでインストールするといいようです。

```
$ ruby -rbundler/inline -e "gemfile(true) do; source 'https://rubygems.org/'; gem 'rails', '5.1.0.beta1'; end"
Fetching gem metadata from https://rubygems.org/..........
Fetching version metadata from https://rubygems.org/..
Fetching dependency metadata from https://rubygems.org/.
Resolving dependencies...
Using rake 12.0.0
Using concurrent-ruby 1.0.4
(省略)
Using sprockets-rails 3.2.0
Installing rails 5.1.0.beta1
```

参考

[Installing pre release gems is giving conflicting dependency errors · Issue \#1653 · rubygems/rubygems](https://github.com/rubygems/rubygems/issues/1653)

## サンプルプロジェクトの作成

Railsのバージョンを確認します。

```
$ rails -v
Rails 5.1.0.beta1
```

サンプルプロジェクトを作成します。

```
$ rails new rails-5-1-sandbox
      create  
      create  README.md
      create  Rakefile
(省略)
         run  bundle exec spring binstub --all
* bin/rake: spring inserted
* bin/rails: spring inserted
```

## scaffoldを使った画面作成

次にscaffoldを使って簡単な画面を作ります。
今回は名前と郵便番号と住所を持った、ユーザーのマスタメンテ画面を作ります。

```
$ rails g scaffold User name postal_code address
$ rails db:migrate
```

この時点で`test/system`ディレクトリが作られ、内部に`user_test.rb`が作成されます。

```ruby:test/system/user_test.rb
require "application_system_test_case"

class UsersTest < ApplicationSystemTestCase
  # test "visiting the index" do
  #   visit users_url
  #
  #   assert_selector "h1", text: "User"
  # end
end
```

## ChromeDriverのインストール

試しにシステムテストを実行してみましょう。
まず、`test/system/user_test.rb`のコメントを外します。

```ruby:test/system/user_test.rb
require "application_system_test_case"

class UsersTest < ApplicationSystemTestCase
  test "visiting the index" do
    visit users_url
  
    assert_selector "h1", text: "User"
  end
end
```

テストを実行します。

```
$ rake                          
Run options: --seed 1731

# Running:

.......E

Error:
UsersTest#visiting the index:
Selenium::WebDriver::Error::WebDriverError:  Unable to find chromedriver. Please download the server from http://chromedriver.storage.googleapis.com/index.html and place it somewhere on your PATH. More info at https://github.com/SeleniumHQ/selenium/wiki/ChromeDriver.

    test/system/users_test.rb:5:in `block in <class:UsersTest>'

Error:
UsersTest#visiting the index:
Selenium::WebDriver::Error::WebDriverError:  Unable to find chromedriver. Please download the server from http://chromedriver.storage.googleapis.com/index.html and place it somewhere on your PATH. More info at https://github.com/SeleniumHQ/selenium/wiki/ChromeDriver.

    


bin/rails test test/system/users_test.rb:4



Finished in 0.225936s, 35.4083 runs/s, 39.8343 assertions/s.

8 runs, 9 assertions, 0 failures, 1 errors, 0 skips
```

するとエラーが出てしまいました。
どうやらChromeDriverというドライバが必要なようです。

https://sites.google.com/a/chromium.org/chromedriver/ にアクセスし、ChromeDriverをダウンロードします。
ただし、ローカルPCにインストールされているChromeのバージョンによって、対応するChromeDriverのバージョンも異なるようなので情報をよく確認してください。

今回は以下の組み合わせで動作確認しています。

- Chrome 56.0.2924.87
- ChromeDriver 2.27

ChromeDriverはPATHの通ったディレクトリに配置する必要があります。

以下のコマンドを打つと、自分のマシンのPATHが確認できます。

```
$ echo $PATH
/usr/local/bin:...
```

僕は`/usr/local/bin`に配置しました。（どこがベストなのか確信はありませんが）

```
$ cp chromedriver /usr/local/bin
```


（2017.7.11追記）Macユーザーの方はHomebrewでインストールするのも良さそうです。

```
$ brew update && brew install chromedriver
```


ChromeDriverのセットアップが完了したら、もう一度テストを実行します。

```
$ rake                                    
Run options: --seed 32591

# Running:

Puma starting in single mode...
* Version 3.7.1 (ruby 2.4.0-p0), codename: Snowy Sagebrush
* Min threads: 0, max threads: 1
* Environment: test
* Listening on tcp://0.0.0.0:58176
Use Ctrl-C to stop
........

Finished in 2.568361s, 3.1148 runs/s, 5.8403 assertions/s.

8 runs, 15 assertions, 0 failures, 0 errors, 0 skips
```

今度はちゃんとパスしました。

なお、テストを実行するとChromeが実際に起動して自動実行されます。
ただし、フォーカスを奪われたりはしないので、普通にPCを操作できます。

## JavaScriptを使った機能のテスト

次に、ちゃんとJavaScriptを使った機能をテストできるか確認してみましょう。
今回は郵便番号の自動検索機能を付けてみます。

### 郵便番号自動検索機能の実装

郵便番号の自動検索にはyubinbango.jsを利用することにします。

https://github.com/yubinbango/yubinbango

yubinbango.jsを組み込みます。

```erb:app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
  <head>
    <!-- 省略 -->
    <script src="https://yubinbango.github.io/yubinbango/yubinbango.js" charset="UTF-8"></script>
  </head>

  <body>
    <%= yield %>
  </body>
</html>
```

フォームに対してyubinbango.jsの実行に必要なclassを付与します。

```erb:app/views/users/_form.html.erb
<%= form_for(user, html: { class: 'h-adr' }) do |f| %>
  <span class="p-country-name" style="display:none;">Japan</span>
  <!-- 省略 -->

  <div class="field">
    <%= f.label :postal_code %>
    <%= f.text_field :postal_code, class: 'p-postal-code' %>
  </div>

  <div class="field">
    <%= f.label :address %>
    <%= f.text_field :address, class: 'p-region p-locality p-street-address p-extended-address' %>
  </div>
  
  <!-- 省略 -->
<% end %>
```

実際にブラウザで動作確認します。

![pcTIxen9DB.gif](https://qiita-image-store.s3.amazonaws.com/0/7465/f046b55f-5586-da47-2cdc-2720497b95a8.gif "pcTIxen9DB.gif")

ブラウザ上ではちゃんと自動検索できているようです。

### 郵便番号自動検索機能のテスト

ではテストを書いてみましょう。
テストデータのトランザクション管理もうまく動いているかどうか確認するため、今回は`setup`メソッド内で予めユーザーのデータを作成しておきます。

```ruby:test/system/user_test.rb
require "application_system_test_case"

class UsersTest < ApplicationSystemTestCase
  setup do
    # テストデータの作成
    @user = User.create!(name: 'いとう')
  end

  test "yubinbango.js" do
    # User編集画面を開く
    visit edit_user_path(@user)

    # Nameに"いとう"が入力されていることを検証する
    assert has_field?('Name', with: 'いとう')

    # 郵便番号を入力
    fill_in 'Postal code', with: '158-0083'
    # 住所が自動入力されたことを検証する
    assert has_field?('Address', with: '東京都世田谷区奥沢')

    # 更新実行
    click_button 'Update User'

    # 正しく更新されていること（＝画面の表示が正しいこと）を検証する
    assert_text 'User was successfully updated.'
    assert_text 'いとう'
    assert_text '158-0083'
    assert_text '東京都世田谷区奥沢'
  end
end
```

テストを実行します。

```
$ rake
Run options: --seed 53971

# Running:

.......Puma starting in single mode...
* Version 3.7.1 (ruby 2.4.0-p0), codename: Snowy Sagebrush
* Min threads: 0, max threads: 1
* Environment: test
* Listening on tcp://0.0.0.0:59153
Use Ctrl-C to stop
.

Finished in 2.370198s, 3.3752 runs/s, 6.3286 assertions/s.

8 runs, 15 assertions, 0 failures, 0 errors, 0 skips
```

ちゃんとパスしましたね！
JavaScriptを使った機能も正しく動いているようです。

### テスト失敗時のスクリーンショットを確認する

SystemTestCaseはテスト失敗時にスクリーンショットを自動的に撮ってくれます。
試しに先ほどのテストを書き換えて、わざと失敗させてみましょう。

```ruby
# 郵便番号を入力
fill_in 'Postal code', with: '158-0083'
# 住所が自動入力されたことを検証する
assert has_field?('Address', with: 'ニューヨーク')
```

テストを実行します。

```
$ rake
Run options: --seed 62244

# Running:

Puma starting in single mode...
* Version 3.7.1 (ruby 2.4.0-p0), codename: Snowy Sagebrush
* Min threads: 0, max threads: 1
* Environment: test
* Listening on tcp://0.0.0.0:59235
Use Ctrl-C to stop
[Screenshot]: tmp/screenshots/failures_test_yubinbango.js.png

F

Failure:
UsersTest#test_yubinbango.js [/Users/jit/dev/sandbox/rails-5-1-sandbox/test/system/users_test.rb:19]:
Expected false to be truthy.


bin/rails test test/system/users_test.rb:9

.......

Finished in 4.547667s, 1.7591 runs/s, 2.4188 assertions/s.

8 runs, 11 assertions, 1 failures, 0 errors, 0 skips
```

予想通りテストが失敗しました。

テストが失敗すると、`tmp/screenshots`にスクリーンショットが保存されます。

```
$ ls tmp/screenshots 
failures_test_yubinbango.js.png
```

以下はそのスクリーンショットです。
これを見れば、テストが失敗した原因を調査しやすくなります。

![failures_test_yubinbango.js.png](https://qiita-image-store.s3.amazonaws.com/0/7465/a1973945-3875-655d-3bb4-ea5ef44cf758.png "failures_test_yubinbango.js.png")

なお、デフォルトでは画面サイズが1400x1400で設定されています。

```ruby:test/application_system_test_case.rb
require "test_helper"

class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
  driven_by :selenium, using: :chrome, screen_size: [1400, 1400]
end
```

## 応用：Poltergeistでテストする

デフォルトのSystemTestCaseでは、予めChromeとChromeDriverをインストールしておく必要があります。
ローカルPCで実行する場合はあまり問題になりませんが、CI環境で実行する場合はChromeのインストールがネックになるかもしれません。

そこで、Chromeの代わりにPhantomJS + Poltergesitでテストを実行できるように設定を変更してみます。

まず、ローカルマシンにPhantomJSをインストールします。
Macの場合、Homebrewからインストールできます。

```
$ brew install phantomjs
```

続いてGemfileに`poltergeist`を追加し、`bundle install`します。

```diff:Gemfile
 group :development, :test do
   # Call 'byebug' anywhere in the code to stop execution and get a debugger console
   gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
   # Adds support for Capybara system testing and selenium driver
   gem 'capybara', '~> 2.7.0'
-  gem 'selenium-webdriver'
+  gem 'poltergeist'
 end
```

それから、`test/application_system_test_case.rb`を書き換えます。

```diff:test/application_system_test_case.rb
 require "test_helper"
+require "capybara/poltergeist"

 class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
-  driven_by :selenium, using: :chrome, screen_size: [1400, 1400]
+  driven_by :poltergeist, screen_size: [1400, 1400]
 end
```

これで準備完了です。テストを実行してみましょう。

```
$ rake
Run options: --seed 38389

# Running:

Puma starting in single mode...
* Version 3.7.1 (ruby 2.4.0-p0), codename: Snowy Sagebrush
* Min threads: 0, max threads: 1
* Environment: test
* Listening on tcp://0.0.0.0:59623
Use Ctrl-C to stop
........

Finished in 1.908633s, 4.1915 runs/s, 7.8590 assertions/s.

8 runs, 15 assertions, 0 failures, 0 errors, 0 skips
```

Poltergeistでも同じように実行できました。
テストが失敗したときもChromeの場合と同様にスクリーンショットが保存されます。

## サンプルアプリケーションのコード

この記事で使ったサンプルアプリケーションはGitHubにpushしてあります。

https://github.com/JunichiIto/rails-5-1-system-test-sandbox

## 参考：CapybaraのAPIドキュメント

`fill_in`のような画面操作のメソッドは以下のAPIドキュメントに載っています。

http://www.rubydoc.info/gems/capybara/2.7.1/Capybara/Node/Actions

`assert_text`や`has_field?`のようなマッチャは以下のAPIドキュメントに載っています。

http://www.rubydoc.info/gems/capybara/2.7.1/Capybara/Node/Matchers

また、RSpecでCapybaraを使う場合も基本的な考え方は同じなので、システムテストの書き方に迷ったときは以下の記事が参考になるかもしれません。

[使えるRSpec入門・その4「どんなブラウザ操作も自由自在！逆引きCapybara大辞典」 \- Qiita](http://qiita.com/jnchito/items/607f956263c38a5fec24) [![](http://b.st-hatena.com/entry/image/http://qiita.com/jnchito/items/607f956263c38a5fec24)](http://b.hatena.ne.jp/entry/http://qiita.com/jnchito/items/607f956263c38a5fec24)

## 2017.4.18追記：ChromeDriverをアップデートする

この記事を書いてしばらくしてから同じテストを実行してみると、次のようなエラーが出てテストが失敗しました。

```
Minitest::UnexpectedError: Selenium::WebDriver::Error::UnknownError: unknown error: cannot get automation extension
from unknown error: page could not be found: chrome-extension://aapnijgdinlhnhlmodcfapnahmbfebeb/_generated_background_page.html
  (Session info: chrome=57.0.2987.133)
  (Driver info: chromedriver=2.27.440174 (e97a722caafc2d3a8b807ee115bfb307f7d2cfd9),platform=Mac OS X 10.12.4 x86_64)
    test/system/users_test.rb:11:in `block in <class:UsersTest>'
test/system/users_test.rb:11:in `block in <class:UsersTest>'
Finished in 21.74418s
1 tests, 0 assertions, 0 failures, 1 errors, 0 skips

Process finished with exit code 0
```

どうもChrome本体が新しくなって、ChromeDriverが対応できなくなってしまったようです。
というわけで以下のような手順でドライバをアップデートしました。

1. 現在インストールされているChromeのバージョンを調べる
    - `Version 57.0.2987.133 (64-bit)`でした
    - この情報は上のログにも出ています
2. 現在インストールされているChromeDriverのバージョンを調べる
    - `chromedriver -v`
    - `2.27.440174`でした
    - この情報は上のログにも出ています
3. https://sites.google.com/a/chromium.org/chromedriver/ にアクセスし、インストールされているChromeのバージョンに対応したChromeDriver（おそらく最新版）をダウンロードする
    - 2017.4.18時点の最新版は`2.29.461585`
4. ダウンロードしたChromeDriverをPATHの通ったディレクトリにコピーする
    - 例 `cp chromedriver /usr/local/bin`
5. 念のためChromeDriverのバージョンが新しくなったことを確認する
    - `chromedriver -v`
6. テストを実行してちゃんと動くことを確認する

もしくはHomebrewを使って、ChromeDriverをアップデートするのが手っ取り早いかもしれません。

参考: http://brewformulas.org/Chromedriver

今まで動いてたテストが突然エラーになったら、ChromeとChromeDriverのバージョンをチェックしてみてください。

## 2017.4.19追記：HeadlessモードでChromeを実行する

テストの実行中に画面を表示しないHeadlessモードでChromeを起動することもできます。
詳しくは下記の記事をご覧ください。

[【暫定版】Rails 5\.1のSystemTestCaseでHeadlessモードのChromeを使ってみる \- Qiita](http://qiita.com/jnchito/items/7de4c13d07d2bfb713d3)

## まとめ

というわけで、この記事ではRails 5.1で導入されたSystemTestCaseの使い方を簡単に説明しました。
Railsが標準でE2Eテストをサポートしたことにより、今後はE2Eテストがより身近なものになってきそうです。
Modelのテストだけでなく、画面の表示やJavaScriptを使った機能のテストも積極的に書いていきましょう！

### あわせて読みたい

Rails 5.1の主要な変更点はこちらにまとめてあります。

[Rails 5\.1の変更点まとめ \- Qiita](http://qiita.com/jnchito/items/b0b47ecfc33de4f9b72f) [![](http://b.st-hatena.com/entry/image/http://qiita.com/jnchito/items/b0b47ecfc33de4f9b72f)](http://b.hatena.ne.jp/entry/http://qiita.com/jnchito/items/b0b47ecfc33de4f9b72f)