GitLab上でE2Eテストを回したいとなった時に色々と調査・検討したのでまとめたいと思います。
前提・環境
- オンプレではなく、SaaS版のGitLabを使用
- GitLab Runnerは特に自前では立てず、Shared Runnersを利用してます。
 
- Dockerベースのローカル開発環境
- テスト対象はAP-DB構成のシンプルなRails構成としてます
検討
E2EといえばSeleniumだろう、というところはチーム全員が経験者ということもあり思考停止で決め打ちしています。(Cypressとかは出てきません)
Seleniumのランタイムをどうするか、というところをまず検討しました。
Seleniumの利用方針
- Dockerベースのローカル開発環境を前提としており、かつalpine-linuxベースのimageだったので、まずはその前提でローカルでSeleniumを動かそうとこちらの記事等を参考に色々試したのですがついぞまともに動かず、かつブラウザバリエーションも担保しづらいのでリモートのものを利用する(Selenium Gridを利用)こととしました。
- Selenium Gridを利用するとなった場合に、公式のSeleniumHQ/docker-seleniumというものが結構よさげだったのでそれを採用しました。
Seleniumを利用するSaaS
たくさんありましたが、結論から言うと実際の利用には至ってません。
ライセンス体系や細々としたところで差異があり、簡易な比較表を以下の通り作成しています。(調査時点での情報ですので、最新情報についてはそれぞれのサイトをご覧ください)

| SWATHub | Sauce Labs | Testing Bot | BrowserStack | Appveyor | CrossBrowserTesting | |
|---|---|---|---|---|---|---|
| 日本語対応 | ◯ | × | × | × | × | × | 
| 日本語情報 | ◯(公式が日本語、サポートも日本人) | ×寄りの△ | × | △ | ◯ | ×(古い情報しかない) | 
| 公式ドキュメント | ◯ | △ | ◎ | ◎ | ◯ | ◯ | 
| 価格(全て月額) | ¥30,000/100画面 | $149/2session(実行時間無制限だと$298/2session,どのプランも年単位契約) | $90/session(monthlyだと$120) | $99/session(スマフォビューも含むと$149/session,どのプランも年単位契約) | - | $100/session(monthlyだと$120) | 
| 対応デバイス | Win,Mac,iOS,Android | Win,Mac,OSX,Linux,iOS,Android(スマフォは実機も有) | Win,Mac,OSX,Linux,iOS,Android | Win,Mac,OSX,Linux,iOS,Android | - | Win,Mac,OSX,Linux,iOS,Android(スマフォは実機も有) | 
| 対応ブラウザ | IE,Edge,Chrome,FF,Safari | IE,Edge,Chrome,FF,Safari | IE,Edge,Chrome,FF,Safari,Opera | IE,Edge,Chrome,FF,Safari,Opera | - | IE,Edge,Chrome,FF,Safari | 
| 対応アプリ形式 | Web | Web,Mobile Native | Web,Mobile Native | Web,Mobile Native | - | Web | 
| 対応Selenium ver | 2系,3系 | 2系,3系 | 2系,3系 | 2系,3系 | - | |
| スクリーンショット | ◯ | ◯ | ◯ | ◯ | △(SeleniumのAPIを自身で叩くしか無い) | |
| 動画保存 | × | ◯ | × | ◯ | - | ◯ | 
| Live Testing(SaaS上の仮想環境上のブラウザでの実行状況を確認できる機能) | × | ◯ | × | ◯ | - | ◯ | 
| Local Testing(ローカルで実行できる機能) | ◯ | ◯(Sauce Labs Connect) | ◯(TestingBotTunnel) | ◯ | - | ◯ | 
| 並行テスト | ◯ | ◯ | ◯ | ◯ | - | ◯ | 
| その他 | 画像比較ができる 実行ノードとしてBrowserStackやSauce Labsを利用できる | ブラウザ,OSのバージョンが圧倒的ラインアップ | Sauce Labsを上回るレベル 設定の自動生成も行える サンプルが充実している | 
実装
dockerを利用したローカルでの開発環境
Dockerfile
FROM ruby:2.5-alpine
WORKDIR /work/
RUN apk update && \
    apk upgrade && \
    apk add --no-cache build-base libxml2-dev libxslt-dev postgresql-dev \
            nodejs tzdata imagemagick git openssh-client curl
