Ruby
Rails
Chrome
docker
docker-compose
OriginalGunosyDay 14

Headless ChromeをDocker上で動かして、E2Eのテスト

この記事はGunosy Advent Calendar 2017の14日目の記事です。
昨日は@harahenikuさんのGoを遅くしないための地味な話でした。

広告技術部の@hoshitocatです。 主な業務はRailsで広告の入稿やレポートのための管理画面の開発をしております。たまにGoで配信側のAPIを触ることがあります。

今回は管理画面でChromeのHeadlessモードを使ったE2Eのテストを導入した話をします。

PhantomJSからChromeへ

私たちはこれまでE2EのテストをPhantomJSを使っていました。
しかし、Javascriptの動作も含めたテストを書いている際に、includes()がPhantomJSだと動作しないことがわかりました。

プロダクトコードをテストコードに合わせて書き直すことも考えましたが、それでは新しく使えるようになった便利な機能をいつまでも使えなくなってしまいます。
そもそもPhantomJSは以下のようにメインメンテナーが開発を今後しないことを公表しています。(https://groups.google.com/forum/#!forum/phantomjs)

I think people will switch to it, eventually. Chrome is faster and more stable than PhantomJS. And it doesn't eat memory like crazy.

また、上記のように今後はChromeのHeadlessモードに期待している旨も書いています。

Rails側でも5.1から導入されたSystemTestCaseではデフォルトでサポートしているブラウザがChromeのようです。

インストール

環境はこちらのImageを使用しています。
(Mac上だとbrewとかで簡単にインストールできたと思います。)

zipファイルで配布されている chromedriver をダウンロードする

// 最新バージョン取得
$ CHROME_DRIVER_VERSION=`curl -sS chromedriver.storage.googleapis.com/LATEST_RELEASE`
// ZIPファイルダウンロード
$ wget -N http://chromedriver.storage.googleapis.com/$CHROME_DRIVER_VERSION/chromedriver_linux64.zip -P ~/

ファイルを解凍して、権限を付与し /usr/bin に移動します

$ unzip ~/chromedriver_linux64.zip -d ~/
$ rm ~/chromedriver_linux64.zip
$ chown root:root ~/chromedriver
$ chmod 755 ~/chromedriver
$ mv ~/chromedriver /usr/bin/chromedriver

Google Chromeは3rdPartyRepositoryのようなので、追加して上げる必要があります。

また、認証に失敗するので鍵の登録も必要みたいです。

sh -c 'wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add -'
sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google-chrome.list'

アップデートしてインストールします。

$ apt-get update -y && apt-get install -y google-chrome-stable

以下が実際に作成した Dockerfile になります。

Dockerfile
FROM ruby:2.3.5

ENV LANG C.UTF-8
ENV APP_HOME /usr/src/app

RUN mkdir -p $APP_HOME
RUN gem update --system
RUN gem install bundler && gem update bundler
RUN apt-get update && apt-get install -y unzip && \
    CHROME_DRIVER_VERSION=`curl -sS chromedriver.storage.googleapis.com/LATEST_RELEASE` && \
    wget -N http://chromedriver.storage.googleapis.com/$CHROME_DRIVER_VERSION/chromedriver_linux64.zip -P ~/ && \
    unzip ~/chromedriver_linux64.zip -d ~/ && \
    rm ~/chromedriver_linux64.zip && \
    chown root:root ~/chromedriver && \
    chmod 755 ~/chromedriver && \
    mv ~/chromedriver /usr/bin/chromedriver && \
    sh -c 'wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add -' && \
    sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google-chrome.list' && \
    apt-get update && apt-get install -y google-chrome-stable

WORKDIR $APP_HOME

COPY Gemfile \
     Gemfile.lock \
     $APP_HOME/

速度

全体を通してちゃんとしたベンチマークはしていませんが、テスト全体で10分~15分程度のものが12分~17分くらいになりました。2分~3分程度遅くなった感じです。

Sandboxオプションをつけないと動作しない

ハマった点としては、Javascriptを動作させるようなテストの場合 sandbox オプションを付与する必要がありました。
https://sites.google.com/a/chromium.org/chromedriver/help/chrome-doesn-t-start

Otherwise, if the problem only occurs in your special testing environment:
Using Chrome's alternate installer. This will install Chrome for all users. This often fixes problems if you are running Selenium as a background service.
Passing '--no-sandbox' flag when creating your WebDriver session. Special test environments sometimes cause Chrome to crash when the sandbox is enabled.

特別なテスト環境とはなんのことを言っているのか特定はできませんでしたが、セキュリティ的な問題なのでしょうか? :thinking:

以下のようにオプション追加してあげることで解決し、無事に手元で実行することができました。

rails_helper.rb
Capybara.register_driver :selenium do |app|
  Capybara::Selenium::Driver.new(app, {
    browser: :chrome,
    desired_capabilities: Selenium::WebDriver::Remote::Capabilities.chrome(
      chrome_options: {
        # NOTE: ここでno-sandboxオプション追加する
        args: %w[no-sandbox headless disable-gpu window-size=1680,1050],
      }
    )
  })
end
Capybara.javascript_driver = :selenium

ちなみにこの問題は開発環境上で発生しただけで、CI上では再現しなくハマってしまいました。

まとめ

今回はHeadlessモードのChromeをDocker上で動かしてみました。最新のブラウザでサポートされている機能が使えるようになり、プロダクトコードを変更しなくても素直にテストを書くことができました。(polyfillとか車輪の再発明的なことはしたくないですよね。)

参考URL