Add explicit reference to cdp types (#15194)

* [java] Ensure purging dead nodes service interval is configurable (#15175)

Fixes #15168

* [bazel] Bump JS rulesets (#15187)

* Add explicit reference to cdp types

---------

Co-authored-by: Puja Jagani <[email protected]>
Co-authored-by: Simon Stewart <[email protected]>
diff --git a/MODULE.bazel b/MODULE.bazel
index e917ac6..8fda55f 100644
--- a/MODULE.bazel
+++ b/MODULE.bazel
@@ -1,15 +1,15 @@
 module(name = "selenium")
 
 bazel_dep(name = "apple_rules_lint", version = "0.4.0")
-bazel_dep(name = "aspect_bazel_lib", version = "2.10.0")
+bazel_dep(name = "aspect_bazel_lib", version = "2.13.0")
 bazel_dep(name = "aspect_rules_esbuild", version = "0.21.0")
-bazel_dep(name = "aspect_rules_js", version = "2.0.1")
-bazel_dep(name = "aspect_rules_ts", version = "3.1.0")
+bazel_dep(name = "aspect_rules_js", version = "2.1.3")
+bazel_dep(name = "aspect_rules_ts", version = "3.4.0")
 bazel_dep(name = "bazel_features", version = "1.23.0")
 bazel_dep(name = "bazel_skylib", version = "1.7.1")
 bazel_dep(name = "buildifier_prebuilt", version = "6.4.0")
 bazel_dep(name = "contrib_rules_jvm", version = "0.27.0")
-bazel_dep(name = "platforms", version = "0.0.10")
+bazel_dep(name = "platforms", version = "0.0.11")
 
 # Required for the closure rules
 bazel_dep(name = "protobuf", version = "29.2", dev_dependency = True, repo_name = "com_google_protobuf")
diff --git a/java/src/org/openqa/selenium/grid/commands/Hub.java b/java/src/org/openqa/selenium/grid/commands/Hub.java
index 22cf384..86733f0 100644
--- a/java/src/org/openqa/selenium/grid/commands/Hub.java
+++ b/java/src/org/openqa/selenium/grid/commands/Hub.java
@@ -162,7 +162,8 @@ protected Handlers createHandlers(Config config) {
             distributorOptions.shouldRejectUnsupportedCaps(),
             newSessionRequestOptions.getSessionRequestRetryInterval(),
             distributorOptions.getNewSessionThreadPoolSize(),
-            distributorOptions.getSlotMatcher());
+            distributorOptions.getSlotMatcher(),
+            distributorOptions.getPurgeNodesInterval());
     handler.addHandler(distributor);
 
     Router router = new Router(tracer, clientFactory, sessions, queue, distributor);
diff --git a/java/src/org/openqa/selenium/grid/commands/Standalone.java b/java/src/org/openqa/selenium/grid/commands/Standalone.java
index 20fa966..83875cb 100644
--- a/java/src/org/openqa/selenium/grid/commands/Standalone.java
+++ b/java/src/org/openqa/selenium/grid/commands/Standalone.java
@@ -168,7 +168,8 @@ protected Handlers createHandlers(Config config) {
             distributorOptions.shouldRejectUnsupportedCaps(),
             newSessionRequestOptions.getSessionRequestRetryInterval(),
             distributorOptions.getNewSessionThreadPoolSize(),
-            distributorOptions.getSlotMatcher());
+            distributorOptions.getSlotMatcher(),
+            distributorOptions.getPurgeNodesInterval());
     combinedHandler.addHandler(distributor);
 
     Router router = new Router(tracer, clientFactory, sessions, queue, distributor);
diff --git a/java/src/org/openqa/selenium/grid/distributor/config/DistributorFlags.java b/java/src/org/openqa/selenium/grid/distributor/config/DistributorFlags.java
index a2f2a44..0692f35 100644
--- a/java/src/org/openqa/selenium/grid/distributor/config/DistributorFlags.java
+++ b/java/src/org/openqa/selenium/grid/distributor/config/DistributorFlags.java
@@ -21,6 +21,7 @@
 import static org.openqa.selenium.grid.distributor.config.DistributorOptions.DEFAULT_DISTRIBUTOR_IMPLEMENTATION;
 import static org.openqa.selenium.grid.distributor.config.DistributorOptions.DEFAULT_HEALTHCHECK_INTERVAL;
 import static org.openqa.selenium.grid.distributor.config.DistributorOptions.DEFAULT_NEWSESSION_THREADPOOL_SIZE;
+import static org.openqa.selenium.grid.distributor.config.DistributorOptions.DEFAULT_PURGE_NODES_INTERVAL;
 import static org.openqa.selenium.grid.distributor.config.DistributorOptions.DEFAULT_REJECT_UNSUPPORTED_CAPS;
 import static org.openqa.selenium.grid.distributor.config.DistributorOptions.DEFAULT_SLOT_MATCHER;
 import static org.openqa.selenium.grid.distributor.config.DistributorOptions.DEFAULT_SLOT_SELECTOR_IMPLEMENTATION;
@@ -115,6 +116,14 @@ public class DistributorFlags implements HasRoles {
   @ConfigValue(section = DISTRIBUTOR_SECTION, name = "newsession-threadpool-size", example = "4")
   public int newSessionThreadPoolSize = DEFAULT_NEWSESSION_THREADPOOL_SIZE;
 
+  @Parameter(
+      names = {"--purge-nodes-interval"},
+      description =
+          "How often, in seconds, will the Distributor purge Nodes that have been down for a while."
+              + " This is calculated based on the heartbeat received from a particular node. ")
+  @ConfigValue(section = DISTRIBUTOR_SECTION, name = "purge-nodes-interval", example = "30")
+  public int purgeNodesInterval = DEFAULT_PURGE_NODES_INTERVAL;
+
   @Override
   public Set<Role> getRoles() {
     return Collections.singleton(DISTRIBUTOR_ROLE);
diff --git a/java/src/org/openqa/selenium/grid/distributor/config/DistributorOptions.java b/java/src/org/openqa/selenium/grid/distributor/config/DistributorOptions.java
index 70da2cb..bf2a0cd 100644
--- a/java/src/org/openqa/selenium/grid/distributor/config/DistributorOptions.java
+++ b/java/src/org/openqa/selenium/grid/distributor/config/DistributorOptions.java
@@ -31,6 +31,7 @@
 public class DistributorOptions {
 
   public static final int DEFAULT_HEALTHCHECK_INTERVAL = 120;
+  public static final int DEFAULT_PURGE_NODES_INTERVAL = 30;
   public static final String DISTRIBUTOR_SECTION = "distributor";
   static final String DEFAULT_DISTRIBUTOR_IMPLEMENTATION =
       "org.openqa.selenium.grid.distributor.local.LocalDistributor";
@@ -97,6 +98,17 @@ public Duration getHealthCheckInterval() {
     return Duration.ofSeconds(seconds);
   }
 
+  public Duration getPurgeNodesInterval() {
+    // If the user sets 0s or less, we default to 0s and disable the purge dead nodes service.
+    int seconds =
+        Math.max(
+            config
+                .getInt(DISTRIBUTOR_SECTION, "purge-nodes-interval")
+                .orElse(DEFAULT_PURGE_NODES_INTERVAL),
+            0);
+    return Duration.ofSeconds(seconds);
+  }
+
   public Distributor getDistributor() {
     return config.getClass(
         DISTRIBUTOR_SECTION,
diff --git a/java/src/org/openqa/selenium/grid/distributor/local/LocalDistributor.java b/java/src/org/openqa/selenium/grid/distributor/local/LocalDistributor.java
index d6cf622..0d6dc9b 100644
--- a/java/src/org/openqa/selenium/grid/distributor/local/LocalDistributor.java
+++ b/java/src/org/openqa/selenium/grid/distributor/local/LocalDistributor.java
@@ -142,6 +142,7 @@ public class LocalDistributor extends Distributor implements Closeable {
   private final GridModel model;
   private final Map<NodeId, Node> nodes;
   private final SlotMatcher slotMatcher;
+  private final Duration purgeNodesInterval;
 
   private final ScheduledExecutorService newSessionService =
       Executors.newSingleThreadScheduledExecutor(
@@ -188,7 +189,8 @@ public LocalDistributor(
       boolean rejectUnsupportedCaps,
       Duration sessionRequestRetryInterval,
       int newSessionThreadPoolSize,
-      SlotMatcher slotMatcher) {
+      SlotMatcher slotMatcher,
+      Duration purgeNodesInterval) {
     super(tracer, clientFactory, registrationSecret);
     this.tracer = Require.nonNull("Tracer", tracer);
     this.bus = Require.nonNull("Event bus", bus);
@@ -202,6 +204,7 @@ public LocalDistributor(
     this.nodes = new ConcurrentHashMap<>();
     this.rejectUnsupportedCaps = rejectUnsupportedCaps;
     this.slotMatcher = slotMatcher;
+    this.purgeNodesInterval = purgeNodesInterval;
     Require.nonNull("Session request interval", sessionRequestRetryInterval);
 
     bus.addListener(NodeStatusEvent.listener(this::register));
@@ -232,8 +235,14 @@ public LocalDistributor(
     NewSessionRunnable newSessionRunnable = new NewSessionRunnable();
     bus.addListener(NodeDrainComplete.listener(this::remove));
 
-    purgeDeadNodesService.scheduleAtFixedRate(
-        GuardedRunnable.guard(model::purgeDeadNodes), 30, 30, TimeUnit.SECONDS);
+    // Disable purge dead nodes service if interval is set to zero
+    if (!this.purgeNodesInterval.isZero()) {
+      purgeDeadNodesService.scheduleAtFixedRate(
+          GuardedRunnable.guard(model::purgeDeadNodes),
+          this.purgeNodesInterval.getSeconds(),
+          this.purgeNodesInterval.getSeconds(),
+          TimeUnit.SECONDS);
+    }
 
     nodeHealthCheckService.scheduleAtFixedRate(
         runNodeHealthChecks(),
@@ -276,7 +285,8 @@ public static Distributor create(Config config) {
         distributorOptions.shouldRejectUnsupportedCaps(),
         newSessionQueueOptions.getSessionRequestRetryInterval(),
         distributorOptions.getNewSessionThreadPoolSize(),
-        distributorOptions.getSlotMatcher());
+        distributorOptions.getSlotMatcher(),
+        distributorOptions.getPurgeNodesInterval());
   }
 
   @Override
diff --git a/java/test/org/openqa/selenium/grid/distributor/AddingNodesTest.java b/java/test/org/openqa/selenium/grid/distributor/AddingNodesTest.java
index 2ed1ab9..3df9bd7 100644
--- a/java/test/org/openqa/selenium/grid/distributor/AddingNodesTest.java
+++ b/java/test/org/openqa/selenium/grid/distributor/AddingNodesTest.java
@@ -143,7 +143,8 @@ void shouldBeAbleToRegisterALocalNode() throws URISyntaxException {
             false,
             Duration.ofSeconds(5),
             newSessionThreadPoolSize,
-            new DefaultSlotMatcher());
+            new DefaultSlotMatcher(),
+            Duration.ofSeconds(30));
 
     distributor =
         new RemoteDistributor(
@@ -183,7 +184,8 @@ void shouldBeAbleToRegisterACustomNode() throws URISyntaxException {
             false,
             Duration.ofSeconds(5),
             newSessionThreadPoolSize,
-            new DefaultSlotMatcher())) {
+            new DefaultSlotMatcher(),
+            Duration.ofSeconds(30))) {
 
       distributor =
           new RemoteDistributor(
@@ -223,7 +225,8 @@ void shouldBeAbleToRegisterNodesByListeningForEvents() throws URISyntaxException
             false,
             Duration.ofSeconds(5),
             newSessionThreadPoolSize,
-            new DefaultSlotMatcher())) {
+            new DefaultSlotMatcher(),
+            Duration.ofSeconds(30))) {
 
       distributor =
           new RemoteDistributor(
@@ -272,7 +275,8 @@ void shouldKeepOnlyOneNodeWhenTwoRegistrationsHaveTheSameUriByListeningForEvents
             false,
             Duration.ofSeconds(5),
             newSessionThreadPoolSize,
-            new DefaultSlotMatcher())) {
+            new DefaultSlotMatcher(),
+            Duration.ofSeconds(30))) {
 
       distributor =
           new RemoteDistributor(
@@ -314,7 +318,8 @@ void distributorShouldUpdateStateOfExistingNodeWhenNodePublishesStateChange()
             false,
             Duration.ofSeconds(5),
             newSessionThreadPoolSize,
-            new DefaultSlotMatcher())) {
+            new DefaultSlotMatcher(),
+            Duration.ofSeconds(30))) {
 
       distributor =
           new RemoteDistributor(
diff --git a/java/test/org/openqa/selenium/grid/distributor/DistributorDrainingTest.java b/java/test/org/openqa/selenium/grid/distributor/DistributorDrainingTest.java
index 1170c1e..5bcbb4c 100644
--- a/java/test/org/openqa/selenium/grid/distributor/DistributorDrainingTest.java
+++ b/java/test/org/openqa/selenium/grid/distributor/DistributorDrainingTest.java
@@ -81,7 +81,8 @@ void drainedNodeDoesNotShutDownIfNotEmpty() throws InterruptedException {
             false,
             Duration.ofSeconds(5),
             newSessionThreadPoolSize,
-            new DefaultSlotMatcher());
+            new DefaultSlotMatcher(),
+            Duration.ofSeconds(30));
     local.add(node);
     waitToHaveCapacity(local);
 
@@ -139,7 +140,8 @@ void drainedNodeShutsDownAfterSessionsFinish() throws InterruptedException {
             false,
             Duration.ofSeconds(5),
             newSessionThreadPoolSize,
-            new DefaultSlotMatcher());
+            new DefaultSlotMatcher(),
+            Duration.ofSeconds(30));
     local.add(node);
     waitToHaveCapacity(local);
 
@@ -211,7 +213,8 @@ void testDrainedNodeShutsDownOnceEmpty() throws InterruptedException {
             false,
             Duration.ofSeconds(5),
             newSessionThreadPoolSize,
-            new DefaultSlotMatcher());
+            new DefaultSlotMatcher(),
+            Duration.ofSeconds(30));
     local.add(node);
     waitToHaveCapacity(local);
 
@@ -261,7 +264,8 @@ void drainingNodeDoesNotAcceptNewSessions() {
             false,
             Duration.ofSeconds(5),
             newSessionThreadPoolSize,
-            new DefaultSlotMatcher());
+            new DefaultSlotMatcher(),
+            Duration.ofSeconds(30));
     local.add(node);
     local.drain(node.getId());
 
diff --git a/java/test/org/openqa/selenium/grid/distributor/DistributorNodeAvailabilityTest.java b/java/test/org/openqa/selenium/grid/distributor/DistributorNodeAvailabilityTest.java
index 97dddc4..decce9a 100644
--- a/java/test/org/openqa/selenium/grid/distributor/DistributorNodeAvailabilityTest.java
+++ b/java/test/org/openqa/selenium/grid/distributor/DistributorNodeAvailabilityTest.java
@@ -78,7 +78,8 @@ void registeringTheSameNodeMultipleTimesOnlyCountsTheFirstTime() {
             false,
             Duration.ofSeconds(5),
             newSessionThreadPoolSize,
-            new DefaultSlotMatcher());
+            new DefaultSlotMatcher(),
+            Duration.ofSeconds(30));
 
     local.add(node);
     local.add(node);
@@ -122,7 +123,8 @@ void shouldBeAbleToRemoveANode() throws MalformedURLException {
             false,
             Duration.ofSeconds(5),
             newSessionThreadPoolSize,
-            new DefaultSlotMatcher());
+            new DefaultSlotMatcher(),
+            Duration.ofSeconds(30));
     Distributor distributor =
         new RemoteDistributor(
             tracer,
@@ -178,7 +180,8 @@ void shouldIncludeHostsThatAreUpInHostList() {
             false,
             Duration.ofSeconds(5),
             newSessionThreadPoolSize,
-            new DefaultSlotMatcher());
+            new DefaultSlotMatcher(),
+            Duration.ofSeconds(30));
     handler.addHandler(local);
     local.add(alwaysDown);
     waitForAllNodesToMeetCondition(local, 1, DOWN);
@@ -258,7 +261,8 @@ void shouldNotRemoveNodeWhoseHealthCheckPassesBeforeThreshold() throws Interrupt
             false,
             Duration.ofSeconds(5),
             newSessionThreadPoolSize,
-            new DefaultSlotMatcher());
+            new DefaultSlotMatcher(),
+            Duration.ofSeconds(30));
     handler.addHandler(local);
     local.add(node);
 
