Skip to content

Commit

Permalink
[java] Add initial support for Selenium Manager
Browse files Browse the repository at this point in the history
  • Loading branch information
bonigarcia authored and titusfortner committed Oct 28, 2022
1 parent ba18ecc commit eecdaca
Show file tree
Hide file tree
Showing 6 changed files with 219 additions and 2 deletions.
1 change: 1 addition & 0 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ JAVA_RELEASE_TARGETS = %w[
//java/src/org/openqa/selenium/ie:ie.publish
//java/src/org/openqa/selenium/json:json.publish
//java/src/org/openqa/selenium/lift:lift.publish
//java/src/org/openqa/selenium/manager:manager.publish
//java/src/org/openqa/selenium/remote/http/jdk:jdk.publish
//java/src/org/openqa/selenium/remote/http:http.publish
//java/src/org/openqa/selenium/remote:remote.publish
Expand Down
1 change: 1 addition & 0 deletions common/manager/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ exports_files(
"windows/selenium-manager.exe",
],
visibility = [
"//java/src/org/openqa/selenium/manager:__pkg__",
"//java/test/org/openqa/selenium/chrome:__pkg__",
"//java/test/org/openqa/selenium/edge:__pkg__",
"//java/test/org/openqa/selenium/firefox:__pkg__",
Expand Down
41 changes: 41 additions & 0 deletions java/src/org/openqa/selenium/manager/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
load("@rules_jvm_external//:defs.bzl", "artifact")
load("//common:defs.bzl", "copy_file")
load("//java:defs.bzl", "java_export")
load("//java:version.bzl", "SE_VERSION")

java_export(
name = "manager",
srcs = glob(["*.java"]),
maven_coordinates = "org.seleniumhq.selenium:selenium-manager:%s" % SE_VERSION,
pom_template = "//java/src/org/openqa/selenium:template-pom",
resources = [
":manager-linux",
":manager-macos",
":manager-windows",
],
visibility = [
"//visibility:public",
],
deps = [
"//java/src/org/openqa/selenium:core",
artifact("com.google.guava:guava"),
],
)

copy_file(
name = "manager-linux",
src = "//common/manager:linux/selenium-manager",
out = "linux/selenium-manager",
)

copy_file(
name = "manager-windows",
src = "//common/manager:windows/selenium-manager.exe",
out = "windows/selenium-manager.exe",
)

copy_file(
name = "manager-macos",
src = "//common/manager:macos/selenium-manager",
out = "macos/selenium-manager",
)
160 changes: 160 additions & 0 deletions java/src/org/openqa/selenium/manager/SeleniumManager.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
// 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.manager;

import com.google.common.collect.ImmutableList;
import com.google.common.io.CharStreams;
import org.openqa.selenium.Platform;
import org.openqa.selenium.WebDriverException;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.logging.Logger;

import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
import static org.openqa.selenium.Platform.MAC;
import static org.openqa.selenium.Platform.WINDOWS;

/**
* The Selenium-Manager binaries are distributed in a JAR file (org.openqa.selenium:selenium-manager) for
* the Java binding language. Since these binaries are compressed within these JAR, we need to serialize
* the proper binary for the current platform (Windows, macOS, or Linux) as an executable file. To
* implement this we use a singleton pattern, since this way, we have a single instance in the JVM, and we
* reuse the resulting binary for all the calls to the Selenium Manager singleton during all the Java
* process lifetime, deleting the binary (stored as a local temporal file) on runtime shutdown.
*/
public class SeleniumManager {

private static final Logger LOG = Logger.getLogger(SeleniumManager.class.getName());

private static final String SELENIUM_MANAGER = "selenium-manager";
private static final String EXE = ".exe";
private static final String INFO = "INFO\t";

private static SeleniumManager manager;

private File binary;

/**
* Wrapper for the Selenium Manager binary.
*/
private SeleniumManager() {
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
if (binary != null && binary.exists()) {
try {
Files.delete(binary.toPath());
} catch (IOException e) {
LOG.warning(String.format("%s deleting temporal file: %s",
e.getClass().getSimpleName(), e.getMessage()));
}
}
}));
}

public static SeleniumManager getInstance() {
if (manager == null) {
manager = new SeleniumManager();
}
return manager;
}

