生産性向上ブログ

継続的な生産性向上を目指すエンジニアのためのブログ

Selenoid 入門 〜軽量で多機能な Selenium Grid〜

github.com

今回は、軽量で多機能な Selenium Grid である Selenoid についてまとめてみます。

目次

Selenium Grid とは?

まず、いきなり Selenoid について書く前に、その前提となる Selenium Grid について簡単に説明します。

ブラウザテストの自動化などでよく使われる Selenium ですが、並列で複数のブラウザを使用して Selenium テストを実行するときに Selenium Grid というアーキテクチャが使われることが多いです。

f:id:miya-jan:20190120183900p:plain

Selenium Grid は、上の画像のように Hub と Node で構成されるクラスタです。Hub は、Selenium テストからのリクエストを受け取り、それぞれの Node へリクエストをリダイレクトします。Node はリクエストに従ってブラウザを立ち上げたり操作したりします。

Hub は、内部的には Java のサーバーが動いています。Node も内部的には Java のサーバーが動いていて、さらにそのサーバーから各ブラウザごとの WebDriver バイナリ(ChromeDriverGeckoDriver など)へリクエストをプロクシします。

Selenium Grid を構築するための Docker イメージが公式で提供されていて、Chrome や Firefox 環境は手軽に用意できるようになっています。(IE, Edge, Safari などは Docker で環境を用意できないので自力でマシン環境を用意する必要があります)

Selenoid とは?

Selenoid は、Aerokube 社が OSS として公開しているツールで、Selenium Grid の改善版です。公式の Selenium Grid の違いとしては、軽量であること、セッションごとに Node が作成されること、便利な機能が追加されていることがあります。

軽量

Selenoid では、Hub の役割を go のシングルバイナリとして再実装しています。また、Node 側ではリクエストをプロクシするだけだった Java のサーバーをなくし、ダイレクトに WebDriver バイナリをメインプロセスとしたコンテナを立ち上げます。

これにより、Hub も Node も JVM への依存がなくなり、従来の Selenium Grid よりも軽量になっています。また、プロクシするサーバーが減ったことにより Selenium テストの安定度が増すことも期待できます。

セッションごとに Node が作成される

従来の Selenium Grid は、事前に必要な数の Node を起動しておかないといけませんでした。例えば、10 並列で Selenium テストを実行するのであれば、10 台の Node を起動しておくといった感じです。

一方 Selenoid では、セッション開始のリクエストを受け取ったタイミングで新たに Node となるコンテナを立ち上げ、セッション終了時にその Node を終了します。

これにより、需要に応じて Node の数を柔軟に変更することができますし、セッションごとにクリーンな環境を用意できるのでより安定した Selenium テストが期待できます。

便利な機能

従来の Selenium Grid になかった機能として、以下の機能などが追加されています。

  • Live ブラウザスクリーン
  • 録画
  • ログ取得

Selenium テストをデバッグする上で役に立つ機能ばかりです。

Getting Started

ここからは実際に動かす方法を解説していきます。Docker は事前にインストールされているという前提で書いていきます。

Selenoid の起動

手っ取り早く動かすには、Configuration Manager というコマンドラインツールを使います。Linux か Mac であれば以下で動きます。

$ curl -s https://aerokube.com/cm/bash | bash
$ ./cm selenoid start --vnc

使用する Docker イメージのダウンロード、Hub となる Docker コンテナの起動などが行われます。ちなみに、自分が試した時点では Firefox と Chrome と Opera の最新 2 バージョンの Docker イメージがダウンロードされました。(cm コマンドに渡す引数で変えられます)

ステータスチェック

正しく動いてるか確認するために、ステータスチェックの URL が存在します。

$ curl http://localhost:4444/status
{"total":5,"used":0,"queued":0,"pending":0,"browsers":{"chrome":{"70.0":{},"71.0":{}},"firefox":{"63.0":{},"64.0":{}},"opera":{"56.0":{},"57.0":{}}}}

"total": 5 というのは最大 5 セッションまで同時に起動できるということです。この上限も cm コマンドに渡す引数で変更できます。

テスト

実際に Selenium を動かしてみます。クライアント側では、http://localhost:4444/wd/hub をエンドポイントに指定します。Python 3 で以下のようなコードを test.py に書きます。

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities

driver = webdriver.Remote(
    command_executor='http://localhost:4444/wd/hub',
    desired_capabilities=DesiredCapabilities.CHROME)