@@ -313,7 +317,8 @@ void shouldReturnNodesThatWereDownToPoolOfNodesOnceTheyMarkTheirHealthCheckPasse
             false,
             Duration.ofSeconds(5),
             newSessionThreadPoolSize,
-            new DefaultSlotMatcher());
+            new DefaultSlotMatcher(),
+            Duration.ofSeconds(30));
     handler.addHandler(local);
     local.add(node);
     waitForAllNodesToMeetCondition(local, 1, DOWN);
@@ -366,7 +371,8 @@ void shouldBeAbleToAddANodeAndCreateASession() {
             false,
             Duration.ofSeconds(5),
             newSessionThreadPoolSize,
-            new DefaultSlotMatcher());
+            new DefaultSlotMatcher(),
+            Duration.ofSeconds(30));
     local.add(node);
     waitToHaveCapacity(local);
 
diff --git a/java/test/org/openqa/selenium/grid/distributor/DistributorTest.java b/java/test/org/openqa/selenium/grid/distributor/DistributorTest.java
index 1fcb2be..c03785a 100644
--- a/java/test/org/openqa/selenium/grid/distributor/DistributorTest.java
+++ b/java/test/org/openqa/selenium/grid/distributor/DistributorTest.java
@@ -69,7 +69,8 @@ void creatingANewSessionWithoutANodeEndsInFailure() {
             false,
             Duration.ofSeconds(5),
             newSessionThreadPoolSize,
-            new DefaultSlotMatcher());
+            new DefaultSlotMatcher(),
+            Duration.ofSeconds(30));
     Either<SessionNotCreatedException, CreateSessionResponse> result =
         local.newSession(createRequest(caps));
     assertThatEither(result).isLeft();
