• Selenium Video Tutorials

Selenium Grid - Customizing a Node



The latest version of Selenium Grid 4, is developed without leveraging the code base of the older versions of the Selenium Grid. This latest version of Selenium Grid 4 version has some advanced features like the customizing a Node.

The latest version of Selenium Grid allows test execution to be triggered in three different Selenium Grid modes. They are known as the Standalone, Hub and Nodes, and the Distributed.

Node Customization in Selenium Grid

While using the Selenium Grid to perform parallel testing or cross browser testing across different browsers and its platforms, devices, we may require to customize the Node as per requirements.

We may need to add some prerequisite steps prior to starting of the execution sessions or any tidying up activities post the sessions completed. To perform a Node customization the below steps are to be performed −

  • Design a class which will extend the −

org.openqa.selenium.grid.node.Node.
  • Append a static method(a factory method) to the newly designed class which should have the signature: public static Node created(Config config). Here, the Node has the same type as org.openqa.selenium.grid.node.Node and Config has the same type as −

org.openqa.selenium.grid.config.Config.
  • Inside the factory method, the implementation logic of the new class should be added.

  • To incorporate the newly developed implementation inside the hub, begin the node and send in the completely qualified class name of the previous to the argument: –node-implementation.

Node Customization Using the Uber Jar

The steps to implement node customization using the uber jar is listed below −

  • Install Java(version above 8) in the system and check if it is present with the command: java -version. The java version installed will be visible if installation has been completed successfully.

  • Install maven in the system and check if it is present with the command: mvn -version. The maven version installed will be visible if installation has been completed successfully.

  • Install any IDE like Eclipse, IntelliJ, and so on.

  • Add the Selenium Grid dependencies to the pom.xml from the link −

    https://mvnrepository.com/artifact/.

  • Add the Apache Maven Shade Plugin to the pom.xml from the link −

    https://mvnrepository.com/artifact/.

  • Append the customized node to the current project.

  • Build the uber jar to kick off the Node with the help of the command: java -jar.

  • Begin the Node using the below command −

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

Node Customization Using a Normal Jar

The steps to implement node customization using a normal jar is listed below −

  • Install Java(version above 8) in the system and check if it is present with the command: java -version. The java version installed will be visible if installation has been completed successfully.

  • Install maven in the system and check if it is present with the command: mvn -version. The maven version installed will be visible if installation has been completed successfully.

  • Install any IDE like Eclipse, IntelliJ, and so on.

  • Add the Selenium Grid dependencies to the pom.xml from the link − https://mvnrepository.com/artifact/.

  • Add the Apache Maven Shade Plugin to the pom.xml from the link − https://maven.apache.org/plugins/.

  • Append the customized node to the current project.

  • Build the uber jar to kick off the Node with the help of the command: java -jar.

  • Begin the Node using the below command −

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

Code Implementation in 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);
      }
   }
}

Source for DecoratedLoggingNode.java − https://www.selenium.dev/documentation/.

In the above implementation, the below code −

Node node = LocalNodeFactory.create(config) is used to generate the LocalNode specifically. There are two types of user facing implementations of the org.openqa.selenium.grid.node.Node. These are excellent beginning areas to create a custom Node and to gather information on a Node.

  • org.openqa.selenium.grid.node.local.LocalNode − This is used to point to a Node which is running for a long time and is the default logic that is fetched while a Node is started. It can be generated by calling the LocalNodeFactory.create(config). Here, the LocalNodeFactory is a part of the org.openqa.selenium.grid.node.local and Config is a part of the org.openqa.selenium.grid.config.

  • org.openqa.selenium.grid.node.k8s.OneShotNode − This is an important reference logic where the Node shuts itself down properly post one test session. This class is unavailable to any pre-existing maven artifact. It can be generated by calling the OneShotNode.create(config). Here, the OneShotNode is a part of the org.openqa.selenium.grid.node.k8s and Config is a part of the org.openqa.selenium.grid.config.

This concludes our comprehensive take on the tutorial on Selenium Grid - Customizing a Node. We’ve started with describing how to perform a Node customization in Selenium Grid, Node Customization using the uber jar, and Node Customization using a normal jar in Selenium Grid.

This equips you with in-depth knowledge of the Selenium Grid Customizing a Node. It is wise to keep practicing what you’ve learned and exploring others relevant to Selenium to deepen your understanding and expand your horizons.

Advertisements