selenium-webdriver

docker-seleniumを使いリモート環境でSeleniumを実行してみる

元になるコード:
RspecとCapybaraを使ってE2Eテストの土台を作ってみる
Capybaraの実行ログをEventListenerを使って表示してみる
Chrome DevTools' Device Modeを使ってモバイルサイトのE2Eテストをしてみる

上記記事で作成したコードを元にdocker-seleniumを使ってリモート環境でSeleniumを実行出来るようにしていきます。ここではDockerの詳細な使い方などは省略します。

実行環境

  • macOS High Sierra 10.13.3
  • JDK 8 (java version "1.8.0_162")
  • selenium-server-standalone-3.11.0.jar
  • DOCKER CE FOR MAC [Version 17.12.0-ce-mac55 (23011)]
  • docker-selenium images:
    • standalone-chrome:3.11.0-bismuth
    • hub:3.11.0-bismuth
    • node-chrome-debug:3.11.0-bismuth

Grid Hubサーバーと接続出来るようにコードを修正する

Capybara::Selenium::Driver.new実行部分のコードを以下のように修正します。この修正によってREMOTE=trueといった感じで環境変数を渡すとSeleniumの実行環境をローカルとリモートを切り替えられるようになります。

spec/helpers/capybara.rb
# (省略)

  remote = 'false'
  remote = ENV['REMOTE'] unless ENV['REMOTE'].nil?

  # WebDriverのインスタンスを作成する
  if remote.casecmp('true').zero?
    # ブラウザーをリモートマシン上で起動する
    Capybara::Selenium::Driver.new(
      app,
      browser: :chrome,
      options: options,
      listener: NavigationListener.new(logger),
      url: 'http://localhost:4444/wd/hub') # `localhost`の部分は環境に合わせて修正する
  else
    # ブラウザーをローカルマシン上で起動する
    Capybara::Selenium::Driver.new(
      app,
      browser: :chrome,
      options: options,
      listener: NavigationListener.new(logger))
  end

ローカルマシン上のSelenium Serverに接続出来るようにする

まずは、Selenium HQのサイトでダウンロード出来る「Selenium Standalone Server」に接続してローカルマシン上のChrome上でSeleniumが実行出来るようにします。

Selenium HQダウンロードページ

Selenium HQのサイトのダウンロードページから「selenium-server-standalone-3.11.0.jar」をダウンロードして以下の場所に保存します。
(パスに特に意味は無いので分かりやすい場所で良いです)

files/server/selenium-server-standalone-3.11.0.jar

以下のコマンドを実行してGrid Hubサーバーを起動します。

# `selenium-server-standalone-3.11.0.jar`のルートフォルダへ移動する
cd files/server

# Grid Hubサーバーを起動する
java -jar selenium-server-standalone-3.11.0.jar -role hub
15:05:46.893 INFO [GridLauncherV3.launch] - Selenium build info: version: '3.11.0', revision: 'e59cfb3'
15:05:46.897 INFO [GridLauncherV3$2.launch] - Launching Selenium Grid hub on port 4444
2018-03-21 15:05:47.392:INFO::main: Logging initialized @1107ms to org.seleniumhq.jetty9.util.log.StdErrLog
15:05:47.623 INFO [Hub.start] - Selenium Grid hub is up and running
15:05:47.625 INFO [Hub.start] - Nodes should register to http://192.168.0.2:4444/grid/register/
15:05:47.625 INFO [Hub.start] - Clients should connect to http://192.168.0.2:4444/wd/hub

# Nodeサーバーをリンクした時のログ
15:10:26.830 INFO [DefaultGridRegistry.add] - Registered a node http://192.168.0.2:5555

次にローカルマシン(macOS)上をNodeサーバーとしてGrid Hubサーバーにリンクさせます。ここで使用しているローカルマシンにはChrome、Firefox、Safariがインストールされた状態で行なっています。

ターミナルのウィンドウを新しく開き、次のコマンドを実行します。-hub http://localhost:4444/grid/registerlocalhost部分はGrid HubサーバーのIPアドレスを指定して下さい。

