-
-
Notifications
You must be signed in to change notification settings - Fork 8.6k
.pr_agent_auto_best_practices
Pattern 1: Enforce deterministic and safe resource handling in tests and runtime by always closing/disposing resources (sockets, streams, HTTP responses, parsers, processes) via context managers or try/finally, and ensuring cleanup on all code paths including exceptions.
Example code before:
sock = socket.socket()
try:
sock.connect(addr)
data = http_client.get("/status")
parser = JsonInput(reader(data))
# ... work ...
finally:
# forgot to close parser / http response
sock.close()
Example code after:
with socket.socket() as sock:
sock.connect(addr)
with http_client.get("/status") as resp:
if resp.status == 200 and "application/json" in resp.headers.get("Content-Type", ""):
with JsonInput(reader(resp)) as parser:
# ... work ...
Relevant past accepted suggestions:
Suggestion 1:
Properly stop driver before nullifying
Setting the global variable to None without properly stopping the driver can lead to resource leaks. The driver should be stopped before nullifying the reference to ensure proper cleanup.
if request.node.get_closest_marker("no_driver_after_test"):
+ if selenium_driver is not None:
+ selenium_driver.stop_driver()
selenium_driver = None
Suggestion 2:
Close HTTP response via context manager
Ensure the HTTP response object is properly closed by using a context manager. This prevents potential resource leaks under heavy usage or exceptions.
py/selenium/webdriver/common/utils.py [150-151]
-res = urllib.request.urlopen(f"{scheme}://{host}:{port}/status")
-return res.getcode() == 200
+with urllib.request.urlopen(f"{scheme}://{host}:{port}/status") as res:
+ return res.getcode() == 200
Suggestion 3:
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 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:
Fix incorrect macOS key mapping
The key mappings appear incorrect for macOS. The Options key should map to Alt/Option functionality, not right_shift, and the Function key has its own distinct behavior that shouldn't be aliased to right_control.
rb/lib/selenium/webdriver/common/keys.rb [98-99]
-options: "\ue050", # macOS Options key, same as right_shift
+options: "\ue052", # macOS Options key, same as right_alt
function: "\ue051", # macOS Function key, same as right_control
Suggestion 9:
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()
Suggestion 10:
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 11:
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")
Pattern 2: Validate inputs and states early with precise checks to prevent NPEs and logic errors, including null/None checks, required JSON fields presence, type checks for dict/list elements, and guarding removal operations against missing items.
Example code before:
def remove_handler(event_name, callback_id):
callbacks = subs[event_name]
callbacks.remove(callback_id)
height = data.get("height")
if not isinstance(height, int):
raise ValueError("height must be an integer")
Example code after:
def remove_handler(event_name, callback_id):
if event_name in subs and callback_id in subs[event_name]:
subs[event_name].remove(callback_id)
if not subs[event_name]:
unsubscribe(event_name)
del subs[event_name]
if "height" not in data or not isinstance(data["height"], int):
raise ValueError("height is required and must be an integer")
Relevant past accepted suggestions:
Suggestion 1:
Add parameter null validation
Validate inputs before using them. Add null checks for nodeUri, id, and availability to avoid potential NullPointerExceptions during logging and model updates.
java/src/org/openqa/selenium/grid/distributor/local/LocalNodeRegistry.java [292-304]
public void updateNodeAvailability(URI nodeUri, NodeId id, Availability availability) {
+ Require.nonNull("Node URI", nodeUri);
+ Require.nonNull("Node ID", id);
+ Require.nonNull("Availability", availability);
Lock writeLock = lock.writeLock();
writeLock.lock();
try {
LOG.log(
getDebugLogLevel(),
String.format("Health check result for %s was %s", nodeUri, availability));
model.setAvailability(id, availability);
model.updateHealthCheckCount(id, availability);
} finally {
writeLock.unlock();
}
}
Suggestion 2:
Add missing coordinates/error validation
Add validation to ensure at least one of coordinates or error is specified. Currently, both can be null which would result in an invalid geolocation override request.
java/src/org/openqa/selenium/bidi/emulation/SetGeolocationOverrideParameters.java [41-50]
if (this.coordinates != null && this.error != null) {
throw new IllegalArgumentException("Cannot specify both coordinates and error");
+}
+if (this.coordinates == null && this.error == null) {
+ throw new IllegalArgumentException("Must specify either coordinates or error");
}
if (this.contexts != null && this.userContexts != null) {
throw new IllegalArgumentException("Cannot specify both contexts and userContexts");
}
if (this.contexts == null && this.userContexts == null) {
throw new IllegalArgumentException("Must specify either contexts or userContexts");
}
Suggestion 3:
Handle IPv6 binding failures gracefully
Add a try-except block around the IPv6 socket creation and binding to handle cases where IPv6 is also unavailable, preventing potential unhandled exceptions that could crash the function.
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))
+ try:
+ # IPv6
+ free_socket = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
+ free_socket.bind(("::1", 0))
+ except OSError:
+ raise RuntimeError("Unable to bind to both IPv4 and IPv6")
Suggestion 4:
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 5:
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 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:
Fix indentation error
Fix the indentation error on line 142. The line uses tabs instead of spaces, which is inconsistent with the rest of the code and will cause a Python indentation error.
py/selenium/webdriver/common/bidi/browser.py [142]
+width = data["width"]
+if not isinstance(width, int) or width < 0:
+ raise ValueError(f"width must be a non-negative integer, got {width}")
-
Suggestion 10:
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 11:
Use specific exception types
Use a more specific exception type instead of the generic Exception class. Consider using a custom exception or a more specific built-in exception that better represents the error condition from the WebSocket response.
py/selenium/webdriver/remote/websocket_connection.py [67-73]
if "error" in response:
error = response["error"]
if "message" in response:
error_msg = f"{error}: {response['message']}"
- raise Exception(error_msg)
+ raise WebDriverException(error_msg)
else:
- raise Exception(error)
+ raise WebDriverException(error)
Pattern 3: Use correct packaging/version spec syntax and explicit bounds by separating specifiers with commas and preferring explicit upper bounds (e.g., <3.0), ensuring consistency across build files.
Example code before:
dependencies = [
"websocket-client>=1.8.0<2.0",
"urllib3[socks]>=2.5.0;<3",
"LICENSE"
"NOTICE"
]
Example code after:
dependencies = [
"websocket-client>=1.8.0,<2.0",
"urllib3[socks]>=2.5.0,<3.0",
"LICENSE",
"NOTICE",
]
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.
-"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.
-"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.
-"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.
-"urllib3[socks]>=2.5.0;<3",
+"urllib3[socks]>=2.5.0,<3.0",
Suggestion 5:
Fix missing comma in array
Add a missing comma after "LICENSE" to properly separate the list items. Without the comma, this creates a syntax error in the TOML array.
-"LICENSE"
+"LICENSE",
"NOTICE"
Suggestion 6:
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.
typing_extensions==4.14.0
-urllib3==2.4.0
+urllib3[socks]==2.4.0
Pattern 4: Make concurrency and lifecycle code robust by using concurrent collections, canceling scheduled tasks, and correctly computing timeouts to avoid race conditions, leaks, and timing bugs.
Example code before:
Map<NodeId, ScheduledFuture<?>> checks = new HashMap<>();
// on shutdown
// no cancellation
Duration lost = period.multipliedBy(MULTIPLIER / 2); // integer division bug
Example code after:
Map<NodeId, ScheduledFuture<?>> checks = new ConcurrentHashMap<>();
// on shutdown
checks.values().forEach(f -> { if (f != null) f.cancel(false); });
checks.clear();
Duration lost = period.multipliedBy((long) MULTIPLIER).dividedBy(2);
Relevant past accepted suggestions:
Suggestion 1:
Use capacity check instead of slot scan
Avoid iterating over all slots just to detect a free one by leveraging NodeStatus.hasCapacity() if it already encapsulates this logic. This reduces per-call overhead and keeps the intent consistent with other uses. Fall back to a short-circuiting check if hasCapacity() is insufficient.
java/src/org/openqa/selenium/grid/distributor/local/LocalDistributor.java [509-527]
protected Set<NodeStatus> getAvailableNodes() {
Lock readLock = this.lock.readLock();
readLock.lock();
try {
return model.getSnapshot().stream()
- .filter(
- node -> {
- // Only consider UP nodes (not DOWN, DRAINING, etc.)
- if (!UP.equals(node.getAvailability())) {
- return false;
- }
- // Consider node has at least one free slot
- return node.getSlots().stream().anyMatch(slot -> slot.getSession() == null);
- })
+ .filter(node -> UP.equals(node.getAvailability()) && node.hasCapacity())
.collect(toImmutableSet());
} finally {
readLock.unlock();
}
}
Suggestion 2:
Process all health check batches
Ensure all batches are processed by removing the limit(maxConcurrentBatches) which drops excess batches, or implement a proper throttling mechanism that still visits every batch
java/src/org/openqa/selenium/grid/distributor/local/LocalNodeRegistry.java [402-422]
private void processBatchesInParallel(List<List<Runnable>> batches, int maxConcurrentBatches) {
if (batches.isEmpty()) {
return;
}
- // Process batches with controlled parallelism
batches.parallelStream()
- .limit(maxConcurrentBatches)
.forEach(
batch ->
batch.parallelStream()
.forEach(
r -> {
try {
r.run();
} catch (Throwable t) {
- LOG.log(
- getDebugLogLevel(), "Health check execution failed in batch", t);
+ LOG.log(getDebugLogLevel(), "Health check execution failed in batch", t);
}
}));
}
Suggestion 3:
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 4:
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 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:
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 7:
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 8:
Bound status probe timeout
Add a short read timeout specifically for the /status probe to avoid hanging on an unresponsive node and blocking session creation. Use a bounded timeout (e.g., a few seconds) independent from the main client timeout.
java/src/org/openqa/selenium/grid/router/HandleSession.java [258-293]
-try (HttpClient httpClient = httpClientFactory.createClient(config)) {
+try (HttpClient httpClient = httpClientFactory.createClient(
+ ClientConfig.defaultConfig()
+ .baseUri(uri)
+ .readTimeout(Duration.ofSeconds(5))
+ .withRetries())) {
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) {
+ 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();
- ...
+ 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);
+ Duration parsed = nodeStatus.getSessionTimeout();
+ if (parsed != null && !parsed.isNegative() && !parsed.isZero()) {
+ sessionTimeout = parsed;
+ }
+ } else {
+ in.skipValue();
+ }
+ }
+ in.endObject();
} else {
in.skipValue();
}
}
- in.endObject();
- } else {
- in.skipValue();
}
}
} 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);
}
Pattern 5: Keep API and documentation accurate and consistent by aligning parameter names and annotations with interfaces, fixing Javadoc links/text, and choosing precise exception types and messages that match usage and tests.
Example code before:
// JavaDoc
@param target - target type, @see OutputType.
public <X> void beforeGetScreenshotAs(WebElement driver, OutputType<X> target) {}
// Python
raise Exception(error_msg)
Example code after:
// JavaDoc
@param target the target type, see {@link OutputType}
public <X> void beforeGetScreenshotAs(WebElement element, OutputType<X> target) {}
// Python
raise WebDriverException(error_msg)
Relevant past accepted suggestions:
Suggestion 1:
Add missing @Override annotation
Add the @Override annotation to getUpNodes() since it implements the new interface method, enabling compile-time checks.
java/src/org/openqa/selenium/grid/distributor/local/LocalNodeRegistry.java [366-376]
+@Override
public Set<NodeStatus> getUpNodes() {
Lock readLock = this.lock.readLock();
readLock.lock();
try {
return model.getSnapshot().stream()
.filter(node -> UP.equals(node.getAvailability()))
.collect(ImmutableSet.toImmutableSet());
} finally {
readLock.unlock();
}
}
Suggestion 2:
Fix incorrect Javadoc reference
The Javadoc text references TargetLocator#alert(), which is unrelated to element screenshots, and misuses "@see" in a @param. Update the description to reflect WebElement.getScreenshotAs and fix parameter docs and punctuation.
java/src/org/openqa/selenium/support/events/WebDriverListener.java [613-622]
/**
- * This action will be performed each time after {@link WebDriver.TargetLocator#alert()} is
- * called.
+ * This method will be called after {@link WebElement#getScreenshotAs(OutputType)} is called.
*
- * @param element - decorated WebElement instance
- * @param target - target type, @see OutputType
- * @param result - object in which is stored information about the screenshot.
- * @param <X> - return type for getScreenshotAs.
+ * @param element the decorated WebElement instance
+ * @param target the target type, {@link OutputType}
+ * @param result the screenshot result
+ * @param <X> the return type for getScreenshotAs
*/
default <X> void afterGetScreenshotAs(WebElement element, OutputType<X> target, X result) {}
Suggestion 3:
Correct Javadoc parameter links
**
-
- This action will be performed each time after {@link WebDriver.TargetLocator#alert()} is
-
- called.
-
- This method will be called after {@link TakesScreenshot#getScreenshotAs(OutputType)} is called.
- @param element - decorated WebElement instance
- @param target - target type, @see OutputType
**In Javadoc, avoid using "@see" within @param descriptions; link the type directly with {@link}. This improves clarity and tooling parsing. Update the parameter docs to use proper links and consistent punctuation.**
[java/src/org/openqa/selenium/support/events/WebDriverListener.java [335-343]](https://github.com/SeleniumHQ/selenium/pull/16233/files#diff-27fd3dec4abffb9b78d39c4f8ca918bcb5cd04926e3d252d2398f561f5ad61ffR335-R343)
```diff
/**
- * This method will be called before {@link TakesScreenshot#getScreenshotAs(OutputType)} is
- * called.
+ * This method will be called before {@link TakesScreenshot#getScreenshotAs(OutputType)} is called.
*
- * @param driver - decorated WebDriver instance
- * @param target - target type, @see OutputType
- * @param <X> - return type for getScreenshotAs.
+ * @param driver the decorated WebDriver instance
+ * @param target the target type, {@link OutputType}
+ * @param <X> the return type for getScreenshotAs
*/
default <X> void beforeGetScreenshotAs(WebDriver driver, OutputType<X> target) {}
Suggestion 4:
Clean up Javadoc references
** * This method will be called after {@link TakesScreenshot#getScreenshotAs(OutputType)} is called. *
-
- @param driver - decorated WebDriver instance
-
- @param target - target type, @see OutputType
-
- @param result - object in which is stored information about the screenshot.
-
- @param - return type for getScreenshotAs.
-
- @param driver decorated WebDriver instance
-
- @param target target type, see {@link OutputType}
-
- @param result object that stores the screenshot information
-
- @param return type for getScreenshotAs */ default void afterGetScreenshotAs(WebDriver driver, OutputType target, X result) {}
@@ -607,19 +607,19 @@ * This method will be called before {@link TakesScreenshot#getScreenshotAs(OutputType)} is * called. *
-
- @param element - decorated WebElement instance
-
- @param target - target type, @see OutputType
-
- @param - return type for getScreenshotAs.
-
- @param element decorated WebElement instance
-
- @param target target type, see {@link OutputType}
-
- @param return type for getScreenshotAs */ default void beforeGetScreenshotAs(WebElement element, OutputType target) {}
/** * This method will be called after {@link TakesScreenshot#getScreenshotAs(OutputType)} is called. *
-
- @param element - decorated WebElement instance
-
- @param target - target type, @see OutputType
-
- @param result - object in which is stored information about the screenshot.
-
- @param - return type for getScreenshotAs.
-
- @param element decorated WebElement instance
-
- @param target target type, see {@link OutputType}
-
- @param result result object that stores the screenshot information
-
- @param return type for getScreenshotAs
**Replace "@see" inline in @param descriptions with proper Javadoc links or move them to @see tags. Also clarify generic return type wording and remove trailing periods in parameter descriptions for consistency.**
[java/src/org/openqa/selenium/support/events/WebDriverListener.java [335-624]](https://github.com/SeleniumHQ/selenium/pull/16233/files#diff-27fd3dec4abffb9b78d39c4f8ca918bcb5cd04926e3d252d2398f561f5ad61ffR335-R624)
```diff
/**
- * This method will be called before {@link TakesScreenshot#getScreenshotAs(OutputType)} is
- * called.
+ * This method will be called before {@link TakesScreenshot#getScreenshotAs(OutputType)} is called.
*
- * @param driver - decorated WebDriver instance
- * @param target - target type, @see OutputType
- * @param <X> - return type for getScreenshotAs.
+ * @param driver decorated WebDriver instance
+ * @param target target type, see {@link OutputType}
+ * @param <X> return type for getScreenshotAs
*/
default <X> void beforeGetScreenshotAs(WebDriver driver, OutputType<X> target) {}
/**
* This method will be called after {@link TakesScreenshot#getScreenshotAs(OutputType)} is called.
*
- * @param driver - decorated WebDriver instance
- * @param target - target type, @see OutputType
- * @param result - object in which is stored information about the screenshot.
- * @param <X> - return type for getScreenshotAs.
+ * @param driver decorated WebDriver instance
+ * @param target target type, see {@link OutputType}
+ * @param result object that stores the screenshot information
+ * @param <X> return type for getScreenshotAs
*/
default <X> void afterGetScreenshotAs(WebDriver driver, OutputType<X> target, X result) {}
/**
- * This method will be called before {@link TakesScreenshot#getScreenshotAs(OutputType)} is
- * called.
+ * This method will be called before {@link TakesScreenshot#getScreenshotAs(OutputType)} is called.
*
- * @param element - decorated WebElement instance
- * @param target - target type, @see OutputType
- * @param <X> - return type for getScreenshotAs.
+ * @param element decorated WebElement instance
+ * @param target target type, see {@link OutputType}
+ * @param <X> return type for getScreenshotAs
*/
default <X> void beforeGetScreenshotAs(WebElement element, OutputType<X> target) {}
/**
* This method will be called after {@link TakesScreenshot#getScreenshotAs(OutputType)} is called.
*
- * @param element - decorated WebElement instance
- * @param target - target type, @see OutputType
- * @param result - object in which is stored information about the screenshot.
- * @param <X> - return type for getScreenshotAs.
+ * @param element decorated WebElement instance
+ * @param target target type, see {@link OutputType}
+ * @param result object that stores the screenshot information
+ * @param <X> return type for getScreenshotAs
*/
default <X> void afterGetScreenshotAs(WebElement element, OutputType<X> target, X result) {}
Suggestion 5:
Fix misleading parameter name
The parameter name driver for WebElement callbacks is misleading and contradicts the interface, risking confusion and incorrect usage. Rename it to element to align with WebDriverListener method signatures and improve test clarity.
java/test/org/openqa/selenium/support/events/EventFiringDecoratorTest.java [476-483]
-public <X> void beforeGetScreenshotAs(WebElement driver, OutputType<X> target) {
+public <X> void beforeGetScreenshotAs(WebElement element, OutputType<X> target) {
acc.append("beforeGetScreenshotAs ").append(target).append("\n");
}
@Override
-public <X> void afterGetScreenshotAs(WebElement driver, OutputType<X> target, X result) {
+public <X> void afterGetScreenshotAs(WebElement element, OutputType<X> target, X result) {
acc.append("afterGetScreenshotAs ").append(target).append(" ").append(result).append("\n");
}
Suggestion 6:
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 7:
Use specific exception types
Use a more specific exception type instead of the generic Exception class. Consider using a custom exception or a more specific built-in exception that better represents the error condition from the WebSocket response.
py/selenium/webdriver/remote/websocket_connection.py [67-73]
if "error" in response:
error = response["error"]
if "message" in response:
error_msg = f"{error}: {response['message']}"
- raise Exception(error_msg)
+ raise WebDriverException(error_msg)
else:
- raise Exception(error)
+ raise WebDriverException(error)
Suggestion 8:
Fix documentation example
Fix the syntax error in the example code where there's an extra closing parenthesis. This will prevent confusion for users following the documentation.
py/selenium/webdriver/remote/webdriver.py [1348-1361]
@property
def webextension(self):
"""Returns a webextension module object for BiDi webextension commands.
Returns:
--------
WebExtension: an object containing access to BiDi webextension commands.
Examples:
---------
>>> extension_path = "/path/to/extension"
- >>> extension_result = driver.webextension.install(path=extension_path)))
+ >>> extension_result = driver.webextension.install(path=extension_path)
>>> driver.webextension.uninstall(extension_result)
"""
Suggestion 9:
Fix unbalanced parentheses
The parentheses in this example are not properly balanced. The closing parenthesis for the method call is placed on a new line but is not aligned with the opening parenthesis, which can lead to syntax errors or confusion.
py/selenium/webdriver/support/expected_conditions.py [391-393]
->>> is_text_in_element = WebDriverWait(driver, 10).until(EC.text_to_be_present_in_element(
- (By.CLASS_NAME, "foo"), "bar")
- )
+>>> is_text_in_element = WebDriverWait(driver, 10).until(
+... EC.text_to_be_present_in_element((By.CLASS_NAME, "foo"), "bar")
+... )
Suggestion 10:
Fix inconsistent log message
The log message doesn't match the test expectation. The test expects a message containing "No valid process exit status" but the actual message uses "Invalid process exit status". Update the log message to match the test expectation.
rb/lib/selenium/webdriver/common/selenium_manager.rb [108-118]
def validate_command_result(command, status, result, stderr)
if status.nil? || status.exitstatus.nil?
- WebDriver.logger.info("Invalid process exit status for: #{command}. Assuming success if result is present.",
+ WebDriver.logger.info("No valid process exit status for: #{command}. Assuming success if result is present.",
id: :selenium_manager)
end
return unless status&.exitstatus&.positive? || result.nil?
raise Error::WebDriverError,
"Unsuccessful command executed: #{command} - Code #{status&.exitstatus}\n#{result}\n#{stderr}"
end
[Auto-generated best practices - 2025-09-29]
This wiki is not where you want to be! Visit the Wiki Home for more useful links
Getting Involved
Triaging Issues
Releasing Selenium
Ruby Development
Python Bindings
Ruby Bindings
WebDriverJs
This content is being evaluated for where it belongs
Architectural Overview
Automation Atoms
HtmlUnitDriver
Lift Style API
LoadableComponent
Logging
PageFactory
RemoteWebDriver
Xpath In WebDriver
Moved to Official Documentation
Bot Style Tests
Buck
Continuous Integration
Crazy Fun Build
Design Patterns
Desired Capabilities
Developer Tips
Domain Driven Design
Firefox Driver
Firefox Driver Internals
Focus Stealing On Linux
Frequently Asked Questions
Google Summer Of Code
Grid Platforms
History
Internet Explorer Driver
InternetExplorerDriver Internals
Next Steps
PageObjects
RemoteWebDriverServer
Roadmap
Scaling WebDriver
SeIDE Release Notes
Selenium Emulation
Selenium Grid 4
Selenium Help
Shipping Selenium 3
The Team
TLC Meetings
Untrusted SSL Certificates
WebDriver For Mobile Browsers
Writing New Drivers