From 12a14a204a14ce2a100f5106e8bfb797cadf4660 Mon Sep 17 00:00:00 2001 From: Titus Date: Tue, 28 Sep 2021 09:03:20 -0500 Subject: [PATCH] Create interfaces for RemoteWebDriver to use with Augmenter (#9856) * Implement Network Conditions for Chromium both local and remote drivers * Allow Chromium casting functionality to be augmented by RemoteWebDriver * Allow Chromium cdp functionality to be augmented by RemoteWebDriver * Implement Safari permission endpoint and make accessible to RemoteWebDriver via Augmenter * Implement Safari attach debugger endpoint and make accessible to RemoteWebDriver via Augmenter * Allow Chromium permissions functionality to be augmented by RemoteWebDriver * Allow Chromium launch app functionality to be augmented by RemoteWebDriver --- .../openqa/selenium/chrome/AddHasCasting.java | 50 ++++++ .../org/openqa/selenium/chrome/AddHasCdp.java | 46 ++++++ .../openqa/selenium/chrome/ChromeDriver.java | 21 ++- .../selenium/chromium/AddHasCasting.java | 81 ++++++++++ .../openqa/selenium/chromium/AddHasCdp.java | 65 ++++++++ .../selenium/chromium/AddHasLaunchApp.java | 69 +++++++++ .../chromium/AddHasNetworkConditions.java | 95 ++++++++++++ .../selenium/chromium/AddHasPermissions.java | 70 +++++++++ .../selenium/chromium/ChromiumDriver.java | 95 ++++++++---- .../chromium/ChromiumDriverCommand.java | 40 ----- .../ChromiumDriverCommandExecutor.java | 58 ++----- .../chromium/ChromiumNetworkConditions.java | 108 +++++++++++++ .../openqa/selenium/chromium/HasCasting.java | 66 ++++++++ .../org/openqa/selenium/chromium/HasCdp.java | 43 ++++++ .../selenium/chromium/HasLaunchApp.java | 33 ++++ .../chromium/HasNetworkConditions.java | 48 ++++++ .../selenium/chromium/HasPermissions.java | 34 +++++ .../openqa/selenium/edge/AddHasCasting.java | 50 ++++++ .../org/openqa/selenium/edge/AddHasCdp.java | 46 ++++++ java/src/org/openqa/selenium/edge/BUILD.bazel | 2 + .../org/openqa/selenium/edge/EdgeDriver.java | 22 ++- .../selenium/firefox/AddHasContext.java | 6 +- .../selenium/firefox/AddHasExtensions.java | 11 +- .../firefox/AddHasFullPageScreenshot.java | 6 +- .../selenium/firefox/FirefoxDriver.java | 6 +- .../openqa/selenium/firefox/HasContext.java | 2 +- .../selenium/firefox/HasExtensions.java | 1 + .../selenium/safari/AddHasDebugger.java | 66 ++++++++ .../selenium/safari/AddHasPermissions.java | 72 +++++++++ .../org/openqa/selenium/safari/BUILD.bazel | 2 + .../openqa/selenium/safari/HasDebugger.java | 34 +++++ .../selenium/safari/HasPermissions.java | 44 ++++++ .../openqa/selenium/safari/SafariDriver.java | 46 +++++- .../org/openqa/selenium/chrome/BUILD.bazel | 4 + .../chrome/ChromeDriverFunctionalTest.java | 107 ++++++++++--- .../test/org/openqa/selenium/edge/BUILD.bazel | 38 ++--- .../edge/EdgeDriverFunctionalTest.java | 142 ++++++++++++++++++ .../openqa/selenium/edge/EdgeOptionsTest.java | 6 +- .../org/openqa/selenium/firefox/BUILD.bazel | 3 + .../selenium/firefox/FirefoxDriverTest.java | 47 +++++- .../org/openqa/selenium/safari/BUILD.bazel | 5 +- .../selenium/safari/SafariDriverTest.java | 30 +++- 42 files changed, 1632 insertions(+), 188 deletions(-) create mode 100644 java/src/org/openqa/selenium/chrome/AddHasCasting.java create mode 100644 java/src/org/openqa/selenium/chrome/AddHasCdp.java create mode 100644 java/src/org/openqa/selenium/chromium/AddHasCasting.java create mode 100644 java/src/org/openqa/selenium/chromium/AddHasCdp.java create mode 100644 java/src/org/openqa/selenium/chromium/AddHasLaunchApp.java create mode 100644 java/src/org/openqa/selenium/chromium/AddHasNetworkConditions.java create mode 100644 java/src/org/openqa/selenium/chromium/AddHasPermissions.java delete mode 100644 java/src/org/openqa/selenium/chromium/ChromiumDriverCommand.java create mode 100644 java/src/org/openqa/selenium/chromium/ChromiumNetworkConditions.java create mode 100644 java/src/org/openqa/selenium/chromium/HasCasting.java create mode 100644 java/src/org/openqa/selenium/chromium/HasCdp.java create mode 100644 java/src/org/openqa/selenium/chromium/HasLaunchApp.java create mode 100644 java/src/org/openqa/selenium/chromium/HasNetworkConditions.java create mode 100644 java/src/org/openqa/selenium/chromium/HasPermissions.java create mode 100644 java/src/org/openqa/selenium/edge/AddHasCasting.java create mode 100644 java/src/org/openqa/selenium/edge/AddHasCdp.java create mode 100644 java/src/org/openqa/selenium/safari/AddHasDebugger.java create mode 100644 java/src/org/openqa/selenium/safari/AddHasPermissions.java create mode 100644 java/src/org/openqa/selenium/safari/HasDebugger.java create mode 100644 java/src/org/openqa/selenium/safari/HasPermissions.java create mode 100644 java/test/org/openqa/selenium/edge/EdgeDriverFunctionalTest.java diff --git a/java/src/org/openqa/selenium/chrome/AddHasCasting.java b/java/src/org/openqa/selenium/chrome/AddHasCasting.java new file mode 100644 index 0000000000000..32b3b2f0447b5 --- /dev/null +++ b/java/src/org/openqa/selenium/chrome/AddHasCasting.java @@ -0,0 +1,50 @@ +// 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.chrome; + +import com.google.auto.service.AutoService; +import com.google.common.collect.ImmutableMap; +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.BrowserType.CHROME; + +@AutoService({AdditionalHttpCommands.class, AugmenterProvider.class}) +public class AddHasCasting extends org.openqa.selenium.chromium.AddHasCasting { + + @Override + public Map getAdditionalCommands() { + return ImmutableMap.of( + GET_CAST_SINKS, new CommandInfo("session/:sessionId/goog/cast/get_sinks", HttpMethod.GET), + SET_CAST_SINK_TO_USE, new CommandInfo("session/:sessionId/goog/cast/set_sink_to_use", HttpMethod.POST), + START_CAST_TAB_MIRRORING, new CommandInfo("session/:sessionId/goog/cast/start_tab_mirroring", HttpMethod.POST), + GET_CAST_ISSUE_MESSAGE, new CommandInfo("session/:sessionId/goog/cast/get_issue_message", HttpMethod.GET), + STOP_CASTING, new CommandInfo("session/:sessionId/goog/cast/stop_casting", HttpMethod.POST)); + } + + @Override + public Predicate isApplicable() { + return caps -> CHROME.equals(caps.getBrowserName()); + } +} diff --git a/java/src/org/openqa/selenium/chrome/AddHasCdp.java b/java/src/org/openqa/selenium/chrome/AddHasCdp.java new file mode 100644 index 0000000000000..660f6170c07b4 --- /dev/null +++ b/java/src/org/openqa/selenium/chrome/AddHasCdp.java @@ -0,0 +1,46 @@ +// 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.chrome; + +import com.google.auto.service.AutoService; +import com.google.common.collect.ImmutableMap; +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.BrowserType.CHROME; + +@AutoService({AdditionalHttpCommands.class, AugmenterProvider.class}) +public class AddHasCdp extends org.openqa.selenium.chromium.AddHasCdp { + + @Override + public Map getAdditionalCommands() { + return ImmutableMap.of( + EXECUTE_CDP, new CommandInfo("session/:sessionId/goog/cdp/execute", HttpMethod.POST)); + } + + @Override + public Predicate isApplicable() { + return caps -> CHROME.equals(caps.getBrowserName()); + } +} diff --git a/java/src/org/openqa/selenium/chrome/ChromeDriver.java b/java/src/org/openqa/selenium/chrome/ChromeDriver.java index 1d95e5bc4332e..9039862f3c30f 100644 --- a/java/src/org/openqa/selenium/chrome/ChromeDriver.java +++ b/java/src/org/openqa/selenium/chrome/ChromeDriver.java @@ -17,11 +17,16 @@ package org.openqa.selenium.chrome; +import com.google.common.collect.ImmutableMap; import org.openqa.selenium.Capabilities; import org.openqa.selenium.WebDriver; import org.openqa.selenium.chromium.ChromiumDriver; import org.openqa.selenium.chromium.ChromiumDriverCommandExecutor; +import org.openqa.selenium.remote.CommandInfo; import org.openqa.selenium.remote.RemoteWebDriver; +import org.openqa.selenium.remote.service.DriverService; + +import java.util.Map; /** * A {@link WebDriver} implementation that controls a Chrome browser running on the local machine. @@ -96,7 +101,21 @@ public ChromeDriver(ChromeDriverService service, ChromeOptions options) { */ @Deprecated public ChromeDriver(ChromeDriverService service, Capabilities capabilities) { - super(new ChromiumDriverCommandExecutor("goog", service), capabilities, ChromeOptions.CAPABILITY); + super(new ChromeDriverCommandExecutor(service), capabilities, ChromeOptions.CAPABILITY); + casting = new AddHasCasting().getImplementation(getCapabilities(), getExecuteMethod()); + cdp = new AddHasCdp().getImplementation(getCapabilities(), getExecuteMethod()); } + private static class ChromeDriverCommandExecutor extends ChromiumDriverCommandExecutor { + public ChromeDriverCommandExecutor(DriverService service) { + super(service, getExtraCommands()); + } + + private static Map getExtraCommands() { + return ImmutableMap.builder() + .putAll(new AddHasCasting().getAdditionalCommands()) + .putAll(new AddHasCdp().getAdditionalCommands()) + .build(); + } + } } diff --git a/java/src/org/openqa/selenium/chromium/AddHasCasting.java b/java/src/org/openqa/selenium/chromium/AddHasCasting.java new file mode 100644 index 0000000000000..54e569ea75b41 --- /dev/null +++ b/java/src/org/openqa/selenium/chromium/AddHasCasting.java @@ -0,0 +1,81 @@ +// 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.chromium; + +import com.google.common.collect.ImmutableMap; +import org.openqa.selenium.Capabilities; +import org.openqa.selenium.internal.Require; +import org.openqa.selenium.remote.AdditionalHttpCommands; +import org.openqa.selenium.remote.AugmenterProvider; +import org.openqa.selenium.remote.CommandInfo; +import org.openqa.selenium.remote.ExecuteMethod; + +import java.util.List; +import java.util.Map; +import java.util.function.Predicate; + +public abstract class AddHasCasting implements AugmenterProvider, AdditionalHttpCommands { + + public static final String GET_CAST_SINKS = "getCastSinks"; + public static final String SET_CAST_SINK_TO_USE = "selectCastSink"; + public static final String START_CAST_TAB_MIRRORING = "startCastTabMirroring"; + public static final String GET_CAST_ISSUE_MESSAGE = "getCastIssueMessage"; + public static final String STOP_CASTING = "stopCasting"; + + @Override + public abstract Map getAdditionalCommands(); + + @Override + public abstract Predicate isApplicable(); + + @Override + public Class getDescribedInterface() { + return HasCasting.class; + } + + @Override + public HasCasting getImplementation(Capabilities capabilities, ExecuteMethod executeMethod) { + return new HasCasting() { + @Override public List> getCastSinks() { + return (List>) executeMethod.execute(GET_CAST_SINKS, null); + } + + @Override public void selectCastSink(String deviceName) { + Require.nonNull("Device Name", deviceName); + + executeMethod.execute(SET_CAST_SINK_TO_USE, ImmutableMap.of("sinkName", deviceName)); + } + + @Override public void startTabMirroring(String deviceName) { + Require.nonNull("Device Name", deviceName); + + executeMethod.execute(START_CAST_TAB_MIRRORING, ImmutableMap.of("sinkName", deviceName)); + } + + @Override public String getCastIssueMessage() { + return executeMethod.execute(GET_CAST_ISSUE_MESSAGE, null).toString(); + } + + @Override public void stopCasting(String deviceName) { + Require.nonNull("Device Name", deviceName); + + executeMethod.execute(STOP_CASTING, ImmutableMap.of("sinkName", deviceName)); + } + }; + } +} diff --git a/java/src/org/openqa/selenium/chromium/AddHasCdp.java b/java/src/org/openqa/selenium/chromium/AddHasCdp.java new file mode 100644 index 0000000000000..c5cd7841da546 --- /dev/null +++ b/java/src/org/openqa/selenium/chromium/AddHasCdp.java @@ -0,0 +1,65 @@ +// 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.chromium; + +import com.google.common.collect.ImmutableMap; +import org.openqa.selenium.Capabilities; +import org.openqa.selenium.internal.Require; +import org.openqa.selenium.remote.AdditionalHttpCommands; +import org.openqa.selenium.remote.AugmenterProvider; +import org.openqa.selenium.remote.CommandInfo; +import org.openqa.selenium.remote.ExecuteMethod; + +import java.util.Map; +import java.util.function.Predicate; + +import static org.openqa.selenium.chromium.ChromiumDriver.KNOWN_CHROMIUM_BROWSERS; + +public abstract class AddHasCdp implements AugmenterProvider, AdditionalHttpCommands { + + public static final String EXECUTE_CDP = "executeCdpCommand"; + + @Override + public abstract Map getAdditionalCommands(); + + @Override + public Predicate isApplicable() { + return caps -> KNOWN_CHROMIUM_BROWSERS.contains(caps.getBrowserName()); + } + + @Override + public Class getDescribedInterface() { + return HasCdp.class; + } + + @Override + public HasCdp getImplementation(Capabilities capabilities, ExecuteMethod executeMethod) { + return new HasCdp() { + @Override + public Map executeCdpCommand(String commandName, Map parameters) { + Require.nonNull("Command name", commandName); + Require.nonNull("Parameters", parameters); + + Map toReturn = (Map) executeMethod.execute( + EXECUTE_CDP, ImmutableMap.of("cmd", commandName, "params", parameters)); + + return ImmutableMap.copyOf(toReturn); + } + }; + } +} diff --git a/java/src/org/openqa/selenium/chromium/AddHasLaunchApp.java b/java/src/org/openqa/selenium/chromium/AddHasLaunchApp.java new file mode 100644 index 0000000000000..8f3e2205f0767 --- /dev/null +++ b/java/src/org/openqa/selenium/chromium/AddHasLaunchApp.java @@ -0,0 +1,69 @@ +// 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.chromium; + +import com.google.auto.service.AutoService; +import com.google.common.collect.ImmutableMap; +import org.openqa.selenium.Capabilities; +import org.openqa.selenium.internal.Require; +import org.openqa.selenium.remote.AdditionalHttpCommands; +import org.openqa.selenium.remote.AugmenterProvider; +import org.openqa.selenium.remote.CommandInfo; +import org.openqa.selenium.remote.ExecuteMethod; +import org.openqa.selenium.remote.http.HttpMethod; + +import java.util.Map; +import java.util.function.Predicate; + +import static org.openqa.selenium.chromium.ChromiumDriver.KNOWN_CHROMIUM_BROWSERS; + +@AutoService({AdditionalHttpCommands.class, AugmenterProvider.class}) +public class AddHasLaunchApp implements AugmenterProvider, AdditionalHttpCommands { + + public static final String LAUNCH_APP = "launchApp"; + + private static final Map COMMANDS = ImmutableMap.of( + LAUNCH_APP, new CommandInfo("/session/:sessionId/chromium/launch_app", HttpMethod.POST)); + + @Override + public Map getAdditionalCommands() { + return COMMANDS; + } + + @Override + public Predicate isApplicable() { + return caps -> KNOWN_CHROMIUM_BROWSERS.contains(caps.getBrowserName()); + } + + @Override + public Class getDescribedInterface() { + return HasLaunchApp.class; + } + + @Override + public HasLaunchApp getImplementation(Capabilities capabilities, ExecuteMethod executeMethod) { + return new HasLaunchApp() { + @Override + public void launchApp(String id) { + Require.nonNull("id of Chromium App", id); + + executeMethod.execute(LAUNCH_APP, ImmutableMap.of("id", id)); + } + }; + } +} diff --git a/java/src/org/openqa/selenium/chromium/AddHasNetworkConditions.java b/java/src/org/openqa/selenium/chromium/AddHasNetworkConditions.java new file mode 100644 index 0000000000000..47e8a122627c8 --- /dev/null +++ b/java/src/org/openqa/selenium/chromium/AddHasNetworkConditions.java @@ -0,0 +1,95 @@ +// 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.chromium; + +import com.google.auto.service.AutoService; +import com.google.common.collect.ImmutableMap; +import org.openqa.selenium.Capabilities; +import org.openqa.selenium.internal.Require; +import org.openqa.selenium.remote.AdditionalHttpCommands; +import org.openqa.selenium.remote.AugmenterProvider; +import org.openqa.selenium.remote.CommandInfo; +import org.openqa.selenium.remote.ExecuteMethod; +import org.openqa.selenium.remote.http.HttpMethod; + +import java.time.Duration; +import java.util.Map; +import java.util.function.Predicate; + +import static org.openqa.selenium.chromium.ChromiumDriver.KNOWN_CHROMIUM_BROWSERS; + +@AutoService({AdditionalHttpCommands.class, AugmenterProvider.class}) +public class AddHasNetworkConditions implements AugmenterProvider, AdditionalHttpCommands { + + public static final String GET_NETWORK_CONDITIONS = "getNetworkConditions"; + public static final String SET_NETWORK_CONDITIONS = "setNetworkConditions"; + public static final String DELETE_NETWORK_CONDITIONS = "deleteNetworkConditions"; + + + private static final Map COMMANDS = ImmutableMap.of( + GET_NETWORK_CONDITIONS, new CommandInfo("/session/:sessionId/chromium/network_conditions",HttpMethod.GET), + SET_NETWORK_CONDITIONS, new CommandInfo("/session/:sessionId/chromium/network_conditions",HttpMethod.POST), + DELETE_NETWORK_CONDITIONS, new CommandInfo("/session/:sessionId/chromium/network_conditions", HttpMethod.DELETE)); + + @Override + public Map getAdditionalCommands() { + return COMMANDS; + } + + @Override + public Predicate isApplicable() { + return caps -> KNOWN_CHROMIUM_BROWSERS.contains(caps.getBrowserName()); + } + + @Override + public Class getDescribedInterface() { + return HasNetworkConditions.class; + } + + @Override + public HasNetworkConditions getImplementation(Capabilities capabilities, ExecuteMethod executeMethod) { + return new HasNetworkConditions() { + @Override + public ChromiumNetworkConditions getNetworkConditions() { + Map result = (Map) executeMethod.execute(GET_NETWORK_CONDITIONS, null); + ChromiumNetworkConditions networkConditions = new ChromiumNetworkConditions(); + networkConditions.setOffline((Boolean) result.getOrDefault(ChromiumNetworkConditions.OFFLINE, false)); + networkConditions.setLatency(Duration.ofMillis((Long) result.getOrDefault(ChromiumNetworkConditions.LATENCY, 0))); + networkConditions.setDownloadThroughput(((Number) result.getOrDefault(ChromiumNetworkConditions.DOWNLOAD_THROUGHPUT, -1)).intValue()); + networkConditions.setDownloadThroughput(((Number) result.getOrDefault(ChromiumNetworkConditions.UPLOAD_THROUGHPUT, -1)).intValue()); + return networkConditions; + } + + @Override + public void setNetworkConditions(ChromiumNetworkConditions networkConditions) { + Require.nonNull("Network Conditions", networkConditions); + + Map conditions = ImmutableMap.of(ChromiumNetworkConditions.OFFLINE, networkConditions.getOffline(), + ChromiumNetworkConditions.LATENCY, networkConditions.getLatency().toMillis(), + ChromiumNetworkConditions.DOWNLOAD_THROUGHPUT, networkConditions.getDownloadThroughput(), + ChromiumNetworkConditions.UPLOAD_THROUGHPUT, networkConditions.getUploadThroughput()); + executeMethod.execute(SET_NETWORK_CONDITIONS, ImmutableMap.of("network_conditions", conditions)); + } + + @Override + public void deleteNetworkConditions() { + executeMethod.execute(DELETE_NETWORK_CONDITIONS, null); + } + }; + } +} diff --git a/java/src/org/openqa/selenium/chromium/AddHasPermissions.java b/java/src/org/openqa/selenium/chromium/AddHasPermissions.java new file mode 100644 index 0000000000000..7758cac4e6856 --- /dev/null +++ b/java/src/org/openqa/selenium/chromium/AddHasPermissions.java @@ -0,0 +1,70 @@ +// 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.chromium; + +import com.google.auto.service.AutoService; +import com.google.common.collect.ImmutableMap; +import org.openqa.selenium.Capabilities; +import org.openqa.selenium.internal.Require; +import org.openqa.selenium.remote.AdditionalHttpCommands; +import org.openqa.selenium.remote.AugmenterProvider; +import org.openqa.selenium.remote.CommandInfo; +import org.openqa.selenium.remote.ExecuteMethod; +import org.openqa.selenium.remote.http.HttpMethod; + +import java.util.Map; +import java.util.function.Predicate; + +import static org.openqa.selenium.chromium.ChromiumDriver.KNOWN_CHROMIUM_BROWSERS; + +@AutoService({AdditionalHttpCommands.class, AugmenterProvider.class}) +public class AddHasPermissions implements AugmenterProvider, AdditionalHttpCommands { + + public static final String SET_PERMISSION = "setPermission"; + + private static final Map COMMANDS = ImmutableMap.of( + SET_PERMISSION, new CommandInfo("/session/:sessionId/permissions", HttpMethod.POST)); + + @Override + public Map getAdditionalCommands() { + return COMMANDS; + } + + @Override + public Predicate isApplicable() { + return caps -> KNOWN_CHROMIUM_BROWSERS.contains(caps.getBrowserName()); + } + + @Override + public Class getDescribedInterface() { + return HasPermissions.class; + } + + @Override + public HasPermissions getImplementation(Capabilities capabilities, ExecuteMethod executeMethod) { + return new HasPermissions() { + @Override + public void setPermission(String name, String value) { + Require.nonNull("Permission name", name); + Require.nonNull("Permission value", value); + + executeMethod.execute(SET_PERMISSION, ImmutableMap.of("descriptor", ImmutableMap.of("name", name), "state", value)); + } + }; + } +} diff --git a/java/src/org/openqa/selenium/chromium/ChromiumDriver.java b/java/src/org/openqa/selenium/chromium/ChromiumDriver.java index 75d4652815b93..cccd09d05555e 100644 --- a/java/src/org/openqa/selenium/chromium/ChromiumDriver.java +++ b/java/src/org/openqa/selenium/chromium/ChromiumDriver.java @@ -17,8 +17,7 @@ package org.openqa.selenium.chromium; -import com.google.common.collect.ImmutableMap; - +import com.google.common.collect.ImmutableList; import org.openqa.selenium.BuildInfo; import org.openqa.selenium.Capabilities; import org.openqa.selenium.Credentials; @@ -56,25 +55,36 @@ import org.openqa.selenium.remote.mobile.RemoteNetworkConnection; import java.net.URI; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.function.Predicate; import java.util.function.Supplier; import java.util.logging.Logger; +import static org.openqa.selenium.remote.BrowserType.CHROME; +import static org.openqa.selenium.remote.BrowserType.EDGE; +import static org.openqa.selenium.remote.BrowserType.OPERA; + /** * A {@link WebDriver} implementation that controls a Chromium browser running on the local machine. * It is used as the base class for Chromium-based browser drivers (Chrome, Edgium). */ public class ChromiumDriver extends RemoteWebDriver implements HasAuthentication, + HasCasting, + HasCdp, HasDevTools, + HasLaunchApp, HasLogEvents, + HasNetworkConditions, + HasPermissions, HasTouchScreen, LocationContext, NetworkConnection, WebStorage { + public static final List KNOWN_CHROMIUM_BROWSERS = ImmutableList.of(CHROME, EDGE, OPERA, "msedge"); private static final Logger LOG = Logger.getLogger(ChromiumDriver.class.getName()); private final Capabilities capabilities; @@ -82,6 +92,11 @@ public class ChromiumDriver extends RemoteWebDriver implements private final RemoteWebStorage webStorage; private final TouchScreen touchScreen; private final RemoteNetworkConnection networkConnection; + private final HasNetworkConditions networkConditions; + private final HasPermissions permissions; + private final HasLaunchApp launch; + protected HasCasting casting; + protected HasCdp cdp; private final Optional connection; private final Optional devTools; @@ -89,8 +104,11 @@ protected ChromiumDriver(CommandExecutor commandExecutor, Capabilities capabilit super(commandExecutor, capabilities); locationContext = new RemoteLocationContext(getExecuteMethod()); webStorage = new RemoteWebStorage(getExecuteMethod()); + permissions = new AddHasPermissions().getImplementation(getCapabilities(), getExecuteMethod()); touchScreen = new RemoteTouchScreen(getExecuteMethod()); networkConnection = new RemoteNetworkConnection(getExecuteMethod()); + networkConditions = new AddHasNetworkConditions().getImplementation(getCapabilities(), getExecuteMethod()); + launch = new AddHasLaunchApp().getImplementation(getCapabilities(), getExecuteMethod()); HttpClient.Factory factory = HttpClient.Factory.createDefault(); Capabilities originalCapabilities = super.getCapabilities(); @@ -171,6 +189,7 @@ public Location location() { @Override public void setLocation(Location location) { + Require.nonNull("Location", location); locationContext.setLocation(location); } @@ -186,34 +205,20 @@ public ConnectionType getNetworkConnection() { @Override public ConnectionType setNetworkConnection(ConnectionType type) { + Require.nonNull("Network Connection Type", type); return networkConnection.setNetworkConnection(type); } - /** - * Launches Chrome app specified by id. - * - * @param id Chrome app id. - */ + @Override public void launchApp(String id) { - execute(ChromiumDriverCommand.LAUNCH_APP, ImmutableMap.of("id", id)); + Require.nonNull("Launch App ID", id); + launch.launchApp(id); } - /** - * Execute a Chrome Devtools Protocol command and get returned result. The - * command and command args should follow - * chrome - * devtools protocol domains/commands. - */ + @Override public Map executeCdpCommand(String commandName, Map parameters) { - Require.nonNull("Command name", commandName); - Require.nonNull("Parameters", parameters); - - @SuppressWarnings("unchecked") - Map toReturn = (Map) getExecuteMethod().execute( - ChromiumDriverCommand.EXECUTE_CDP_COMMAND, - ImmutableMap.of("cmd", commandName, "params", parameters)); - - return ImmutableMap.copyOf(toReturn); + Require.nonNull("Command Name", commandName); + return cdp.executeCdpCommand(commandName, parameters); } @Override @@ -221,31 +226,55 @@ public Optional maybeGetDevTools() { return devTools; } - public String getCastSinks() { - Object response = getExecuteMethod().execute(ChromiumDriverCommand.GET_CAST_SINKS, null); - return response.toString(); + @Override + public List> getCastSinks() { + return casting.getCastSinks(); } + @Override public String getCastIssueMessage() { - Object response = getExecuteMethod().execute(ChromiumDriverCommand.GET_CAST_ISSUE_MESSAGE, null); - return response.toString(); + return casting.getCastIssueMessage(); } + @Override public void selectCastSink(String deviceName) { - getExecuteMethod().execute(ChromiumDriverCommand.SET_CAST_SINK_TO_USE, ImmutableMap.of("sinkName", deviceName)); + Require.nonNull("Device Name", deviceName); + casting.selectCastSink(deviceName); } + @Override public void startTabMirroring(String deviceName) { - getExecuteMethod().execute(ChromiumDriverCommand.START_CAST_TAB_MIRRORING, ImmutableMap.of("sinkName", deviceName)); + Require.nonNull("Device Name", deviceName); + casting.startTabMirroring(deviceName); } + @Override public void stopCasting(String deviceName) { - getExecuteMethod().execute(ChromiumDriverCommand.STOP_CASTING, ImmutableMap.of("sinkName", deviceName)); + Require.nonNull("Device Name", deviceName); + casting.stopCasting(deviceName); } + @Override public void setPermission(String name, String value) { - getExecuteMethod().execute(ChromiumDriverCommand.SET_PERMISSION, - ImmutableMap.of("descriptor", ImmutableMap.of("name", name), "state", value)); + Require.nonNull("Permission Name", name); + Require.nonNull("Permission Value", value); + permissions.setPermission(name, value); + } + + @Override + public ChromiumNetworkConditions getNetworkConditions() { + return networkConditions.getNetworkConditions(); + } + + @Override + public void setNetworkConditions(ChromiumNetworkConditions networkConditions) { + Require.nonNull("Network Conditions", networkConditions); + this.networkConditions.setNetworkConditions(networkConditions); + } + + @Override + public void deleteNetworkConditions() { + networkConditions.deleteNetworkConditions(); } @Override diff --git a/java/src/org/openqa/selenium/chromium/ChromiumDriverCommand.java b/java/src/org/openqa/selenium/chromium/ChromiumDriverCommand.java deleted file mode 100644 index 95190fdced8be..0000000000000 --- a/java/src/org/openqa/selenium/chromium/ChromiumDriverCommand.java +++ /dev/null @@ -1,40 +0,0 @@ -// 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.chromium; - -/** - * Constants for the ChromiumDriver specific command IDs. - */ -final class ChromiumDriverCommand { - private ChromiumDriverCommand() {} - - static final String LAUNCH_APP = "launchApp"; - static final String GET_NETWORK_CONDITIONS = "getNetworkConditions"; - static final String SET_NETWORK_CONDITIONS = "setNetworkConditions"; - static final String DELETE_NETWORK_CONDITIONS = "deleteNetworkConditions"; - static final String EXECUTE_CDP_COMMAND = "executeCdpCommand"; - - // Cast Media Router APIs - static final String GET_CAST_SINKS = "getCastSinks"; - static final String SET_CAST_SINK_TO_USE = "selectCastSink"; - static final String START_CAST_TAB_MIRRORING = "startCastTabMirroring"; - static final String GET_CAST_ISSUE_MESSAGE = "getCastIssueMessage"; - static final String STOP_CASTING = "stopCasting"; - - static final String SET_PERMISSION = "setPermission"; -} diff --git a/java/src/org/openqa/selenium/chromium/ChromiumDriverCommandExecutor.java b/java/src/org/openqa/selenium/chromium/ChromiumDriverCommandExecutor.java index af4eeb8ad4cca..c52b29e6f9cdd 100644 --- a/java/src/org/openqa/selenium/chromium/ChromiumDriverCommandExecutor.java +++ b/java/src/org/openqa/selenium/chromium/ChromiumDriverCommandExecutor.java @@ -17,16 +17,13 @@ package org.openqa.selenium.chromium; -import static java.util.Collections.unmodifiableMap; - -import java.util.HashMap; -import java.util.Map; - +import com.google.common.collect.ImmutableMap; import org.openqa.selenium.remote.CommandInfo; -import org.openqa.selenium.remote.http.HttpMethod; import org.openqa.selenium.remote.service.DriverCommandExecutor; import org.openqa.selenium.remote.service.DriverService; +import java.util.Map; + /** * {@link DriverCommandExecutor} that understands ChromiumDriver specific commands. * @@ -34,47 +31,16 @@ */ public class ChromiumDriverCommandExecutor extends DriverCommandExecutor { - private static Map buildChromiumCommandMappings(String vendorKeyword) { - String sessionPrefix = "/session/:sessionId/"; - String chromiumPrefix = sessionPrefix + "chromium"; - String vendorPrefix = sessionPrefix + vendorKeyword; - - HashMap mappings = new HashMap<>(); - - mappings.put(ChromiumDriverCommand.LAUNCH_APP, - new CommandInfo(chromiumPrefix + "/launch_app", HttpMethod.POST)); - - String networkConditions = chromiumPrefix + "/network_conditions"; - mappings.put(ChromiumDriverCommand.GET_NETWORK_CONDITIONS, - new CommandInfo(networkConditions, HttpMethod.GET)); - mappings.put(ChromiumDriverCommand.SET_NETWORK_CONDITIONS, - new CommandInfo(networkConditions, HttpMethod.POST)); - mappings.put(ChromiumDriverCommand.DELETE_NETWORK_CONDITIONS, - new CommandInfo(networkConditions, HttpMethod.DELETE)); - - mappings.put( ChromiumDriverCommand.EXECUTE_CDP_COMMAND, - new CommandInfo(vendorPrefix + "/cdp/execute", HttpMethod.POST)); - - // Cast / Media Router APIs - String cast = vendorPrefix + "/cast"; - mappings.put(ChromiumDriverCommand.GET_CAST_SINKS, - new CommandInfo(cast + "/get_sinks", HttpMethod.GET)); - mappings.put(ChromiumDriverCommand.SET_CAST_SINK_TO_USE, - new CommandInfo(cast + "/set_sink_to_use", HttpMethod.POST)); - mappings.put(ChromiumDriverCommand.START_CAST_TAB_MIRRORING, - new CommandInfo(cast + "/start_tab_mirroring", HttpMethod.POST)); - mappings.put(ChromiumDriverCommand.GET_CAST_ISSUE_MESSAGE, - new CommandInfo(cast + "/get_issue_message", HttpMethod.GET)); - mappings.put(ChromiumDriverCommand.STOP_CASTING, - new CommandInfo(cast + "/stop_casting", HttpMethod.POST)); - - mappings.put(ChromiumDriverCommand.SET_PERMISSION, - new CommandInfo(sessionPrefix + "/permissions", HttpMethod.POST)); - - return unmodifiableMap(mappings); + public ChromiumDriverCommandExecutor(DriverService service, Map extraCommands) { + super(service, getExtraCommands(extraCommands)); } - public ChromiumDriverCommandExecutor(String vendorPrefix, DriverService service) { - super(service, buildChromiumCommandMappings(vendorPrefix)); + private static Map getExtraCommands(Map commands) { + return ImmutableMap.builder() + .putAll(commands) + .putAll(new AddHasNetworkConditions().getAdditionalCommands()) + .putAll(new AddHasPermissions().getAdditionalCommands()) + .putAll(new AddHasLaunchApp().getAdditionalCommands()) + .build(); } } diff --git a/java/src/org/openqa/selenium/chromium/ChromiumNetworkConditions.java b/java/src/org/openqa/selenium/chromium/ChromiumNetworkConditions.java new file mode 100644 index 0000000000000..b50cb6b4d7889 --- /dev/null +++ b/java/src/org/openqa/selenium/chromium/ChromiumNetworkConditions.java @@ -0,0 +1,108 @@ +// 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.chromium; + +import java.time.Duration; + +/** + * Provides manipulation of getting and setting network conditions from Chromium. + * + */ +public class ChromiumNetworkConditions { + public static final String OFFLINE = "offline"; + public static final String LATENCY = "latency"; + public static final String DOWNLOAD_THROUGHPUT = "download_throughput"; + public static final String UPLOAD_THROUGHPUT = "upload_throughput"; + + private boolean offline = false; + private Duration latency = Duration.ZERO; + private int downloadThroughput = -1; + private int uploadThroughput = -1; + + + /** + * + * @return whether network is simulated to be offline. + */ + public boolean getOffline() { + return offline; + } + + /** + * Whether the network is set to offline. Defaults to false. + * + * @param offline when set to true, network is simulated to be offline. + */ + public void setOffline(boolean offline) { + this.offline = offline; + } + + /** + * The current simulated latency of the connection. + * + * @return amount of latency, typically a Duration of milliseconds. + */ + public Duration getLatency() { + return latency; + } + + /** + * Sets the simulated latency of the connection. + * + * @param latency amount of latency, typically a Duration of millisceonds. + */ + public void setLatency(Duration latency) { + this.latency = latency; + } + + /** + * The current throughput of the network connection in kb/second for downloading. + * + * @return the current download throughput in kb/second. + */ + public int getDownloadThroughput() { + return downloadThroughput; + } + + /** + * ets the throughput of the network connection in kb/second for downloading. + * + * @param downloadThroughput thoughput in kb/second + */ + public void setDownloadThroughput(int downloadThroughput) { + this.downloadThroughput = downloadThroughput; + } + + /** + * The current throughput of the network connection in kb/second for uploading. + * + * @return the current upload throughput in kb/second. + */ + public int getUploadThroughput() { + return uploadThroughput; + } + + /** + * ets the throughput of the network connection in kb/second for uploading. + * + * @param uploadThroughput thoughput in kb/second + */ + public void setUploadThroughput(int uploadThroughput) { + this.uploadThroughput = uploadThroughput; + } +} diff --git a/java/src/org/openqa/selenium/chromium/HasCasting.java b/java/src/org/openqa/selenium/chromium/HasCasting.java new file mode 100644 index 0000000000000..ca082538f04bf --- /dev/null +++ b/java/src/org/openqa/selenium/chromium/HasCasting.java @@ -0,0 +1,66 @@ +// 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.chromium; + +import org.openqa.selenium.Beta; + +import java.util.List; +import java.util.Map; + +/** + * Used by classes to indicate that they can cast devices to available sink targets. + */ +@Beta +public interface HasCasting { + + /** + * Returns the list of cast sinks (Cast devices) available to the Chrome media router. + * + * @return array of ID / Name pairs of available cast sink targets + * + */ + List> getCastSinks(); + + /** + * Selects a cast sink (Cast device) as the recipient of media router intents (connect or play). + * + * @param deviceName name of the target device. + */ + void selectCastSink(String deviceName); + + /** + * Initiates tab mirroring for the current browser tab on the specified device. + * + * @param deviceName name of the target device. + */ + void startTabMirroring(String deviceName); + + /** + * + * @return an error message if there is any issue in a Cast session. + */ + String getCastIssueMessage(); + + /** + * Stops casting from media router to the specified device, if connected. + * + * @param deviceName name of the target device. + */ + void stopCasting(String deviceName); + +} diff --git a/java/src/org/openqa/selenium/chromium/HasCdp.java b/java/src/org/openqa/selenium/chromium/HasCdp.java new file mode 100644 index 0000000000000..e2b09868d1e66 --- /dev/null +++ b/java/src/org/openqa/selenium/chromium/HasCdp.java @@ -0,0 +1,43 @@ +// 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.chromium; + +import org.openqa.selenium.Beta; + +import java.util.Map; + +/** + * Used by classes to indicate that they can execute Command DevTools commands. + */ +@Beta +public interface HasCdp { + + /** + * Execute a Chrome DevTools Protocol command and get returned result. The + * command and command args should follow + * chrome + * devtools protocol domains/commands. + * + * It is strongly encouraged to use {@link org.openqa.selenium.devtools.DevTools} API instead of this + * + * @param commandName the command to execute with Chrome Dev Tools. + * @param parameters any information needed to execute the Dev Tools command. + * @return the name and value of the response. + */ + public Map executeCdpCommand(String commandName, Map parameters); +} diff --git a/java/src/org/openqa/selenium/chromium/HasLaunchApp.java b/java/src/org/openqa/selenium/chromium/HasLaunchApp.java new file mode 100644 index 0000000000000..a61ff971c1aa6 --- /dev/null +++ b/java/src/org/openqa/selenium/chromium/HasLaunchApp.java @@ -0,0 +1,33 @@ +// 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.chromium; + +import org.openqa.selenium.Beta; + +/** + * Used by classes to indicate that they can launch a Chromium app. + */ +@Beta +public interface HasLaunchApp { + + /** + * + * @param id which Chromium app to launch. + */ + public void launchApp(String id); +} diff --git a/java/src/org/openqa/selenium/chromium/HasNetworkConditions.java b/java/src/org/openqa/selenium/chromium/HasNetworkConditions.java new file mode 100644 index 0000000000000..cfa72d24a9908 --- /dev/null +++ b/java/src/org/openqa/selenium/chromium/HasNetworkConditions.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.chromium; + +import org.openqa.selenium.Beta; + +/** + * Used by classes to indicate that they can simulate different network conditions. + */ +@Beta +public interface HasNetworkConditions { + + /** + * Gets map of network conditions. + * These have to be set before they can be retrieved. + * + * @return the current network condition values. + */ + ChromiumNetworkConditions getNetworkConditions(); + + /** + * Set network limitations + * + * @param networkConditions object containing valid network condition settings. + */ + void setNetworkConditions(ChromiumNetworkConditions networkConditions); + + /** + * Resets the network conditions to the default settings. + */ + void deleteNetworkConditions(); + +} diff --git a/java/src/org/openqa/selenium/chromium/HasPermissions.java b/java/src/org/openqa/selenium/chromium/HasPermissions.java new file mode 100644 index 0000000000000..fc15720f4d6ca --- /dev/null +++ b/java/src/org/openqa/selenium/chromium/HasPermissions.java @@ -0,0 +1,34 @@ +// 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.chromium; + +import org.openqa.selenium.Beta; + +/** + * Used by classes to indicate that they can adjust permissions compatible items. + */ +@Beta +public interface HasPermissions { + + /** + * + * @param name what item to set the permission on. + * @param value what to set the permission to. + */ + public void setPermission(String name, String value); +} diff --git a/java/src/org/openqa/selenium/edge/AddHasCasting.java b/java/src/org/openqa/selenium/edge/AddHasCasting.java new file mode 100644 index 0000000000000..09b7f1695a577 --- /dev/null +++ b/java/src/org/openqa/selenium/edge/AddHasCasting.java @@ -0,0 +1,50 @@ +// 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.edge; + +import com.google.auto.service.AutoService; +import com.google.common.collect.ImmutableMap; +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; + +@AutoService({AdditionalHttpCommands.class, AugmenterProvider.class}) +public class AddHasCasting extends org.openqa.selenium.chromium.AddHasCasting { + + private static final Map COMMANDS = ImmutableMap.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 caps -> "msedge".equals(caps.getBrowserName()); + } +} diff --git a/java/src/org/openqa/selenium/edge/AddHasCdp.java b/java/src/org/openqa/selenium/edge/AddHasCdp.java new file mode 100644 index 0000000000000..0464d66d8e20e --- /dev/null +++ b/java/src/org/openqa/selenium/edge/AddHasCdp.java @@ -0,0 +1,46 @@ +// 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.edge; + +import com.google.auto.service.AutoService; +import com.google.common.collect.ImmutableMap; +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; + +@AutoService({AdditionalHttpCommands.class, AugmenterProvider.class}) +public class AddHasCdp extends org.openqa.selenium.chromium.AddHasCdp { + + private static final Map COMMANDS = ImmutableMap.of( + EXECUTE_CDP, new CommandInfo("session/:sessionId/ms/cdp/execute", HttpMethod.POST)); + + @Override + public Map getAdditionalCommands() { + return COMMANDS; + } + + @Override + public Predicate isApplicable() { + return caps -> "msedge".equals(caps.getBrowserName()); + } +} diff --git a/java/src/org/openqa/selenium/edge/BUILD.bazel b/java/src/org/openqa/selenium/edge/BUILD.bazel index ac7fd784d62c8..86ff92dffede4 100644 --- a/java/src/org/openqa/selenium/edge/BUILD.bazel +++ b/java/src/org/openqa/selenium/edge/BUILD.bazel @@ -1,3 +1,4 @@ +load("@rules_jvm_external//:defs.bzl", "artifact") load("//java:defs.bzl", "java_export") load("//java:version.bzl", "SE_VERSION") @@ -17,5 +18,6 @@ java_export( "//java/src/org/openqa/selenium:core", "//java/src/org/openqa/selenium/chromium", "//java/src/org/openqa/selenium/remote", + artifact("com.google.guava:guava"), ], ) diff --git a/java/src/org/openqa/selenium/edge/EdgeDriver.java b/java/src/org/openqa/selenium/edge/EdgeDriver.java index 42f53f9928b69..b4308f18252d1 100644 --- a/java/src/org/openqa/selenium/edge/EdgeDriver.java +++ b/java/src/org/openqa/selenium/edge/EdgeDriver.java @@ -16,11 +16,16 @@ // under the License. package org.openqa.selenium.edge; +import com.google.common.collect.ImmutableMap; import org.openqa.selenium.Capabilities; import org.openqa.selenium.WebDriver; 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.service.DriverService; + +import java.util.Map; /** * A {@link WebDriver} implementation that controls an Edge browser running on the local machine. @@ -41,11 +46,26 @@ public EdgeDriver(EdgeDriverService service) { } public EdgeDriver(EdgeDriverService service, EdgeOptions options) { - super(new ChromiumDriverCommandExecutor("ms", service), Require.nonNull("Driver options", options), EdgeOptions.CAPABILITY); + super(new EdgeDriverCommandExecutor(service), Require.nonNull("Driver options", options), EdgeOptions.CAPABILITY); + casting = new AddHasCasting().getImplementation(getCapabilities(), getExecuteMethod()); + cdp = new AddHasCdp().getImplementation(getCapabilities(), getExecuteMethod()); } @Deprecated public EdgeDriver(Capabilities capabilities) { this(new EdgeDriverService.Builder().build(), new EdgeOptions().merge(capabilities)); } + + private static class EdgeDriverCommandExecutor extends ChromiumDriverCommandExecutor { + public EdgeDriverCommandExecutor(DriverService service) { + super(service, getExtraCommands()); + } + + private static Map getExtraCommands() { + return ImmutableMap.builder() + .putAll(new AddHasCasting().getAdditionalCommands()) + .putAll(new AddHasCdp().getAdditionalCommands()) + .build(); + } + } } diff --git a/java/src/org/openqa/selenium/firefox/AddHasContext.java b/java/src/org/openqa/selenium/firefox/AddHasContext.java index 2ca213bdd894c..66b63e0bfc5f5 100644 --- a/java/src/org/openqa/selenium/firefox/AddHasContext.java +++ b/java/src/org/openqa/selenium/firefox/AddHasContext.java @@ -20,6 +20,7 @@ import com.google.auto.service.AutoService; import com.google.common.collect.ImmutableMap; import org.openqa.selenium.Capabilities; +import org.openqa.selenium.internal.Require; import org.openqa.selenium.remote.AdditionalHttpCommands; import org.openqa.selenium.remote.AugmenterProvider; import org.openqa.selenium.remote.CommandInfo; @@ -57,7 +58,10 @@ public Class getDescribedInterface() { @Override public HasContext getImplementation(Capabilities capabilities, ExecuteMethod executeMethod) { return new HasContext() { - @Override public void setContext(FirefoxCommandContext context) { + @Override + public void setContext(FirefoxCommandContext context) { + Require.nonNull("Firefox Command Context", context); + executeMethod.execute( CONTEXT, ImmutableMap.of(CONTEXT, context)); diff --git a/java/src/org/openqa/selenium/firefox/AddHasExtensions.java b/java/src/org/openqa/selenium/firefox/AddHasExtensions.java index 21591e7da0d0e..938e5a3b575b2 100644 --- a/java/src/org/openqa/selenium/firefox/AddHasExtensions.java +++ b/java/src/org/openqa/selenium/firefox/AddHasExtensions.java @@ -20,6 +20,7 @@ import com.google.auto.service.AutoService; import com.google.common.collect.ImmutableMap; import org.openqa.selenium.Capabilities; +import org.openqa.selenium.internal.Require; import org.openqa.selenium.remote.AdditionalHttpCommands; import org.openqa.selenium.remote.AugmenterProvider; import org.openqa.selenium.remote.CommandInfo; @@ -30,7 +31,6 @@ import java.util.Map; import java.util.function.Predicate; -import static java.util.Collections.singletonMap; import static org.openqa.selenium.remote.BrowserType.FIREFOX; @AutoService({AdditionalHttpCommands.class, AugmenterProvider.class}) @@ -40,7 +40,7 @@ public class AddHasExtensions implements AugmenterProvider, Addit public static final String UNINSTALL_EXTENSION = "uninstallExtension"; private static final Map COMMANDS = ImmutableMap.of( - INSTALL_EXTENSION, new CommandInfo("/session/:sessionId/moz/addon/install",HttpMethod.POST), + INSTALL_EXTENSION, new CommandInfo("/session/:sessionId/moz/addon/install", HttpMethod.POST), UNINSTALL_EXTENSION, new CommandInfo("/session/:sessionId/moz/addon/uninstall", HttpMethod.POST)); @Override @@ -63,15 +63,18 @@ public HasExtensions getImplementation(Capabilities capabilities, ExecuteMethod return new HasExtensions() { @Override public String installExtension(Path path) { + Require.nonNull("Extension Path", path); + return (String) executeMethod.execute( INSTALL_EXTENSION, ImmutableMap.of("path", path.toAbsolutePath().toString(), "temporary", false)); - } @Override public void uninstallExtension(String extensionId) { - executeMethod.execute(UNINSTALL_EXTENSION, singletonMap("id", extensionId)); + Require.nonNull("Extension ID", extensionId); + + executeMethod.execute(UNINSTALL_EXTENSION, ImmutableMap.of("id", extensionId)); } }; } diff --git a/java/src/org/openqa/selenium/firefox/AddHasFullPageScreenshot.java b/java/src/org/openqa/selenium/firefox/AddHasFullPageScreenshot.java index e4d1ae0bf8fd5..e027ebb5be315 100644 --- a/java/src/org/openqa/selenium/firefox/AddHasFullPageScreenshot.java +++ b/java/src/org/openqa/selenium/firefox/AddHasFullPageScreenshot.java @@ -21,6 +21,7 @@ import com.google.common.collect.ImmutableMap; import org.openqa.selenium.Capabilities; import org.openqa.selenium.OutputType; +import org.openqa.selenium.internal.Require; import org.openqa.selenium.remote.AdditionalHttpCommands; import org.openqa.selenium.remote.AugmenterProvider; import org.openqa.selenium.remote.CommandInfo; @@ -58,7 +59,10 @@ public Class getDescribedInterface() { @Override public HasFullPageScreenshot getImplementation(Capabilities capabilities, ExecuteMethod executeMethod) { return new HasFullPageScreenshot() { - @Override public X getFullPageScreenshotAs(OutputType outputType) { + @Override + public X getFullPageScreenshotAs(OutputType outputType) { + Require.nonNull("Output Type", outputType); + Object result = executeMethod.execute(FULL_PAGE_SCREENSHOT, null); if (result instanceof String) { diff --git a/java/src/org/openqa/selenium/firefox/FirefoxDriver.java b/java/src/org/openqa/selenium/firefox/FirefoxDriver.java index 11327bf4dc505..b1459ffebda5c 100644 --- a/java/src/org/openqa/selenium/firefox/FirefoxDriver.java +++ b/java/src/org/openqa/selenium/firefox/FirefoxDriver.java @@ -285,10 +285,14 @@ public void uninstallExtension(String extensionId) { */ @Override public X getFullPageScreenshotAs(OutputType outputType) throws WebDriverException { + Require.nonNull("OutputType", outputType); + return fullPageScreenshot.getFullPageScreenshotAs(outputType); } - @Override public void setContext(FirefoxCommandContext commandContext) { + @Override + public void setContext(FirefoxCommandContext commandContext) { + Require.nonNull("Firefox Command Context", commandContext); context.setContext(commandContext); } diff --git a/java/src/org/openqa/selenium/firefox/HasContext.java b/java/src/org/openqa/selenium/firefox/HasContext.java index dd40425497ed5..c4255865cd4a1 100644 --- a/java/src/org/openqa/selenium/firefox/HasContext.java +++ b/java/src/org/openqa/selenium/firefox/HasContext.java @@ -20,7 +20,7 @@ import org.openqa.selenium.Beta; /** - * Used by classes to indicate that they can install and uninstall browser extensions on the fly. + * Used by classes to indicate that they can change the context commands operate in. */ @Beta public interface HasContext { diff --git a/java/src/org/openqa/selenium/firefox/HasExtensions.java b/java/src/org/openqa/selenium/firefox/HasExtensions.java index da1a2360def8a..31a534855b8fa 100644 --- a/java/src/org/openqa/selenium/firefox/HasExtensions.java +++ b/java/src/org/openqa/selenium/firefox/HasExtensions.java @@ -37,6 +37,7 @@ public interface HasExtensions { /** * Uninstall the extension by the given identifier. + * This value can be found in the extension's manifest, and typically ends with "@mozilla.org". * * @param extensionId The unique extension identifier returned by {{@link #installExtension(Path)}} */ diff --git a/java/src/org/openqa/selenium/safari/AddHasDebugger.java b/java/src/org/openqa/selenium/safari/AddHasDebugger.java new file mode 100644 index 0000000000000..3baa58b7324d2 --- /dev/null +++ b/java/src/org/openqa/selenium/safari/AddHasDebugger.java @@ -0,0 +1,66 @@ +// 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.safari; + +import com.google.auto.service.AutoService; +import com.google.common.collect.ImmutableMap; +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.ExecuteMethod; +import org.openqa.selenium.remote.http.HttpMethod; + +import java.util.Map; +import java.util.function.Predicate; + +import static java.util.Collections.singletonMap; + +@AutoService({AdditionalHttpCommands.class, AugmenterProvider.class}) +public class AddHasDebugger implements AugmenterProvider, AdditionalHttpCommands { + + public static final String ATTACH_DEBUGGER = "attachDebugger"; + + private static final Map COMMANDS = ImmutableMap.of( + ATTACH_DEBUGGER, new CommandInfo("/session/:sessionId/apple/attach_debugger", HttpMethod.POST)); + + @Override + public Map getAdditionalCommands() { + return COMMANDS; + } + + @Override + public Predicate isApplicable() { + return caps -> "Safari".equals(caps.getBrowserName()); + } + + @Override + public Class getDescribedInterface() { + return HasDebugger.class; + } + + @Override + public HasDebugger getImplementation(Capabilities capabilities, ExecuteMethod executeMethod) { + return new HasDebugger() { + @Override + public void attachDebugger() { + executeMethod.execute(ATTACH_DEBUGGER, ImmutableMap.of(ATTACH_DEBUGGER, null)); + } + }; + } +} diff --git a/java/src/org/openqa/selenium/safari/AddHasPermissions.java b/java/src/org/openqa/selenium/safari/AddHasPermissions.java new file mode 100644 index 0000000000000..ae654a29b7055 --- /dev/null +++ b/java/src/org/openqa/selenium/safari/AddHasPermissions.java @@ -0,0 +1,72 @@ +// 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.safari; + +import com.google.auto.service.AutoService; +import com.google.common.collect.ImmutableMap; +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.ExecuteMethod; +import org.openqa.selenium.remote.http.HttpMethod; + +import java.util.Map; +import java.util.function.Predicate; + +@AutoService({AdditionalHttpCommands.class, AugmenterProvider.class}) +public class AddHasPermissions implements AugmenterProvider, AdditionalHttpCommands { + + public static final String GET_PERMISSIONS = "getPermissions"; + public static final String SET_PERMISSIONS = "setPermissions"; + + private static final Map COMMANDS = ImmutableMap.of( + GET_PERMISSIONS, new CommandInfo("/session/:sessionId/apple/permissions",HttpMethod.GET), + SET_PERMISSIONS, new CommandInfo("/session/:sessionId/apple/permissions", HttpMethod.POST)); + + @Override + public Map getAdditionalCommands() { + return COMMANDS; + } + + @Override + public Predicate isApplicable() { + return caps -> "Safari".equals(caps.getBrowserName()); + } + + @Override + public Class getDescribedInterface() { + return HasPermissions.class; + } + + @Override + public HasPermissions getImplementation(Capabilities capabilities, ExecuteMethod executeMethod) { + return new HasPermissions() { + @Override + public void setPermissions(String permission, boolean value) { + executeMethod.execute(SET_PERMISSIONS, ImmutableMap.of("permissions", ImmutableMap.of(permission, value))); + } + + @Override + public Map getPermissions() { + Map results = (Map) executeMethod.execute(GET_PERMISSIONS, null); + return (Map) results.get("permissions"); + } + }; + } +} diff --git a/java/src/org/openqa/selenium/safari/BUILD.bazel b/java/src/org/openqa/selenium/safari/BUILD.bazel index dc775de85206b..d54ae7ffce5dc 100644 --- a/java/src/org/openqa/selenium/safari/BUILD.bazel +++ b/java/src/org/openqa/selenium/safari/BUILD.bazel @@ -1,3 +1,4 @@ +load("@rules_jvm_external//:defs.bzl", "artifact") load("//java:defs.bzl", "java_export") load("//java:version.bzl", "SE_VERSION") @@ -11,5 +12,6 @@ java_export( "//java:auto-service", "//java/src/org/openqa/selenium:core", "//java/src/org/openqa/selenium/remote", + artifact("com.google.guava:guava"), ], ) diff --git a/java/src/org/openqa/selenium/safari/HasDebugger.java b/java/src/org/openqa/selenium/safari/HasDebugger.java new file mode 100644 index 0000000000000..3b4d001479332 --- /dev/null +++ b/java/src/org/openqa/selenium/safari/HasDebugger.java @@ -0,0 +1,34 @@ +// 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.safari; + +import org.openqa.selenium.Beta; + +/** + * Used by classes to indicate that they can open the browser debugger. + */ +@Beta +public interface HasDebugger { + + /** + * This opens Safari's Web Inspector + * If driver subsequently executes script of "debugger;" the execution will pause, no additional commands will be processed, + * and the code will time out. + */ + void attachDebugger(); +} diff --git a/java/src/org/openqa/selenium/safari/HasPermissions.java b/java/src/org/openqa/selenium/safari/HasPermissions.java new file mode 100644 index 0000000000000..5dc84baf621ba --- /dev/null +++ b/java/src/org/openqa/selenium/safari/HasPermissions.java @@ -0,0 +1,44 @@ +// 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.safari; + +import org.openqa.selenium.Beta; + +import java.util.Map; + +/** + * Used by classes to indicate that they can take adjust permissions. + */ +@Beta +public interface HasPermissions { + + /** + * Set permission on the browser. + * The only supported permission at this time is "getUserMedia". + * + * @param permission the name of the item to set permission on. + * @param value whether the permission has been granted. + */ + void setPermissions(String permission, boolean value); + + /** + * + * @return each permission and whether it is allowed or not. + */ + Map getPermissions(); +} diff --git a/java/src/org/openqa/selenium/safari/SafariDriver.java b/java/src/org/openqa/selenium/safari/SafariDriver.java index 6fbc457ad6109..60fb97c5ddc84 100644 --- a/java/src/org/openqa/selenium/safari/SafariDriver.java +++ b/java/src/org/openqa/selenium/safari/SafariDriver.java @@ -17,11 +17,17 @@ package org.openqa.selenium.safari; +import com.google.common.collect.ImmutableMap; import org.openqa.selenium.Capabilities; import org.openqa.selenium.WebDriverException; +import org.openqa.selenium.internal.Require; +import org.openqa.selenium.remote.CommandInfo; import org.openqa.selenium.remote.FileDetector; import org.openqa.selenium.remote.RemoteWebDriver; import org.openqa.selenium.remote.service.DriverCommandExecutor; +import org.openqa.selenium.remote.service.DriverService; + +import java.util.Map; /** * A WebDriver implementation that controls Safari using a browser extension @@ -29,7 +35,10 @@ * * This driver can be configured using the {@link SafariOptions} class. */ -public class SafariDriver extends RemoteWebDriver { +public class SafariDriver extends RemoteWebDriver implements HasPermissions, HasDebugger { + + private final HasPermissions permissions; + private final HasDebugger debugger; /** * Initializes a new SafariDriver} class with default {@link SafariOptions}. @@ -75,7 +84,40 @@ public SafariDriver(SafariDriverService safariService) { * @param safariOptions safari specific options / capabilities for the driver */ public SafariDriver(SafariDriverService safariServer, SafariOptions safariOptions) { - super(new DriverCommandExecutor(safariServer), safariOptions); + super(new SafariDriverCommandExecutor(safariServer), safariOptions); + permissions = new AddHasPermissions().getImplementation(getCapabilities(), getExecuteMethod()); + debugger = new AddHasDebugger().getImplementation(getCapabilities(), getExecuteMethod()); + } + + @Override + public void setPermissions(String permission, boolean value) { + Require.nonNull("Permission Name", permission); + Require.nonNull("Permission Value", value); + + this.permissions.setPermissions(permission, value); + } + + @Override + public Map getPermissions() { + return permissions.getPermissions(); + } + + @Override + public void attachDebugger() { + debugger.attachDebugger(); + } + + private static class SafariDriverCommandExecutor extends DriverCommandExecutor { + public SafariDriverCommandExecutor(DriverService service) { + super(service, getExtraCommands()); + } + + private static Map getExtraCommands() { + return ImmutableMap.builder() + .putAll(new AddHasPermissions().getAdditionalCommands()) + .putAll(new AddHasDebugger().getAdditionalCommands()) + .build(); + } } @Override diff --git a/java/test/org/openqa/selenium/chrome/BUILD.bazel b/java/test/org/openqa/selenium/chrome/BUILD.bazel index 5c46d1004d3f1..e554a8e92d004 100644 --- a/java/test/org/openqa/selenium/chrome/BUILD.bazel +++ b/java/test/org/openqa/selenium/chrome/BUILD.bazel @@ -11,6 +11,9 @@ java_selenium_test_suite( data = [ "//third_party/chrome_ext:backspace.crx", ], + tags = [ + "selenium-remote", + ], javacopts = [ "--release", "11", @@ -22,6 +25,7 @@ java_selenium_test_suite( "//java/test/org/openqa/selenium/build", "//java/test/org/openqa/selenium/testing:annotations", "//java/test/org/openqa/selenium/testing:test-base", + "//java/test/org/openqa/selenium/testing/drivers", artifact("junit:junit"), artifact("org.assertj:assertj-core"), artifact("org.mockito:mockito-core"), diff --git a/java/test/org/openqa/selenium/chrome/ChromeDriverFunctionalTest.java b/java/test/org/openqa/selenium/chrome/ChromeDriverFunctionalTest.java index c1f7c59c58d85..5ed6d0ff48b22 100644 --- a/java/test/org/openqa/selenium/chrome/ChromeDriverFunctionalTest.java +++ b/java/test/org/openqa/selenium/chrome/ChromeDriverFunctionalTest.java @@ -18,33 +18,39 @@ package org.openqa.selenium.chrome; import org.junit.Test; -import org.openqa.selenium.chromium.ChromiumDriver; -import org.openqa.selenium.testing.Ignore; +import org.openqa.selenium.JavascriptExecutor; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebDriverException; +import org.openqa.selenium.chromium.ChromiumNetworkConditions; +import org.openqa.selenium.chromium.HasCasting; +import org.openqa.selenium.chromium.HasCdp; +import org.openqa.selenium.chromium.HasNetworkConditions; +import org.openqa.selenium.chromium.HasPermissions; import org.openqa.selenium.testing.JUnit4TestBase; -import org.openqa.selenium.testing.drivers.Browser; +import org.openqa.selenium.testing.drivers.WebDriverBuilder; +import java.time.Duration; +import java.util.List; import java.util.Map; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; public class ChromeDriverFunctionalTest extends JUnit4TestBase { private final String CLIPBOARD_READ = "clipboard-read"; private final String CLIPBOARD_WRITE = "clipboard-write"; - private ChromiumDriver driver = null; - @Test public void canSetPermission() { - //Cast provided driver to enable ChromeSpecific calls - driver = (ChromiumDriver) super.driver; + HasPermissions permissions = (HasPermissions) driver; driver.get(pages.clicksPage); assertThat(checkPermission(driver, CLIPBOARD_READ)).isEqualTo("prompt"); assertThat(checkPermission(driver, CLIPBOARD_WRITE)).isEqualTo("granted"); - driver.setPermission(CLIPBOARD_READ, "denied"); - driver.setPermission(CLIPBOARD_WRITE, "prompt"); + permissions.setPermission(CLIPBOARD_READ, "denied"); + permissions.setPermission(CLIPBOARD_WRITE, "prompt"); assertThat(checkPermission(driver, CLIPBOARD_READ)).isEqualTo("denied"); assertThat(checkPermission(driver, CLIPBOARD_WRITE)).isEqualTo("prompt"); @@ -54,28 +60,83 @@ public void canSetPermission() { public void canSetPermissionHeadless() { ChromeOptions options = new ChromeOptions(); options.setHeadless(true); - //TestChromeDriver is not honoring headless request; using ChromeDriver instead - super.driver = new ChromeDriver(options); - driver = (ChromeDriver) super.driver; - - driver.get(pages.clicksPage); - assertThat(checkPermission(driver, CLIPBOARD_READ)).isEqualTo("prompt"); - assertThat(checkPermission(driver, CLIPBOARD_WRITE)).isEqualTo("prompt"); - - driver.setPermission(CLIPBOARD_READ, "granted"); - driver.setPermission(CLIPBOARD_WRITE, "granted"); - assertThat(checkPermission(driver, CLIPBOARD_READ)).isEqualTo("granted"); - assertThat(checkPermission(driver, CLIPBOARD_WRITE)).isEqualTo("granted"); + //TestChromeDriver is not honoring headless request; using ChromeDriver instead + WebDriver driver = new WebDriverBuilder().get(options); + try { + HasPermissions permissions = (HasPermissions) driver; + + driver.get(pages.clicksPage); + assertThat(checkPermission(driver, CLIPBOARD_READ)).isEqualTo("prompt"); + assertThat(checkPermission(driver, CLIPBOARD_WRITE)).isEqualTo("prompt"); + + permissions.setPermission(CLIPBOARD_READ, "granted"); + permissions.setPermission(CLIPBOARD_WRITE, "granted"); + + assertThat(checkPermission(driver, CLIPBOARD_READ)).isEqualTo("granted"); + assertThat(checkPermission(driver, CLIPBOARD_WRITE)).isEqualTo("granted"); + } finally { + driver.quit(); + } } - public String checkPermission(ChromiumDriver driver, String permission){ + public String checkPermission(WebDriver driver, String permission){ @SuppressWarnings("unchecked") - Map result = (Map) driver.executeAsyncScript( + Map result = (Map) ((JavascriptExecutor) driver).executeAsyncScript( "callback = arguments[arguments.length - 1];" + "callback(navigator.permissions.query({" + "name: arguments[0]" + "}));", permission); return result.get("state").toString(); } + + @Test + public void canCast() throws InterruptedException { + HasCasting caster = (HasCasting) driver; + + // Does not get list the first time it is called + caster.getCastSinks(); + Thread.sleep(1500); + List> castSinks = caster.getCastSinks(); + + // Can not call these commands if there are no sinks available + if (castSinks.size() > 0) { + String deviceName = castSinks.get(0).get("name"); + + caster.startTabMirroring(deviceName); + caster.stopCasting(deviceName); + } + } + + @Test + public void canManageNetworkConditions() { + HasNetworkConditions conditions = (HasNetworkConditions) driver; + + ChromiumNetworkConditions networkConditions = new ChromiumNetworkConditions(); + networkConditions.setLatency(Duration.ofMillis(200)); + + conditions.setNetworkConditions(networkConditions); + assertThat(conditions.getNetworkConditions().getLatency()).isEqualTo(Duration.ofMillis(200)); + + conditions.deleteNetworkConditions(); + + try { + conditions.getNetworkConditions(); + fail("If Network Conditions were deleted, should not be able to get Network Conditions"); + } catch (WebDriverException e) { + if (!e.getMessage().contains("network conditions must be set before it can be retrieved")) { + throw e; + } + } + } + + @Test + public void canExecuteCdpCommands() { + HasCdp cdp = (HasCdp) driver; + + Map parameters = Map.of("url", pages.simpleTestPage); + cdp.executeCdpCommand("Page.navigate", parameters); + + assertThat(driver.getTitle()).isEqualTo("Hello WebDriver"); + } } diff --git a/java/test/org/openqa/selenium/edge/BUILD.bazel b/java/test/org/openqa/selenium/edge/BUILD.bazel index 2a45f595adc31..f3f9fe5bbfc0d 100644 --- a/java/test/org/openqa/selenium/edge/BUILD.bazel +++ b/java/test/org/openqa/selenium/edge/BUILD.bazel @@ -1,46 +1,34 @@ load("@rules_jvm_external//:defs.bzl", "artifact") -load("//java:defs.bzl", "java_selenium_test_suite", "java_test_suite") - -LARGE_TESTS = [ - "EdgeOptionsFunctionalTest.java", -] - -java_test_suite( - name = "medium-tests", - size = "medium", - srcs = glob( - ["*.java"], - exclude = LARGE_TESTS, - ), - deps = [ - "//java/src/org/openqa/selenium/edge", - "//java/src/org/openqa/selenium/remote", - "//java/test/org/openqa/selenium/testing:annotations", - artifact("com.google.guava:guava"), - artifact("junit:junit"), - artifact("org.assertj:assertj-core"), - artifact("org.mockito:mockito-core"), - ], -) +load("//java:defs.bzl", "java_selenium_test_suite") java_selenium_test_suite( name = "large-tests", size = "large", - srcs = glob(LARGE_TESTS), + srcs = glob(["*Test.java"]), browsers = [ "edge", ], data = [ "//third_party/chrome_ext:backspace.crx", ], - deps = [ + tags = [ + "selenium-remote", + ], + deps = [ "//java/src/org/openqa/selenium/edge", "//java/src/org/openqa/selenium/remote", "//java/src/org/openqa/selenium/support", "//java/test/org/openqa/selenium/build", "//java/test/org/openqa/selenium/testing:annotations", + "//java/test/org/openqa/selenium/testing/drivers" , "//java/test/org/openqa/selenium/testing:test-base", + artifact("com.google.guava:guava"), artifact("junit:junit"), artifact("org.assertj:assertj-core"), + artifact("org.mockito:mockito-core"), + ], + javacopts = [ + "--release", + "11", ], ) diff --git a/java/test/org/openqa/selenium/edge/EdgeDriverFunctionalTest.java b/java/test/org/openqa/selenium/edge/EdgeDriverFunctionalTest.java new file mode 100644 index 0000000000000..4e21eb5470441 --- /dev/null +++ b/java/test/org/openqa/selenium/edge/EdgeDriverFunctionalTest.java @@ -0,0 +1,142 @@ +// 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.edge; + +import org.junit.Test; +import org.openqa.selenium.JavascriptExecutor; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebDriverException; +import org.openqa.selenium.chromium.ChromiumNetworkConditions; +import org.openqa.selenium.chromium.HasCasting; +import org.openqa.selenium.chromium.HasCdp; +import org.openqa.selenium.chromium.HasNetworkConditions; +import org.openqa.selenium.chromium.HasPermissions; +import org.openqa.selenium.testing.JUnit4TestBase; +import org.openqa.selenium.testing.drivers.WebDriverBuilder; + +import java.time.Duration; +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +public class EdgeDriverFunctionalTest extends JUnit4TestBase { + + private final String CLIPBOARD_READ = "clipboard-read"; + private final String CLIPBOARD_WRITE = "clipboard-write"; + + @Test + public void canSetPermission() { + HasPermissions permissions = (HasPermissions) driver; + + driver.get(pages.clicksPage); + assertThat(checkPermission(driver, CLIPBOARD_READ)).isEqualTo("prompt"); + assertThat(checkPermission(driver, CLIPBOARD_WRITE)).isEqualTo("granted"); + + permissions.setPermission(CLIPBOARD_READ, "denied"); + permissions.setPermission(CLIPBOARD_WRITE, "prompt"); + + assertThat(checkPermission(driver, CLIPBOARD_READ)).isEqualTo("denied"); + assertThat(checkPermission(driver, CLIPBOARD_WRITE)).isEqualTo("prompt"); + } + + @Test + public void canSetPermissionHeadless() { + EdgeOptions options = new EdgeOptions(); + options.setHeadless(true); + + //TestEdgeDrivere is not honoring headless request; using EdgeDriver instead + WebDriver driver = new WebDriverBuilder().get(options); + try { + HasPermissions permissions = (HasPermissions) driver; + + driver.get(pages.clicksPage); + assertThat(checkPermission(driver, CLIPBOARD_READ)).isEqualTo("prompt"); + assertThat(checkPermission(driver, CLIPBOARD_WRITE)).isEqualTo("prompt"); + + permissions.setPermission(CLIPBOARD_READ, "granted"); + permissions.setPermission(CLIPBOARD_WRITE, "granted"); + + assertThat(checkPermission(driver, CLIPBOARD_READ)).isEqualTo("granted"); + assertThat(checkPermission(driver, CLIPBOARD_WRITE)).isEqualTo("granted"); + } finally { + driver.quit(); + } + } + + public String checkPermission(WebDriver driver, String permission){ + @SuppressWarnings("unchecked") + Map result = (Map) ((JavascriptExecutor) driver).executeAsyncScript( + "callback = arguments[arguments.length - 1];" + + "callback(navigator.permissions.query({" + + "name: arguments[0]" + + "}));", permission); + return result.get("state").toString(); + } + + @Test + public void canCast() throws InterruptedException { + HasCasting caster = (HasCasting) driver; + + // Does not get list the first time it is called + caster.getCastSinks(); + Thread.sleep(1500); + List> castSinks = caster.getCastSinks(); + + // Can not call these commands if there are no sinks available + if (castSinks.size() > 0) { + String deviceName = castSinks.get(0).get("name"); + + caster.startTabMirroring(deviceName); + caster.stopCasting(deviceName); + } + } + + @Test + public void canManageNetworkConditions() { + HasNetworkConditions conditions = (HasNetworkConditions) driver; + + ChromiumNetworkConditions networkConditions = new ChromiumNetworkConditions(); + networkConditions.setLatency(Duration.ofMillis(200)); + + conditions.setNetworkConditions(networkConditions); + assertThat(conditions.getNetworkConditions().getLatency()).isEqualTo(Duration.ofMillis(200)); + + conditions.deleteNetworkConditions(); + + try { + conditions.getNetworkConditions(); + fail("If Network Conditions were deleted, should not be able to get Network Conditions"); + } catch (WebDriverException e) { + if (!e.getMessage().contains("network conditions must be set before it can be retrieved")) { + throw e; + } + } + } + + @Test + public void canExecuteCdpCommands() { + HasCdp cdp = (HasCdp) driver; + + Map parameters = Map.of("url", pages.simpleTestPage); + cdp.executeCdpCommand("Page.navigate", parameters); + + assertThat(driver.getTitle()).isEqualTo("Hello WebDriver"); + } +} diff --git a/java/test/org/openqa/selenium/edge/EdgeOptionsTest.java b/java/test/org/openqa/selenium/edge/EdgeOptionsTest.java index 86c02b87c869c..66f31ab37a8cf 100644 --- a/java/test/org/openqa/selenium/edge/EdgeOptionsTest.java +++ b/java/test/org/openqa/selenium/edge/EdgeOptionsTest.java @@ -65,9 +65,9 @@ public void canAddArguments() { } @Test - public void canAddExtensions() { + public void canAddExtensions() throws IOException { EdgeOptions options = new EdgeOptions(); - File tmpDir = Files.createTempDir(); + File tmpDir = File.createTempFile("webdriver", "tmp"); tmpDir.deleteOnExit(); File ext1 = createTempFile(tmpDir, "ext1 content"); File ext2 = createTempFile(tmpDir, "ext2 content"); @@ -118,4 +118,4 @@ public void mergingOptionsMergesArguments() { .extractingByKey("args").asInstanceOf(LIST) .containsExactly("verbose", "silent"); } -} \ No newline at end of file +} diff --git a/java/test/org/openqa/selenium/firefox/BUILD.bazel b/java/test/org/openqa/selenium/firefox/BUILD.bazel index 9f67ce3058e15..ead7cf4f8483f 100644 --- a/java/test/org/openqa/selenium/firefox/BUILD.bazel +++ b/java/test/org/openqa/selenium/firefox/BUILD.bazel @@ -52,6 +52,9 @@ java_selenium_test_suite( "//third_party/firebug:firebug-1.5.0-fx.xpi", "//third_party/firebug:mooltipass-1.1.87.xpi", ], + tags = [ + "selenium-remote", + ], resources = [ "//third_party/firebug:favourite_colour-1.1-an+fx.xpi", "//third_party/firebug:firebug-1.5.0-fx.xpi", diff --git a/java/test/org/openqa/selenium/firefox/FirefoxDriverTest.java b/java/test/org/openqa/selenium/firefox/FirefoxDriverTest.java index c0f347afc9720..1e746f9bd0a56 100644 --- a/java/test/org/openqa/selenium/firefox/FirefoxDriverTest.java +++ b/java/test/org/openqa/selenium/firefox/FirefoxDriverTest.java @@ -21,8 +21,18 @@ import org.junit.After; import org.junit.Test; import org.mockito.ArgumentMatchers; -import org.openqa.selenium.*; +import org.openqa.selenium.By; +import org.openqa.selenium.Capabilities; +import org.openqa.selenium.Dimension; +import org.openqa.selenium.HasCapabilities; +import org.openqa.selenium.ImmutableCapabilities; +import org.openqa.selenium.JavascriptExecutor; +import org.openqa.selenium.OutputType; +import org.openqa.selenium.ParallelTestRunner; import org.openqa.selenium.ParallelTestRunner.Worker; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.build.InProject; import org.openqa.selenium.remote.CapabilityType; import org.openqa.selenium.remote.Command; import org.openqa.selenium.remote.CommandExecutor; @@ -42,6 +52,7 @@ import java.io.File; import java.io.IOException; import java.lang.reflect.Field; +import java.nio.file.Path; import java.time.Duration; import java.util.ArrayList; import java.util.List; @@ -488,6 +499,40 @@ public void testFirefoxCanNativelyClickOverlappingElements() { + "click in over (handled by body)"); } + @Test + public void canAddRemoveExtensions() { + Path extension = InProject.locate("third_party/firebug/favourite_colour-1.1-an+fx.xpi"); + + ((HasExtensions) driver).installExtension(extension); + ((HasExtensions) driver).uninstallExtension("favourite-colour-examples@mozilla.org"); + } + + @Test + public void canTakeFullPageScreenshot() { + File tempFile = ((HasFullPageScreenshot) driver).getFullPageScreenshotAs(OutputType.FILE); + assertThat(tempFile.exists()).isTrue(); + assertThat(tempFile.length()).isGreaterThan(0); + } + + @Test + public void canSetContext() { + FirefoxOptions options = new FirefoxOptions(); + String dir = "foo/bar"; + options.addPreference("browser.download.dir", dir); + + // Need to set the preference to be able to read it + WebDriver driver = new WebDriverBuilder().get(options); + + try { + ((HasContext) driver).setContext(FirefoxCommandContext.CHROME); + String result = (String) ((JavascriptExecutor) driver) + .executeScript("return Services.prefs.getStringPref('browser.download.dir')"); + assertThat(result).isEqualTo(dir); + } finally { + driver.quit(); + } + } + private static class CustomFirefoxProfile extends FirefoxProfile {} } diff --git a/java/test/org/openqa/selenium/safari/BUILD.bazel b/java/test/org/openqa/selenium/safari/BUILD.bazel index ff8bfc81c1c45..e3d5cfb54c115 100644 --- a/java/test/org/openqa/selenium/safari/BUILD.bazel +++ b/java/test/org/openqa/selenium/safari/BUILD.bazel @@ -8,7 +8,10 @@ java_selenium_test_suite( browsers = [ "safari", ], - deps = [ + tags = [ + "selenium-remote", + ], + deps = [ "//java/src/org/openqa/selenium/remote", "//java/src/org/openqa/selenium/safari", "//java/src/org/openqa/selenium/support", diff --git a/java/test/org/openqa/selenium/safari/SafariDriverTest.java b/java/test/org/openqa/selenium/safari/SafariDriverTest.java index 94fc35cd1358e..24a459d3cfecf 100644 --- a/java/test/org/openqa/selenium/safari/SafariDriverTest.java +++ b/java/test/org/openqa/selenium/safari/SafariDriverTest.java @@ -17,21 +17,21 @@ package org.openqa.selenium.safari; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assume.assumeTrue; - import org.junit.After; import org.junit.Test; import org.openqa.selenium.WebDriver; import org.openqa.selenium.net.PortProber; import org.openqa.selenium.testing.JUnit4TestBase; +import org.openqa.selenium.testing.drivers.WebDriverBuilder; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assume.assumeTrue; + public class SafariDriverTest extends JUnit4TestBase { private boolean technologyPreviewInstalled() { @@ -75,4 +75,26 @@ public void canStartTechnologyPreview() { assertThat(driver2.getTitle()).isEqualTo("XHTML Test Page"); } + @Test + public void canChangePermissions() { + HasPermissions permissions = (HasPermissions) driver; + + assertThat(permissions.getPermissions().get("getUserMedia")).isEqualTo(true); + + permissions.setPermissions("getUserMedia", false); + + assertThat(permissions.getPermissions().get("getUserMedia")).isEqualTo(false); + } + + @Test + public void canAttachDebugger() { + // Need to close driver after opening the inspector + WebDriver driver = new WebDriverBuilder().get(new SafariOptions()); + + try { + ((HasDebugger) driver).attachDebugger(); + } finally { + driver.quit(); + } + } }