$ java -jar selenium-server-standalone-3.11.0.jar -role node -hub http://localhost:4444/grid/register
15:10:26.003 INFO [GridLauncherV3.launch] - Selenium build info: version: '3.11.0', revision: 'e59cfb3'
15:10:26.008 INFO [GridLauncherV3$3.launch] - Launching a Selenium Grid node on port 5555
2018-03-21 15:10:26.150:INFO::main: Logging initialized @534ms to org.seleniumhq.jetty9.util.log.StdErrLog
15:10:26.349 INFO [SeleniumServer.boot] - Welcome to Selenium for Workgroups....
15:10:26.349 INFO [SeleniumServer.boot] - Selenium Server is up and running on port 5555
15:10:26.350 INFO [GridLauncherV3$3.launch] - Selenium Grid node is up and ready to register to the hub
15:10:26.359 INFO [SelfRegisteringRemote$1.run] - Starting auto registration thread. Will try to register every 5000 ms.
15:10:26.359 INFO [SelfRegisteringRemote.registerToHub] - Registering the node to the hub: http://localhost:4444/grid/register
15:10:26.835 INFO [SelfRegisteringRemote.registerToHub] - Updating the node configuration from the hub
15:10:26.908 INFO [SelfRegisteringRemote.registerToHub] - The node is registered to the hub and ready to use

ブラウザーで次のURL開くとGrid Consoleのページに1つのNodeサーバーがリンクされた状態になっていると思います。

URL: http://{Grid HubサーバーのIPアドレス}:4444/grid/console

スクリーンショット 2018-03-21 15.12.33.png

ちなみに、デフォルトではChromeとFirefoxのmaxInstancesが5、SafariのmaxInstancesが1となるようです。

Grid Hub経由してNodeサーバー上でSeleniumを実行する

(Nodeサーバーと書いていますが、実質ローカルマシン上なので少しややこしいです)

コードの修正とGrid Hub+Nodeサーバーが起動したらフィーチャースペックを実行してみます。実行コマンドは以下のようになり、exportコマンドで設定する環境変数がBROWSERREMOTEの2つになっています。

$ export BROWSER=chrome REMOTE=true; bundle exec rspec spec/features/example_spec.rb

Yahoo! JAPAN
  タイトルが`Yahoo! JAPAN`であること
  検索フォームのパーツの要素が存在すること
  ニューストピックスボックスの要素が存在すること
  主なサービスメニュー内に`ヤフオク!`が存在すること
  検索ワードを未入力で検索した時
    検索結果ページに`ウェブ検索の急上昇ワード`が表示されること
  検索ワードに`Selenium Capybara`を入力した時
    検索結果ページの入力フィールドに検索ワードが入力されていること

Finished in 21.7 seconds (files took 0.57159 seconds to load)
6 examples, 0 failures

問題無くスペックは完走出来ました。

スペック実行時のGrid Hubサーバー上のログは以下のように出力されます。

2018-03-21 15:05:47.392:INFO::main: Logging initialized @1107ms to org.seleniumhq.jetty9.util.log.StdErrLog
15:05:47.623 INFO [Hub.start] - Selenium Grid hub is up and running
15:05:47.625 INFO [Hub.start] - Nodes should register to http://192.168.0.2:4444/grid/register/
15:05:47.625 INFO [Hub.start] - Clients should connect to http://192.168.0.2:4444/wd/hub
15:10:26.830 INFO [DefaultGridRegistry.add] - Registered a node http://192.168.0.2:5555
15:32:27.748 INFO [RequestHandler.process] - Got a request to create a new session: Capabilities {browserName: chrome, chromeOptions: {args: [disable-notifications, disable-translate, disable-extensions, disable-infobars, window-size=1280,960]}, cssSelectorsEnabled: true, javascriptEnabled: true, nativeEvents: false, rotatable: false, takesScreenshot: false, version: }
15:32:27.755 INFO [TestSlot.getNewSession] - Trying to create a new session on test slot {server:CONFIG_UUID=5d0eb217-4425-4623-8c23-db889a64daf9, seleniumProtocol=WebDriver, browserName=chrome, maxInstances=5, platformName=MAC, platform=MAC}

スペック実行時のGrid Nodeサーバー上のログは以下のように出力されます。

15:32:27.915 INFO [ActiveSessionFactory.apply] - Capabilities are: Capabilities {browserName: chrome, chromeOptions: {args: [disable-notifications, disable-translate, disable-extensions, disable-infobars, window-size=1280,960]}, cssSelectorsEnabled: true, javascriptEnabled: true, nativeEvents: false, rotatable: false, takesScreenshot: false, version: }
15:32:27.917 INFO [ActiveSessionFactory.lambda$apply$11] - Matched factory org.openqa.selenium.remote.server.ServicedSession$Factory (provider: org.openqa.selenium.chrome.ChromeDriverService)
Starting ChromeDriver 2.36.540469 (1881fd7f8641508feb5166b7cae561d87723cfa8) on port 30789
Only local connections are allowed.
15:32:29.474 INFO [ProtocolHandshake.createSession] - Detected dialect: OSS
15:32:29.933 INFO [RemoteSession$Factory.lambda$performHandshake$0] - Started new session 81c22bb12926cf26ccf956ca2fe0fa65 (org.openqa.selenium.chrome.ChromeDriverService)