/**
* Executes a process with the given arguments.
* @param command the file and arguments to execute.
* @return the standard output of the execution.
*/
private static String runCommand(String... command) {
String output = "";
try {
Process process = new ProcessBuilder(command)
.redirectErrorStream(false).start();
process.waitFor();
output = CharStreams.toString(new InputStreamReader(
process.getInputStream(), StandardCharsets.UTF_8));
} catch (InterruptedException e) {
LOG.warning(String.format("Interrupted exception running command %s: %s",
Arrays.toString(command), e.getMessage()));
Thread.currentThread().interrupt();
} catch (Exception e) {
LOG.warning(String.format("%s running command %s: %s",
e.getClass().getSimpleName(), Arrays.toString(command), e.getMessage()));
}
if (!output.startsWith(INFO)) {
throw new WebDriverException("Error running command: " + Arrays.toString(command));
}

return output.trim();
}

/**
* Determines the correct Selenium Manager binary to use.
* @return the path to the Selenium Manager binary.
*/
private File getBinary() {
if (binary == null) {
try {
Platform current = Platform.getCurrent();
String folder = "linux";
String extension = "";
if (current.is(WINDOWS)) {
extension = EXE;
folder = "windows";
} else if (current.is(MAC)) {
folder = "macos";
}
String binaryPath = String.format("%s/%s%s", folder, SELENIUM_MANAGER, extension);
try (InputStream inputStream = this.getClass().getResourceAsStream(binaryPath)) {
Path tmpPath = Files.createTempDirectory(SELENIUM_MANAGER + System.nanoTime());
File tmpFolder = tmpPath.toFile();
tmpFolder.deleteOnExit();
binary = new File(tmpFolder, SELENIUM_MANAGER + extension);
Files.copy(inputStream, binary.toPath(), REPLACE_EXISTING);
}
binary.setExecutable(true);
} catch (Exception e) {
throw new WebDriverException("Unable to obtain Selenium Manager", e);
}
}
return binary;
}

/**
* Determines the location of the correct driver.
* @param driverName which driver the service needs.
* @return the location of the driver.
*/
public String getDriverPath(String driverName) {
if (!ImmutableList.of("geckodriver", "chromedriver").contains(driverName)) {
throw new WebDriverException("Unable to locate driver with name: " + driverName);
}

String driverPath = null;
File binaryFile = getBinary();
if (binaryFile != null) {
String output = runCommand(binaryFile.getAbsolutePath(),
"--driver", driverName.replaceAll(EXE, ""));
driverPath = output.replace(INFO, "");
}
return driverPath;
}
}
1 change: 1 addition & 0 deletions java/src/org/openqa/selenium/remote/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ java_library(
"//java/src/org/openqa/selenium/devtools",
"//java/src/org/openqa/selenium/io",
"//java/src/org/openqa/selenium/json",
"//java/src/org/openqa/selenium/manager",
"//java/src/org/openqa/selenium/os",
"//java/src/org/openqa/selenium/remote/http/netty",
"//java/src/org/openqa/selenium/remote/tracing",
Expand Down
17 changes: 15 additions & 2 deletions java/src/org/openqa/selenium/remote/service/DriverService.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import org.openqa.selenium.Capabilities;
import org.openqa.selenium.WebDriverException;
import org.openqa.selenium.internal.Require;
import org.openqa.selenium.manager.SeleniumManager;
import org.openqa.selenium.net.PortProber;
import org.openqa.selenium.net.UrlChecker;
import org.openqa.selenium.os.CommandLine;
Expand All @@ -49,6 +50,7 @@
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Logger;

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

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

private final ExecutorService executorService = Executors.newFixedThreadPool(2, r -> {
Expand Down Expand Up @@ -132,13 +135,23 @@ protected static File findExecutable(
String exeDownload) {
String defaultPath = new ExecutableFinder().find(exeName);
String exePath = System.getProperty(exeProperty, defaultPath);
Require.state("The path to the driver executable", exePath).nonNull(

if (exePath == null) {
try {
exePath = SeleniumManager.getInstance().getDriverPath(exeName);
checkExecutable(new File(exePath));
} catch (Exception e) {
LOG.warning(String.format("Unable to obtain driver using Selenium Manager: %s", e.getMessage()));
}
}

String validPath = Require.state("The path to the driver executable", exePath).nonNull(
"The path to the driver executable must be set by the %s system property;"
+ " for more information, see %s. "
+ "The latest version can be downloaded from %s",
exeProperty, exeDocs, exeDownload);

File exe = new File(exePath);
File exe = new File(validPath);
checkExecutable(exe);
return exe;
}
Expand Down

0 comments on commit eecdaca

Please sign in to comment.