@@ -109,7 +110,8 @@ void creatingASessionAddsItToTheSessionMap() {
             false,
             Duration.ofSeconds(5),
             newSessionThreadPoolSize,
-            new DefaultSlotMatcher());
+            new DefaultSlotMatcher(),
+            Duration.ofSeconds(30));
     local.add(node);
     waitToHaveCapacity(local);
 
@@ -160,7 +162,8 @@ void shouldReleaseSlotOnceSessionEnds() {
             false,
             Duration.ofSeconds(5),
             newSessionThreadPoolSize,
-            new DefaultSlotMatcher());
+            new DefaultSlotMatcher(),
+            Duration.ofSeconds(30));
     local.add(node);
     waitToHaveCapacity(local);
 
@@ -223,7 +226,8 @@ void shouldNotStartASessionIfTheCapabilitiesAreNotSupported() {
             false,
             Duration.ofSeconds(5),
             newSessionThreadPoolSize,
-            new DefaultSlotMatcher());
+            new DefaultSlotMatcher(),
+            Duration.ofSeconds(30));
     handler.addHandler(distributor);
 
     Node node = createNode(caps, 1, 0);
@@ -273,7 +277,8 @@ void attemptingToStartASessionWhichFailsMarksAsTheSlotAsAvailable() {
             false,
             Duration.ofSeconds(5),
             newSessionThreadPoolSize,
-            new DefaultSlotMatcher());
+            new DefaultSlotMatcher(),
+            Duration.ofSeconds(30));
     local.add(node);
     waitToHaveCapacity(local);
 