driver.get('https://google.com')
element = driver.find_element_by_name('q')
element.send_keys('生産性向上ブログ')
element.submit()
driver.quit()

事前に pip3selenium パッケージをインストールしてからスクリプトを実行します。

$ pip3 install selenium
$ python3 test.py

特にエラーが出ないで終了すれば OK です。といっても、現時点だとブラウザがコンテナ内で動いてる様子が見えないので実感がないです。

Live ブラウザスクリーン

Live ブラウザスクリーンの機能を使うためには、Selenoid とは別に Selenoid UI を別途起動します。こちらも cm コマンドから起動できます。

$ ./cm selenoid-ui start

正常に起動すれば、http://localhost:8080 にブラウザで接続できます。

f:id:miya-jan:20190120184920p:plain

まだセッションがないので情報が少ないです。

Live ブラウザスクリーン機能は VNC によって実現されており、機能を有効にするにはクライアント側でも capability に enableVNC: true を追加する必要があります。先ほどのスクリプトを少し修正します。

import time
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities

capabilities = DesiredCapabilities.CHROME.copy()
capabilities['enableVNC'] = True
driver = webdriver.Remote(
    command_executor='http://localhost:4444/wd/hub',
    desired_capabilities=capabilities)
time.sleep(10)
driver.get('https://google.com')
time.sleep(3)
element = driver.find_element_by_name('q')
time.sleep(3)
element.send_keys('生産性向上ブログ')
time.sleep(3)
element.submit()
time.sleep(3)
driver.quit()

capability に enableVNC を追加しただけでなく、目視で確認しやすいように sleep を何箇所か追加しています。このスクリプトを実行中に Selenoid UI にアクセスすると以下のように Live ブラウザスクリーン機能を利用できます。

youtu.be

Selenoid UI 上で起動中のセッションが表示され、動いているブラウザが見られることが確認できます。

録画

次は、録画機能を試してみます。ちなみに、この機能は VNC 機能を有効にしなくても動きます。

録画機能を有効にするには、enableVideo: true の capability が必要です。デフォルトだと <セッション ID>.mp4 というファイル名で保存されますが、videoName: "my-video.mp4" のように capability でファイル名を指定することもできます。他にも、videoScreenSize, videoFrameRate, videoCodec あたりが指定できるようですが、今回は省略。

import time
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities

capabilities = DesiredCapabilities.CHROME.copy()
capabilities['enableVideo'] = True
capabilities['videoName'] = 'my-video.mp4'
driver = webdriver.Remote(
    command_executor='http://localhost:4444/wd/hub',
    desired_capabilities=capabilities)
(以下同じなので省略)

上記のスクリプトを実行すると、~/.aerokube/selenoid/video/ 下に my-video.mp4 という動画ファイルが作成されました。

ログ取得

Selenium のセッションのログを記録するには、enableLog: true を capability に追加します。こちらもデフォルトでは <セッション ID>.log というファイル名で保存されますが、logName: my-log.log のように capability でファイル名を指定できます。

import time
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities

capabilities = DesiredCapabilities.CHROME.copy()
capabilities['enableLog'] = True
capabilities['logName'] = 'my-log.log'
driver = webdriver.Remote(
    command_executor='http://localhost:4444/wd/hub',
    desired_capabilities=capabilities)
(以下同じなので省略)

~/.aerokube/selenoid/logs/ 下に my-log.log というログファイルが作成されます。

Selenoid の停止

起動した Selenoid と Selenoid UI を停止します。

$ ./cm selenoid-ui stop
$ ./cm selenoid stop

Docker の推奨設定

ドキュメントによると、以下の設定が推奨されています。

  • ストレージドライバは OverlayFS を推奨
  • 同時に実行されるコンテナ数はホストマシンの CPU コア数の 1.5 ~ 2.0 倍ぐらいの上限を推奨
    • Selenoid に -limit 10 を引数として渡すとセッション数の上限を 10 に設定できる
  • docker0 ネットワークの MAC アドレスを eth0 と同じにする
    • docker0 ネットワークの低パフォーマンスが理由で Selenium コマンドがランダムで失敗することがあるらしい
  • 各コンテナごとの CPU とメモリの使用量上限を設定する
    • Selenoid に -mem 128m, -cpu 1.5 とか引数で渡すと設定できる
  • docker コマンドと同様に DOCKER_API_VERSION などの環境変数を指定できる
    • ホストマシンの Docker サーバーと Selenoid が使用しているクライアントのバージョンが異なるときとかに使える

