Skip to content

Commit eecdaca

Browse files
bonigarciatitusfortner
authored andcommitted
[java] Add initial support for Selenium Manager
1 parent ba18ecc commit eecdaca

File tree

6 files changed

+219
-2
lines changed

6 files changed

+219
-2
lines changed

Rakefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ JAVA_RELEASE_TARGETS = %w[
110110
//java/src/org/openqa/selenium/ie:ie.publish
111111
//java/src/org/openqa/selenium/json:json.publish
112112
//java/src/org/openqa/selenium/lift:lift.publish
113+
//java/src/org/openqa/selenium/manager:manager.publish
113114
//java/src/org/openqa/selenium/remote/http/jdk:jdk.publish
114115
//java/src/org/openqa/selenium/remote/http:http.publish
115116
//java/src/org/openqa/selenium/remote:remote.publish

common/manager/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ exports_files(
2020
"windows/selenium-manager.exe",
2121
],
2222
visibility = [
23+
"//java/src/org/openqa/selenium/manager:__pkg__",
2324
"//java/test/org/openqa/selenium/chrome:__pkg__",
2425
"//java/test/org/openqa/selenium/edge:__pkg__",
2526
"//java/test/org/openqa/selenium/firefox:__pkg__",
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
load("@rules_jvm_external//:defs.bzl", "artifact")
2+
load("//common:defs.bzl", "copy_file")
3+
load("//java:defs.bzl", "java_export")
4+
load("//java:version.bzl", "SE_VERSION")
5+
6+
java_export(
7+
name = "manager",
8+
srcs = glob(["*.java"]),
9+
maven_coordinates = "org.seleniumhq.selenium:selenium-manager:%s" % SE_VERSION,
10+
pom_template = "//java/src/org/openqa/selenium:template-pom",
11+
resources = [
12+
":manager-linux",
13+
":manager-macos",
14+
":manager-windows",
15+
],
16+
visibility = [
17+
"//visibility:public",
18+
],
19+
deps = [
20+
"//java/src/org/openqa/selenium:core",
21+
artifact("com.google.guava:guava"),
22+
],
23+
)
24+
25+
copy_file(
26+
name = "manager-linux",
27+
src = "//common/manager:linux/selenium-manager",
28+
out = "linux/selenium-manager",
29+
)
30+
31+
copy_file(
32+
name = "manager-windows",
33+
src = "//common/manager:windows/selenium-manager.exe",
34+
out = "windows/selenium-manager.exe",
35+
)
36+
37+
copy_file(
38+
name = "manager-macos",
39+
src = "//common/manager:macos/selenium-manager",
40+
out = "macos/selenium-manager",
41+
)
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
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+
package org.openqa.selenium.manager;
18+
19+
import com.google.common.collect.ImmutableList;
20+
import com.google.common.io.CharStreams;
21+
import org.openqa.selenium.Platform;
22+
import org.openqa.selenium.WebDriverException;
23+
24+
import java.io.File;
25+
import java.io.IOException;
26+
import java.io.InputStream;
27+
import java.io.InputStreamReader;
28+
import java.nio.charset.StandardCharsets;
29+
import java.nio.file.Files;
30+
import java.nio.file.Path;
31+
import java.util.Arrays;
32+
import java.util.logging.Logger;
33+
34+
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
35+
import static org.openqa.selenium.Platform.MAC;
36+
import static org.openqa.selenium.Platform.WINDOWS;
37+
38+
/**
39+
* The Selenium-Manager binaries are distributed in a JAR file (org.openqa.selenium:selenium-manager) for
40+
* the Java binding language. Since these binaries are compressed within these JAR, we need to serialize
41+
* the proper binary for the current platform (Windows, macOS, or Linux) as an executable file. To
42+
* implement this we use a singleton pattern, since this way, we have a single instance in the JVM, and we
43+
* reuse the resulting binary for all the calls to the Selenium Manager singleton during all the Java
44+
* process lifetime, deleting the binary (stored as a local temporal file) on runtime shutdown.
45+
*/
46+
public class SeleniumManager {
47+
48+
private static final Logger LOG = Logger.getLogger(SeleniumManager.class.getName());
49+
50+
private static final String SELENIUM_MANAGER = "selenium-manager";
51+
private static final String EXE = ".exe";
52+
private static final String INFO = "INFO\t";
53+
54+
private static SeleniumManager manager;
55+
56+
private File binary;
57+
58+
/**
59+
* Wrapper for the Selenium Manager binary.
60+
*/
61+
private SeleniumManager() {
62+
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
63+
if (binary != null && binary.exists()) {
64+
try {
65+
Files.delete(binary.toPath());
66+
} catch (IOException e) {
67+
LOG.warning(String.format("%s deleting temporal file: %s",
68+
e.getClass().getSimpleName(), e.getMessage()));
69+
}
70+
}
71+
}));
72+
}
73+
74+
public static SeleniumManager getInstance() {
75+
if (manager == null) {
76+
manager = new SeleniumManager();
77+
}
78+
return manager;
79+
}
80+
81+
/**
82+
* Executes a process with the given arguments.
83+
* @param command the file and arguments to execute.
84+
* @return the standard output of the execution.
85+
*/
86+
private static String runCommand(String... command) {
87+
String output = "";
88+
try {
89+
Process process = new ProcessBuilder(command)
90+
.redirectErrorStream(false).start();
91+
process.waitFor();
92+
output = CharStreams.toString(new InputStreamReader(
93+
process.getInputStream(), StandardCharsets.UTF_8));
94+
} catch (InterruptedException e) {
95+
LOG.warning(String.format("Interrupted exception running command %s: %s",
96+
Arrays.toString(command), e.getMessage()));
97+
Thread.currentThread().interrupt();
98+
} catch (Exception e) {
99+
LOG.warning(String.format("%s running command %s: %s",
100+
e.getClass().getSimpleName(), Arrays.toString(command), e.getMessage()));
101+
}
102+
if (!output.startsWith(INFO)) {
103+
throw new WebDriverException("Error running command: " + Arrays.toString(command));
104+
}
105+
106+
return output.trim();
107+
}
108+
109+
/**
110+
* Determines the correct Selenium Manager binary to use.
111+
* @return the path to the Selenium Manager binary.
112+
*/
113+
private File getBinary() {
114+
if (binary == null) {
115+
try {
116+
Platform current = Platform.getCurrent();
117+
String folder = "linux";
118+
String extension = "";
119+
if (current.is(WINDOWS)) {
120+
extension = EXE;
121+
folder = "windows";
122+
} else if (current.is(MAC)) {
123+
folder = "macos";
124+
}
125+
String binaryPath = String.format("%s/%s%s", folder, SELENIUM_MANAGER, extension);
126+
try (InputStream inputStream = this.getClass().getResourceAsStream(binaryPath)) {
127+
Path tmpPath = Files.createTempDirectory(SELENIUM_MANAGER + System.nanoTime());
128+
File tmpFolder = tmpPath.toFile();
129+
tmpFolder.deleteOnExit();
130+
binary = new File(tmpFolder, SELENIUM_MANAGER + extension);
131+
Files.copy(inputStream, binary.toPath(), REPLACE_EXISTING);
132+
}
133+
binary.setExecutable(true);
134+
} catch (Exception e) {
135+
throw new WebDriverException("Unable to obtain Selenium Manager", e);
136+
}
137+
}
138+
return binary;
139+
}
140+
141+
/**
142+
* Determines the location of the correct driver.
143+
* @param driverName which driver the service needs.
144+
* @return the location of the driver.
145+
*/
146+
public String getDriverPath(String driverName) {
147+
if (!ImmutableList.of("geckodriver", "chromedriver").contains(driverName)) {
148+
throw new WebDriverException("Unable to locate driver with name: " + driverName);
149+
}
150+
151+
String driverPath = null;
152+
File binaryFile = getBinary();
153+
if (binaryFile != null) {
154+
String output = runCommand(binaryFile.getAbsolutePath(),
155+
"--driver", driverName.replaceAll(EXE, ""));
156+
driverPath = output.replace(INFO, "");
157+
}
158+
return driverPath;
159+
}
160+
}