@@ -320,7 +325,8 @@ void shouldFallbackToSecondAvailableCapabilitiesIfFirstNotAvailable() {
             false,
             Duration.ofSeconds(5),
             newSessionThreadPoolSize,
-            new DefaultSlotMatcher());
+            new DefaultSlotMatcher(),
+            Duration.ofSeconds(30));
 
     local.add(firstNode);
     local.add(secondNode);
@@ -368,7 +374,8 @@ void shouldFallbackToSecondAvailableCapabilitiesIfFirstThrowsOnCreation() {
             false,
             Duration.ofSeconds(5),
             newSessionThreadPoolSize,
-            new DefaultSlotMatcher());
+            new DefaultSlotMatcher(),
+            Duration.ofSeconds(30));
     local.add(brokenNode);
     local.add(node);
     waitForAllNodesToHaveCapacity(local, 2);
diff --git a/java/test/org/openqa/selenium/grid/distributor/SessionSchedulingTest.java b/java/test/org/openqa/selenium/grid/distributor/SessionSchedulingTest.java
index 5b4b8ad..58f1924 100644
--- a/java/test/org/openqa/selenium/grid/distributor/SessionSchedulingTest.java
+++ b/java/test/org/openqa/selenium/grid/distributor/SessionSchedulingTest.java
@@ -93,7 +93,8 @@ void theMostLightlyLoadedNodeIsSelectedFirst() {
                 false,
                 Duration.ofSeconds(5),
                 newSessionThreadPoolSize,
-                new DefaultSlotMatcher())
+                new DefaultSlotMatcher(),
+                Duration.ofSeconds(30))
             .add(heavy)
             .add(medium)
             .add(lightest)
