Skip to content

.pr_agent_auto_best_practices

root edited this page Aug 29, 2025 · 10 revisions

Pattern 1: Ensure Python dependency specifiers use valid PEP 508 syntax with comma-separated range bounds and explicit upper bounds (e.g., "pkg>=x,<y.z") and include required extras consistently across files.

Example code before:

install_requires = [
  "websocket-client>=1.8.0<2.0",
  "urllib3[socks]>=2.5.0;<3",
  "urllib3==2.4.0",
]

Example code after:

install_requires = [
  "websocket-client>=1.8.0,<2.0",
  "urllib3[socks]>=2.5.0,<3.0",
  "urllib3[socks]==2.4.0",
]
Relevant past accepted suggestions:
Suggestion 1:

Fix broken version specifier

The version specifier is invalid due to a missing comma between the lower and upper bounds. This will break dependency parsing and resolution. Add a comma to separate the specifiers.

py/BUILD.bazel [335]

-"websocket-client>=1.8.0<2.0",
+"websocket-client>=1.8.0,<2.0",

Suggestion 2:

Correct malformed dependency range

The dependency constraint is malformed because it lacks a comma between specifiers, violating PEP 508. Insert the comma to ensure valid parsing by build tools.

py/pyproject.toml [34]

-"websocket-client>=1.8.0<2.0",
+"websocket-client>=1.8.0,<2.0",

Suggestion 3:

Fix version constraint separator syntax

The version constraint uses a semicolon separator which is not standard for pip dependency specifications. Use comma separator instead for better compatibility across packaging tools.

py/BUILD.bazel [330]

-"urllib3[socks]>=2.5.0;<3.0",
+"urllib3[socks]>=2.5.0,<3.0",

Suggestion 4:

Use explicit version constraint format

The version constraint should use <3.0 instead of <3 for better clarity and consistency with semantic versioning practices. This makes the upper bound more explicit and follows common Python packaging conventions.

py/BUILD.bazel [330]

-"urllib3[socks]>=2.5.0;<3",
+"urllib3[socks]>=2.5.0,<3.0",

Suggestion 5:

Fix missing package extra

The urllib3 package is specified without the [socks] extra in requirements.txt, but the pyproject.toml file specifies urllib3[socks]. This inconsistency could lead to missing dependencies when installing from requirements.txt directly.

py/requirements.txt [59-60]

 typing_extensions==4.14.0
-urllib3==2.4.0
+urllib3[socks]==2.4.0

Pattern 2: Initialize attributes and validate inputs early to avoid None or uninitialized states, adding default values or constructing fallbacks where parameters are optional.

Example code before:

class Service:
  def __init__(self, cfg=None):
    self.log_output: IOBase | int | None
    if cfg:
      self.log_output = cfg.stream

Example code after:

class Service:
  def __init__(self, cfg=None):
    self.log_output: IOBase | int | None = None
    if cfg and cfg.stream is not None:
      self.log_output = cfg.stream
Relevant past accepted suggestions:
Suggestion 1:

Ensure attribute is always initialized

Initialize self.log_output with a default to avoid potential mypy "possibly uninitialized" errors when new branches are added. Also fix minor formatting for readability. This keeps the attribute always defined and improves maintainability.

py/selenium/webdriver/common/service.py [60-68]

-self.log_output: Optional[Union[int, IOBase]]
+self.log_output: Optional[Union[int, IOBase]] = None
 if isinstance(log_output, str):
     self.log_output = cast(IOBase, open(log_output, "a+", encoding="utf-8"))
 elif log_output == subprocess.STDOUT:
     self.log_output = None
 elif log_output is None or log_output == subprocess.DEVNULL:
     self.log_output = subprocess.DEVNULL
 else:
-    self.log_output = cast(Union[int, IOBase],log_output)
+    self.log_output = cast(Union[int, IOBase], log_output)

Suggestion 2:

Prevent invalid value reassignment

Avoid overwriting value with a non-dict message, which can lead to later .get() accesses failing. Only reassign value when message is a dict; otherwise keep value intact.

py/selenium/webdriver/remote/errorhandler.py [174-179]

 if not isinstance(message, str):
-    value = message
     if isinstance(message, dict):
