Skip to content

Commit e2fc5e8

Browse files
[java] Fix 15634/ensure driver closed java (#16038)
Co-authored-by: Puja Jagani <[email protected]>
1 parent e12da0d commit e2fc5e8

File tree

5 files changed

+154
-33
lines changed

5 files changed

+154
-33
lines changed

java/src/org/openqa/selenium/remote/RemoteWebDriver.java

Lines changed: 46 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@
8888
import org.openqa.selenium.remote.http.ClientConfig;
8989
import org.openqa.selenium.remote.http.ConnectionFailedException;
9090
import org.openqa.selenium.remote.http.HttpClient;
91+
import org.openqa.selenium.remote.service.DriverCommandExecutor;
9192
import org.openqa.selenium.remote.tracing.TracedHttpClient;
9293
import org.openqa.selenium.remote.tracing.Tracer;
9394
import org.openqa.selenium.remote.tracing.opentelemetry.OpenTelemetryTracer;
@@ -241,46 +242,58 @@ protected void startSession(Capabilities capabilities) {
241242
checkNonW3CCapabilities(capabilities);
242243
checkChromeW3CFalse(capabilities);
243244

244-
Response response = execute(DriverCommand.NEW_SESSION(singleton(capabilities)));
245+
try {
246+
Response response = execute(DriverCommand.NEW_SESSION(singleton(capabilities)));
245247

246-
if (response == null) {
247-
throw new SessionNotCreatedException(
248-
"The underlying command executor returned a null response.");
249-
}
248+
if (response == null) {
249+
throw new SessionNotCreatedException(
250+
"The underlying command executor returned a null response.");
251+
}
250252

251-
Object responseValue = response.getValue();
253+
Object responseValue = response.getValue();
252254

253-
if (responseValue == null) {
254-
throw new SessionNotCreatedException(
255-
"The underlying command executor returned a response without payload: " + response);
256-
}
255+
if (responseValue == null) {
256+
throw new SessionNotCreatedException(
257+
"The underlying command executor returned a response without payload: " + response);
258+
}
257259

258-
if (!(responseValue instanceof Map)) {
259-
throw new SessionNotCreatedException(
260-
"The underlying command executor returned a response with a non well formed payload: "
261-
+ response);
262-
}
260+
if (!(responseValue instanceof Map)) {
261+
throw new SessionNotCreatedException(
262+
"The underlying command executor returned a response with a non well formed payload: "
263+
+ response);
264+
}
263265

264-
@SuppressWarnings("unchecked")
265-
Map<String, Object> rawCapabilities = (Map<String, Object>) responseValue;
266-
MutableCapabilities returnedCapabilities = new MutableCapabilities(rawCapabilities);
267-
String platformString = (String) rawCapabilities.get(PLATFORM_NAME);
268-
Platform platform;
269-
try {
270-
if (platformString == null || platformString.isEmpty()) {
271-
platform = Platform.ANY;
272-
} else {
273-
platform = Platform.fromString(platformString);
266+
@SuppressWarnings("unchecked")
267+
Map<String, Object> rawCapabilities = (Map<String, Object>) responseValue;
268+
MutableCapabilities returnedCapabilities = new MutableCapabilities(rawCapabilities);
269+
String platformString = (String) rawCapabilities.get(PLATFORM_NAME);
270+
Platform platform;
271+
try {
272+
if (platformString == null || platformString.isEmpty()) {
273+
platform = Platform.ANY;
274+
} else {
275+
platform = Platform.fromString(platformString);
276+
}
277+
} catch (WebDriverException e) {
278+
// The server probably responded with a name matching the os.name
279+
// system property. Try to recover and parse this.
280+
platform = Platform.extractFromSysProperty(platformString);
274281
}
275-
} catch (WebDriverException e) {
276-
// The server probably responded with a name matching the os.name
277-
// system property. Try to recover and parse this.
278-
platform = Platform.extractFromSysProperty(platformString);
282+
returnedCapabilities.setCapability(PLATFORM_NAME, platform);
283+
284+
this.capabilities = returnedCapabilities;
285+
sessionId = new SessionId(response.getSessionId());
286+
} catch (Exception e) {
287+
// If session creation fails, stop the driver service to prevent zombie processes
288+
if (executor instanceof DriverCommandExecutor) {
289+
try {
290+
((DriverCommandExecutor) executor).close();
291+
} catch (Exception ignored) {
292+
// Ignore cleanup exceptions, we'll propagate the original failure
293+
}
294+
}
295+
throw e;
279296
}
280-
returnedCapabilities.setCapability(PLATFORM_NAME, platform);
281-
282-
this.capabilities = returnedCapabilities;
283-
sessionId = new SessionId(response.getSessionId());
284297
}
285298

286299
public ErrorHandler getErrorHandler() {
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Licensed to the Software Freedom Conservancy (SFC) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The SFC licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
package org.openqa.selenium.chrome;
19+
20+
import static org.assertj.core.api.Assertions.assertThat;
21+
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
22+
import static org.mockito.Mockito.spy;
23+
24+
import org.junit.jupiter.api.Tag;
25+
import org.junit.jupiter.api.Test;
26+
import org.openqa.selenium.SessionNotCreatedException;
27+
28+
@Tag("UnitTests")
29+
class ChromeDriverServiceCleanupTest {
30+
31+
@Test
32+
void shouldStopServiceWhenSessionCreationFails() {
33+
// Create a Chrome options that will cause session creation to fail
34+
ChromeOptions options = new ChromeOptions();
35+
options.addArguments("--user-data-dir=/no/such/location");
36+
37+
// Create a service
38+
ChromeDriverService service = ChromeDriverService.createDefaultService();
39+
ChromeDriverService serviceSpy = spy(service);
40+
41+
// Attempt to create driver - should fail and cleanup the service
42+
assertThatExceptionOfType(SessionNotCreatedException.class)
43+
.isThrownBy(() -> new ChromeDriver(serviceSpy, options));
44+
45+
// Verify that the service was stopped
46+
assertThat(serviceSpy.isRunning()).isFalse();
47+
}
48+
}

java/test/org/openqa/selenium/edge/EdgeDriverServiceTest.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
package org.openqa.selenium.edge;
1919

2020
import static org.assertj.core.api.Assertions.assertThat;
21+
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
2122
import static org.mockito.ArgumentMatchers.any;
2223
import static org.mockito.ArgumentMatchers.anyInt;
2324
import static org.mockito.ArgumentMatchers.eq;
@@ -30,6 +31,7 @@
3031
import java.util.List;
3132
import org.junit.jupiter.api.Tag;
3233
import org.junit.jupiter.api.Test;
34+
import org.openqa.selenium.SessionNotCreatedException;
3335
import org.openqa.selenium.chromium.ChromiumDriverLogLevel;
3436

3537
@Tag("UnitTests")
@@ -87,4 +89,22 @@ void ignoreFalseLogging() {
8789
builderMock.withLoglevel(ChromiumDriverLogLevel.DEBUG).usingPort(1).withSilent(false).build();
8890
verify(builderMock).createDriverService(any(), anyInt(), any(), eq(falseSilent), any());
8991
}
92+
93+
@Test
94+
void shouldStopServiceWhenSessionCreationFails() {
95+
// Create Edge options that will cause session creation to fail
96+
EdgeOptions options = new EdgeOptions();
97+
options.addArguments("--user-data-dir=/no/such/location");
98+
99+
// Create a service
100+
EdgeDriverService service = EdgeDriverService.createDefaultService();
101+
EdgeDriverService serviceSpy = spy(service);
102+
103+
// Attempt to create driver - should fail and cleanup the service
104+
assertThatExceptionOfType(SessionNotCreatedException.class)
105+
.isThrownBy(() -> new EdgeDriver(serviceSpy, options));
106+
107+
// Verify that the service was stopped
108+
assertThat(serviceSpy.isRunning()).isFalse();
109+
}
90110
}

java/test/org/openqa/selenium/firefox/GeckoDriverServiceTest.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717

1818
package org.openqa.selenium.firefox;
1919

20+
import static org.assertj.core.api.Assertions.assertThat;
21+
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
2022
import static org.mockito.ArgumentMatchers.any;
2123
import static org.mockito.ArgumentMatchers.anyInt;
2224
import static org.mockito.ArgumentMatchers.eq;
@@ -46,4 +48,22 @@ void builderPassesTimeoutToDriverService() {
4648
builderMock.build();
4749
verify(builderMock).createDriverService(any(), anyInt(), eq(customTimeout), any(), any());
4850
}
51+
52+
@Test
53+
void shouldStopServiceWhenSessionCreationFails() {
54+
// Create Firefox options that will cause session creation to fail
55+
FirefoxOptions options = new FirefoxOptions();
56+
options.setBinary("/path/to/nonexistent/firefox/binary");
57+
58+
// Create a service
59+
GeckoDriverService service = GeckoDriverService.createDefaultService();
60+
GeckoDriverService serviceSpy = spy(service);
61+
62+
// Attempt to create driver - should fail and cleanup the service
63+
assertThatExceptionOfType(Exception.class)
64+
.isThrownBy(() -> new FirefoxDriver(serviceSpy, options));
65+
66+
// Verify that the service was stopped
67+
assertThat(serviceSpy.isRunning()).isFalse();
68+
}
4969
}

java/test/org/openqa/selenium/safari/SafariDriverServiceTest.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717

1818
package org.openqa.selenium.safari;
1919

20+
import static org.assertj.core.api.Assertions.assertThat;
21+
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
2022
import static org.mockito.ArgumentMatchers.any;
2123
import static org.mockito.ArgumentMatchers.anyInt;
2224
import static org.mockito.ArgumentMatchers.eq;
@@ -47,6 +49,24 @@ void builderPassesTimeoutToDriverService() {
4749
verify(builderMock).createDriverService(any(), anyInt(), eq(customTimeout), any(), any());
4850
}
4951

52+
@Test
53+
void shouldStopServiceWhenSessionCreationFails() {
54+
// Create Safari options that will cause session creation to fail
55+
SafariOptions options = new SafariOptions();
56+
options.setCapability("invalidCapability", "invalidValue");
57+
58+
// Create a service
59+
SafariDriverService service = SafariDriverService.createDefaultService();
60+
SafariDriverService serviceSpy = spy(service);
61+
62+
// Attempt to create driver - should fail and cleanup the service
63+
assertThatExceptionOfType(Exception.class)
64+
.isThrownBy(() -> new SafariDriver(serviceSpy, options));
65+
66+
// Verify that the service was stopped
67+
assertThat(serviceSpy.isRunning()).isFalse();
68+
}
69+
5070
public static class MockSafariDriverServiceBuilder extends SafariDriverService.Builder {
5171

5272
@Override

0 commit comments

Comments
 (0)