【暫定版】Rails 5.1のSystemTestCaseでHeadlessモードのChromeを使ってみる


## はじめに

2017年4月13日に、Headlessモード付きのChromeがまもなく登場するというアナウンスがありました。
下記ドキュメントによるとバージョン59からHeadlessモードが導入されるとのことです。

参考: [Headless mode \- Chrome Platform Status](https://www.chromestatus.com/feature/5678767817097216)

Headless(ヘッドレス)モードとは、GUIを起動しない（つまり目に見える形でブラウザを起動しない）モードのことです。
これによりブラウザを自動実行させる際の速度が向上したり、GUIを持たないサーバー上でブラウザを実行できたりするようになります。

また、Rails 5.1では新しくSystemTestCaseというテストケースが導入されました。
SystemTestCaseはエンドツーエンド（E2E）テスト、すなわちブラウザ上の動きを検証するためのテストケースです。
これを使えばJavaScriptを利用する画面のテストケースも書けるようになります。

参考: [Rails 5\.1のSystemTestCaseを試してみた \- Qiita](http://qiita.com/jnchito/items/4d01f2faa1deee36bd27)

SystemTestCaseはデフォルトでChromeを起動するようになっています。
ですが、Headlessモードではないため、ローカルマシンで実行するとChromeが自動的に起動して画面に表示されます。

そこでこの記事ではSystemTestCaseでHeadlessモードのChromeを使う方法を紹介します。

### 
おことわり



本記事の執筆時点（2017年4月19日）ではRails 5.1も、Chrome 59もまだ公式にリリースされていません。
公式リリース後にはこの記事の内容が使えなくなるかもしれないのでご注意ください。


### 2017.7.26追記：正式リリース版でも試してみました

正式リリース版が出揃ったので、以下の環境（Mac環境）で試してみました。

- Rails 5.1.2
- Chrome 59.0.3071.86
- ChromeDriver 2.31
- Capybara 2.14.4
- selenium-webdriver 3.4.4

上記のバージョンを使うと、後述する「ウインドウサイズの変更をスキップする（一時的な回避策）」が不要になっていました！

動作確認したい方はGitHubの以下のブランチを動かしてみてください。

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

## デモ

Headlessモードを使うと、こんな感じでテストを実行した際にChromeが画面に表示されなくなります。

<blockquote class="twitter-tweet" data-lang="en"><p lang="ja" dir="ltr">Rails 5.1のSystemTestCaseでHeadlessモードのChromeを使ってみた。(ちょっとわかりにくいけど、Chromeが起動しても画面に表示されないところがポイントです) <a href="https://t.co/suoXbtaCeP">pic.twitter.com/suoXbtaCeP</a></p>&mdash; Junichi Ito (伊藤淳一) (@jnchito) <a href="https://twitter.com/jnchito/status/854472332638683137">April 18, 2017</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>

それでは以下が手順です。

## 手順

### ふつうにSystemTestCaseを使うテストコードを書く

最初にHeadlessモードではないChromeを使ってテストコードを書きます。
本記事では以下の記事で作成したテストコードをベースにして説明を進めます。

[Rails 5\.1のSystemTestCaseを試してみた \- Qiita](http://qiita.com/jnchito/items/4d01f2faa1deee36bd27)

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

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

```ruby:test/system/users_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
```

### Chrome 59をインストールする

Headlessモード付きのChromeはバージョン59以降で導入されますが、本記事の執筆時点ではまだ公式にリリースしていません。
そこで以下のURLから開発中バージョンのChromeを入手し、インストールします。

https://www.google.com/chrome/browser/desktop/index.html?extra=devchannel

インストール後、Chromeのバージョンが59以上になっていることを確認します。

![Screen Shot 2017-04-19 at 7.20.03.png](https://qiita-image-store.s3.amazonaws.com/0/7465/8b65c0aa-4d46-99fa-869e-f84ff4e049a5.png "Screen Shot 2017-04-19 at 7.20.03.png")

### 最新版のChromeDriverをインストールする

下記のリンクから最新版のChromeDriverを入手し、インストールします。

https://sites.google.com/a/chromium.org/chromedriver/downloads

本記事の執筆時点ではバージョン2.29が最新バージョンです。
ただし、Chrome 58までしかサポートしていないため、一部使えない機能が出てきます。（後述）

![Screen Shot 2017-04-19 at 7.23.30.png](https://qiita-image-store.s3.amazonaws.com/0/7465/9760ce06-d994-8768-c6ad-9d763334ef9d.png "Screen Shot 2017-04-19 at 7.23.30.png")

ChromeDriverのインストール方法は以下の記事を参考にしてください。

[Rails 5\.1のSystemTestCaseを試してみた \- Qiita](http://qiita.com/jnchito/items/4d01f2faa1deee36bd27)

ChromeDriverをインストールしたら、念のためバージョンを確認してください。

```
$ chromedriver -v               
ChromeDriver 2.29.461585 (0be2cd95f834e9ee7c46bcc7cf405b483f5ae83b)
```


### Railsやテスト関連のgemをアップデートする

この記事では以下のバージョンのgemで動作確認しています。

- rails 5.1.0.rc1
- capybara 2.13.0
- selenium-webdriver 3.3.0

バージョンが古い場合はGemfileを更新して`bundle update`してください。

```ruby:Gemfile
gem 'rails', '~> 5.1.0.rc1'

group :development, :test do
  gem 'capybara', '~> 2.13.0'
  gem 'selenium-webdriver', '~> 3.3.0'
end
```

### HeadlessモードでChromeを起動するようにオプションを付ける

環境のアップデートが終わったので、いよいよSystemTestCaseをHeadlessモードで動かすようにオプションを付けます。

```diff:test/application_system_test_case.rb
 require "test_helper"
  
 class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
-  driven_by :selenium, using: :chrome, screen_size: [1400, 1400]
+  caps = Selenium::WebDriver::Remote::Capabilities.chrome("chromeOptions" => {"args" => %w(--headless)})
+  driven_by :selenium, using: :chrome, screen_size: [1400, 1400], options: { desired_capabilities: caps }
 end
```

それではテストを実行してみましょう。

```
$ rails test:system
Run options: --seed 22839

# Running:

E

Error:
UsersTest#test_yubinbango.js:
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: headless chrome=59.0.3071.9)
  (Driver info: chromedriver=2.29.461585 (0be2cd95f834e9ee7c46bcc7cf405b483f5ae83b),platform=Mac OS X 10.12.4 x86_64)
    test/system/users_test.rb:11:in `block in <class:UsersTest>'

Error:
UsersTest#test_yubinbango.js:
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: headless chrome=59.0.3071.9)
  (Driver info: chromedriver=2.29.461585 (0be2cd95f834e9ee7c46bcc7cf405b483f5ae83b),platform=Mac OS X 10.12.4 x86_64)
    


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



Finished in 21.419511s, 0.0467 runs/s, 0.0000 assertions/s.

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

おっと、エラーが出てしまいましたね。
そこで次のステップでこのエラーを回避します。

### ウインドウサイズの変更をスキップする（一時的な回避策）

このエラーの原因を調べたところ、どうやらテスト実行中にブラウザのウインドウサイズを変更しようとするところでエラーが発生しているようでした。

これはまだChromeDriverがChrome 59を公式サポートしていないのが原因かもしれません。
というわけで、この問題が解決されるまで以下のようなモンキーパッチを当てて、一時的にウインドウサイズのリサイズ処理をスキップすることにします。

```ruby:config/initializers/system_testing.rb
begin
  require 'action_dispatch/system_testing/driver'

  module ActionDispatch
    module SystemTesting
      class Driver
        private

        # Monkey patch for ChromeDriver 2.29
        def register
          Capybara.register_driver @name do |app|
            Capybara::Selenium::Driver.new(app, { browser: @browser }.merge(@options)).tap do |driver|
              # driver.browser.manage.window.size = Selenium::WebDriver::Dimension.new(*@screen_size)
            end
          end
        end
      end
    end
  end
rescue LoadError
  # ignore
end
```

こうしてやるとテストがパスするはずです。

```
$ rails test:system
Run options: --seed 58423

# 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:55250
Use Ctrl-C to stop
.

Finished in 1.782806s, 0.5609 runs/s, 3.3655 assertions/s.

1 runs, 6 assertions, 0 failures, 0 errors, 0 skips
```

ChromeDriverがChrome 59を公式サポートするようになれば、このモンキーパッチは不要になるはずなので削除するようにしてください。

SystemTestCaseでHeadlessモードのChromeを使用する手順は以上です！

### 参考：diffを見る

HeadlessモードのChromeで動かすために加えた変更点は下記のリンクにあるdiffで確認できます。

[Rails 5\.1 rc1 with Headless Chrome by JunichiIto · Pull Request \#1 · JunichiIto/rails\-5\-1\-system\-test\-sandbox](https://github.com/JunichiIto/rails-5-1-system-test-sandbox/pull/1/files)

## 備考

### シンプルなスクリプトでHeadlessモードの動作を確認する

Rails環境なしで手っ取り早くChromeをHeadlessモードで動かしてみたい場合は、以下のようなコードで動作確認することができます。

```ruby
require 'selenium-webdriver'

caps = Selenium::WebDriver::Remote::Capabilities.chrome("chromeOptions" => {"args" => %w(--headless)})
driver = Selenium::WebDriver.for :chrome, desired_capabilities: caps
driver.navigate.to "http://google.com"

element = driver.find_element(:name, 'q')
element.send_keys "Hello WebDriver!"
element.submit

puts driver.title

driver.quit
```

【謝辞】上記のコードは下記ブログの内容を参考にさせてもらいました。

[rubyからchrome操作してみた by selenium\-webdriver \- shoprevのブログ](http://shoprev.hatenablog.com/entry/2014/04/14/210529)

### PhantomJSからHeadless Chromeへの移行が急速に進むかも？

これまでRailsのテスト（RSpecのフィーチャスペック等）ではHeadlessブラウザとしてPhantomJS + Poltergeistがよく使われていました。
しかし、Headlessモード付きのChromeが登場したことを受けて、PhantomJSのメンテナが「PhantomJSはもうメンテナンスしない」とアナウンスしました。

[\[Announcement\] Stepping down as maintainer \- Google グループ](https://groups.google.com/forum/#!topic/phantomjs/9aI5d-LDuNE)

なので、これからはPhantomJSからHeadless Chromeへの移行が急速に進む可能性が高いかもしれません。

### まだ試していないこと

以下のようなユースケースではまだHeadless Chromeを試していません。

- CI環境（CircleCI、Wercker、Travis等）でHeadlessモードのChromeを実行する
- RSpecのフィーチャスペックでHeadlessモードのChromeを実行する

実際に試した人がいたらコメント等で教えてもらえると助かります :bow: 

（2017.6.15追記）
RSpecで使う場合はこちらの記事が参考になりそうです。

- [RSpec の feature spec でヘッドレス Chrome を使う \- Speee DEVELOPER BLOG](http://tech.speee.jp/entry/2017/06/15/135636)

（2017.7.26追記）
CI環境で動かしたり、RSpecと組み合わせて動かしたりするバージョンも作ってみました。

- [CI対応版](https://github.com/JunichiIto/rails-5-1-system-test-sandbox/tree/with-travisci)（Travis、CircleCI）
  - [`.travis.yml`](https://github.com/JunichiIto/rails-5-1-system-test-sandbox/blob/with-travisci/.travis.yml)と[`circle.yml`](https://github.com/JunichiIto/rails-5-1-system-test-sandbox/blob/with-travisci/circle.yml)を参照。
- [RSpec版](https://github.com/JunichiIto/rails-5-1-system-test-sandbox/tree/with-rspec)
  - [`rails_helper.rb`](https://github.com/JunichiIto/rails-5-1-system-test-sandbox/blob/with-rspec/spec/rails_helper.rb#L58-L62)を参照。

こちらも参考にしてみてください。

## まとめ

というわけでこの記事ではRails 5.1のSystemTestCaseでHeadlessモードのChromeを使う方法を紹介しました。
HeadlessモードのChromeが気になっているRailsプログラマのみなさんは、よかったら参考にしてみてください。

### 参考文献

- [Runnng Selenium with the new headless Chrome](https://intoli.com/blog/running-selenium-with-headless-chrome/)
- [Capabilities & ChromeOptions \- ChromeDriver \- WebDriver for Chrome](https://sites.google.com/a/chromium.org/chromedriver/capabilities)