Docker CE for Macのインストール

前置きが長くなりましたがselenium-server-standalone-{version}.jarとChromeやFirefoxがインストールされたdockerイメージが公開されているので、そちらを使ってみたいと思います。

テストコードの実行には「Docker CE for Mac」を使用しますので、必要に応じて下記ドキュメントを参考にインストールして下さい。
Install Docker for Mac

「Docker CE for Mac」のインストールが完了すると、以下のようなバージョン情報が返ってくると思います。

$ docker --version
Docker version 17.12.0-ce, build c97c6d6

$ docker-compose --version
docker-compose version 1.18.0, build 8dd22a9

$ docker-machine --version
docker-machine version 0.13.0, build 9ba6da9

docker-compose.ymlの作成

次に、docker-composeを使用してコンテナを管理するため、以下のようなYAMLファイルを作成します。

$ mkdir files
$ mkdir files/docker
$ touch files/docker/docker-compose.yml

作成したdocker-compose.ymlをエディタで開いて編集します。

イメージはdocker-seleniumのものを使用します。
docker-seleniumリポジトリ

files/docker/docker-compose.yml
# https://github.com/SeleniumHQ/docker-selenium

# Usage:
# docker-compose up  # dialogue mode
# docker-compose up -d  # background mode
# docker-compose down  # shutdown all nodes
# docker-compose -f docker-compose.yml up --force-recreate

# How to scale up:
#1 remove: ports with value
#2 run: docker-compose -f docker-compose.yml up --force-recreate --scale node-chrome=2

version: '3'
services:
  selenium-grid-hub:
    image: selenium/hub:3.11.0-bismuth
    container_name: selenium-grid-hub
    ports:
      - '4444:4444'
    volumes:
      - /dev/shm:/dev/shm
    privileged: true
    environment:
      - TZ=Asia/Tokyo
      - no_proxy=localhost
      - HUB_ENV_no_proxy=localhost
      - GRID_BROWSER_TIMEOUT=60000
      - GRID_TIMEOUT=60000
      - GRID_MAX_SESSION=50
      - GRID_MAX_INSTANCES=3
      - GRID_CLEAN_UP_CYCLE=60000
      - GRID_UNREGISTER_IF_STILL_DOWN_AFTER=180000
      - GRID_NEW_SESSION_WAIT_TIMEOUT=60000
  node-chrome:
    image: selenium/node-chrome-debug:3.11.0-bismuth
    volumes:
      - /dev/shm:/dev/shm
    privileged: true
    depends_on:
      - selenium-grid-hub
    ports:
      - '15900:5900'
    environment:
      - TZ=Asia/Tokyo
      - NODE_MAX_SESSION=1
      - no_proxy=localhost
      - HUB_ENV_no_proxy=localhost
      - HUB_PORT_4444_TCP_ADDR=selenium-grid-hub
      - HUB_PORT_4444_TCP_PORT=4444
      - SCREEN_WIDTH=1440
      - SCREEN_HEIGHT=900

Dockerコンテナの起動

注意: ここではselenium-grid-hubnode-chrome-debugのイメージを使用しますが、初回実行時はサイズの大きなファイルをダウンロードしますので、定額プランかつ安定した回線を使用している時に実行することをお勧めします。

まずは、Dockerを起動します。

スクリーンショット 2018-03-21 15.56.28.png

Docker is runnningとなっていればOKです。

次に、docker-compose upコマンドを使用し、docker-compose.ymlの設定に従ってDockerコンテナが起動します。

$ cd files/docker
$ docker-compose up
Creating network "docker_default" with the default driver
Pulling selenium-grid-hub (selenium/hub:3.11.0-bismuth)...
3.11.0-bismuth: Pulling from selenium/hub
22dc81ace0ea: Pull complete

# (中略)

e8f0e9a4fbc2: Pull complete
Digest: sha256:43ee53001dcef8963211eb6b4c9186843d4e6eac1c58708b935ab37344fce32b
Status: Downloaded newer image for selenium/hub:3.11.0-bismuth
Pulling node-chrome (selenium/node-chrome-debug:3.11.0-bismuth)...
3.11.0-bismuth: Pulling from selenium/node-chrome-debug
22dc81ace0ea: Already exists

