-
-
Notifications
You must be signed in to change notification settings - Fork 8.6k
.pr_agent_accepted_suggestions
| PR 16854 (2026-01-06) |
[possible issue] Reverse frame iteration for correct traceback
✅ Reverse frame iteration for correct traceback
Reverse the frame iteration in rebuild_traceback to construct the traceback in the correct order by using reversed(frames).
def rebuild_traceback(frames):
"""Rebuild a traceback object from frames list."""
new_tb = None
- for frame, lineno, lasti in frames:
+ for frame, lineno, lasti in reversed(frames):
new_tb = types.TracebackType(new_tb, frame, lasti, lineno)
return new_tbSuggestion importance[1-10]: 9
__
Why: The suggestion correctly identifies a critical bug where the traceback is reconstructed in reverse order, and provides the simple, correct fix using reversed().
[learned best practice] Stop using private pytest APIs
✅ Stop using private pytest APIs
Avoid using pytest’s private call.excinfo._excinfo; use the public ExceptionInfo attributes and guard for missing fields to keep this hook compatible across pytest versions.
-exc_type, exc_value, exc_tb = call.excinfo._excinfo
+exc_type = call.excinfo.type
+exc_value = call.excinfo.value
+exc_tb = call.excinfo.tbSuggestion importance[1-10]: 6
__
Why: Relevant best practice - Add explicit validation/guards at integration boundaries and avoid relying on private/internal APIs.
[learned best practice] Centralize duplicated configuration values
✅ Centralize duplicated configuration values
Replace the duplicated width=130 literals with a single constant to keep console/traceback formatting consistent and easier to change.
-console = rich.console.Console(force_terminal=force_terminal, width=130)
+TRACEBACK_WIDTH = 130
+console = rich.console.Console(force_terminal=force_terminal, width=TRACEBACK_WIDTH)
...
tb = rich.traceback.Traceback.from_exception(
exc_type,
exc_value,
new_tb,
show_locals=False,
max_frames=5,
- width=130,
+ width=TRACEBACK_WIDTH,
)Suggestion importance[1-10]: 5
__
Why: Relevant best practice - Reduce duplication by centralizing shared configuration values.
| PR 16844 (2026-01-04) |
[general] Create a defensive copy of map
✅ Create a defensive copy of map
Make the CommandSpec class more robust by creating a defensive, unmodifiable copy of the httpHeaders map in its constructor to ensure immutability.
java/src/org/openqa/selenium/remote/codec/AbstractHttpCommandCodec.java [372-380]
private CommandSpec(HttpMethod method, String path, Map<String, String> httpHeaders) {
this.method = Require.nonNull("HTTP method", method);
this.path = path;
this.pathSegments =
Arrays.stream(path.split("/"))
.filter(e -> !e.isEmpty())
.collect(Collectors.toUnmodifiableList());
- this.httpHeaders = httpHeaders;
+ this.httpHeaders = Map.copyOf(httpHeaders);
}Suggestion importance[1-10]: 6
__
Why: The suggestion correctly identifies a potential mutability issue and proposes using Map.copyOf to create an immutable defensive copy, which improves the robustness and immutability of CommandSpec.
[learned best practice] Validate inputs and parse headers robustly
✅ Validate inputs and parse headers robustly
Trim and reject blank name values, and make the Accept check tolerant of compound header values (e.g., multiple media types) instead of exact string equality.
java/src/org/openqa/selenium/grid/node/local/LocalNode.java [825-837]
String filename =
Optional.ofNullable(incoming.get("name"))
.map(Object::toString)
+ .map(String::trim)
+ .filter(name -> !name.isEmpty())
.orElseThrow(
() ->
new WebDriverException(
"Please specify file to download in payload as {\"name\":"
+ " \"fileToDownload\"}"));
File file = findDownloadedFile(downloadsDirectory, filename);
-if (MediaType.OCTET_STREAM.toString().equalsIgnoreCase(req.getHeader(HttpHeader.Accept))) {
+String accept = Optional.ofNullable(req.getHeader(HttpHeader.Accept)).orElse("");
+if (accept.toLowerCase(US).contains(MediaType.OCTET_STREAM.toString())) {
return fileAsBinaryResponse(file);
}Suggestion importance[1-10]: 5
__
Why: Relevant best practice - Add explicit validation and guards at integration boundaries by trimming inputs and validating presence/format before use.
| PR 16840 (2026-01-04) |
[possible issue] Fix incorrect parsing of failed tests
✅ Fix incorrect parsing of failed tests
Fix the flawed logic for parsing failed tests. The current script exits prematurely; use grep and awk to robustly find the test summary and extract failed tests.
.github/workflows/bazel.yml [180-186]
- name: Parse Bazel log for failures
if: always()
shell: bash
run: >
- tac bazel-testlogs/bazel-console.log |
- awk '/PASSED in/ {exit} /FAILED in/ {print $1}' |
- tac > bazel-testlogs/bazel-failures.txt
+ grep -A 1000 "Test Summary" bazel-testlogs/bazel-console.log |
+ awk '/FAILED in/ {print $1}' > bazel-testlogs/bazel-failures.txtSuggestion importance[1-10]: 9
__
Why: This suggestion correctly identifies a critical flaw in the log parsing logic that would prevent the new test rerun feature from working as intended, making it a high-impact fix.
[possible issue] Ensure log directory exists
✅ Ensure log directory exists
Create the bazel-testlogs directory using mkdir -p before attempting to write log files into it to prevent potential errors.
.github/workflows/bazel.yml [177-179]
run: |
set -o pipefail
+ mkdir -p bazel-testlogs
${{ inputs.run }} 2>&1 | tee bazel-testlogs/bazel-console.logSuggestion importance[1-10]: 6
__
Why: This is a good defensive coding practice that prevents the 'Run Bazel' step from failing if the bazel-testlogs directory doesn't exist, improving the workflow's robustness.
| PR 16835 (2026-01-03) |
[learned best practice] Centralize overload implementations
✅ Centralize overload implementations
Replace the repeated overload bodies with a single varargs implementation (and optionally keep overloads delegating to it) to reduce maintenance and risk of inconsistencies.
java/src/org/openqa/selenium/internal/Sets.java [31-63]
-public static <T> Set<T> orderedSetOf(T v1, T v2) {
+@SafeVarargs
+public static <T> Set<T> orderedSetOf(T... values) {
Set<T> set = new LinkedHashSet<>();
- set.add(v1);
- set.add(v2);
+ Collections.addAll(set, values);
return unmodifiableSet(set);
}
-public static <T> Set<T> orderedSetOf(T v1, T v2, T v3) {
- Set<T> set = new LinkedHashSet<>();
- set.add(v1);
- set.add(v2);
- set.add(v3);
- return unmodifiableSet(set);
-}
-
-public static <T> Set<T> orderedSetOf(T v1, T v2, T v3, T v4) {
- Set<T> set = new LinkedHashSet<>();
- set.add(v1);
- set.add(v2);
- set.add(v3);
- set.add(v4);
- return unmodifiableSet(set);
-}
-
-public static <T> Set<T> orderedSetOf(T v1, T v2, T v3, T v4, T v5) {
- Set<T> set = new LinkedHashSet<>();
- set.add(v1);
- set.add(v2);
- set.add(v3);
- set.add(v4);
- set.add(v5);
- return unmodifiableSet(set);
-}
-Suggestion importance[1-10]: 5
__
Why: Relevant best practice - Reduce duplication by centralizing repeated logic (prefer a single shared helper over many near-identical overloads).
| PR 16831 (2026-01-02) |
[possible issue] Handle null exception message
✅ Handle null exception message
Add a null check for the exception message in the shortDescription method to prevent a NullPointerException when exception.getMessage() is null.
java/src/org/openqa/selenium/support/ui/ExpectedConditions.java [1820-1823]
private static String shortDescription(@Nullable Exception exception) {
- if (exception == null) return "";
- return exception.getClass().getName() + ": " + exception.getMessage().split("\\n", 2)[0];
+ if (exception == null) {
+ return "";
+ }
+ String msg = exception.getMessage();
+ String summary = (msg == null) ? "" : msg.split("\\n", 2)[0];
+ return exception.getClass().getName() + ": " + summary;
}Suggestion importance[1-10]: 8
__
Why: This suggestion correctly identifies a potential NullPointerException if exception.getMessage() returns null. The proposed change adds a necessary null check, preventing a crash and making the helper method more robust.
[learned best practice] Null-guard toString state fields
✅ Null-guard toString state fields
Make toString() safe if it's evaluated before apply() by guarding windowHandles (and its size) to prevent test flakiness/NPEs during timeout construction.
java/test/org/openqa/selenium/WaitingConditions.java [200-218]
public static ExpectedCondition<Set<String>> windowHandleCountToBe(
final int expectedWindowCount) {
return new ExpectedCondition<Set<String>>() {
private Set<String> windowHandles;
@Override
public Set<String> apply(WebDriver driver) {
windowHandles = driver.getWindowHandles();
return windowHandles.size() == expectedWindowCount ? windowHandles : null;
}
@Override
public String toString() {
+ if (windowHandles == null) {
+ return String.format("window count to be: %s, but was: <unknown>", expectedWindowCount);
+ }
return String.format(
"window count to be: %s, but was: %s (%s)",
expectedWindowCount, windowHandles.size(), windowHandles);
}
};
}Suggestion importance[1-10]: 5
__
Why:
Relevant best practice - Add explicit validation/guards before using fields in error messages; avoid using potentially-uninitialized state in toString().
| PR 16816 (2025-12-30) |
[general] Cache logger instance
✅ Cache logger instance
In init.py, store the result of logging.getLogger("selenium") in a variable to avoid repeated calls and improve readability.
py/selenium/webdriver/init.py [22-25]
+logger = logging.getLogger("selenium")
if os.environ.get("SE_DEBUG"):
- logging.getLogger("selenium").setLevel(logging.DEBUG)
- if not logging.getLogger("selenium").handlers:
- logging.getLogger("selenium").addHandler(logging.StreamHandler())
+ logger.setLevel(logging.DEBUG)
+ if not logger.handlers:
+ logger.addHandler(logging.StreamHandler())Suggestion importance[1-10]: 4
__
Why: This is a good suggestion for improving code readability and maintainability by caching the logger instance in a variable, which is a common best practice.
| PR 16800 (2025-12-27) |
[possible issue] Fix incorrect chained comparison syntax
✅ Fix incorrect chained comparison syntax
Fix the syntactically incorrect chained comparison github.event.repository.fork == false == 'seleniumhq' by using a logical && to correctly check both the fork status and repository owner.
.github/workflows/ci-rbe.yml [27]
-if: github.event.repository.fork == false == 'seleniumhq' && startsWith(github.head_ref, 'renovate/') != true
+if: github.event.repository.fork == false && github.repository_owner == 'seleniumhq' && startsWith(github.head_ref, 'renovate/') != trueSuggestion importance[1-10]: 10
__
Why: The suggestion correctly identifies a critical bug in the PR where a chained comparison will always evaluate to false, preventing the CI jobs from running. The proposed fix is correct and restores the intended logic.
[learned best practice] Make fork guard null-safe
✅ Make fork guard null-safe
For schedule/workflow_dispatch, github.event.repository may be absent, so comparing it to false can incorrectly skip jobs; use != true and keep the owner check separate.
.github/workflows/nightly.yml [37]
-if: (github.event.repository.fork == false == 'seleniumhq') && (inputs.language == 'ruby' || inputs.language == 'all' || github.event_name == 'schedule')
+if: (github.repository_owner == 'seleniumhq' && github.event.repository.fork != true) && (inputs.language == 'ruby' || inputs.language == 'all' || github.event_name == 'schedule')Suggestion importance[1-10]: 5
__
Why: Relevant best practice - Validate and sanitize external inputs/contexts before use; account for missing/null GitHub event fields across different triggers.
| PR 16796 (2025-12-25) |
[general] Handle invalid timeout property
✅ Handle invalid timeout property
Add validation for the webdriver.bidi.timeout system property to handle non-numeric or negative values by falling back to a default.
java/src/org/openqa/selenium/bidi/BiDi.java [32-33]
-private final Duration timeout =
- Duration.ofSeconds(parseLong(System.getProperty("webdriver.bidi.timeout", "30")));
+private final Duration timeout;
+{
+ long seconds;
+ try {
+ seconds = parseLong(System.getProperty("webdriver.bidi.timeout", "30"));
+ if (seconds < 0) {
+ seconds = 30;
+ }
+ } catch (NumberFormatException e) {
+ seconds = 30;
+ }
+ timeout = Duration.ofSeconds(seconds);
+}
+Suggestion importance[1-10]: 7
__
Why: The suggestion improves robustness by handling non-numeric and negative values for the timeout property, preventing potential runtime exceptions from invalid configuration.
| PR 16789 (2025-12-24) |
[possible issue] Raise the AssertionError
✅ Raise the AssertionError
Add a raise statement before AssertionError to ensure the test fails correctly when an exception is caught.
py/test/selenium/webdriver/common/click_scrolling_tests.py [52-53]
except MoveTargetOutOfBoundsException as e:
- AssertionError(f"Should not be out of bounds: {e.msg}")
+ raise AssertionError(f"Should not be out of bounds: {e.msg}")Suggestion importance[1-10]: 9
__
Why: The suggestion correctly identifies a critical bug where a test would silently pass instead of failing because the AssertionError was not raised.
[learned best practice] Validate caller-provided input shape
✅ Validate caller-provided input shape
raw.get(...) assumes raw is a mapping; add a type/structure guard (or normalize input) and raise a clear error for invalid types to avoid runtime attribute errors.
py/selenium/webdriver/common/proxy.py [114-140]
def __init__(self, raw=None):
"""Creates a new Proxy.
Args:
raw: Raw proxy data. If None, default class values are used.
"""
- if raw:
- if raw.get("proxyType"):
- self.proxy_type = ProxyType.load(raw["proxyType"])
- if raw.get("httpProxy"):
- self.http_proxy = raw["httpProxy"]
- if raw.get("noProxy"):
- self.no_proxy = raw["noProxy"]
- if raw.get("proxyAutoconfigUrl"):
- self.proxy_autoconfig_url = raw["proxyAutoconfigUrl"]
- if raw.get("sslProxy"):
- self.sslProxy = raw["sslProxy"]
- if raw.get("autodetect"):
- self.auto_detect = raw["autodetect"]
- if raw.get("socksProxy"):
- self.socks_proxy = raw["socksProxy"]
- if raw.get("socksUsername"):
- self.socks_username = raw["socksUsername"]
- if raw.get("socksPassword"):
- self.socks_password = raw["socksPassword"]
- if raw.get("socksVersion"):
- self.socks_version = raw["socksVersion"]
+ if raw is None:
+ return
+ if not isinstance(raw, dict):
+ raise TypeError(f"`raw` must be a dict, got {type(raw)!r}")
+ if raw.get("proxyType"):
+ self.proxy_type = ProxyType.load(raw["proxyType"])
+ if raw.get("httpProxy"):
+ self.http_proxy = raw["httpProxy"]
+ if raw.get("noProxy"):
+ self.no_proxy = raw["noProxy"]
+ if raw.get("proxyAutoconfigUrl"):
+ self.proxy_autoconfig_url = raw["proxyAutoconfigUrl"]
+ if raw.get("sslProxy"):
+ self.sslProxy = raw["sslProxy"]
+ if raw.get("autodetect"):
+ self.auto_detect = raw["autodetect"]
+ if raw.get("socksProxy"):
+ self.socks_proxy = raw["socksProxy"]
+ if raw.get("socksUsername"):
+ self.socks_username = raw["socksUsername"]
+ if raw.get("socksPassword"):
+ self.socks_password = raw["socksPassword"]
+ if raw.get("socksVersion"):
+ self.socks_version = raw["socksVersion"]
+Suggestion importance[1-10]: 6
__
Why: Relevant best practice - Add explicit validation/guards at integration boundaries before using caller-provided inputs.
[general] Refactor string formatting for better readability
✅ Refactor string formatting for better readability
Refactor the str.format() call into a "".join() with a list of keys to improve readability and remove the noqa: UP032 comment.
py/test/selenium/webdriver/common/typing_tests.py [301-316]
-element.send_keys(
- "abcd{}{}{}{}{}{}{}{}{}{}{}{}abcd".format( # noqa: UP032
- Keys.MULTIPLY,
- Keys.SUBTRACT,
- Keys.ADD,
- Keys.DECIMAL,
- Keys.SEPARATOR,
- Keys.NUMPAD0,
- Keys.NUMPAD1,
- Keys.NUMPAD2,
- Keys.F1,
- Keys.F2,
- Keys.F3,
- Keys.F4,
- )
-)
+keys_to_send = [
+ "abcd",
+ Keys.MULTIPLY,
+ Keys.SUBTRACT,
+ Keys.ADD,
+ Keys.DECIMAL,
+ Keys.SEPARATOR,
+ Keys.NUMPAD0,
+ Keys.NUMPAD1,
+ Keys.NUMPAD2,
+ Keys.F1,
+ Keys.F2,
+ Keys.F3,
+ Keys.F4,
+ "abcd",
+]
+element.send_keys("".join(keys_to_send))Suggestion importance[1-10]: 5
__
Why: The suggestion correctly identifies an opportunity to refactor a complex str.format() call, improving readability and eliminating the need for a noqa comment.
| PR 16782 (2025-12-22) |
[possible issue] Remove ineffective window style setting
✅ Remove ineffective window style setting
Remove the ineffective WindowStyle assignment, as it has no effect when UseShellExecute is false. The existing CreateNoWindow property already handles hiding the window.
dotnet/src/webdriver/DriverService.cs [254-260]
this.driverServiceProcess.StartInfo.UseShellExecute = false;
this.driverServiceProcess.StartInfo.CreateNoWindow = this.HideCommandPromptWindow;
-if (this.HideCommandPromptWindow)
-{
- this.driverServiceProcess.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
-}
-Suggestion importance[1-10]: 7
__
Why: The suggestion correctly identifies that the code added in the PR is ineffective because UseShellExecute is set to false, and it proposes removing the dead code, which is the optimal fix.
| PR 16780 (2025-12-22) |
[possible issue] fix exit code for push branch
✅ fix exit code for push branch
Modify the script's exit logic to ensure the job passes after a successful auto-format push. Move exit 1 into the else block and add exit 0 to the success path.
.github/workflows/ci-rbe.yml [42-54]
if [ "${{ github.event_name }}" = "pull_request" ] && \
[ "${{ github.event.pull_request.head.repo.full_name }}" = "${{ github.repository }}" ]; then
git config --local user.name "Selenium CI Bot"
git config --local user.email "[email protected]"
git add -A
git commit -m "Auto-format code"
git push
echo "::notice::Auto-formatted and pushed. New CI run will start."
+ exit 0
else
echo "::error::Code needs formatting. Run ./scripts/format.sh locally."
git diff
+ exit 1
fi
-exit 1Suggestion importance[1-10]: 9
__
Why: This suggestion correctly identifies a critical bug in the workflow logic where the job would fail even after successfully auto-formatting and pushing a fix. The fix is correct and essential for the new workflow to function as intended.
[high-level] CI workflow design is inefficient
✅ CI workflow design is inefficient
The CI workflow is inefficient as it runs test jobs concurrently with the formatting job, causing wasted resources when auto-formatting triggers a new CI run. It is recommended to make the test jobs dependent on the successful completion of the format job.
.github/workflows/ci-rbe.yml [21-64]
format:
name: Format
if: github.repository_owner == 'seleniumhq' && startsWith(github.head_ref, 'renovate/') != true
runs-on: ubuntu-latest
steps:
- name: Checkout source tree
uses: actions/checkout@v4
with:
ref: ${{ github.head_ref || github.ref }}
token: ${{ secrets.SELENIUM_CI_TOKEN || secrets.GITHUB_TOKEN }}
... (clipped 34 lines)jobs:
format:
# ...
steps:
- run: ./scripts/format.sh
- run: |
if [ ...changes... ]; then
git commit ...
git push
# The push triggers a new run, cancelling this one.
fi
exit 1
test:
# This job starts in parallel with 'format' and gets cancelled
# if 'format' pushes a change, wasting resources.
uses: ./.github/workflows/bazel.yml
# ...
jobs:
format:
# ...
steps:
- run: ./scripts/format.sh
- run: |
if [ ...changes... ]; then
git commit ...
git push
exit 1 # Fail job to prevent 'test' from running in this workflow
fi
# Succeeds if no changes
test:
needs: format # This ensures 'test' only runs after 'format' succeeds.
uses: ./.github/workflows/bazel.yml
# ...
Suggestion importance[1-10]: 8
__
Why: The suggestion correctly identifies a significant inefficiency in the CI workflow where the test job starts in parallel with the format job, leading to wasted resources when the latter pushes a commit and cancels the run.
[general] enable full-depth checkout
✅ enable full-depth checkout
Add fetch-depth: 0 to the checkout step to ensure a full Git history is available, which is necessary for the git push command to succeed reliably.
.github/workflows/ci-rbe.yml [26-30]
- name: Checkout source tree
uses: actions/checkout@v4
with:
+ fetch-depth: 0
ref: ${{ github.head_ref || github.ref }}
token: ${{ secrets.SELENIUM_CI_TOKEN || secrets.GITHUB_TOKEN }}Suggestion importance[1-10]: 8
__
Why: The suggestion correctly points out that a shallow clone (the default for actions/checkout) can cause the git push operation to fail. Adding fetch-depth: 0 is crucial for the reliability of the auto-formatting feature.
[general] Ensure full git history for push
✅ Ensure full git history for push
Add fetch-depth: 0 to the actions/checkout step to ensure the git push command has the full git history and can execute successfully.
.github/workflows/ci-rbe.yml [38-42]
- name: Checkout source tree
uses: actions/checkout@v4
with:
ref: ${{ github.head_ref || github.ref }}
token: ${{ secrets.SELENIUM_CI_TOKEN || secrets.GITHUB_TOKEN }}
+ fetch-depth: 0Suggestion importance[1-10]: 9
__
Why: This suggestion correctly identifies that git push requires the full git history, which is not fetched by default, and fetch-depth: 0 is the necessary fix to prevent the push from failing.
[high-level] Use a GitHub App for auto-commits
✅ Use a GitHub App for auto-commits
Replace the Personal Access Token (PAT) used for auto-commits with a more secure, narrowly-scoped GitHub App. This aligns with security best practices and minimizes risks from token exposure.
.github/workflows/ci-rbe.yml [42]
token: ${{ secrets.SELENIUM_CI_TOKEN || secrets.GITHUB_TOKEN }}# .github/workflows/ci-rbe.yml
commit-fixes:
steps:
- name: Checkout source tree
uses: actions/checkout@v4
with:
ref: ${{ github.head_ref }}
token: ${{ secrets.SELENIUM_CI_TOKEN || secrets.GITHUB_TOKEN }}
- name: Apply and commit fixes
run: |
...
git apply changes.patch
...
git commit -m "Auto-format code"
git push
# .github/workflows/ci-rbe.yml
commit-fixes:
steps:
- name: Generate GitHub App token
id: generate_token
uses: actions/create-github-app-token@v1
with:
app_id: ${{ secrets.YOUR_APP_ID }}
private_key: ${{ secrets.YOUR_APP_PRIVATE_KEY }}
- name: Checkout source tree
uses: actions/checkout@v4
with:
ref: ${{ github.head_ref }}
token: ${{ steps.generate_token.outputs.token }}
- name: Apply and commit fixes
...
Suggestion importance[1-10]: 8
__
Why: The suggestion correctly identifies a significant security risk with using a PAT for commits and proposes the industry best-practice solution of using a GitHub App, which greatly improves repository security.
| PR 16766 (2025-12-20) |
[possible issue] Restore missing BiDi tests
✅ Restore missing BiDi tests
Conditionally add the -bidi suffix to the bazel test command for non-Safari browsers to restore the BiDi tests that were removed during refactoring.
.github/workflows/ci-python.yml [116-117]
run: |
- bazel test --local_test_jobs 1 --flaky_test_attempts 3 --pin_browsers=true //py:common-${{ matrix.browser }} //py:test-${{ matrix.browser }}
+ BIDI=""
+ if [[ "${{ matrix.browser }}" != "safari" ]]; then
+ BIDI="-bidi"
+ fi
+ bazel test --local_test_jobs 1 --flaky_test_attempts 3 --pin_browsers=true //py:common-${{ matrix.browser }}$BIDI //py:test-${{ matrix.browser }}Suggestion importance[1-10]: 9
__
Why: The suggestion correctly identifies that the refactoring inadvertently removed the BiDi tests for Windows, leading to a reduction in test coverage. Restoring these tests is critical for maintaining CI integrity.
| PR 16747 (2025-12-18) |
[possible issue] Prevent race condition in server startup
✅ Prevent race condition in server startup
To prevent a race condition in start_local_http_server, use a threading.Event to ensure the server is fully initialized before the function returns.
py/test/selenium/webdriver/common/bidi_browser_tests.py [36-47]
def start_local_http_server(port, response_content=b"local server response", log_prefix=None):
class Handler(http.server.SimpleHTTPRequestHandler):
def do_GET(self):
if log_prefix:
print(f"[{log_prefix}] Intercepted request to: {self.path}")
self.send_response(200)
self.end_headers()
self.wfile.write(response_content)
server = socketserver.TCPServer(("localhost", port), Handler)
- threading.Thread(target=server.serve_forever, daemon=True).start()
+ started = threading.Event()
+
+ def serve():
+ started.set()
+ server.serve_forever()
+
+ threading.Thread(target=serve, daemon=True).start()
+ started.wait()
return serverSuggestion importance[1-10]: 7
__
Why: The suggestion correctly identifies a race condition in the new start_local_http_server function and proposes a robust solution using threading.Event to prevent flaky tests.
[general] Enable address reuse
✅ Enable address reuse
Set allow_reuse_address = True on the TCPServer to prevent "address already in use" errors during rapid test execution.
py/test/selenium/webdriver/common/bidi_browser_tests.py [45]
-server = socketserver.TCPServer(("localhost", port), Handler)
+class ReusableTCPServer(socketserver.TCPServer):
+ allow_reuse_address = True
+server = ReusableTCPServer(("localhost", port), Handler)
+Suggestion importance[1-10]: 6
__
Why: This suggestion improves the robustness of the test setup by setting allow_reuse_address, which helps prevent "address already in use" errors when tests are run in quick succession.
[learned best practice] Guarantee cleanup on early failures
✅ Guarantee cleanup on early failures
Ensure servers are shut down even if create_user_context/browsing_context.create fails by wrapping server creation and later steps in a broader try/finally (or a contextmanager) so cleanup always runs.
py/test/selenium/webdriver/common/bidi_browser_tests.py [174-212]
proxy_port = free_port()
+no_proxy_port = free_port()
fake_proxy_server = start_fake_proxy(port=proxy_port)
-
-# Start a local HTTP server for the no_proxy test
-no_proxy_port = free_port()
no_proxy_server = start_local_http_server(port=no_proxy_port, response_content=b"direct connection - not proxied")
-proxy = Proxy()
-proxy.proxy_type = ProxyType.MANUAL
-proxy.http_proxy = f"localhost:{proxy_port}"
-proxy.ssl_proxy = f"localhost:{proxy_port}"
-proxy.socks_proxy = f"localhost:{proxy_port}"
-proxy.socks_version = 5
-proxy.no_proxy = [f"localhost:{no_proxy_port}"]
+user_context = None
+try:
+ proxy = Proxy()
+ proxy.proxy_type = ProxyType.MANUAL
+ proxy.http_proxy = f"localhost:{proxy_port}"
+ proxy.ssl_proxy = f"localhost:{proxy_port}"
+ proxy.socks_proxy = f"localhost:{proxy_port}"
+ proxy.socks_version = 5
+ proxy.no_proxy = [f"localhost:{no_proxy_port}"]
-user_context = driver.browser.create_user_context(proxy=proxy)
+ user_context = driver.browser.create_user_context(proxy=proxy)
-# Create and switch to a new browsing context using this proxy
-bc = driver.browsing_context.create(type=WindowTypes.WINDOW, user_context=user_context)
-driver.switch_to.window(bc)
+ bc = driver.browsing_context.create(type=WindowTypes.WINDOW, user_context=user_context)
+ driver.switch_to.window(bc)
-try:
- # Visit no proxy site, it should bypass proxy
driver.get(f"http://localhost:{no_proxy_port}/")
body_text = driver.find_element(By.TAG_NAME, "body").text.lower()
assert "direct connection - not proxied" in body_text
- # Visit a site that should be proxied
driver.get("http://example.com/")
-
body_text = driver.find_element("tag name", "body").text
assert "proxied response" in body_text.lower()
-
finally:
- driver.browser.remove_user_context(user_context)
+ if user_context is not None:
+ driver.browser.remove_user_context(user_context)
no_proxy_server.shutdown()
no_proxy_server.server_close()
fake_proxy_server.shutdown()
fake_proxy_server.server_close()Suggestion importance[1-10]: 6
__
Why: Relevant best practice - Always wrap creation of external contexts/resources in try/finally and perform explicit cleanup in finally to prevent leaks when assertions/exceptions occur.
| PR 16736 (2025-12-15) |
[possible issue] Prevent build failures from conflicting conditions
✅ Prevent build failures from conflicting conditions
To prevent build failures from conflicting conditions in the select statement, use selects.config_setting_group to create a mutually exclusive condition. This ensures that building from source only occurs when the OS matches and neither the use_pinned_browser nor stamp flags are set.
common/manager/BUILD.bazel [12-20]
+load("//bazel:rules.bzl", "selects")
+
+selects.config_setting_group(
+ name = "build_sm_linux_from_source",
+ match_all = [
+ "//common:linux",
+ ],
+ match_none = [
+ "//common:use_pinned_browser",
+ "//common:stamp",
+ ],
+)
+
alias(
name = "selenium-manager-linux",
actual = select({
"//common:use_pinned_browser": "@download_sm_linux//file",
"//common:stamp": "@download_sm_linux//file",
- "//common:linux": "//rust:selenium-manager-linux",
+ ":build_sm_linux_from_source": "//rust:selenium-manager-linux",
"//conditions:default": "@download_sm_linux//file",
}),
)Suggestion importance[1-10]: 9
__
Why: The suggestion correctly identifies a critical flaw in the select logic that would cause build failures when multiple independent conditions are met, and it provides the correct, idiomatic Bazel fix using config_setting_group to ensure mutual exclusivity.
| PR 16730 (2025-12-15) |
[possible issue] Fix incorrect __new__ method signature
✅ Fix incorrect new method signature
**kwargs from the object.new call (changing it to object.new(cls)), which prevents the TypeError the suggestion warned about. However, it did not switch to super().new(cls) as suggested.
code diff:
def __new__(cls, *args, **kwargs):
if cls is LocalWebDriver:
raise TypeError(f"Only children of '{cls.__name__}' may be instantiated")
- return object.__new__(cls, *args, **kwargs)
+ return object.__new__(cls)**In LocalWebDriver.new, replace the incorrect call to object.new(cls, *args, kwargs) with super().new(cls) to prevent a TypeError during instantiation.
py/selenium/webdriver/common/webdriver.py [29-32]
def __new__(cls, *args, **kwargs):
if cls is LocalWebDriver:
raise TypeError(f"Only children of '{cls.__name__}' may be instantiated")
- return object.__new__(cls, *args, **kwargs)
+ return super().__new__(cls)Suggestion importance[1-10]: 9
__
Why: The suggestion correctly identifies a critical bug in the __new__ method that would cause a TypeError when instantiating any subclass of LocalWebDriver, making the core refactoring of the PR non-functional.
| PR 16717 (2025-12-11) |
[possible issue] Add missing JSON type property
✅ Add missing JSON type property
Add the missing Type property to the RemoteReferenceLocalValue abstract record to ensure correct JSON serialization for its derived classes.
dotnet/src/webdriver/BiDi/Script/LocalValue.cs [270-280]
-public abstract record RemoteReferenceLocalValue : LocalValue, IRemoteReference;
+public abstract record RemoteReferenceLocalValue : LocalValue, IRemoteReference
+{
+ [JsonInclude]
+ internal string Type { get; } = "remote";
+}
public sealed record SharedReferenceLocalValue(string SharedId) : RemoteReferenceLocalValue, ISharedReference
{
public Handle? Handle { get; set; }
}
public sealed record RemoteObjectReferenceLocalValue(Handle Handle) : RemoteReferenceLocalValue, IRemoteObjectReference
{
public string? SharedId { get; set; }
}Suggestion importance[1-10]: 9
__
Why: The suggestion correctly identifies that the new RemoteReferenceLocalValue and its derived classes are missing the Type property, which is essential for correct JSON serialization and is inconsistent with the pattern applied to all other types in this PR.
[learned best practice] Centralize type discriminator logic
✅ Centralize type discriminator logic
Avoid repeating the discriminator property across all records; centralize it in a shared base/helper to prevent drift and reduce boilerplate.
dotnet/src/webdriver/BiDi/Script/LocalValue.cs [284-362]
-public sealed record NumberLocalValue([property: JsonConverter(typeof(SpecialNumberConverter))] double Value) : PrimitiveProtocolLocalValue
+public abstract record TypedLocalValue(string Discriminator) : LocalValue
{
[JsonInclude]
- internal string Type { get; } = "number";
+ internal string Type { get; } = Discriminator;
+}
+public abstract record TypedPrimitiveLocalValue(string Discriminator) : PrimitiveProtocolLocalValue
+{
+ [JsonInclude]
+ internal string Type { get; } = Discriminator;
+}
+
+public sealed record NumberLocalValue([property: JsonConverter(typeof(SpecialNumberConverter))] double Value)
+ : TypedPrimitiveLocalValue("number")
+{
public static explicit operator NumberLocalValue(double n) => new NumberLocalValue(n);
}
-public sealed record StringLocalValue(string Value) : PrimitiveProtocolLocalValue
-{
- [JsonInclude]
- internal string Type { get; } = "string";
-}
+public sealed record StringLocalValue(string Value) : TypedPrimitiveLocalValue("string");
+public sealed record NullLocalValue() : TypedPrimitiveLocalValue("null");
+public sealed record UndefinedLocalValue() : TypedPrimitiveLocalValue("undefined");
+public sealed record BooleanLocalValue(bool Value) : TypedPrimitiveLocalValue("boolean");
+public sealed record BigIntLocalValue(string Value) : TypedPrimitiveLocalValue("bigint");
-public sealed record NullLocalValue : PrimitiveProtocolLocalValue
-{
- [JsonInclude]
- internal string Type { get; } = "null";
-}
+public sealed record ChannelLocalValue(ChannelProperties Value) : TypedLocalValue("channel");
+public sealed record ArrayLocalValue(IEnumerable<LocalValue> Value) : TypedLocalValue("array");
+public sealed record DateLocalValue(string Value) : TypedLocalValue("date");
+public sealed record MapLocalValue(IEnumerable<IEnumerable<LocalValue>> Value) : TypedLocalValue("map");
+public sealed record ObjectLocalValue(IEnumerable<IEnumerable<LocalValue>> Value) : TypedLocalValue("object");
+public sealed record RegExpLocalValue(RegExpValue Value) : TypedLocalValue("regexp");
+public sealed record SetLocalValue(IEnumerable<LocalValue> Value) : TypedLocalValue("set");
-public sealed record UndefinedLocalValue : PrimitiveProtocolLocalValue
-{
- [JsonInclude]
- internal string Type { get; } = "undefined";
-}
-
-public sealed record BooleanLocalValue(bool Value) : PrimitiveProtocolLocalValue
-{
- [JsonInclude]
- internal string Type { get; } = "boolean";
-}
-
-public sealed record BigIntLocalValue(string Value) : PrimitiveProtocolLocalValue
-{
- [JsonInclude]
- internal string Type { get; } = "bigint";
-}
-
-public sealed record ChannelLocalValue(ChannelProperties Value) : LocalValue
-{
- [JsonInclude]
- internal string Type { get; } = "channel";
-}
-
-public sealed record ArrayLocalValue(IEnumerable<LocalValue> Value) : LocalValue
-{
- [JsonInclude]
- internal string Type { get; } = "array";
-}
-
-public sealed record DateLocalValue(string Value) : LocalValue
-{
- [JsonInclude]
- internal string Type { get; } = "date";
-}
-
-public sealed record MapLocalValue(IEnumerable<IEnumerable<LocalValue>> Value) : LocalValue
-{
- [JsonInclude]
- internal string Type { get; } = "map";
-}
-
-public sealed record ObjectLocalValue(IEnumerable<IEnumerable<LocalValue>> Value) : LocalValue
-{
- [JsonInclude]
- internal string Type { get; } = "object";
-}
-
-public sealed record RegExpLocalValue(RegExpValue Value) : LocalValue
-{
- [JsonInclude]
- internal string Type { get; } = "regexp";
-}
-
-public sealed record SetLocalValue(IEnumerable<LocalValue> Value) : LocalValue
-{
- [JsonInclude]
- internal string Type { get; } = "set";
-}
-Suggestion importance[1-10]: 6
__
Why: Relevant best practice - Replace ad-hoc duplication with shared helpers/utilities to centralize logic and reduce repetition.
| PR 16711 (2025-12-09) |
[learned best practice] Validate environment variable path
✅ Validate environment variable path
Validate that SE_MANAGER_PATH points to an existing executable file and ignore or error out if it does not.
py/selenium/webdriver/common/selenium_manager.py [81-83]
if (env_path := os.getenv("SE_MANAGER_PATH")) is not None:
logger.debug(f"Selenium Manager set by env SE_MANAGER_PATH to: {env_path}")
- path = Path(env_path)
+ env_path = env_path.strip()
+ candidate = Path(env_path)
+ if candidate.is_file():
+ path = candidate
+ else:
+ logger.warning(f"SE_MANAGER_PATH does not point to a file: {env_path}")Suggestion importance[1-10]: 6
__
Why: Relevant best practice - Validate and sanitize external inputs such as environment variables before use to avoid invalid paths or injection issues.
| PR 16705 (2025-12-08) |
[learned best practice] Return unmodifiable serialized map
✅ Return unmodifiable serialized map
Return an unmodifiable copy to prevent callers from mutating the returned map; this preserves immutability of serialized state.
java/src/org/openqa/selenium/bidi/emulation/ScreenOrientation.java [46-51]
public Map<String, Object> toMap() {
Map<String, Object> map = new HashMap<>();
map.put("natural", natural.toString());
map.put("type", type.toString());
- return map;
+ return java.util.Collections.unmodifiableMap(map);
}Suggestion importance[1-10]: 6
__
Why: Relevant best practice - Use defensive copying and immutability for maps returned by accessors to avoid external mutation.
[general] Remove inconsistent and unnecessary page reload
✅ Remove inconsistent and unnecessary page reload
Remove the inconsistent and unnecessary page reload after the first setScreenOrientationOverride call to make the test accurately reflect the expected dynamic behavior.
java/test/org/openqa/selenium/bidi/emulation/SetScreenOrientationOverrideTest.java [68-93]
-// Reload the page to apply the orientation change
-context.navigate(url, ReadinessState.COMPLETE);
-
Map<String, Object> currentOrientation = getScreenOrientation(contextId);
assertThat(currentOrientation.get("type")).isEqualTo("landscape-primary");
assertThat(currentOrientation.get("angle")).isEqualTo(0);
// Set portrait-secondary orientation
ScreenOrientation portraitOrientation =
new ScreenOrientation(
ScreenOrientationNatural.PORTRAIT, ScreenOrientationType.PORTRAIT_SECONDARY);
emulation.setScreenOrientationOverride(
new SetScreenOrientationOverrideParameters(portraitOrientation)
.contexts(List.of(contextId)));
currentOrientation = getScreenOrientation(contextId);
assertThat(currentOrientation.get("type")).isEqualTo("portrait-secondary");
assertThat(currentOrientation.get("angle")).isEqualTo(180);
// Clear the override
emulation.setScreenOrientationOverride(
new SetScreenOrientationOverrideParameters(null).contexts(List.of(contextId)));
currentOrientation = getScreenOrientation(contextId);
assertThat(currentOrientation.get("type")).isEqualTo(initialOrientation.get("type"));
assertThat(currentOrientation.get("angle")).isEqualTo(initialOrientation.get("angle"));Suggestion importance[1-10]: 7
__
Why: The suggestion correctly identifies an inconsistent and likely unnecessary page reload in the test, improving test correctness and consistency.
[general] Use idiomatic null checks for consistency
✅ Use idiomatic null checks for consistency
In the ScreenOrientation constructor, replace manual null checks with the idiomatic Require.nonNull() for consistency with the rest of the codebase.
java/src/org/openqa/selenium/bidi/emulation/ScreenOrientation.java [27-36]
-public ScreenOrientation(ScreenOrientationNatural natural, ScreenOrientationType type) {
- if (natural == null) {
- throw new IllegalArgumentException("Natural orientation cannot be null");
+import org.openqa.selenium.internal.Require;
+
+public class ScreenOrientation {
+ // ...
+ public ScreenOrientation(ScreenOrientationNatural natural, ScreenOrientationType type) {
+ this.natural = Require.nonNull("Natural orientation", natural);
+ this.type = Require.nonNull("Orientation type", type);
}
- if (type == null) {
- throw new IllegalArgumentException("Orientation type cannot be null");
- }
- this.natural = natural;
- this.type = type;
+ // ...
}Suggestion importance[1-10]: 4
__
Why: The suggestion improves code consistency by proposing the use of the idiomatic Require.nonNull() helper, aligning the new class with existing codebase conventions.
[possible issue] Fetch screen orientation properties atomically
✅ Fetch screen orientation properties atomically
Refactor the getScreenOrientation method to fetch screen orientation type and angle in a single, atomic JavaScript execution to prevent potential race conditions.
java/test/org/openqa/selenium/bidi/emulation/SetScreenOrientationOverrideTest.java [36-44]
private Map<String, Object> getScreenOrientation(String context) {
driver.switchTo().window(context);
JavascriptExecutor executor = (JavascriptExecutor) driver;
- String type = (String) executor.executeScript("return screen.orientation.type;");
- Number angle = (Number) executor.executeScript("return screen.orientation.angle;");
+ Map<String, Object> orientation =
+ (Map<String, Object>)
+ executor.executeScript(
+ "return { type: screen.orientation.type, angle: screen.orientation.angle };");
- return Map.of("type", type, "angle", angle.intValue());
+ return Map.of("type", orientation.get("type"), "angle", ((Number) orientation.get("angle")).intValue());
}Suggestion importance[1-10]: 6
__
Why: The suggestion correctly identifies a potential race condition in a test helper method and proposes a more robust, atomic operation which also slightly improves performance.
| PR 16702 (2025-12-08) |
[high-level] Implement the full network conditions spec
✅ Implement the full network conditions spec
The set_network_conditions method should be extended to support the full WebDriver BiDi specification, which includes emulating various network speeds with specific latency and throughput, not just the 'offline' state.
py/selenium/webdriver/common/bidi/emulation.py [435-471]
def set_network_conditions(
self,
offline: bool | None = None,
contexts: list[str] | None = None,
user_contexts: list[str] | None = None,
) -> None:
"""Set network conditions for the given contexts or user contexts.
Args:
offline: True to emulate offline network conditions, False or None to disable.
... (clipped 27 lines)class Emulation:
def set_network_conditions(
self,
offline: bool | None = None,
contexts: list[str] | None = None,
user_contexts: list[str] | None = None,
) -> None:
# ...
params: dict[str, Any] = {}
if offline:
params["networkConditions"] = {"type": "offline"}
else:
# if offline is False or None, then clear the override
params["networkConditions"] = None
# ...
self.conn.execute(command_builder("emulation.setNetworkConditions", params))class Emulation:
def set_network_conditions(
self,
offline: bool = False,
latency: int | None = None,
download_throughput: int | None = None,
upload_throughput: int | None = None,
contexts: list[str] | None = None,
user_contexts: list[str] | None = None,
) -> None:
# ...
params: dict[str, Any] = {}
if offline:
params["networkConditions"] = {"type": "offline"}
elif latency is not None or download_throughput is not None or upload_throughput is not None:
conditions = {"type": "network"}
if latency is not None: conditions["latency"] = latency
# ... add other throughput values
params["networkConditions"] = conditions
else:
params["networkConditions"] = None
# ...
self.conn.execute(command_builder("emulation.setNetworkConditions", params))Suggestion importance[1-10]: 7
__
Why: The suggestion correctly identifies that the PR only partially implements the WebDriver BiDi spec for set_network_conditions, omitting network speed and latency emulation, which is a significant part of the feature.
[learned best practice] Always cleanup network overrides
✅ Always cleanup network overrides
Wrap the network conditions override with try/finally and reset in finally to guarantee cleanup even if assertions fail.
py/test/selenium/webdriver/common/bidi_emulation_tests.py [584-598]
@pytest.mark.xfail_firefox
@pytest.mark.xfail_edge
def test_set_network_conditions_offline_with_context(driver, pages):
context_id = driver.current_window_handle
driver.browsing_context.navigate(context_id, pages.url("formPage.html"), wait="complete")
assert is_online(driver, context_id) is True
- # Set offline
- driver.emulation.set_network_conditions(offline=True, contexts=[context_id])
- assert is_online(driver, context_id) is False
+ try:
+ # Set offline
+ driver.emulation.set_network_conditions(offline=True, contexts=[context_id])
+ assert is_online(driver, context_id) is False
+ finally:
+ # Always reset
+ driver.emulation.set_network_conditions(offline=None, contexts=[context_id])
- # Reset
- driver.emulation.set_network_conditions(offline=None, contexts=[context_id])
assert is_online(driver, context_id) is TrueSuggestion importance[1-10]: 6
__
Why: Relevant best practice - Pattern 1: Ensure external contexts/resources are cleaned up using try/finally to prevent leaks.
[general] Use explicit boolean for test clarity
✅ Use explicit boolean for test clarity
In the test test_set_network_conditions_offline_with_context, use offline=False instead of offline=None to explicitly reset the network conditions, which improves test readability.
py/test/selenium/webdriver/common/bidi_emulation_tests.py [596-598]
# Reset
-driver.emulation.set_network_conditions(offline=None, contexts=[context_id])
+driver.emulation.set_network_conditions(offline=False, contexts=[context_id])
assert is_online(driver, context_id) is TrueSuggestion importance[1-10]: 4
__
Why: The suggestion to use offline=False instead of offline=None for resetting network conditions is a valid point for improving test clarity and explicitness, though the current implementation with None is functionally correct.
| PR 16678 (2025-12-04) |
[high-level] Avoid modifying the core SessionId class
✅ Avoid modifying the core SessionId class
**
-
- Sets the reason why this session was closed. Once set, indicates the session is no longer
-
- active.
-
-
- @param reason The reason for session closure
- */
- public void setCloseReason(String reason) {
- this.closeReason = reason;
- }
- /**
-
- Gets the reason why this session was closed, if any.
-
-
- @return The close reason, or null if the session is still active
- */
- public @Nullable String getCloseReason() {
- return closeReason;
- }
- /**
-
- Checks if this session has been closed.
-
-
- @return true if the session has a close reason set, false otherwise
- */
- public boolean isClosed() {
- return closeReason != null; }
@Override @@ -85,17 +53,7 @@ }
private Object toJson() {
- // For backward compatibility, serialize as string when there's no closeReason
- // This ensures SessionId works properly in URLs and simple contexts
- if (closeReason == null) {
-
return opaqueKey; - }
- // When there is a closeReason, serialize as Map to preserve the metadata
- Map<String, Object> json = new HashMap<>();
- json.put("value", opaqueKey);
- json.put("closeReason", closeReason);
- return json;
- return opaqueKey; }
private static SessionId fromJson(Object raw) { @@ -103,18 +61,6 @@ return new SessionId(String.valueOf(raw)); }
- if (raw instanceof Map) {
-
Map<?, ?> map = (Map<?, ?>) raw; -
if (map.get("value") instanceof String) { -
SessionId sessionId = new SessionId(String.valueOf(map.get("value"))); -
// Restore closeReason if present -
if (map.get("closeReason") instanceof String) { -
sessionId.setCloseReason(String.valueOf(map.get("closeReason"))); -
} -
return sessionId; -
} - }
- throw new JsonException("Unable to coerce session id from " + raw); } }
@@ -34,21 +34,23 @@ }
public SessionClosedEvent(SessionId id, SessionClosedReason reason) {
- super(SESSION_CLOSED, markSessionId(id, reason));
- super(SESSION_CLOSED, new SessionClosedData(id, reason)); Require.nonNull("Session ID", id); Require.nonNull("Reason", reason); }
- // Helper method to mark the SessionId before passing to Event constructor
- private static SessionId markSessionId(SessionId id, SessionClosedReason reason) {
- id.setCloseReason(reason.getReasonText());
- return id;
- // Standard listener method that provides access to both SessionId and reason
- public static EventListener listener(Consumer handler) {
- Require.nonNull("Handler", handler);
- return new EventListener<>(SESSION_CLOSED, SessionClosedData.class, handler); }
- // Standard listener method following the Event pattern
- public static EventListener listener(Consumer handler) {
- // Convenience method for listeners that only care about the SessionId
- public static EventListener sessionListener(Consumer handler) { Require.nonNull("Handler", handler);
- return new EventListener<>(SESSION_CLOSED, SessionId.class, handler);
- return new EventListener<>(
-
} }
SESSION_CLOSED, SessionClosedData.class, data -> handler.accept(data.getSessionId()));
**Avoid modifying the core SessionId class to include session closure state and changing its JSON format. Instead, manage the closure reason separately to prevent a significant breaking change.**
### Examples:
java/src/org/openqa/selenium/remote/SessionId.java [33-115]
```java
private @Nullable String closeReason;
public SessionId(UUID uuid) {
this(Require.nonNull("Session ID key", uuid).toString());
}
public SessionId(String opaqueKey) {
this.opaqueKey = Require.nonNull("Session ID key", opaqueKey);
this.closeReason = null; // Session is alive initially
}
... (clipped 73 lines)
java/src/org/openqa/selenium/grid/data/SessionClosedEvent.java [36-46]
public SessionClosedEvent(SessionId id, SessionClosedReason reason) {
super(SESSION_CLOSED, markSessionId(id, reason));
Require.nonNull("Session ID", id);
Require.nonNull("Reason", reason);
}
// Helper method to mark the SessionId before passing to Event constructor
private static SessionId markSessionId(SessionId id, SessionClosedReason reason) {
id.setCloseReason(reason.getReasonText());
return id;
... (clipped 1 lines)// In SessionId.java
class SessionId {
private final String opaqueKey;
private @Nullable String closeReason;
public void setCloseReason(String reason) { ... }
private Object toJson() {
if (closeReason == null) {
return opaqueKey; // Returns a string
}
// Returns a map, which is a breaking change
return Map.of("value", opaqueKey, "closeReason", closeReason);
}
}
// In SessionClosedEvent.java
class SessionClosedEvent extends Event {
public SessionClosedEvent(SessionId id, SessionClosedReason reason) {
super(SESSION_CLOSED, markSessionId(id, reason)); // Modifies SessionId state
}
}// In SessionId.java (reverted to original state)
class SessionId {
private final String opaqueKey;
private String toJson() {
return opaqueKey; // Always returns a string
}
}
// In a new file, e.g., SessionClosure.java
class SessionClosure {
private final SessionId sessionId;
private final SessionClosedReason reason;
}
// In SessionClosedEvent.java
class SessionClosedEvent extends Event {
public SessionClosedEvent(SessionId id, SessionClosedReason reason) {
super(SESSION_CLOSED, new SessionClosure(id, reason)); // Pass a dedicated object
}
}Suggestion importance[1-10]: 9
__
Why: This suggestion correctly identifies a significant, potentially breaking change to the core SessionId class and its JSON serialization, proposing a safer, non-breaking alternative that achieves the same goal.
[learned best practice] Make SessionId immutable
✅ Make SessionId immutable
**
-
- Sets the reason why this session was closed. Once set, indicates the session is no longer
-
- active.
-
-
- @param reason The reason for session closure
- */
- public void setCloseReason(String reason) {
- this.closeReason = reason;
- }
- /**
-
- Gets the reason why this session was closed, if any.
-
-
- @return The close reason, or null if the session is still active
- */
- public @Nullable String getCloseReason() {
- return closeReason;
- }
- /**
-
- Checks if this session has been closed.
-
-
- @return true if the session has a close reason set, false otherwise
- */
- public boolean isClosed() {
- return closeReason != null; }
@Override @@ -85,17 +53,7 @@ }
private Object toJson() {
- // For backward compatibility, serialize as string when there's no closeReason
- // This ensures SessionId works properly in URLs and simple contexts
- if (closeReason == null) {
-
return opaqueKey; - }
- // When there is a closeReason, serialize as Map to preserve the metadata
- Map<String, Object> json = new HashMap<>();
- json.put("value", opaqueKey);
- json.put("closeReason", closeReason);
- return json;
- return opaqueKey; }
private static SessionId fromJson(Object raw) { @@ -103,18 +61,6 @@ return new SessionId(String.valueOf(raw)); }
- if (raw instanceof Map) {
-
Map<?, ?> map = (Map<?, ?>) raw; -
if (map.get("value") instanceof String) { -
SessionId sessionId = new SessionId(String.valueOf(map.get("value"))); -
// Restore closeReason if present -
if (map.get("closeReason") instanceof String) { -
sessionId.setCloseReason(String.valueOf(map.get("closeReason"))); -
} -
return sessionId; -
} - }
- throw new JsonException("Unable to coerce session id from " + raw); }
**Avoid making SessionId mutable and externally settable; carry close reason separately or return a defensive copy during serialization to prevent unintended mutation and cross-component coupling.**
[java/src/org/openqa/selenium/remote/SessionId.java [30-120]](https://github.com/SeleniumHQ/selenium/pull/16678/files#diff-c5b09ea9aa7ec16a444542ec011f3305f89d73aee6239e9cb1f5f314a7426ca2R30-R120)
```diff
-public class SessionId implements Serializable {
+public final class SessionId implements Serializable {
private final String opaqueKey;
- private @Nullable String closeReason;
-...
- public void setCloseReason(String reason) {
- this.closeReason = reason;
+ private final @Nullable String closeReason;
+
+ public SessionId(UUID uuid) {
+ this(Require.nonNull("Session ID key", uuid).toString(), null);
}
-...
+
+ public SessionId(String opaqueKey) {
+ this(opaqueKey, null);
+ }
+
+ private SessionId(String opaqueKey, @Nullable String closeReason) {
+ this.opaqueKey = Require.nonNull("Session ID key", opaqueKey);
+ this.closeReason = closeReason;
+ }
+
+ public SessionId withCloseReason(String reason) {
+ return new SessionId(this.opaqueKey, reason);
+ }
+
+ public @Nullable String getCloseReason() {
+ return closeReason;
+ }
+
+ public boolean isClosed() {
+ return closeReason != null;
+ }
+
private Object toJson() {
- // For backward compatibility, serialize as string when there's no closeReason
- // This ensures SessionId works properly in URLs and simple contexts
if (closeReason == null) {
return opaqueKey;
}
-
- // When there is a closeReason, serialize as Map to preserve the metadata
Map<String, Object> json = new HashMap<>();
json.put("value", opaqueKey);
json.put("closeReason", closeReason);
return json;
}
+ private static SessionId fromJson(Object raw) {
+ if (raw instanceof String) {
+ return new SessionId(String.valueOf(raw));
+ }
+ if (raw instanceof Map) {
+ Map<?, ?> map = (Map<?, ?>) raw;
+ if (map.get("value") instanceof String) {
+ String value = String.valueOf(map.get("value"));
+ String reason = map.get("closeReason") instanceof String ? String.valueOf(map.get("closeReason")) : null;
+ return new SessionId(value, reason);
+ }
+ }
+ throw new JsonException("Unable to coerce session id from " + raw);
+ }
+}
+
Suggestion importance[1-10]: 6
__
Why: Relevant best practice - Use defensive copying and avoid exposing or mutating shared internal state through objects passed across system boundaries.
[learned best practice] Remove side-effectful mutation
✅ Remove side-effectful mutation
Avoid mutating SessionId in a helper; instead create a new value or event payload carrying the reason to prevent hidden side effects.
java/src/org/openqa/selenium/grid/data/SessionClosedEvent.java [36-46]
public SessionClosedEvent(SessionId id, SessionClosedReason reason) {
- super(SESSION_CLOSED, markSessionId(id, reason));
Require.nonNull("Session ID", id);
Require.nonNull("Reason", reason);
+ super(SESSION_CLOSED, id.withCloseReason(reason.getReasonText()));
}
-// Helper method to mark the SessionId before passing to Event constructor
-private static SessionId markSessionId(SessionId id, SessionClosedReason reason) {
- id.setCloseReason(reason.getReasonText());
- return id;
-}
-Suggestion importance[1-10]: 5
__
Why: Relevant best practice - Replace ad-hoc duplication with shared helpers/utilities and avoid side effects in helper methods.
[general] Return an immutable set copy
✅ Return an immutable set copy
In getSessionsByUri, replace new HashSet<>(result) with Set.copyOf(result) to return an immutable copy of the set, which is a safer programming practice.
java/src/org/openqa/selenium/grid/sessionmap/local/LocalSessionMap.java [293-297]
public Set<SessionId> getSessionsByUri(URI uri) {
Set<SessionId> result = sessionsByUri.get(uri);
// Return a copy to prevent concurrent modification issues
- return (result != null && !result.isEmpty()) ? new HashSet<>(result) : Set.of();
+ return (result != null && !result.isEmpty()) ? Set.copyOf(result) : Set.of();
}Suggestion importance[1-10]: 5
__
Why: This is a good suggestion that improves code robustness by returning an immutable collection, which prevents callers from making unintended modifications and aligns with best practices for encapsulation.
| PR 16670 (2025-12-03) |
[possible issue] Hydrate event arguments after deserialization
✅ Hydrate event arguments after deserialization
Add hydration logic for deserialized event arguments in the ProcessReceivedMessage method, similar to how command results are handled, to prevent runtime errors.
dotnet/src/webdriver/BiDi/Broker.cs [282-285]
+if (commandResult is IBiDiHydratable bidiHydratable)
+{
+ bidiHydratable.Hydrate(_bidi);
+}
-Suggestion importance[1-10]: 9
__
Why: The suggestion correctly identifies a critical bug where event arguments are not hydrated, which would lead to NullReferenceException at runtime, defeating a major goal of the PR.
[learned best practice] Eliminate dead JSON options
✅ Eliminate dead JSON options
Remove the now-unused JsonSerializerOptions method and inline config; prefer the new source-generated contexts to avoid dead code and duplication.
dotnet/src/webdriver/BiDi/BiDi.cs [95-117]
-return new JsonSerializerOptions
-{
- //PropertyNameCaseInsensitive = true,
- //PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
- //DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
+// Remove GetJsonOptions() entirely and references to it.
+// Modules now use source-generated contexts (e.g., BrowsingContextJsonSerializerContext.Default)
- // BiDi returns special numbers such as "NaN" as strings
- // Additionally, -0 is returned as a string "-0"
- NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals | JsonNumberHandling.AllowReadingFromString,
- Converters =
- {
- //new BrowsingContextConverter(),
- //new BrowserUserContextConverter(),
- //new CollectorConverter(),
- //new InterceptConverter(),
- //new HandleConverter(),
- //new InternalIdConverter(),
- //new PreloadScriptConverter(),
- //new RealmConverter(),
- new DateTimeOffsetConverter(),
- //new WebExtensionConverter(),
- }
-};
-Suggestion importance[1-10]: 6
__
Why: Relevant best practice - Pattern 4: Centralize shared configuration logic to avoid duplication and ensure consistency (e.g., JSON options/converters).
[high-level] Add runtime checks for hydration
✅ Add runtime checks for hydration
To prevent NullReferenceExceptions, add a runtime check to the BiDi property getter on objects using the new hydration pattern. This check should throw an InvalidOperationException if the property is accessed before it has been hydrated.
dotnet/src/webdriver/BiDi/Browser/UserContext.cs [38]
public BiDi BiDi { get; internal set; }dotnet/src/webdriver/BiDi/BrowsingContext/BrowsingContext.cs [45]
public BiDi BiDi { get; internal set; }// In dotnet/src/webdriver/BiDi/Browser/UserContext.cs
public sealed class UserContext : IAsyncDisposable
{
// ... constructor ...
[JsonIgnore]
public BiDi BiDi { get; internal set; }
public Task RemoveAsync()
{
// This will throw NullReferenceException if BiDi is not hydrated
return BiDi.Browser.RemoveUserContextAsync(this);
}
// ...
}// In dotnet/src/webdriver/BiDi/Browser/UserContext.cs
public sealed class UserContext : IAsyncDisposable
{
private BiDi? _bidi;
// ... constructor ...
[JsonIgnore]
public BiDi BiDi
{
get => _bidi ?? throw new InvalidOperationException("BiDi instance has not been hydrated.");
internal set => _bidi = value;
}
public Task RemoveAsync()
{
return BiDi.Browser.RemoveUserContextAsync(this);
}
// ...
}Suggestion importance[1-10]: 8
__
Why: The suggestion correctly identifies a significant design weakness in the new hydration pattern, preventing potential NullReferenceExceptions and improving API robustness with explicit, fail-fast checks.
| PR 16664 (2025-12-01) |
[possible issue] Fix race condition with thread-safe initialization
✅ Fix race condition with thread-safe initialization
The lazy initialization using ??= is not thread-safe and can cause a race condition. To fix this, use Interlocked.CompareExchange for a lock-free, thread-safe initialization and mark the backing fields as volatile.
dotnet/src/webdriver/BiDi/BrowsingContext/BrowsingContext.cs [34-58]
-private BrowsingContextLogModule? _logModule;
-private BrowsingContextNetworkModule? _networkModule;
-private BrowsingContextScriptModule? _scriptModule;
-private BrowsingContextStorageModule? _storageModule;
-private BrowsingContextInputModule? _inputModule;
+private volatile BrowsingContextLogModule? _logModule;
+private volatile BrowsingContextNetworkModule? _networkModule;
+private volatile BrowsingContextScriptModule? _scriptModule;
+private volatile BrowsingContextStorageModule? _storageModule;
+private volatile BrowsingContextInputModule? _inputModule;
internal string Id { get; }
[JsonIgnore]
public BiDi BiDi { get; }
[JsonIgnore]
-public BrowsingContextLogModule Log => _logModule ??= new BrowsingContextLogModule(this, BiDi.Log);
+public BrowsingContextLogModule Log => _logModule ?? Interlocked.CompareExchange(ref _logModule, new BrowsingContextLogModule(this, BiDi.Log), null) ?? _logModule!;
[JsonIgnore]
-public BrowsingContextNetworkModule Network => _networkModule ??= new BrowsingContextNetworkModule(this, BiDi.Network);
+public BrowsingContextNetworkModule Network => _networkModule ?? Interlocked.CompareExchange(ref _networkModule, new BrowsingContextNetworkModule(this, BiDi.Network), null) ?? _networkModule!;
[JsonIgnore]
-public BrowsingContextScriptModule Script => _scriptModule ??= new BrowsingContextScriptModule(this, BiDi.Script);
+public BrowsingContextScriptModule Script => _scriptModule ?? Interlocked.CompareExchange(ref _scriptModule, new BrowsingContextScriptModule(this, BiDi.Script), null) ?? _scriptModule!;
[JsonIgnore]
-public BrowsingContextStorageModule Storage => _storageModule ??= new BrowsingContextStorageModule(this, BiDi.Storage);
+public BrowsingContextStorageModule Storage => _storageModule ?? Interlocked.CompareExchange(ref _storageModule, new BrowsingContextStorageModule(this, BiDi.Storage), null) ?? _storageModule!;
[JsonIgnore]
-public BrowsingContextInputModule Input => _inputModule ??= new BrowsingContextInputModule(this, BiDi.InputModule);
+public BrowsingContextInputModule Input => _inputModule ?? Interlocked.CompareExchange(ref _inputModule, new BrowsingContextInputModule(this, BiDi.InputModule), null) ?? _inputModule!;Suggestion importance[1-10]: 9
__
Why: The suggestion correctly identifies that the refactoring from Lazy to ??= introduces a race condition, as ??= is not an atomic operation. This is a critical regression in thread safety, and the proposed fix using Interlocked.CompareExchange is a correct and robust pattern for thread-safe lazy initialization.
| PR 16658 (2025-11-30) |
[general] Remove statement with no effect
✅ Remove statement with no effect
In the generate_from_json method, remove the line expr which is a statement with no effect and likely a debugging leftover.
def generate_from_json(self, dict_):
"""Generate the code that creates an instance from a JSON dict named `dict_`."""
if self.items:
if self.items.ref:
py_ref = ref_to_python(self.items.ref)
expr = f"[{py_ref}.from_json(i) for i in {dict_}['{self.name}']]"
- expr
else:
cons = CdpPrimitiveType.get_constructor(self.items.type, "i")
expr = f"[{cons} for i in {dict_}['{self.name}']]"
else:
if self.ref:
py_ref = ref_to_python(self.ref)
expr = f"{py_ref}.from_json({dict_}['{self.name}'])"
else:
expr = CdpPrimitiveType.get_constructor(self.type, f"{dict_}['{self.name}']")
if self.optional:
expr = f"{expr} if '{self.name}' in {dict_} else None"
return exprSuggestion importance[1-10]: 3
__
Why: The suggestion correctly identifies a useless statement on line 300 that has no effect on the program's logic, and removing it improves code clarity.
| PR 16650 (2025-11-29) |
[possible issue] Make the Dispose method idempotent
✅ Make the Dispose method idempotent
Make the Dispose method idempotent by using a private flag. This prevents returning the same buffer to the ArrayPool multiple times, which would otherwise cause a critical error.
dotnet/src/webdriver/BiDi/WebSocketTransport.cs [91-97]
+private bool _disposed;
+
public void Dispose()
{
+ if (_disposed)
+ {
+ return;
+ }
+
_webSocket.Dispose();
_sharedMemoryStream.Dispose();
_socketSendSemaphoreSlim.Dispose();
ArrayPool<byte>.Shared.Return(_receiveBuffer);
+ _disposed = true;
}Suggestion importance[1-10]: 9
__
Why: The suggestion correctly identifies a critical issue where calling Dispose multiple times would corrupt the ArrayPool by returning the same buffer more than once, potentially leading to memory corruption.
| PR 16647 (2025-11-27) |
[general] Improve window handle matching logic
✅ Improve window handle matching logic
In findTarget, change the filter logic from windowHandle.contains(id.toString()) to windowHandle.endsWith(id.toString()) for a more precise and robust matching of the window handle to the target ID.
java/src/org/openqa/selenium/devtools/DevTools.java [214-228]
private TargetID findTarget(String windowHandle) {
// Figure out the targets.
List<TargetInfo> infos =
connection.sendAndWait(cdpSession, getDomains().target().getTargets(), timeout);
// Grab the first "page" type, and glom on to that.
// Find out which one might be the current one
// (using given window handle like "CDwindow-24426957AC62D8BC83E58C184C38AF2D")
return infos.stream()
.filter(info -> "page".equals(info.getType()))
.map(TargetInfo::getTargetId)
- .filter(id -> windowHandle == null || windowHandle.contains(id.toString()))
+ .filter(id -> windowHandle == null || windowHandle.endsWith(id.toString()))
.findAny()
.orElseThrow(() -> new DevToolsException("Unable to find target id of a page"));
}Suggestion importance[1-10]: 4
__
Why: The suggestion correctly points out that using endsWith is more precise than contains for matching a windowHandle to a TargetID, making the implementation more robust against potential edge cases.
| PR 16646 (2025-11-27) |
[possible issue] Clean up temporary test directories
✅ Clean up temporary test directories
Add cleanup logic to the finally block to delete the temporary directory created during the test. This prevents resource leaks on the file system.
java/test/org/openqa/selenium/bidi/browser/BrowserCommandsTest.java [113-142]
Path tmpDir = TemporaryFilesystem.getDefaultTmpFS().createTempDir("downloads", "test").toPath();
try {
browser.setDownloadBehavior(new SetDownloadBehaviorParameters(true, tmpDir));
BrowsingContext context = new BrowsingContext(driver, driver.getWindowHandle());
String url = appServer.whereIs("downloads/download.html");
context.navigate(url, ReadinessState.COMPLETE);
driver.findElement(By.id("file-1")).click();
new WebDriverWait(driver, Duration.ofSeconds(5))
.until(
d -> {
try {
return Files.list(tmpDir)
.anyMatch(path -> path.getFileName().toString().equals("file_1.txt"));
} catch (Exception e) {
return false;
}
});
List<String> fileNames =
Files.list(tmpDir)
.map(path -> path.getFileName().toString())
.collect(Collectors.toList());
assertThat(fileNames).contains("file_1.txt");
} finally {
browser.setDownloadBehavior(new SetDownloadBehaviorParameters(null, (Path) null));
+ TemporaryFilesystem.getDefaultTmpFS().deleteTempDir(tmpDir.toFile());
}Suggestion importance[1-10]: 7
__
Why: The suggestion correctly identifies a resource leak where temporary directories created for tests are not cleaned up, which is important for test suite hygiene, especially on CI servers.
| PR 16631 (2025-11-24) |
[learned best practice] Cleanup created user contexts
✅ Cleanup created user contexts
Ensure created user contexts are cleaned up after the test by deleting them in a finally block to prevent resource leaks.
java/test/org/openqa/selenium/bidi/emulation/SetScriptingEnabledTest.java [82-113]
String userContext = browser.createUserContext();
-BrowsingContext context =
- new BrowsingContext(
- driver, new CreateContextParameters(WindowType.TAB).userContext(userContext));
-String contextId = context.getId();
-...
-// Clear the scripting override
-emulation.setScriptingEnabled(
- new SetScriptingEnabledParameters(null).userContexts(List.of(userContext)));
+try {
+ BrowsingContext context =
+ new BrowsingContext(
+ driver, new CreateContextParameters(WindowType.TAB).userContext(userContext));
+ String contextId = context.getId();
+ ...
+ emulation.setScriptingEnabled(
+ new SetScriptingEnabledParameters(null).userContexts(List.of(userContext)));
+} finally {
+ browser.deleteUserContext(userContext);
+}Suggestion importance[1-10]: 6
__
Why: Relevant best practice - Ensure test setup/teardown and resource cleanup to avoid leaking browser contexts or user contexts.
[general] Extract duplicated code into a helper
✅ Extract duplicated code into a helper
Extract the duplicated script evaluation logic for checking if 'foo' in window into a private helper method to reduce code repetition and improve test readability.
java/test/org/openqa/selenium/bidi/emulation/SetScriptingEnabledTest.java [57-74]
-EvaluateResult resultDisabled =
- script.evaluateFunctionInBrowsingContext(
- contextId, "'foo' in window", false, Optional.empty());
-Boolean valueDisabled =
- (Boolean) ((EvaluateResultSuccess) resultDisabled).getResult().getValue().get();
-assertThat(valueDisabled).isFalse();
+assertThat(isFooInWindow(contextId, script)).isFalse();
emulation.setScriptingEnabled(
new SetScriptingEnabledParameters(null).contexts(List.of(contextId)));
context.navigate(appServer.whereIs("script_page.html"), ReadinessState.COMPLETE);
-EvaluateResult resultEnabled =
- script.evaluateFunctionInBrowsingContext(
- contextId, "'foo' in window", false, Optional.empty());
-Boolean valueEnabled =
- (Boolean) ((EvaluateResultSuccess) resultEnabled).getResult().getValue().get();
-assertThat(valueEnabled).isTrue();
+assertThat(isFooInWindow(contextId, script)).isTrue();Suggestion importance[1-10]: 5
__
Why: The suggestion correctly identifies duplicated code in the test and proposes extracting it into a helper method, which improves code readability and maintainability.
[general] Simplify constructor logic for clarity
✅ Simplify constructor logic for clarity
Simplify the constructor logic by first checking for the invalid true case for the enabled parameter, then using a single map.put call for both null and false values.
java/src/org/openqa/selenium/bidi/emulation/SetScriptingEnabledParameters.java [22-30]
-// allow null
-if (enabled == null) {
- map.put("enabled", null);
-} else if (!enabled) {
- map.put("enabled", enabled);
-} else {
+if (enabled != null && enabled) {
throw new IllegalArgumentException(
"Only emulation of disabled JavaScript is supported (enabled must be false or null)");
}
+map.put("enabled", enabled);Suggestion importance[1-10]: 4
__
Why: The suggestion correctly simplifies the constructor's conditional logic, making the code more concise and readable without changing its behavior.
| PR 16628 (2025-11-22) |
[learned best practice] Pin dependency version
✅ Pin dependency version
Pin the jspecify artifact to a specific version to avoid accidental upgrades and non-reproducible builds.
java/src/org/openqa/selenium/chromium/BUILD.bazel [23]
-artifact("org.jspecify:jspecify"),
+artifact("org.jspecify:jspecify:1.0.0"),Suggestion importance[1-10]: 6
__
Why: Relevant best practice - Guard external dependencies with explicit versioning to ensure reproducible builds.
[learned best practice] Document nullable fields
✅ Document nullable fields
** May be null when the driver does not support casting; initialized during setup if available. */ protected @Nullable HasCasting casting;
- /** May be null when CDP is unavailable for the current browser/session. */ protected @Nullable HasCdp cdp;
**Document that casting and cdp may be null and describe when they are initialized or remain absent to align with the new annotations.**
[java/src/org/openqa/selenium/chromium/ChromiumDriver.java [97-98]](https://github.com/SeleniumHQ/selenium/pull/16628/files#diff-1af793ae2090a0cb691f1efbb3ee0d2b186c4027c5b441f6f3d55b3663e67ba1R97-R98)
```diff
+/** May be null when the driver does not support casting; initialized during setup if available. */
protected @Nullable HasCasting casting;
+/** May be null when CDP is unavailable for the current browser/session. */
protected @Nullable HasCdp cdp;
Suggestion importance[1-10]: 5
__
Why: Relevant best practice - Enforce accurate and consistent documentation and naming to reflect nullability behavior.
| PR 16627 (2025-11-22) |
[possible issue] Safely decode non-text and error responses
✅ Safely decode non-text and error responses
Refactor W3CHttpResponseCodec.decode to correctly handle binary content by keeping it as a stream supplier only when appropriate, and improve error handling for non-textual error responses to prevent crashes.
java/src/org/openqa/selenium/remote/codec/w3c/W3CHttpResponseCodec.java [75-175]
@Override
public Response decode(HttpResponse encodedResponse) {
if (LOG.isLoggable(Level.FINER)) {
LOG.log(
Level.FINER,
"Decoding response. Response code was: {0} and content: {1}",
- new Object[] {encodedResponse.getStatus(), encodedResponse.getContent()});
+ new Object[] {encodedResponse.getStatus(), encodedResponse.getHeader(HttpHeader.ContentType.getName())});
}
String contentType =
Objects.requireNonNullElse(encodedResponse.getHeader(HttpHeader.ContentType.getName()), "");
Response response = new Response();
- // Are we dealing with an error?
- // {"error":"no such alert","message":"No tab modal was open when attempting to get the dialog
- // text"}
if (!encodedResponse.isSuccessful()) {
LOG.fine("Processing an error");
- String content = encodedResponse.contentAsString().trim();
+ boolean isBinary =
+ contentType.toLowerCase(Locale.ENGLISH).startsWith(MediaType.OCTET_STREAM.toString());
+ String content = isBinary ? "" : encodedResponse.contentAsString().trim();
+
if (HTTP_BAD_METHOD == encodedResponse.getStatus()) {
response.setState("unknown command");
response.setStatus(ErrorCodes.UNKNOWN_COMMAND);
response.setValue(content);
} else if (HTTP_GATEWAY_TIMEOUT == encodedResponse.getStatus()
|| HTTP_BAD_GATEWAY == encodedResponse.getStatus()) {
response.setState("unknown error");
response.setStatus(ErrorCodes.UNHANDLED_ERROR);
response.setValue(content);
} else {
+ if (isBinary || content.isEmpty()) {
+ response.setState("unknown error");
+ response.setStatus(ErrorCodes.UNHANDLED_ERROR);
+ response.setValue(String.format("Non-textual error response (status %s, Content-Type: %s)", encodedResponse.getStatus(), contentType));
+ return response;
+ }
Map<String, Object> org = json.toType(content, MAP_TYPE);
-...
+ populateResponse(encodedResponse, response, org);
+ }
+ return response;
+ }
+
response.setState("success");
response.setStatus(ErrorCodes.SUCCESS);
- if (contentType.startsWith(MediaType.OCTET_STREAM.toString())) {
+ if (contentType.toLowerCase(Locale.ENGLISH).startsWith(MediaType.OCTET_STREAM.toString())) {
+ // Keep as stream supplier so clients can stream large files
response.setValue(encodedResponse.getContent());
return response;
}
String content = encodedResponse.contentAsString().trim();
if (!content.isEmpty()) {
- if (contentType.startsWith("application/json")) {
+ if (contentType.toLowerCase(Locale.ENGLISH).startsWith("application/json")) {
Map<String, Object> parsed = json.toType(content, MAP_TYPE);
if (parsed.containsKey("value")) {
+ populateResponse(encodedResponse, response, parsed);
+ } else {
+ response.setValue(parsed);
+ }
+ } else {
+ response.setValue(content);
+ }
+ }
+ return response;
+}
+private void populateResponse(HttpResponse encodedResponse, Response response, Map<String, Object> map) {
+ if (map.containsKey("sessionId")) {
+ Object sid = map.get("sessionId");
+ if (sid != null) {
+ response.setSessionId(String.valueOf(sid));
+ }
+ }
+ response.setValue(map.get("value"));
+}
+Suggestion importance[1-10]: 8
__
Why: The suggestion correctly identifies that setting a Contents.Supplier as a Response value is incorrect for JSON-based clients and that error handling for binary content is fragile. The proposed changes improve the robustness of response decoding, especially for binary streams and error conditions.
| PR 16624 (2025-11-21) |
[possible issue] Prevent unintended workflow cancellations
✅ Prevent unintended workflow cancellations
To prevent unintended cancellations between scheduled and push-triggered workflows, update the concurrency group to include the event type by adding ${{ github.event_name }}.
.github/workflows/ci.yml [13-15]
concurrency:
- group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
+ group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: trueSuggestion importance[1-10]: 8
__
Why: The suggestion correctly identifies a subtle but important issue where scheduled and push-triggered workflows would cancel each other, and the proposed fix using ${{ github.event_name }} is the correct solution.
| PR 16623 (2025-11-21) |
[general] Align type hints with behavior
✅ Align type hints with behavior
Update the type hint for the device_pixel_ratio parameter in set_viewport to float | None | UNDEFINED to match the implementation that accepts None for resetting the value.
py/selenium/webdriver/common/bidi/browsing_context.py [984-990]
def set_viewport(
self,
context: str | None = None,
viewport: dict | None | UNDEFINED = UNDEFINED,
- device_pixel_ratio: float | UNDEFINED = UNDEFINED,
+ device_pixel_ratio: float | None | UNDEFINED = UNDEFINED,
user_contexts: list[str] | None = None,
) -> None:Suggestion importance[1-10]: 5
__
Why: The suggestion correctly identifies that the type hint for device_pixel_ratio is inconsistent with the implementation, which explicitly handles None as a valid input to reset the value.
[possible issue] Fix incorrect variable assignment bug
✅ Fix incorrect variable assignment bug
In set_viewport, fix the incorrect assignment to params["devicePixelRatio"] by using the device_pixel_ratio variable instead of viewport.
py/selenium/webdriver/common/bidi/browsing_context.py [1011-1016]
if device_pixel_ratio is UNDEFINED:
pass
elif device_pixel_ratio is None:
params["devicePixelRatio"] = None
else:
- params["devicePixelRatio"] = viewport
+ params["devicePixelRatio"] = device_pixel_ratioSuggestion importance[1-10]: 10
__
Why: The suggestion correctly identifies a critical copy-paste bug that assigns the wrong variable, breaking the set_viewport functionality for the device_pixel_ratio parameter.
[possible issue] Correct a test assertion typo
✅ Correct a test assertion typo
In test_set_viewport_back_to_default, correct the assertion for viewport height to compare against the default height (default_viewport_size[1]) instead of the default width.
py/test/selenium/webdriver/common/bidi_browsing_context_tests.py [402-404]
assert viewport_size[0] == default_viewport_size[0]
-assert viewport_size[1] == default_viewport_size[0]
+assert viewport_size[1] == default_viewport_size[1]
assert device_pixel_ratio == default_device_pixel_ratioSuggestion importance[1-10]: 9
__
Why: The suggestion correctly identifies a copy-paste error in a test assertion, which would cause the test to validate the wrong behavior and potentially pass incorrectly.
| PR 16620 (2025-11-20) |
[general] Improve filtering logic for performance
✅ Improve filtering logic for performance
Refactor the label filtering logic to use a Set for grouping keys instead of a List and multiple if conditions, improving lookup performance and code clarity.
java/src/org/openqa/selenium/grid/node/docker/DockerOptions.java [269-289]
// Get custom grouping labels from configuration
List<String> customLabelKeys =
config.getAll(DOCKER_SECTION, "grouping-labels").orElseGet(Collections::emptyList);
+
+Set<String> groupingKeys = new HashSet<>(customLabelKeys);
+groupingKeys.add("com.docker.compose.project");
+groupingKeys.add("io.podman.compose.project");
Map<String, String> allLabels = info.get().getLabels();
// Filter for project/grouping labels that work across orchestration systems
// Keep only project identifiers, exclude service-specific labels to prevent
// exit monitoring in Docker Compose, Podman Compose, etc.
return allLabels.entrySet().stream()
- .filter(
- entry -> {
- String key = entry.getKey();
- // Docker Compose project label
- if (key.equals("com.docker.compose.project")) return true;
- // Podman Compose project label
- if (key.equals("io.podman.compose.project")) return true;
- // Custom user-defined grouping labels
- if (customLabelKeys.contains(key)) return true;
- return false;
- })
+ .filter(entry -> groupingKeys.contains(entry.getKey()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));Suggestion importance[1-10]: 6
__
Why: The suggestion correctly proposes using a Set for more efficient lookups, which improves performance and code readability, aligning with best practices.
| PR 16616 (2025-11-20) |
[general] Avoid code duplication by linking files
✅ Avoid code duplication by linking files
Remove the duplicated StringSyntaxAttribute.cs file and instead link to the single source file from the project file to avoid code duplication.
dotnet/src/support/Internal/StringSyntaxAttribute.cs [1-88]
-// <copyright file="StringSyntaxAttribute.cs" company="Selenium Committers">
-// Licensed to the Software Freedom Conservancy (SFC) under one
-// or more contributor license agreements. See the NOTICE file
-// distributed with this work for additional information
-// regarding copyright ownership. The SFC licenses this file
-// to you under the Apache License, Version 2.0 (the
-// "License"); you may not use this file except in compliance
-// with the License. You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing,
-// software distributed under the License is distributed on an
-// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-// KIND, either express or implied. See the License for the
-// specific language governing permissions and limitations
-// under the License.
-// </copyright>
+// This file should be deleted.
-#if !NET8_0_OR_GREATER
-
-namespace System.Diagnostics.CodeAnalysis;
-
-/// <summary>Specifies the syntax used in a string.</summary>
-[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
-internal sealed class StringSyntaxAttribute : Attribute
-{
- /// <summary>Initializes the <see cref="StringSyntaxAttribute"/> with the identifier of the syntax used.</summary>
- /// <param name="syntax">The syntax identifier.</param>
- public StringSyntaxAttribute(string syntax)
- {
- Syntax = syntax;
- Arguments = [];
- }
-
- /// <summary>Initializes the <see cref="StringSyntaxAttribute"/> with the identifier of the syntax used.</summary>
- /// <param name="syntax">The syntax identifier.</param>
- /// <param name="arguments">Optional arguments associated with the specific syntax employed.</param>
- public StringSyntaxAttribute(string syntax, params object?[] arguments)
- {
- Syntax = syntax;
- Arguments = arguments;
- }
-
- /// <summary>Gets the identifier of the syntax used.</summary>
- public string Syntax { get; }
-
- /// <summary>Optional arguments associated with the specific syntax employed.</summary>
- public object?[] Arguments { get; }
-
- /// <summary>The syntax identifier for strings containing composite formats for string formatting.</summary>
- public const string CompositeFormat = nameof(CompositeFormat);
-
- /// <summary>The syntax identifier for strings containing date format specifiers.</summary>
- public const string DateOnlyFormat = nameof(DateOnlyFormat);
-
- /// <summary>The syntax identifier for strings containing date and time format specifiers.</summary>
- public const string DateTimeFormat = nameof(DateTimeFormat);
-
- /// <summary>The syntax identifier for strings containing <see cref="Enum"/> format specifiers.</summary>
- public const string EnumFormat = nameof(EnumFormat);
-
- /// <summary>The syntax identifier for strings containing <see cref="Guid"/> format specifiers.</summary>
- public const string GuidFormat = nameof(GuidFormat);
-
- /// <summary>The syntax identifier for strings containing JavaScript Object Notation (JSON).</summary>
- public const string Json = nameof(Json);
-
- /// <summary>The syntax identifier for strings containing numeric format specifiers.</summary>
- public const string NumericFormat = nameof(NumericFormat);
-
- /// <summary>The syntax identifier for strings containing regular expressions.</summary>
- public const string Regex = nameof(Regex);
-
- /// <summary>The syntax identifier for strings containing time format specifiers.</summary>
- public const string TimeOnlyFormat = nameof(TimeOnlyFormat);
-
- /// <summary>The syntax identifier for strings containing <see cref="TimeSpan"/> format specifiers.</summary>
- public const string TimeSpanFormat = nameof(TimeSpanFormat);
-
- /// <summary>The syntax identifier for strings containing URIs.</summary>
- public const string Uri = nameof(Uri);
-
- /// <summary>The syntax identifier for strings containing XML.</summary>
- public const string Xml = nameof(Xml);
-}
-
-#endif
-Suggestion importance[1-10]: 6
__
Why: The suggestion correctly identifies that the PR introduces a duplicated file and proposes a standard .NET practice to resolve it, which improves code maintainability.
| PR 16613 (2025-11-19) |
[possible issue] Avoid mutating a constructor parameter
✅ Avoid mutating a constructor parameter
To avoid mutating a constructor parameter and prevent potential runtime exceptions, create a new mutable copy of the composeLabels map before adding the new label.
java/src/org/openqa/selenium/grid/node/docker/DockerSessionFactory.java [144-146]
-this.composeLabels = Require.nonNull("Docker Compose labels", composeLabels);
// Merge compose labels with oneoff=False to prevent triggering --exit-code-from dynamic grid
-this.composeLabels.put("com.docker.compose.oneoff", "False");
+Map<String, String> allLabels = new HashMap<>(Require.nonNull("Docker Compose labels", composeLabels));
+allLabels.put("com.docker.compose.oneoff", "False");
+this.composeLabels = Collections.unmodifiableMap(allLabels);Suggestion importance[1-10]: 8
__
Why: The suggestion correctly identifies a potential UnsupportedOperationException if an immutable map is passed to the constructor and avoids a side effect by not mutating the input parameter, which significantly improves the code's robustness.
| PR 16610 (2025-11-18) |
[possible issue] Revert breaking lockfile version change
✅ Revert breaking lockfile version change
Revert the lockfileVersion update from '6.0' to '9.0'. This change requires a pnpm major version upgrade (to v9+) across all environments, which could break dependency installation if not coordinated.
-lockfileVersion: '9.0'
+lockfileVersion: '6.0'Suggestion importance[1-10]: 10
__
Why: This suggestion correctly points out a critical, project-wide breaking change in the pnpm lockfileVersion, which could halt all development and CI/CD pipelines if not managed correctly.
| PR 16607 (2025-11-17) |
[general] Ensure consistent docstring quote style
✅ Ensure consistent docstring quote style
Update the docstring function to generate docstrings with double quotes (""") instead of single quotes (''') for consistency.
def docstring(description):
""" Generate a docstring from a description. """
if not description:
return ''
description = escape_backticks(description)
- return dedent("'''\n{}\n'''").format(description)
+ return dedent('"""\n{}\n"""').format(description)Suggestion importance[1-10]: 7
__
Why: The suggestion correctly points out that while the PR changes existing docstrings to use double quotes, the docstring generator function itself still produces single-quoted docstrings, which is inconsistent with the PR's intent.
| PR 16603 (2025-11-16) |
[general] Remove redundant client-side context filtering
✅ Remove redundant client-side context filtering
Remove the redundant client-side context check in OnEntryAddedAsync methods. The event subscription is already filtered by context at the protocol level, making the if statement unnecessary.
dotnet/src/webdriver/BiDi/BrowsingContext/BrowsingContextLogModule.cs [28-48]
public Task<Subscription> OnEntryAddedAsync(Func<Log.LogEntry, Task> handler, ContextSubscriptionOptions? options = null)
{
- return logModule.OnEntryAddedAsync(async args =>
- {
- if (args.Source.Context?.Equals(context) is true)
- {
- await handler(args).ConfigureAwait(false);
- }
- }, options.WithContext(context));
+ return logModule.OnEntryAddedAsync(handler, options.WithContext(context));
}
public Task<Subscription> OnEntryAddedAsync(Action<Log.LogEntry> handler, ContextSubscriptionOptions? options = null)
{
- return logModule.OnEntryAddedAsync(args =>
- {
- if (args.Source.Context?.Equals(context) is true)
- {
- handler(args);
- }
- }, options.WithContext(context));
+ return logModule.OnEntryAddedAsync(handler, options.WithContext(context));
}Suggestion importance[1-10]: 6
__
Why: The suggestion correctly identifies redundant client-side filtering logic that is now handled by the protocol-level subscription, simplifying the code and improving maintainability.
| PR 16601 (2025-11-15) |
[possible issue] Correct JSON serialization for optional parameters
✅ Correct JSON serialization for optional parameters
To align with the WebDriver BiDi specification, correct the JSON serialization by removing the JsonIgnore attribute from the Viewport property and adding it to the DevicePixelRatio property.
dotnet/src/webdriver/BiDi/BrowsingContext/SetViewportCommand.cs [28]
-internal sealed record SetViewportParameters(BrowsingContext Context, [property: JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] Viewport? Viewport, double? DevicePixelRatio) : Parameters;
+internal sealed record SetViewportParameters(BrowsingContext Context, Viewport? Viewport, [property: JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] double? DevicePixelRatio) : Parameters;Suggestion importance[1-10]: 5
__
Why: The suggestion correctly identifies that the PR's change to Viewport is incorrect, as it prevents resetting the viewport by sending a null value. However, the proposed fix of moving the attribute to DevicePixelRatio is also incorrect, as the BiDi spec requires sending null to reset that value as well.
| PR 16599 (2025-11-15) |
[possible issue] Sanitize browser name for container
✅ Sanitize browser name for container
Sanitize the browser name by replacing characters invalid for Docker container names to prevent potential creation failures.
java/src/org/openqa/selenium/grid/node/docker/DockerSessionFactory.java [314-316]
-// Generate container name: browser-<browserName>-<timestamp>-<uuid>
-String browserName = stereotype.getBrowserName().toLowerCase();
+// Generate container name: browser-<browserName>-<sessionIdentifier>
+String browserName = stereotype.getBrowserName().toLowerCase().replaceAll("[^a-z0-9_.-]", "-");
String containerName = String.format("browser-%s-%s", browserName, sessionIdentifier);Suggestion importance[1-10]: 8
__
Why: The suggestion correctly identifies that the browser name is not sanitized before being used in a Docker container name, which could lead to container creation failures if the name contains invalid characters.
[learned best practice] Validate and encode URL parameter
✅ Validate and encode URL parameter
Validate and URL-encode the optional container name to avoid invalid requests and injection issues. Reject empty/whitespace-only names and percent-encode the value.
java/src/org/openqa/selenium/docker/client/CreateContainer.java [66-69]
String url = String.format("/v%s/containers/create", apiVersion);
-if (info.getName() != null && !info.getName().isEmpty()) {
- url += "?name=" + info.getName();
+String name = info.getName();
+if (name != null) {
+ name = name.trim();
+ if (!name.isEmpty()) {
+ String encoded = java.net.URLEncoder.encode(name, java.nio.charset.StandardCharsets.UTF_8);
+ url += "?name=" + encoded;
+ }
}Suggestion importance[1-10]: 6
__
Why: Relevant best practice - Guard external API and I/O operations with targeted validation and proper encoding when building request URLs.
| PR 16596 (2025-11-14) |
[possible issue] Avoid modifying nested map directly
✅ Avoid modifying nested map directly
To avoid side effects, create a defensive copy of the networkSettings map in adaptContainerInspectResponse before modifying it, ensuring the original response data structure remains unchanged.
java/src/org/openqa/selenium/docker/client/V148Adapter.java [165-205]
@SuppressWarnings("unchecked")
-Map<String, Object> networkSettings = (Map<String, Object>) adapted.get("NetworkSettings");
+Map<String, Object> originalNetworkSettings =
+ (Map<String, Object>) adapted.get("NetworkSettings");
-if (networkSettings != null) {
+if (originalNetworkSettings != null) {
+ // Create a mutable copy to avoid modifying the original map
+ Map<String, Object> networkSettings = new HashMap<>(originalNetworkSettings);
+
@SuppressWarnings("unchecked")
Map<String, Object> networks = (Map<String, Object>) networkSettings.get("Networks");
if (networks != null) {
for (Map.Entry<String, Object> entry : networks.entrySet()) {
if (entry.getValue() instanceof Map) {
@SuppressWarnings("unchecked")
Map<String, Object> network = (Map<String, Object>) entry.getValue();
if (network.containsKey("GwPriority")) {
LOG.fine(
"Network '" + entry.getKey() + "' includes GwPriority (API v" + apiVersion + ")");
}
}
}
}
// Remove deprecated fields (should not be present in v1.48+)
String[] deprecatedFields = {
"HairpinMode",
"LinkLocalIPv6Address",
"LinkLocalIPv6PrefixLen",
"SecondaryIPAddresses",
"SecondaryIPv6Addresses",
"Bridge" // Deprecated in v1.51, removed in v1.52
};
for (String field : deprecatedFields) {
if (networkSettings.containsKey(field)) {
LOG.fine(
"Removing deprecated field '"
+ field
+ "' from NetworkSettings (deprecated in earlier API versions)");
networkSettings.remove(field);
}
}
+ adapted.put("NetworkSettings", networkSettings);
}Suggestion importance[1-10]: 6
__
Why: The suggestion correctly identifies that a nested map is being modified, which could lead to side effects. Creating a defensive copy is good practice for immutability and prevents unintended modification of the original data structure.
| PR 16594 (2025-11-14) |
[learned best practice] Close context in finally
✅ Close context in finally
Wrap context creation/usage in try-finally and close the BrowsingContext in finally to avoid leaks if assertions throw.
java/test/org/openqa/selenium/bidi/emulation/SetGeolocationOverrideTest.java [201-235]
BrowsingContext context = new BrowsingContext(driver, driver.getWindowHandle());
-String contextId = context.getId();
-...
-// Error because there's no real geolocation available
-assertThat(r2.containsKey("error")).isTrue();
+try {
+ String contextId = context.getId();
+ String url = appServer.whereIsSecure("blank.html");
+ context.navigate(url, ReadinessState.COMPLETE);
+ driver.switchTo().window(context.getId());
+ String origin =
+ (String) ((JavascriptExecutor) driver).executeScript("return window.location.origin;");
+ Emulation emul = new Emulation(driver);
+ GeolocationCoordinates coords = new GeolocationCoordinates(37.7749, -122.4194);
+ emul.setGeolocationOverride(
+ new SetGeolocationOverrideParameters(coords).contexts(List.of(contextId)));
+ Object firstResult = getBrowserGeolocation(driver, null, origin);
+ Map<String, Object> r1 = ((Map<String, Object>) firstResult);
+ assertThat(r1.containsKey("error")).isFalse();
+ double latitude1 = ((Number) r1.get("latitude")).doubleValue();
+ double longitude1 = ((Number) r1.get("longitude")).doubleValue();
+ assertThat(abs(latitude1 - coords.getLatitude())).isLessThan(0.0001);
+ assertThat(abs(longitude1 - coords.getLongitude())).isLessThan(0.0001);
+ emul.setGeolocationOverride(
+ new SetGeolocationOverrideParameters((GeolocationCoordinates) null)
+ .contexts(List.of(contextId)));
+ Object secondResult = getBrowserGeolocation(driver, null, origin);
+ Map<String, Object> r2 = ((Map<String, Object>) secondResult);
+ assertThat(r2.containsKey("error")).isTrue();
+} finally {
+ context.close();
+}Suggestion importance[1-10]: 6
__
Why: Relevant best practice - Ensure resources are cleaned up using try-finally or equivalent to close/tear down contexts and user contexts, even on exceptions.
| PR 16564 (2025-11-08) |
[possible issue] Fix incorrect target file paths
✅ Fix incorrect target file paths
Fix typos in the target attribute of file elements for the net462 framework in Selenium.WebDriver.StrongNamed.nuspec by removing the extra dot in the filenames.
dotnet/src/webdriver/Selenium.WebDriver.StrongNamed.nuspec [43-45]
-<file src="lib/net462/WebDriver.StrongNamed.dll" target="lib/net462/WebDriverStrongNamed..dll" />
-<file src="lib/net462/WebDriver.StrongNamed.pdb" target="lib/net462/WebDriverStrongNamed..pdb" />
-<file src="lib/net462/WebDriver.StrongNamed.xml" target="lib/net462/WebDriverStrongNamed..xml" />
+<file src="lib/net462/WebDriver.StrongNamed.dll" target="lib/net462/WebDriver.StrongNamed.dll" />
+<file src="lib/net462/WebDriver.StrongNamed.pdb" target="lib/net462/WebDriver.StrongNamed.pdb" />
+<file src="lib/net462/WebDriver.StrongNamed.xml" target="lib/net462/WebDriver.StrongNamed.xml" />Suggestion importance[1-10]: 9
__
Why: The suggestion correctly identifies a typo in the NuGet package specification that would lead to incorrectly named files, breaking the package for the net462 target.
| PR 16556 (2025-11-06) |
[general] Replace fixed sleep with explicit wait
✅ Replace fixed sleep with explicit wait
Replace time.sleep(1) with an explicit WebDriverWait to create a more reliable test for verifying that a file is not downloaded.
py/test/selenium/webdriver/common/bidi_browser_tests.py [300-305]
driver.find_element(By.ID, "file-1").click()
-time.sleep(1)
+try:
+ WebDriverWait(driver, 2, poll_frequency=0.2).until(lambda _: len(os.listdir(tmp_dir)) > 0)
+ # If we get here, a file was created, which is a failure
+ files = os.listdir(tmp_dir)
+ pytest.fail(f"A file was downloaded unexpectedly: {files}")
+except TimeoutException:
+ # This is the expected outcome, no file was downloaded within the timeout.
+ pass
-files = os.listdir(tmp_dir)
-assert len(files) == 0, f"No files should be downloaded when denied, but found: {files}"
-Suggestion importance[1-10]: 6
__
Why: The suggestion correctly identifies that using time.sleep() can lead to flaky tests and proposes a more robust explicit wait, which improves test reliability.
| PR 16551 (2025-11-04) |
[general] Make required arguments explicit in signature
✅ Make required arguments explicit in signature
Update the ChromiumDriver.init method signature to make browser_name and vendor_prefix required arguments, removing the Optional type hint and default None value, and then remove the now-redundant runtime checks for None.
py/selenium/webdriver/chromium/webdriver.py [31-67]
def __init__(
self,
- browser_name: Optional[str] = None,
- vendor_prefix: Optional[str] = None,
+ browser_name: str,
+ vendor_prefix: str,
options: Optional[ChromiumOptions] = None,
service: Optional[ChromiumService] = None,
keep_alive: bool = True,
) -> None:
"""Create a new WebDriver instance, start the service, and create new ChromiumDriver instance.
Args:
browser_name: Browser name used when matching capabilities.
vendor_prefix: Company prefix to apply to vendor-specific WebDriver extension commands.
options: This takes an instance of ChromiumOptions.
service: Service object for handling the browser driver if you need to pass extra details.
keep_alive: Whether to configure ChromiumRemoteConnection to use HTTP keep-alive.
"""
self.service = service if service else ChromiumService()
options = options if options else ChromiumOptions()
finder = DriverFinder(self.service, options)
if finder.get_browser_path():
options.binary_location = finder.get_browser_path()
options.browser_version = None
self.service.path = self.service.env_path() or finder.get_driver_path()
self.service.start()
- if browser_name is None:
- raise ValueError("browser_name must be specified")
- if vendor_prefix is None:
- raise ValueError("vendor_prefix must be specified")
-
executor = ChromiumRemoteConnection(
remote_server_addr=self.service.service_url,
browser_name=browser_name,
vendor_prefix=vendor_prefix,Suggestion importance[1-10]: 7
__
Why: The suggestion correctly identifies a contradiction between the method signature, which marks arguments as optional, and the implementation, which requires them. Aligning the signature makes the API contract clearer and enables static analysis tools to catch errors.
| PR 16530 (2025-10-29) |
[general] Return a defensive copy of map
✅ Return a defensive copy of map
Return a defensive copy of the internal map in the toMap() method to prevent external modification and maintain encapsulation.
java/src/org/openqa/selenium/bidi/emulation/AbstractOverrideParameters.java [57]
-return map;
+return new HashMap<>(map);Suggestion importance[1-10]: 8
__
Why: The suggestion correctly identifies a significant encapsulation issue in the new AbstractOverrideParameters class where returning a direct reference to the internal map allows bypassing validation logic. Providing a defensive copy is crucial for maintaining the object's integrity.
[possible issue] Create a defensive copy of parameters
✅ Create a defensive copy of parameters
To ensure the Command object is immutable, create a defensive, unmodifiable copy of the params map that allows null values.
java/src/org/openqa/selenium/bidi/Command.java [51]
-this.params = Require.nonNull("Command parameters", params);
+this.params = java.util.Collections.unmodifiableMap(new java.util.HashMap<>(Require.nonNull("Command parameters", params)));Suggestion importance[1-10]: 7
__
Why: The PR removed Map.copyOf() which made the params map immutable but disallowed nulls. This suggestion correctly identifies that the new implementation is mutable from the outside and proposes a solution that restores immutability while still allowing null values, improving the robustness of the Command object.
| PR 16511 (2025-10-25) |
[general] Correct docstring return type
✅ Correct docstring return type
Correct the return type in the web_socket_url docstring from bool to str to accurately reflect that it returns a URL.
py/selenium/webdriver/common/options.py [316-328]
web_socket_url = _BaseOptionsDescriptor("webSocketUrl")
"""Gets and Sets WebSocket URL.
Usage:
- Get: `self.web_socket_url`
- Set: `self.web_socket_url = value`
Args:
value: str
Returns:
- bool when getting, None when setting.
+ str when getting, None when setting.
"""Suggestion importance[1-10]: 3
__
Why: The suggestion correctly identifies and fixes a documentation error where the return type for web_socket_url is incorrectly stated as bool instead of str, improving the accuracy of the docstring.
| PR 16504 (2025-10-24) |
[learned best practice] Add robust test cleanup
✅ Add robust test cleanup
Ensure the tab and user context are cleaned up even if assertions fail by wrapping them in try/finally blocks.
py/test/selenium/webdriver/common/bidi_emulation_tests.py [347-363]
def test_set_locale_override_with_user_contexts(driver, pages, value):
"""Test setting locale override with user contexts."""
user_context = driver.browser.create_user_context()
+ try:
+ context_id = driver.browsing_context.create(type=WindowTypes.TAB, user_context=user_context)
+ try:
+ driver.switch_to.window(context_id)
+ driver.emulation.set_locale_override(locale=value, user_contexts=[user_context])
+ driver.browsing_context.navigate(context_id, pages.url("formPage.html"), wait="complete")
+ current_locale = get_browser_locale(driver)
+ assert current_locale == value, f"Expected locale {value}, got {current_locale}"
+ finally:
+ driver.browsing_context.close(context_id)
+ finally:
+ driver.browser.remove_user_context(user_context)
- context_id = driver.browsing_context.create(type=WindowTypes.TAB, user_context=user_context)
-
- driver.switch_to.window(context_id)
-
- driver.emulation.set_locale_override(locale=value, user_contexts=[user_context])
-
- driver.browsing_context.navigate(context_id, pages.url("formPage.html"), wait="complete")
-
- current_locale = get_browser_locale(driver)
- assert current_locale == value, f"Expected locale {value}, got {current_locale}"
-
- driver.browsing_context.close(context_id)
- driver.browser.remove_user_context(user_context)
-Suggestion importance[1-10]: 6
__
Why: Relevant best practice - Enforce deterministic cleanup of created resources in tests to avoid leaks and cross-test interference.
| PR 16487 (2025-10-22) |
[learned best practice] Guard message fields before use
✅ Guard message fields before use
Add defensive checks to ensure message['params'] is a Hash before use and safely duplicate callbacks to prevent nil errors and races when handlers are removed concurrently.
rb/lib/selenium/webdriver/common/websocket_connection.rb [137-147]
while (frame = incoming_frame.next)
break if @closing
message = process_frame(frame)
- next unless message['method']
+ method = message['method']
+ next unless method.is_a?(String)
params = message['params']
- @messages_mtx.synchronize { callbacks[message['method']].dup }.each do |callback|
+ next unless params.is_a?(Hash)
+
+ handlers = @callbacks_mtx.synchronize { callbacks[method].dup }
+ handlers.each do |callback|
@callback_threads.add(callback_thread(params, &callback))
end
endSuggestion importance[1-10]: 6
__
Why: Relevant best practice - Validate inputs and states early to avoid nil errors; guard message handling and callback spawning against missing fields.
[high-level] Refactor to use multiple specialized mutexes
✅ Refactor to use multiple specialized mutexes
**payload) @@ -98,12 +106,10 @@ begin socket.write(out_frame.to_s) rescue *CONNECTION_ERRORS => e
-
raise Error::WebDriverError, "WebSocket is closed (#{e.class}: #{e.message})" -
end -
wait.until do -
@mtx.synchronize { messages.delete(id) } -
end
-
raise e, "WebSocket is closed (#{e.class}: #{e.message})" -
end -
wait.until { @messages_mtx.synchronize { messages.delete(id) } } end private
@@ -132,9 +138,8 @@ message = process_frame(frame) next unless message['method']
-
params = message['params'] -
@mtx.synchronize { callbacks[message['method']].dup }.each do |callback| -
@callback_threads.add(callback_thread(params, &callback))
-
@messages_mtx.synchronize { callbacks[message['method']].dup }.each do |callback| -
@callback_threads.add(callback_thread(message['params'], &callback)) end end end
@@ -154,7 +159,7 @@ return {} if message.empty?
msg = JSON.parse(message)
-
@mtx.synchronize { messages[msg['id']] = msg if msg.key?('id') }
-
@messages_mtx.synchronize { messages[msg['id']] = msg if msg.key?('id') } WebDriver.logger.debug "WebSocket <- #{msg}"[...MAX_LOG_MESSAGE_SIZE], id: :ws msg
@@ -170,7 +175,8 @@ rescue Error::WebDriverError, *CONNECTION_ERRORS => e WebDriver.logger.debug "Callback aborted: #{e.class}: #{e.message}", id: :ws rescue StandardError => e
-
# Unexpected handler failure; log with a short backtrace.
-
return if @closing -
bt = Array(e.backtrace).first(5).join("\n") WebDriver.logger.error "Callback error: #{e.class}: #{e.message}\n#{bt}", id: :ws end
**Replace the single, coarse-grained mutex with multiple, more granular mutexes, one for each shared resource (@callbacks, @messages, @closing). This will reduce lock contention and improve concurrency.**
### Examples:
rb/lib/selenium/webdriver/common/websocket_connection.rb [40]
```ruby
@mtx = Mutex.new
rb/lib/selenium/webdriver/common/websocket_connection.rb [77-80]
@mtx.synchronize do
callbacks[event] << block
block.object_id
endclass WebSocketConnection
def initialize(url:)
@mtx = Mutex.new
@closing = false
@callbacks = {}
@messages = {}
# ...
end
def add_callback(event, █)
@mtx.synchronize do
# ... access @callbacks
end
end
def send_cmd(**payload)
# ...
wait.until do
@mtx.synchronize { messages.delete(id) }
end
end
def process_frame(frame)
# ...
@mtx.synchronize { messages[msg['id']] = msg }
# ...
end
endclass WebSocketConnection
def initialize(url:)
@callbacks_mtx = Mutex.new
@messages_mtx = Mutex.new
@closing_mtx = Mutex.new
@closing = false
@callbacks = {}
@messages = {}
# ...
end
def add_callback(event, █)
@callbacks_mtx.synchronize do
# ... access @callbacks
end
end
def send_cmd(**payload)
# ...
wait.until do
@messages_mtx.synchronize { messages.delete(id) }
end
end
def process_frame(frame)
# ...
@messages_mtx.synchronize { messages[msg['id']] = msg }
# ...
end
endSuggestion importance[1-10]: 8
__
Why: The suggestion correctly identifies that a single coarse-grained mutex is used to protect multiple independent resources, which can cause unnecessary lock contention and performance issues. Adopting a more granular locking strategy is a significant architectural improvement for the concurrency model introduced in this PR.
[possible issue] Ensure session is always deleted
✅ Ensure session is always deleted
Modify the quit method to ensure super is always called, even if bidi.close fails. Wrap only the bidi.close call in a begin/rescue block to prevent leaving orphaned browser sessions.
rb/lib/selenium/webdriver/remote/bidi_bridge.rb [48-53]
def quit
- bidi.close
+ begin
+ bidi.close
+ rescue *QUIT_ERRORS
+ nil
+ end
super
-rescue *QUIT_ERRORS
- nil
endSuggestion importance[1-10]: 8
__
Why: The suggestion correctly identifies a critical flaw where a failure in bidi.close would prevent the super call from executing, potentially leaving browser sessions running. The proposed fix ensures the session is always cleaned up.
[possible issue] Fix race condition in callback removal
✅ Fix race condition in callback removal
Refactor the remove_callback method to use a single synchronize block. This will prevent a race condition by ensuring the callback removal and error message data collection are performed atomically.
rb/lib/selenium/webdriver/common/websocket_connection.rb [84-88]
-removed = @mtx.synchronize { callbacks[event].reject! { |cb| cb.object_id == id } }
-return if removed || @closing
+@mtx.synchronize do
+ return if @closing
-ids = @mtx.synchronize { callbacks[event]&.map(&:object_id) }
-raise Error::WebDriverError, "Callback with ID #{id} does not exist for event #{event}: #{ids}"
+ callbacks_for_event = callbacks[event]
+ if callbacks_for_event.reject! { |cb| cb.object_id == id }
+ return
+ end
+ ids = callbacks_for_event.map(&:object_id)
+ raise Error::WebDriverError, "Callback with ID #{id} does not exist for event #{event}: #{ids}"
+end
+Suggestion importance[1-10]: 7
__
Why: The suggestion correctly identifies a race condition in the remove_callback method where two separate synchronize blocks could lead to inconsistent state, and proposes a valid fix by using a single atomic block.
| PR 16477 (2025-10-21) |
[possible issue] Fix off-by-one error in slicing
✅ Fix off-by-one error in slicing
Correct the string slicing from f[:-7] to f[:-8] to properly remove the _spec.rb suffix from test file names.
rb/spec/integration/selenium/webdriver/BUILD.bazel [56-63]
[
rb_integration_test(
- name = f[:-7],
+ name = f[:-8],
srcs = [f],
tags = ["bidi"],
)
for f in _BIDI_FILES
]Suggestion importance[1-10]: 9
__
Why: The suggestion correctly identifies a functional bug in the build script. The incorrect string slicing f[:-7] would generate malformed test target names, likely causing build failures or tests not being correctly identified.
[learned best practice] Remove early macro return
✅ Remove early macro return
Avoid returning early from the macro; guard only the BiDi target creation with a conditional so other targets remain unaffected and macro flow stays predictable.
-if "bidi" not in tags:
- return # don't create -bidi targets for non-BiDi tests
+if "bidi" in tags:
+ rb_test(
+ name = "{}-{}-bidi".format(name, browser),
+ size = "large",
+ srcs = srcs,
+ # ...
+ )Suggestion importance[1-10]: 6
__
Why: Relevant best practice - Keep build macros pure and predictable by avoiding early returns that skip subsequent target generation unexpectedly; prefer structured conditionals so all intended targets are considered.
| PR 16473 (2025-10-20) |
[possible issue] Fix conditional logic for GitHub Actions
✅ Fix conditional logic for GitHub Actions
Fix the conditional logic for ignoring tests on GitHub Actions. The current implementation incorrectly ignores tests when the gitHubActions attribute is not specified.
java/test/org/openqa/selenium/testing/IgnoreComparator.java [52-56]
return ignoreList.anyMatch(
driver ->
(ignored.contains(driver.value()) || driver.value() == Browser.ALL)
- && ((!driver.gitHubActions() || TestUtilities.isOnGitHubActions()))
+ && (!driver.gitHubActions() || TestUtilities.isOnGitHubActions())
&& isOpen(driver.issue()));Suggestion importance[1-10]: 10
__
Why: The suggestion correctly identifies a critical logic bug in the shouldIgnore method. The current logic causes tests to be ignored incorrectly when @Ignore is used without gitHubActions=true, which is a significant flaw introduced by the PR.
| PR 16449 (2025-10-16) |
[possible issue] Simplify file serving by reading all files in binary mode
✅ Simplify file serving by reading all files in binary mode
Simplify the _serve_file method by opening all files in binary mode ("rb") to avoid unnecessary and potentially corrupting text re-encoding.
py/test/selenium/webdriver/common/webserver.py [72-87]
def _serve_file(self, file_path):
"""Serve a file from the HTML root directory."""
- content_type, _ = mimetypes.guess_type(file_path)
+ with open(file_path, "rb") as f:
+ return f.read()
- if content_type and (
- content_type.startswith("image/")
- or content_type.startswith("application/")
- or content_type.startswith("video/")
- or content_type.startswith("audio/")
- ):
- with open(file_path, "rb") as f:
- return f.read()
- else:
- # text files
- with open(file_path, encoding="latin-1") as f:
- return f.read().encode("utf-8")
-Suggestion importance[1-10]: 7
__
Why: The suggestion correctly identifies that re-encoding text files from latin-1 to utf-8 is problematic and can cause corruption. The proposed change to read all files in binary mode is simpler, more robust, and the correct approach for a web server.
[learned best practice] Default Content-Type when unknown
✅ Default Content-Type when unknown
Ensure a valid Content-Type is always sent by defaulting to a sensible MIME type when guessing fails. This avoids sending a None header value.
py/test/selenium/webdriver/common/webserver.py [104-108]
elif os.path.isfile(file_path):
content_type, _ = mimetypes.guess_type(file_path)
+ if not content_type:
+ content_type = "application/octet-stream"
content = self._serve_file(file_path)
self._send_response(content_type)
self.wfile.write(content)Suggestion importance[1-10]: 6
__
Why: Relevant best practice - Validate inputs and states early, providing safe defaults to prevent logic errors and None-related issues.
| PR 16444 (2025-10-15) |
[incremental [*]] Prevent heartbeat interval overflow
✅ Prevent heartbeat interval overflow
** * Configures ZeroMQ heartbeat settings on a socket to prevent stale connections.
-
-
-
The heartbeat interval is clamped between 1 second and ~24 days to prevent integer overflow
-
-
- and ensure reasonable values. If the provided duration is outside this range, it will be
-
- adjusted and a warning will be logged.
- @param socket The ZMQ socket to configure
- @param heartbeatPeriod The heartbeat interval duration @@ -39,14 +46,58 @@ */ static void configureHeartbeat(ZMQ.Socket socket, Duration heartbeatPeriod, String socketType) { if (heartbeatPeriod != null && !heartbeatPeriod.isZero() && !heartbeatPeriod.isNegative()) {
-
int heartbeatIvl = (int) heartbeatPeriod.toMillis();
-
long heartbeatMs = heartbeatPeriod.toMillis(); -
long clampedHeartbeatMs = clampHeartbeatInterval(heartbeatMs, socketType); -
// Safe to cast to int now -
int heartbeatIvl = (int) clampedHeartbeatMs; -
int heartbeatTimeout = heartbeatIvl * 3; -
int heartbeatTtl = heartbeatIvl * 6; -
socket.setHeartbeatIvl(heartbeatIvl);
-
socket.setHeartbeatTimeout(heartbeatIvl * 3); -
socket.setHeartbeatTtl(heartbeatIvl * 6);
-
socket.setHeartbeatTimeout(heartbeatTimeout); -
socket.setHeartbeatTtl(heartbeatTtl); -
LOG.info( String.format(
-
"ZMQ %s socket heartbeat configured: interval=%dms, timeout=%dms, ttl=%dms", -
socketType, heartbeatIvl, heartbeatIvl * 3, heartbeatIvl * 6));
-
"ZMQ %s socket heartbeat configured: interval=%ds, timeout=%ds, ttl=%ds", -
} }
socketType, heartbeatIvl / 1000, heartbeatTimeout / 1000, heartbeatTtl / 1000)); - /**
-
- Clamps the heartbeat interval to safe bounds and logs warnings if adjustments are made.
-
-
- @param heartbeatMs The heartbeat interval in milliseconds
-
- @param socketType The socket type for logging
-
- @return The clamped heartbeat interval
- */
- private static long clampHeartbeatInterval(long heartbeatMs, String socketType) {
- if (heartbeatMs < MIN_HEARTBEAT_MS) {
-
logHeartbeatClampWarning(socketType, heartbeatMs, MIN_HEARTBEAT_MS, "below minimum"); -
return MIN_HEARTBEAT_MS; - }
- if (heartbeatMs > MAX_HEARTBEAT_MS) {
-
logHeartbeatClampWarning(socketType, heartbeatMs, MAX_HEARTBEAT_MS, "exceeds maximum"); -
return MAX_HEARTBEAT_MS; - }
- return heartbeatMs;
- }
- /**
-
- Logs a warning when the heartbeat interval is clamped.
-
-
- @param socketType The socket type
-
- @param originalMs The original interval value in milliseconds
-
- @param clampedMs The clamped interval value in milliseconds
-
- @param reason The reason for clamping
- */
- private static void logHeartbeatClampWarning(
-
String socketType, long originalMs, long clampedMs, String reason) { - LOG.log(
-
Level.WARNING, -
String.format( -
"ZMQ %s socket heartbeat interval %ds %s %ds, clamping to %ds", -
socketType, originalMs / 1000, reason, clampedMs / 1000, clampedMs / 1000)); - }
**In configureHeartbeat, guard against integer overflow by clamping the heartbeat interval in milliseconds to a safe maximum before casting to int and setting socket options. Also, enforce a sensible minimum value.**
[java/src/org/openqa/selenium/events/zeromq/ZmqUtils.java [40-52]](https://github.com/SeleniumHQ/selenium/pull/16444/files#diff-f9f1bd9c11ff8d0995d73085dc4b3e906a8aa15c1a5ba90fc6b9059a3a8080a1R40-R52)
```diff
static void configureHeartbeat(ZMQ.Socket socket, Duration heartbeatPeriod, String socketType) {
- if (heartbeatPeriod != null && !heartbeatPeriod.isZero() && !heartbeatPeriod.isNegative()) {
- int heartbeatIvl = (int) heartbeatPeriod.toMillis();
- socket.setHeartbeatIvl(heartbeatIvl);
- socket.setHeartbeatTimeout(heartbeatIvl * 3);
- socket.setHeartbeatTtl(heartbeatIvl * 6);
- LOG.info(
- String.format(
- "ZMQ %s socket heartbeat configured: interval=%dms, timeout=%dms, ttl=%dms",
- socketType, heartbeatIvl, heartbeatIvl * 3, heartbeatIvl * 6));
+ if (heartbeatPeriod == null || heartbeatPeriod.isZero() || heartbeatPeriod.isNegative()) {
+ return;
}
+ long ivlMsLong = heartbeatPeriod.toMillis();
+ // Enforce sane bounds: at least 100ms and at most Integer.MAX_VALUE / 6 to avoid overflow below
+ long min = 100L;
+ long max = Integer.MAX_VALUE / 6L;
+ if (ivlMsLong < min) {
+ ivlMsLong = min;
+ } else if (ivlMsLong > max) {
+ ivlMsLong = max;
+ }
+ int heartbeatIvl = (int) ivlMsLong;
+ int heartbeatTimeout = heartbeatIvl * 3;
+ int heartbeatTtl = heartbeatIvl * 6;
+ socket.setHeartbeatIvl(heartbeatIvl);
+ socket.setHeartbeatTimeout(heartbeatTimeout);
+ socket.setHeartbeatTtl(heartbeatTtl);
+ LOG.info(
+ String.format(
+ "ZMQ %s socket heartbeat configured: interval=%dms, timeout=%dms, ttl=%dms",
+ socketType, heartbeatIvl, heartbeatTimeout, heartbeatTtl));
}
Suggestion importance[1-10]: 8
__
Why: The suggestion correctly identifies a potential integer overflow when calculating heartbeat intervals from a large Duration, which could lead to invalid socket configuration and runtime errors.
[general] Refactor duplicated code into a utility method
✅ Refactor duplicated code into a utility method
Extract the duplicated configureHeartbeat method from BoundZmqEventBus and UnboundZmqEventBus into a static helper method in a new utility class to avoid code repetition and improve maintainability.
java/src/org/openqa/selenium/events/zeromq/BoundZmqEventBus.java [84-97]
-private void configureHeartbeat(ZMQ.Socket socket, Duration heartbeatPeriod, String socketType) {
- if (heartbeatPeriod != null && !heartbeatPeriod.isZero() && !heartbeatPeriod.isNegative()) {
- int heartbeatIvl = (int) heartbeatPeriod.toMillis();
- socket.setHeartbeatIvl(heartbeatIvl);
- // Set heartbeat timeout to 3x the interval
- socket.setHeartbeatTimeout(heartbeatIvl * 3);
- // Set heartbeat TTL to 6x the interval
- socket.setHeartbeatTtl(heartbeatIvl * 6);
- LOG.info(
- String.format(
- "Event bus %s socket heartbeat configured: interval=%dms, timeout=%dms, ttl=%dms",
- socketType, heartbeatIvl, heartbeatIvl * 3, heartbeatIvl * 6));
+// In a new file: java/src/org/openqa/selenium/events/zeromq/ZmqUtils.java
+package org.openqa.selenium.events.zeromq;
+
+import java.time.Duration;
+import java.util.logging.Logger;
+import org.zeromq.ZMQ;
+
+class ZmqUtils {
+ private static final Logger LOG = Logger.getLogger(ZmqUtils.class.getName());
+
+ private ZmqUtils() {
+ // Utility class
+ }
+
+ static void configureHeartbeat(ZMQ.Socket socket, Duration heartbeatPeriod, String socketType) {
+ if (heartbeatPeriod != null && !heartbeatPeriod.isZero() && !heartbeatPeriod.isNegative()) {
+ long periodMillis = heartbeatPeriod.toMillis();
+ if (periodMillis > Integer.MAX_VALUE) {
+ LOG.warning(
+ String.format(
+ "Heartbeat period %dms is too large. Capping at %dms.",
+ periodMillis, Integer.MAX_VALUE));
+ periodMillis = Integer.MAX_VALUE;
+ }
+ int heartbeatIvl = (int) periodMillis;
+ socket.setHeartbeatIvl(heartbeatIvl);
+ // Set heartbeat timeout to 3x the interval
+ socket.setHeartbeatTimeout(heartbeatIvl * 3);
+ // Set heartbeat TTL to 6x the interval
+ socket.setHeartbeatTtl(heartbeatIvl * 6);
+ LOG.info(
+ String.format(
+ "Event bus %s socket heartbeat configured: interval=%dms, timeout=%dms, ttl=%dms",
+ socketType, heartbeatIvl, heartbeatIvl * 3, heartbeatIvl * 6));
+ }
}
}
+// In BoundZmqEventBus.java, replace the configureHeartbeat method with:
+private void configureHeartbeat(ZMQ.Socket socket, Duration heartbeatPeriod, String socketType) {
+ ZmqUtils.configureHeartbeat(socket, heartbeatPeriod, socketType);
+}
+Suggestion importance[1-10]: 6
__
Why: The suggestion correctly identifies that the configureHeartbeat method is duplicated in BoundZmqEventBus and UnboundZmqEventBus, and proposes a valid refactoring to a shared utility method, which improves code maintainability by adhering to the DRY principle.
| PR 16442 (2025-10-15) |
[possible issue] Fix incorrect test assertion order
✅ Fix incorrect test assertion order
In the RSpec tests, move the action (e.g., network.continue_request) before the expect(...).to have_received(...) assertion to correctly verify the behavior.
rb/spec/unit/selenium/webdriver/bidi/network_spec.rb [35-41]
it 'sends only the mandatory request ID when all optional args are nil' do
expected_payload = {request: request_id}
+ network.continue_request(id: request_id)
+
expect(mock_bidi).to have_received(:send_cmd).with('network.continueRequest', expected_payload)
-
- network.continue_request(id: request_id)
endSuggestion importance[1-10]: 9
__
Why: The suggestion correctly identifies a critical flaw in the test implementation where the assertion precedes the action, rendering all new tests ineffective at verifying the intended behavior.
| PR 16427 (2025-10-14) |
[possible issue] Remove global docstring check ignore
✅ Remove global docstring check ignore
Remove the global ignore rule for docstring (D) checks from pyproject.toml to ensure the linter can report docstring issues as intended.
[tool.ruff.lint.per-file-ignores]
-"*.py" = ["D"]Suggestion importance[1-10]: 8
__
Why: The suggestion correctly identifies that the per-file-ignores configuration completely negates the enabling of docstring checks, making the primary change of this PR ineffective.
| PR 16421 (2025-10-13) |
[general] Consolidate browser skip logic
✅ Consolidate browser skip logic
Consolidate the separate if conditions for skipping the test on Chrome and Edge into a single condition using in for better readability and maintainability.
py/test/selenium/webdriver/common/bidi_network_tests.py [104-107]
-if driver.caps["browserName"] == "chrome":
- pytest.skip(reason="Request handlers don't yet work in Chrome when using classic navigation")
-if driver.caps["browserName"] == "edge":
- pytest.skip(reason="Request handlers don't yet work in Edge when using classic navigation")
+if driver.caps["browserName"] in ("chrome", "edge"):
+ pytest.skip(reason="Request handlers don't yet work in Chrome/Edge when using classic navigation")Suggestion importance[1-10]: 4
__
Why: The suggestion improves code conciseness and maintainability by consolidating two if statements into a single check, which is a good practice but has a low impact on functionality.
| PR 16403 (2025-10-08) |
[possible issue] Use a non-generic base class
✅ Use a non-generic base class
Modify _pendingCommands to use a non-generic base CommandInfo class. This will allow storing different command result types in the dictionary, improving type safety.
dotnet/src/webdriver/BiDi/Communication/Broker.cs [40]
-private readonly ConcurrentDictionary<long, CommandInfo<EmptyResult>> _pendingCommands = new();
+private readonly ConcurrentDictionary<long, CommandInfo> _pendingCommands = new();Suggestion importance[1-10]: 8
__
Why: The suggestion correctly identifies a type safety issue where _pendingCommands is constrained to CommandInfo, which forces an unsafe cast later. Proposing a non-generic base class is a valid and robust design pattern to solve this problem.
| PR 16392 (2025-10-06) |
[possible issue] Remove exception from unimplemented method
Remove exception from unimplemented method
Remove the NotImplementedException from the Initialize method in EmulationModule and other modules. If no initialization is needed, the method body should be empty to prevent runtime crashes.
dotnet/src/webdriver/BiDi/Emulation/EmulationModule.cs [93-96]
protected internal override void Initialize(Broker broker)
{
- throw new NotImplementedException();
+ // No-op for this module.
}Suggestion importance[1-10]: 9
__
Why: This suggestion correctly identifies a critical bug introduced by the PR. The Initialize method will be called upon module creation, and throwing a NotImplementedException will cause a runtime crash, making the modules unusable.
[general] Register module-specific JSON serialization context
Register module-specific JSON serialization context
Implement the Initialize method in BrowsingContextModule to register a module-specific JsonSerializerContext. This aligns with the pattern in BrowserModule and is necessary for proper JSON serialization.
dotnet/src/webdriver/BiDi/BrowsingContext/BrowsingContextModule.cs [254-257]
protected internal override void Initialize(Broker broker)
{
-
+ broker.ConfigureJsonContext(opts => opts.TypeInfoResolverChain.Add(BrowsingContextModuleJsonSerializerContext.Default));
}Suggestion importance[1-10]: 8
__
Why: The suggestion correctly points out that the PR's refactoring goal is to register module-specific JSON contexts, which is missing for BrowsingContextModule. Implementing this is crucial for completing the refactoring and gaining the performance/AOT benefits of source-generated serialization.
| PR 16386 (2025-10-05) |
[possible issue] Fix incorrect enum serialization casing
✅ Fix incorrect enum serialization casing
The UseStringEnumConverter option serializes enums to PascalCase, but the BiDi specification requires lowercase. Remove this option and instead implement custom JsonConverters for each enum to ensure correct serialization.
dotnet/src/webdriver/BiDi/Communication/Json/BiDiJsonSerializerContext.cs [189]
-[JsonSourceGenerationOptions(UseStringEnumConverter = true)]
+// This line should be removed.
+// [JsonSourceGenerationOptions(UseStringEnumConverter = true)]Suggestion importance[1-10]: 10
__
Why: The suggestion correctly identifies a critical issue where using UseStringEnumConverter would cause enum serialization to violate the WebDriver BiDi specification (PascalCase vs. lowercase), leading to communication failures.
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