java/src/org/openqa/selenium/remote/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ java_library(
5353
"//java/src/org/openqa/selenium/devtools",
5454
"//java/src/org/openqa/selenium/io",
5555
"//java/src/org/openqa/selenium/json",
56+
"//java/src/org/openqa/selenium/manager",
5657
"//java/src/org/openqa/selenium/os",
5758
"//java/src/org/openqa/selenium/remote/http/netty",
5859
"//java/src/org/openqa/selenium/remote/tracing",

java/src/org/openqa/selenium/remote/service/DriverService.java

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.openqa.selenium.Capabilities;
2828
import org.openqa.selenium.WebDriverException;
2929
import org.openqa.selenium.internal.Require;
30+
import org.openqa.selenium.manager.SeleniumManager;
3031
import org.openqa.selenium.net.PortProber;
3132
import org.openqa.selenium.net.UrlChecker;
3233
import org.openqa.selenium.os.CommandLine;
@@ -49,6 +50,7 @@
4950
import java.util.concurrent.TimeUnit;
5051
import java.util.concurrent.TimeoutException;
5152
import java.util.concurrent.locks.ReentrantLock;
53+
import java.util.logging.Logger;
5254

5355
/**
5456
* Manages the life and death of a native executable driver server.
@@ -62,6 +64,7 @@
6264
public class DriverService implements Closeable {
6365

6466
private static final String NAME = "Driver Service Executor";
67+
private static final Logger LOG = Logger.getLogger(DriverService.class.getName());
6568
protected static final Duration DEFAULT_TIMEOUT = Duration.ofSeconds(20);
6669

6770
private final ExecutorService executorService = Executors.newFixedThreadPool(2, r -> {
@@ -132,13 +135,23 @@ protected static File findExecutable(
132135
String exeDownload) {
133136
String defaultPath = new ExecutableFinder().find(exeName);
134137
String exePath = System.getProperty(exeProperty, defaultPath);
135-
Require.state("The path to the driver executable", exePath).nonNull(
138+
139+
if (exePath == null) {
140+
try {
141+
exePath = SeleniumManager.getInstance().getDriverPath(exeName);
142+
checkExecutable(new File(exePath));
143+
} catch (Exception e) {
144+
LOG.warning(String.format("Unable to obtain driver using Selenium Manager: %s", e.getMessage()));
145+
}
146+
}
147+
148+
String validPath = Require.state("The path to the driver executable", exePath).nonNull(
136149
"The path to the driver executable must be set by the %s system property;"
137150
+ " for more information, see %s. "
138151
+ "The latest version can be downloaded from %s",
139152
exeProperty, exeDocs, exeDownload);
140153

141-
File exe = new File(exePath);
154+
File exe = new File(validPath);
142155
checkExecutable(exe);
143156
return exe;
144157
}

0 commit comments

Comments
 (0)