@@ -144,7 +145,8 @@ void shouldUseLastSessionCreatedTimeAsTieBreaker() {
                 false,
                 Duration.ofSeconds(5),
                 newSessionThreadPoolSize,
-                new DefaultSlotMatcher())
+                new DefaultSlotMatcher(),
+                Duration.ofSeconds(30))
             .add(leastRecent);
     waitToHaveCapacity(local);
 
@@ -218,7 +220,8 @@ void shouldNotScheduleAJobIfAllSlotsAreBeingUsed() {
             false,
             Duration.ofSeconds(5),
             newSessionThreadPoolSize,
-            new DefaultSlotMatcher());
+            new DefaultSlotMatcher(),
+            Duration.ofSeconds(30));
 
     local.add(node);
     waitToHaveCapacity(local);
@@ -267,7 +270,8 @@ void shouldPrioritizeHostsWithTheMostSlotsAvailableForASessionType() {
             false,
             Duration.ofSeconds(5),
             newSessionThreadPoolSize,
-            new DefaultSlotMatcher());
+            new DefaultSlotMatcher(),
+            Duration.ofSeconds(30));
 
     // Create all three Capability types
     Capabilities edge = new ImmutableCapabilities("browserName", "edge");
diff --git a/java/test/org/openqa/selenium/grid/distributor/local/LocalDistributorTest.java b/java/test/org/openqa/selenium/grid/distributor/local/LocalDistributorTest.java
index af5adb6..a0092c9 100644
--- a/java/test/org/openqa/selenium/grid/distributor/local/LocalDistributorTest.java
+++ b/java/test/org/openqa/selenium/grid/distributor/local/LocalDistributorTest.java
@@ -134,7 +134,8 @@ void testAddNodeToDistributor() {
             false,
             Duration.ofSeconds(5),
             newSessionThreadPoolSize,
-            new DefaultSlotMatcher());
+            new DefaultSlotMatcher(),
+            Duration.ofSeconds(30));
     distributor.add(localNode);
     DistributorStatus status = distributor.getStatus();
 