ブラウザの設定ファイル

Selenoid が要求されたブラウザに対して使用する Docker イメージを、JSON ファイルで設定します。例のように Configuration Manager 経由で起動した場合は、~/.aerokube/selenoid/ 下に browsers.json というファイルが自動で生成されています。

$ cat ~/.aerokube/selenoid/browsers.json
{
    "chrome": {
        "default": "71.0",
        "versions": {
            "70.0": {
                "image": "selenoid/vnc_chrome:70.0",
                "port": "4444",
                "path": "/"
            },
            "71.0": {
                "image": "selenoid/vnc_chrome:71.0",
                "port": "4444",
                "path": "/"
            }
        }
    },
    "firefox": {
        "default": "64.0",
        "versions": {
            "63.0": {
                "image": "selenoid/vnc_firefox:63.0",
                "port": "4444",
                "path": "/wd/hub"
            },
            "64.0": {
                "image": "selenoid/vnc_firefox:64.0",
                "port": "4444",
                "path": "/wd/hub"
            }
        }
    },
    "opera": {
        "default": "57.0",
        "versions": {
            "56.0": {
                "image": "selenoid/vnc_opera:56.0",
                "port": "4444",
                "path": "/"
            },
            "57.0": {
                "image": "selenoid/vnc_opera:57.0",
                "port": "4444",
                "path": "/"
            }
        }
    }
}

上の例ですと、Chrome と Firefox と Opera に対してデフォルトで使用するバージョンと、バージョンごとに使用される Docker イメージが設定されています。この例だと、selenoid にある最新 2 バージョンのイメージを利用するように設定されていますが、設定を変えれば独自に用意したイメージを利用することもできます。

設定を追加すれば環境変数を指定したり、ボリュームをマウントしたり、tmpfs を利用するようにしたりといったことも可能です。詳細はドキュメントへ。

ちなみに、この記事では Docker で起動できるブラウザしか取り扱っていませんが、Docker イメージの代わりに Selenium Server や standalone な WebDriver バイナリを指定することもできます。つまり、IE, Edge, Safari なども扱えるということです。

その他の機能

まだまだ機能がたくさんあるのですが、すべてを紹介するのは厳しいので目についた機能をざっくり紹介します。

  • ログをローカルではなく外部の Logstash や Graylog などに送ったりできる
  • 設定変更時に再起動なしにリロードできる
  • スクリーンの解像度変更
  • セッション単位でタイムゾーンを変えられる
  • セッション単位で環境変数を変えられる
  • Selenoid の利用状況の統計データを [Telegraf] や [Graphite] などに送ってモニタリングできる
  • ブラウザがダウンロードしたファイルを取得する API が存在する
  • ブラウザのクリップボードを取得する API が存在する
  • websocket 経由で Chrome Developer Tools を操作できる
  • 録画ファイルやログファイルを S3 互換の外部ストレージに保存できる
  • ggr というツールを使ってロードバランシングすれば複数の Selenoid を組み合わせてスケールできる

どれもめちゃ便利そうですね。

まとめ

Selenium Grid を改善した Selenoid について紹介しました。

個人的な所感としては、全体的にとても筋のよい設計だと思いました。例えば、セッション単位でノードを作り直すことによって、テストごとにノードの設定を変えるということが可能になっています。これにより、不安定なテストだけ録画やログ出力したり、国際化対応のテストだけ環境変数で言語やロケールを変更するといったことも可能になっています。

セッション単位でノードを作り直すオーバーヘッドが気がかりでしたが、今回試している範囲では特に気になりませんでした。Node 側から Selenium Server を排除しているのが大きいのではと思います。

細かい所としては、Selenoid が提供している Docker イメージのタグがブラウザのバージョンと一致するようになっているのが地味に嬉しいです。公式の Docker イメージは Selenium Server のバージョンがタグに設定されているので、例えば selenium/node-chrome:3.141.59-europium が Chrome のどのバージョンに対応するのかがまったくわかりませんでした。一方、selenoid/vnc_chrome:71.0 ならすぐにわかります。脱 Selenium Server による大きな恩恵だと思います。

今後、Selenium で並列テストを行う上でデファクトになる可能性のあるツールだと思います。(自分が知らないだけでもうなってるのかもしれませんが)