はじめに
Seleniumの調査だったり開発環境がApple Siliconだったりで考慮しないといけない事が多いのでSystemTestの設定を後回しにしていたRailsアプリがあったのだが、CupriteというSeleniumではなくCDP(Chrome DevTools Protocol)を使用したGemの存在を知ったのがきっかけでやっぱりきちんとやろうという事になり、まずは勉強がてら一旦Railsから離れてCupriteとDockerのChormeで環境を作って試してみた。
詳細
リポジトリ
Gemなど
RubyでCDPを使うためのGem。Ferrumとはラテン語で鉄の意味らしい。
名前がラテン語である理由やGItHubのトップにあるイラストがMUCのエンドゲームでサノスが使っていたインフェニティティガントレットっぽく見えるのが気になる。
CapybaraでFerrumを使うためのGem。
Cupriteを知るまでその存在を知らなかったのだがプログラムから呼び出せるChromeに実装されたAPI。E2ETestなどではSeleniumを代替出来るのでCuprite & Ferrumはそのアプローチを取っている。
やったこと
疎通のテストなので単純なリクエストを返してくれるRackアプリをテスト用に作成。
最終的にCapybaraを通してJavaScriptの実行を試したいので検証用のボタンも追加しておく。
config.ru
class App
def call(env)
html = "<html><head><script>function changeText() { document.querySelector('#text').textContent = 'Change Text'; }</script></head><body><h1 id='text'>Hello World!</h1><button onclick=changeText()>Click</button></body></html>"
[200,{ "Content-Type" => "text/html" },[html]]
end
end
run App.new
DockerはWebアプリを起動するためのRubyとChromeを別コンテナで立ち上げている。
version: "3.9"
services:
web:
build:
context: .
command: ["bundle", "exec", "rackup", "-p", "80", "-o", "0.0.0.0"]
ports:
- "80:80"
volumes:
- ./app:/app
depends_on:
- chrome
chrome:
image: browserless/chrome
ports:
- "3333:3333"
volumes:
- .:/app:cached
environment:
PORT: 3333
CONNECTION_TIMEOUT: 600000
Rubyのコンテナでは必要なGemをbuild時にインストールしておりRuby3からWEBRickが標準ライブラリでなくなったのでPumaを追加している。
source "https://rubygems.org"
gem "rack"
gem "puma"
gem "cuprite"
gem "test-unit"
gem "debug"
FROM ruby:3.1.1
RUN mkdir /app
WORKDIR /app
ADD ./app /app
RUN gem install bundler
RUN bundle install
$ docker compose build
$ docker compose up -d
Dockerのビルドとコンテナの起動が済んだら動作を一つずつ確認していく。
Webアプリコンテナ <-> Chromeコンテナ
Rackアプリの起動の確認とWebコンテナからChormeのコンテナにアクセス出来るかを確認しておく
$ docker compose exec web curl 'http://localhost:80'
<html><head><script>function changeText() { document.querySelector('#text').textContent = 'Change Text'; }</script></head><body><h1 id='text'>Hello World!</h1><button onclick=changeText()>Click</button></body></html>%
$ docker compose exec web curl 'http://chrome:3333'
<!doctype html><html><head><meta charset="utf-8"/><title>browserless debugger</title>...
Chrome経由でWebアプリに接続するのでChromeのコンテナからホストネームでアクセス出来るかも確認しておく
$ docker compose exec chrome curl 'http://web:80'
<html><head><script>function changeText() { document.querySelector('#text').textContent = 'Change Text'; }</script></head><body><h1 id='text'>Hello World!</h1><button onclick=changeText()>Click</button></body></html>%
Ferrum -> Chromeコンテナ
WebコンテナからFerrum経由でChromeにアクセス出来るか確認
options = {
url: 'http://chrome:3333',
browser_options: { 'no-sandbox': nil }
}
browser = Ferrum::Browser.new(options)
browser.go_to("https://google.com")
puts browser.current_title
browser.quit
$ docker compose exec web ruby ferrum.rb
Google
Capybra -> Chromeコンテナ -> Webアプリ
Capybara経由でChromeを通してRackアプリに接続出来るか確認
require "capybara/cuprite"
require 'capybara/dsl'
options = {
url: 'http://chrome:3333',
browser_options: { 'no-sandbox': nil },
base_url: "http://web"
}
Capybara.register_driver(:cuprite) do |app|
Capybara::Cuprite::Driver.new(app,options)
end
Capybara.javascript_driver = :cuprite
Capybara.server_host = "0.0.0.0"
Capybara.app_host = "http://web:80"
session = Capybara::Session.new(:cuprite)
driver = session.driver
browser = driver.browser
page = browser.page
browser.visit "/"
puts page.body
今回ChromeもDockerにした上に似た名前の項目が多いので設定が紛らわしいが Capybara::Cuprite::Driver.new
のurlにはChromeの宛先をCapybara.app_host
にはWebアプリケーションの宛先を設定する。
その他はCapybaraからvisit "/"
のようにホスト名を省略してページを選択したければ
Capybara::Cuprite::Driver.new
にbasu_url
を渡す。
browser_options: { 'no-sandbox': nil }
はDocker経由の場合必要とのこと。
Capybara.server_host
もDocker経由の場合 "0.0.0.0" を指定して外部からのアクセスを許可してやる必要がある。
この内容を保存して実行してやるとCapybara経由でRackアプリに接続出来る事が確認できる。
$ docker compose exec web bundle exec ruby capybara.rb
<html><head><script>function changeText() { document.querySelector('#text').textContent = 'Change Text'; }</script></head><body><h1 id="text">Hello World!</h1><button onclick="changeText()">Click</button></body></html>
TestCase
最後にテストケースを作成してSystemTestでの実行を試す。
Capybara.default_driver = :cuprite
require 'test/unit'
require 'capybara/dsl'
class SystemTestCase < Test::Unit::TestCase
include Capybara::DSL
def test_foo
visit '/'
assert_equal(page.find('h1').text, 'Hello World!')
page.click_on 'Click'
assert_equal(page.find('h1').text, 'Change Text')
end
end
$ docker compose exec web ruby test_case.rb
Loaded suite test_case
Started
.
Finished in 0.319190042 seconds.
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
1 tests, 2 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
3.13 tests/s, 6.27 assertions/s
JavaScriptの実行も確認出来た
つまずいたところ
途中上記の設定で実行しても以下のような空のHTMLで返ってきて困った。
<html><head></head><body></body></html>
原因は意外なところでDockerComposeで指定していたサービス名が理由だった。
こちらを参考にしてapp -> webに変えたらうまく動いた。エラー内容も違うし詳細な理由はわからないのが、とりあえずはDockerComposeのService名はappにしないほうが無難なのだろうか
まとめ
SystemTestはブラウザが必要だったりUnitTestよりも複雑でそこにDockerや新しく使うGemが絡んでくると確信のないまま設定をあれこれいじくる事になりがちなので一旦小さな構成で試してみた。 とりあえず迷った時に立ちかえられる構成が出来たので次はRailsアプリに組み込んでいきたい。
参照