From 6b40d9ea765d4cdc3d4a61f7f13a87a2b2e6a037 Mon Sep 17 00:00:00 2001 From: Sandeep Suryaprasad <26169602+sandeepsuryaprasad@users.noreply.github.com> Date: Mon, 28 Oct 2024 18:19:02 +0530 Subject: [PATCH 1/8] [py] moved mypy settings from `mypy.ini` to `pyproject.toml` (#14253) fixed linting issues Co-authored-by: Diego Molina Co-authored-by: Viet Nguyen Duc --- py/mypy.ini | 39 --------------------------------------- py/pyproject.toml | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 39 deletions(-) delete mode 100644 py/mypy.ini diff --git a/py/mypy.ini b/py/mypy.ini deleted file mode 100644 index 9ccefe45dd10c..0000000000000 --- a/py/mypy.ini +++ /dev/null @@ -1,39 +0,0 @@ -; The aim in future here is we would be able to turn (most) of these flags on, however the typing technical -; debt is quite colossal right now. For now we should maybe get everything working with the config here -; then look at going after partially or completely untyped defs as a phase-2. -[mypy] -files = selenium -; warn about per-module sections in the config file that do not match any files processed. -warn_unused_configs = True -; disallows subclassing of typing.Any. -disallow_subclassing_any = False -; disallow usage of generic types that do not specify explicit type parameters. -disallow_any_generics = False -; disallow calling functions without type annotations from functions that have type annotations. -disallow_untyped_calls = False -; disallow defining functions without type annotations or with incomplete annotations. -disallow_untyped_defs = False -; disallow defining functions with incomplete type annotations. -disallow_incomplete_defs = False -; type-checks the interior of functions without type annotations. -check_untyped_defs = False -; reports an error whenever a function with type annotations is decorated with a decorator without annotations. -disallow_untyped_decorators = False -; changes the treatment of arguments with a default value of None by not implicitly making their type `typing.Optional`. -no_implicit_optional = False -; warns about casting an expression to it's inferred type. -warn_redundant_casts = True -; warns about unneeded `# type: ignore` comments. -warn_unused_ignores = True -; warns when returning a value with typing.Any from a function with a non typing.Any return type. -warn_return_any = False -; Shows a warning when encountering any code inferred to be unreachable after performing type analysis. -warn_unreachable = False - -[mypy-trio_websocket] -; suppress error messages about imports that cannot be resolved. -ignore_missing_imports = True - -[mypy-_winreg] -; suppress error messages about imports that cannot be resolved. -ignore_missing_imports = True diff --git a/py/pyproject.toml b/py/pyproject.toml index c17be72be8eb3..87d451bbf987f 100644 --- a/py/pyproject.toml +++ b/py/pyproject.toml @@ -19,3 +19,45 @@ markers = [ ] python_files = ["test_*.py", "*_test.py"] testpaths = ["test"] + +# mypy global options +[tool.mypy] +# The aim in future here is we would be able to turn (most) of these flags on, however the typing technical +# debt is quite colossal right now. For now we should maybe get everything working with the config here +# then look at going after partially or completely untyped defs as a phase-2. +files = "selenium" +# warn about per-module sections in the config file that do not match any files processed. +warn_unused_configs = true +# disallows subclassing of typing.Any. +disallow_subclassing_any = false +# disallow usage of generic types that do not specify explicit type parameters. +disallow_any_generics = false +# disallow calling functions without type annotations from functions that have type annotations. +disallow_untyped_calls = false +# disallow defining functions without type annotations or with incomplete annotations. +disallow_untyped_defs = false +# disallow defining functions with incomplete type annotations. +disallow_incomplete_defs = false +# type-checks the interior of functions without type annotations. +check_untyped_defs = false +# reports an error whenever a function with type annotations is decorated with a decorator without annotations. +disallow_untyped_decorators = false +# changes the treatment of arguments with a default value of None by not implicitly making their type `typing.Optional`. +no_implicit_optional = false +# warns about casting an expression to it's inferred type. +warn_redundant_casts = true +# warns about unneeded `# type: ignore` comments. +warn_unused_ignores = true +# warns when returning a value with typing.Any from a function with a non typing.Any return type. +warn_return_any = false +# Shows a warning when encountering any code inferred to be unreachable after performing type analysis. +warn_unreachable = false + +# mypy module specific options +[[tool.mypy.trio_websocket]] +# suppress error messages about imports that cannot be resolved. +ignore_missing_imports = true + +[[tool.mypy._winreg]] +# suppress error messages about imports that cannot be resolved. +ignore_missing_imports = true From b2702ca20d68c53e53ae9627440d41f641a7c068 Mon Sep 17 00:00:00 2001 From: Nikolay Borisenko <22616990+nvborisenko@users.noreply.github.com> Date: Mon, 28 Oct 2024 21:36:43 +0300 Subject: [PATCH 2/8] [dotnet] Treat SM's logs always as Trace to avoid SM writing at Info level (#14667) --- dotnet/src/webdriver/SeleniumManager.cs | 24 ++++-------------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/dotnet/src/webdriver/SeleniumManager.cs b/dotnet/src/webdriver/SeleniumManager.cs index 4b28f7862d138..60c8cd5255389 100644 --- a/dotnet/src/webdriver/SeleniumManager.cs +++ b/dotnet/src/webdriver/SeleniumManager.cs @@ -195,28 +195,12 @@ private static ResultResponse RunCommand(string fileName, string arguments) if (jsonResponse.Logs is not null) { - foreach (var entry in jsonResponse.Logs) + // Treat SM's logs always as Trace to avoid SM writing at Info level + if (_logger.IsEnabled(LogEventLevel.Trace)) { - switch (entry.Level) + foreach (var entry in jsonResponse.Logs) { - case "WARN": - if (_logger.IsEnabled(LogEventLevel.Warn)) - { - _logger.Warn(entry.Message); - } - break; - case "DEBUG": - if (_logger.IsEnabled(LogEventLevel.Debug)) - { - _logger.Debug(entry.Message); - } - break; - case "INFO": - if (_logger.IsEnabled(LogEventLevel.Info)) - { - _logger.Info(entry.Message); - } - break; + _logger.Trace($"{entry.Level} {entry.Message}"); } } } From 68f82b3302b6d1e7975dc0bdaea99f5f43f1db63 Mon Sep 17 00:00:00 2001 From: Priyansh Garg Date: Tue, 29 Oct 2024 00:11:41 +0530 Subject: [PATCH 3/8] [js]: Fix sendKeys command fail on FileDetector.handleFile error. (#14663) Fix sendKeys command failing on FileDetector handleFile error. Co-authored-by: David Burns Co-authored-by: Sri Harsha <12621691+harsha509@users.noreply.github.com> --- .../node/selenium-webdriver/lib/webdriver.js | 1 + .../test/lib/webdriver_test.js | 26 +++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/javascript/node/selenium-webdriver/lib/webdriver.js b/javascript/node/selenium-webdriver/lib/webdriver.js index cb60563695a06..e004e4505c1e4 100644 --- a/javascript/node/selenium-webdriver/lib/webdriver.js +++ b/javascript/node/selenium-webdriver/lib/webdriver.js @@ -2752,6 +2752,7 @@ class WebElement { keys = await this.driver_.fileDetector_.handleFile(this.driver_, keys.join('')) } catch (ex) { this.log_.severe('Error trying parse string as a file with file detector; sending keys instead' + ex) + keys = keys.join('') } return this.execute_( diff --git a/javascript/node/selenium-webdriver/test/lib/webdriver_test.js b/javascript/node/selenium-webdriver/test/lib/webdriver_test.js index f850c3e4fcac8..419fdf6f80212 100644 --- a/javascript/node/selenium-webdriver/test/lib/webdriver_test.js +++ b/javascript/node/selenium-webdriver/test/lib/webdriver_test.js @@ -932,6 +932,32 @@ describe('WebDriver', function () { return driver.findElement(By.id('foo')).sendKeys('original/', 'path') }) + + it('sendKeysWithAFileDetector_handlerError', function () { + let executor = new FakeExecutor() + .expect(CName.FIND_ELEMENT, { + using: 'css selector', + value: '*[id="foo"]', + }) + .andReturnSuccess(WebElement.buildId('one')) + .expect(CName.SEND_KEYS_TO_ELEMENT, { + id: WebElement.buildId('one'), + text: 'original/path', + value: 'original/path'.split(''), + }) + .andReturnSuccess() + .end() + + let driver = executor.createDriver() + let handleFile = function (d, path) { + assert.strictEqual(driver, d) + assert.strictEqual(path, 'original/path') + return Promise.reject('unhandled file error') + } + driver.setFileDetector({ handleFile }) + + return driver.findElement(By.id('foo')).sendKeys('original/', 'path') + }) }) describe('switchTo()', function () { From b01041fd8e1f134a098dca73b0265aaa18ad3257 Mon Sep 17 00:00:00 2001 From: Navin Chandra <98466550+navin772@users.noreply.github.com> Date: Tue, 29 Oct 2024 03:45:04 +0530 Subject: [PATCH 4/8] [py]: set consistent polling across java and python for `WebDriverWait` methods (#14626) Co-authored-by: Sri Harsha <12621691+harsha509@users.noreply.github.com> --- py/selenium/webdriver/support/wait.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/py/selenium/webdriver/support/wait.py b/py/selenium/webdriver/support/wait.py index 949b47238d20e..35bd74a695eb8 100644 --- a/py/selenium/webdriver/support/wait.py +++ b/py/selenium/webdriver/support/wait.py @@ -99,9 +99,9 @@ def until(self, method: Callable[[D], Union[Literal[False], T]], message: str = except self._ignored_exceptions as exc: screen = getattr(exc, "screen", None) stacktrace = getattr(exc, "stacktrace", None) - time.sleep(self._poll) if time.monotonic() > end_time: break + time.sleep(self._poll) raise TimeoutException(message, screen, stacktrace) def until_not(self, method: Callable[[D], T], message: str = "") -> Union[T, Literal[True]]: @@ -122,7 +122,7 @@ def until_not(self, method: Callable[[D], T], message: str = "") -> Union[T, Lit return value except self._ignored_exceptions: return True - time.sleep(self._poll) if time.monotonic() > end_time: break + time.sleep(self._poll) raise TimeoutException(message) From e9e684d86b68516fbd1922ce1885907fdb41df65 Mon Sep 17 00:00:00 2001 From: joerg1985 <16140691+joerg1985@users.noreply.github.com> Date: Tue, 29 Oct 2024 13:55:02 +0100 Subject: [PATCH 5/8] [grid] limit the number of websocket connections per session (#14410) Co-authored-by: Viet Nguyen Duc --- .../org/openqa/selenium/grid/node/Node.java | 11 +++++ .../grid/node/ProxyNodeWebsockets.java | 7 +++ .../grid/node/TryAcquireConnection.java | 45 +++++++++++++++++++ .../selenium/grid/node/config/NodeFlags.java | 9 ++++ .../grid/node/config/NodeOptions.java | 10 +++++ .../selenium/grid/node/k8s/OneShotNode.java | 15 ++++++- .../selenium/grid/node/local/LocalNode.java | 33 +++++++++++++- .../grid/node/local/LocalNodeFactory.java | 3 +- .../selenium/grid/node/local/SessionSlot.java | 9 ++++ .../selenium/grid/node/remote/RemoteNode.java | 12 +++++ .../grid/distributor/AddingNodesTest.java | 5 +++ 11 files changed, 154 insertions(+), 5 deletions(-) create mode 100644 java/src/org/openqa/selenium/grid/node/TryAcquireConnection.java diff --git a/java/src/org/openqa/selenium/grid/node/Node.java b/java/src/org/openqa/selenium/grid/node/Node.java index 09fe7d02ae9f5..bc2b5c75b5ab7 100644 --- a/java/src/org/openqa/selenium/grid/node/Node.java +++ b/java/src/org/openqa/selenium/grid/node/Node.java @@ -101,6 +101,12 @@ * by {@code sessionId}. This returns a boolean. * * + * POST + * /se/grid/node/connection/{sessionId} + * Allows the node to be ask about whether or not new websocket connections are allowed for the {@link Session} + * identified by {@code sessionId}. This returns a boolean. + * + * * * * /session/{sessionId}/* * The request is forwarded to the {@link Session} identified by {@code sessionId}. When the @@ -172,6 +178,9 @@ protected Node( get("/se/grid/node/owner/{sessionId}") .to(params -> new IsSessionOwner(this, sessionIdFrom(params))) .with(spanDecorator("node.is_session_owner").andThen(requiresSecret)), + post("/se/grid/node/connection/{sessionId}") + .to(params -> new TryAcquireConnection(this, sessionIdFrom(params))) + .with(spanDecorator("node.is_session_owner").andThen(requiresSecret)), delete("/se/grid/node/session/{sessionId}") .to(params -> new StopNodeSession(this, sessionIdFrom(params))) .with(spanDecorator("node.stop_session").andThen(requiresSecret)), @@ -244,6 +253,8 @@ public TemporaryFilesystem getDownloadsFilesystem(UUID uuid) throws IOException public abstract boolean isSessionOwner(SessionId id); + public abstract boolean tryAcquireConnection(SessionId id); + public abstract boolean isSupporting(Capabilities capabilities); public abstract NodeStatus getStatus(); diff --git a/java/src/org/openqa/selenium/grid/node/ProxyNodeWebsockets.java b/java/src/org/openqa/selenium/grid/node/ProxyNodeWebsockets.java index e3f656c069125..eff13dc5a40f5 100644 --- a/java/src/org/openqa/selenium/grid/node/ProxyNodeWebsockets.java +++ b/java/src/org/openqa/selenium/grid/node/ProxyNodeWebsockets.java @@ -94,6 +94,13 @@ public Optional> apply(String uri, Consumer downstrea return Optional.empty(); } + // ensure one session does not open to many connections, this might have a negative impact on + // the grid health + if (!node.tryAcquireConnection(id)) { + LOG.warning("Too many websocket connections initiated by " + id); + return Optional.empty(); + } + Session session = node.getSession(id); Capabilities caps = session.getCapabilities(); LOG.fine("Scanning for endpoint: " + caps); diff --git a/java/src/org/openqa/selenium/grid/node/TryAcquireConnection.java b/java/src/org/openqa/selenium/grid/node/TryAcquireConnection.java new file mode 100644 index 0000000000000..6c8822bea84cd --- /dev/null +++ b/java/src/org/openqa/selenium/grid/node/TryAcquireConnection.java @@ -0,0 +1,45 @@ +// 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. + +package org.openqa.selenium.grid.node; + +import static org.openqa.selenium.remote.http.Contents.asJson; + +import com.google.common.collect.ImmutableMap; +import java.io.UncheckedIOException; +import org.openqa.selenium.internal.Require; +import org.openqa.selenium.remote.SessionId; +import org.openqa.selenium.remote.http.HttpHandler; +import org.openqa.selenium.remote.http.HttpRequest; +import org.openqa.selenium.remote.http.HttpResponse; + +class TryAcquireConnection implements HttpHandler { + + private final Node node; + private final SessionId id; + + TryAcquireConnection(Node node, SessionId id) { + this.node = Require.nonNull("Node", node); + this.id = Require.nonNull("Session id", id); + } + + @Override + public HttpResponse execute(HttpRequest req) throws UncheckedIOException { + return new HttpResponse() + .setContent(asJson(ImmutableMap.of("value", node.tryAcquireConnection(id)))); + } +} diff --git a/java/src/org/openqa/selenium/grid/node/config/NodeFlags.java b/java/src/org/openqa/selenium/grid/node/config/NodeFlags.java index 800a0798a4e17..b56e57b3dcb97 100644 --- a/java/src/org/openqa/selenium/grid/node/config/NodeFlags.java +++ b/java/src/org/openqa/selenium/grid/node/config/NodeFlags.java @@ -18,6 +18,7 @@ package org.openqa.selenium.grid.node.config; import static org.openqa.selenium.grid.config.StandardGridRoles.NODE_ROLE; +import static org.openqa.selenium.grid.node.config.NodeOptions.DEFAULT_CONNECTION_LIMIT; import static org.openqa.selenium.grid.node.config.NodeOptions.DEFAULT_DETECT_DRIVERS; import static org.openqa.selenium.grid.node.config.NodeOptions.DEFAULT_DRAIN_AFTER_SESSION_COUNT; import static org.openqa.selenium.grid.node.config.NodeOptions.DEFAULT_ENABLE_BIDI; @@ -77,6 +78,14 @@ public class NodeFlags implements HasRoles { @ConfigValue(section = NODE_SECTION, name = "session-timeout", example = "60") public int sessionTimeout = DEFAULT_SESSION_TIMEOUT; + @Parameter( + names = {"--connection-limit-per-session"}, + description = + "Let X be the maximum number of websocket connections per session.This will ensure one" + + " session is not able to exhaust the connection limit of the host") + @ConfigValue(section = NODE_SECTION, name = "connection-limit-per-session", example = "8") + public int connectionLimitPerSession = DEFAULT_CONNECTION_LIMIT; + @Parameter( names = {"--detect-drivers"}, arity = 1, diff --git a/java/src/org/openqa/selenium/grid/node/config/NodeOptions.java b/java/src/org/openqa/selenium/grid/node/config/NodeOptions.java index 7317f6e7c8870..ff8fc6d76667a 100644 --- a/java/src/org/openqa/selenium/grid/node/config/NodeOptions.java +++ b/java/src/org/openqa/selenium/grid/node/config/NodeOptions.java @@ -73,6 +73,7 @@ public class NodeOptions { public static final int DEFAULT_HEARTBEAT_PERIOD = 60; public static final int DEFAULT_SESSION_TIMEOUT = 300; public static final int DEFAULT_DRAIN_AFTER_SESSION_COUNT = 0; + public static final int DEFAULT_CONNECTION_LIMIT = 10; public static final boolean DEFAULT_ENABLE_CDP = true; public static final boolean DEFAULT_ENABLE_BIDI = true; static final String NODE_SECTION = "node"; @@ -262,6 +263,15 @@ public int getMaxSessions() { return Math.min(maxSessions, DEFAULT_MAX_SESSIONS); } + public int getConnectionLimitPerSession() { + int connectionLimit = + config + .getInt(NODE_SECTION, "connection-limit-per-session") + .orElse(DEFAULT_CONNECTION_LIMIT); + Require.positive("Session connection limit", connectionLimit); + return connectionLimit; + } + public Duration getSessionTimeout() { // If the user sets 10s or less, we default to 10s. int seconds = diff --git a/java/src/org/openqa/selenium/grid/node/k8s/OneShotNode.java b/java/src/org/openqa/selenium/grid/node/k8s/OneShotNode.java index d293d1c6ba78c..af8c05cf7a7c1 100644 --- a/java/src/org/openqa/selenium/grid/node/k8s/OneShotNode.java +++ b/java/src/org/openqa/selenium/grid/node/k8s/OneShotNode.java @@ -34,6 +34,7 @@ import java.util.Optional; import java.util.ServiceLoader; import java.util.UUID; +import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Logger; import java.util.stream.StreamSupport; import org.openqa.selenium.Capabilities; @@ -98,6 +99,8 @@ public class OneShotNode extends Node { private final Duration heartbeatPeriod; private final URI gridUri; private final UUID slotId = UUID.randomUUID(); + private final int connectionLimitPerSession; + private final AtomicInteger connectionCounter = new AtomicInteger(); private RemoteWebDriver driver; private SessionId sessionId; private HttpClient client; @@ -114,7 +117,8 @@ private OneShotNode( URI uri, URI gridUri, Capabilities stereotype, - WebDriverInfo driverInfo) { + WebDriverInfo driverInfo, + int connectionLimitPerSession) { super(tracer, id, uri, registrationSecret, Require.positive(sessionTimeout)); this.heartbeatPeriod = heartbeatPeriod; @@ -122,6 +126,7 @@ private OneShotNode( this.gridUri = Require.nonNull("Public Grid URI", gridUri); this.stereotype = ImmutableCapabilities.copyOf(Require.nonNull("Stereotype", stereotype)); this.driverInfo = Require.nonNull("Driver info", driverInfo); + this.connectionLimitPerSession = connectionLimitPerSession; new JMXHelper().register(this); } @@ -177,7 +182,8 @@ public static Node create(Config config) { .getPublicGridUri() .orElseThrow(() -> new ConfigException("Unable to determine public grid address")), stereotype, - driverInfo); + driverInfo, + nodeOptions.getConnectionLimitPerSession()); } @Override @@ -357,6 +363,11 @@ public boolean isSessionOwner(SessionId id) { return driver != null && sessionId.equals(id); } + @Override + public boolean tryAcquireConnection(SessionId id) { + return sessionId.equals(id) && connectionLimitPerSession > connectionCounter.getAndIncrement(); + } + @Override public boolean isSupporting(Capabilities capabilities) { return driverInfo.isSupporting(capabilities); diff --git a/java/src/org/openqa/selenium/grid/node/local/LocalNode.java b/java/src/org/openqa/selenium/grid/node/local/LocalNode.java index 7304db8a87847..b42c557c91008 100644 --- a/java/src/org/openqa/selenium/grid/node/local/LocalNode.java +++ b/java/src/org/openqa/selenium/grid/node/local/LocalNode.java @@ -59,6 +59,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Collectors; @@ -127,6 +128,7 @@ public class LocalNode extends Node { private final int configuredSessionCount; private final boolean cdpEnabled; private final boolean managedDownloadsEnabled; + private final int connectionLimitPerSession; private final boolean bidiEnabled; private final AtomicBoolean drainAfterSessions = new AtomicBoolean(); @@ -153,7 +155,8 @@ protected LocalNode( Duration heartbeatPeriod, List factories, Secret registrationSecret, - boolean managedDownloadsEnabled) { + boolean managedDownloadsEnabled, + int connectionLimitPerSession) { super( tracer, new NodeId(UUID.randomUUID()), @@ -176,6 +179,7 @@ protected LocalNode( this.cdpEnabled = cdpEnabled; this.bidiEnabled = bidiEnabled; this.managedDownloadsEnabled = managedDownloadsEnabled; + this.connectionLimitPerSession = connectionLimitPerSession; this.healthCheck = healthCheck == null @@ -579,6 +583,24 @@ public boolean isSessionOwner(SessionId id) { return currentSessions.getIfPresent(id) != null; } + @Override + public boolean tryAcquireConnection(SessionId id) throws NoSuchSessionException { + SessionSlot slot = currentSessions.getIfPresent(id); + + if (slot == null) { + return false; + } + + if (connectionLimitPerSession == -1) { + // no limit + return true; + } + + AtomicLong counter = slot.getConnectionCounter(); + + return connectionLimitPerSession > counter.getAndIncrement(); + } + @Override public Session getSession(SessionId id) throws NoSuchSessionException { Require.nonNull("Session ID", id); @@ -987,6 +1009,7 @@ public static class Builder { private HealthCheck healthCheck; private Duration heartbeatPeriod = Duration.ofSeconds(NodeOptions.DEFAULT_HEARTBEAT_PERIOD); private boolean managedDownloadsEnabled = false; + private int connectionLimitPerSession = -1; private Builder(Tracer tracer, EventBus bus, URI uri, URI gridUri, Secret registrationSecret) { this.tracer = Require.nonNull("Tracer", tracer); @@ -1041,6 +1064,11 @@ public Builder enableManagedDownloads(boolean enable) { return this; } + public Builder connectionLimitPerSession(int connectionLimitPerSession) { + this.connectionLimitPerSession = connectionLimitPerSession; + return this; + } + public LocalNode build() { return new LocalNode( tracer, @@ -1057,7 +1085,8 @@ public LocalNode build() { heartbeatPeriod, factories.build(), registrationSecret, - managedDownloadsEnabled); + managedDownloadsEnabled, + connectionLimitPerSession); } public Advanced advanced() { diff --git a/java/src/org/openqa/selenium/grid/node/local/LocalNodeFactory.java b/java/src/org/openqa/selenium/grid/node/local/LocalNodeFactory.java index 4224b2483f9db..600f516b02992 100644 --- a/java/src/org/openqa/selenium/grid/node/local/LocalNodeFactory.java +++ b/java/src/org/openqa/selenium/grid/node/local/LocalNodeFactory.java @@ -70,7 +70,8 @@ public static Node create(Config config) { .enableCdp(nodeOptions.isCdpEnabled()) .enableBiDi(nodeOptions.isBiDiEnabled()) .enableManagedDownloads(nodeOptions.isManagedDownloadsEnabled()) - .heartbeatPeriod(nodeOptions.getHeartbeatPeriod()); + .heartbeatPeriod(nodeOptions.getHeartbeatPeriod()) + .connectionLimitPerSession(nodeOptions.getConnectionLimitPerSession()); List> builders = new ArrayList<>(); ServiceLoader.load(DriverService.Builder.class).forEach(builders::add); diff --git a/java/src/org/openqa/selenium/grid/node/local/SessionSlot.java b/java/src/org/openqa/selenium/grid/node/local/SessionSlot.java index 5b84accc84c31..3c51b785c13c0 100644 --- a/java/src/org/openqa/selenium/grid/node/local/SessionSlot.java +++ b/java/src/org/openqa/selenium/grid/node/local/SessionSlot.java @@ -21,6 +21,7 @@ import java.util.ServiceLoader; import java.util.UUID; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; import java.util.function.Function; import java.util.function.Predicate; import java.util.logging.Level; @@ -59,6 +60,7 @@ public class SessionSlot private final AtomicBoolean reserved = new AtomicBoolean(false); private final boolean supportingCdp; private final boolean supportingBiDi; + private final AtomicLong connectionCounter; private ActiveSession currentSession; public SessionSlot(EventBus bus, Capabilities stereotype, SessionFactory factory) { @@ -68,6 +70,7 @@ public SessionSlot(EventBus bus, Capabilities stereotype, SessionFactory factory this.factory = Require.nonNull("Session factory", factory); this.supportingCdp = isSlotSupportingCdp(this.stereotype); this.supportingBiDi = isSlotSupportingBiDi(this.stereotype); + this.connectionCounter = new AtomicLong(); } public UUID getId() { @@ -112,6 +115,7 @@ public void stop() { LOG.log(Level.WARNING, "Unable to cleanly close session", e); } currentSession = null; + connectionCounter.set(0); release(); bus.fire(new SessionClosedEvent(id)); LOG.info(String.format("Stopping session %s", id)); @@ -148,6 +152,7 @@ public Either apply(CreateSessionRequest sess if (possibleSession.isRight()) { ActiveSession session = possibleSession.right(); currentSession = session; + connectionCounter.set(0); return Either.right(session); } else { return Either.left(possibleSession.left()); @@ -185,4 +190,8 @@ public boolean hasRelayFactory() { public boolean isRelayServiceUp() { return hasRelayFactory() && ((RelaySessionFactory) factory).isServiceUp(); } + + public AtomicLong getConnectionCounter() { + return connectionCounter; + } } diff --git a/java/src/org/openqa/selenium/grid/node/remote/RemoteNode.java b/java/src/org/openqa/selenium/grid/node/remote/RemoteNode.java index ae7cc8e1af9fb..5df5da5969c42 100644 --- a/java/src/org/openqa/selenium/grid/node/remote/RemoteNode.java +++ b/java/src/org/openqa/selenium/grid/node/remote/RemoteNode.java @@ -174,6 +174,18 @@ public boolean isSessionOwner(SessionId id) { return Boolean.TRUE.equals(Values.get(res, Boolean.class)); } + @Override + public boolean tryAcquireConnection(SessionId id) { + Require.nonNull("Session ID", id); + + HttpRequest req = new HttpRequest(POST, "/se/grid/node/connection/" + id); + HttpTracing.inject(tracer, tracer.getCurrentContext(), req); + + HttpResponse res = client.with(addSecret).execute(req); + + return Boolean.TRUE.equals(Values.get(res, Boolean.class)); + } + @Override public Session getSession(SessionId id) throws NoSuchSessionException { Require.nonNull("Session ID", id); diff --git a/java/test/org/openqa/selenium/grid/distributor/AddingNodesTest.java b/java/test/org/openqa/selenium/grid/distributor/AddingNodesTest.java index 0649e1bbee235..1485d04fca4c6 100644 --- a/java/test/org/openqa/selenium/grid/distributor/AddingNodesTest.java +++ b/java/test/org/openqa/selenium/grid/distributor/AddingNodesTest.java @@ -445,6 +445,11 @@ public boolean isSessionOwner(SessionId id) { return running != null && running.getId().equals(id); } + @Override + public boolean tryAcquireConnection(SessionId id) { + return false; + } + @Override public boolean isSupporting(Capabilities capabilities) { return Objects.equals("cake", capabilities.getCapability("cheese")); From 8e95ea95676b5ea79c3becc3b51d1160279cd4c2 Mon Sep 17 00:00:00 2001 From: Sandeep Suryaprasad <26169602+sandeepsuryaprasad@users.noreply.github.com> Date: Tue, 29 Oct 2024 18:27:50 +0530 Subject: [PATCH 6/8] [py] moved `isort`, `black` and `docformatter` settings from `tox.ini` file to `pyproject.toml` (#14671) * moved isort,black and docformatter settings to pyproject.toml * moved isort, black and docformatter settings to pyproject.toml * removed redundant pytest settings from setup.cfg --- py/pyproject.toml | 16 ++++++++++++++++ py/setup.cfg | 5 ----- py/tox.ini | 11 ----------- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/py/pyproject.toml b/py/pyproject.toml index 87d451bbf987f..8a5e26071de6a 100644 --- a/py/pyproject.toml +++ b/py/pyproject.toml @@ -61,3 +61,19 @@ ignore_missing_imports = true [[tool.mypy._winreg]] # suppress error messages about imports that cannot be resolved. ignore_missing_imports = true + +[tool.isort] +# isort is a common python tool for keeping imports nicely formatted. +# Automatically keep imports alphabetically sorted, on single lines in +# PEP recommended sections (https://peps.python.org/pep-0008/#imports) +# files or individual lines can be ignored via `# isort:skip|# isort:skip_file`. +profile = "black" +py_version=38 +force_single_line = true + +[tool.black] +line-length = 120 +target-version = ['py38'] + +[tool.docformatter] +recursive = true diff --git a/py/setup.cfg b/py/setup.cfg index 0cda7cace9e8c..c8eb38080b7d3 100644 --- a/py/setup.cfg +++ b/py/setup.cfg @@ -4,8 +4,3 @@ exclude = .tox,docs/source/conf.py,*venv extend-ignore = E501, E203 # This does nothing for now as E501 is ignored. max-line-length = 120 - -[tool:pytest] -addopts = -ra -python_files = test_*.py *_tests.py -testpaths = test diff --git a/py/tox.ini b/py/tox.ini index f454af1ee3347..fefb4daa3997f 100644 --- a/py/tox.ini +++ b/py/tox.ini @@ -25,17 +25,6 @@ deps = trio-typing==0.7.0 commands = mypy --install-types {posargs} - -[isort] -; isort is a common python tool for keeping imports nicely formatted. -; Automatically keep imports alphabetically sorted, on single lines in -; PEP recommended sections (https://peps.python.org/pep-0008/#imports) -; files or individual lines can be ignored via `# isort:skip|# isort:skip_file`. -profile = black -py_version=38 -force_single_line = True - - [testenv:linting-ci] ; checks linting for CI with stricter exiting when failing. skip_install = true From 9b8cfb1c7e5777c887743751253d3770e61d374d Mon Sep 17 00:00:00 2001 From: BlitzDestroyer <143762104+BlitzDestroyer@users.noreply.github.com> Date: Tue, 29 Oct 2024 08:59:51 -0500 Subject: [PATCH 7/8] [dotnet] Fixed typo in ResponseData MymeType -> MimeType (#14670) fixed typo in ResponseData MymeType -> MimeType --- dotnet/src/webdriver/BiDi/Modules/Network/ResponseData.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet/src/webdriver/BiDi/Modules/Network/ResponseData.cs b/dotnet/src/webdriver/BiDi/Modules/Network/ResponseData.cs index 6096bda7fd8a6..3d21c2c202fe8 100644 --- a/dotnet/src/webdriver/BiDi/Modules/Network/ResponseData.cs +++ b/dotnet/src/webdriver/BiDi/Modules/Network/ResponseData.cs @@ -11,7 +11,7 @@ public record ResponseData(string Url, string StatusText, bool FromCache, IReadOnlyList
Headers, - string MymeType, + string MimeType, long BytesReceived, long? HeadersSize, long? BodySize, From f391cd018c2735a13da10c8bb6d455211ee8a787 Mon Sep 17 00:00:00 2001 From: Simon Benzer <69980130+shbenzer@users.noreply.github.com> Date: Tue, 29 Oct 2024 10:21:47 -0400 Subject: [PATCH 8/8] [py] Added more internal logging for CDP (#14668) Co-authored-by: Viet Nguyen Duc --- py/selenium/webdriver/common/bidi/cdp.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/py/selenium/webdriver/common/bidi/cdp.py b/py/selenium/webdriver/common/bidi/cdp.py index c4cb0feeedf40..c9ed47825e4da 100644 --- a/py/selenium/webdriver/common/bidi/cdp.py +++ b/py/selenium/webdriver/common/bidi/cdp.py @@ -211,13 +211,19 @@ async def execute(self, cmd: typing.Generator[dict, T, typing.Any]) -> T: if self.session_id: request["sessionId"] = self.session_id request_str = json.dumps(request) + if logger.isEnabledFor(logging.DEBUG): + logger.debug(f"Sending CDP message: {cmd_id} {cmd_event}: {request_str}") try: await self.ws.send_message(request_str) except WsConnectionClosed as wcc: raise CdpConnectionClosed(wcc.reason) from None await cmd_event.wait() response = self.inflight_result.pop(cmd_id) + if logger.isEnabledFor(logging.DEBUG): + logger.debug(f"Received CDP message: {response}") if isinstance(response, Exception): + if logger.isEnabledFor(logging.DEBUG): + logger.debug(f"Exception raised by {cmd_event} message: {type(response).__name__}") raise response return response