@@ -174,7 +175,8 @@ void testRemoveNodeFromDistributor() {
             false,
             Duration.ofSeconds(5),
             newSessionThreadPoolSize,
-            new DefaultSlotMatcher());
+            new DefaultSlotMatcher(),
+            Duration.ofSeconds(30));
     distributor.add(localNode);
 
     // Check the size
@@ -213,7 +215,8 @@ void testAddSameNodeTwice() {
             false,
             Duration.ofSeconds(5),
             newSessionThreadPoolSize,
-            new DefaultSlotMatcher());
+            new DefaultSlotMatcher(),
+            Duration.ofSeconds(30));
     distributor.add(localNode);
     distributor.add(localNode);
     DistributorStatus status = distributor.getStatus();
@@ -275,7 +278,8 @@ public HttpResponse execute(HttpRequest req) {
             false,
             Duration.ofSeconds(5),
             newSessionThreadPoolSize,
-            new DefaultSlotMatcher());
+            new DefaultSlotMatcher(),
+            Duration.ofSeconds(30));
 
     distributor.add(node);
     wait.until(obj -> distributor.getStatus().hasCapacity());
@@ -341,7 +345,8 @@ void testDrainNodeFromDistributor() {
             false,
             Duration.ofSeconds(5),
             newSessionThreadPoolSize,
-            new DefaultSlotMatcher());
+            new DefaultSlotMatcher(),
+            Duration.ofSeconds(30));
     distributor.add(localNode);
     assertThat(localNode.isDraining()).isFalse();
 
@@ -387,7 +392,8 @@ void testDrainNodeFromNode() {
             false,
             Duration.ofSeconds(5),
             newSessionThreadPoolSize,
-            new DefaultSlotMatcher());
+            new DefaultSlotMatcher(),
+            Duration.ofSeconds(30));
     distributor.add(localNode);
 
     localNode.drain();
@@ -419,7 +425,8 @@ void slowStartingNodesShouldNotCauseReservationsToBeSerialized() {
             false,
             Duration.ofSeconds(5),
             newSessionThreadPoolSize,
-            new DefaultSlotMatcher());
+            new DefaultSlotMatcher(),
+            Duration.ofSeconds(30));
 
     Capabilities caps = new ImmutableCapabilities("browserName", "cheese");
 
diff --git a/java/test/org/openqa/selenium/grid/graphql/GraphqlHandlerTest.java b/java/test/org/openqa/selenium/grid/graphql/GraphqlHandlerTest.java
index 3a442da..f01ae94 100644
--- a/java/test/org/openqa/selenium/grid/graphql/GraphqlHandlerTest.java
+++ b/java/test/org/openqa/selenium/grid/graphql/GraphqlHandlerTest.java
@@ -138,7 +138,8 @@ public void setupGrid() {
             false,
             Duration.ofSeconds(5),
             newSessionThreadPoolSize,
-            new DefaultSlotMatcher());
+            new DefaultSlotMatcher(),
+            Duration.ofSeconds(30));
   }
 
   @Test
@@ -322,7 +323,8 @@ void shouldBeAbleToGetSessionCount() throws URISyntaxException {
             false,
             Duration.ofSeconds(5),
             newSessionThreadPoolSize,
-            new DefaultSlotMatcher());
+            new DefaultSlotMatcher(),
+            Duration.ofSeconds(30));
 
     distributor.add(node);
     wait.until(obj -> distributor.getStatus().hasCapacity());
@@ -371,7 +373,8 @@ void shouldBeAbleToGetSessionInfo() throws URISyntaxException {
             false,
             Duration.ofSeconds(5),
             newSessionThreadPoolSize,
-            new DefaultSlotMatcher());
+            new DefaultSlotMatcher(),
+            Duration.ofSeconds(30));
 
     distributor.add(node);
     wait.until(obj -> distributor.getStatus().hasCapacity());
