Docker 上で実行した feature spec をデバッグする方法


概要

Docker + Selenium + Headless Chrome な環境で feature spec を実行する時に、ブラウザの開発者ツールを使ってデバッグしたかったので調べた。

こんな感じで使える↓

サンプルコードはこちら
https://github.com/yszk0123/selenium-docker-rails-example

今回の例では Chrome を使ったが、Firefox も可能なはず。

検証環境

やり方

ブラウザ・Selenium サーバー・VNC サーバーなどが同梱されたselenium/standalone-chrome-debug という docker イメージを使う1
Docker for Mac には
VNC クライアントが同梱されているので2、特別な設定なしにコンテナの外からブラウザを操作できる。

今回は次の2つのコンテナに分ける。

  1. テストを実行するコンテナ app
  2. Selenium サーバー用のコンテナ chrome

また、テスト用に以下のホスト・ポートを使用する (docker-compose のネットワーク機能を活用)。

  • アプリケーションサーバー http://app.com:3000
  • Selenium サーバー http://chrome.com:4444
  • VNC サーバー http://localhost:5900 (ホストマシンからアクセス)

docker-compose の設定

docker-compose.yml
version: "2"
services:
  # テストを実行する Rails サーバー。Dockerfile は以下のリンク
  # https://github.com/yszk0123/selenium-docker-rails-example/blob/master/Dockerfile
  app:
    container_name: app
    build: .
    ports:
      - 3000:3000
    volumes:
      - .:/app
    networks:
      test-network:
        aliases:
          - app.com
  chrome:
    container_name: chrome
    image: selenium/standalone-chrome-debug:3.9.1-actinium
    ports:
      - 4444:4444
      - 5900:5900
    networks:
      test-network:
        aliases:
          - chrome.com
networks:
  test-network:

RSpec (Capybara) の設定

spec/rails_helper.rb
require 'rspec/rails'
require "capybara/rails"
require "selenium/webdriver"

if ENV["LAUNCH_BROWSER"]
  Capybara.configure do |config|
    # docker-compose で設定した alias を使い
    # chrome コンテナ側から app コンテナ内のサーバーを参照
    config.server_host = "app.com"
    # ポートはデフォルトではランダムに割り当てられるが、設定を簡単にするため固定
    config.server_port = 3000
    config.javascript_driver = :selenium_chrome_headless
  end

  Capybara.register_driver :selenium_chrome_headless do |app|
    Capybara::Selenium::Driver.new(
      app,
      # 別コンテナで動かしている Selenium のリモートサーバーを操作
      browser: :remote,
      # Chrome 以外は適宜設定を変える
      desired_capabilities: Selenium::WebDriver::Remote::Capabilities.chrome(
        chromeOptions: {
          args: [
            "window-size=1024,512",
          ]
        }
      ),
      url: "http://chrome.com:4444/wd/hub",
    )
  end
end

実行方法

$ docker-compose up -d
# VNC クライアントを起動
$ open vnc://localhost:5900
# ここでパスワードを求められるので secret と入力
# テストを実行
$ docker exec -it app sh -c 'LAUNCH_BROWSER=true bundle exec rspec'

後は止めたいところに pry.binding などを仕込めばOK。

spec/features/sample_spec.rb
require "rails_helper"

RSpec.feature "Sample", js: true do
  scenario "sample" do
    visit sample_path

    binding.pry

    expect(page).to have_content("Hello, world")
  end
end

おまけ (JavaScript でテストを書く場合)

サーバーサイドは Rails だけど JavaScript でテストを書くケースもありそうだったので軽く調べた。
以下は selenium-webdriver を使った例。

$ docker-compose run --rm app sh -c 'bin/rails s'
$ docker-compose run --rm chrome
$ docker-compose run --rm node
docker-compose.yml
+  node:
+    container_name: node-test
+    image: node:9.6.1-alpine
+    command: sh -c 'cd /app && yarn init -y && yarn add selenium-webdriver && node test.js'
+    volumes:
+      - ./test.js:/app/test.js
+    networks:
+      test-network:
   chrome:
     container_name: chrome
test.js
const { Builder } = require('selenium-webdriver');

function wait(delay) {
  return new Promise(resolve => setTimeout(resolve, delay));
}

(async () => {
  const driver = new Builder()
    .forBrowser('chrome')
    .usingServer('http://chrome.com:4444/wd/hub')
    .build();

  try {
    await driver.get('http://app.com:3000/sample');
    await wait(3000);
  } finally {
    await driver.quit();
  }
})();

おわりに

Rails と Selenium にはあまり詳しくないため、間違いがあればご指摘いただけると助かります。

参考