1// Licensed to the Software Freedom Conservancy (SFC) under one2// or more contributor license agreements. See the NOTICE file3// distributed with this work for additional information4// regarding copyright ownership. The SFC licenses this file5// to you under the Apache License, Version 2.0 (the6// "License"); you may not use this file except in compliance7// with the License. You may obtain a copy of the License at8//9// http://www.apache.org/licenses/LICENSE-2.010//11// Unless required by applicable law or agreed to in writing,12// software distributed under the License is distributed on an13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY14// KIND, either express or implied. See the License for the15// specific language governing permissions and limitations16// under the License.17package org.openqa.selenium.chromium;18import com.google.common.collect.ImmutableMap;19import org.openqa.selenium.BuildInfo;20import org.openqa.selenium.Capabilities;21import org.openqa.selenium.Credentials;22import org.openqa.selenium.HasAuthentication;23import org.openqa.selenium.WebDriver;24import org.openqa.selenium.WebDriverException;25import org.openqa.selenium.devtools.CdpInfo;26import org.openqa.selenium.devtools.CdpVersionFinder;27import org.openqa.selenium.devtools.Connection;28import org.openqa.selenium.devtools.DevTools;29import org.openqa.selenium.devtools.HasDevTools;30import org.openqa.selenium.devtools.noop.NoOpCdpInfo;31import org.openqa.selenium.html5.LocalStorage;32import org.openqa.selenium.html5.Location;33import org.openqa.selenium.html5.LocationContext;34import org.openqa.selenium.html5.SessionStorage;35import org.openqa.selenium.html5.WebStorage;36import org.openqa.selenium.interactions.HasTouchScreen;37import org.openqa.selenium.interactions.TouchScreen;38import org.openqa.selenium.internal.Require;39import org.openqa.selenium.logging.EventType;40import org.openqa.selenium.logging.HasLogEvents;41import org.openqa.selenium.mobile.NetworkConnection;42import org.openqa.selenium.remote.CommandExecutor;43import org.openqa.selenium.remote.FileDetector;44import org.openqa.selenium.remote.RemoteTouchScreen;45import org.openqa.selenium.remote.RemoteWebDriver;46import org.openqa.selenium.remote.html5.RemoteLocationContext;47import org.openqa.selenium.remote.html5.RemoteWebStorage;48import org.openqa.selenium.remote.http.HttpClient;49import org.openqa.selenium.remote.mobile.RemoteNetworkConnection;50import java.net.URI;51import java.util.Map;52import java.util.Optional;53import java.util.function.Predicate;54import java.util.function.Supplier;55import java.util.logging.Logger;56/**57 * A {@link WebDriver} implementation that controls a Chromium browser running on the local machine.58 * This class is provided as a convenience for easily testing the Chromium browser. The control server59 * which each instance communicates with will live and die with the instance.60 * <p>61 * To avoid unnecessarily restarting the ChromiumDriver server with each instance, use a62 * {@link RemoteWebDriver} coupled with the desired WebDriverService, which is managed63 * separately.64 * <p>65 * Note that unlike ChromiumDriver, RemoteWebDriver doesn't directly implement66 * role interfaces such as {@link LocationContext} and {@link WebStorage}.67 * Therefore, to access that functionality, it needs to be68 * {@link org.openqa.selenium.remote.Augmenter augmented} and then cast69 * to the appropriate interface.70 */71public class ChromiumDriver extends RemoteWebDriver implements72 HasAuthentication,73 HasDevTools,74 HasLogEvents,75 HasTouchScreen,76 LocationContext,77 NetworkConnection,78 WebStorage {79 private static final Logger LOG = Logger.getLogger(ChromiumDriver.class.getName());80 private final RemoteLocationContext locationContext;81 private final RemoteWebStorage webStorage;82 private final TouchScreen touchScreen;83 private final RemoteNetworkConnection networkConnection;84 private final Optional<Connection> connection;85 private final Optional<DevTools> devTools;86 protected ChromiumDriver(CommandExecutor commandExecutor, Capabilities capabilities, String capabilityKey) {87 super(commandExecutor, capabilities);88 locationContext = new RemoteLocationContext(getExecuteMethod());89 webStorage = new RemoteWebStorage(getExecuteMethod());90 touchScreen = new RemoteTouchScreen(getExecuteMethod());91 networkConnection = new RemoteNetworkConnection(getExecuteMethod());92 HttpClient.Factory factory = HttpClient.Factory.createDefault();93 connection = ChromiumDevToolsLocator.getChromeConnector(94 factory,95 getCapabilities(),96 capabilityKey);97 CdpInfo cdpInfo = new CdpVersionFinder().match(getCapabilities().getBrowserVersion())98 .orElseGet(() -> {99 LOG.warning(100 String.format(101 "Unable to find version of CDP to use for %s. You may need to " +102 "include a dependency on a specific version of the CDP using " +103 "something similar to " +104 "`org.seleniumhq.selenium:selenium-devtools-v86:%s` where the " +105 "version (\"v86\") matches the version of the chromium-based browser " +106 "you're using and the version number of the artifact is the same " +107 "as Selenium's.",108 capabilities.getBrowserVersion(),109 new BuildInfo().getReleaseLabel()));110 return new NoOpCdpInfo();111 });112 devTools = connection.map(conn -> new DevTools(cdpInfo::getDomains, conn));113 }114 @Override115 public void setFileDetector(FileDetector detector) {116 throw new WebDriverException(117 "Setting the file detector only works on remote webdriver instances obtained " +118 "via RemoteWebDriver");119 }120 @Override121 public <X> void onLogEvent(EventType<X> kind) {122 Require.nonNull("Event type", kind);123 kind.initializeListener(this);124 }125 @Override126 public void register(Predicate<URI> whenThisMatches, Supplier<Credentials> useTheseCredentials) {127 Require.nonNull("Check to use to see how we should authenticate", whenThisMatches);128 Require.nonNull("Credentials to use when authenticating", useTheseCredentials);129 getDevTools().createSessionIfThereIsNotOne();130 getDevTools().getDomains().network().addAuthHandler(whenThisMatches, useTheseCredentials);131 }132 @Override133 public LocalStorage getLocalStorage() {134 return webStorage.getLocalStorage();135 }136 @Override137 public SessionStorage getSessionStorage() {138 return webStorage.getSessionStorage();139 }140 @Override141 public Location location() {142 return locationContext.location();143 }144 @Override145 public void setLocation(Location location) {146 locationContext.setLocation(location);147 }148 @Override149 public TouchScreen getTouch() {150 return touchScreen;151 }152 @Override153 public ConnectionType getNetworkConnection() {154 return networkConnection.getNetworkConnection();155 }156 @Override157 public ConnectionType setNetworkConnection(ConnectionType type) {158 return networkConnection.setNetworkConnection(type);159 }160 /**161 * Launches Chrome app specified by id.162 *163 * @param id Chrome app id.164 */165 public void launchApp(String id) {166 execute(ChromiumDriverCommand.LAUNCH_APP, ImmutableMap.of("id", id));167 }168 /**169 * Execute a Chrome Devtools Protocol command and get returned result. The170 * command and command args should follow171 * <a href="https://chromedevtools.github.io/devtools-protocol/">chrome172 * devtools protocol domains/commands</a>.173 */174 public Map<String, Object> executeCdpCommand(String commandName, Map<String, Object> parameters) {175 Require.nonNull("Command name", commandName);176 Require.nonNull("Parameters", parameters);177 @SuppressWarnings("unchecked")178 Map<String, Object> toReturn = (Map<String, Object>) getExecuteMethod().execute(179 ChromiumDriverCommand.EXECUTE_CDP_COMMAND,180 ImmutableMap.of("cmd", commandName, "params", parameters));181 return ImmutableMap.copyOf(toReturn);182 }183 @Override184 public DevTools getDevTools() {185 return devTools.orElseThrow(() -> new WebDriverException("Unable to create DevTools connection"));186 }187 public String getCastSinks() {188 Object response = getExecuteMethod().execute(ChromiumDriverCommand.GET_CAST_SINKS, null);189 return response.toString();190 }191 public String getCastIssueMessage() {192 Object response = getExecuteMethod().execute(ChromiumDriverCommand.GET_CAST_ISSUE_MESSAGE, null);193 return response.toString();194 }195 public void selectCastSink(String deviceName) {196 getExecuteMethod().execute(ChromiumDriverCommand.SET_CAST_SINK_TO_USE, ImmutableMap.of("sinkName", deviceName));197 }198 public void startTabMirroring(String deviceName) {199 getExecuteMethod().execute(ChromiumDriverCommand.START_CAST_TAB_MIRRORING, ImmutableMap.of("sinkName", deviceName));200 }201 public void stopCasting(String deviceName) {202 getExecuteMethod().execute(ChromiumDriverCommand.STOP_CASTING, ImmutableMap.of("sinkName", deviceName));203 }204 public void setPermission(String name, String value) {205 getExecuteMethod().execute(ChromiumDriverCommand.SET_PERMISSION,206 ImmutableMap.of("descriptor", ImmutableMap.of("name", name), "state", value));207 }208 @Override209 public void quit() {210 connection.ifPresent(Connection::close);211 super.quit();212 }213}...