diff --git a/java/src/org/openqa/selenium/chrome/ChromeDriver.java b/java/src/org/openqa/selenium/chrome/ChromeDriver.java index 663e568c390a3..8e3f89754c99e 100644 --- a/java/src/org/openqa/selenium/chrome/ChromeDriver.java +++ b/java/src/org/openqa/selenium/chrome/ChromeDriver.java @@ -25,6 +25,7 @@ import org.openqa.selenium.chromium.ChromiumDriver; import org.openqa.selenium.chromium.ChromiumDriverCommandExecutor; import org.openqa.selenium.internal.Require; +import org.openqa.selenium.manager.SeleniumManagerOutput.Result; import org.openqa.selenium.remote.CommandInfo; import org.openqa.selenium.remote.RemoteWebDriver; import org.openqa.selenium.remote.RemoteWebDriverBuilder; @@ -95,8 +96,9 @@ private static ChromeDriverCommandExecutor generateExecutor( Require.nonNull("Driver options", options); Require.nonNull("Driver clientConfig", clientConfig); if (service.getExecutable() == null) { - String path = DriverFinder.getPath(service, options); - service.setExecutable(path); + Result result = DriverFinder.getPath(service, options); + service.setExecutable(result.getDriverPath()); + options.setBinary(result.getBrowserPath()); } return new ChromeDriverCommandExecutor(service, clientConfig); } diff --git a/java/src/org/openqa/selenium/edge/EdgeDriver.java b/java/src/org/openqa/selenium/edge/EdgeDriver.java index ff7581c4b8834..2a7651395123c 100644 --- a/java/src/org/openqa/selenium/edge/EdgeDriver.java +++ b/java/src/org/openqa/selenium/edge/EdgeDriver.java @@ -23,6 +23,8 @@ import org.openqa.selenium.chromium.ChromiumDriver; import org.openqa.selenium.chromium.ChromiumDriverCommandExecutor; import org.openqa.selenium.internal.Require; +import org.openqa.selenium.manager.SeleniumManagerOutput; +import org.openqa.selenium.manager.SeleniumManagerOutput.Result; import org.openqa.selenium.remote.CommandInfo; import org.openqa.selenium.remote.RemoteWebDriver; import org.openqa.selenium.remote.RemoteWebDriverBuilder; @@ -67,8 +69,9 @@ private static EdgeDriverCommandExecutor generateExecutor( Require.nonNull("Driver options", options); Require.nonNull("Driver clientConfig", clientConfig); if (service.getExecutable() == null) { - String path = DriverFinder.getPath(service, options); - service.setExecutable(path); + Result result = DriverFinder.getPath(service, options); + service.setExecutable(result.getDriverPath()); + options.setBinary(result.getBrowserPath()); } return new EdgeDriverCommandExecutor(service, clientConfig); } diff --git a/java/src/org/openqa/selenium/firefox/FirefoxDriver.java b/java/src/org/openqa/selenium/firefox/FirefoxDriver.java index 61d8e1cadb065..1fe1aa18d53a6 100644 --- a/java/src/org/openqa/selenium/firefox/FirefoxDriver.java +++ b/java/src/org/openqa/selenium/firefox/FirefoxDriver.java @@ -50,6 +50,8 @@ import org.openqa.selenium.html5.SessionStorage; import org.openqa.selenium.html5.WebStorage; import org.openqa.selenium.internal.Require; +import org.openqa.selenium.manager.SeleniumManagerOutput; +import org.openqa.selenium.manager.SeleniumManagerOutput.Result; import org.openqa.selenium.remote.CommandInfo; import org.openqa.selenium.remote.FileDetector; import org.openqa.selenium.remote.RemoteWebDriver; @@ -137,8 +139,9 @@ private static FirefoxDriverCommandExecutor generateExecutor( Require.nonNull("Driver options", options); Require.nonNull("Driver clientConfig", clientConfig); if (service.getExecutable() == null) { - String path = DriverFinder.getPath(service, options); - service.setExecutable(path); + Result result = DriverFinder.getPath(service, options); + service.setExecutable(result.getDriverPath()); + options.setBinary(result.getBrowserPath()); } return new FirefoxDriverCommandExecutor(service, clientConfig); } diff --git a/java/src/org/openqa/selenium/grid/node/config/BUILD.bazel b/java/src/org/openqa/selenium/grid/node/config/BUILD.bazel index 620d0cc0b7385..d71932bada408 100644 --- a/java/src/org/openqa/selenium/grid/node/config/BUILD.bazel +++ b/java/src/org/openqa/selenium/grid/node/config/BUILD.bazel @@ -11,11 +11,11 @@ java_library( ], deps = [ "//java:auto-service", - "//java/src/org/openqa/selenium/chromium", "//java/src/org/openqa/selenium/grid/config", "//java/src/org/openqa/selenium/grid/data", "//java/src/org/openqa/selenium/grid/node", "//java/src/org/openqa/selenium/json", + "//java/src/org/openqa/selenium/manager", "//java/src/org/openqa/selenium/remote", artifact("com.beust:jcommander"), artifact("com.google.guava:guava"), diff --git a/java/src/org/openqa/selenium/grid/node/config/DriverServiceSessionFactory.java b/java/src/org/openqa/selenium/grid/node/config/DriverServiceSessionFactory.java index bab7238633756..1287cb8bcfc48 100644 --- a/java/src/org/openqa/selenium/grid/node/config/DriverServiceSessionFactory.java +++ b/java/src/org/openqa/selenium/grid/node/config/DriverServiceSessionFactory.java @@ -26,7 +26,9 @@ import java.net.URL; import java.time.Duration; import java.time.Instant; +import java.util.Arrays; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; @@ -48,6 +50,7 @@ import org.openqa.selenium.grid.node.SessionFactory; import org.openqa.selenium.internal.Either; import org.openqa.selenium.internal.Require; +import org.openqa.selenium.manager.SeleniumManagerOutput.Result; import org.openqa.selenium.net.HostIdentifier; import org.openqa.selenium.net.NetworkUtils; import org.openqa.selenium.remote.Command; @@ -58,6 +61,7 @@ import org.openqa.selenium.remote.SessionId; import org.openqa.selenium.remote.http.ClientConfig; import org.openqa.selenium.remote.http.HttpClient; +import org.openqa.selenium.remote.service.DriverFinder; import org.openqa.selenium.remote.service.DriverService; import org.openqa.selenium.remote.tracing.AttributeKey; import org.openqa.selenium.remote.tracing.EventAttribute; @@ -134,6 +138,13 @@ public Either apply(CreateSessionRequest sess AttributeKey.LOGGER_CLASS.getKey(), EventAttribute.setValue(this.getClass().getName())); DriverService service = builder.build(); + if (service.getExecutable() == null) { + Result result = DriverFinder.getPath(service, capabilities); + service.setExecutable(result.getDriverPath()); + if (result.getBrowserPath() != null) { + capabilities = setBrowserBinary(capabilities, result.getBrowserPath()); + } + } try { service.start(); @@ -327,4 +338,27 @@ private String getHost() { return HostIdentifier.getHostName(); } } + + private Capabilities setBrowserBinary(Capabilities options, String browserPath) { + List vendorOptionsCapabilities = + Arrays.asList("moz:firefoxOptions", "goog:chromeOptions", "ms:edgeOptions"); + for (String vendorOptionsCapability : vendorOptionsCapabilities) { + if (options.asMap().containsKey(vendorOptionsCapability)) { + try { + @SuppressWarnings("unchecked") + Map vendorOptions = + (Map) options.getCapability(vendorOptionsCapability); + vendorOptions.put("binary", browserPath); + return new PersistentCapabilities(options) + .setCapability(vendorOptionsCapability, vendorOptions); + } catch (Exception e) { + LOG.warning( + String.format( + "Exception while setting the browser binary path. %s: %s", + options, e.getMessage())); + } + } + } + return options; + } } diff --git a/java/src/org/openqa/selenium/ie/InternetExplorerDriver.java b/java/src/org/openqa/selenium/ie/InternetExplorerDriver.java index 5cc15b0702409..99de6989b8b53 100644 --- a/java/src/org/openqa/selenium/ie/InternetExplorerDriver.java +++ b/java/src/org/openqa/selenium/ie/InternetExplorerDriver.java @@ -133,7 +133,7 @@ public InternetExplorerDriver( service = InternetExplorerDriverService.createDefaultService(); } if (service.getExecutable() == null) { - String path = DriverFinder.getPath(service, options); + String path = DriverFinder.getPath(service, options).getDriverPath(); service.setExecutable(path); } if (clientConfig == null) { diff --git a/java/src/org/openqa/selenium/manager/SeleniumManager.java b/java/src/org/openqa/selenium/manager/SeleniumManager.java index ccc5185059fbe..a06a6348f3f82 100644 --- a/java/src/org/openqa/selenium/manager/SeleniumManager.java +++ b/java/src/org/openqa/selenium/manager/SeleniumManager.java @@ -41,6 +41,7 @@ import org.openqa.selenium.WebDriverException; import org.openqa.selenium.json.Json; import org.openqa.selenium.json.JsonException; +import org.openqa.selenium.manager.SeleniumManagerOutput.Result; /** * This implementation is still in beta, and may change. @@ -100,7 +101,7 @@ public static SeleniumManager getInstance() { * @param command the file and arguments to execute. * @return the standard output of the execution. */ - private static String runCommand(String... command) { + private static Result runCommand(String... command) { LOG.fine(String.format("Executing Process: %s", Arrays.toString(command))); String output; int code; @@ -118,12 +119,12 @@ private static String runCommand(String... command) { } catch (Exception e) { throw new WebDriverException("Failed to run command: " + Arrays.toString(command), e); } - SeleniumManagerJsonOutput jsonOutput = null; + SeleniumManagerOutput jsonOutput = null; JsonException failedToParse = null; String dump = output; if (!output.isEmpty()) { try { - jsonOutput = new Json().toType(output, SeleniumManagerJsonOutput.class); + jsonOutput = new Json().toType(output, SeleniumManagerOutput.class); jsonOutput.logs.forEach( logged -> { if (logged.level.equalsIgnoreCase(WARN)) { @@ -147,12 +148,12 @@ private static String runCommand(String... command) { + "\n" + dump, failedToParse); - } else if (failedToParse != null) { + } else if (failedToParse != null || jsonOutput == null) { throw new WebDriverException( "Failed to parse json output, executed: " + Arrays.toString(command) + "\n" + dump, failedToParse); } - return jsonOutput.result.message; + return jsonOutput.result; } /** @@ -223,7 +224,7 @@ private String getBrowserBinary(Capabilities options) { * @param options Browser Options instance. * @return the location of the driver. */ - public String getDriverPath(Capabilities options, boolean offline) { + public Result getDriverPath(Capabilities options, boolean offline) { File binaryFile = getBinary(); if (binaryFile == null) { return null; @@ -265,9 +266,12 @@ public String getDriverPath(Capabilities options, boolean offline) { } } - String path = runCommand(commandList.toArray(new String[0])); - LOG.fine(String.format("Using driver at location: %s", path)); - return path; + Result result = runCommand(commandList.toArray(new String[0])); + LOG.fine( + String.format( + "Using driver at location: %s, browser at location %s", + result.getDriverPath(), result.getBrowserPath())); + return result; } private Level getLogLevel() { diff --git a/java/src/org/openqa/selenium/manager/SeleniumManagerJsonOutput.java b/java/src/org/openqa/selenium/manager/SeleniumManagerOutput.java similarity index 55% rename from java/src/org/openqa/selenium/manager/SeleniumManagerJsonOutput.java rename to java/src/org/openqa/selenium/manager/SeleniumManagerOutput.java index 2cc532ac55d14..271949c729f41 100644 --- a/java/src/org/openqa/selenium/manager/SeleniumManagerJsonOutput.java +++ b/java/src/org/openqa/selenium/manager/SeleniumManagerOutput.java @@ -16,9 +16,11 @@ // under the License. package org.openqa.selenium.manager; +import org.openqa.selenium.json.JsonInput; + import java.util.List; -public class SeleniumManagerJsonOutput { +public class SeleniumManagerOutput { public List logs; public Result result; @@ -72,6 +74,19 @@ public void setMessage(String message) { public static class Result { public int code; public String message; + public String driverPath; + public String browserPath; + + public Result(String driverPath) { + this.driverPath = driverPath; + } + + public Result(int code, String message, String driverPath, String browserPath) { + this.code = code; + this.message = message; + this.driverPath = driverPath; + this.browserPath = browserPath; + } public int getCode() { return code; @@ -88,5 +103,56 @@ public String getMessage() { public void setMessage(String message) { this.message = message; } + + public String getDriverPath() { + return driverPath; + } + + public void setDriverPath(String driverPath) { + this.driverPath = driverPath; + } + + public String getBrowserPath() { + return browserPath; + } + + public void setBrowserPath(String browserPath) { + this.browserPath = browserPath; + } + + public static Result fromJson(JsonInput input) { + int code = 0; + String message = null; + String driverPath = null; + String browserPath = null; + + input.beginObject(); + while (input.hasNext()) { + switch (input.nextName()) { + case "code": + code = input.read(Integer.class); + break; + + case "message": + message = input.read(String.class); + break; + + case "driver_path": + driverPath = input.read(String.class); + break; + + case "browser_path": + browserPath = input.read(String.class); + break; + + default: + input.skipValue(); + break; + } + } + input.endObject(); + + return new Result(code, message, driverPath, browserPath); + } } } diff --git a/java/src/org/openqa/selenium/remote/service/DriverFinder.java b/java/src/org/openqa/selenium/remote/service/DriverFinder.java index c4c2fac3e5702..d61bf53cffedc 100644 --- a/java/src/org/openqa/selenium/remote/service/DriverFinder.java +++ b/java/src/org/openqa/selenium/remote/service/DriverFinder.java @@ -4,36 +4,37 @@ import org.openqa.selenium.Capabilities; import org.openqa.selenium.internal.Require; import org.openqa.selenium.manager.SeleniumManager; +import org.openqa.selenium.manager.SeleniumManagerOutput.Result; import org.openqa.selenium.remote.NoSuchDriverException; public class DriverFinder { - public static String getPath(DriverService service, Capabilities options) { + public static Result getPath(DriverService service, Capabilities options) { return getPath(service, options, false); } - public static String getPath(DriverService service, Capabilities options, boolean offline) { + public static Result getPath(DriverService service, Capabilities options, boolean offline) { Require.nonNull("Browser options", options); - String exePath = System.getProperty(service.getDriverProperty()); + Result result = new Result(System.getProperty(service.getDriverProperty())); - if (exePath == null) { + if (result.getDriverPath() == null) { try { - exePath = SeleniumManager.getInstance().getDriverPath(options, offline); + result = SeleniumManager.getInstance().getDriverPath(options, offline); } catch (Exception e) { throw new NoSuchDriverException(String.format("Unable to obtain: %s", options), e); } } String message; - if (exePath == null) { + if (result.getDriverPath() == null) { message = String.format("Unable to locate or obtain %s", service.getDriverName()); - } else if (!new File(exePath).exists()) { - message = String.format("%s located at %s, but invalid", service.getDriverName(), exePath); - } else if (!new File(exePath).canExecute()) { + } else if (!new File(result.getDriverPath()).exists()) { + message = String.format("%s located at %s, but invalid", service.getDriverName(), result.getDriverPath()); + } else if (!new File(result.getDriverPath()).canExecute()) { message = - String.format("%s located at %s, cannot be executed", service.getDriverName(), exePath); + String.format("%s located at %s, cannot be executed", service.getDriverName(), result.getDriverPath()); } else { - return exePath; + return result; } throw new NoSuchDriverException(message); diff --git a/java/src/org/openqa/selenium/remote/service/DriverService.java b/java/src/org/openqa/selenium/remote/service/DriverService.java index cd080736579fc..67ebdafa95b18 100644 --- a/java/src/org/openqa/selenium/remote/service/DriverService.java +++ b/java/src/org/openqa/selenium/remote/service/DriverService.java @@ -194,12 +194,6 @@ public void start() throws IOException { if (process != null) { return; } - if (this.executable == null) { - if (getDefaultDriverOptions().getBrowserName().isEmpty()) { - throw new WebDriverException("Driver executable is null and browser name is not set."); - } - this.executable = DriverFinder.getPath(this, getDefaultDriverOptions()); - } LOG.fine(String.format("Starting driver at %s with %s", this.executable, this.args)); process = new CommandLine(this.executable, args.toArray(new String[] {})); process.setEnvironmentVariables(environment); diff --git a/java/src/org/openqa/selenium/safari/SafariDriver.java b/java/src/org/openqa/selenium/safari/SafariDriver.java index ef4e1a40a23fd..02961719d8f89 100644 --- a/java/src/org/openqa/selenium/safari/SafariDriver.java +++ b/java/src/org/openqa/selenium/safari/SafariDriver.java @@ -91,7 +91,7 @@ private static SafariDriverCommandExecutor generateExecutor( Require.nonNull("Driver options", options); Require.nonNull("Driver clientConfig", clientConfig); if (service.getExecutable() == null) { - String path = DriverFinder.getPath(service, options); + String path = DriverFinder.getPath(service, options).getDriverPath(); service.setExecutable(path); } return new SafariDriverCommandExecutor(service, clientConfig); diff --git a/java/test/org/openqa/selenium/ExecutingAsyncJavascriptTest.java b/java/test/org/openqa/selenium/ExecutingAsyncJavascriptTest.java index b3b1e1ca31232..b2e6cfd53cfbe 100644 --- a/java/test/org/openqa/selenium/ExecutingAsyncJavascriptTest.java +++ b/java/test/org/openqa/selenium/ExecutingAsyncJavascriptTest.java @@ -139,6 +139,7 @@ void shouldBeAbleToReturnWebElementsFromAsyncScripts() { } @Test + @Ignore(value = CHROME, reason = "https://bugs.chromium.org/p/chromedriver/issues/detail?id=4525") void shouldBeAbleToReturnArraysOfWebElementsFromAsyncScripts() { driver.get(pages.ajaxyPage); diff --git a/java/test/org/openqa/selenium/grid/node/config/DriverServiceSessionFactoryTest.java b/java/test/org/openqa/selenium/grid/node/config/DriverServiceSessionFactoryTest.java index 5d1928ad8c52e..ca03b6ae5ec61 100644 --- a/java/test/org/openqa/selenium/grid/node/config/DriverServiceSessionFactoryTest.java +++ b/java/test/org/openqa/selenium/grid/node/config/DriverServiceSessionFactoryTest.java @@ -68,6 +68,7 @@ public void setUp() throws MalformedURLException { driverService = mock(DriverService.class); when(driverService.getUrl()).thenReturn(new URL("http://localhost:1234/")); + when(driverService.getExecutable()).thenReturn("/usr/bin/driver"); builder = mock(DriverService.Builder.class); when(builder.build()).thenReturn(driverService); @@ -145,6 +146,7 @@ void shouldNotInstantiateSessionIfRemoteEndReturnsInvalidResponse() throws IOExc verify(builder, times(1)).build(); verifyNoMoreInteractions(builder); + verify(driverService, times(1)).getExecutable(); verify(driverService, times(1)).start(); verify(driverService, atLeastOnce()).getUrl(); verify(driverService, times(1)).stop(); @@ -176,6 +178,7 @@ void shouldInstantiateSessionIfEverythingIsOK() throws IOException { verify(builder, times(1)).build(); verifyNoMoreInteractions(builder); + verify(driverService, times(1)).getExecutable(); verify(driverService, times(1)).start(); verify(driverService, atLeastOnce()).getUrl(); verifyNoMoreInteractions(driverService);