ノードのカスタマイズ
ノードをカスタマイズする方法
ノードをニーズに合わせてカスタマイズしたい場合があります。
例えば、セッション実行開始前に追加のセットアップを行い、セッション完了後にクリーンアップを行うことが考えられます。
これを行うには、次の手順に従います。
`org.openqa.selenium.grid.node.Node` を拡張するクラスを作成します。
次のような署名を持つスタティックメソッド(これはファクトリメソッドになります)を新しく作成したクラスに追加します。
`public static Node create(Config config)`。ここで
- `Node` は `org.openqa.selenium.grid.node.Node` 型です。
- `Config` は `org.openqa.selenium.grid.config.Config` 型です。
このファクトリメソッド内に、新しいクラスを作成するロジックを含めます。
この新しいカスタマイズされたロジックをハブに組み込むには、ノードを起動し、上記のクラスの完全修飾クラス名を引数 `--node-implementation` に渡します。
これらすべての例を見てみましょう。
Uber jar としてのカスタムノード
- お好みのビルドツール(Maven|Gradle)を使用してサンプルプロジェクトを作成します。
- サンプルプロジェクトに以下の依存関係を追加します。
- カスタマイズしたノードをプロジェクトに追加します。
- `java -jar` コマンドを使用してノードを起動できるように、uber jar をビルドします。
- 次のコマンドを使用してノードを起動します。
java -jar custom_node-server.jar node \
--node-implementation org.seleniumhq.samples.DecoratedLoggingNode
注: ビルドツールとして Maven を使用している場合は、maven-shade-plugin を maven-assembly-plugin の代わりに使用することをお勧めします。maven-assembly-plugin は複数の Service Provider Interface ファイル (META-INF/services
) をマージする際に問題があるようです。
通常の jar としてのカスタムノード
- お好みのビルドツール(Maven|Gradle)を使用してサンプルプロジェクトを作成します。
- サンプルプロジェクトに以下の依存関係を追加します。
- カスタマイズしたノードをプロジェクトに追加します。
- ビルドツールを使用してプロジェクトの jar をビルドします。
- 次のコマンドを使用してノードを起動します。
java -jar selenium-server-4.6.0.jar \
--ext custom_node-1.0-SNAPSHOT.jar node \
--node-implementation org.seleniumhq.samples.DecoratedLoggingNode
以下は、ノード上で関心のあるアクティビティ(セッションの作成、セッションの削除、WebDriver コマンドの実行など)が発生するたびにコンソールにメッセージを出力するサンプルです。
カスタマイズされたノードのサンプル
package org.seleniumhq.samples;
import java.io.IOException;
import java.net.URI;
import java.util.UUID;
import java.util.function.Supplier;
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.io.TemporaryFilesystem;
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, NodeId nodeId, URI uri, Secret registrationSecret, Duration sessionTimeout) {
super(tracer, nodeId, uri, registrationSecret, sessionTimeout);
}
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);
NodeOptions nodeOptions = new NodeOptions(config);
Duration sessionTimeout = nodeOptions.getSessionTimeout();
// Refer to the foot notes for additional context on this line.
Node node = LocalNodeFactory.create(config);
DecoratedLoggingNode wrapper = new DecoratedLoggingNode(loggingOptions.getTracer(),
node.getId(),
uri,
secretOptions.getRegistrationSecret(),
sessionTimeout);
wrapper.node = node;
return wrapper;
}
@Override
public Either<WebDriverException, CreateSessionResponse> newSession(
CreateSessionRequest sessionRequest) {
return perform(() -> node.newSession(sessionRequest), "newSession");
}
@Override
public HttpResponse executeWebDriverCommand(HttpRequest req) {
return perform(() -> node.executeWebDriverCommand(req), "executeWebDriverCommand");
}
@Override
public Session getSession(SessionId id) throws NoSuchSessionException {
return perform(() -> node.getSession(id), "getSession");
}
@Override
public HttpResponse uploadFile(HttpRequest req, SessionId id) {
return perform(() -> node.uploadFile(req, id), "uploadFile");
}
@Override
public HttpResponse downloadFile(HttpRequest req, SessionId id) {
return perform(() -> node.downloadFile(req, id), "downloadFile");
}
@Override
public TemporaryFilesystem getDownloadsFilesystem(UUID uuid) {
return perform(() -> {
try {
return node.getDownloadsFilesystem(uuid);
} catch (IOException e) {
throw new RuntimeException(e);
}
}, "downloadsFilesystem");
}
@Override
public TemporaryFilesystem getUploadsFilesystem(SessionId id) throws IOException {
return perform(() -> {
try {
return node.getUploadsFilesystem(id);
} catch (IOException e) {
throw new RuntimeException(e);
}
}, "uploadsFilesystem");
}
@Override
public void stop(SessionId id) throws NoSuchSessionException {
perform(() -> node.stop(id), "stop");
}
@Override
public boolean isSessionOwner(SessionId id) {
return perform(() -> node.isSessionOwner(id), "isSessionOwner");
}
@Override
public boolean isSupporting(Capabilities capabilities) {
return perform(() -> node.isSupporting(capabilities), "isSupporting");
}
@Override
public NodeStatus getStatus() {
return perform(() -> node.getStatus(), "getStatus");
}
@Override
public HealthCheck getHealthCheck() {
return perform(() -> node.getHealthCheck(), "getHealthCheck");
}
@Override
public void drain() {
perform(() -> node.drain(), "drain");
}
@Override
public boolean isReady() {
return perform(() -> node.isReady(), "isReady");
}
private void perform(Runnable function, String operation) {
try {
System.err.printf("[COMMENTATOR] Before %s()%n", operation);
function.run();
} finally {
System.err.printf("[COMMENTATOR] After %s()%n", operation);
}
}
private <T> T perform(Supplier<T> function, String operation) {
try {
System.err.printf("[COMMENTATOR] Before %s()%n", operation);
return function.get();
} finally {
System.err.printf("[COMMENTATOR] After %s()%n", operation);
}
}
}
注釈
上記の例では、`Node node = LocalNodeFactory.create(config);` という行で明示的に `LocalNode` を作成しています。
基本的に、利用可能な `org.openqa.selenium.grid.node.Node` の *ユーザー向け実装* は 2 種類あります。
これらのクラスは、カスタムノードを構築する方法とノードの内部構造を学ぶための良い出発点です。
- `org.openqa.selenium.grid.node.local.LocalNode` - 長時間実行されるノードを表すために使用され、`node` を起動したときに組み込まれるデフォルトの実装です。
- これは、`LocalNodeFactory.create(config);` を呼び出すことで作成できます。ここで
- `LocalNodeFactory` は `org.openqa.selenium.grid.node.local` に属します。
- `Config` は `org.openqa.selenium.grid.config` に属します。
- これは、`LocalNodeFactory.create(config);` を呼び出すことで作成できます。ここで
- `org.openqa.selenium.grid.node.k8s.OneShotNode` - これは、1 回のテストセッションを処理した後、ノードが正常にシャットダウンする特別なリファレンス実装です。このクラスは現在、プリビルドされた Maven アーティファクトの一部としては利用できません。