Selenium Grid を利用して、クライアントからリモートブラウザーインスタンスにコマンドを ルーティングし、リモートマシン上で WebDriver スクリプトを実行することができます。
Grid の目標は、
- 複数のマシンでの並行したテスト実行を、簡単な方法で提供する
- 異なるバージョンのブラウザでのテストを可能にする
- クロスプラットフォームテストを可能にする
興味がありますか? Grid の仕組みと設定方法が知りたければ以下のセクションを読んでください。
これは、このセクションの複数ページの印刷可能なビューです。 印刷するには、ここをクリックしてください.
Selenium Grid を利用して、クライアントからリモートブラウザーインスタンスにコマンドを ルーティングし、リモートマシン上で WebDriver スクリプトを実行することができます。
Grid の目標は、
興味がありますか? Grid の仕組みと設定方法が知りたければ以下のセクションを読んでください。
事前条件
PATH
から見つからない場合 Selenium Manager がドライバーを設定します。PATH
が通っているインストール済みのものGrid の起動
java -jar selenium-server-<version>.jar standalone
あなたの WebDriver テストの対象を http://localhost:4444 に向ける
(必要があれば) ブラウザでhttp://localhost:4444を開いて実行中のテストや利用可能な capabilities を確認する。
さらにオプションを知りたい場合は以降のセクションに進んでください。
Grid は 6 つの異なるコンポーネントで構成され、様々な方法でデプロイすることができます。
必要に応じて、それぞれ個別に起動する(分散)か、ハブ&ノードのグループに分けるか、 全てを一つのマシンで起動する(スタンドアロン)かを選べます。
スタンドアロンは全ての Gridコンポーネントを 1 つに連結します。 スタンドアロンモードはシングルプロセスで動き、Grid の全機能を利用することができます。 スタンドアロンは単一のマシン上でのみ動かすことができます。
スタンドアロンは Selenium Grid を起動する最も簡単な方法でもあります。
デフォルトではサーバーはhttp://localhost:4444 で RemoteWebDriver
リクエストをリッスンします。
サーバーはデフォルトでシステムパス上の利用可能なドライバーを検出します。
java -jar selenium-server-<version>.jar standalone
スタンドアロンで Grid のを起動したら、WebDriver テストの対象をhttp://localhost:4444に向けてください。
スタンドアロンの一般的なユースケースは:
RemoteWebDriver
を使用したローカルでの開発やデバッグハブ&ノードは最も利用されているロールです。その理由は:
ハブは以下のコンポーネントで構成されています。 ルーター、ディストリビューター、セッションマップ、新規セッションキュー、イベントバス。
java -jar selenium-server-<version>.jar hub
デフォルトでは、サーバーはhttp://localhost:4444にて RemoteWebDriver
リクエストを待ち受けます。
ノードは起動時にシステムのパス が通っている利用可能なドライバーを検出します。
次のコマンドはノードがハブと同じマシン上で動作していることを前提としています。
java -jar selenium-server-<version>.jar node
ノード 1
java -jar selenium-server-<version>.jar node --port 5555
ノード 2
java -jar selenium-server-<version>.jar node --port 6666
ハブとノードは HTTP とイベントバスを介して通信します (イベントバスはハブの一部として存在します)。 ノードはイベントバスを通じてメッセージを送信し、登録処理を開始します。 ハブがメッセージを受け取り、ノードの存在を確かめるため HTTP を使ってノードにアクセスします。
ハブがデフォルトのポートを使用していれば、 --hub
フラグでノードを登録することができます。
java -jar selenium-server-<version>.jar node --hub http://<hub-ip>:4444
ハブがデフォルトのポートを使用していない場合、--publish-events
と --subscribe-events
のフラグが必要です。
例えばハブが 8886
8887
8888
ポートを利用している場合、
java -jar selenium-server-<version>.jar hub --publish-events tcp://<hub-ip>:8886 --subscribe-events tcp://<hub-ip>:8887 --port 8888
ノードはこれらのポートを登録する際に使用します。
java -jar selenium-server-<version>.jar node --publish-events tcp://<hub-ip>:8886 --subscribe-events tcp://<hub-ip>:8887
分散 Grid を利用すると、各コンポーネントは別々に起動され異なるマシン上で動作します。
デフォルトポートは 4442
, 4443
, 5557
です。
java -jar selenium-server-<version>.jar event-bus --publish-events tcp://<event-bus-ip>:4442 --subscribe-events tcp://<event-bus-ip>:4443 --port 5557
デフォルトポートは 5559
です。
java -jar selenium-server-<version>.jar sessionqueue --port 5559
デフォルトのセッションマップのポートは 5556
です。
セッションマップはイベントバスと通信します。
java -jar selenium-server-<version>.jar sessions --publish-events tcp://<event-bus-ip>:4442 --subscribe-events tcp://<event-bus-ip>:4443 --port 5556
デフォルトのディストリビューターのポートは 5553
です。
ディストリビューターは新規セッションキュー、セッションマップ、イベントバス、ノードと通信します。
java -jar selenium-server-<version>.jar distributor --publish-events tcp://<event-bus-ip>:4442 --subscribe-events tcp://<event-bus-ip>:4443 --sessions http://<sessions-ip>:5556 --sessionqueue http://<new-session-queue-ip>:5559 --port 5553 --bind-bus false
デフォルトのルーターのポートは 4444
です。
ルーターは新規セッションキュー、セッションマップ、ディストリビューターと通信します。
java -jar selenium-server-<version>.jar router --sessions http://<sessions-ip>:5556 --distributor http://<distributor-ip>:5553 --sessionqueue http://<new-session-queue-ip>:5559 --port 4444
デフォルトのノードのポートは 5555
です。
java -jar selenium-server-<version>.jar node --publish-events tcp://<event-bus-ip>:4442 --subscribe-events tcp://<event-bus-ip>:4443
テストにメタデータを追加して、GraphQL 経由で使用するか、Selenium Grid UI 経由でその一部( se:name
など) を可視化します。
メタデータは capability にse:
プリフィックスをつけることで追加できます。
Java での簡単な例を紹介します。
ChromeOptions chromeOptions = new ChromeOptions();
chromeOptions.setCapability("browserVersion", "100");
chromeOptions.setCapability("platformName", "Windows");
// Showing a test name instead of the session id in the Grid UI
chromeOptions.setCapability("se:name", "My simple test");
// Other type of metadata can be seen in the Grid UI by clicking on the
// session info or via GraphQL
chromeOptions.setCapability("se:sampleMetadata", "Sample metadata value");
WebDriver driver = new RemoteWebDriver(new URL("http://gridUrl:4444"), chromeOptions);
driver.get("http://www.google.com");
driver.quit();
Grid 起動後、ステータスを問い合わせる方法は、 Grid UI と API 呼び出しの主に 2 通りあります。
Grid UI は、お好みのブラウザでhttp://localhost:4444 にアクセスすることで見られます。
API 呼び出しはhttp://localhost:4444/statusのエンドポイントか、 GraphQLが利用できます。
このページで紹介するコマンドの例は、わかりやすくするために コンポーネントがローカルで動作していると仮定しています。 より詳細な例と使用方法はコンポーネントの章を参照してください。
デフォルトでは Grid はAsyncHttpClientを使用します。 AsyncHttpClient は Netty を使ったオープンソースのライブラリで、非同期での HTTP リクエストを実現します。 さらに WebSocket をサポートするため Grid に適しています。
しかし、AsyncHttpClient は 2021 年からあまり活発にメンテナンスされていません。 そして Java 11+ではビルトインの HTTP と WebSocket のクライアントを提供しています。 現在 Selenium はサポートする最小バージョンを Java11 にアップグレードする計画をしていますが、 それにはかなりの労力が必要です。メジャーリリースに合わせることはユーザー体験にとって重要です。
Java11 のクライアントを利用するにはselenium-http-jdk-client
jar ファイルをダウンロードし、
--ext
フラグで Grid の jar のクラスパスに通す必要があります。
jar ファイルはrepo1.maven.orgから直接ダウンロードできます。 Grid を起動する方法は以下の通りです:
java -Dwebdriver.http.factory=jdk-http-client -jar selenium-server-<version>.jar --ext selenium-http-jdk-client-<version>.jar standalone
selenium-http-jdk-client
をダウンロードする別の方法としてCoursierを使う方法があります。
java -Dwebdriver.http.factory=jdk-http-client -jar selenium-server-<version>.jar --ext $(coursier fetch -p org.seleniumhq.selenium:selenium-http-jdk-client:<version>) standalone
ハブ&ノードか分散モードで動かす場合、-Dwebdriver.http.factory=jdk-http-client
と
--ext
フラグの設定が各コンポーネントに必要になります。
Grid ロールの選択は、どのような OS やブラウザをサポートする必要があるかによって決まります。 どの OS、ブラウザをサポートするか、どのくらいの並列セッションを実行するか、マシンの数とそれらの性能(CPU、RAM)に依存します。
セッションを並列で作成するのは、ディストリビューターが利用可能なプロセッサーに依存します。たとえば、 マシンに 4 つの CPU がある場合、ディストリビューターは同時に最大 4 つのセッションしか作成できません。
デフォルトでは、ノードがサポートする同時セッションの最大数は、利用可能な CPU の数によって制限されます。 たとえば、ノードのマシンに 8CPU がある場合、同時に最大 8 つのブラウザセッションを実行できます(ただし、Safari は常に 1 つです)。 また各ブラウザセッションは約 1GB の RAM を使用することが期待されます。
一般的にノードはできるだけ小さくすることが推奨されます。32CPU と 32GB の RAM を持つマシンで 32 の同時ブラウザセッションを実行するよりも、 32 の小さな プロセスをよりよく分離するために、 32 の小さなノードを持つことが推奨されます。これによって、もしノードに障害が発生しても、分離されて処理されます。 Docker はこの方法を実現するための優れたツールです。
デフォルト値(ブラウザあたり 1CPU/1GB RAM)は推奨値であり、あなたの用途に沿わない可能性があることに注意してください。 この値は参考値としての推奨であり、継続的にパフォーマンスを測定することで、あなたの環境にとって理想的な値を見つけることができるでしょう。
Grid のサイズは、サポートされる同時セッションの数と、ノードの数に関連しており、 万能なサイズというものはありません。以下のサイズは概算で、環境が違えば変わる可能性があります。 例えば、120 台のノードを持つハブ&ノードの場合、ハブが十分なリソースを持っていればうまく機能するかもしれません。 また、この数値は確定したものではありません。フィードバックをお待ちしています。
5 台以下のノードで、スタンドアロンかハブ&ノード
6〜60 台のノードで、ハブ&ノード
60〜100 台のノードで、ハブ&ノード、あるいは 100 台以上のノードで分散
Grid を保護しないと、以下のような問題が発生する可能性があります。
Detectify のブログで公開されてしまった Grid が どのように悪用されるかを紹介しています: Don’t Leave your Grid Wide Open
Grid をいつ使うべきでしょうか?
Selenium Grid はノードと呼ばれる複数のマシンを使用して並列でテストスイートを実行します。 大規模な長時間実行されるテストでは数分から数時間、あるいは数日単位での短縮が可能です。 つまり、あなたのテスト対象のアプリケーションのテスト結果を得るまでの時間を短縮します。
Grid は複数の異なるブラウザーに対してテストを実行でき、また同じブラウザーインスタンスに対して複数のテストを実行することも可能です。 たとえば 6 つのノードで構成された Grid があるとします。 最初のマシンは FireFox の最新バージョン、2 つめは FireFox の最新から一つ前のバージョン、 3 つめは 最新の Chrome、そして残りは Mac Mini で最新の Safari を使って 3 つのテストを並行して実行することができます。
実行時間は簡単な式で表すことができます:
テストの数 * 平均テスト時間 / ノード数 = 合計実行時間
15 * 45s / 1 = 11m 15s // Grid なし
15 * 45s / 5 = 2m 15s // 5 ノードの Grid
15 * 45s / 15 = 45s // 15 ノードの Grid
100 * 120s / 15 = 13m 20s // Grid なしの場合3時間以上かかります
テストスイートが実行されると Grid はテストで設定されたブラウザに対して実行するテストを割り当てます。
このような設定により、大規模な Selenium テストスイートであっても実行時間を大幅に短縮することができます。
Selenium Grid は Selenium プロジェクトの一部であり、 Selenium コアの開発コミッターと同じチームによってメンテナンスされています。 テストの実行速度の重要性を認識し、Grid は Selenium プロジェクトの初期段階から重要な役割を担っています。
Selenium Grid 4 は以前のバージョンから一新し、全面的に作り直されました。 全体的なパフォーマンスの改善と標準の準拠に加え、より現代的なコンピューティングとソフトウェア開発 に適応するために機能ごとに分割されました。 コンテナ化とクラウド上での分散スケーラビリティのために構築された、現代に適した全く新しいソリューションです。
これは Grid のエントリポイントであり、すべての外部リクエストを受信し正しいコンポーネントへルーティングします。
ルーターが新規セッションリクエストを受信すると、新規セッションキューに転送します。
リクエストが既存のセッションのものである場合、ルーターはセッションマップに、 セッションが実行されている ノード ID の取得を要求します。そしてノードにリクエストを直接転送します。
ルーターは、リクエストをより処理能力の高いコンポーネントに負荷を分散させます。 この負荷分散処理自体もコンポーネントに不必要に負荷をかけることはありません。
ディストリビューターの主な責務は 2 つあります:
イベントバスを通じてノード登録イベントを贈ることでノードをディストリビューターに登録します。 ディストリビューターはそのイベントを受けてノードの存在を HTTP リクエストで確認します。 リクエストが成功した場合、ディストリビューターはノードを登録し、Grid モデルを通じて追跡を開始します。
新規セッションリクエストがルーターに送信されると、リクエストは新規セッションキューに転送されます。 ディストリビューターは新規セッションキューをポーリングし、保留中の新規セッションリクエストを見つけると、 セッションが作成可能なノードを探します。 セッションが作成されるとディストリビューターは セッション ID とセッションが実行されるノードの紐付けをセッションマップに保存します。
セッションマップはセッション ID とセッションが実行されているノードの紐付けを保存します。 これによりルーターがリクエストをノードに転送できるようにします。 ルーターはセッションマップにセッション ID に紐づくノードを問い合わせます。
新規セッションキューはすべての新規セッションリクエストを FIFO 順で保持します。 リクエストのタイムアウトとリトライ間隔の設定が可能です。
ルーターは新規セッションリクエストを新規セッションキューに追加し、レスポンスを待ちます。 新規セッションキューは定期的にキュー内のリクエストがタイムアウトしていないかをチェックし、 タイムアウトしたリクエストがあればリクエストを拒否しキューから取り除きます。
ディストリビューターはスロットに空きがあるかを定期的にチェックします。 もし空きがあれば、新規セッションキューから最初にマッチするリクエストを取り出し、 新規セッションの作成を試みます。
リクエストされた capabilities にマッチする空きノードスロットがあれば、 ディストリビューターは空きスロットの確保を試みます。全てのスロットがビジーだった場合、 ディストリビューターはリクエストをキューに戻します。 リトライ中やキューに戻す最中にリクエストがタイムアウトした場合リクエストは拒否されます。
セッションの作成に成功すると、ディストリビューターはセッションの情報を新規セッションキューに送信し、 これがルーターへのレスポンスとして送信され、最終的にクライアントに返ります。
Grid は複数のノードを持つことができます。 ノードは、各ノードが実行されているマシン上の利用可能なブラウザのスロットを管理します。
ノードは、イベントバスを介して自身をディストリビューターに登録します。 構成情報は登録メッセージの一部として送信されます。
デフォルトでは、ノードはマシンのパス上に存在する全てのブラウザドライバーを自動で登録します。 また FireFox と Chromium ベースブラウザの場合、CPU1 つにつき 1 スロットを作成します。 Safari の場合は 1 つのスロットのみ作成します。 特定の設定によってセッションを Docker コンテナで実行したり、コマンドを中継したりすることも可能です。
ノードは受信したコマンドを実行するだけで、コマンドの評価・判断や、フロー制御以外の制御は行いません。 ノードが実行されているマシンは、他のコンポーネントと同じ OS を持つ必要はありません。 たとえば、Windows ノードには IE Mode on Edge をブラウザーオプションとして提供する機能がありますが、 これは Linux または Mac では不可能です。 Grid は 複数の Windows, Mac, Linux ノードで構成することが可能です。
イベントバスはノード、ディストリビューター、セッションキュー、セッションマップ間の通信経路として機能します。 Grid は内部通信のほとんどをメッセージで行うことで、負荷の高い HTTP 呼び出しを避けています。 分散モードで Grid を起動する場合、イベントバスは最初に起動されるべきコンポーネントです。
ヘルプコマンドは、現在のコード実装に基づいて情報を表示します。 したがって、ドキュメントが更新されない場合に備えて、正確な情報を提供します。 それは、新しいバージョンのグリッド 4 の構成について学習する最も簡単な方法です。
info コマンドは、次のトピックに関する詳細なドキュメントを提供します。
クイック設定のヘルプと概要は、以下を実行することで提供されます。
java -jar selenium-server-<version>.jar info config
安全な通信とノード登録のためのグリッドサーバーの設定の詳細を取得するには、以下を実行します。
java -jar selenium-server-<version>.jar info security
デフォルトでは、グリッドはローカルセッションマップを使用してセッション情報を保存します。 グリッドは、Redis や JDBC-SQL がサポートするデータベースなどの追加のストレージオプションをサポートしています。 別のセッションストレージをセットアップするには、次のコマンドを使用してセットアップ手順を取得します。
java -jar selenium-server-<version>.jar info sessionmap
デフォルトでは、トレースは有効になっています。 トレースをエクスポートして Jaeger 経由で視覚化するには、次のコマンドを使用して手順を実行します。
java -jar selenium-server-<version>.jar info tracing
java -jar selenium-server-<version>.jar --config-help
使用可能なすべてのコマンドとそれぞれの説明が表示されます。
Selenium ロールの後に–help config オプションを渡して、コンポーネント固有の構成情報を取得します。
java -jar selenium-server-<version>.jar standalone --help
java -jar selenium-server-<version>.jar hub --help
java -jar selenium-server-<version>.jar sessions --help
java -jar selenium-server-<version>.jar sessionqueue --help
java -jar selenium-server-<version>.jar distributor --help
java -jar selenium-server-<version>.jar router --help
java -jar selenium-server-<version>.jar node --help
Grid の設定には、さまざまなセクションが用意されています。 各セクションには、コマンドライン引数で設定可能なオプションがあります。コマンドライン引数で設定できます。
コンポーネントとセクションの対応は以下の通りです。
オプションが変更、または追加されたが文書化されていない場合、 このドキュメントは古くなる可能性があることに注意してください。 もしそのような状況を見つけたら、“構成ヘルプ”を確認し、 ドキュメントを更新するプルリクエストを気軽に送ってください。
スタンドアロン | ハブ | ノード | ディストリビューター | ルーター | セッション | 新規セッションキュー | |
---|---|---|---|---|---|---|---|
Distributor | |||||||
Docker | |||||||
Events | |||||||
Logging | |||||||
Network | |||||||
Node | |||||||
Router | |||||||
Relay | |||||||
Server | |||||||
SessionQueue | |||||||
Sessions |
オプション | 型 | 値/例 | 概要 |
---|---|---|---|
--healthcheck-interval |
int | 120 |
全てのノードに対してヘルスチェックを実行する頻度(秒)を指定します。これにより、サーバーは全てのノードに対して正常に ping を送信できるようになります。 |
--distributor |
uri | http://localhost:5553 |
ディストリビューターの URL。 |
--distributor-host |
string | localhost |
ディストリビューターがリッスンするホスト名。 |
--distributor-implementation |
string | org.openqa.selenium.grid.distributor.local.LocalDistributor |
デフォルトでないディストリビューター実装の完全なクラス名。 |
--distributor-port |
int | 5553 |
ディストリビューターがリッスンするポート番号。 |
--reject-unsupported-caps |
boolean | false |
Grid がサポートしていない capabilities をリクエストされた時、ディストリビューターがリクエストを即座に今日できるようにします。これはオンデマンドでノードを立ち上げをしない Grid の設定に適しています。 |
--slot-matcher |
string | org.openqa.selenium.grid.data.DefaultSlotMatcher |
デフォルト以外で使用するスロットマッチャーの完全なクラス名。これはノードが特定のセッションをサポートできるかを判断するために使用されます。 |
--slot-selector |
string | org.openqa.selenium.grid.distributor.selector.DefaultSlotSelector |
デフォルト以外のスロットセレクターの完全なクラス名。これは、ノードがマッチした後ノード内のスロットを選択するために使用されます。 |
オプション | 型 | 値/例 | 概要 |
---|---|---|---|
--docker-assets-path |
string | /opt/selenium/assets |
アセットが保存される絶対パス。 |
--docker- |
string[] | selenium/standalone-firefox:latest '{"browserName": "firefox"}' |
イメージとステレオタイプの capabilities を対応付ける Docker 設定 (例 `-D selenium/standalone-firefox:latest ‘{“browserName”: “firefox”}’) |
--docker-devices |
string[] | /dev/kvm:/dev/kvm |
コンテナに対してデバイスを公開します。各デバイスマッピングは、ホストとコンテナの両方のデバイスへのパスを、コロンで区切って保つ必要があります。例: /device/path/in/host:/device/path/in/container |
--docker-host |
string | localhost |
Docker デーモンが動作しているホスト名。 |
--docker-port |
int | 2375 |
Docker デーモンが動作しているポート名。 |
--docker-url |
string | http://localhost:2375 |
Docker デーモンに接続するための URL。 |
--docker-video-image |
string | selenium/video:latest |
ビデオレコーディングが有効になっているときに利用される Docker イメージ。 |
オプション | 型 | 値/例 | 概要 |
---|---|---|---|
--bind-bus |
boolean | false |
接続をバインドするかコネクトするかを指定します。 true の場合、コンポーネントはイベントバスにバインドされます(イベントバスもコンポーネントによって起動されます、通常はディストリビューターとハブによって起動されます)。 false の場合、コンポーネントがイベントバスにコネクトします。 |
--events-implementation |
string | org.openqa.selenium.events.zeromq.ZeroMqEventBus |
デフォルトでないイベントバス実装の完全なクラス名。 |
--publish-events |
string | tcp://*:4442 |
イベントをイベントバスに配信するための接続文字列。 |
--subscribe-events |
string | tcp://*:4443 |
イベントをイベントバスから購読するための接続文字列。 |
オプション | 型 | 値/例 | 概要 |
---|---|---|---|
--http-logs |
boolean | false |
http ログを有効にします。http ログを記録するには、トレースを有効にする必要があります。 |
--log-encoding |
string | UTF-8 |
ログのエンコーディング。 |
--log |
string | Windows パスの例: '\path\to\file\gridlog.log' or 'C:\path\path\to\file\gridlog.log' Linux/Unix/MacOS パスの例: '/path/to/file/gridlog.log' |
ログを出力するファイル。OS のファイルパスと互換性があることを確認してください。 |
--log-level |
string | “INFO” |
ログレベル。デフォルトは INFO です。 ログレベルはこちらを参照してください。 https://docs.oracle.com/javase/7/docs/api/java/util/logging/Level.html |
--plain-logs |
boolean | true |
プレーンなログを使用します。 |
--structured-logs |
boolean | false |
構造化ログを使用します。 |
--tracing |
boolean | true |
トレースを有効にします。 |
--log-timestamp-format |
string | HH:mm:ss.SSS |
ログのタイムスタンプ形式を設定できます。 |
オプション | 型 | 値/例 | 概要 |
---|---|---|---|
--relax-checks |
boolean | false |
受信リクエストのオリジンヘッダーとコンテンツタイプに対する、厳格な W3C 準拠の検証をを緩和します。 |
オプション | 型 | 値/例 | 概要 |
---|---|---|---|
--detect-drivers |
boolean | true |
現在のシステム上で利用可能なドライバーを自動で検出してノードに追加します。 |
--driver-configuration |
string[] | display-name="Firefox Nightly" max-sessions=2 webdriver-path="/usr/local/bin/geckodriver" stereotype='{"browserName": "firefox", "browserVersion": "86", "moz:firefoxOptions": {"binary":"/Applications/Firefox Nightly.app/Contents/MacOS/firefox-bin"}}' |
ノードがサポートするドライバーの一覧。可読性向上のため TOML ファイルで設定することを推奨します。 |
--driver-factory |
string[] | org.openqa.selenium.example.LynxDriverFactory '{"browserName": "lynx"}' |
完全修飾クラス名と、そのクラスが対応するブラウザの設定とのマッピング。 |
--driver-implementation |
string[] | "firefox" |
チェックされるドライバー。指定された場合、自動設定はスキップされます。 |
--node-implementation |
string | "org.openqa.selenium.grid.node.local.LocalNodeFactory" |
デフォルトでないノード実装の完全なクラス名。これはセッションのライフサイクルを管理するために使用されます。 |
--grid-url |
string | https://grid.example.com |
Grid 全体のパブリックな URL (通常ハブかルーターのアドレスです)。 |
--heartbeat-period |
int | 60 |
ノードが生存していることを知らせるため、ノードがディストリビューターに送るハードビートを、どのくらいの頻度(秒)で送るか。 |
--max-sessions |
int | 8 |
最大同時接続セッション数。デフォルトは利用可能なプロセッサーの数です。 |
--override-max-sessions |
boolean | false |
利用可能なプロセッサーの数は、推奨される最大セッション数(プロセッサーごとに 1 つのブラウザセッション)です。このフラグを true に設定すると、推奨される最大値を上書きすることができます。セッションの安定性と信頼性が損なわれ、ホストがリソースを使い果たす可能性があります。 |
--register-cycle |
int | 10 |
ノードがディストリビューターに初回登録を試みる頻度(秒)。 |
--register-period |
int | 120 |
ノードが初めてディストリビューターに初回登録を試みるのにかかる時間(秒)。この時間が経過すると、ノードは再登録を試みない。 |
--session-timeout |
int | 300 |
X をセッションタイムアウト(秒)としたとき、 ノード は、過去 X 秒間に何の活動もなかったセッションを自動的に終了させます。 これにより他のテストが利用できるようスロットを解放します。 |
--vnc-env-var |
string | START_XVFB |
VNC ストリームが利用可能かどうかを判断するために利用する環境変数。 |
--no-vnc-port |
int | 7900 |
VNC が利用可能な場合、ローカルの noVNC ストリームを取得できるポートを設定します。 |
--drain-after-session-count |
int | 1 |
X 個のセッションが実行された後に、ノードをドレインしてシャットダウンします。 Kubernetes のような環境で有用です。 0 より大きい値を指定すると、この機能が有効になります。 |
--hub |
string | http://localhost:4444 |
ハブ・ノード構成におけるハブのアドレスを指定します。ホスト名か IP アドレスが指定できます。この場合、ハブは http://hostname:4444 とみなされ、 --grid-url は同じものになります。 --publish-events は tcp://hostname:4442 、--subscribe-events は tcp://hostname:4443 となります。 hostname にポート番号が含まれている場合は、それが --grid-url に使用されますが、イベントバスの URI は変更されません。これらのデフォルト値は、適切なフラグを設定することでオーバーライドすることができます。ホスト名にプロトコル(https のような)が含まれる場合もそれが利用されます。 |
--enable-cdp |
boolean | true |
Grid 内で CDP プロキシーを有効にします。もしネットワークが web socket を許可していない場合、Grid 管理者は CDP を無効にできます。デフォルトは true です。 |
--enable-managed-downloads |
boolean | false |
This causes the Node to auto manage files downloaded for a given session on the Node. |
--selenium-manager |
boolean | false |
When drivers are not available on the current system, use Selenium Manager. False by default. |
オプション | 型 | 値/例 | 概要 |
---|---|---|---|
--service-url |
string | http://localhost:4723 |
Appium サーバーやクラウドサービスなど、WebDriver コマンドをサポートするサービスに接続するための URL です。 |
--service-host |
string | localhost |
WebDriver コマンドをサポートしてるサービスが稼働しているホスト名。 |
--service-port |
int | 4723 |
WebDriver コマンドをサポートしてるサービスが稼働しているポート番号。 |
--service-status-endpoint |
string | /status |
WebDriver サービスの状態を問い合わせるエンドポイント、オプショナルです。HTTP 200 レスポンスが期待されます。 |
--service-configuration |
string[] | max-sessions=2 stereotype='{"browserName": "safari", "platformName": "iOS", "appium:platformVersion": "14.5"}}' |
呼び出しの中継先となるサービスの設定。可読性向上のため、TOML ファイルで設定することを推奨します。 |
オプション | 型 | 値/例 | 概要 |
---|---|---|---|
--password |
string | myStrongPassword |
クライアントがサーバーに接続する際に使用するパスワード。このパスワードとユーザー名の両方が設定されていないと使用できません。 |
--username |
string | admin |
クライアントがサーバーに接続する際に使用するユーザー名。このユーザー名とパスワードの両方が設定されていないと使用できません。 |
--sub-path |
string | my_company/selenium_grid |
A sub-path that should be considered for all user facing routes on the Hub/Router/Standalone. |
オプション | 型 | 値/例 | 概要 |
---|---|---|---|
--allow-cors |
boolean | true |
Selenium サーバーが任意のホストからのウェブブラウザ接続を許可するかどうか。 |
--host |
string | localhost |
サーバーの IP もしくはホスト名、通常自動的に決定されます。 |
--bind-host |
boolean | true |
サーバがホストアドレス/ホスト名にバインドするか、あるいは到達可能な URL を知らせるためだけに使用するかを指定します。複雑なネットワーク構成で、サーバが現在の IP やホスト名ではなく、 外部の IP やホスト名で自分自身を公開する場合に有用です (例: Docker コンテナ内)。 |
--https-certificate |
path | /path/to/cert.pem |
HTTPS のためのサーバー証明書。詳細は “java -jar selenium-server.jar info security” を実行してください。 |
--https-private-key |
path | /path/to/key.pkcs8 |
HTTPS のための秘密鍵。 詳細は “java -jar selenium-server.jar info security” を実行してください。 |
--max-threads |
int | 24 |
リスナースレッドの最大数。デフォルトは、有効なプロセッサーの * 3 です。 |
--port |
int | 4444 |
リッスンポート。このパラメータは異なるコンポーネントによって使用されるため、デフォルトはありません。例えば、ルータ/ハブ/スタンドアロンは 4444 を使用し、ノードは 5555 を使用します。 |
オプション | 型 | 値/例 | 概要 |
---|---|---|---|
--sessionqueue |
uri | http://localhost:1237 |
新規セッションキューサーバーのアドレス。 |
-sessionqueue-host |
string | localhost |
新規セッションキューがリッスンするホスト。 |
--sessionqueue-port |
int | 1234 |
新規セッションキューがリッスンするポート |
--session-request-timeout |
int | 300 |
タイムアウト(秒)。 新規セッションリクエストはキューに追加され、設定された時間以上キューに残っているリクエストはタイムアウトします。 |
--session-retry-interval |
int | 5 |
リトライ間隔(秒)。すべてのスロットがビジーな場合、 新規セッションリクエストはこの時間の間隔をおいてからリトライされます。 |
オプション | 型 | 値/例 | 概要 |
---|---|---|---|
--sessions |
uri | http://localhost:1234 |
セッションマップサーバーのアドレス。 |
--sessions-host |
string | localhost |
セッションマップサーバーがリッスンするホスト。 |
--sessions-port |
int | 1234 |
セッションマップサーバーがリッスンするポート。 |
上記のオプションはすべて、Grid コンポーネントを起動する際に使用することができます。 Grid の適切な設定を模索するのに利用してください。
TOML ファイル を使用して Grid を設定することをおすすめします。 設定ファイルは読みやすく、コード管理できます。
必要に応じて TOML ファイルと CLI オプションを併用することができます。
コマンドラインフラグとしてオプションを渡すには、適切なコンポーネントを特定し以下のテンプレートのようにします。
java -jar selenium-server-<version>.jar <component> --<option> value
java -jar selenium-server-<version>.jar standalone --max-sessions 4 --port 4444
java -jar selenium-server-<version>.jar hub --session-request-timeout 500 --port 3333 --tracing false
java -jar selenium-server-<version>.jar node --max-sessions 4 --log-level "fine" --port 7777 --driver-implementation "firefox" --driver-implementation "edge"
java -jar selenium-server-<version>.jar distributor --sessions http://localhost:5556 --sessionqueue http://localhost:5559 --bind-bus false
重要: カスタム capabilities は全てのノードに設定される必要があります。 また全てのセッションリクエストに含まれなければいけません。
java -jar selenium-server-<version>.jar hub
true
をセットしてノード A を起動するjava -jar selenium-server-<version>.jar node --detect-drivers false --driver-configuration display-name="Chrome (custom capability true)" max-sessions=1 stereotype='{"browserName":"chrome","gsg:customcap":true}' --port 6161
false
をセットしてノード B を起動するjava -jar selenium-server-<version>.jar node --detect-drivers false --driver-configuration display-name="Chrome (custom capability true)" max-sessions=1 stereotype='{"browserName":"chrome","gsg:customcap":false}' --port 6262
ChromeOptions options = new ChromeOptions();
options.setCapability("gsg:customcap", true);
WebDriver driver = new RemoteWebDriver(new URL("http://localhost:4444"), options);
driver.get("https://selenium.dev");
driver.quit();
ノード B とマッチさせるにはカスタム capability を false
に設定します。
At times a test may need to access files that were downloaded by it on the Node. To retrieve such files, following can be done.
java -jar selenium-server-<version>.jar hub
java -jar selenium-server-<version>.jar node --enable-managed-downloads true
Tests that want to use this feature should set the capability "se:downloadsEnabled"
to true
options.setCapability("se:downloadsEnabled", true);
"se:downloadsEnabled"
against ONLY those nodes which were started with --enable-managed-downloads true
Note: Currently this capability is ONLY supported on:
Edge
Firefox
andChrome
browserGET
from is /session/<sessionId>/se/files
.{
"value": {
"names": [
"Red-blue-green-channel.jpg"
]
}
}
In the response the list of file names appear under the key names
.
POST
from is /session/<sessionId>/se/files
with a payload of the form {"name": "fileNameGoesHere}
{
"value": {
"filename": "Red-blue-green-channel.jpg",
"contents": "Base64EncodedStringContentsOfDownloadedFileAsZipGoesHere"
}
}
filename
- The file name that was downloaded.contents
- Base64 encoded zipped contents of the file.The below mentioned curl
example can be used to list all the files that were downloaded by the current session in the Node, and which can be retrieved locally.
curl -X GET "http://localhost:4444/session/90c0149a-2e75-424d-857a-e78734943d4c/se/files"
A sample response would look like below:
{
"value": {
"names": [
"Red-blue-green-channel.jpg"
]
}
}
Assuming the downloaded file is named Red-blue-green-channel.jpg
, and using curl
, the
file could be downloaded with the following command:
curl -H "Accept: application/json" \
-H "Content-Type: application/json; charset=utf-8" \
-X POST -d '{"name":"Red-blue-green-channel.jpg"}' \
"http://localhost:4444/session/18033434-fa4f-4d11-a7df-9e6d75920e19/se/files"
A sample response would look like below:
{
"value": {
"filename": "Red-blue-green-channel.jpg",
"contents": "UEsDBBQACAgIAJpagVYAAAAAAAAAAAAAAAAaAAAAUmVkLWJsAAAAAAAAAAAAUmVkLWJsdWUtZ3JlZW4tY2hhbm5lbC5qcGdQSwUGAAAAAAEAAQBIAAAAcNkAAAAA"
}
}
Below is an example in Java that does the following:
import static java.util.Collections.singletonMap;
import static org.openqa.selenium.remote.http.Contents.string;
import java.io.File;
import java.lang.reflect.Type;
import java.net.URL;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.firefox.FirefoxOptions;
import org.openqa.selenium.io.Zip;
import org.openqa.selenium.json.Json;
import org.openqa.selenium.json.TypeToken;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.openqa.selenium.remote.http.Contents;
import org.openqa.selenium.remote.http.HttpClient;
import org.openqa.selenium.remote.http.HttpMethod;
import org.openqa.selenium.remote.http.HttpRequest;
import org.openqa.selenium.remote.http.HttpResponse;
public class DownloadsSample {
public static void main(String[] args) throws Exception {
// Assuming the Grid is running locally.
URL gridUrl = new URL("http://localhost:4444");
RemoteWebDriver driver = new RemoteWebDriver(gridUrl, firefoxOptions());
try {
demoFileDownloads(driver, gridUrl);
} finally {
driver.quit();
}
}
private static void demoFileDownloads(RemoteWebDriver driver, URL gridUrl) throws Exception {
// Make sure the following directory exists on your machine
File dirToCopyTo = new File("/usr/downloads/file");
driver.get("http://the-internet.herokuapp.com/download");
WebElement element = driver.findElement(By.cssSelector(".example a"));
element.click();
// The download happens in a remote Node, which makes it difficult to know when the file
// has been completely downloaded. For demonstration purposes, this example uses a
// 10-second sleep which should be enough time for a file to be downloaded.
// We strongly recommend to avoid hardcoded sleeps, and ideally, to modify your
// application under test so it offers a way to know when the file has been completely
// downloaded.
TimeUnit.SECONDS.sleep(10);
//This is the endpoint which will provide us with list of files to download and also to
//let us download a specific file.
String uri = String.format("/session/%s/se/files", driver.getSessionId());
String fileToDownload = null;
try (HttpClient client = HttpClient.Factory.createDefault().createClient(gridUrl)) {
// To list all files that are were downloaded on the remote node for the current session
// we trigger GET request.
HttpRequest request = new HttpRequest(HttpMethod.GET, uri);
HttpResponse response = client.execute(request);
String text = string(response);
Type responseType = new TypeToken<Map<String, Map<String, List<String>>>>() {
}.getType();
Map<String, Map<String, List<String>>> map = new Json().toType(text, responseType);
Map<String, List<String>> parsedResponse = map.get("value");
for (String eachFile : parsedResponse.get("names")) {
if (fileToDownload == null) {
// Let's say there were "n" files downloaded for the current session, we would like
// to retrieve ONLY the first file.
fileToDownload = eachFile;
}
}
}
try (HttpClient client = HttpClient.Factory.createDefault().createClient(gridUrl)) {
// To retrieve a specific file from one or more files that were downloaded by the current session
// on a remote node, we use a POST request.
HttpRequest request = new HttpRequest(HttpMethod.POST, uri);
request.setContent(Contents.asJson(singletonMap("name", fileToDownload)));
HttpResponse response = client.execute(request);
String text = string(response);
Type responseType = new TypeToken<Map<String, Map<String, String>>>() {
}.getType();
Map<String, Map<String, String>> map = new Json().toType(text, responseType);
Map<String, String> parsedResponse = map.get("value");
// The returned map would contain 2 keys,
// filename - This represents the name of the file (same as what was provided by the test)
// contents - Base64 encoded String which contains the zipped file.
String encodedContents = parsedResponse.get("contents");
//The file contents would always be a zip file and has to be unzipped.
Zip.unzip(encodedContents, dirToCopyTo);
System.out.println("The file which was "
+ "downloaded in the node is now available in the directory: "
+ dirToCopyTo.getAbsolutePath());
}
}
private static FirefoxOptions firefoxOptions() {
FirefoxOptions options = new FirefoxOptions();
// This capability tells the Grid, that this test should be routed ONLY to a node that can
// auto manage downloads.
options.setCapability("se:downloadsEnabled", true);
// Options specific for Firefox to avoid prompting a dialog for downloads. They might
// change in the future, so please refer to the Firefox documentation for up to date details.
options.addPreference("browser.download.manager.showWhenStarting", false);
options.addPreference("browser.helperApps.neverAsk.saveToDisk",
"images/jpeg, application/pdf, application/octet-stream");
options.addPreference("pdfjs.disabled", true);
return options;
}
}
CLI オプション に記載されている全てのオプションは TOML ファイルでも設定ができます。 このページでは異なる Grid コンポーネントの設定例を紹介します。
オプションが変更、または追加されたが文書化されていない場合、 このドキュメントは古くなる可能性があることに注意してください。 もしそのような状況を見つけたら、“構成ヘルプ”を確認し、 ドキュメントを更新するプルリクエストを気軽に送ってください。
Selenium Grid はTOMLフォーマットの設定ファイルを使用します。 設定ファイルはセクションで構成され、各セクションはオプションとその値が設定されています。
詳しい使い方はTOML ドキュメントを参照してください。 パースエラーに遭遇した場合、TOML リンターを使って検証してください。
一般的な設定の構成は以下のパターンです:
[section1]
option1="value"
[section2]
option2=["value1","value2"]
option3=true
TOML ファイルで設定された Grid コンポーネントを起動するには以下のように起動できます:
java -jar selenium-server-<version>.jar <component> --config /path/to/file/<file-name>.toml
ポート 4449 で動作し、新規セッションリクエストのタイムアウトが 500 秒のスタンドアロンサーバー。
[server]
port = 4449
[sessionqueue]
session-request-timeout = 500
Firefox と Chrome のみがデフォルトで有効になっているスタンドアロンサーバー、またはノード
[node]
drivers = ["chrome", "firefox"]
max-sessions = 3
Firefox Beta や Nightly のような、異なるブラウザのバージョンを持つことができるカスタマイズされた ドライバを用いた、スタンドアロン、またはノード。
[node]
detect-drivers = false
[[node.driver-configuration]]
max-sessions = 100
display-name = "Firefox Nightly"
stereotype = "{\"browserName\": \"firefox\", \"browserVersion\": \"93\", \"platformName\": \"MAC\", \"moz:firefoxOptions\": {\"binary\": \"/Applications/Firefox Nightly.app/Contents/MacOS/firefox-bin\"}}"
[[node.driver-configuration]]
display-name = "Chrome Beta"
stereotype = "{\"browserName\": \"chrome\", \"browserVersion\": \"94\", \"platformName\": \"MAC\", \"goog:chromeOptions\": {\"binary\": \"/Applications/Google Chrome Beta.app/Contents/MacOS/Google Chrome Beta\"}}"
[[node.driver-configuration]]
display-name = "Chrome Dev"
stereotype = "{\"browserName\": \"chrome\", \"browserVersion\": \"95\", \"platformName\": \"MAC\", \"goog:chromeOptions\": {\"binary\": \"/Applications/Google Chrome Dev.app/Contents/MacOS/Google Chrome Dev\"}}"
webdriver-executable = '/path/to/chromedriver/95/chromedriver'
Docker コンテナでセッションを実行できるスタンドアロン、またはノードサーバー。
ドライバを検出を無効にし、最大 2 つの同時セッションを持ちます。
ステレオタイプは、Docker イメージにマッピングされる必要があり、
Docker デーモンが http/tcp で公開されている必要があります。
また、devices
プロパティを用いて、ホスト上でアクセス可能なデバイスファイルを、コンテナで利用できるようにすることも可能です。
Docker デバイスをマッピングする詳しい方法はdocker
を参照してください。
[node]
detect-drivers = false
max-sessions = 2
[docker]
configs = [
"selenium/standalone-chrome:93.0", "{\"browserName\": \"chrome\", \"browserVersion\": \"91\"}",
"selenium/standalone-firefox:92.0", "{\"browserName\": \"firefox\", \"browserVersion\": \"92\"}"
]
#Optionally define all device files that should be mapped to docker containers
#devices = [
# "/dev/kvm:/dev/kvm"
#]
url = "http://localhost:2375"
video-image = "selenium/video:latest"
WebDriver をサポートする外部サービスを Selenium Grid に接続すると便利です。 例えばクラウドプロバイダーや Appium サーバーなどです。 Grid はローカルに存在しないプラットフォームやバージョンなどを幅広くカバーできるようになります。
以下は Appium サーバーを Grid に接続する例です。
[node]
detect-drivers = false
[relay]
# Default Appium/Cloud server endpoint
url = "http://localhost:4723/wd/hub"
status-endpoint = "/status"
# Stereotypes supported by the service. The initial number is "max-sessions", and will allocate
# that many test slots to that particular configuration
configs = [
"5", "{\"browserName\": \"chrome\", \"platformName\": \"android\", \"appium:platformVersion\": \"11\"}"
]
ルーター、ハブ、スタンドアロンにユーザー名とパスワードを設定することで、 Basic 認証で Grid を保護することができます。 このユーザーとパスワードは、Grid UI を読み込む時や 新しいセッションを開始する時に必要になります。
[router]
username = "admin"
password = "myStrongPassword"
Java でユーザーとパスワードを使ってセッションを開始する方法の例です。
URL gridUrl = new URL("http://admin:myStrongPassword@localhost:4444");
RemoteWebDriver webDriver = new RemoteWebDriver(gridUrl, new ChromeOptions());
重要: カスタム capabilities は全てのノードで設定する必要があります。 また全てのセッションリクエストで常に含まれる必要があります。
[node]
detect-drivers = false
[[node.driver-configuration]]
display-name = "firefox"
stereotype = '{"browserName": "firefox", "platformName": "macOS", "browserVersion":"96", "networkname:applicationName":"node_1", "nodename:applicationName":"app_1" }'
max-sessions = 5
Java でノードにマッチさせる方法の例です。
FirefoxOptions options = new FirefoxOptions();
options.setCapability("networkname:applicationName", "node_1");
options.setCapability("nodename:applicationName", "app_1");
options.setBrowserVersion("96");
options.setPlatformName("macOS");
WebDriver driver = new RemoteWebDriver(new URL("http://localhost:4444"), options);
driver.get("https://selenium.dev");
driver.quit();
The Node can be instructed to manage downloads automatically. This will cause the Node to save all files that were downloaded for a particular session into a temp directory, which can later be retrieved from the node. To turn this capability on, use the below configuration:
[node]
enable-managed-downloads = true
Refer to the CLI section for a complete example.
Grid は、Grid を運用するためのコンポーネントの集合体として設計されています。 非常に複雑に見えるかもしれませんが、 このドキュメントが混乱を解消する手助けとなることを期待します。
主要な Grid のコンポーネント:
グリッドについて説明する際に、覚えておくと便利な概念がいくつかあります:
Grid 内では主に 2 つの通信メカニズムが使われています。
どちらの仕組みを使うべきかどのように決めればよいでしょうか? 結局のところ、Grid 全体をイベントベースにモデリングしたとしても、 うまくいくでしょう。
答えは、もし実行されるアクションが同期である場合(例えばほとんどの WebDriver の呼び出しなど)、 あるいはレスポンスを受け取れなかったときに問題になるような場合、 Grid は同期呼び出しを利用します。かわりにもし、関心のあるコンポーネントに情報を ブロードキャストしたい場合、あるいはレスポンスを受け取れなくても問題にならない場合は イベントバスを使用することが望ましいです。
特筆する点として、非同期呼び出しは同期呼び出しよりも、 よりリスナーが分離されています。
Grid はコンポーネントを任意の順番で起動できるように設計されていますが、 概念的にはコンポーネントが起動する順番は:
コンポーネント間の依存関係を以下のよう表すことができます。 “✅” は同期的な依存関係があることを示しています。
イベントバス | ディストリビューター | ノード | ルーター | セッションマップ | 新規セッションキュー | |
---|---|---|---|---|---|---|
イベントバス | X | |||||
ディストリビューター | ✅ | X | ✅ | ✅ | ||
ノード | ✅ | X | ||||
ルーター | ✅ | X | ✅ | |||
セッションマップ | X | |||||
新規セッションキュー | ✅ | X |
Grid に新しいノードを登録するプロセスは軽量です。
/status
エンドポイントに GET
リクエストを試みます。
この情報をもとに Grid が設定されます。ディストリビューターは同じ /status
エンドポイントを使用して、定期的にノードをチェックしますが、
ノードは起動した後もハートビートを送り続けなければなりません。
これにより、ディストリビューターは Grid の状態を永続化しませんが、
再起動しても Grid を(最終的に)最新の状態にすることができます。
Node Status は以下のフィールドを持つ JSON オブジェクトです:
名前 | 型 | 概要 |
---|---|---|
availability | string | up , draining , down のいずれかの文字列です。重要なのは draining で、これはノードに新しいセッションを送らないことを示し、最後のセッションが終了するとノードは終了、または再起動します。 |
externalUrl | string | Grid 内の他のコンポーネントが接続するための URI。 |
lastSessionCreated | integer | このノードで最後にセッションが作成された時間のエポックタイムスタンプです。ディストリビューターは、他の条件がすべて同じであれば、最も長くアイドルであったノードに新しいセッションを送信しようとします。 |
maxSessionCount | integer | セッション数は利用可能なスロット数をカウントすることで推測できますが、この値はノードが「満杯」とみなされるまでに、ノード上で同時に実行されるセッションの最大数を決定するために使用されます。 |
nodeId | string | ノード ID を識別する UUID です。 |
osInfo | object | arch , name , version フィールドを持つオブジェクトです。Grid UI と GraphQL クエリーで利用されます。 |
slots | array | Slot オブジェクト(以下を参照)の配列。 |
version | string | ノードのバージョン(Selenium では、これは Selenium のバージョンと同じです)。 |
すべてのフィールドの値を設定することが推奨されます。
Slot オブジェクトは 1 ノードでの 1 スロットを表します。 1 スロットにつき 1 セッションを実行することができます。 1 つのノードが同時に実行できる数よりも多くのスロットを持つことができます。 例えばあるノードが 10 セッションまで同時に実行でき、 それらのセッションは Chrome, Edge, Firefox のどの組み合わせでも良い時、 ノードは ‘max session count’ を 10 として、 10 の Chrome スロット、10 の Edge スロット、10 の Firefox スロットを持ちます。
名前 | 型 | 概要 |
---|---|---|
id | string | スロット ID。 |
lastStarted | string | スロットが最後にセッションを開始した時間。ISO-8601 フォーマット。 |
stereotype | object | このスロットがマッチする最小限のcapabilities のセット。 最小の例: {"browserName": "firefox"} |
session | object | Session オブジェクト(以下を参照)。 |
スロットで実行中のセッションを表します。
名前 | 型 | 概要 |
---|---|---|
capabilities | object | セッションが持つ実際の capabilities。 The actual capabilities provided by the session. 新規セッションコマンドの戻り値と同じです。 |
startTime | string | セッションが開始した時間。ISO-8601 フォーマット。 |
stereotype | object | このスロットがマッチする最小限のcapabilities のセット。 最小の例: {"browserName": "firefox"} |
uri | string | ノードがセッションに接続するための URI。 |
Grid は、さまざまなブラウザとオペレーティングシステムの組み合わせでテストを実行することにより、テストのスケーリングと分散を支援します。
可観測性(Observability) には、トレース、メトリクス、ログの 3 つの柱があります。 Selenium Grid 4 は完全分散型に設計されているため、可観測性を確保することで内部を理解し、デバッグすることが容易になります。
1 つのリクエストやトランザクションは、複数のサービスやコンポーネントにまたがります。 トレースは、各サービスがリクエストを実行する際に、リクエストのライフサイクルをトラックします。これは、エラーシナリオのデバッグに有用です。 トレースで使用される用語は次のとおりです:
トレース トレースでは、複数のサービスを通じてリクエストの出発点から最終地点までを追跡することができます。 このリクエストの旅は、デバッグ、エンドツーエンドフローの監視、障害の特定に役立ちます。 トレースは、エンドツーエンドのリクエストフローを描きます。 各トレースは識別子としてユニークな ID を持っています。
スパン
各トレースは、スパンと呼ばれる時間で区切られたオペレーションで構成されています。 スパンには開始時刻と終了時刻があり、サービスによって実行される操作を表します。 スパンの粒度は実装方法に依存します。各スパンは一意の識別子を持ちます。 トレース内のすべてのスパンは、同じトレース ID を持ちます。
スパン属性 スパン属性は各スパンの付加的な情報を提供するキーと値のペアです。
イベント イベントは、スパン内のタイムスタンプ付きログです。 既存のスパンに追加のコンテキストを提供します。 イベントには、イベント属性としてキーと値のペアも含まれます。
アプリケーションのデバッグには、ロギングが欠かせません。 ログの記録は多くの場合、人間が読める形式で行われます。 しかし、機械がログを検索・分析するためには、明確に定義されたフォーマットである必要があります。 構造化ロギングは、固定フォーマットで一貫してログを記録する一般的な方法です。 一般的には次のようなフィールドが含まれます。
ログとイベントは密接に関連しています。 イベントは、1 つの処理を行うための情報を全てカプセル化します。 ログは基本的にイベントのサブセットです。 重要なのは、どちらもデバッグを支援することです。 詳細については、以下のリソースを参照してください。
Selenium サーバーは OpenTelemetry を使ってトレースできるようになっています。 サーバへのすべてのリクエストは、最初から最後までトレースされます。 各トレースは、リクエストがサーバ内で実行されるときの一連のスパンから構成されます。 Selenium サーバのスパンのほとんどは、2 つのイベントから構成されています。
Selenium サーバーを起動する:
すべてのスパン、イベント、およびそれぞれの属性がトレースの一部となります。 トレースは、上記のすべてのモードでサーバを実行している間動作します。 トレースはデフォルトで Selenium サーバで有効になっています。
Selenium サーバーは、2 つのエクスポーター経由でトレースをエクスポートします。
java -jar selenium-server-4.0.0-<selenium-version>.jar standalone --log-level FINE
Jaeger UI を用いたトレースの可視化の詳細な手順を確認するには、次のコマンドを実行してください:
java -jar selenium-server-4.0.0-<selenium-version>.jar info tracing
非常に参考になる例と、Jaeger にトレースを送信するスクリプトです
トレースを可視化しない場合でも、イベントロギングではトレースを有効にする必要があります。 デフォルトでは、トレースは有効です。コンソールでログを見るために、追加のパラメータを渡す必要はありません。 スパン内のすべてのイベントは FINE レベルでログに記録されます。エラーイベントは、WARN レベルでログに記録されます。
全てのイベントは次のフィールドを持ちます:
フィールド | フィールド名 | 概要 |
---|---|---|
イベント時刻 | eventId | イベントのタイムスタンプ(エポックナノ秒)。 |
トレース ID | tracedId | 各トレースはトレース ID で一意に識別されます。 |
スパン ID | spanId | トレース内の各スパンは、スパン ID により一意に識別されます。 |
スパン種別 | spanKind | スパン種別は、スパンの種類を示すスパンのプロパティです。スパンの処理の性質を識別するのに役立ちます。 |
イベント名 | eventName | ログメッセージにマッピングされます。 |
イベント属性 | eventAttributes | イベントログの核となるもので、実行された操作に基づいて JSON フォーマットのキーと値のペアが用意されています。また、ロガークラスを表示するために、ハンドラークラスアトリビュートも含まれます。 |
サンプルログ
FINE [LoggingOptions$1.lambda$export$1] - {
"traceId": "fc8aef1d44b3cc8bc09eb8e581c4a8eb",
"spanId": "b7d3b9865d3ddd45",
"spanKind": "INTERNAL",
"eventTime": 1597819675128886121,
"eventName": "Session request execution complete",
"attributes": {
"http.status_code": 200,
"http.handler_class": "org.openqa.selenium.grid.router.HandleSession",
"http.url": "\u002fsession\u002fdd35257f104bb43fdfb06242953f4c85",
"http.method": "DELETE",
"session.id": "dd35257f104bb43fdfb06242953f4c85"
}
}
上記のフィールドに加えて、OpenTelemetry の仕様に基づきエラーログは以下のフィールドで構成されます:
フィールド | フィールド名 | 概要 |
---|---|---|
例外タイプ | exception.type | 例外クラス名。 |
例外メッセージ | exception.message | 例外の原因。 |
スタックトレース | exception.stacktrace | 例外が発生した時点のコールスタックを表示します。 例外の発生源を把握するのに役立ちます。 |
サンプルエラーログ
WARN [LoggingOptions$1.lambda$export$1] - {
"traceId": "7efa5ea57e02f89cdf8de586fe09f564",
"spanId": "914df6bc9a1f6e2b",
"spanKind": "INTERNAL",
"eventTime": 1597820253450580272,
"eventName": "exception",
"attributes": {
"exception.type": "org.openqa.selenium.ScriptTimeoutException",
"exception.message": "Unable to execute request: java.sql.SQLSyntaxErrorException: Table 'mysql.sessions_mappa' doesn't exist ..." (full message will be printed),
"exception.stacktrace": "org.openqa.selenium.ScriptTimeoutException: java.sql.SQLSyntaxErrorException: Table 'mysql.sessions_mappa' doesn't exist\nBuild info: version: '4.0.0-alpha-7', revision: 'Unknown'\nSystem info: host: 'XYZ-MacBook-Pro.local', ip: 'fe80:0:0:0:10d5:b63a:bdc6:1aff%en0', os.name: 'Mac OS X', os.arch: 'x86_64', os.version: '10.13.6', java.version: '11.0.7'\nDriver info: driver.version: unknown ...." (full stack will be printed),
"http.handler_class": "org.openqa.selenium.grid.distributor.remote.RemoteDistributor",
"http.url": "\u002fsession",
"http.method": "POST"
}
}
注: ログは読みやすさのためプリティプリントされています。Selenimu サーバーではぷるティプリントはオフになっています。
以上がトレースとログをセットアップするための手順です。
GraphQLは、APIのクエリ言語であり、既存のデータでこれらのクエリを実行するためのランタイムです。 これにより、ユーザーは必要なものだけを正確に要求することができます。
列挙型は、フィールドの可能な値のセットを表します。
たとえば、 Node
オブジェクトには status
というフィールドがあります。
UP
、 DRAINING
、または UNAVAILABLE
の可能性があるため、状態は、 列挙型(具体的には、Status
タイプ)です。
スカラーはプリミティブ値です: Int
、 Float
、 String
、 Boolean
、または ID
。
GraphQL APIを呼び出すときは、スカラーのみを返すまでネストされたサブフィールドを指定する必要があります。
グリッドスキーマの構造は次のとおりです。
{
session(id: "<session-id>") : {
id,
capabilities,
startTime,
uri,
nodeId,
nodeUri,
sessionDurationMillis
slot : {
id,
stereotype,
lastStarted
}
}
grid: {
uri,
totalSlots,
nodeCount,
maxSession,
sessionCount,
version,
sessionQueueSize
}
sessionsInfo: {
sessionQueueRequests,
sessions: [
{
id,
capabilities,
startTime,
uri,
nodeId,
nodeUri,
sessionDurationMillis
slot : {
id,
stereotype,
lastStarted
}
}
]
}
nodesInfo: {
nodes : [
{
id,
uri,
status,
maxSession,
slotCount,
sessions: [
{
id,
capabilities,
startTime,
uri,
nodeId,
nodeUri,
sessionDurationMillis
slot : {
id,
stereotype,
lastStarted
}
}
],
sessionCount,
stereotypes,
version,
osInfo: {
arch,
name,
version
}
}
]
}
}
GraphQLをクエリする最良の方法は、 curl
リクエストを使用することです。
GraphQLを使用すると、必要なデータのみをフェッチできます。それ以上でもそれ以下でもありません。
GraphQLクエリの例のいくつかを以下に示します。 必要に応じて独自のクエリを作成できます。
maxSession
と sessionCount
の数を照会するcurl -X POST -H "Content-Type: application/json" --data '{"query": "{ grid { maxSession, sessionCount } }"}' -s <LINK_TO_GRAPHQL_ENDPOINT>
通常、ローカルマシンでは、 <LINK_TO_GRAPHQL_ENDPOINT>
は http://localhost:4444/graphql
になります。
curl -X POST -H "Content-Type: application/json" --data '{"query":"{ grid { uri, maxSession, sessionCount }, nodesInfo { nodes { id, uri, status, sessions { id, capabilities, startTime, uri, nodeId, nodeUri, sessionDurationMillis, slot { id, stereotype, lastStarted } }, slotCount, sessionCount }} }"}' -s <LINK_TO_GRAPHQL_ENDPOINT>
curl -X POST -H "Content-Type: application/json" --data '{"query":"{ grid { sessionCount } }"}' -s <LINK_TO_GRAPHQL_ENDPOINT>
curl -X POST -H "Content-Type: application/json" --data '{"query":"{ grid { maxSession } }"}' -s <LINK_TO_GRAPHQL_ENDPOINT>
curl -X POST -H "Content-Type: application/json" --data '{"query":"{ sessionsInfo { sessions { id, capabilities, startTime, uri, nodeId, nodeId, sessionDurationMillis } } }"}' -s <LINK_TO_GRAPHQL_ENDPOINT>
curl -X POST -H "Content-Type: application/json" --data '{"query":"{ sessionsInfo { sessions { id, slot { id, stereotype, lastStarted } } } }"}' -s <LINK_TO_GRAPHQL_ENDPOINT>
curl -X POST -H "Content-Type: application/json" --data '{"query":"{ session (id: \"<session-id>\") { id, capabilities, startTime, uri, nodeId, nodeUri, sessionDurationMillis, slot { id, stereotype, lastStarted } } } "}' -s <LINK_TO_GRAPHQL_ENDPOINT>
curl -X POST -H "Content-Type: application/json" --data '{"query": "{ nodesInfo { nodes { stereotypes } } }"}' -s <LINK_TO_GRAPHQL_ENDPOINT>
curl -X POST -H "Content-Type: application/json" --data '{"query": "{ nodesInfo { nodes { status } } }"}' -s <LINK_TO_GRAPHQL_ENDPOINT>
curl -X POST -H "Content-Type: application/json" --data '{"query": "{ nodesInfo { nodes { uri } } }"}' -s <LINK_TO_GRAPHQL_ENDPOINT>
curl -X POST -H "Content-Type: application/json" --data '{"query":"{ sessionsInfo { sessionQueueRequests } }"}' -s <LINK_TO_GRAPHQL_ENDPOINT>
curl -X POST -H "Content-Type: application/json" --data '{"query":"{ grid { sessionQueueSize } }"}' -s <LINK_TO_GRAPHQL_ENDPOINT>
Grid ステータスは Grid の現在の状態を提供します。 登録されている全てのノードの詳細で構成されます。 各ノードのステータスには、ノードの稼働状況、セッション、およびスロットに関する情報が含まれます。
cURL GET 'http://localhost:4444/status'
スタンドアロンモードでは、Grid URL は スタンドアロンサーバーのアドレスになります。
ハブ&ノードモードでは、Grid URL は ハブのアドレスになります。
完全分散モードでは、Grid URL は ルーターのアドレスになります。
上記すべてのモードのデフォルトの URL は http://localhost:4444 です。
ノードを Grid から削除するには、以下の cURL コマンドを使用します。 このコマンドは、そのノード上で実行中のセッションを停止させるものではありません。 ノードは明示的に強制終了されない限り、そのまま動作し続けます。 ディストリビューターはそのノードを認識しなくなるため、マッチする新しいセッションのリクエストは はその Node に転送されません。
スタンドアロンモードでは、ディストリビューターの URL はスタンドアロンサーバーのアドレスとなります。
ハブ&ノードモードでは、ディストリビューターの URL は ハブのアドレスになります。
cURL --request DELETE 'http://localhost:4444/se/grid/distributor/node/<node-id>' --header 'X-REGISTRATION-SECRET: <secret> '
完全分散モードでは、ディストリビューター URL は ディストリビューターのアドレスになります。
cURL --request DELETE 'http://localhost:5553/se/grid/distributor/node/<node-id>' --header 'X-REGISTRATION-SECRET: <secret>'
Grid の設定時に登録用の secret を設定していない場合は次のようにします:
cURL --request DELETE 'http://<Distributor-URL>/se/grid/distributor/node/<node-id>' --header 'X-REGISTRATION-SECRET;'
ノードドレインコマンドはノードをグレースフルシャットダウンするために利用します。 ドレインは実行中のセッションがすべて完了した後にノードを停止します。 新規のセッションは受け付けません。
スタンドアロンモードでは、ディストリビューターの URL はスタンドアロンサーバーのアドレスとなります。
ハブ&ノードモードでは、ディストリビューターの URL は ハブのアドレスになります。
cURL --request POST 'http://localhost:4444/se/grid/distributor/node/<node-id>/drain' --header 'X-REGISTRATION-SECRET: <secret> '
完全分散モードでは、ディストリビューター URL は ディストリビューターのアドレスになります。
cURL --request POST 'http://localhost:5553/se/grid/distributor/node/<node-id>/drain' --header 'X-REGISTRATION-SECRET: <secret>'
Grid の設定時に登録用の secret を設定していない場合は次のようにします:
cURL --request POST 'http://<Distributor-URL>/se/grid/distributor/node/<node-id>/drain' --header 'X-REGISTRATION-SECRET;'
この節でのエンドポイントは、ハブ&ノードモードとノードが独立して動作する完全分散型 Grid モードに適用されます。 ノードが 1 つの場合、デフォルトのノード URL は http://localhost:5555 です。 複数のノードがある場合は、Grid ステータス を使ってすべてのノードの詳細とノードアドレスを取得してください。
ノードステータスは基本的にノードのヘルスチェックのためのものです。 ディストリビューターは定期的にノードの状態を ping で取得し、それに応じて Grid モデルを更新します。 ステータスには稼働状況、セッション、およびスロットに関する情報が含まれます。
cURL --request GET 'http://localhost:5555/status'
ディストリビューターは ドレインコマンドを適切なノードに渡します。 ノードを直接ドレインするには以下の cURL コマンドを使います。 どちらのエンドポイントも有効であり、同じ結果になります。 ドレインは、ノードを停止する前に進行中のセッションを終了させます。
cURL --request POST 'http://localhost:5555/se/grid/node/drain' --header 'X-REGISTRATION-SECRET: <secret>'
Grid の設定時に登録用の secret を設定していない場合は次のようにします:
cURL --request POST 'http://<node-URL>/se/grid/node/drain' --header 'X-REGISTRATION-SECRET;'
あるセッションがノードに属しているかどうかをチェックするには、以下の cURL コマンドを使います。
cURL --request GET 'http://localhost:5555/se/grid/node/owner/<session-id>' --header 'X-REGISTRATION-SECRET: <secret>'
Grid の設定時に登録用の secret を設定していない場合は次のようにします:
cURL --request GET 'http://<node-URL>/se/grid/node/owner/<session-id>' --header 'X-REGISTRATION-SECRET;'
もしセッションがノードに属していたら true を返し、そうでなければ false が返ります。
セッションを削除すると、WebDriver セッションが終了し、ドライバがアクティブなセッションマップから削除されます。 削除されたセッション ID を使用するリクエストや、ドライバのインスタンスを再利用しようとすると、エラーとなります。
cURL --request DELETE 'http://localhost:5555/se/grid/node/session/<session-id>' --header 'X-REGISTRATION-SECRET: <secret>'
Grid の設定時に登録用の secret を設定していない場合は次のようにします:
cURL --request DELETE 'http://<node-URL>/se/grid/node/session/<session-id>' --header 'X-REGISTRATION-SECRET;'
新規セッションキューには、新規セッションリクエストが格納されます。 キューをクリアするには、以下に挙げる cURL コマンドを使用します。 キューを消去すると、キューにあるすべてのリクエストを拒否します。 サーバーは各リクエストのそれぞれのクライアントにエラーレスポンスを返します。 クリアコマンドの結果は、削除されたリクエストの数です。
スタンドアロンモードでは、キューの URL はスタンドアロンサーバーのアドレスとなります。
ハブ&ノードモードでは、キューの URL は ハブのアドレスになります。
cURL --request DELETE 'http://localhost:4444/se/grid/newsessionqueue/queue' --header 'X-REGISTRATION-SECRET: <secret>'
完全分散モードでは、キューの URL は 新規セッションキューのアドレスになります。
cURL --request DELETE 'http://localhost:5559/se/grid/newsessionqueue/queue' --header 'X-REGISTRATION-SECRET: <secret>'
Grid の設定時に登録用の secret を設定していない場合は次のようにします:
cURL --request DELETE 'http://<URL>/se/grid/newsessionqueue/queue' --header 'X-REGISTRATION-SECRET;'
新規セッションキューには、新規セッションリクエストが格納されます。 キューにある現在のリクエストを取得するには、以下に挙げる cURL コマンドを使用します。 レスポンスはキュー内のリクエストの数とリクエストのペイロードを返します。
スタンドアロンモードでは、キューの URL はスタンドアロンサーバーのアドレスとなります。
ハブ&ノードモードでは、キューの URL は ハブのアドレスになります。
cURL --request GET 'http://localhost:4444/se/grid/newsessionqueue/queue'
完全分散モードでは、キューの URL は 新規セッションキューのアドレスになります。
cURL --request GET 'http://localhost:5559/se/grid/newsessionqueue/queue'
Page being translated from English to Japanese. Do you speak Japanese? Help us to translate it by sending us pull requests!
There are times when we would like a Node to be customized to our needs.
For e.g., we may like to do some additional setup before a session begins execution and some clean-up after a session runs to completion.
Following steps can be followed for this:
Create a class that extends org.openqa.selenium.grid.node.Node
Add a static method (this will be our factory method) to the newly created class whose signature looks like this:
public static Node create(Config config)
. Here:
Node
is of type org.openqa.selenium.grid.node.Node
Config
is of type org.openqa.selenium.grid.config.Config
Within this factory method, include logic for creating your new Class.
To wire in this new customized logic into the hub, start the node and pass in the fully qualified class name of the above class to the argument --node-implementation
Let’s see an example of all this:
java -jar
command.java -jar custom_node-server.jar node \
--node-implementation org.seleniumhq.samples.DecoratedLoggingNode
Note: If you are using Maven as a build tool, please prefer using maven-shade-plugin instead of maven-assembly-plugin because maven-assembly plugin seems to have issues with being able to merge multiple Service Provider Interface files (META-INF/services
)
java -jar selenium-server-4.6.0.jar \
--ext custom_node-1.0-SNAPSHOT.jar node \
--node-implementation org.seleniumhq.samples.DecoratedLoggingNode
Below is a sample that just prints some messages on to the console whenever there’s an activity of interest (session created, session deleted, a webdriver command executed etc.,) on the Node.
package org.seleniumhq.samples;
import java.net.URI;
import java.util.UUID;
import org.openqa.selenium.Capabilities;
import org.openqa.selenium.NoSuchSessionException;
import org.openqa.selenium.WebDriverException;
import org.openqa.selenium.grid.config.Config;
import org.openqa.selenium.grid.data.CreateSessionRequest;
import org.openqa.selenium.grid.data.CreateSessionResponse;
import org.openqa.selenium.grid.data.NodeId;
import org.openqa.selenium.grid.data.NodeStatus;
import org.openqa.selenium.grid.data.Session;
import org.openqa.selenium.grid.log.LoggingOptions;
import org.openqa.selenium.grid.node.HealthCheck;
import org.openqa.selenium.grid.node.Node;
import org.openqa.selenium.grid.node.local.LocalNodeFactory;
import org.openqa.selenium.grid.security.Secret;
import org.openqa.selenium.grid.security.SecretOptions;
import org.openqa.selenium.grid.server.BaseServerOptions;
import org.openqa.selenium.internal.Either;
import org.openqa.selenium.remote.SessionId;
import org.openqa.selenium.remote.http.HttpRequest;
import org.openqa.selenium.remote.http.HttpResponse;
import org.openqa.selenium.remote.tracing.Tracer;
public class DecoratedLoggingNode extends Node {
private Node node;
protected DecoratedLoggingNode(Tracer tracer, URI uri, Secret registrationSecret) {
super(tracer, new NodeId(UUID.randomUUID()), uri, registrationSecret);
}
public static Node create(Config config) {
LoggingOptions loggingOptions = new LoggingOptions(config);
BaseServerOptions serverOptions = new BaseServerOptions(config);
URI uri = serverOptions.getExternalUri();
SecretOptions secretOptions = new SecretOptions(config);
// Refer to the foot notes for additional context on this line.
Node node = LocalNodeFactory.create(config);
DecoratedLoggingNode wrapper = new DecoratedLoggingNode(loggingOptions.getTracer(),
uri, secretOptions.getRegistrationSecret());
wrapper.node = node;
return wrapper;
}
@Override
public Either<WebDriverException, CreateSessionResponse> newSession(
CreateSessionRequest sessionRequest) {
System.out.println("Before newSession()");
try {
return this.node.newSession(sessionRequest);
} finally {
System.out.println("After newSession()");
}
}
@Override
public HttpResponse executeWebDriverCommand(HttpRequest req) {
try {
System.out.println("Before executeWebDriverCommand(): " + req.getUri());
return node.executeWebDriverCommand(req);
} finally {
System.out.println("After executeWebDriverCommand()");
}
}
@Override
public Session getSession(SessionId id) throws NoSuchSessionException {
try {
System.out.println("Before getSession()");
return node.getSession(id);
} finally {
System.out.println("After getSession()");
}
}
@Override
public HttpResponse uploadFile(HttpRequest req, SessionId id) {
try {
System.out.println("Before uploadFile()");
return node.uploadFile(req, id);
} finally {
System.out.println("After uploadFile()");
}
}
@Override
public void stop(SessionId id) throws NoSuchSessionException {
try {
System.out.println("Before stop()");
node.stop(id);
} finally {
System.out.println("After stop()");
}
}
@Override
public boolean isSessionOwner(SessionId id) {
try {
System.out.println("Before isSessionOwner()");
return node.isSessionOwner(id);
} finally {
System.out.println("After isSessionOwner()");
}
}
@Override
public boolean isSupporting(Capabilities capabilities) {
try {
System.out.println("Before isSupporting");
return node.isSupporting(capabilities);
} finally {
System.out.println("After isSupporting()");
}
}
@Override
public NodeStatus getStatus() {
try {
System.out.println("Before getStatus()");
return node.getStatus();
} finally {
System.out.println("After getStatus()");
}
}
@Override
public HealthCheck getHealthCheck() {
try {
System.out.println("Before getHealthCheck()");
return node.getHealthCheck();
} finally {
System.out.println("After getHealthCheck()");
}
}
@Override
public void drain() {
try {
System.out.println("Before drain()");
node.drain();
} finally {
System.out.println("After drain()");
}
}
@Override
public boolean isReady() {
try {
System.out.println("Before isReady()");
return node.isReady();
} finally {
System.out.println("After isReady()");
}
}
}
Foot Notes:
In the above example, the line Node node = LocalNodeFactory.create(config);
explicitly creates a LocalNode
.
There are basically 2 types of user facing implementations of org.openqa.selenium.grid.node.Node
available.
These classes are good starting points to learn how to build a custom Node and also to learn the internals of a Node.
org.openqa.selenium.grid.node.local.LocalNode
- Used to represent a long running Node and is the default implementation that gets wired in when you start a node
.
LocalNodeFactory.create(config);
, where:
LocalNodeFactory
belongs to org.openqa.selenium.grid.node.local
Config
belongs to org.openqa.selenium.grid.config
org.openqa.selenium.grid.node.k8s.OneShotNode
- This is a special reference implementation wherein the Node gracefully shuts itself down after servicing one test session. This class is currently not available as part of any pre-built maven artifact.
Page being translated from English to Japanese. Do you speak Japanese? Help us to translate it by sending us pull requests!
Selenium Grid allows you to persist information related to currently running sessions into an external data store. The external data store could be backed by your favourite database (or) Redis Cache system.
For the sake of this illustration, we are going to work with PostGreSQL database.
We will spin off a PostGreSQL database as a docker container using a docker compose file.
You can skip this step if you already have a PostGreSQL database instance available at your disposal.
init.sql
with the below contents:CREATE TABLE IF NOT EXISTS sessions_map(
session_ids varchar(256),
session_caps text,
session_uri varchar(256),
session_stereotype text,
session_start varchar(256)
);
init.sql
, create a file named docker-compose.yml
with its contents as below:version: '3.8'
services:
db:
image: postgres:9.6-bullseye
restart: always
environment:
- POSTGRES_USER=seluser
- POSTGRES_PASSWORD=seluser
- POSTGRES_DB=selenium_sessions
ports:
- "5432:5432"
volumes:
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
We can now start our database container by running:
docker-compose up -d
Our database name is selenium_sessions
with its username and password set to seluser
If you are working with an already running PostGreSQL DB instance, then you just need to create a database named selenium_sessions
and the table sessions_map
using the above mentioned SQL statement.
sessions.toml
with the below contents:[sessions]
implementation = "org.openqa.selenium.grid.sessionmap.jdbc.JdbcBackedSessionMap"
jdbc-url = "jdbc:postgresql://localhost:5432/selenium_sessions"
jdbc-user = "seluser"
jdbc-password = "seluser"
Note: If you plan to use an existing PostGreSQL DB instance, then replace localhost:5432
with the actual host and port number of your instance.
distributed.sh
) that we will use to bring up our distributed Grid.SE_VERSION=<current_selenium_version>
JAR_NAME=selenium-server-${SE_VERSION}.jar
PUBLISH="--publish-events tcp://localhost:4442"
SUBSCRIBE="--subscribe-events tcp://localhost:4443"
SESSIONS="--sessions http://localhost:5556"
SESSIONS_QUEUE="--sessionqueue http://localhost:5559"
echo 'Starting Event Bus'
java -jar $JAR_NAME event-bus $PUBLISH $SUBSCRIBE --port 5557 &
echo 'Starting New Session Queue'
java -jar $JAR_NAME sessionqueue --port 5559 &
echo 'Starting Sessions Map'
java -jar $JAR_NAME \
--ext $(coursier fetch -p org.seleniumhq.selenium:selenium-session-map-jdbc:${SE_VERSION} org.postgresql:postgresql:42.3.1) \
sessions $PUBLISH $SUBSCRIBE --port 5556 --config sessions.toml &
echo 'Starting Distributor'
java -jar $JAR_NAME distributor $PUBLISH $SUBSCRIBE $SESSIONS $SESSIONS_QUEUE --port 5553 --bind-bus false &
echo 'Starting Router'
java -jar $JAR_NAME router $SESSIONS --distributor http://localhost:5553 $SESSIONS_QUEUE --port 4444 &
echo 'Starting Node'
java -jar $JAR_NAME node $PUBLISH $SUBSCRIBE &
At this point the current directory should contain the following files:
docker-compose.yml
init.sql
sessions.toml
distributed.sh
You can now spawn the Grid by running distributed.sh
shell script and quickly run a test. You will notice that the Grid now stores session information into the PostGreSQL database.
In the line which spawns a SessionMap
on a machine:
export SE_VERSION=<current_selenium_version>
java -jar selenium-server-${SE_VERSION}.jar \
--ext $(coursier fetch -p org.seleniumhq.selenium:selenium-session-map-jdbc:${SE_VERSION} org.postgresql:postgresql:42.3.1) \
sessions --publish-events tcp://localhost:4442 \
--subscribe-events tcp://localhost:4443 \
--port 5556 --config sessions.toml
localhost
with the actual hostname of the machine where your Event-Bus
is running.coursier
are basically the GAV (Group Artifact Version) Maven co-ordinates of:
sessions.toml
is the configuration file that we created earlier.We will spin off a Redis Cache docker container using a docker compose file.
You can skip this step if you already have a Redis Cache instance available at your disposal.
docker-compose.yml
with its contents as below:version: '3.8'
services:
redis:
image: redis:bullseye
restart: always
ports:
- "6379:6379"
We can now start our Redis container by running:
docker-compose up -d
sessions.toml
with the below contents:[sessions]
scheme = "redis"
implementation = "org.openqa.selenium.grid.sessionmap.redis.RedisBackedSessionMap"
hostname = "localhost"
port = 6379
Note: If you plan to use an existing Redis Cache instance, then replace localhost
and 6379
with the actual host and port number of your instance.
distributed.sh
) that we will use to bring up our distributed grid.SE_VERSION=<current_selenium_version>
JAR_NAME=selenium-server-${SE_VERSION}.jar
PUBLISH="--publish-events tcp://localhost:4442"
SUBSCRIBE="--subscribe-events tcp://localhost:4443"
SESSIONS="--sessions http://localhost:5556"
SESSIONS_QUEUE="--sessionqueue http://localhost:5559"
echo 'Starting Event Bus'
java -jar $JAR_NAME event-bus $PUBLISH $SUBSCRIBE --port 5557 &
echo 'Starting New Session Queue'
java -jar $JAR_NAME sessionqueue --port 5559 &
echo 'Starting Session Map'
java -jar $JAR_NAME \
--ext $(coursier fetch -p org.seleniumhq.selenium:selenium-session-map-redis:${SE_VERSION}) \
sessions $PUBLISH $SUBSCRIBE --port 5556 --config sessions.toml &
echo 'Starting Distributor'
java -jar $JAR_NAME distributor $PUBLISH $SUBSCRIBE $SESSIONS $SESSIONS_QUEUE --port 5553 --bind-bus false &
echo 'Starting Router'
java -jar $JAR_NAME router $SESSIONS --distributor http://localhost:5553 $SESSIONS_QUEUE --port 4444 &
echo 'Starting Node'
java -jar $JAR_NAME node $PUBLISH $SUBSCRIBE &
At this point the current directory should contain the following files:
docker-compose.yml
sessions.toml
distributed.sh
You can now spawn the Grid by running distributed.sh
shell script and quickly run a test. You will notice that the Grid now stores session information into the Redis instance. You can perhaps make use of a Redis GUI such as TablePlus to see them (Make sure that you have setup a debug point in your test, because the values will get deleted as soon as the test runs to completion).
In the line which spawns a SessionMap
on a machine:
export SE_VERSION=<current_selenium_version>
java -jar selenium-server-${SE_VERSION}.jar \
--ext $(coursier fetch -p org.seleniumhq.selenium:selenium-session-map-redis:${SE_VERSION}) \
sessions --publish-events tcp://localhost:4442 \
--subscribe-events tcp://localhost:4443 \
--port 5556 --config sessions.toml
localhost
with the actual hostname of the machine where your Event-Bus
is running.coursier
are basically the GAV (Group Artifact Version) Maven co-ordinates of:
sessions.toml
is the configuration file that we created earlier.Want to support the Selenium project? Learn more or view the full list of sponsors.