ADD Gemfile Gemfile.lock ./
RUN bundle install --without production
RUN gem install ffi --no-doc --no-ri
RUN rm -f tmp/pids/server.pid
CMD bundle exec rails s -p 3000 -b '0.0.0.0'
docker-compose
version: '3'
services:
  postgres:
    image: postgres:9.6
    environment:
      POSTGRES_USER: "root"
    ports:
      - "5432:5432"
  rails:
    build: .
    volumes:
      - .:/work
    environment:
      - RAILS_ENV=development
      - APP_HOST=rails
      - HUB_HOST=selenium-hub
      - HUB_PORT=4444
    ports:
      - "3000:3000"
  selenium-hub:
    image: selenium/hub:3.14
    container_name: selenium-hub
    ports:
      - "4444:4444"
  chrome:
    image: selenium/node-chrome:3.14
    depends_on:
      - selenium-hub
    environment:
      - HUB_HOST=selenium-hub
      - HUB_PORT=4444
selenium-hubの定義の仕方とかは、公式のREADMEを参考にしました。
テスト用コード
Railsアプリなので基本的にCapybara経由でSeleniumを触ることを前提とします。
また、実際のテストコードについては割愛し、docker-seleniumとCapybaraの組み合わせ方のところが明確なドキュメンテーションがなかなかなかったのでそちらを記載します。
Rspec
E2Eの手段としてFeature Specではなく、こちらでRails5.1以降で推奨されている、System Specの利用を前提としてます。
require 'capybara/rspec'
require 'selenium-webdriver'
options = ENV.has_key?('HUB_HOST') ? { url: "http://#{ENV['HUB_HOST']}:#{ENV['HUB_PORT']}/wd/hub" } : {}
Capybara.run_server = false
RSpec.configure do |config|
  config.before(:each, type: :system) do
    driven_by :selenium,
              using: :chrome,
              screen_size: [1400, 1400],
              options: options
    host! "http://#{ENV.fetch('APP_HOST', 'localhost')}:3000"
  end
  # その他雑多な定義は省略
end
肝はoptionsとhost!でしょうか。
urlオプションは、~~デフォルトのlocalhost:{各ブラウザDriver毎のデフォルトポート}というURL、つまりローカルのWebDriver経由でテスト実行をしたくないときに指定します。~~リモート(Remote WebDriver)でテストを実行したい時に指定します。
実際に定義されているURLは、selenium-hubコンテナ上で稼働しているのSelenium Grid Hub(=実態としてはSelenium Server)のURLです。
host!メソッドの引数はなんでこういう書き方になっているかというと、ローカルでRailsアプリを起動してローカルのWebDriver経由でローカルのブラウザに対してテストしたいメンバがいたためです。
Minitest
require 'test_helper'
require 'capybara/rails'
require 'capybara/minitest'
require 'selenium-webdriver'
class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
  options = ENV.has_key?('HUB_HOST') ? { url: "http://#{ENV['HUB_HOST']}:#{ENV['HUB_PORT']}/wd/hub" } : {}
  driven_by :selenium,
            using: :chrome,
            screen_size: [1400, 1400], 
            options: options
  Capybara.run_server = false
  def setup
    host! "http://#{ENV.fetch('APP_HOST', 'localhost')}:3000"
  end
  # その他雑多な(ry
end
各SystemTestCaseが上記ApplicationSystemTestCaseクラスを継承して実装していく感じになります。
細かいところはRSpec版と同じなので説明は省略します。
GitLab CI
前置きが長くなってしまいましたが、GitLab Advent Calendarの本命です。
.gitlab-ci.ymlミニマム版
services:
  - docker:dind
variables:
  GIT_SUBMODULE_STRATEGY: normal
  POSTGRES_USER: root
  DOCKER_DRIVER: overlay2
rails_e2e_test:
  stage: test
  image: docker
  retry: 2
  artifacts:
    paths:
      # capybaraを使うとデフォルトで以下のディレクトリに失敗時のスクリーンショットが残るので、失敗結果を確認できるようにキャッシュしておく
      - tmp/screenshots
    when: always
    expire_in: 4 weeks
  variables:
    APP_HOST: rails
    RAILS_ENV: development
    HUB_HOST: selenium-hub
    HUB_PORT: 4444
  script:
    - docker build -t rails-image .
    - docker network create e2e-test
    - docker run -d --net e2e-test --name postgres -e POSTGRES_USER=root postgres:9.6
    - docker run -d --net e2e-test --name rails -e APP_HOST -e RAILS_ENV -e HUB_HOST -e HUB_PORT -v $PWD:/work rails-image
    - docker run -d --net e2e-test --name selenium-hub selenium/hub:3.14
    - docker run -d --net e2e-test --name chrome -e HUB_HOST -v /dev/shm:/dev/shm selenium/node-chrome:3.14
    - docker exec -i rails rails db:reset
    - docker exec -i rails rails spec:system
