8
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Docker環境でHeadless Chromeを使ったSystemt Specを実行する

Last updated at Posted at 2019-08-16

この記事なんやねん

みなさん、Rspecで統合テスト(System Spec)書いてますか?

すっかりSeleniumを使ってヘッドレスブラウザを使った統合テストが主流になりましたが、DockerHeadless Chromeを使ったやり方が意外とヒットしなかったので投稿します。

手順

ここから早速始めていきましょう。因みに、以下の環境は揃っているという前提で進めます。

  • Dockerとdocker-composeをインストール済み
  • docker-composeを使ってRails環境を構築済み
  • Rspec実行環境を構築済み

もし、 環境構築まだやねん って状態だったら、こちらの記事を参考に開発環境を作ってみてください。

イメージの準備

Dockerfileにheadless chromeドライバをインストールしてもいいのですが、正直めんどくさいです。
brewで簡単にインストールできるのにDocker使った方が環境構築めんどくさいのも本末転倒な感じするので、Dockerhubにイメージ上がってないか検索したら案の定ありました

seleniumがイメージを提供してくれてるようですね。因みに、googleはイメージ提供していないようなので、seleniumさんのイメージをありがたく使いましょう。

サービス追加

docker-composeに以下のように既存サービスの一番下にサービスを追加してください。

docker-copompose.yml
version: '3'
services:
  ### 省略 ###
  chrome:
    image: selenium/standalone-chrome
    ports:
      - "4444:4444"

これだけです。やっぱりイメージ使えば楽ですなあ。

Rspecの設定

次に、Rspecの設定をしていきます。Capybaraとrails_helperを設定するだけで済むので簡単です。

Gemfileの修正

もしまだライブラリをインストールしていなければ以下を追記してから、bundle installしてください。

Gemfile
group :test do
  # Call 'byebug' anywhere in the code to stop execution and get a debugger console
  gem "database_cleaner"
  gem "factory_bot_rails"
  gem "rspec-rails"
 # 下記を追加
  gem 'capybara' 
  gem 'selenium-webdriver'
  gem 'rspec-retry'
end

Capibaraの設定

下記のファイルを作成してください。ポート番号は他でもいいですよ。

spec/support/capybara.rb
Capybara.default_driver    = :selenium_chrome
Capybara.javascript_driver = :selenium_chrome
Capybara.server_host = Socket.ip_address_list.detect(&:ipv4_private?).ip_address
Capybara.server_port = 3001
Capybara.default_max_wait_time = 5
Capybara.ignore_hidden_elements = true

Capybara.register_driver :selenium_chrome do |app|
  opts = {
    desired_capabilities: :chrome,
    browser:              :remote,
    url:                  "http://chrome:4444/wd/hub",
  }
  Capybara::Selenium::Driver.new(app, opts)
end

rails_helperの修正

下記のようにrails_helperを修正してください。
なお、spec/support/system_support.rbというSystemスペック用のヘルパーを
作成しなければ任意ってコメントしてる設定は不要です。

spec/rails_helper.rb
  ### 省略 ###

# コメントアウトされてるので、コメントアウトを外してください
Dir[Rails.root.join('spec', 'support', '**', '*.rb')].each { |f| require f }

RSpec.configure do |config|
  ### 省略 ###

  # Systemスペック用のヘルパー。任意です。
  config.include SystemSupport, type: :system

  # 名称を一意にするために設定。任意かな。
  config.before(:all, type: :system) do
    timestamp!
  end

  # Systemスペックは不安定なのでリトライ用の設定。
  config.verbose_retry = true
  config.display_try_failure_messages = true
  config.default_retry_count = 3

  # 下記を追加
  config.before(:each, type: :system) do
    driven_by Capybara.default_driver
  end

  config.before(:each, type: :system, js: true) do
    driven_by Capybara.javascript_driver
    host! "http://#{Capybara.server_host}:#{Capybara.server_port}"
  end
end

ヘルパーの作成(任意)

Systemスペックは共通の処理(ログイン処理とか)が多いので、ヘルパーを用意しておくと便利です。
任意と書いていますが、作成しておいて損はないと思います。