@@ -448,7 +451,8 @@ void shouldBeAbleToGetNodeInfoForSession() throws URISyntaxException {
             false,
             Duration.ofSeconds(5),
             newSessionThreadPoolSize,
-            new DefaultSlotMatcher());
+            new DefaultSlotMatcher(),
+            Duration.ofSeconds(30));
 
     distributor.add(node);
     wait.until(obj -> distributor.getStatus().hasCapacity());
@@ -523,7 +527,8 @@ void shouldBeAbleToGetSlotInfoForSession() throws URISyntaxException {
             false,
             Duration.ofSeconds(5),
             newSessionThreadPoolSize,
-            new DefaultSlotMatcher());
+            new DefaultSlotMatcher(),
+            Duration.ofSeconds(30));
 
     distributor.add(node);
     wait.until(obj -> distributor.getStatus().hasCapacity());
@@ -606,7 +611,8 @@ void shouldBeAbleToGetSessionDuration() throws URISyntaxException {
             false,
             Duration.ofSeconds(5),
             newSessionThreadPoolSize,
-            new DefaultSlotMatcher());
+            new DefaultSlotMatcher(),
+            Duration.ofSeconds(30));
 
     distributor.add(node);
     wait.until(obj -> distributor.getStatus().hasCapacity());
diff --git a/java/test/org/openqa/selenium/grid/router/JmxTest.java b/java/test/org/openqa/selenium/grid/router/JmxTest.java
index 2561eee..040c98b 100644
--- a/java/test/org/openqa/selenium/grid/router/JmxTest.java
+++ b/java/test/org/openqa/selenium/grid/router/JmxTest.java
@@ -296,7 +296,8 @@ void shouldBeAbleToMonitorHub() throws Exception {
             false,
             Duration.ofSeconds(5),
             Runtime.getRuntime().availableProcessors(),
-            new DefaultSlotMatcher())) {
+            new DefaultSlotMatcher(),
+            Duration.ofSeconds(30))) {
 
       distributor.add(localNode);
 
diff --git a/java/test/org/openqa/selenium/grid/router/NewSessionCreationTest.java b/java/test/org/openqa/selenium/grid/router/NewSessionCreationTest.java
index 5ff4d19..cfd9395 100644
--- a/java/test/org/openqa/selenium/grid/router/NewSessionCreationTest.java
+++ b/java/test/org/openqa/selenium/grid/router/NewSessionCreationTest.java
@@ -118,7 +118,8 @@ void ensureJsCannotCreateANewSession() throws URISyntaxException {
             false,
             Duration.ofSeconds(5),
             newSessionThreadPoolSize,
-            new DefaultSlotMatcher());
+            new DefaultSlotMatcher(),
+            Duration.ofSeconds(30));
 
     Routable router =
         new Router(tracer, clientFactory, sessions, queue, distributor)
@@ -229,7 +230,8 @@ void shouldNotRetryNewSessionRequestOnUnexpectedError() throws URISyntaxExceptio
             false,
             Duration.ofSeconds(5),
             newSessionThreadPoolSize,
-            new DefaultSlotMatcher());
+            new DefaultSlotMatcher(),
+            Duration.ofSeconds(30));
     handler.addHandler(distributor);
 
     distributor.add(localNode);
@@ -295,7 +297,8 @@ void shouldRejectRequestForUnsupportedCaps() throws URISyntaxException {
             true,
             Duration.ofSeconds(5),
             newSessionThreadPoolSize,
-            new DefaultSlotMatcher());
+            new DefaultSlotMatcher(),
+            Duration.ofSeconds(30));
     handler.addHandler(distributor);
 
     distributor.add(localNode);
diff --git a/java/test/org/openqa/selenium/grid/router/RouterTest.java b/java/test/org/openqa/selenium/grid/router/RouterTest.java
index 352495f..6350508 100644
--- a/java/test/org/openqa/selenium/grid/router/RouterTest.java
+++ b/java/test/org/openqa/selenium/grid/router/RouterTest.java
@@ -146,7 +146,8 @@ public void setUp() {
             false,
             Duration.ofSeconds(5),
             Runtime.getRuntime().availableProcessors(),
-            new DefaultSlotMatcher());
+            new DefaultSlotMatcher(),
+            Duration.ofSeconds(30));
     handler.addHandler(distributor);
 
     router = new Router(tracer, clientFactory, sessions, queue, distributor);
