package com.constellio.sdk.tests;
import static org.openqa.selenium.Platform.WINDOWS;
import static org.openqa.selenium.remote.CapabilityType.ACCEPT_SSL_CERTS;
import static org.openqa.selenium.remote.CapabilityType.HAS_NATIVE_EVENTS;
import static org.openqa.selenium.remote.CapabilityType.LOGGING_PREFS;
import static org.openqa.selenium.remote.CapabilityType.PROXY;
import static org.openqa.selenium.remote.CapabilityType.SUPPORTS_WEB_STORAGE;
import java.io.File;
import java.io.IOException;
import java.util.Date;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.openqa.selenium.Capabilities;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.Platform;
import org.openqa.selenium.Proxy;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.WebDriverException;
import org.openqa.selenium.browserlaunchers.Proxies;
import org.openqa.selenium.firefox.ExtensionConnection;
import org.openqa.selenium.firefox.FirefoxBinary;
import org.openqa.selenium.firefox.internal.MarionetteConnection;
import org.openqa.selenium.firefox.internal.NewProfileExtensionConnection;
import org.openqa.selenium.internal.Killable;
import org.openqa.selenium.internal.Lock;
import org.openqa.selenium.internal.SocketLock;
import org.openqa.selenium.io.TemporaryFilesystem;
import org.openqa.selenium.io.Zip;
import org.openqa.selenium.logging.LocalLogs;
import org.openqa.selenium.logging.LoggingPreferences;
import org.openqa.selenium.logging.NeedsLocalLogs;
import org.openqa.selenium.remote.BeanToJsonConverter;
import org.openqa.selenium.remote.Command;
import org.openqa.selenium.remote.CommandExecutor;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.remote.DriverCommand;
import org.openqa.selenium.remote.FileDetector;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.openqa.selenium.remote.Response;
import org.openqa.selenium.remote.SessionNotFoundException;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
public class ZeUltimateFirefoxDriver extends RemoteWebDriver implements TakesScreenshot, Killable {
public static final String BINARY = "firefox_binary";
public static final String PROFILE = "firefox_profile";
// For now, only enable native events on Windows
public static final boolean DEFAULT_ENABLE_NATIVE_EVENTS = Platform.getCurrent().is(WINDOWS);
// For now, only enable native events on Windows
public static final boolean USE_MARIONETTE = Boolean.parseBoolean(
System.getProperty("webdriver.firefox.marionette"));
// Accept untrusted SSL certificates.
@Deprecated
public static final boolean ACCEPT_UNTRUSTED_CERTIFICATES = true;
// Assume that the untrusted certificates will come from untrusted issuers
// or will be self signed.
@Deprecated
public static final boolean ASSUME_UNTRUSTED_ISSUER = true;
protected FirefoxBinary binary;
public ZeUltimateFirefoxDriver() {
this(newFirefoxBinary(), null);
}
private static FirefoxBinary newFirefoxBinary() {
long startTime = new Date().getTime();
FirefoxBinary firefoxBinary = new FirefoxBinary();
System.out.println("Create FirefoxBinary took " + (new Date().getTime() - startTime) + "ms");
return firefoxBinary;
}
public ZeUltimateFirefoxDriver(ZeUltimateFirefoxProfile profile) {
this(newFirefoxBinary(), profile);
}
public ZeUltimateFirefoxDriver(Capabilities desiredCapabilities) {
this(getBinary(desiredCapabilities), extractProfile(desiredCapabilities, null),
desiredCapabilities);
}
public ZeUltimateFirefoxDriver(Capabilities desiredCapabilities, Capabilities requiredCapabilities) {
this(getBinary(desiredCapabilities), extractProfile(desiredCapabilities, requiredCapabilities),
desiredCapabilities, requiredCapabilities);
}
private static ZeUltimateFirefoxProfile extractProfile(Capabilities desiredCapabilities,
Capabilities requiredCapabilities) {
long startTime = new Date().getTime();
ZeUltimateFirefoxProfile profile = null;
Object raw = null;
if (desiredCapabilities != null && desiredCapabilities.getCapability(PROFILE) != null) {
raw = desiredCapabilities.getCapability(PROFILE);
}
if (requiredCapabilities != null && requiredCapabilities.getCapability(PROFILE) != null) {
raw = requiredCapabilities.getCapability(PROFILE);
}
if (raw != null) {
if (raw instanceof ZeUltimateFirefoxProfile) {
profile = (ZeUltimateFirefoxProfile) raw;
} else if (raw instanceof String) {
try {
File dir = TemporaryFilesystem.getDefaultTmpFS().createTempDir("webdriver", "duplicated");
new Zip().unzip((String) raw, dir);
profile = new ZeUltimateFirefoxProfile(dir);
} catch (IOException e) {
throw new WebDriverException(e);
}
}
}
profile = getProfile(profile);
populateProfile(profile, desiredCapabilities);
populateProfile(profile, requiredCapabilities);
System.out.println("Extract profile took " + (new Date().getTime() - startTime) + "ms");
return profile;
}
static void populateProfile(ZeUltimateFirefoxProfile profile, Capabilities capabilities) {
if (capabilities == null) {
return;
}
if (capabilities.getCapability(SUPPORTS_WEB_STORAGE) != null) {
Boolean supportsWebStorage = (Boolean) capabilities.getCapability(SUPPORTS_WEB_STORAGE);
profile.setPreference("dom.storage.enabled", supportsWebStorage.booleanValue());
}
if (capabilities.getCapability(ACCEPT_SSL_CERTS) != null) {
Boolean acceptCerts = (Boolean) capabilities.getCapability(ACCEPT_SSL_CERTS);
profile.setAcceptUntrustedCertificates(acceptCerts);
}
if (capabilities.getCapability(LOGGING_PREFS) != null) {
LoggingPreferences logsPrefs =
(LoggingPreferences) capabilities.getCapability(LOGGING_PREFS);
for (String logtype : logsPrefs.getEnabledLogTypes()) {
profile.setPreference("webdriver.log." + logtype,
logsPrefs.getLevel(logtype).intValue());
}
}
if (capabilities.getCapability(HAS_NATIVE_EVENTS) != null) {
Boolean nativeEventsEnabled = (Boolean) capabilities.getCapability(HAS_NATIVE_EVENTS);
profile.setEnableNativeEvents(nativeEventsEnabled);
}
}
private static FirefoxBinary getBinary(Capabilities capabilities) {
long startTime = new Date().getTime();
if (capabilities != null && capabilities.getCapability(BINARY) != null) {
Object raw = capabilities.getCapability(BINARY);
if (raw instanceof FirefoxBinary) {
return (FirefoxBinary) raw;
}
File file = new File((String) raw);
return new FirefoxBinary(file);
}
System.out.println("getBinary took " + (new Date().getTime() - startTime) + "ms");
return newFirefoxBinary();
}
public ZeUltimateFirefoxDriver(FirefoxBinary binary, ZeUltimateFirefoxProfile profile) {
this(binary, profile, DesiredCapabilities.firefox());
}
public ZeUltimateFirefoxDriver(FirefoxBinary binary, ZeUltimateFirefoxProfile profile, Capabilities capabilities) {
this(binary, profile, capabilities, null);
}
public ZeUltimateFirefoxDriver(FirefoxBinary binary, ZeUltimateFirefoxProfile profile,
Capabilities desiredCapabilities, Capabilities requiredCapabilities) {
super(new LazyCommandExecutor(binary, profile),
dropCapabilities(desiredCapabilities, BINARY, PROFILE),
dropCapabilities(requiredCapabilities, BINARY, PROFILE));
this.binary = binary;
}
@Override
public void setFileDetector(FileDetector detector) {
throw new WebDriverException(
"Setting the file detector only works on remote webdriver instances obtained " +
"via RemoteWebDriver");
}
/**
* Attempt to forcibly kill this Killable at the OS level. Useful where the extension has
* stopped responding, and you don't want to leak resources. Should not ordinarily be called.
*/
public void kill() {
binary.quit();
}
@Override
public Options manage() {
return new RemoteWebDriverOptions() {
@Override
public Timeouts timeouts() {
return new RemoteTimeouts() {
public Timeouts implicitlyWait(long time, TimeUnit unit) {
execute(DriverCommand.SET_TIMEOUT, ImmutableMap.of(
"type", "implicit",
"ms", TimeUnit.MILLISECONDS.convert(time, unit)));
return this;
}
public Timeouts setScriptTimeout(long time, TimeUnit unit) {
execute(DriverCommand.SET_TIMEOUT, ImmutableMap.of(
"type", "script",
"ms", TimeUnit.MILLISECONDS.convert(time, unit)));
return this;
}
};
}
};
}
@Override
protected void startClient() {
LazyCommandExecutor exe = (LazyCommandExecutor) getCommandExecutor();
ZeUltimateFirefoxProfile profileToUse = getProfile(exe.profile);
ExtensionConnection connection = connectTo(exe.binary, profileToUse, "localhost");
exe.setConnection(connection);
try {
connection.start();
} catch (IOException e) {
throw new WebDriverException("An error occurred while connecting to Firefox", e);
}
}
private static ZeUltimateFirefoxProfile getProfile(ZeUltimateFirefoxProfile profile) {
ZeUltimateFirefoxProfile profileToUse = profile;
String suggestedProfile = System.getProperty("webdriver.firefox.profile");
if (profileToUse == null && suggestedProfile != null) {
throw new RuntimeException("Ze ultimate firefox driver doesn't support 'webdriver.firefox.profile'");
} else if (profileToUse == null) {
profileToUse = new ZeUltimateFirefoxProfile();
}
return profileToUse;
}
protected Lock obtainLock() {
return new SocketLock();
}
@Override
protected void stopClient() {
((LazyCommandExecutor) this.getCommandExecutor()).quit();
}
/**
* Drops capabilities that we shouldn't send over the wire.
*
* Used for capabilities which aren't BeanToJson-convertable, and are only used by the local
* launcher.
*/
private static Capabilities dropCapabilities(Capabilities capabilities, String... keysToRemove) {
if (capabilities == null) {
return new DesiredCapabilities();
}
final Set<String> toRemove = Sets.newHashSet(keysToRemove);
DesiredCapabilities caps = new DesiredCapabilities(Maps.filterKeys(capabilities.asMap(), new Predicate<String>() {
public boolean apply(String key) {
return !toRemove.contains(key);
}
}));
// Ensure that the proxy is in a state fit to be sent to the extension
Proxy proxy = Proxies.extractProxy(capabilities);
if (proxy != null) {
caps.setCapability(PROXY, new BeanToJsonConverter().convert(proxy));
}
return caps;
}
public <X> X getScreenshotAs(OutputType<X> target) {
// Get the screenshot as base64.
String base64 = execute(DriverCommand.SCREENSHOT).getValue().toString();
// ... and convert it.
return target.convertFromBase64Png(base64);
}
private static class LazyCommandExecutor implements CommandExecutor, NeedsLocalLogs {
private ExtensionConnection connection;
private final FirefoxBinary binary;
private final ZeUltimateFirefoxProfile profile;
private LocalLogs logs = LocalLogs.getNullLogger();
private LazyCommandExecutor(FirefoxBinary binary, ZeUltimateFirefoxProfile profile) {
this.binary = binary;
this.profile = profile;
}
public void setConnection(ExtensionConnection connection) {
this.connection = connection;
connection.setLocalLogs(logs);
}
public void quit() {
if (connection != null) {
connection.quit();
connection = null;
}
if (profile != null) {
profile.cleanTemporaryModel();
}
}
public Response execute(Command command)
throws IOException {
if (connection == null) {
if (command.getName().equals(DriverCommand.QUIT)) {
return new Response();
}
throw new SessionNotFoundException(
"The FirefoxDriver cannot be used after quit() was called.");
}
return connection.execute(command);
}
public void setLocalLogs(LocalLogs logs) {
this.logs = logs;
if (connection != null) {
connection.setLocalLogs(logs);
}
}
}
protected ExtensionConnection connectTo(FirefoxBinary binary, ZeUltimateFirefoxProfile profile, String host) {
ExtensionConnection connection;
long connectionStart = new Date().getTime();
Lock lock = obtainLock();
try {
FirefoxBinary bin = binary == null ? newFirefoxBinary() : binary;
if (USE_MARIONETTE) {
// System.out.println("************************** Using marionette");
connection = new MarionetteConnection(lock, bin, profile, host);
} else if ("localhost".equals(host)) {
connection = new ZeUltimateLocalhostFirefoxConnection(lock, bin, profile);
} else {
connection = new NewProfileExtensionConnection(lock, bin, profile, host);
}
} catch (Exception e) {
throw new WebDriverException(e);
} finally {
lock.unlock();
}
return connection;
}
@Override
public String toString() {
return "FirefoxDriver";
}
}