spec/support/system_support.rb
module SystemSupport
  # 一意の名称を作成するために実行時のtimestampを、
  # 数字でインスタンス変数に格納するsetterです。地味に便利。
  def timestamp!(timestamp = Time.now.to_i)
    @timestamp = timestamp
  end

  # getterです
  def timestamp
    @timestamp
  end

  # ブロックの結果がtrueになるまでループするメソッド。すげえ使う。
  def wait_until(wait_time = Capybara.default_max_wait_time)
    Timeout.timeout(wait_time) do
      loop until yield
    end
  end

  # 特定のcssが登場する、もしくは、なくなるまでループするメソッド
  def wait_for_css(selector, wait_time = Capybara.default_max_wait_time, non_display: false)
    Timeout.timeout(wait_time) do
      loop until send((non_display ? :has_no_css? : :has_css?), selector)
    end
    yield if block_given?
  end

  # 非同期通信が終わるまでループするメソッド
  def wait_for_ajax(wait_time = Capybara.default_max_wait_time)
    Timeout.timeout(wait_time) do
      loop until page.evaluate_script("jQuery.active").zero?
    end
    yield if block_given?
  end
end

System Specの作成

ここまでで設定は完了してるので実際にSystem Specを書いていきましょう。
私のサンプルソースではこんな感じで書いてます。自分のソースコードに合わせて適宜書き直してください。

require "rails_helper"

# typeはsystemを設定、Javascriptも使うのでjsもtrueにしておく。
RSpec.describe "HelloWorlds", type: :system, js: true do
  # 最初にテストデータ作成
  before(:all) {
    create(:hello_world, country: "JP", hello: "こんにちわ世界", priority: 1, file_name: "jp.jpeg")
    create(:hello_world, country: "US", hello: "Hello World", priority: 2)
    create(:hello_world, country: "CN", hello: "你好 世界", priority: 3)
  }

  before(:each) {
    visit root_path

    # コンテンツが全て表示されるまで待つ
    wait_until { (page.all("div.portfolio-item").count == 3) }
  }

  context "when go to index page" do
    it "show contents" do
      expect(page).to have_css("h1", text: "Hello World")

      content = page.first("div.portfolio-item")
      expect(content.first("p.card-text").text).to eq("こんにちわ世界")
      expect(content.first("h4.card-title")).to have_link("日本")
      expect(content.first("img.card-img-top")[:src]).to match(/jp\.jpeg/)
    end
  end

  context "when go to Create page" do
    it "create content" do
      page.first("#new_hello_world").click

      # タイトル出るまで待つ
      wait_until { page.has_css?("h3", text: "New Helloworld") }

      select "ドイツ", from: "hello_world_country"
      # 一意の名称で検索してテストデータを作成
      fill_in "hello_world_hello", with: "Hallo Welt #{timestamp}"
      fill_in "hello_world_priority", with: 4

      click_on "Submit"

      # メッセージ出るまで待つ
      wait_until { page.has_content?("Hello world was successfully created.") }

      content = page.first("div.form").all("label.form-control")
      expect(content[0].text).to eq("ドイツ")
      expect(content[1].text).to eq("Hallo Welt #{timestamp}")
      expect(content[2].text).to eq("4")
    end
  end
end

テスト実行

早速テストを実行してみましょう。
下記のwebはサービス名なので適宜自分の設定してるサービス名に変更して実行してください。

% docker-compose run --rm web rspec spec/system/hello_worlds_spec.rb
Starting hr_chrome_1 ... done
Starting hr_db_1     ... done
Capybara starting Puma...
* Version 4.1.0 , codename: Fourth and One
* Min threads: 0, max threads: 4
* Listening on tcp://192.168.176.4:3001
..

Finished in 1 minute 1.58 seconds (files took 3.71 seconds to load)
2 examples, 0 failures

通りましたね、やったぜ

まとめ

Dockerでも簡単にHeadless Chromeを使った統合テスト環境を実現できました。
テストだけじゃなくてスプレイピングにも利用できるので試してみてください。

因みに、今回使ったサンプルソースはこちらのリポジトリになるので参考までにどうぞ。

8
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?