diff --git a/java/test/org/openqa/selenium/grid/router/SessionCleanUpTest.java b/java/test/org/openqa/selenium/grid/router/SessionCleanUpTest.java
index 9944736..e5692dc 100644
--- a/java/test/org/openqa/selenium/grid/router/SessionCleanUpTest.java
+++ b/java/test/org/openqa/selenium/grid/router/SessionCleanUpTest.java
@@ -162,7 +162,8 @@ void shouldRemoveSessionAfterNodeIsShutDownGracefully() {
             false,
             Duration.ofSeconds(5),
             Runtime.getRuntime().availableProcessors(),
-            new DefaultSlotMatcher())) {
+            new DefaultSlotMatcher(),
+            Duration.ofSeconds(30))) {
       handler.addHandler(distributor);
 
       Router router = new Router(tracer, clientFactory, sessions, queue, distributor);
@@ -292,7 +293,8 @@ void shouldRemoveSessionAfterNodeIsDown() throws URISyntaxException {
             false,
             Duration.ofSeconds(5),
             Runtime.getRuntime().availableProcessors(),
-            new DefaultSlotMatcher())) {
+            new DefaultSlotMatcher(),
+            Duration.ofSeconds(30))) {
       handler.addHandler(distributor);
       distributor.add(node);
 
diff --git a/java/test/org/openqa/selenium/grid/router/SessionQueueGridTest.java b/java/test/org/openqa/selenium/grid/router/SessionQueueGridTest.java
index be3d745..cf7d1bc 100644
--- a/java/test/org/openqa/selenium/grid/router/SessionQueueGridTest.java
+++ b/java/test/org/openqa/selenium/grid/router/SessionQueueGridTest.java
@@ -144,7 +144,8 @@ public void setup() throws URISyntaxException, MalformedURLException {
             false,
             Duration.ofSeconds(5),
             Runtime.getRuntime().availableProcessors(),
-            new DefaultSlotMatcher());
+            new DefaultSlotMatcher(),
+            Duration.ofSeconds(30));
     handler.addHandler(distributor);
 
     distributor.add(localNode);
diff --git a/java/test/org/openqa/selenium/grid/router/SessionQueueGridWithTimeoutTest.java b/java/test/org/openqa/selenium/grid/router/SessionQueueGridWithTimeoutTest.java
index cd48aef..7e3f0bd 100644
--- a/java/test/org/openqa/selenium/grid/router/SessionQueueGridWithTimeoutTest.java
+++ b/java/test/org/openqa/selenium/grid/router/SessionQueueGridWithTimeoutTest.java
@@ -143,7 +143,8 @@ public void setup() throws URISyntaxException, MalformedURLException {
             false,
             Duration.ofSeconds(5),
             Runtime.getRuntime().availableProcessors(),
-            new DefaultSlotMatcher());
+            new DefaultSlotMatcher(),
+            Duration.ofSeconds(30));
     handler.addHandler(distributor);
 
     distributor.add(localNode);
diff --git a/third_party/dotnet/devtools/src/generator/CodeGen/TypeInfo.cs b/third_party/dotnet/devtools/src/generator/CodeGen/TypeInfo.cs
index 2ab67c9..73e7fa4 100644
--- a/third_party/dotnet/devtools/src/generator/CodeGen/TypeInfo.cs
+++ b/third_party/dotnet/devtools/src/generator/CodeGen/TypeInfo.cs
@@ -14,5 +14,18 @@
         public string TypeName { get; } = typeName;
 
         public string? SourcePath { get; set; }
+
+        public string FullSnakeTypeName
+        {
+            get
+            {
+                if (string.IsNullOrEmpty(Namespace))
+                {
+                    return TypeName.Replace(".", "_");
+                }
+
+                return $"{Namespace.Replace(".", "_")}_{TypeName.Replace(".", "_")}";
+            }
+        }
     }
 }
diff --git a/third_party/dotnet/devtools/src/generator/Templates/DevToolsSessionDomains.hbs b/third_party/dotnet/devtools/src/generator/Templates/DevToolsSessionDomains.hbs
index 82ab752..d57a9f5 100644
--- a/third_party/dotnet/devtools/src/generator/Templates/DevToolsSessionDomains.hbs
+++ b/third_party/dotnet/devtools/src/generator/Templates/DevToolsSessionDomains.hbs
@@ -56,6 +56,11 @@
 {{#each events}}
     [global::System.Text.Json.Serialization.JsonSerializable(typeof({{FullTypeName}}), TypeInfoPropertyName = "{{FullSnakeTypeName}}")]
 {{/each}}
+{{#each types}}
+{{#unless IsPrimitive}}
+    [global::System.Text.Json.Serialization.JsonSerializable(typeof({{Namespace}}.{{TypeName}}), TypeInfoPropertyName = "{{FullSnakeTypeName}}")]
+{{/unless}}
+{{/each}}
     [global::System.Text.Json.Serialization.JsonSourceGenerationOptions(Converters = [typeof(OpenQA.Selenium.DevTools.Json.StringConverter)])]
     internal sealed partial class {{protocolVersion}}JsonSerializerContext : global::System.Text.Json.Serialization.JsonSerializerContext;
 }