diff --git a/java/src/org/openqa/selenium/opera/AddHasCasting.java b/java/src/org/openqa/selenium/opera/AddHasCasting.java new file mode 100644 index 0000000000000..fe9391a51a173 --- /dev/null +++ b/java/src/org/openqa/selenium/opera/AddHasCasting.java @@ -0,0 +1,57 @@ +// 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.opera; + +import com.google.auto.service.AutoService; +import org.openqa.selenium.Capabilities; +import org.openqa.selenium.remote.AdditionalHttpCommands; +import org.openqa.selenium.remote.AugmenterProvider; +import org.openqa.selenium.remote.CommandInfo; +import org.openqa.selenium.remote.http.HttpMethod; + +import java.util.Map; +import java.util.function.Predicate; + +import static org.openqa.selenium.remote.Browser.OPERA; + +@SuppressWarnings({"rawtypes", "RedundantSuppression"}) +@AutoService({AdditionalHttpCommands.class, AugmenterProvider.class}) +public class AddHasCasting extends org.openqa.selenium.chromium.AddHasCasting { + + private static final Map COMMANDS = + Map.of( + GET_CAST_SINKS, new CommandInfo("session/:sessionId/ms/cast/get_sinks", HttpMethod.GET), + SET_CAST_SINK_TO_USE, + new CommandInfo("session/:sessionId/ms/cast/set_sink_to_use", HttpMethod.POST), + START_CAST_TAB_MIRRORING, + new CommandInfo("session/:sessionId/ms/cast/start_tab_mirroring", HttpMethod.POST), + GET_CAST_ISSUE_MESSAGE, + new CommandInfo("session/:sessionId/ms/cast/get_issue_message", HttpMethod.GET), + STOP_CASTING, + new CommandInfo("session/:sessionId/ms/cast/stop_casting", HttpMethod.POST)); + + @Override + public Map getAdditionalCommands() { + return COMMANDS; + } + + @Override + public Predicate isApplicable() { + return OPERA::is; + } +} diff --git a/java/src/org/openqa/selenium/opera/AddHasCdp.java b/java/src/org/openqa/selenium/opera/AddHasCdp.java new file mode 100644 index 0000000000000..d1e787741d927 --- /dev/null +++ b/java/src/org/openqa/selenium/opera/AddHasCdp.java @@ -0,0 +1,48 @@ +// 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.opera; + +import com.google.auto.service.AutoService; +import org.openqa.selenium.Capabilities; +import org.openqa.selenium.remote.AdditionalHttpCommands; +import org.openqa.selenium.remote.AugmenterProvider; +import org.openqa.selenium.remote.CommandInfo; +import org.openqa.selenium.remote.http.HttpMethod; + +import java.util.Map; +import java.util.function.Predicate; + +import static org.openqa.selenium.remote.Browser.OPERA; + +@SuppressWarnings({"rawtypes", "RedundantSuppression"}) +@AutoService({AdditionalHttpCommands.class, AugmenterProvider.class}) +public class AddHasCdp extends org.openqa.selenium.chromium.AddHasCdp { + + private static final Map COMMANDS = + Map.of(EXECUTE_CDP, new CommandInfo("session/:sessionId/ms/cdp/execute", HttpMethod.POST)); + + @Override + public Map getAdditionalCommands() { + return COMMANDS; + } + + @Override + public Predicate isApplicable() { + return OPERA::is; + } +} diff --git a/java/src/org/openqa/selenium/opera/BUILD.bazel b/java/src/org/openqa/selenium/opera/BUILD.bazel index a55c7aefa50a4..b8030e54a3848 100644 --- a/java/src/org/openqa/selenium/opera/BUILD.bazel +++ b/java/src/org/openqa/selenium/opera/BUILD.bazel @@ -6,10 +6,20 @@ java_export( srcs = glob(["*.java"]), maven_coordinates = "org.seleniumhq.selenium:selenium-opera-driver:%s" % SE_VERSION, pom_template = "//java/src/org/openqa/selenium:template-pom", - visibility = ["//visibility:public"], + tags = [ + "release-artifact", + ], + visibility = [ + "//visibility:public", + ], + exports = [ + "//java/src/org/openqa/selenium/chromium", + ], deps = [ "//java:auto-service", "//java/src/org/openqa/selenium:core", + "//java/src/org/openqa/selenium/chromium", + "//java/src/org/openqa/selenium/manager", "//java/src/org/openqa/selenium/remote", ], ) diff --git a/java/src/org/openqa/selenium/opera/OperaDriver.java b/java/src/org/openqa/selenium/opera/OperaDriver.java index 284e32431d96a..139030cdcba7f 100644 --- a/java/src/org/openqa/selenium/opera/OperaDriver.java +++ b/java/src/org/openqa/selenium/opera/OperaDriver.java @@ -17,149 +17,81 @@ package org.openqa.selenium.opera; -import org.openqa.selenium.Capabilities; +import org.openqa.selenium.Beta; import org.openqa.selenium.WebDriver; -import org.openqa.selenium.WebDriverException; -import org.openqa.selenium.html5.LocalStorage; -import org.openqa.selenium.html5.Location; -import org.openqa.selenium.html5.LocationContext; -import org.openqa.selenium.html5.SessionStorage; -import org.openqa.selenium.html5.WebStorage; -import org.openqa.selenium.remote.FileDetector; +import org.openqa.selenium.chromium.ChromiumDriver; +import org.openqa.selenium.chromium.ChromiumDriverCommandExecutor; +import org.openqa.selenium.internal.Require; +import org.openqa.selenium.remote.CommandInfo; import org.openqa.selenium.remote.RemoteWebDriver; -import org.openqa.selenium.remote.html5.RemoteLocationContext; -import org.openqa.selenium.remote.html5.RemoteWebStorage; -import org.openqa.selenium.remote.service.DriverCommandExecutor; +import org.openqa.selenium.remote.RemoteWebDriverBuilder; +import org.openqa.selenium.remote.http.ClientConfig; +import org.openqa.selenium.remote.service.DriverFinder; +import org.openqa.selenium.remote.service.DriverService; -import java.io.File; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; /** - * A {@link WebDriver} implementation that controls a Blink-based Opera browser running on the local + * A {@link WebDriver} implementation that controls a Chromium-based Opera browser running on the local * machine. It requires an operadriver executable to be available in PATH. * * @see operadriver - * - * Since operadriver does not support w3c, Selenium will remove the support in the next version. - * @deprecated Use {@link org.openqa.selenium.chrome.ChromeDriver} with - * {@link org.openqa.selenium.chrome.ChromeOptions#setBinary(File)} or {@link org.openqa.selenium.chrome.ChromeOptions#setBinary(String)} - * to set the path to the Opera browser. - * - *

