
org.openqa.selenium.remote.RemoteWebDriver Maven / Gradle / Ivy
// 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.remote;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import org.openqa.selenium.AcceptedW3CCapabilityKeys;
import org.openqa.selenium.Alert;
import org.openqa.selenium.Beta;
import org.openqa.selenium.By;
import org.openqa.selenium.Capabilities;
import org.openqa.selenium.Cookie;
import org.openqa.selenium.Dimension;
import org.openqa.selenium.HasCapabilities;
import org.openqa.selenium.ImmutableCapabilities;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.MutableCapabilities;
import org.openqa.selenium.NoSuchFrameException;
import org.openqa.selenium.NoSuchWindowException;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.Pdf;
import org.openqa.selenium.Platform;
import org.openqa.selenium.Point;
import org.openqa.selenium.PrintsPage;
import org.openqa.selenium.SearchContext;
import org.openqa.selenium.SessionNotCreatedException;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebDriverException;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.WindowType;
import org.openqa.selenium.bidi.BiDi;
import org.openqa.selenium.bidi.HasBiDi;
import org.openqa.selenium.devtools.DevTools;
import org.openqa.selenium.devtools.HasDevTools;
import org.openqa.selenium.interactions.Interactive;
import org.openqa.selenium.interactions.Sequence;
import org.openqa.selenium.internal.Require;
import org.openqa.selenium.logging.LocalLogs;
import org.openqa.selenium.logging.LoggingHandler;
import org.openqa.selenium.logging.Logs;
import org.openqa.selenium.logging.NeedsLocalLogs;
import org.openqa.selenium.print.PrintOptions;
import org.openqa.selenium.remote.http.ClientConfig;
import org.openqa.selenium.remote.http.ConnectionFailedException;
import org.openqa.selenium.remote.http.HttpClient;
import org.openqa.selenium.remote.internal.WebElementToJsonConverter;
import org.openqa.selenium.remote.tracing.TracedHttpClient;
import org.openqa.selenium.remote.tracing.Tracer;
import org.openqa.selenium.remote.tracing.opentelemetry.OpenTelemetryTracer;
import org.openqa.selenium.virtualauthenticator.Credential;
import org.openqa.selenium.virtualauthenticator.HasVirtualAuthenticator;
import org.openqa.selenium.virtualauthenticator.VirtualAuthenticator;
import org.openqa.selenium.virtualauthenticator.VirtualAuthenticatorOptions;
import java.net.MalformedURLException;
import java.net.URL;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.BiFunction;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static java.util.Collections.singleton;
import static java.util.concurrent.TimeUnit.SECONDS;
import static java.util.logging.Level.SEVERE;
import static org.openqa.selenium.remote.CapabilityType.PLATFORM_NAME;
import static org.openqa.selenium.remote.CapabilityType.SUPPORTS_JAVASCRIPT;
@Augmentable
public class RemoteWebDriver implements WebDriver,
JavascriptExecutor,
HasCapabilities,
HasVirtualAuthenticator,
Interactive,
PrintsPage,
TakesScreenshot {
// TODO: This static logger should be unified with the per-instance localLogs
private static final Logger logger = Logger.getLogger(RemoteWebDriver.class.getName());
private final ElementLocation elementLocation = new ElementLocation();
private Level level = Level.FINE;
private ErrorHandler errorHandler = new ErrorHandler();
private CommandExecutor executor;
private Capabilities capabilities;
private SessionId sessionId;
private FileDetector fileDetector = new UselessFileDetector();
private ExecuteMethod executeMethod;
private JsonToWebElementConverter converter;
private Logs remoteLogs;
private LocalLogs localLogs;
// For cglib
protected RemoteWebDriver() {
this.capabilities = init(new ImmutableCapabilities());
}
public RemoteWebDriver(Capabilities capabilities) {
this(getDefaultServerURL(),
Require.nonNull("Capabilities", capabilities), true);
}
public RemoteWebDriver(Capabilities capabilities, boolean enableTracing) {
this(getDefaultServerURL(),
Require.nonNull("Capabilities", capabilities), enableTracing);
}
public RemoteWebDriver(URL remoteAddress, Capabilities capabilities) {
this(createExecutor(Require.nonNull("Server URL", remoteAddress), true),
Require.nonNull("Capabilities", capabilities));
}
public RemoteWebDriver(URL remoteAddress, Capabilities capabilities, boolean enableTracing) {
this(createExecutor(Require.nonNull("Server URL", remoteAddress), enableTracing),
Require.nonNull("Capabilities", capabilities));
}
public RemoteWebDriver(CommandExecutor executor, Capabilities capabilities) {
this.executor = Require.nonNull("Command executor", executor);
this.capabilities = init(capabilities);
if (executor instanceof NeedsLocalLogs) {
((NeedsLocalLogs) executor).setLocalLogs(localLogs);
}
try {
startSession(capabilities);
} catch (RuntimeException e) {
try {
quit();
} catch (Exception ignored) {
// Ignore the clean-up exception. We'll propagate the original failure.
}
throw e;
}
}
private static URL getDefaultServerURL() {
try {
return new URL(System.getProperty("webdriver.remote.server", "http://localhost:4444/"));
} catch (MalformedURLException e) {
throw new WebDriverException(e);
}
}
private static CommandExecutor createExecutor(URL remoteAddress, boolean enableTracing) {
ClientConfig config = ClientConfig.defaultConfig().baseUrl(remoteAddress);
if (enableTracing) {
Tracer tracer = OpenTelemetryTracer.getInstance();
CommandExecutor executor = new HttpCommandExecutor(
Collections.emptyMap(),
config,
new TracedHttpClient.Factory(tracer, HttpClient.Factory.createDefault()));
return new TracedCommandExecutor(executor, tracer);
} else {
return new HttpCommandExecutor(config);
}
}
@Beta
public static RemoteWebDriverBuilder builder() {
return new RemoteWebDriverBuilder();
}
private Capabilities init(Capabilities capabilities) {
capabilities = capabilities == null ? new ImmutableCapabilities() : capabilities;
logger.addHandler(LoggingHandler.getInstance());
converter = new JsonToWebElementConverter(this);
executeMethod = new RemoteExecuteMethod(this);
ImmutableSet.Builder builder = new ImmutableSet.Builder<>();
Set logTypesToInclude = builder.build();
LocalLogs performanceLogger = LocalLogs.getStoringLoggerInstance(logTypesToInclude);
LocalLogs clientLogs = LocalLogs.getHandlerBasedLoggerInstance(
LoggingHandler.getInstance(), logTypesToInclude);
localLogs = LocalLogs.getCombinedLogsHolder(clientLogs, performanceLogger);
remoteLogs = new RemoteLogs(executeMethod, localLogs);
return capabilities;
}
public SessionId getSessionId() {
return sessionId;
}
protected void setSessionId(String opaqueKey) {
sessionId = new SessionId(opaqueKey);
}
protected void startSession(Capabilities capabilities) {
checkNonW3CCapabilities(capabilities);
checkChromeW3CFalse(capabilities);
Response response = execute(DriverCommand.NEW_SESSION(singleton(capabilities)));
if (response == null) {
throw new SessionNotCreatedException(
"The underlying command executor returned a null response.");
}
Object responseValue = response.getValue();
if (responseValue == null) {
throw new SessionNotCreatedException(
"The underlying command executor returned a response without payload: " +
response);
}
if (!(responseValue instanceof Map)) {
throw new SessionNotCreatedException(
"The underlying command executor returned a response with a non well formed payload: " +
response);
}
@SuppressWarnings("unchecked") Map rawCapabilities = (Map) responseValue;
MutableCapabilities returnedCapabilities = new MutableCapabilities(rawCapabilities);
String platformString = (String) rawCapabilities.get(PLATFORM_NAME);
Platform platform;
try {
if (platformString == null || "".equals(platformString)) {
platform = Platform.ANY;
} else {
platform = Platform.fromString(platformString);
}
} catch (WebDriverException e) {
// The server probably responded with a name matching the os.name
// system property. Try to recover and parse this.
platform = Platform.extractFromSysProperty(platformString);
}
returnedCapabilities.setCapability(PLATFORM_NAME, platform);
this.capabilities = returnedCapabilities;
sessionId = new SessionId(response.getSessionId());
}
public ErrorHandler getErrorHandler() {
return errorHandler;
}
public void setErrorHandler(ErrorHandler handler) {
this.errorHandler = handler;
}
public CommandExecutor getCommandExecutor() {
return executor;
}
protected void setCommandExecutor(CommandExecutor executor) {
this.executor = executor;
}
@Override
public Capabilities getCapabilities() {
if(capabilities == null){
return new ImmutableCapabilities();
}
return capabilities;
}
@Override
public void get(String url) {
execute(DriverCommand.GET(url));
}
@Override
public String getTitle() {
Response response = execute(DriverCommand.GET_TITLE);
Object value = response.getValue();
return value == null ? "" : value.toString();
}
@Override
public String getCurrentUrl() {
Response response = execute(DriverCommand.GET_CURRENT_URL);
if (response == null || response.getValue() == null) {
throw new WebDriverException("Remote browser did not respond to getCurrentUrl");
}
return response.getValue().toString();
}
@Override
public X getScreenshotAs(OutputType outputType) throws WebDriverException {
Response response = execute(DriverCommand.SCREENSHOT);
Object result = response.getValue();
if (result instanceof String) {
String base64EncodedPng = (String) result;
return outputType.convertFromBase64Png(base64EncodedPng);
} else if (result instanceof byte[]) {
return outputType.convertFromPngBytes((byte[]) result);
} else {
throw new RuntimeException(String.format("Unexpected result for %s command: %s",
DriverCommand.SCREENSHOT,
result == null ? "null" : result.getClass().getName() + " instance"));
}
}
@Override
public Pdf print(PrintOptions printOptions) throws WebDriverException {
Response response = execute(DriverCommand.PRINT_PAGE(printOptions));
Object result = response.getValue();
return new Pdf((String) result);
}
@Override
public WebElement findElement(By locator) {
Require.nonNull("Locator", locator);
return findElement(this, DriverCommand::FIND_ELEMENT, locator);
}
WebElement findElement(
SearchContext context,
BiFunction findCommand,
By locator) {
return elementLocation.findElement(this, context, findCommand, locator);
}
@Override
public List findElements(By locator) {
Require.nonNull("Locator", locator);
return findElements(this, DriverCommand::FIND_ELEMENTS, locator);
}
public List findElements(
SearchContext context,
BiFunction findCommand,
By locator) {
return elementLocation.findElements(this, context, findCommand, locator);
}
/**
* @deprecated Rely on using {@link By.Remotable} instead
*/
@Deprecated
protected WebElement findElement(String by, String using) {
throw new UnsupportedOperationException("`findElement` has been replaced by usages of " + By.Remotable.class);
}
/**
* @deprecated Rely on using {@link By.Remotable} instead
*/
@Deprecated
protected List findElements(String by, String using) {
throw new UnsupportedOperationException("`findElement` has been replaced by usages of " + By.Remotable.class);
}
protected void setFoundBy(SearchContext context, WebElement element, String by, String using) {
if (element instanceof RemoteWebElement) {
RemoteWebElement remoteElement = (RemoteWebElement) element;
remoteElement.setFoundBy(context, by, using);
remoteElement.setFileDetector(getFileDetector());
}
}
@Override
public String getPageSource() {
return (String) execute(DriverCommand.GET_PAGE_SOURCE).getValue();
}
// Misc
@Override
public void close() {
if (this instanceof HasDevTools) {
// This is a brute force approach to "solving" the problem of a hanging
// CDP connection. Take a look at https://github.com/aslushnikov/getting-started-with-cdp#session-hierarchy
// to get up to speed, but essentially if the page session of the
// first browser window is closed, the next CDP command will hang
// indefinitely. To prevent that from happening, we close the current
// connection. The next CDP command _should_ make us reconnect
try {
((HasDevTools) this).maybeGetDevTools().ifPresent(DevTools::disconnectSession);
}
catch (ConnectionFailedException unableToEstablishWebsocketConnection) {
logger.log(SEVERE, "Failed to disconnect DevTools session", unableToEstablishWebsocketConnection);
}
}
Response response = execute(DriverCommand.CLOSE);
Object value = response.getValue();
List windowHandles = (ArrayList) value;
if (windowHandles.isEmpty() && this instanceof HasBiDi) {
// If no top-level browsing contexts are open after calling close, it indicates that the WebDriver session is closed.
// If the WebDriver session is closed, the BiDi session also needs to be closed.
((HasBiDi) this).maybeGetBiDi().ifPresent(BiDi::close);
}
}
@Override
public void quit() {
// no-op if session id is null. We're only going to make ourselves unhappy
if (sessionId == null) {
return;
}
try {
if (this instanceof HasDevTools) {
((HasDevTools) this).maybeGetDevTools().ifPresent(DevTools::close);
}
if (this instanceof HasBiDi) {
((HasBiDi) this).maybeGetBiDi().ifPresent(BiDi::close);
}
execute(DriverCommand.QUIT);
} finally {
sessionId = null;
}
}
@Override
@SuppressWarnings({"unchecked"})
public Set getWindowHandles() {
Response response = execute(DriverCommand.GET_WINDOW_HANDLES);
Object value = response.getValue();
try {
List returnedValues = (List) value;
return new LinkedHashSet<>(returnedValues);
} catch (ClassCastException ex) {
throw new WebDriverException(
"Returned value cannot be converted to List: " + value, ex);
}
}
@Override
public String getWindowHandle() {
return String.valueOf(execute(DriverCommand.GET_CURRENT_WINDOW_HANDLE).getValue());
}
@Override
public Object executeScript(String script, Object... args) {
if (!isJavascriptEnabled()) {
throw new UnsupportedOperationException(
"You must be using an underlying instance of WebDriver that supports executing javascript");
}
List
© 2015 - 2025 Weber Informatics LLC | Privacy Policy