+        value = message
         message = message.get("message")
     else:
         message = None

Suggestion 3:

Add missing null check validation

Add null check for clientConfig parameter to maintain consistency with other parameter validations. The method handles null options and service but doesn't validate clientConfig which could cause issues downstream.

java/src/org/openqa/selenium/ie/InternetExplorerDriver.java [111-119]

 public InternetExplorerDriver(
     @Nullable InternetExplorerDriverService service,
     @Nullable InternetExplorerOptions options,
     @Nullable ClientConfig clientConfig) {
   if (options == null) {
     options = new InternetExplorerOptions();
   }
   if (service == null) {
     service = InternetExplorerDriverService.createDefaultService();
+  }
+  if (clientConfig == null) {
+    clientConfig = ClientConfig.defaultConfig();
+  }

Suggestion 4:

Fix incomplete variable assignment

The else branch assigns None to nothing, which is a no-op. This should assign None to remote_server_addr or handle the case where command_executor doesn't have the expected attributes.

py/selenium/webdriver/remote/webdriver.py [126-129]

 if hasattr(command_executor, "client_config") and command_executor.client_config:
     remote_server_addr = command_executor.client_config.remote_server_addr
 else:
-    None
+    remote_server_addr = command_executor

Suggestion 5:

Add null validation for directory

The currentDirectory variable should be validated for null before being used in Path.Combine() calls. Add a null check to prevent potential NullReferenceException if AppContext.BaseDirectory returns null.

dotnet/src/webdriver/SeleniumManager.cs [82-89]

+ArgumentNullException.ThrowIfNull(currentDirectory);
+
 binaryFullPath = platform switch
 {
     SupportedPlatform.Windows => Path.Combine(currentDirectory, "runtimes", "win", "native", "selenium-manager.exe"),
     SupportedPlatform.Linux => Path.Combine(currentDirectory, "runtimes", "linux", "native", "selenium-manager"),
     SupportedPlatform.MacOS => Path.Combine(currentDirectory, "runtimes", "osx", "native", "selenium-manager"),
     _ => throw new PlatformNotSupportedException(
         $"Selenium Manager doesn't support your runtime platform: {RuntimeInformation.OSDescription}"),
 };

Pattern 3: Always close or dispose resources (sockets, streams, parsers, listeners, scheduled tasks) using with/try-finally and cancel/clear on shutdown to prevent leaks.

Example code before:

sock = socket.socket()
try:
  sock.connect(addr)
  data = sock.recv(1024)
finally:
  pass  # forgot to close

Example code after:

with socket.socket() as sock:
  sock.connect(addr)
  data = sock.recv(1024)
Relevant past accepted suggestions:
Suggestion 1:

Close parser and validate response

Ensure the JsonInput is always closed to avoid resource leaks. Additionally, verify the HTTP response status and content type before parsing to prevent parsing errors on non-200 responses or non-JSON bodies.

java/src/org/openqa/selenium/grid/router/HandleSession.java [255-296]

 private ClientConfig fetchNodeSessionTimeout(URI uri) {
   ClientConfig config = ClientConfig.defaultConfig().baseUri(uri).withRetries();
   Duration sessionTimeout = config.readTimeout();
   try (HttpClient httpClient = httpClientFactory.createClient(config)) {
     HttpRequest statusRequest = new HttpRequest(GET, "/status");
     HttpResponse res = httpClient.execute(statusRequest);
-    Reader reader = reader(res);
-    Json JSON = new Json();
-    JsonInput in = JSON.newInput(reader);
-    in.beginObject();
-    // Skip everything until we find "value"
-    while (in.hasNext()) {
-      if ("value".equals(in.nextName())) {
+    if (res.getStatus() == 200 && res.getHeader("Content-Type") != null && res.getHeader("Content-Type").contains("application/json")) {
+      try (Reader rdr = reader(res); JsonInput in = new Json().newInput(rdr)) {
         in.beginObject();
         while (in.hasNext()) {
-          if ("node".equals(in.nextName())) {
-            NodeStatus nodeStatus = in.read(NodeStatus.class);
-            sessionTimeout = nodeStatus.getSessionTimeout();
-            LOG.fine(
-                "Fetched session timeout from node status (read timeout: "
-                    + sessionTimeout.toSeconds()
-                    + " seconds) for "
-                    + uri);
+          String name = in.nextName();
+          if ("value".equals(name)) {
+            in.beginObject();
+            while (in.hasNext()) {
+              String inner = in.nextName();
+              if ("node".equals(inner)) {
+                NodeStatus nodeStatus = in.read(NodeStatus.class);
+                sessionTimeout = nodeStatus.getSessionTimeout();
+                LOG.fine("Fetched session timeout from node status (read timeout: "
+                    + sessionTimeout.toSeconds() + " seconds) for " + uri);
+              } else {
+                in.skipValue();
+              }
+            }
+            in.endObject();
           } else {
             in.skipValue();
           }
         }
-        in.endObject();
-      } else {
-        in.skipValue();
       }
+    } else {
+      LOG.fine("Non-OK or non-JSON status response from " + uri + " for /status, using default read timeout.");
     }
   } catch (Exception e) {
-    LOG.fine(
-        "Use default from ClientConfig (read timeout: "
-            + config.readTimeout().toSeconds()
-            + " seconds) for "
-            + uri);
+    LOG.fine("Use default from ClientConfig (read timeout: "
+        + config.readTimeout().toSeconds() + " seconds) for " + uri);
   }
-  config = config.readTimeout(sessionTimeout);
-  return config;
+  return config.readTimeout(sessionTimeout);
 }

Suggestion 2:

Cancel tasks and free resources

Ensure scheduled tasks are cancelled and resources are released on close; cancel per-node futures and clear maps to prevent thread and memory leaks

java/src/org/openqa/selenium/grid/distributor/local/LocalNodeRegistry.java [535-539]

 @Override
 public void close() {
   LOG.info("Shutting down LocalNodeRegistry");
+  Lock writeLock = lock.writeLock();
+  writeLock.lock();
+  try {
+    scheduledHealthChecks.values().forEach(f -> {
+      if (f != null) {
+        f.cancel(false);
+      }
+    });
+    scheduledHealthChecks.clear();
+    allChecks.clear();
+    nodes.values().forEach(n -> {
+      if (n instanceof RemoteNode) {
+        try {
+          ((RemoteNode) n).close();
+        } catch (Exception e) {
+          LOG.log(Level.WARNING, "Unable to close node properly: " + e.getMessage());
+        }
+      }
+    });
+    nodes.clear();
+  } finally {
+    writeLock.unlock();
+  }
 }

Suggestion 3:

Cancel tasks and close nodes

Ensure scheduled tasks are cancelled and nodes are closed on shutdown. Cancel futures in scheduledHealthChecks, clear listeners if needed, and close any RemoteNode instances to prevent resource leaks.

java/src/org/openqa/selenium/grid/distributor/local/LocalNodeRegistry.java [532-535]

 @Override
 public void close() {
   LOG.info("Shutting down LocalNodeRegistry");
+  Lock writeLock = lock.writeLock();
+  writeLock.lock();
+  try {
+    // Cancel scheduled health checks
+    for (ScheduledFuture<?> future : scheduledHealthChecks.values()) {
+      if (future != null) {
+        future.cancel(false);
+      }
+    }
+    scheduledHealthChecks.clear();
+    allChecks.clear();
+    // Close remote nodes
+    for (Node node : nodes.values()) {
+      if (node instanceof RemoteNode) {
+        try {
+          ((RemoteNode) node).close();
+        } catch (Exception e) {
+          LOG.log(Level.WARNING, "Unable to close node properly: " + e.getMessage());
+        }
+      }
+    }
+    nodes.clear();
+  } finally {
+    writeLock.unlock();
+  }
 }

Suggestion 4:

Add proper resource disposal handling

The IPv4 fallback listener is not properly disposed if an exception occurs during its operation. Wrap the fallback listener in a using statement or try-finally block to ensure proper resource cleanup.

dotnet/src/webdriver/Internal/PortUtilities.cs [45-53]

 catch (SocketException)
 {
     // If IPv6Any is not supported, fallback to IPv4
     var listener = new TcpListener(IPAddress.Any, 0);
-    listener.Start();
-    int port = ((IPEndPoint)listener.LocalEndpoint).Port;
-    listener.Stop();
-    return port;
+    try
+    {
+        listener.Start();
+        int port = ((IPEndPoint)listener.LocalEndpoint).Port;
+        return port;
+    }
+    finally
+    {
+        listener.Stop();
+    }
 }

Suggestion 5:

Use proper resource disposal patterns

The TcpListener resources are not properly disposed in case of exceptions. Use using statements or try-finally blocks to ensure proper cleanup. This prevents resource leaks if exceptions occur between Start() and Stop() calls.

dotnet/src/webdriver/Internal/PortUtilities.cs [38-43]

-var listener = new TcpListener(IPAddress.IPv6Any, 0);
+using var listener = new TcpListener(IPAddress.IPv6Any, 0);
 listener.Server.DualMode = true; // Listen on both IPv4 and IPv6
 listener.Start();
 int port = ((IPEndPoint)listener.LocalEndpoint).Port;
-listener.Stop();
 return port;

Suggestion 6:

Ensure proper socket cleanup

The socket resource is not properly cleaned up if an exception occurs after the IPv6 socket creation or during listen/getsockname operations. Use a try-finally block to ensure the socket is always closed, preventing resource leaks.

py/selenium/webdriver/common/utils.py [45-47]

 free_socket = None
 try:
     # IPv4
     free_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
     free_socket.bind(("127.0.0.1", 0))
 except OSError:
     if free_socket:
         free_socket.close()
     # IPv6
     free_socket = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
     free_socket.bind(("::1", 0))
-free_socket.listen(5)
-port: int = free_socket.getsockname()[1]
-free_socket.close()
 
+try:
+    free_socket.listen(5)
+    port: int = free_socket.getsockname()[1]
+finally:
+    free_socket.close()
+

Suggestion 7:

Prevent socket resource leak

The IPv4 socket should be closed if binding fails to prevent resource leaks. Currently, if IPv4 socket creation succeeds but binding fails, the socket remains open when the exception is caught.

py/selenium/webdriver/common/utils.py [34-44]

+free_socket = None
 try:
     # IPv4
     free_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
     free_socket.bind(("127.0.0.1", 0))
 except OSError:
+    if free_socket:
+        free_socket.close()
     # IPv6
     free_socket = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
     free_socket.bind(("::1", 0))
 free_socket.listen(5)
 port: int = free_socket.getsockname()[1]
 free_socket.close()

Suggestion 8:

Close socket to prevent leaks

The socket is not being closed after use, which can lead to resource leaks. Always close sockets after use, preferably using a context manager or explicitly calling close().

py/selenium/webdriver/remote/server.py [120-125]

 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 host = self.host if self.host is not None else "localhost"
 try:
     sock.connect((host, self.port))
+    sock.close()
     raise ConnectionError(f"Selenium server is already running, or something else is using port {self.port}")
 except ConnectionRefusedError:
+    sock.close()

Suggestion 9:

Check process state before terminating

The stop() method should be more resilient by checking if the process is still running before attempting to terminate it. The current implementation could raise an exception if the process has already terminated.

py/selenium/webdriver/remote/server.py [134-142]

 def stop(self):
     """Stop the server."""
     if self.process is None:
         raise RuntimeError("Selenium server isn't running")
     else:
-        self.process.terminate()
-        self.process.wait()
+        if self.process.poll() is None:  # Check if process is still running
+            self.process.terminate()
+            self.process.wait()
         self.process = None
         print("Selenium server has been terminated")

Suggestion 10:

Ensure proper resource cleanup

Ensure the driver is properly closed even if the test fails by using a try-finally block. Currently, if the test fails before reaching driver.quit(), the driver won't be properly cleaned up.

py/test/selenium/webdriver/remote/remote_connection_tests.py [38-53]

 def test_remote_webdriver_with_http_timeout(firefox_options, webserver):
     """This test starts a remote webdriver with an http client timeout
     set less than the implicit wait timeout, and verifies the http timeout
     is triggered first when waiting for an element.
     """
     http_timeout = 6
     wait_timeout = 8
     server_addr = f"http://{webserver.host}:{webserver.port}"
     client_config = ClientConfig(remote_server_addr=server_addr, timeout=http_timeout)
     assert client_config.timeout == http_timeout
     driver = webdriver.Remote(options=firefox_options, client_config=client_config)
-    driver.get(f"{server_addr}/simpleTest.html")
-    driver.implicitly_wait(wait_timeout)
-    with pytest.raises(ReadTimeoutError):
-        driver.find_element(By.ID, "no_element_to_be_found")
-    driver.quit()
+    try:
+        driver.get(f"{server_addr}/simpleTest.html")
+        driver.implicitly_wait(wait_timeout)
+        with pytest.raises(ReadTimeoutError):
+            driver.find_element(By.ID, "no_element_to_be_found")
+    finally:
+        driver.quit()

Pattern 4: Add precise validation and defensive checks around parsing and collections (status code/content-type checks before JSON parse, bounds/type checks, guard against missing keys, safe removals).

Example code before:

data = json.loads(resp.text)
value = data["value"]
handlers[event].remove(cb_id)

Example code after:

if resp.status_code == 200 and "application/json" in resp.headers.get("Content-Type",""):
  data = json.loads(resp.text)
  value = data.get("value")
if event in handlers and cb_id in handlers[event]:
  handlers[event].remove(cb_id)
Relevant past accepted suggestions:
Suggestion 1:

Close parser and validate response

Ensure the JsonInput is always closed to avoid resource leaks. Additionally, verify the HTTP response status and content type before parsing to prevent parsing errors on non-200 responses or non-JSON bodies.

java/src/org/openqa/selenium/grid/router/HandleSession.java [255-296]

 private ClientConfig fetchNodeSessionTimeout(URI uri) {
   ClientConfig config = ClientConfig.defaultConfig().baseUri(uri).withRetries();
   Duration sessionTimeout = config.readTimeout();
   try (HttpClient httpClient = httpClientFactory.createClient(config)) {
     HttpRequest statusRequest = new HttpRequest(GET, "/status");
     HttpResponse res = httpClient.execute(statusRequest);
-    Reader reader = reader(res);
-    Json JSON = new Json();
-    JsonInput in = JSON.newInput(reader);
-    in.beginObject();
-    // Skip everything until we find "value"
-    while (in.hasNext()) {
-      if ("value".equals(in.nextName())) {
+    if (res.getStatus() == 200 && res.getHeader("Content-Type") != null && res.getHeader("Content-Type").contains("application/json")) {
+      try (Reader rdr = reader(res); JsonInput in = new Json().newInput(rdr)) {
         in.beginObject();
         while (in.hasNext()) {
-          if ("node".equals(in.nextName())) {
-            NodeStatus nodeStatus = in.read(NodeStatus.class);
-            sessionTimeout = nodeStatus.getSessionTimeout();
-            LOG.fine(
-                "Fetched session timeout from node status (read timeout: "
-                    + sessionTimeout.toSeconds()
-                    + " seconds) for "
-                    + uri);
+          String name = in.nextName();
+          if ("value".equals(name)) {
+            in.beginObject();
+            while (in.hasNext()) {
+              String inner = in.nextName();
+              if ("node".equals(inner)) {
+                NodeStatus nodeStatus = in.read(NodeStatus.class);
+                sessionTimeout = nodeStatus.getSessionTimeout();
+                LOG.fine("Fetched session timeout from node status (read timeout: "
+                    + sessionTimeout.toSeconds() + " seconds) for " + uri);
+              } else {
+                in.skipValue();
+              }
+            }
+            in.endObject();
           } else {
             in.skipValue();
           }
         }
-        in.endObject();
-      } else {
-        in.skipValue();
       }
+    } else {
+      LOG.fine("Non-OK or non-JSON status response from " + uri + " for /status, using default read timeout.");
     }
   } catch (Exception e) {
-    LOG.fine(
-        "Use default from ClientConfig (read timeout: "
-            + config.readTimeout().toSeconds()
-            + " seconds) for "
-            + uri);
+    LOG.fine("Use default from ClientConfig (read timeout: "
+        + config.readTimeout().toSeconds() + " seconds) for " + uri);
   }
-  config = config.readTimeout(sessionTimeout);
-  return config;
+  return config.readTimeout(sessionTimeout);
 }

Suggestion 2:

Handle potential None from regex

The regex search could return None if the pattern doesn't match, causing an AttributeError when accessing index [1]. Add a check to handle cases where the URL format doesn't match the expected pattern.

scripts/update_multitool_binaries.py [40-42]

-version = re.search(f"download/(.*?)/{tool}", data[tool]["binaries"][0]["url"])[
-    1
-]
+version_match = re.search(f"download/(.*?)/{tool}", data[tool]["binaries"][0]["url"])
+if not version_match:
+    continue
+version = version_match[1]

Suggestion 3:

Handle negative numeric strings properly

The isdigit() method only checks for positive integers and doesn't handle negative numbers, floats, or other numeric formats. Use a more robust check to determine if the string represents a number that shouldn't be parsed as JSON.

py/selenium/webdriver/remote/errorhandler.py [165]

-if not value_json.isdigit():
+if not (value_json.isdigit() or (value_json.startswith('-') and value_json[1:].isdigit())):

Suggestion 4:

Validate required JSON fields

The from_json method should validate that required fields (realm, origin, type) are present in the JSON data. Currently, it allows None values for required fields which could cause issues downstream.

py/selenium/webdriver/common/bidi/script.py [57-75]

 @classmethod
 def from_json(cls, json: dict) -> "RealmInfo":
     """Creates a RealmInfo instance from a dictionary.
 
     Parameters:
     -----------
         json: A dictionary containing the realm information.
 
     Returns:
     -------
         RealmInfo: A new instance of RealmInfo.
     """
+    if not json.get("realm") or not json.get("origin") or not json.get("type"):
+        raise ValueError("Required fields 'realm', 'origin', and 'type' must be present")
+    
     return cls(
         realm=json.get("realm"),
         origin=json.get("origin"),
         type=json.get("type"),
         context=json.get("context"),
         sandbox=json.get("sandbox"),
     )

Suggestion 5:

Prevent potential KeyError

**params))

  •        del self.subscriptions[event_name]
    
  •    if event_name in self.subscriptions:
    
  •        callbacks = self.subscriptions[event_name]
    
  •        if callback_id in callbacks:
    
  •            callbacks.remove(callback_id)
    
  •            if not callbacks:
    
  •                params = {"events": [event_name]}
    
  •                session = Session(self.conn)
    
  •                self.conn.execute(session.unsubscribe(**params))
    
  •                del self.subscriptions[event_name]
    







**The code attempts to access self.subscriptions[event_name] after checking if event_name is in self.subscriptions, but then immediately accesses it again on the next line without checking. This could cause a KeyError if event_name is not in self.subscriptions.**

[py/selenium/webdriver/common/bidi/browsing_context.py [716-718]](https://github.com/SeleniumHQ/selenium/pull/15848/files#diff-a65d02c951aeb477672aa52e5eb0106645ba869aa186c0af6d2672c42cac95c6R716-R718)

```diff
 +        self.conn.remove_callback(event_obj, callback_id)
 +        if event_name in self.subscriptions and callback_id in self.subscriptions[event_name]:
 +            self.subscriptions[event_name].remove(callback_id)
++            if len(self.subscriptions[event_name]) == 0:
++                params = {"events": [event_name]}
++                session = Session(self.conn)
++                self.conn.execute(session.unsubscribe(**params))
++                del self.subscriptions[event_name]

Suggestion 6:

Add warning for unexpected types

The current implementation may silently ignore non-dictionary items in the children list without any warning. Consider adding a warning or log message when encountering unexpected child types to help with debugging.

py/selenium/webdriver/common/bidi/browsing_context.py [111-113]

 raw_children = json.get("children")
 if raw_children is not None and isinstance(raw_children, list):
-    children = [BrowsingContextInfo.from_json(child) for child in raw_children if isinstance(child, dict)]
+    children = []
+    for child in raw_children:
+        if isinstance(child, dict):
+            children.append(BrowsingContextInfo.from_json(child))
+        else:
+            import warnings
+            warnings.warn(f"Unexpected child type in browsing context: {type(child)}")

Suggestion 7:

Prevent ValueError on removal

Check if the callback_id exists in the subscriptions before attempting to remove it. The current implementation will raise a ValueError if the callback_id is not in the list, which could happen if it was already removed or never added.

py/selenium/webdriver/common/bidi/browsing_context.py [716]

 def remove_event_handler(self, event: str, callback_id: int) -> None:
     """Remove an event handler from the browsing context.
 
     Parameters:
     ----------
         event: The event to unsubscribe from.
         callback_id: The callback id to remove.
     """
     try:
         event_name = self.EVENTS[event]
     except KeyError:
         raise Exception(f"Event {event} not found")
 
     event_obj = BrowsingContextEvent(event_name)
 
     self.conn.remove_callback(event_obj, callback_id)
-    self.subscriptions[event_name].remove(callback_id)
+    if event_name in self.subscriptions and callback_id in self.subscriptions[event_name]:
+        self.subscriptions[event_name].remove(callback_id)

Suggestion 8:

Validate dictionary type before processing

Add type checking for each child element before calling from_json to prevent runtime errors. The current code assumes each child is a dictionary, but if any non-dictionary value is present in the list, it will cause a runtime error.

py/selenium/webdriver/common/bidi/browsing_context.py [111-113]

 raw_children = json.get("children")
 if raw_children is not None and isinstance(raw_children, list):
-    children = [BrowsingContextInfo.from_json(child) for child in raw_children]
+    children = [BrowsingContextInfo.from_json(child) for child in raw_children if isinstance(child, dict)]

Suggestion 9:

Check for missing fields

The code uses data.get() which returns None for missing keys, but then checks types without checking for None values. This could lead to misleading error messages when fields are missing versus when they have incorrect types.

py/selenium/webdriver/common/bidi/browser.py [131-146]

 try:
     client_window = data.get("clientWindow")
+    if client_window is None:
+        raise ValueError("clientWindow is required")
     if not isinstance(client_window, str):
         raise ValueError("clientWindow must be a string")
 
     state = data.get("state")
+    if state is None:
+        raise ValueError("state is required")
     if not isinstance(state, str):
         raise ValueError("state must be a string")
 
     width = data.get("width")
+    if width is None:
+        raise ValueError("width is required")
     if not isinstance(width, int):
         raise ValueError("width must be an integer")
 
     height = data.get("height")
+    if height is None:
+        raise ValueError("height is required")
     if not isinstance(height, int):
         raise ValueError("height must be an integer")

Suggestion 10:

Consistent field access pattern

Use data["height"] instead of data.get("height") for consistency with other required fields. The current implementation will accept None values, which contradicts the validation check.

py/selenium/webdriver/common/bidi/browser.py [146-148]

-height = data.get("height")
-if not isinstance(height, int):
-    raise ValueError("height must be an integer")
+height = data["height"]
+if not isinstance(height, int) or height < 0:
+    raise ValueError(f"height must be a non-negative integer, got {height}")

Pattern 5: Use platform- and language-appropriate APIs and patterns (path separators, time arithmetic without integer truncation, concurrent collections, string interpolation) to improve correctness and clarity.

Example code before:

var paths = env.Split(';');
var timeout = baseDuration.multipliedBy(MULT / 2); // int division risk
var map = new Dictionary<Key, Val>(); // accessed concurrently
sb.Append(string.Format(" id={0}", id));

Example code after:

var paths = env.Split(Path.PathSeparator);
var timeout = baseDuration.multipliedBy((long)MULT).dividedBy(2);
var map = new ConcurrentDictionary<Key, Val>();
sb.Append($" id={id}");
Relevant past accepted suggestions:
Suggestion 1:

Use platform-appropriate path separator

The code assumes semicolon as the path separator, but this may not be correct on all platforms. On Unix-like systems, the path separator is typically a colon (:). Consider using Path.PathSeparator or detecting the platform-appropriate separator.

dotnet/src/webdriver/SeleniumManager.cs [105-111]

 // Supporting .NET5+ applications deployed as self-contained applications (single file or AOT)
 var nativeDllSearchDirectories = AppContext.GetData("NATIVE_DLL_SEARCH_DIRECTORIES")?.ToString();
 
 if (nativeDllSearchDirectories is not null)
 {
-    probingPaths.AddRange(nativeDllSearchDirectories.Split(';').Select(path => Path.Combine(path, seleniumManagerFileName)));
+    probingPaths.AddRange(nativeDllSearchDirectories.Split(Path.PathSeparator).Select(path => Path.Combine(path, seleniumManagerFileName)));
 }

Suggestion 2:

Use LINQ FirstOrDefault for consistency

The Find method returns null when no matching element is found, but the subsequent null check uses is null pattern. Consider using FirstOrDefault with LINQ for consistency with the added using System.Linq import, or keep the current approach but ensure consistent null checking patterns throughout the codebase.

dotnet/src/webdriver/SeleniumManager.cs [134-140]

-binaryFullPath = probingPaths.Find(path => File.Exists(path));
+binaryFullPath = probingPaths.FirstOrDefault(File.Exists);
 
 if (binaryFullPath is null)
 {
     throw new FileNotFoundException(
         $"Unable to locate Selenium Manager binary in the following paths: {string.Join(", ", probingPaths)}");
 }

Suggestion 3:

Fix integer division in timing

The division PURGE_TIMEOUT_MULTIPLIER / 2 performs integer division; for odd values it truncates and may degrade timing logic. Compute using long arithmetic to preserve intended scaling, e.g., multiply first or use exact constants.

java/src/org/openqa/selenium/grid/distributor/local/LocalGridModel.java [245-248]

 Instant lostTime =
-    lastTouched.plus(node.getHeartbeatPeriod().multipliedBy(PURGE_TIMEOUT_MULTIPLIER / 2));
+    lastTouched.plus(node.getHeartbeatPeriod().multipliedBy((long) PURGE_TIMEOUT_MULTIPLIER).dividedBy(2));
 Instant deadTime =
-    lastTouched.plus(node.getHeartbeatPeriod().multipliedBy(PURGE_TIMEOUT_MULTIPLIER));
+    lastTouched.plus(node.getHeartbeatPeriod().multipliedBy((long) PURGE_TIMEOUT_MULTIPLIER));

Suggestion 4:

Simplify heartbeat timeout math

Use consistent and clearer time calculations; avoid chained multiply/divide on Duration which can be error-prone. Replace with a single multiplication by half the multiplier to match intent and readability.

java/src/org/openqa/selenium/grid/distributor/local/LocalGridModel.java [245-247]

 Instant lostTime =
-    lastTouched.plus(
-        node.getHeartbeatPeriod().multipliedBy(PURGE_TIMEOUT_MULTIPLIER).dividedBy(2));
+    lastTouched.plus(node.getHeartbeatPeriod().multipliedBy(PURGE_TIMEOUT_MULTIPLIER / 2));

Suggestion 5:

Use concurrent maps for health checks

Access to allChecks and scheduledHealthChecks occurs under locks but the maps themselves are non-concurrent. Replace them with concurrent maps to avoid accidental unsynchronized access paths and reduce risk of race conditions during listener callbacks. This is critical since listeners and scheduled tasks can run concurrently.

java/src/org/openqa/selenium/grid/distributor/local/LocalNodeRegistry.java [85-86]

-private final Map<NodeId, Runnable> allChecks = new HashMap<>();
-private final Map<NodeId, ScheduledFuture<?>> scheduledHealthChecks = new HashMap<>();
+private final Map<NodeId, Runnable> allChecks = new ConcurrentHashMap<>();
+private final Map<NodeId, ScheduledFuture<?>> scheduledHealthChecks = new ConcurrentHashMap<>();

Suggestion 6:

Use string interpolation for cleaner code

Consider using string interpolation instead of string.Format for better readability and performance. The current approach is unnecessarily verbose for a simple string concatenation.

dotnet/src/webdriver/Chromium/ChromiumDriverService.cs [164]

-argsBuilder.Append(string.Format(CultureInfo.InvariantCulture, " --log-level={0}", this.LogLevel.ToString().ToUpperInvariant()));
+argsBuilder.Append($" --log-level={this.LogLevel.ToString().ToUpperInvariant()}");

[Auto-generated best practices - 2025-08-29]

Clone this wiki locally