Selenium 简明教程

Selenium Grid - Customizing a Node

最新版本的 Selenium Grid 4 是在不利用较旧版本的 Selenium Grid 的代码库的情况下开发的。最新版本的 Selenium Grid 4 版本拥有 Node 自定化等一些高级功能。

最新版本的 Selenium Grid 允许在三种不同的 Selenium Grid 模式下触发测试执行。它们被称为 Standalone、Hub 和 Nodes 以及 Distributed。

Node Customization in Selenium Grid

在使用 Selenium Grid 在不同的浏览器及其实平台、设备上执行并行测试或跨浏览器测试时,可能需要根据实际需要自定义 Node。

在执行会话开始前,可能需要添加一些先决步骤或在会话完成后进行任何整理活动。要自定义 Node,请执行以下步骤 −

  1. 设计一个将扩展 − 的类

org.openqa.selenium.grid.node.Node.
  1. 向新设计的类添加一个静态方法(工厂方法),该方法应当具有以下签名:public static Node created(Config config)。在此处,Node 与 org.openqa.selenium.grid.node.Node 具有相同的类型,而 Config 与 − 具有相同的类型

org.openqa.selenium.grid.config.Config.
  1. 在工厂方法内部,应添加新类的实现逻辑。

  2. 要将新开发的实现纳入 hub,请启动 node 并向 argumentos:-node-implementation 中的上一个类发送完全限定类名称。

Node Customization Using the Uber Jar

使用 uber jar 实现 node 自定化的步骤如下 −

  1. 在系统中安装 Java(版本高于 8),并使用以下命令检查它是否存在:java -version。如果已成功完成安装,将显示已安装的 java 版本。

  2. 在系统中安装 maven 并使用以下命令检查它是否存在:mvn -version。如果已成功完成安装,将显示已安装的 maven 版本。

  3. 安装任何一个 IDE,例如 Eclipse、IntelliJ 等等。

  4. 从链接 https://mvnrepository.com/artifact/ 向 pom.xml 中添加 Selenium Grid 依赖项。

  5. 从链接 https://mvnrepository.com/artifact/ 向 pom.xml 中添加 Apache Maven Shade Plugin。

  6. 向当前项目中追加自定义的 node。

  7. 借助以下命令构建 uber jar 以启动 Node:java -jar。

  8. 使用以下命令启动节点 −

java -jar custom_node-server.jar node \
--node-implementation org.seleniumhq.samples.DecoratedLoggingNode

Node Customization Using a Normal Jar

下面列出使用普通 JAR 实现节点自定义的步骤 −

  1. 在系统中安装 Java(版本高于 8),并使用以下命令检查它是否存在:java -version。如果已成功完成安装,将显示已安装的 java 版本。

  2. 在系统中安装 maven 并使用以下命令检查它是否存在:mvn -version。如果已成功完成安装,将显示已安装的 maven 版本。

  3. 安装任何一个 IDE,例如 Eclipse、IntelliJ 等等。

  4. 从链接中将 Selenium Grid 依赖项添加到 pom.xml − https://mvnrepository.com/artifact/

  5. 从链接中将 Apache Maven Shade Plugin 添加到 pom.xml − https://maven.apache.org/plugins/

  6. 向当前项目中追加自定义的 node。

  7. 借助以下命令构建 uber jar 以启动 Node:java -jar。

  8. 使用以下命令启动节点 −

java -jar selenium-server-4.6.0.jar \
--ext custom_node-1.0-SNAPSHOT.jar node \
--node-implementation org.seleniumhq.samples.DecoratedLoggingNode

DecoratedLoggingNode.java 中实现代码。

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) {
      super(tracer, nodeId, 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(),
         node.getId(),
         uri,
         secretOptions.getRegistrationSecret());
      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);
      }
   }
}

DecoratedLoggingNode.java 的源 − https://www.selenium.dev/documentation/

在上述实现中,以下代码 −

Node node = LocalNodeFactory.create(config) 用于专门生成 LocalNode。org.openqa.selenium.grid.node.Node 具有用于面向用户实现的两种类型。这些是创建自定义节点和收集节点信息的绝佳开端。

  1. org.openqa.selenium.grid.node.local.LocalNode − 这用于指向长期运行的节点,并且是从启动节点时获取的默认逻辑。可通过调用 LocalNodeFactory.create(config) 生成此内容。此处,LocalNodeFactory 是 org.openqa.selenium.grid.node.local 的一部分,Config 是 org.openqa.selenium.grid.config 的一部分。

  2. org.openqa.selenium.grid.node.k8s.OneShotNode − 这是节点在某个测试会话发布后自行正确关闭的重要参考逻辑。此类不适用于任何预先存在的 maven 工件。可通过调用 OneShotNode.create(config) 生成此内容。此处,OneShotNode 是 org.openqa.selenium.grid.node.k8s 的一部分,Config 是 org.openqa.selenium.grid.config 的一部分。

这样就完成了我们关于使用 Selenium Grid 自定义节点教程的全面讲解。我们从如何使用 Selenium Grid 执行节点自定义、使用 uber JAR 的节点自定义以及在 Selenium Grid 中使用普通 JAR 的节点自定义开始介绍。

这使你深入了解 Selenium Grid 自定义节点。明智的做法是不断实践你所学到的知识,探索与 Selenium 相关的内容,以便加深你的理解并拓宽你的视野。