実際にはいろんなミドルウェア使ってるので、もっとdockerコンテナを起動しているのですが、AP-DB構成だと最小構成で上記みたいな感じになるかと思います。
servicesでDockerイメージを指定すると、それらのコンテナが自動的に同一ネットワーク上で起動されるので、要はdockerコマンドの--netオプション相当のことが自動で行われるので、こんなほぼ脳筋な方法よりGitLabの便利なやり方に乗っ取りたかったのですが、
- 
services側で定義したイメージに環境変数を食わせられない- 
こちらを見たら、commandを指定して環境変数をexportすることでいけたかもしれません。。。
 
- 
こちらを見たら、
- 
selenium-node-chromeイメージの起動時のボリュームマウントの設定を事前にやっておく方法がパッと思いつかなかった。- 事前にDockerfileなりに定義しておいて予めビルドし、レジストリにpushしておくとかになるのでしょうか。Docker力が雑魚なのでお詳しい方いらっしゃればご教示ください。。
 
また、docker-composeを利用して実行すると言う方法もあり、確かにそれだとローカルの開発環境と平仄が揃うというメリットがあるので試してみましたが、実行時間が上記のほうがまだ早かったので上記にしました。
.gitlab-ci.ymlちょっと整理した版
実際Railsアプリ用のイメージを毎回フルビルドするのはあまり効率的では無いので、こちらを参考にGitLab内にホストされているDockerレジストリをキャッシュとして利用します。
services:
  - docker:dind
variables:
  GIT_SUBMODULE_STRATEGY: normal
  POSTGRES_USER: root
  RAILS_CI_IMAGE: registry.gitlab.com/your-group/your-repository/rails-image
  DOCKER_DRIVER: overlay2
# これとは別に定期的にDOckerイメージをフルビルドするジョブを定義してもいいかもしれません
build-ci-from-cache:
  stage: build-ci
  image: docker
  # よくコケるのでretryできるようにしておく
  retry: 2
  script:
    # see https://docs.gitlab.com/ee/ci/docker/using_docker_build.html#using-docker-caching
    - docker login -u gitlab-ci-token -p $CI_JOB_TOKEN registry.gitlab.com
    - docker pull $RAILS_CI_IMAGE:latest || true
    - docker build --cache-from $RAILS_CI_IMAGE:latest -t $RAILS_CI_IMAGE:latest .
    # 最新のビルド済Docker ImageをGitLab上のDocker Registryにpushする
    - docker push $RAILS_CI_IMAGE:latest
rails_e2e_test:
  stage: test
  image: docker
  retry: 2
  artifacts:
    paths:
      - tmp/screenshots
    when: always
    expire_in: 4 weeks
  variables:
    APP_HOST: rails
    RAILS_ENV: development
    HUB_HOST: selenium-hub
    HUB_PORT: 4444
  script:
    - docker login -u gitlab-ci-token -p $CI_JOB_TOKEN registry.gitlab.com
    - docker pull $RAILS_CI_IMAGE:latest
    - docker network create e2e-test
    - docker run -d --net e2e-test --name postgres -e POSTGRES_USER=root postgres:9.6
    - docker run -d --net e2e-test --name rails -e APP_HOST -e RAILS_ENV -e HUB_HOST -e HUB_PORT -v $PWD:/work $RAILS_CI_IMAGE:latest
    - docker run -d --net e2e-test --name selenium-hub selenium/hub:3.14
    - docker run -d --net e2e-test --name chrome -e HUB_HOST -v /dev/shm:/dev/shm selenium/node-chrome:3.14
    - docker exec -i rails rails db:reset
    - docker exec -i rails rails spec:system
所感
- 
.gitlab-ci.ymlの構成、多分まだまだ改善の余地ある
- もともとオンプレJenkinsユーザで、転職してからSaaSのGitLab CIをまともに触り始めたのですが、CI環境メンテナンス要らないとか素敵で、GitLab使っている以上あまり無理して他のCIサービス使う理由も無いので、当分はGitLabに積極的にロックインされていくぞ!!という気持ち
参考
- GitHub - SeleniumHQ/docker-selenium: Docker images for Selenium Grid Server (Standalone, Hub, and Nodes).
- RSpec 3.7 has been released!
- GitHub - teamcapybara/capybara: Acceptance test framework for web applications
- WebDriver について私が知っていること (2017 年版) - ひだまりソケットは壊れない
- Configuring GitLab Runners | GitLab
- Building Docker images with GitLab CI/CD | GitLab
- Using Docker images | GitLab
- .gitlab-ci.yml によるジョブの設定方法(日本語訳) - Qiita
- CapybaraからHeadless Chromeを動かす環境をDockerで構築する - Qiita