Example usage: - *


- * ChromeOptions options = new ChromeOptions()
- * options.setBinary(new File("/path/to/opera"));
- *
- * // For using Opera browser with ChromeDriver:
- * ChromeDriver driver = new ChromeDriver(options);
- *
- * // For use with RemoteWebDriver:
- * ChromeOptions options = new ChromeOptions();
- * options.setBinary(new File("/path/to/opera"));
- * RemoteWebDriver driver = new RemoteWebDriver(
- *     new URL("http://localhost:4444/"), options);
- * 
*/ -@Deprecated -public class OperaDriver extends RemoteWebDriver - implements LocationContext, WebStorage { - - private RemoteLocationContext locationContext; - private RemoteWebStorage webStorage; +public class OperaDriver extends ChromiumDriver { - /** - * Creates a new OperaDriver using the {@link OperaDriverService#createDefaultService default} - * server configuration. - * - * @see #OperaDriver(OperaDriverService, OperaOptions) - */ public OperaDriver() { - this(OperaDriverService.createDefaultService(), new OperaOptions()); - } - - /** - * Creates a new OperaDriver instance. The {@code service} will be started along with the driver, - * and shutdown upon calling {@link #quit()}. - * - * @param service The service to use. - * @see #OperaDriver(OperaDriverService, OperaOptions) - */ - public OperaDriver(OperaDriverService service) { - this(service, new OperaOptions()); + this(new OperaOptions()); } - /** - * Creates a new OperaDriver instance. The {@code capabilities} will be passed to the - * chromedriver service. - * - * @param capabilities The capabilities required from the OperaDriver. - * @see #OperaDriver(OperaDriverService, Capabilities) - * @deprecated Use {@link #OperaDriver(OperaOptions)} instead. - */ - @Deprecated - public OperaDriver(Capabilities capabilities) { - this(OperaDriverService.createDefaultService(), capabilities); - } - - /** - * Creates a new OperaDriver instance with the specified options. - * - * @param options The options to use. - * @see #OperaDriver(OperaDriverService, OperaOptions) - */ public OperaDriver(OperaOptions options) { - this(OperaDriverService.createDefaultService(), options); - } - - /** - * Creates a new OperaDriver instance with the specified options. The {@code service} will be - * started along with the driver, and shutdown upon calling {@link #quit()}. - * - * @param service The service to use. - * @param options The options to use. - */ - public OperaDriver(OperaDriverService service, OperaOptions options) { - this(service, (Capabilities) options); + this(new OperaDriverService.Builder().build(), options); } - /** - * Creates a new OperaDriver instance. The {@code service} will be started along with the - * driver, and shutdown upon calling {@link #quit()}. - * - * @param service The service to use. - * @param capabilities The capabilities required from the OperaDriver. - * @deprecated Use {@link #OperaDriver(OperaDriverService, OperaOptions)} instead. - */ - @Deprecated - public OperaDriver(OperaDriverService service, Capabilities capabilities) { - super(new DriverCommandExecutor(service), capabilities); - locationContext = new RemoteLocationContext(getExecuteMethod()); - webStorage = new RemoteWebStorage(getExecuteMethod()); + public OperaDriver(OperaDriverService service) { + this(service, new OperaOptions()); } - @Override - public void setFileDetector(FileDetector detector) { - throw new WebDriverException( - "Setting the file detector only works on remote webdriver instances obtained " + - "via RemoteWebDriver"); + public OperaDriver(OperaDriverService service, OperaOptions options) { + this(service, options, ClientConfig.defaultConfig()); } - @Override - public LocalStorage getLocalStorage() { - return webStorage.getLocalStorage(); + public OperaDriver(OperaDriverService service, OperaOptions options, ClientConfig clientConfig) { + super(generateExecutor(service, options, clientConfig), options, OperaOptions.CAPABILITY); + casting = new AddHasCasting().getImplementation(getCapabilities(), getExecuteMethod()); + cdp = new AddHasCdp().getImplementation(getCapabilities(), getExecuteMethod()); } - @Override - public SessionStorage getSessionStorage() { - return webStorage.getSessionStorage(); + private static OperaDriver.OperaDriverCommandExecutor generateExecutor( + OperaDriverService service, OperaOptions options, ClientConfig clientConfig) { + Require.nonNull("Driver service", service); + Require.nonNull("Driver options", options); + Require.nonNull("Driver clientConfig", clientConfig); + DriverFinder finder = new DriverFinder(service, options); + service.setExecutable(finder.getDriverPath()); + if (finder.hasBrowserPath()) { + options.setBinary(finder.getBrowserPath()); + options.setCapability("browserVersion", (Object) null); + } + return new OperaDriver.OperaDriverCommandExecutor(service, clientConfig); } - @Override - public Location location() { - return locationContext.location(); + @Beta + public static RemoteWebDriverBuilder builder() { + return RemoteWebDriver.builder().oneOf(new OperaOptions()); } - @Override - public void setLocation(Location location) { - locationContext.setLocation(location); + private static class OperaDriverCommandExecutor extends ChromiumDriverCommandExecutor { + public OperaDriverCommandExecutor(DriverService service, ClientConfig clientConfig) { + super(service, getExtraCommands(), clientConfig); + } + + private static Map getExtraCommands() { + return Stream.of( + new AddHasCasting().getAdditionalCommands(), new AddHasCdp().getAdditionalCommands()) + .flatMap((m) -> m.entrySet().stream()) + .collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, Map.Entry::getValue)); + } } } diff --git a/java/src/org/openqa/selenium/opera/OperaDriverInfo.java b/java/src/org/openqa/selenium/opera/OperaDriverInfo.java index ca8cfc5969da1..12f29569a7eb8 100644 --- a/java/src/org/openqa/selenium/opera/OperaDriverInfo.java +++ b/java/src/org/openqa/selenium/opera/OperaDriverInfo.java @@ -23,8 +23,9 @@ import org.openqa.selenium.ImmutableCapabilities; import org.openqa.selenium.SessionNotCreatedException; import org.openqa.selenium.WebDriver; -import org.openqa.selenium.WebDriverException; import org.openqa.selenium.WebDriverInfo; +import org.openqa.selenium.chromium.ChromiumDriverInfo; +import org.openqa.selenium.remote.service.DriverFinder; import java.util.Optional; @@ -32,7 +33,7 @@ import static org.openqa.selenium.remote.CapabilityType.BROWSER_NAME; @AutoService(WebDriverInfo.class) -public class OperaDriverInfo implements WebDriverInfo { +public class OperaDriverInfo extends ChromiumDriverInfo { @Override public String getDisplayName() { @@ -51,31 +52,33 @@ public boolean isSupporting(Capabilities capabilities) { @Override public boolean isSupportingCdp() { + return true; + } + + @Override + public boolean isSupportingBiDi() { return false; } @Override public boolean isAvailable() { - try { - OperaDriverService.createDefaultService(); - return true; - } catch (IllegalStateException | WebDriverException e) { - return false; - } + return new DriverFinder(OperaDriverService.createDefaultService(), getCanonicalCapabilities()) + .isAvailable(); } @Override - public int getMaximumSimultaneousSessions() { - return Runtime.getRuntime().availableProcessors(); + public boolean isPresent() { + return new DriverFinder(OperaDriverService.createDefaultService(), getCanonicalCapabilities()) + .isPresent(); } @Override public Optional createDriver(Capabilities capabilities) - throws SessionNotCreatedException { - if (!isAvailable()) { + throws SessionNotCreatedException { + if (!isAvailable() || !isSupporting(capabilities)) { return Optional.empty(); } - return Optional.of(new OperaDriver(capabilities)); + return Optional.of(new OperaDriver(new OperaOptions().merge(capabilities))); } } diff --git a/java/src/org/openqa/selenium/opera/OperaDriverService.java b/java/src/org/openqa/selenium/opera/OperaDriverService.java index 6ea4645e4212c..96001fc97092b 100644 --- a/java/src/org/openqa/selenium/opera/OperaDriverService.java +++ b/java/src/org/openqa/selenium/opera/OperaDriverService.java @@ -20,6 +20,8 @@ import com.google.auto.service.AutoService; import org.openqa.selenium.Capabilities; import org.openqa.selenium.WebDriverException; +import org.openqa.selenium.chromium.ChromiumDriverLogLevel; +import org.openqa.selenium.remote.service.DriverFinder; import org.openqa.selenium.remote.service.DriverService; import java.io.File; @@ -37,31 +39,54 @@ */ public class OperaDriverService extends DriverService { + public static final String OPERA_DRIVER_NAME = "operadriver"; + /** * System property that defines the location of the operadriver executable that will be used by * the {@link #createDefaultService() default service}. */ public static final String OPERA_DRIVER_EXE_PROPERTY = "webdriver.opera.driver"; + /** System property that toggles the formatting of the timestamps of the logs */ + public static final String OPERA_DRIVER_READABLE_TIMESTAMP = "webdriver.opera.readableTimestamp"; + /** * System property that defines the location of the log that will be written by * the {@link #createDefaultService() default service}. */ public static final String OPERA_DRIVER_LOG_PROPERTY = "webdriver.opera.logfile"; + /** System property that defines the {@link ChromiumDriverLogLevel} for OperaDriver logs. */ + public static final String OPERA_DRIVER_LOG_LEVEL_PROPERTY = "webdriver.opera.loglevel"; + + /** + * Boolean system property that defines whether OperaDriver should append to existing log file. + */ + public static final String OPERA_DRIVER_APPEND_LOG_PROPERTY = "webdriver.opera.appendLog"; + /** * Boolean system property that defines whether the OperaDriver executable should be started * with verbose logging. */ - public static final String OPERA_DRIVER_VERBOSE_LOG_PROPERTY = - "webdriver.opera.verboseLogging"; + public static final String OPERA_DRIVER_VERBOSE_LOG_PROPERTY = "webdriver.opera.verboseLogging"; /** * Boolean system property that defines whether the OperaDriver executable should be started * in silent mode. */ - public static final String OPERA_DRIVER_SILENT_OUTPUT_PROPERTY = - "webdriver.opera.silentOutput"; + public static final String OPERA_DRIVER_SILENT_OUTPUT_PROPERTY = "webdriver.opera.silentOutput"; + + /** + * System property that defines comma-separated list of remote IPv4 addresses which are allowed to + * connect to OperaDriver. + */ + public static final String OPERA_DRIVER_ALLOWED_IPS_PROPERTY = "webdriver.opera.withAllowedIps"; + + /** + * System property that defines whether the OperaDriver executable should check for build version + * compatibility between OperaDriver and the browser. + */ + public static final String OPERA_DRIVER_DISABLE_BUILD_CHECK = "webdriver.opera.disableBuildCheck"; /** * @@ -98,6 +123,28 @@ public OperaDriverService(File executable, int port, Duration timeout, List { - private boolean verbose = Boolean.getBoolean(OPERA_DRIVER_VERBOSE_LOG_PROPERTY); - private boolean silent = Boolean.getBoolean(OPERA_DRIVER_SILENT_OUTPUT_PROPERTY); + private Boolean disableBuildCheck; + private Boolean readableTimestamp; + private Boolean appendLog; + private Boolean verbose; + private Boolean silent; + private String allowedListIps; + private ChromiumDriverLogLevel logLevel; @Override public int score(Capabilities capabilities) { @@ -127,6 +179,41 @@ public int score(Capabilities capabilities) { return score; } + /** + * Configures the driver server appending to log file. + * + * @param appendLog True for appending to log file, false otherwise. + * @return A self reference. + */ + public OperaDriverService.Builder withAppendLog(boolean appendLog) { + this.appendLog = appendLog; + return this; + } + + /** + * Allows the driver to be used with potentially incompatible versions of the browser. + * + * @param noBuildCheck True for not enforcing matching versions. + * @return A self reference. + */ + public OperaDriverService.Builder withBuildCheckDisabled(boolean noBuildCheck) { + this.disableBuildCheck = noBuildCheck; + return this; + } + + /** + * Configures the driver server log level. + * + * @param logLevel {@link ChromiumDriverLogLevel} for desired log level output. + * @return A self reference. + */ + public OperaDriverService.Builder withLoglevel(ChromiumDriverLogLevel logLevel) { + this.logLevel = logLevel; + this.silent = false; + this.verbose = false; + return this; + } + /** * Configures the driver server verbosity. * @@ -150,10 +237,30 @@ public Builder withSilent(boolean silent) { } @Override - protected File findDefaultExecutable() { - return findExecutable("operadriver", OPERA_DRIVER_EXE_PROPERTY, - "https://github.com/operasoftware/operachromiumdriver", - "https://github.com/operasoftware/operachromiumdriver/releases"); + protected void loadSystemProperties() { + parseLogOutput(OPERA_DRIVER_LOG_PROPERTY); + if (disableBuildCheck == null) { + this.disableBuildCheck = Boolean.getBoolean(OPERA_DRIVER_DISABLE_BUILD_CHECK); + } + if (readableTimestamp == null) { + this.readableTimestamp = Boolean.getBoolean(OPERA_DRIVER_READABLE_TIMESTAMP); + } + if (appendLog == null) { + this.appendLog = Boolean.getBoolean(OPERA_DRIVER_APPEND_LOG_PROPERTY); + } + if (verbose == null && Boolean.getBoolean(OPERA_DRIVER_VERBOSE_LOG_PROPERTY)) { + withVerbose(Boolean.getBoolean(OPERA_DRIVER_VERBOSE_LOG_PROPERTY)); + } + if (silent == null && Boolean.getBoolean(OPERA_DRIVER_SILENT_OUTPUT_PROPERTY)) { + withSilent(Boolean.getBoolean(OPERA_DRIVER_SILENT_OUTPUT_PROPERTY)); + } + if (allowedListIps == null) { + this.allowedListIps = System.getProperty(OPERA_DRIVER_ALLOWED_IPS_PROPERTY); + } + if (logLevel == null && System.getProperty(OPERA_DRIVER_LOG_LEVEL_PROPERTY) != null) { + String level = System.getProperty(OPERA_DRIVER_LOG_LEVEL_PROPERTY); + withLoglevel(ChromiumDriverLogLevel.fromString(level)); + } } @Override @@ -181,10 +288,8 @@ protected List createArgs() { } @Override - protected OperaDriverService createDriverService(File exe, int port, - Duration timeout, - List args, - Map environment) { + protected OperaDriverService createDriverService( + File exe, int port, Duration timeout, List args, Map environment) { try { return new OperaDriverService(exe, port, timeout, args, environment); } catch (IOException e) { diff --git a/java/src/org/openqa/selenium/opera/OperaOptions.java b/java/src/org/openqa/selenium/opera/OperaOptions.java index d9c8f97f6abf1..74ac38e85c44f 100644 --- a/java/src/org/openqa/selenium/opera/OperaOptions.java +++ b/java/src/org/openqa/selenium/opera/OperaOptions.java @@ -18,238 +18,53 @@ package org.openqa.selenium.opera; import org.openqa.selenium.Capabilities; -import org.openqa.selenium.WebDriverException; +import org.openqa.selenium.chromium.ChromiumOptions; import org.openqa.selenium.internal.Require; -import org.openqa.selenium.remote.AbstractDriverOptions; +import org.openqa.selenium.remote.CapabilityType; -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.util.*; - -import static java.util.Collections.unmodifiableList; import static org.openqa.selenium.remote.Browser.OPERA; -import static org.openqa.selenium.remote.CapabilityType.BROWSER_NAME; /** * Class to manage options specific to {@link OperaDriver}. * *

Example usage: + * *


  * OperaOptions options = new OperaOptions()
  * options.addExtensions(new File("/path/to/extension.crx"))
- * options.setBinary(new File("/path/to/chrome"));
+ * options.setBinary(new File("/path/to/opera"));
  *
  * // For use with OperaDriver:
  * OperaDriver driver = new OperaDriver(options);
  *
  * // For use with RemoteWebDriver:
- * OperaOptions options = new OperaOptions();
- * RemoteWebDriver driver = new RemoteWebDriver(
- *     new URL("http://localhost:4444/"), options);
- * 
- * - * Since operadriver does not support w3c, Selenium will remove the support in the next version. - * @deprecated Use {@link org.openqa.selenium.chrome.ChromeDriver} with - * {@link org.openqa.selenium.chrome.ChromeOptions#setBinary(File)} or {@link org.openqa.selenium.chrome.ChromeOptions#setBinary(String)} - * to set the path to the Opera browser. - * - *

Example usage: - *


- * ChromeOptions options = new ChromeOptions()
- * options.setBinary(new File("/path/to/opera"));
- *
- * // For using Opera browser with ChromeDriver:
- * ChromeDriver driver = new ChromeDriver(options);
- *
- * // For use with RemoteWebDriver:
- * ChromeOptions options = new ChromeOptions();
- * options.setBinary(new File("/path/to/opera"));
  * RemoteWebDriver driver = new RemoteWebDriver(
- *     new URL("http://localhost:4444/"), options);
+ *     new URL("http://localhost:4444/"),
+ *     new OperaOptions());
  * 
*/ -@Deprecated -public class OperaOptions extends AbstractDriverOptions { +public class OperaOptions extends ChromiumOptions { /** * Key used to store a set of OperaOptions in a {@link org.openqa.selenium.Capabilities} * object. */ - public static final String CAPABILITY = "operaOptions"; - - private String binary; - private List args = new ArrayList<>(); - private List extensionFiles = new ArrayList<>(); - private List extensions = new ArrayList<>(); - private Map experimentalOptions = new HashMap<>(); + public static final String CAPABILITY = "opera:operaOptions"; public OperaOptions() { - setCapability(BROWSER_NAME, OPERA.browserName()); + super(CapabilityType.BROWSER_NAME, OPERA.browserName(), CAPABILITY); + setExperimentalOption("w3c", true); } @Override public OperaOptions merge(Capabilities extraCapabilities) { - OperaOptions newInstance = new OperaOptions(); - this.asMap().forEach(newInstance::setCapability); - extraCapabilities.asMap().forEach(newInstance::setCapability); - return newInstance; - } - - /** - * Sets the path to the Opera executable. This path should exist on the - * machine which will launch Opera. The path should either be absolute or - * relative to the location of running OperaDriver server. - * - * @param path Path to Opera executable. - */ - public OperaOptions setBinary(File path) { - binary = Require.nonNull("Path to the opera executable", path).getPath(); - return this; - } - - /** - * Sets the path to the Opera executable. This path should exist on the - * machine which will launch Opera. The path should either be absolute or - * relative to the location of running OperaDriver server. - * - * @param path Path to Opera executable. - */ - public OperaOptions setBinary(String path) { - binary = Require.nonNull("Path to the opera executable", path); - return this; - } - - /** - * @param arguments The arguments to use when starting Opera. - * @see #addArguments(java.util.List) - */ - public OperaOptions addArguments(String... arguments) { - addArguments(Arrays.asList(arguments)); - return this; - } - - /** - * Adds additional command line arguments to be used when starting Opera. - *

For example: - *


-   *   options.setArguments(
-   *       "load-extension=/path/to/unpacked_extension",
-   *       "allow-outdated-plugins");
-   * 
- * - *

Each argument may contain an option "--" prefix: "--foo" or "foo". - * Arguments with an associated value should be delimited with an "=": - * "foo=bar". - * - * @param arguments The arguments to use when starting Opera. - */ - public OperaOptions addArguments(List arguments) { - args.addAll(arguments); - return this; - } - - /** - * @param paths Paths to the extensions to install. - * @see #addExtensions(java.util.List) - */ - public OperaOptions addExtensions(File... paths) { - addExtensions(Arrays.asList(paths)); - return this; - } - - /** - * Adds a new Opera extension to install on browser startup. Each path should - * specify a packed Opera extension (CRX file). - * - * @param paths Paths to the extensions to install. - */ - public OperaOptions addExtensions(List paths) { - paths.forEach(path -> Require.argument("Extension", path).isFile()); - extensionFiles.addAll(paths); - return this; - } - - /** - * @param encoded Base64 encoded data of the extensions to install. - * @see #addEncodedExtensions(java.util.List) - */ - public OperaOptions addEncodedExtensions(String... encoded) { - addEncodedExtensions(Arrays.asList(encoded)); - return this; - } + Require.nonNull("Capabilities to merge", extraCapabilities); - /** - * Adds a new Opera extension to install on browser startup. Each string data should - * specify a Base64 encoded string of packed Opera extension (CRX file). - * - * @param encoded Base64 encoded data of the extensions to install. - */ - public OperaOptions addEncodedExtensions(List encoded) { - for (String extension : encoded) { - Require.nonNull("Encoded exception", extension); - } - extensions.addAll(encoded); - return this; - } - - /** - * Sets an experimental option. Useful for new OperaDriver options not yet - * exposed through the {@link OperaOptions} API. - * - * @param name Name of the experimental option. - * @param value Value of the experimental option, which must be convertible - * to JSON. - */ - public OperaOptions setExperimentalOption(String name, Object value) { - experimentalOptions.put(Require.nonNull("Option name", name), value); - return this; - } - - /** - * Returns the value of an experimental option. - * - * @param name The option name. - * @return The option value, or {@code null} if not set. - */ - public Object getExperimentalOption(String name) { - return experimentalOptions.get(Require.nonNull("Option name", name)); - } - - @Override - protected Set getExtraCapabilityNames() { - return Collections.singleton(CAPABILITY); - } - - @Override - protected Object getExtraCapability(String capabilityName) { - Require.nonNull("Capability name", capabilityName); - - if (!CAPABILITY.equals(capabilityName)) { - return null; - } - - Map options = new TreeMap<>(experimentalOptions); - - if (binary != null) { - options.put("binary", binary); - } - - options.put("args", unmodifiableList(new ArrayList<>(args))); - - List encodedExtensions = new ArrayList<>(); - for (File file : extensionFiles) { - try { - String encoded = Base64.getEncoder().encodeToString(Files.readAllBytes(file.toPath())); - - encodedExtensions.add(encoded); - } catch (IOException e) { - throw new WebDriverException(e); - } - } - encodedExtensions.addAll(extensions); - options.put("extensions", unmodifiableList(encodedExtensions)); + OperaOptions newInstance = new OperaOptions(); + newInstance.mergeInPlace(this); + newInstance.mergeInPlace(extraCapabilities); + newInstance.mergeInOptionsFromCaps(CAPABILITY, extraCapabilities); - return Collections.unmodifiableMap(options); + return newInstance; } } diff --git a/java/test/org/openqa/selenium/opera/OperaDriverServiceTest.java b/java/test/org/openqa/selenium/opera/OperaDriverServiceTest.java index 4f9e54b991d57..ba24fa7c4c5a9 100644 --- a/java/test/org/openqa/selenium/opera/OperaDriverServiceTest.java +++ b/java/test/org/openqa/selenium/opera/OperaDriverServiceTest.java @@ -26,9 +26,12 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Tag; +import org.openqa.selenium.chromium.ChromiumDriverLogLevel; import java.io.File; import java.time.Duration; +import java.util.Arrays; +import java.util.List; @Tag("UnitTests") public class OperaDriverServiceTest { @@ -40,7 +43,6 @@ public void builderPassesTimeoutToDriverService() { Duration customTimeout = Duration.ofSeconds(60); OperaDriverService.Builder builderMock = spy(OperaDriverService.Builder.class); - doReturn(exe).when(builderMock).findDefaultExecutable(); builderMock.build(); verify(builderMock).createDriverService(any(), anyInt(), eq(defaultTimeout), any(), any()); @@ -49,4 +51,41 @@ public void builderPassesTimeoutToDriverService() { builderMock.build(); verify(builderMock).createDriverService(any(), anyInt(), eq(customTimeout), any(), any()); } + + @Test + void testScoring() { + OperaDriverService.Builder builder = new OperaDriverService.Builder(); + assertThat(builder.score(new OperaOptions())).isPositive(); + } + + @Test + void logLevelLastWins() { + OperaDriverService.Builder builderMock = spy(OperaDriverService.Builder.class); + + List silentLast = Arrays.asList("--port=1", "--log-level=OFF"); + builderMock.withLoglevel(ChromiumDriverLogLevel.ALL).usingPort(1).withSilent(true).build(); + verify(builderMock).createDriverService(any(), anyInt(), any(), eq(silentLast), any()); + + List silentFirst = Arrays.asList("--port=1", "--log-level=DEBUG"); + builderMock.withSilent(true).withLoglevel(ChromiumDriverLogLevel.DEBUG).usingPort(1).build(); + verify(builderMock).createDriverService(any(), anyInt(), any(), eq(silentFirst), any()); + + List verboseLast = Arrays.asList("--port=1", "--log-level=ALL"); + builderMock.withLoglevel(ChromiumDriverLogLevel.OFF).usingPort(1).withVerbose(true).build(); + verify(builderMock).createDriverService(any(), anyInt(), any(), eq(verboseLast), any()); + + List verboseFirst = Arrays.asList("--port=1", "--log-level=INFO"); + builderMock.withVerbose(true).withLoglevel(ChromiumDriverLogLevel.INFO).usingPort(1).build(); + verify(builderMock).createDriverService(any(), anyInt(), any(), eq(verboseFirst), any()); + } + + // Setting these to false makes no sense; we're just going to ignore it. + @Test + void ignoreFalseLogging() { + OperaDriverService.Builder builderMock = spy(OperaDriverService.Builder.class); + + List falseSilent = Arrays.asList("--port=1", "--log-level=DEBUG"); + builderMock.withLoglevel(ChromiumDriverLogLevel.DEBUG).usingPort(1).withSilent(false).build(); + verify(builderMock).createDriverService(any(), anyInt(), any(), eq(falseSilent), any()); + } } diff --git a/java/test/org/openqa/selenium/opera/OperaOptionsFunctionalTest.java b/java/test/org/openqa/selenium/opera/OperaOptionsFunctionalTest.java index ff85e266b4dd9..424df9f814afc 100644 --- a/java/test/org/openqa/selenium/opera/OperaOptionsFunctionalTest.java +++ b/java/test/org/openqa/selenium/opera/OperaOptionsFunctionalTest.java @@ -18,41 +18,56 @@ package org.openqa.selenium.opera; import static org.assertj.core.api.Assertions.assertThat; +import static org.openqa.selenium.remote.CapabilityType.ACCEPT_INSECURE_CERTS; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; +import org.openqa.selenium.HasCapabilities; +import org.openqa.selenium.JavascriptExecutor; import org.openqa.selenium.testing.JupiterTestBase; +import org.openqa.selenium.testing.NoDriverBeforeTest; +import org.openqa.selenium.testing.drivers.WebDriverBuilder; + +import java.io.IOException; +import java.nio.file.Files; +import java.util.Base64; /** * Functional tests for {@link OperaOptions}. */ public class OperaOptionsFunctionalTest extends JupiterTestBase { - private OperaDriver driver = null; - - @AfterEach - public void tearDown() { - if (driver != null) { - driver.quit(); - } - } @Test + @NoDriverBeforeTest public void canStartOperaWithCustomOptions() { OperaOptions options = new OperaOptions(); options.addArguments("user-agent=foo;bar"); - driver = new OperaDriver(options); + localDriver = new WebDriverBuilder().get(options); - driver.get(pages.clickJacker); - Object userAgent = driver.executeScript("return window.navigator.userAgent"); + localDriver.get(pages.clickJacker); + Object userAgent = + ((JavascriptExecutor) localDriver).executeScript("return window.navigator.userAgent"); assertThat(userAgent).isEqualTo("foo;bar"); } @Test - public void optionsStayEqualAfterSerialization() { + void optionsStayEqualAfterSerialization() { OperaOptions options1 = new OperaOptions(); OperaOptions options2 = new OperaOptions(); - assertThat(options1).isEqualTo(options2); + assertThat(options2).isEqualTo(options1); options1.asMap(); - assertThat(options1).isEqualTo(options2); + assertThat(options2).isEqualTo(options1); + } + + @Test + @NoDriverBeforeTest + public void canSetAcceptInsecureCerts() { + OperaOptions options = new OperaOptions(); + options.setAcceptInsecureCerts(true); + localDriver = new WebDriverBuilder().get(options); + System.out.println(((HasCapabilities) localDriver).getCapabilities()); + + assertThat( + ((HasCapabilities) localDriver).getCapabilities().getCapability(ACCEPT_INSECURE_CERTS)) + .isEqualTo(true); } } diff --git a/java/test/org/openqa/selenium/opera/OperaOptionsTest.java b/java/test/org/openqa/selenium/opera/OperaOptionsTest.java new file mode 100644 index 0000000000000..e8ae8c5252fd6 --- /dev/null +++ b/java/test/org/openqa/selenium/opera/OperaOptionsTest.java @@ -0,0 +1,265 @@ +// 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.opera; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.InstanceOfAssertFactories.LIST; +import static org.assertj.core.api.InstanceOfAssertFactories.MAP; +import static org.assertj.core.api.InstanceOfAssertFactories.STRING; +import static org.openqa.selenium.remote.Browser.OPERA; +import static org.openqa.selenium.remote.CapabilityType.ACCEPT_INSECURE_CERTS; + +import java.io.File; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Base64; +import java.util.Collections; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.openqa.selenium.ImmutableCapabilities; +import org.openqa.selenium.MutableCapabilities; +import org.openqa.selenium.PageLoadStrategy; +import org.openqa.selenium.remote.CapabilityType; +import org.openqa.selenium.testing.TestUtilities; + +@Tag("UnitTests") +class OperaOptionsTest { + + @Test + void testDefaultOptions() { + OperaOptions options = new OperaOptions(); + checkCommonStructure(options); + assertThat(options.asMap()) + .extracting(OperaOptions.CAPABILITY) + .asInstanceOf(MAP) + .containsEntry("args", Collections.emptyList()) + .containsEntry("extensions", Collections.emptyList()); + } + + @Test + void canAddArguments() { + OperaOptions options = new OperaOptions(); + options.addArguments("--arg1", "--arg2"); + checkCommonStructure(options); + assertThat(options.asMap()) + .extracting(OperaOptions.CAPABILITY) + .asInstanceOf(MAP) + .containsEntry("args", Arrays.asList("--arg1", "--arg2")) + .containsEntry("extensions", Collections.emptyList()); + } + + @Test + void canAddExtensions() throws IOException { + OperaOptions options = new OperaOptions(); + Path tmpDir = Files.createTempDirectory("webdriver"); + File ext1 = createTempFile(tmpDir, "ext1 content"); + File ext2 = createTempFile(tmpDir, "ext2 content"); + options.addExtensions(ext1, ext2); + checkCommonStructure(options); + assertThat(options.asMap()) + .extracting(OperaOptions.CAPABILITY) + .asInstanceOf(MAP) + .containsEntry("args", Collections.emptyList()) + .containsEntry( + "extensions", + Stream.of("ext1 content", "ext2 content") + .map(s -> Base64.getEncoder().encodeToString(s.getBytes())) + .collect(Collectors.toList())); + } + + @Test + void canMergeWithoutChangingOriginalObject() { + OperaOptions options = new OperaOptions(); + OperaOptions merged = + options.merge( + new ImmutableCapabilities(CapabilityType.PAGE_LOAD_STRATEGY, PageLoadStrategy.NONE)); + assertThat(merged.getCapability(CapabilityType.PAGE_LOAD_STRATEGY)) + .isEqualTo(PageLoadStrategy.NONE); + } + + @Test + void mergingOptionsWithMutableCapabilities() { + File ext1 = TestUtilities.createTmpFile("ext1"); + String ext1Encoded = Base64.getEncoder().encodeToString("ext1".getBytes()); + String ext2 = Base64.getEncoder().encodeToString("ext2".getBytes()); + + MutableCapabilities one = new MutableCapabilities(); + + OperaOptions options = new OperaOptions(); + options.addArguments("verbose"); + options.addArguments("silent"); + options.setExperimentalOption("opt1", "val1"); + options.setExperimentalOption("opt2", "val4"); + options.addExtensions(ext1); + options.addEncodedExtensions(ext2); + options.setAcceptInsecureCerts(true); + File binary = TestUtilities.createTmpFile("binary"); + options.setBinary(binary); + + one.setCapability(OperaOptions.CAPABILITY, options); + + OperaOptions two = new OperaOptions(); + two.addArguments("verbose"); + two.setExperimentalOption("opt2", "val2"); + two.setExperimentalOption("opt3", "val3"); + + two = two.merge(one); + + Map map = two.asMap(); + + assertThat(map) + .asInstanceOf(MAP) + .extractingByKey(OperaOptions.CAPABILITY) + .asInstanceOf(MAP) + .extractingByKey("args") + .asInstanceOf(LIST) + .containsExactly("verbose", "silent"); + + assertThat(map) + .asInstanceOf(MAP) + .extractingByKey(OperaOptions.CAPABILITY) + .asInstanceOf(MAP) + .containsEntry("opt1", "val1") + .containsEntry("opt2", "val4") + .containsEntry("opt3", "val3"); + + assertThat(map) + .asInstanceOf(MAP) + .extractingByKey(ACCEPT_INSECURE_CERTS) + .isExactlyInstanceOf(Boolean.class); + + assertThat(map) + .asInstanceOf(MAP) + .extractingByKey(OperaOptions.CAPABILITY) + .asInstanceOf(MAP) + .extractingByKey("extensions") + .asInstanceOf(LIST) + .containsExactly(ext1Encoded, ext2); + + assertThat(map) + .asInstanceOf(MAP) + .extractingByKey(OperaOptions.CAPABILITY) + .asInstanceOf(MAP) + .extractingByKey("binary") + .asInstanceOf(STRING) + .isEqualTo(binary.getPath()); + } + + @Test + void mergingOptionsWithOptionsAsMutableCapabilities() { + File ext1 = TestUtilities.createTmpFile("ext1"); + String ext1Encoded = Base64.getEncoder().encodeToString("ext1".getBytes()); + String ext2 = Base64.getEncoder().encodeToString("ext2".getBytes()); + + MutableCapabilities browserCaps = new MutableCapabilities(); + + File binary = TestUtilities.createTmpFile("binary"); + + browserCaps.setCapability("binary", binary.getPath()); + browserCaps.setCapability("opt1", "val1"); + browserCaps.setCapability("opt2", "val4"); + browserCaps.setCapability("args", Arrays.asList("silent", "verbose")); + browserCaps.setCapability("extensions", Arrays.asList(ext1, ext2)); + + MutableCapabilities one = new MutableCapabilities(); + one.setCapability(OperaOptions.CAPABILITY, browserCaps); + + OperaOptions two = new OperaOptions(); + two.addArguments("verbose"); + two.setExperimentalOption("opt2", "val2"); + two.setExperimentalOption("opt3", "val3"); + two = two.merge(one); + + Map map = two.asMap(); + + assertThat(map) + .asInstanceOf(MAP) + .extractingByKey(OperaOptions.CAPABILITY) + .asInstanceOf(MAP) + .extractingByKey("args") + .asInstanceOf(LIST) + .containsExactly("verbose", "silent"); + + assertThat(map).asInstanceOf(MAP).containsEntry("opt1", "val1"); + + assertThat(map).asInstanceOf(MAP).containsEntry("opt2", "val4"); + + assertThat(map) + .asInstanceOf(MAP) + .extractingByKey(OperaOptions.CAPABILITY) + .asInstanceOf(MAP) + .containsEntry("opt2", "val2") + .containsEntry("opt3", "val3"); + + assertThat(map) + .asInstanceOf(MAP) + .extractingByKey(OperaOptions.CAPABILITY) + .asInstanceOf(MAP) + .extractingByKey("extensions") + .asInstanceOf(LIST) + .containsExactly(ext1Encoded, ext2); + + assertThat(map) + .asInstanceOf(MAP) + .extractingByKey(OperaOptions.CAPABILITY) + .asInstanceOf(MAP) + .extractingByKey("binary") + .asInstanceOf(STRING) + .isEqualTo(binary.getPath()); + } + + private void checkCommonStructure(OperaOptions options) { + assertThat(options.asMap()) + .containsEntry(CapabilityType.BROWSER_NAME, OPERA.browserName()) + .extracting(OperaOptions.CAPABILITY) + .asInstanceOf(MAP) + .containsOnlyKeys("args", "extensions"); + } + + private File createTempFile(Path tmpDir, String content) { + try { + Path file = Files.createTempFile(tmpDir, "tmp", "ext"); + Files.writeString(file, content, Charset.defaultCharset()); + return file.toFile(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + @Test + void mergingOptionsMergesArguments() { + OperaOptions one = new OperaOptions().addArguments("verbose"); + OperaOptions two = new OperaOptions().addArguments("silent"); + OperaOptions merged = one.merge(two); + + assertThat(merged.asMap()) + .asInstanceOf(MAP) + .extractingByKey(OperaOptions.CAPABILITY) + .asInstanceOf(MAP) + .extractingByKey("args") + .asInstanceOf(LIST) + .containsExactly("verbose", "silent"); + } +}