これは、このセクションの複数ページの印刷可能なビューです。 印刷するには、ここをクリックしてください.
高度な機能
- 1: 可観測性
- 2: GraphQLクエリのサポート
- 3: Grid エンドポイント
- 4: Customizing a Node
- 5: External datastore
1 - 可観測性
目次
Selenium Grid
Grid は、さまざまなブラウザとオペレーティングシステムの組み合わせでテストを実行することにより、テストのスケーリングと分散を支援します。
可観測性
可観測性(Observability) には、トレース、メトリクス、ログの 3 つの柱があります。 Selenium Grid 4 は完全分散型に設計されているため、可観測性を確保することで内部を理解し、デバッグすることが容易になります。
分散トレーシング
1 つのリクエストやトランザクションは、複数のサービスやコンポーネントにまたがります。 トレースは、各サービスがリクエストを実行する際に、リクエストのライフサイクルをトラックします。これは、エラーシナリオのデバッグに有用です。 トレースで使用される用語は次のとおりです:
トレース トレースでは、複数のサービスを通じてリクエストの出発点から最終地点までを追跡することができます。 このリクエストの旅は、デバッグ、エンドツーエンドフローの監視、障害の特定に役立ちます。 トレースは、エンドツーエンドのリクエストフローを描きます。 各トレースは識別子としてユニークな ID を持っています。
スパン
各トレースは、スパンと呼ばれる時間で区切られたオペレーションで構成されています。 スパンには開始時刻と終了時刻があり、サービスによって実行される操作を表します。 スパンの粒度は実装方法に依存します。各スパンは一意の識別子を持ちます。 トレース内のすべてのスパンは、同じトレース ID を持ちます。
スパン属性 スパン属性は各スパンの付加的な情報を提供するキーと値のペアです。
イベント イベントは、スパン内のタイムスタンプ付きログです。 既存のスパンに追加のコンテキストを提供します。 イベントには、イベント属性としてキーと値のペアも含まれます。
イベントロギング
アプリケーションのデバッグには、ロギングが欠かせません。 ログの記録は多くの場合、人間が読める形式で行われます。 しかし、機械がログを検索・分析するためには、明確に定義されたフォーマットである必要があります。 構造化ロギングは、固定フォーマットで一貫してログを記録する一般的な方法です。 一般的には次のようなフィールドが含まれます。
- タイムスタンプ
- ログレベル
- ロガークラス
- ログメッセージ (これはさらに、ログが記録された操作に関するフィールドに分解されます)
ログとイベントは密接に関連しています。 イベントは、1 つの処理を行うための情報を全てカプセル化します。 ログは基本的にイベントのサブセットです。 重要なのは、どちらもデバッグを支援することです。 詳細については、以下のリソースを参照してください。
- https://www.honeycomb.io/blog/how-are-structured-logs-different-from-events/
- https://charity.wtf/2019/02/05/logs-vs-structured-events/
Grid の可観測性
Selenium サーバーは OpenTelemetry を使ってトレースできるようになっています。 サーバへのすべてのリクエストは、最初から最後までトレースされます。 各トレースは、リクエストがサーバ内で実行されるときの一連のスパンから構成されます。 Selenium サーバのスパンのほとんどは、2 つのイベントから構成されています。
- 通常イベント- 単一の処理に関するすべての情報を記録し、処理が正常に完了したことを知らせます。
- エラーイベント- エラーが発生するまでのすべての情報を記録し、エラー情報を記録します。例外イベントをマークします。
Selenium サーバーを起動する:
トレースの可視化
すべてのスパン、イベント、およびそれぞれの属性がトレースの一部となります。 トレースは、上記のすべてのモードでサーバを実行している間動作します。 トレースはデフォルトで Selenium サーバで有効になっています。
Selenium サーバーは、2 つのエクスポーター経由でトレースをエクスポートします。
- コンソール - すべてのトレースと、それに含まれるスパンを FINE レベルでログに出力します。デフォルトでは、Selenium サーバーは INFO レベル以上のログを出力します。 log-level フラグを使うと、Selenium Grid jar を実行する際に任意のログレベルを指定することができます。
java -jar selenium-server-4.0.0-<selenium-version>.jar standalone --log-level FINE
- Jaeger UI - OpenTelemetry は、コード内のトレースを計測するための API と SDK を提供します。一方、Jaeger はトレースのバックエンドで、トレースのテレメトリデータを収集し、データのクエリ、フィルタリング、ビジュアライズの機能を提供します。
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 サーバーではぷるティプリントはオフになっています。
以上がトレースとログをセットアップするための手順です。
参考
2 - GraphQLクエリのサポート
GraphQLは、APIのクエリ言語であり、既存のデータでこれらのクエリを実行するためのランタイムです。 これにより、ユーザーは必要なものだけを正確に要求することができます。
列挙型(Enum)
列挙型は、フィールドの可能な値のセットを表します。
たとえば、 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で照会する
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>
グリッド内の各ノードのcapabilityを照会する
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>
各ノードとグリッドのURIを照会する
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>
3 - Grid エンドポイント
Grid
Grid ステータス
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'
4 - Customizing a Node
Page being translated from English to Japanese. Do you speak Japanese? Help us to translate it by sending us pull requests!
How to customize a Node
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 typeorg.openqa.selenium.grid.node.Node
Config
is of typeorg.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:
Custom Node as an uber jar
- Create a sample project using your favourite build tool (Maven|Gradle).
- Add the below dependency to your sample project.
- Add your customized Node to the project.
- Build an uber jar to be able to start the Node using
java -jar
command. - Now start the Node using the 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
)
Custom Node as a regular jar
- Create a sample project using your favourite build tool (Maven|Gradle).
- Add the below dependency to your sample project.
- Add your customized Node to the project.
- Build a jar of your project using your build tool.
- Now start the Node using the command:
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.
Sample customized 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 anode
.- It can be created by calling
LocalNodeFactory.create(config);
, where:LocalNodeFactory
belongs toorg.openqa.selenium.grid.node.local
Config
belongs toorg.openqa.selenium.grid.config
- It can be created by calling
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.
5 - External datastore
Page being translated from English to Japanese. Do you speak Japanese? Help us to translate it by sending us pull requests!
Table of Contents
Introduction
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.
Setup
- Coursier - As a dependency resolver, so that we can download maven artifacts on the fly and make them available in our classpath
- Docker - To manage our PostGreSQL/Redis docker containers.
Database backed Session Map
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.
Steps
You can skip this step if you already have a PostGreSQL database instance available at your disposal.
- Create a sql file named
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)
);
- In the same directory as the
init.sql
, create a file nameddocker-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.
- Create a Selenium Grid configuration file named
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.
- Below is a simple shell script (let’s call it
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
- The variable names from the above script have been replaced with their actual values for clarity.
- Remember to substitute
localhost
with the actual hostname of the machine where yourEvent-Bus
is running. - The arguments being passed to
coursier
are basically the GAV (Group Artifact Version) Maven co-ordinates of:- selenium-session-map-jdbc which is needed to help us store sessions information in database
- postgresql which is needed to help us talk PostGreSQL database.
sessions.toml
is the configuration file that we created earlier.
Redis backed Session Map
We will spin off a Redis Cache docker container using a docker compose file.
Steps
You can skip this step if you already have a Redis Cache instance available at your disposal.
- Create a file named
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
- Create a Selenium Grid configuration file named
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.
- Below is a simple shell script (let’s call it
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
- The variable names from the above script have been replaced with their actual values for clarity.
- Remember to substitute
localhost
with the actual hostname of the machine where yourEvent-Bus
is running. - The arguments being passed to
coursier
are basically the GAV (Group Artifact Version) Maven co-ordinates of:- selenium-session-map-redis which is needed to help us store sessions information in Redis Cache.
sessions.toml
is the configuration file that we created earlier.