# (中略)

06103f6e5022: Already exists
f8b88675be78: Downloading [=====================>                             ]  18.54MB/43.34MB
a5295068c821: Download complete
c1df2b919ff3: Download complete
06c22aed31db: Downloading [=>                                                 ]  3.209MB/154.6MB

# (中略)

b678f40a77a3: Pull complete
Digest: sha256:81288934d33e7a64ce6017d3041fa54623b25b02f2815b6d29900f00f1615410
Creating selenium-grid-hub ... done
Creating selenium-grid-hub ...
Creating docker_node-chrome_1       ... done
Attaching to selenium-grid-hub, docker_node-chrome_1

selenium-grid-hub    | 14:44:39.203 INFO [DefaultGridRegistry.add] - Registered a node http://172.18.0.3:5555
node-chrome_1        | 14:44:39.217 INFO [SelfRegisteringRemote.registerToHub] - Updating the node configuration from the hub
node-chrome_1        | 14:44:39.345 INFO [SelfRegisteringRemote.registerToHub] - The node is registered to the hub and ready to use

Grid Console を開く

Dockerコンテナが起動したら次のURLをブラウザーで開きます。

URL: http://localhost:4444/grid/console
(localhostやポート番号は必要に応じて変更して下さい)

以下の画像のようなページが開けば、selenium-grid-hubnode-chrome-debugコンテナの起動とGrid HubサーバーとNodeサーバーのリンクが成功しています。

スクリーンショット 2018-03-21 16.00.30.png

スペックも問題無く実行出来ました。

$ export BROWSER=chrome REMOTE=true; bundle exec rspec spec/features/example_spec.rb

Yahoo! JAPAN
  タイトルが`Yahoo! JAPAN`であること
  検索フォームのパーツの要素が存在すること
  ニューストピックスボックスの要素が存在すること
  主なサービスメニュー内に`ヤフオク!`が存在すること
  検索ワードを未入力で検索した時
    検索結果ページに`ウェブ検索の急上昇ワード`が表示されること
  検索ワードに`Selenium Capybara`を入力した時
    検索結果ページの入力フィールドに検索ワードが入力されていること

Finished in 19.47 seconds (files took 0.59972 seconds to load)
6 examples, 0 failures

その他

例えば、1つのChromeのNodeサーバーしか使用しない場合は、以下のコマンドでシンプルに起動することも出来ます。

$ docker run -p 4444:4444 -p 15900:5900 -v /dev/shm:/dev/shm selenium/standalone-chrome-debug:3.11.0-bismuth

また、Nodeサーバーでブラウザーが動作している状況を確認したい場合は、debugの名前が付いたDockerコンテナであればリモートアクセスを行うことも可能です。

スクリーンショット 2018-03-21 16.10.16.png

commandボタン+Kで「サーバーへ接続」ウィンドウを表示して、サーバーアドレス欄に「vnc://localhost:15900」を入力して「接続」ボタンを押下します。パスワードは「secret」の文字列です。

接続後にスペックを実行すると、以下のようにブラウザーのChromeが起動して実行中の画面を見ることが出来ます。

スクリーンショット 2018-03-21 16.13.05.png

不具合

原因不明なのですが、当記事のdocker-compose.ymlを使ってdocker-compose upで起動したdocker-seleniumのコンテナ上で以下のChrome::Optionsが効かないという問題があります。

standalone-chrome-debug:3.11.0-bismuthのコンテナでは効くのに何故だ…

spec/helpers/capybara.rb
  options = Selenium::WebDriver::Chrome::Options.new

  options.add_argument('disable-notifications')       # Web通知やPush APIによる通知を無視する
  options.add_argument('disable-translate')           # 翻訳ツールバーを無効にする
  options.add_argument('disable-extensions')          # 拡張機能を無効にする
  options.add_argument('disable-infobars')            # インフォバーの表示を無効にする
  options.add_argument('window-size=1280,960')        # ブラウザーのサイズを指定する

  # ChromeのSPエミュレーションを使用する
  # Device Toolbarで選択出来るEmulated Devicesを指定する
  browser = :chrome
  browser = ENV['BROWSER'].to_sym unless ENV['BROWSER'].nil?
  options.add_emulation(device_name: 'iPhone 5/SE') if browser == :chrome_sp