【2019年7月版・動画付き】Cloud9上でEveryday Railsの`js: true`付きのフィーチャスペックを実行する手順

<b>注：2020年8月版の手順を最後に追記しています。</b>

## はじめに

先ほど、こちらの記事を拝見しました。

[【Everyday Rails 6章 JavaScriptテスト】AWS Cloud9でChromeエラーが出たときの対処法 \- Qiita](https://qiita.com/natecotus/items/51443acfbdd539e04ee8)

詳しくは上記の記事を読んでいただきたいのですが、簡単にまとめると、

- 「[Everyday Rails - RSpecによるRailsテスト入門](https://leanpub.com/everydayrailsrspec-jp)」（僕が翻訳した電子書籍。以下Everyday Rails）の第6章に書いてある`js: true`付きのテストコードをCloud9で動かそうとしたらエラーが起きた
- [参考記事](https://kuracux.hatenablog.jp/entry/2018/12/30/182617)を読んで対応しようとしたが、それでもダメだった
- Chromeを使うのはあきらめてPhantomjs + poltergeistを使うようにしたらテストが動いた

というものです。

たしかに、本の中では第2の選択肢としてPhantomjs + poltergeistを紹介しています。
ですが、こちらはすでに開発が止まっているため、できることならChromeを使って実行したいところです。

そこで、この記事ではCloud9上でサンプルコードを動かしながらEveryday Railsを勉強しようとしている読者さんのために、Cloud9上で`js: true`付きのフィーチャスペックを実行する手順（Phantomjs + poltergeistではなく、Chromeを使う手順）を紹介します。

### 実行環境

この記事は以下の環境で動作確認しています。

- Amazon Linux AMI release 2018.03
- Ruby 2.6.3
- Chrome 75
- [everydayrails-rspec-2017](https://github.com/everydayrails/everydayrails-rspec-2017)のサンプルコード

また、サンプルコードを動かすために参照しているEveryday Railsの版は、最新の変更履歴が2019/04/08の版（chromedriver-helperではなく、webdriversを使っている版）です。

## 手順

### 1. tasks_spec.rb を準備する

今回は`spec/features/tasks_spec.rb`をCloud9上で動かせるようにしていきます。
第6章にある以下の説明文まで、本書の説明通りにサンプルコードを準備してください。

> さあ、これで準備が整いました。実際にやってみましょう。新しく作ったスペックを実行してみてください。
>
> $ bin/rspec spec/features/tasks_spec.rb

Cloud9上でこのコマンドを実行すると以下のようなエラーが発生するはずです。

```
 Failures:

  1) Tasks user toggles a task
     Failure/Error: visit root_path
     
     Webdrivers::VersionError:
       Failed to find Chrome binary or its version.
     # /home/ec2-user/.rvm/gems/ruby-2.6.3/gems/webdrivers-4.0.1/lib/webdrivers/chrome_finder.rb:10:in `version'
     # 以下省略...
```
 
もしくは次のようなエラーになっているかもしれません。
（webdrivers 4.1.0でのみ発生？ - [参考](https://github.com/titusfortner/webdrivers/issues/144)）
 
```
Failures:

  1) Tasks user toggles a task
     Failure/Error: visit root_path
     
     ArgumentError:
       wrong first argument
     # /home/ec2-user/.rvm/gems/ruby-2.6.3/gems/webdrivers-4.1.0/lib/webdrivers/system.rb:150:in `popen'
     # 以下省略...
```

### 2. Chromeブラウザをインストールする

`js: true`付きのフィーチャスペックを実行するにはChromeブラウザが必要ですが、初期状態のAmazon LinuxにはChromeがインストールされていません。
そこで、ターミナルから以下のコマンドを実行し、Chromeをインストールしてください。

```console
$ curl https://intoli.com/install-google-chrome.sh | bash
```

この記事の執筆時点ではChrome 75がインストールされました。

```
Successfully installed google-chrome-stable, Google Chrome 75.0.3770.100 .
```

### 3. ヘッドレスモードでChromeを起動できるようにする

Cloud9上ではGUIモードのChromeを起動することはできません。
ですので、代わりにヘッドレスモード（GUIを起動しないモード）でテストを実行するようにします。

`spec/support/capybara.rb`にある`selenium_chrome`を`selenium_chrome_headless`に変更してください。

```diff:spec/support/capybara.rb
-Capybara.javascript_driver = :selenium_chrome
+Capybara.javascript_driver = :selenium_chrome_headless
```

### 4. selenium-webdriverとcapybaraを最新版にする

最新のChromeを正常に操作できるよう、selenium-webdriverとcapybaraを最新版にします。

まず、Gemfileを編集してcapybaraのバージョン制限を外します。

```diff:Gemfile
 group :test do 
-  gem 'capybara', '~> 2.15.2'
+  gem 'capybara'
   gem 'webdrivers'
 end
```

続いて以下のコマンドを実行し、selenium-webdriverとcapybaraをアップデートします。

```console
$ bundle update selenium-webdriver capybara
```

この記事の執筆時点では以下のバージョンにアップデートされました。

```console
$ bundle list | grep 'selenium\|capybara'
  * capybara (3.25.0)
  * selenium-webdriver (3.142.3)
```

### 5. テストが正常に実行できることを確認する

これで準備完了です。
うまくいけば次のように`tasks_spec.rb`が正常に実行できるはずです。

```console
$ bin/rspec spec/features/tasks_spec.rb 
Running via Spring preloader in process 18989

Tasks
Capybara starting Puma...
* Version 3.8.2 , codename: Sassy Salamander
* Min threads: 0, max threads: 4
* Listening on tcp://127.0.0.1:35495
  user toggles a task

Finished in 2.13 seconds (files took 0.28209 seconds to load)
1 example, 0 failures
```

ちなみに、上の実行例では`.rspec`ファイルから`--warnings`を外して、不要な警告が表示されないようにしています。

```:.rspec
--require spec_helper
--format documentation
```

## 動画を使った、より詳しい説明

この記事の内容をもっと詳しく説明した動画をYouTubeにアップしています。
こちらもぜひご覧ください。

[https://www.youtube.com/watch?v=wIKW3mIPrdc![image](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7465/533af376-e0fd-6253-5b3f-0a55a692d234.png)](https://www.youtube.com/watch?v=wIKW3mIPrdc)


## 参考文献

- [Everyday Rails - RSpecによるRailsテスト入門](https://leanpub.com/everydayrailsrspec-jp)
- [AWS Cloud9でCapybara\+Selenium\+Chrome設定時のエラーとその対処法について\[ Everyday Rails 6章 \] \- クラクスの記録帳](https://kuracux.hatenablog.jp/entry/2018/12/30/182617)

## 追記：2020年8月版の手順
下記の質問に答える形で、2020年8月版の手順も動画にまとめました。

- [【2020年8月版】Cloud9上でEveryday Railsの\`js: true\`付きのフィーチャスペックを実行する手順 \- YouTube](https://www.youtube.com/watch?v=ZRJU1oKu6XI&feature=youtu.be)

具体的には本記事の手順4と手順5の間に以下の手順が追加で必要になります。

### webdrivers gemを使う

selenium-webdriver gemはサポートが止まっています。代わりにwebdrivers gemを使います。

```diff:Gemfile
 group :test do
   gem 'capybara'
   gem 'selenium-webdriver'
-  gem 'chromedriver-helper'
+  gem 'webdrivers'
```

```
$ bundle install
```

### spec/support/vcr.rb に設定を追加

webdrivers gemをインストールした関係で、vcrの設定を追加する必要があります。

```diff:spec/support/vcr.rb 
 VCR.configure do |config|
   config.cassette_library_dir = "#{::Rails.root}/spec/cassettes"
   config.hook_into :webmock
   config.ignore_localhost = true
+  config.ignore_hosts 'chromedriver.storage.googleapis.com'
   config.configure_rspec_metadata!
 end
```

詳しくは上記の動画を参考にしてください。

## 追記2：さらにRuby 2.6系で実行する場合

上の「2020年8月版の手順」ではRuby 2.4系で動かしたのですが、Ruby 2.6系で動かすと以下のようなエラーが出る場合があります。（masterブランチのテストコードをそのまま実行しようとすると発生します）

```
Failures:

  1) Tasks user toggles a task
     Got 0 failures and 2 other errors:

     1.1) Failure/Error: visit root_path
          
          ArgumentError:
            unknown keyword: write_timeout
          
          
          
          # /home/ec2-user/.rvm/gems/ruby-2.6.3/gems/webmock-3.0.1/lib/webmock/http_lib_adapters/net_http.rb:259:in `initialize'
          # /home/ec2-user/.rvm/gems/ruby-2.6.3/gems/webmock-3.0.1/lib/webmock/http_lib_adapters/net_http.rb:136:in `start_with_connect_without_finish'
          # /home/ec2-user/.rvm/gems/ruby-2.6.3/gems/webmock-3.0.1/lib/webmock/http_lib_adapters/net_http.rb:104:in `request'
          # /home/ec2-user/.rvm/gems/ruby-2.6.3/gems/capybara-3.32.2/lib/capybara/server.rb:56:in `block in responsive?'
          # /home/ec2-user/.rvm/gems/ruby-2.6.3/gems/webmock-3.0.1/lib/webmock/http_lib_adapters/net_http.rb:123:in `start_without_connect'
          # /home/ec2-user/.rvm/gems/ruby-2.6.3/gems/webmock-3.0.1/lib/webmock/http_lib_adapters/net_http.rb:150:in `start'
          # /home/ec2-user/.rvm/gems/ruby-2.6.3/gems/capybara-3.32.2/lib/capybara/server/checker.rb:36:in `make_request'
          # /home/ec2-user/.rvm/gems/ruby-2.6.3/gems/capybara-3.32.2/lib/capybara/server/checker.rb:28:in `http_request'
          # /home/ec2-user/.rvm/gems/ruby-2.6.3/gems/capybara-3.32.2/lib/capybara/server/checker.rb:14:in `request'
          # /home/ec2-user/.rvm/gems/ruby-2.6.3/gems/capybara-3.32.2/lib/capybara/server.rb:56:in `responsive?'
          # /home/ec2-user/.rvm/gems/ruby-2.6.3/gems/capybara-3.32.2/lib/capybara/server.rb:81:in `boot'
          # /home/ec2-user/.rvm/gems/ruby-2.6.3/gems/capybara-3.32.2/lib/capybara/session.rb:93:in `initialize'
          # /home/ec2-user/.rvm/gems/ruby-2.6.3/gems/capybara-3.32.2/lib/capybara.rb:415:in `new'
          # /home/ec2-user/.rvm/gems/ruby-2.6.3/gems/capybara-3.32.2/lib/capybara.rb:415:in `block in session_pool'
          # /home/ec2-user/.rvm/gems/ruby-2.6.3/gems/capybara-3.32.2/lib/capybara.rb:312:in `current_session'
          # /home/ec2-user/.rvm/gems/ruby-2.6.3/gems/capybara-3.32.2/lib/capybara/dsl.rb:46:in `page'
          # /home/ec2-user/.rvm/gems/ruby-2.6.3/gems/capybara-3.32.2/lib/capybara/dsl.rb:58:in `block (2 levels) in <module:DSL>'
          # ./spec/system/tasks_spec.rb:24:in `go_to_project'
          # ./spec/system/tasks_spec.rb:14:in `block (2 levels) in <top (required)>'
          # /home/ec2-user/.rvm/gems/ruby-2.6.3/gems/spring-commands-rspec-1.0.4/lib/spring/commands/rspec.rb:18:in `call'
          # -e:1:in `<main>'

     1.2) Failure/Error:
                def initialize(io, read_timeout: 60, continue_timeout: nil, debug_output: nil)
                  @read_timeout = read_timeout
                  @rbuf = ''
                  @debug_output = debug_output
            
                  @io = case io
                  when Socket, OpenSSL::SSL::SSLSocket, IO
                    io
                  when StringIO
                    PatchedStringIO.new(io.string)
          
          ArgumentError:
            unknown keyword: write_timeout
          
          
          
          # /home/ec2-user/.rvm/gems/ruby-2.6.3/gems/webmock-3.0.1/lib/webmock/http_lib_adapters/net_http.rb:259:in `initialize'
          # /home/ec2-user/.rvm/gems/ruby-2.6.3/gems/webmock-3.0.1/lib/webmock/http_lib_adapters/net_http.rb:136:in `start_with_connect_without_finish'
          # /home/ec2-user/.rvm/gems/ruby-2.6.3/gems/webmock-3.0.1/lib/webmock/http_lib_adapters/net_http.rb:104:in `request'
          # /home/ec2-user/.rvm/gems/ruby-2.6.3/gems/capybara-3.32.2/lib/capybara/server.rb:56:in `block in responsive?'
          # /home/ec2-user/.rvm/gems/ruby-2.6.3/gems/webmock-3.0.1/lib/webmock/http_lib_adapters/net_http.rb:123:in `start_without_connect'
          # /home/ec2-user/.rvm/gems/ruby-2.6.3/gems/webmock-3.0.1/lib/webmock/http_lib_adapters/net_http.rb:150:in `start'
          # /home/ec2-user/.rvm/gems/ruby-2.6.3/gems/capybara-3.32.2/lib/capybara/server/checker.rb:36:in `make_request'
          # /home/ec2-user/.rvm/gems/ruby-2.6.3/gems/capybara-3.32.2/lib/capybara/server/checker.rb:28:in `http_request'
          # /home/ec2-user/.rvm/gems/ruby-2.6.3/gems/capybara-3.32.2/lib/capybara/server/checker.rb:14:in `request'
          # /home/ec2-user/.rvm/gems/ruby-2.6.3/gems/capybara-3.32.2/lib/capybara/server.rb:56:in `responsive?'
          # /home/ec2-user/.rvm/gems/ruby-2.6.3/gems/capybara-3.32.2/lib/capybara/server.rb:73:in `boot'
          # /home/ec2-user/.rvm/gems/ruby-2.6.3/gems/capybara-3.32.2/lib/capybara/session.rb:93:in `initialize'
          # /home/ec2-user/.rvm/gems/ruby-2.6.3/gems/capybara-3.32.2/lib/capybara.rb:415:in `new'
          # /home/ec2-user/.rvm/gems/ruby-2.6.3/gems/capybara-3.32.2/lib/capybara.rb:415:in `block in session_pool'
          # /home/ec2-user/.rvm/gems/ruby-2.6.3/gems/capybara-3.32.2/lib/capybara.rb:312:in `current_session'
          # /home/ec2-user/.rvm/gems/ruby-2.6.3/gems/capybara-3.32.2/lib/capybara/dsl.rb:46:in `page'
          # /home/ec2-user/.rvm/gems/ruby-2.6.3/gems/spring-commands-rspec-1.0.4/lib/spring/commands/rspec.rb:18:in `call'
          # -e:1:in `<main>'
```

このエラーが出た場合はwebmockのバージョンを最新にすると解消します。（[参考](https://github.com/bblimke/webmock/issues/786)）

```
$ bundle update webmock
```