package com.codeborne.selenide.webdriver;
import com.codeborne.selenide.Selenide;
import com.codeborne.selenide.WebDriverProvider;
import org.openqa.selenium.Capabilities;
import org.openqa.selenium.Dimension;
import org.openqa.selenium.Proxy;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.edge.EdgeDriver;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.firefox.FirefoxProfile;
import org.openqa.selenium.htmlunit.HtmlUnitDriver;
import org.openqa.selenium.ie.InternetExplorerDriver;
import org.openqa.selenium.internal.BuildInfo;
import org.openqa.selenium.remote.CapabilityType;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.remote.LocalFileDetector;
import org.openqa.selenium.remote.RemoteWebDriver;
import java.awt.*;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Logger;
import static com.codeborne.selenide.Configuration.*;
import static com.codeborne.selenide.WebDriverRunner.*;
import static com.codeborne.selenide.impl.Describe.describe;
import static org.openqa.selenium.remote.CapabilityType.*;
public class WebDriverFactory {
private static final Logger log = Logger.getLogger(WebDriverFactory.class.getName());
public WebDriver createWebDriver(Proxy proxy) {
log.config("Configuration.browser=" + browser);
log.config("Configuration.browser.version=" + browserVersion);
log.config("Configuration.remote=" + remote);
log.config("Configuration.browserSize=" + browserSize);
log.config("Configuration.startMaximized=" + startMaximized);
WebDriver webdriver = remote != null ? createRemoteDriver(remote, browser, proxy) :
CHROME.equalsIgnoreCase(browser) ? createChromeDriver(proxy) :
isMarionette() ? createMarionetteDriver(proxy) :
isFirefox() ? createFirefoxDriver(proxy) :
isHtmlUnit() ? createHtmlUnitDriver(proxy) :
isEdge() ? createEdgeDriver(proxy) :
isIE() ? createInternetExplorerDriver(proxy) :
isPhantomjs() ? createPhantomJsDriver(proxy) :
isOpera() ? createOperaDriver(proxy) :
isSafari() ? createSafariDriver(proxy) :
isJBrowser() ? createJBrowserDriver(proxy) :
createInstanceOf(browser, proxy);
webdriver = adjustBrowserSize(webdriver);
if (!isHeadless()) {
Capabilities capabilities = ((RemoteWebDriver) webdriver).getCapabilities();
log.info("BrowserName=" + capabilities.getBrowserName() + " Version=" + capabilities.getVersion()
+ " Platform=" + capabilities.getPlatform());
}
log.info("Selenide v. " + Selenide.class.getPackage().getImplementationVersion());
if (remote == null) {
BuildInfo seleniumInfo = new BuildInfo();
log.info("Selenium WebDriver v. " + seleniumInfo.getReleaseLabel() + " build time: " + seleniumInfo.getBuildTime());
} else {
((RemoteWebDriver) webdriver).setFileDetector(new LocalFileDetector());
}
return webdriver;
}
protected WebDriver createRemoteDriver(String remote, String browser, Proxy proxy) {
try {
DesiredCapabilities capabilities = createCommonCapabilities(proxy);
capabilities.setBrowserName(browser);
return new RemoteWebDriver(new URL(remote), capabilities);
} catch (MalformedURLException e) {
throw new IllegalArgumentException("Invalid 'remote' parameter: " + remote, e);
}
}
protected DesiredCapabilities createCommonCapabilities(Proxy proxy) {
DesiredCapabilities browserCapabilities = new DesiredCapabilities();
if (proxy != null) {
browserCapabilities.setCapability(PROXY, proxy);
}
if (browserVersion != null && !browserVersion.isEmpty()) {
browserCapabilities.setVersion(browserVersion);
}
browserCapabilities.setCapability(CapabilityType.PAGE_LOAD_STRATEGY, pageLoadStrategy);
browserCapabilities.setCapability("acceptSslCerts", true);
browserCapabilities = transferCapabilitiesFromSystemProperties(browserCapabilities, "capabilities.");
return browserCapabilities;
}
private DesiredCapabilities transferCapabilitiesFromSystemProperties(DesiredCapabilities currentBrowserCapabilities, String prefix) {
for (String key : System.getProperties().stringPropertyNames()) {
if (key.startsWith(prefix)) {
String capability = key.substring(prefix.length());
String value = System.getProperties().getProperty(key);
log.config("Use " + key + "=" + value);
if (value.equals("true") || value.equals("false")) {
currentBrowserCapabilities.setCapability(capability, Boolean.valueOf(value));
} else if (value.matches("^-?\\d+$")) { //if integer
currentBrowserCapabilities.setCapability(capability, Integer.parseInt(value));
} else {
currentBrowserCapabilities.setCapability(capability, value);
}
}
}
return currentBrowserCapabilities;
}
private FirefoxProfile transferFirefoxProfileFromSystemProperties(FirefoxProfile currentFirefoxProfile, String prefix) {
for (String key : System.getProperties().stringPropertyNames()) {
if (key.startsWith(prefix)) {
String capability = key.substring(prefix.length());
String value = System.getProperties().getProperty(key);
log.config("Use " + key + "=" + value);
if (value.equals("true") || value.equals("false")) {
currentFirefoxProfile.setPreference(capability, Boolean.valueOf(value));
} else if (value.matches("^-?\\d+$")) { //if integer
currentFirefoxProfile.setPreference(capability, Integer.parseInt(value));
} else {
currentFirefoxProfile.setPreference(capability, value);
}
}
}
return currentFirefoxProfile;
}
/**
* This method only handles so-called "arguments" for ChromeOptions (there is also "ExperimentalOptions", "Extensions" etc.)
*
* @param currentChromeOptions
* @param prefix
* @return
*/
private ChromeOptions transferChromeOptionsFromSystemProperties(ChromeOptions currentChromeOptions, String prefix) {
for (String key : System.getProperties().stringPropertyNames()) {
if (key.startsWith(prefix)) {
String capability = key.substring(prefix.length());
String value = System.getProperties().getProperty(key);
if (capability.equals("args")) {
List<String> args = Arrays.asList(value.split(","));
currentChromeOptions.addArguments(args);
} else {
log.warning(capability + "is ignored." +
"Only so-called arguments (chromeoptions.args=<values comma separated>) " +
"are supported for the chromeoptions at the moment");
}
}
}
return currentChromeOptions;
}
protected WebDriver createChromeDriver(Proxy proxy) {
DesiredCapabilities capabilities = createCommonCapabilities(proxy);
ChromeOptions options = createChromeOptions();
capabilities.setCapability(ChromeOptions.CAPABILITY, options);
return new ChromeDriver(capabilities);
}
protected ChromeOptions createChromeOptions() {
ChromeOptions options = new ChromeOptions();
options.addArguments("--no-sandbox"); // This make Chromium reachable (?)
if (chromeSwitches != null) {
options.addArguments(chromeSwitches);
}
options = transferChromeOptionsFromSystemProperties(options, "chromeoptions.");
try {
log.config("Chrome options:" + options.toJson().toString());
} catch (IOException e) {
log.warning("Error while reading from file:" + e.getMessage() + ". Ignoring it.");
e.printStackTrace(System.err);
}
return options;
}
protected WebDriver createFirefoxDriver(Proxy proxy) {
DesiredCapabilities capabilities = createFirefoxCapabilities(proxy);
log.info("Firefox 48+ is currently not supported by Selenium Firefox driver. " +
"Use browser=marionette with geckodriver, when using it.");
return new FirefoxDriver(capabilities);
}
protected DesiredCapabilities createFirefoxCapabilities(Proxy proxy) {
FirefoxProfile myProfile = new FirefoxProfile();
myProfile.setPreference("network.automatic-ntlm-auth.trusted-uris", "http://,https://");
myProfile.setPreference("network.automatic-ntlm-auth.allow-non-fqdn", true);
myProfile.setPreference("network.negotiate-auth.delegation-uris", "http://,https://");
myProfile.setPreference("network.negotiate-auth.trusted-uris", "http://,https://");
myProfile.setPreference("network.http.phishy-userpass-length", 255);
myProfile.setPreference("security.csp.enable", false);
DesiredCapabilities capabilities = createCommonCapabilities(proxy);
myProfile = transferFirefoxProfileFromSystemProperties(myProfile, "firefoxprofile.");
capabilities.setCapability(FirefoxDriver.PROFILE, myProfile);
capabilities.setCapability("marionette", false);
return capabilities;
}
protected WebDriver createMarionetteDriver(Proxy proxy) {
DesiredCapabilities capabilities = createFirefoxCapabilities(proxy);
capabilities.setCapability("marionette", true);
return new FirefoxDriver(capabilities);
}
protected WebDriver createHtmlUnitDriver(Proxy proxy) {
DesiredCapabilities capabilities = DesiredCapabilities.htmlUnit();
capabilities.merge(createCommonCapabilities(proxy));
capabilities.setCapability(HtmlUnitDriver.INVALIDSELECTIONERROR, true);
capabilities.setCapability(HtmlUnitDriver.INVALIDXPATHERROR, false);
if (browser.indexOf(':') > -1) {
// Use constants BrowserType.IE, BrowserType.FIREFOX, BrowserType.CHROME etc.
String emulatedBrowser = browser.replaceFirst("htmlunit:(.*)", "$1");
capabilities.setVersion(emulatedBrowser);
}
return new HtmlUnitDriver(capabilities);
}
protected WebDriver createInternetExplorerDriver(Proxy proxy) {
DesiredCapabilities capabilities = createCommonCapabilities(proxy);
return new InternetExplorerDriver(capabilities);
}
protected WebDriver createEdgeDriver(Proxy proxy) {
DesiredCapabilities capabilities = createCommonCapabilities(proxy);
return new EdgeDriver(capabilities);
}
protected WebDriver createPhantomJsDriver(Proxy proxy) {
return createInstanceOf("org.openqa.selenium.phantomjs.PhantomJSDriver", proxy);
}
protected WebDriver createOperaDriver(Proxy proxy) {
return createInstanceOf("com.opera.core.systems.OperaDriver", proxy);
}
protected WebDriver createSafariDriver(Proxy proxy) {
return createInstanceOf("org.openqa.selenium.safari.SafariDriver", proxy);
}
protected WebDriver createJBrowserDriver(Proxy proxy) {
return createInstanceOf("com.machinepublishers.jbrowserdriver.JBrowserDriver", proxy);
}
protected WebDriver adjustBrowserSize(WebDriver driver) {
if (browserSize != null) {
log.info("Set browser size to " + browserSize);
String[] dimension = browserSize.split("x");
int width = Integer.parseInt(dimension[0]);
int height = Integer.parseInt(dimension[1]);
driver.manage().window().setSize(new org.openqa.selenium.Dimension(width, height));
} else if (startMaximized) {
try {
if (isChrome()) {
maximizeChromeBrowser(driver.manage().window());
} else {
driver.manage().window().maximize();
}
} catch (Exception cannotMaximize) {
log.warning("Cannot maximize " + describe(driver) + ": " + cannotMaximize);
}
}
return driver;
}
protected void maximizeChromeBrowser(WebDriver.Window window) {
// Chrome driver does not yet support maximizing. Let' apply black magic!
org.openqa.selenium.Dimension screenResolution = getScreenSize();
window.setSize(screenResolution);
window.setPosition(new org.openqa.selenium.Point(0, 0));
}
Dimension getScreenSize() {
Toolkit toolkit = Toolkit.getDefaultToolkit();
return new Dimension(
(int) toolkit.getScreenSize().getWidth(),
(int) toolkit.getScreenSize().getHeight());
}
protected WebDriver createInstanceOf(String className, Proxy proxy) {
try {
DesiredCapabilities capabilities = createCommonCapabilities(proxy);
capabilities.setJavascriptEnabled(true);
capabilities.setCapability(TAKES_SCREENSHOT, true);
capabilities.setCapability(ACCEPT_SSL_CERTS, true);
capabilities.setCapability(SUPPORTS_ALERTS, true);
if (isPhantomjs()) {
capabilities.setCapability("phantomjs.cli.args", // PhantomJSDriverService.PHANTOMJS_CLI_ARGS == "phantomjs.cli.args"
new String[]{"--web-security=no", "--ignore-ssl-errors=yes"});
}
Class<?> clazz = Class.forName(className);
if (WebDriverProvider.class.isAssignableFrom(clazz)) {
Constructor<?> constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);
return ((WebDriverProvider) constructor.newInstance()).createDriver(capabilities);
} else {
Constructor<?> constructor = Class.forName(className).getConstructor(Capabilities.class);
return (WebDriver) constructor.newInstance(capabilities);
}
} catch (InvocationTargetException e) {
throw runtime(e.getTargetException());
} catch (Exception invalidClassName) {
throw new IllegalArgumentException(invalidClassName);
}
}
protected RuntimeException runtime(Throwable exception) {
return exception instanceof RuntimeException ? (RuntimeException) exception : new RuntimeException(exception);
}
}