/*
* (C) Copyright 2014 Kurento (http://kurento.org/)
*
* Licensed 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.kurento.test.browser;
import static org.kurento.commons.PropertiesManager.getProperty;
import static org.kurento.test.config.TestConfiguration.SAUCELAB_COMMAND_TIMEOUT_DEFAULT;
import static org.kurento.test.config.TestConfiguration.SAUCELAB_COMMAND_TIMEOUT_PROPERTY;
import static org.kurento.test.config.TestConfiguration.SAUCELAB_IDLE_TIMEOUT_DEFAULT;
import static org.kurento.test.config.TestConfiguration.SAUCELAB_IDLE_TIMEOUT_PROPERTY;
import static org.kurento.test.config.TestConfiguration.SAUCELAB_KEY_PROPERTY;
import static org.kurento.test.config.TestConfiguration.SAUCELAB_MAX_DURATION_DEFAULT;
import static org.kurento.test.config.TestConfiguration.SAUCELAB_MAX_DURATION_PROPERTY;
import static org.kurento.test.config.TestConfiguration.SAUCELAB_USER_PROPERTY;
import static org.kurento.test.config.TestConfiguration.SELENIUM_MAX_DRIVER_ERROR_DEFAULT;
import static org.kurento.test.config.TestConfiguration.SELENIUM_MAX_DRIVER_ERROR_PROPERTY;
import static org.kurento.test.config.TestConfiguration.SELENIUM_REMOTEWEBDRIVER_TIME_DEFAULT;
import static org.kurento.test.config.TestConfiguration.SELENIUM_REMOTEWEBDRIVER_TIME_PROPERTY;
import static org.kurento.test.config.TestConfiguration.SELENIUM_REMOTE_HUB_URL_PROPERTY;
import static org.kurento.test.config.TestConfiguration.SELENIUM_SCOPE_PROPERTY;
import static org.kurento.test.config.TestConfiguration.SELENIUM_VERSION;
import static org.kurento.test.config.TestConfiguration.TEST_HOST_PROPERTY;
import static org.kurento.test.config.TestConfiguration.TEST_NODE_LOGIN_PROPERTY;
import static org.kurento.test.config.TestConfiguration.TEST_NODE_PASSWD_PROPERTY;
import static org.kurento.test.config.TestConfiguration.TEST_NODE_PEM_PROPERTY;
import static org.kurento.test.config.TestConfiguration.TEST_PATH_DEFAULT;
import static org.kurento.test.config.TestConfiguration.TEST_PATH_PROPERTY;
import static org.kurento.test.config.TestConfiguration.TEST_PORT_PROPERTY;
import static org.kurento.test.config.TestConfiguration.TEST_PROTOCOL_DEFAULT;
import static org.kurento.test.config.TestConfiguration.TEST_PROTOCOL_PROPERTY;
import static org.kurento.test.config.TestConfiguration.TEST_PUBLIC_IP_DEFAULT;
import static org.kurento.test.config.TestConfiguration.TEST_PUBLIC_IP_PROPERTY;
import static org.kurento.test.config.TestConfiguration.TEST_PUBLIC_PORT_PROPERTY;
import static org.kurento.test.config.TestConfiguration.TEST_SCREEN_SHARE_TITLE_DEFAULT;
import static org.kurento.test.config.TestConfiguration.TEST_SCREEN_SHARE_TITLE_DEFAULT_WIN;
import static org.kurento.test.config.TestConfiguration.TEST_SCREEN_SHARE_TITLE_PROPERTY;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.kurento.commons.exception.KurentoException;
import org.kurento.test.base.KurentoTest;
import org.kurento.test.config.AudioChannel;
import org.kurento.test.config.BrowserScope;
import org.kurento.test.config.Protocol;
import org.kurento.test.config.TestConfiguration;
import org.kurento.test.docker.Docker;
import org.kurento.test.grid.GridHandler;
import org.kurento.test.grid.GridNode;
import org.kurento.test.services.WebServerService;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.Platform;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebDriverException;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.firefox.FirefoxProfile;
import org.openqa.selenium.ie.InternetExplorerDriver;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.openqa.selenium.safari.SafariDriver;
import org.openqa.selenium.support.ui.ExpectedCondition;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.github.bonigarcia.wdm.ChromeDriverManager;
/**
* Wrapper of Selenium Webdriver for testing Kurento applications.
*
* @author Boni Garcia (bgarcia@gsyc.es)
* @author Micael Gallego (micael.gallego@gmail.com)
* @since 5.1.0
* @see <a href="http://www.seleniumhq.org/">Selenium</a>
*/
public class Browser implements Closeable {
public static Logger log = LoggerFactory.getLogger(Browser.class);
private WebDriver driver;
private String jobId;
private Builder builder;
private BrowserType browserType;
private BrowserScope scope;
private String browserVersion;
private Platform platform;
private String video;
private String audio;
private int recordAudio;
private int audioSampleRate;
private AudioChannel audioChannel;
private int timeout;
private boolean usePhysicalCam;
private boolean enableScreenCapture;
private String name;
private String id;
private double colorDistance;
private int thresholdTime;
private int numInstances;
private int browserPerInstance;
private Protocol protocol;
private String node;
private String host;
private int serverPort;
private WebPageType webPageType;
private String webPagePath;
private String login;
private String passwd;
private String pem;
private boolean avoidProxy;
private String parentTunnel;
private List<Map<String, String>> extensions;
private URI url;
private static volatile DockerBrowserManager dockerManager;
private static Docker docker = Docker.getSingleton();
public Browser(Builder builder) {
this.builder = builder;
this.scope = builder.scope;
this.video = builder.video;
this.audio = builder.audio;
this.serverPort =
getProperty(TEST_PORT_PROPERTY, getProperty(TEST_PUBLIC_PORT_PROPERTY, builder.serverPort));
this.webPageType = builder.webPageType;
this.browserType = builder.browserType;
this.usePhysicalCam = builder.usePhysicalCam;
this.enableScreenCapture = builder.enableScreenCapture;
this.recordAudio = builder.recordAudio;
this.audioSampleRate = builder.audioSampleRate;
this.audioChannel = builder.audioChannel;
this.browserVersion = builder.browserVersion;
this.platform = builder.platform;
this.timeout = builder.timeout;
this.colorDistance = builder.colorDistance;
this.thresholdTime = builder.thresholdTime;
this.node = builder.node;
this.protocol = builder.protocol;
this.numInstances = builder.numInstances;
this.browserPerInstance = builder.browserPerInstance;
this.login = builder.login;
this.passwd = builder.passwd;
this.pem = builder.pem;
this.host = builder.host;
this.avoidProxy = builder.avoidProxy;
this.parentTunnel = builder.parentTunnel;
this.extensions = builder.extensions;
this.url = builder.url;
this.webPagePath = builder.webPagePath;
}
public void init() {
log.debug("Starting browser {}", getId());
Class<? extends WebDriver> driverClass = browserType.getDriverClass();
try {
DesiredCapabilities capabilities = new DesiredCapabilities();
if (driverClass.equals(FirefoxDriver.class)) {
createFirefoxBrowser(capabilities);
} else if (driverClass.equals(ChromeDriver.class)) {
createChromeBrowser(capabilities);
} else if (driverClass.equals(InternetExplorerDriver.class)) {
if (scope == BrowserScope.SAUCELABS) {
capabilities.setBrowserName(DesiredCapabilities.internetExplorer().getBrowserName());
capabilities.setCapability("ignoreProtectedModeSettings", true);
createSaucelabsDriver(capabilities);
}
} else if (driverClass.equals(SafariDriver.class)) {
if (scope == BrowserScope.SAUCELABS) {
capabilities.setBrowserName(DesiredCapabilities.safari().getBrowserName());
createSaucelabsDriver(capabilities);
}
}
// Timeouts
changeTimeout(timeout);
log.debug("Browser {} started", getId());
calculateUrl();
log.debug("Browser {} loading url {}", getId(), url);
driver.get(url.toString());
log.debug("Browser {} initialized", getId());
} catch (MalformedURLException e) {
log.error("MalformedURLException in Browser.init", e);
}
}
private void calculateUrl() {
if (url == null) {
if (protocol == Protocol.FILE) {
String webPage = webPagePath != null ? webPagePath : webPageType.toString();
File webPageFile =
new File(this.getClass().getClassLoader().getResource("static" + webPage).getFile());
try {
url = new URI(protocol.toString() + webPageFile.getAbsolutePath());
} catch (URISyntaxException e) {
throw new KurentoException("Exception generating URI from " + protocol + " and "
+ webPageFile.getAbsolutePath());
}
} else {
String hostName;
log.debug("BrowserScope is {}", scope);
if (scope == BrowserScope.DOCKER) {
if (docker.isRunningInContainer()) {
hostName = docker.getContainerIpAddress();
} else {
hostName = docker.getHostIpForContainers();
}
} else {
hostName = host != null ? host : node;
}
log.debug("Protocol: {}, Hostname: {}, Port: {}, Web page type: {}", protocol, hostName,
serverPort, webPageType);
try {
url = new URI(protocol.toString(), null, hostName, serverPort, webPageType.toString(),
null, null);
} catch (URISyntaxException e) {
throw new KurentoException("Exception generating URI from " + protocol + ", " + hostName
+ ", server port " + serverPort + " and webpage type " + webPageType);
}
}
}
}
private void createChromeBrowser(DesiredCapabilities capabilities) throws MalformedURLException {
// Chrome driver
ChromeDriverManager.getInstance().setup();
// Chrome options
ChromeOptions options = new ChromeOptions();
// Chrome extensions
if (extensions != null && !extensions.isEmpty()) {
for (Map<String, String> extension : extensions) {
InputStream is = getExtensionAsInputStream(extension.values().iterator().next());
if (is != null) {
try {
File crx = File.createTempFile(extension.keySet().iterator().next(), ".crx");
FileUtils.copyInputStreamToFile(is, crx);
options.addExtensions(crx);
} catch (Throwable t) {
log.error("Error loading Chrome extension {} ({} : {})", extension, t.getClass(),
t.getMessage());
}
}
}
}
if (enableScreenCapture) {
// This flag enables the screen sharing
options.addArguments("--enable-usermedia-screen-capturing");
String windowTitle = TEST_SCREEN_SHARE_TITLE_DEFAULT;
if (platform != null
&& (platform == Platform.WINDOWS || platform == Platform.XP || platform == Platform.VISTA
|| platform == Platform.WIN8 || platform == Platform.WIN8_1)) {
windowTitle = TEST_SCREEN_SHARE_TITLE_DEFAULT_WIN;
}
options.addArguments("--auto-select-desktop-capture-source="
+ getProperty(TEST_SCREEN_SHARE_TITLE_PROPERTY, windowTitle));
} else {
// This flag avoids grant the camera
options.addArguments("--use-fake-ui-for-media-stream");
}
// This flag avoids warning in Chrome. See:
// https://code.google.com/p/chromedriver/issues/detail?id=799
options.addArguments("--test-type");
if (protocol == Protocol.FILE) {
// This flag allows reading local files in video tags
options.addArguments("--allow-file-access-from-files");
}
if (!usePhysicalCam) {
// This flag makes using a synthetic video (green with
// spinner) in WebRTC. Or it is needed to combine with
// use-file-for-fake-video-capture to use a file faking the
// cam
options.addArguments("--use-fake-device-for-media-stream=fps=30");
if (video != null && (isLocal() || isDocker())) {
if (!Files.exists(Paths.get(video))) {
throw new RuntimeException("Trying to create a browser using video file " + video
+ ", but this file doesn't exist.");
}
log.debug("Using video {} in browser {}", video, id);
options.addArguments("--use-file-for-fake-video-capture=" + video);
}
}
capabilities.setCapability(ChromeOptions.CAPABILITY, options);
capabilities.setBrowserName(DesiredCapabilities.chrome().getBrowserName());
createDriver(capabilities, options);
}
private void createFirefoxBrowser(DesiredCapabilities capabilities) throws MalformedURLException {
FirefoxProfile profile = new FirefoxProfile();
// This flag avoids granting the access to the camera
profile.setPreference("media.navigator.permission.disabled", true);
capabilities.setCapability(FirefoxDriver.PROFILE, profile);
capabilities.setBrowserName(DesiredCapabilities.firefox().getBrowserName());
// Firefox extensions
if (extensions != null && !extensions.isEmpty()) {
for (Map<String, String> extension : extensions) {
InputStream is = getExtensionAsInputStream(extension.values().iterator().next());
if (is != null) {
try {
File xpi = File.createTempFile(extension.keySet().iterator().next(), ".xpi");
FileUtils.copyInputStreamToFile(is, xpi);
profile.addExtension(xpi);
} catch (Throwable t) {
log.error("Error loading Firefox extension {} ({} : {})", extension, t.getClass(),
t.getMessage());
}
}
}
}
createDriver(capabilities, profile);
}
private void createDriver(DesiredCapabilities capabilities, Object options)
throws MalformedURLException {
log.debug("Creating driver in scope {} for browser {}", scope, id);
if (scope == BrowserScope.SAUCELABS) {
createSaucelabsDriver(capabilities);
} else if (scope == BrowserScope.REMOTE) {
createRemoteDriver(capabilities);
} else if (scope == BrowserScope.DOCKER) {
driver = getDockerManager().createDockerDriver(id, capabilities);
} else {
driver = newWebDriver(options);
}
}
private DockerBrowserManager getDockerManager() {
if (dockerManager == null) {
synchronized (Browser.class) {
if (dockerManager == null) {
dockerManager = new DockerBrowserManager();
}
}
}
return dockerManager;
}
public static WebDriver newWebDriver(Object options) {
WebDriver driver = null;
int numDriverTries = 0;
final int maxDriverError =
getProperty(SELENIUM_MAX_DRIVER_ERROR_PROPERTY, SELENIUM_MAX_DRIVER_ERROR_DEFAULT);
final String errMessage = "Exception creating webdriver for chrome";
do {
try {
if (options instanceof ChromeOptions) {
driver = new ChromeDriver((ChromeOptions) options);
} else if (options instanceof FirefoxProfile) {
driver = new FirefoxDriver((FirefoxProfile) options);
}
} catch (Throwable t) {
driver = null;
log.warn(errMessage + " #" + numDriverTries, t);
} finally {
numDriverTries++;
if (numDriverTries > maxDriverError) {
throw new KurentoException(errMessage + " (" + maxDriverError + " times)");
}
}
} while (driver == null);
return driver;
}
public void reload() {
if (url != null) {
this.driver.get(url.toString());
}
}
public InputStream getExtensionAsInputStream(String extension) {
InputStream is = null;
try {
log.debug("Trying to locate extension in the classpath ({}) ...", extension);
is = ClassLoader.getSystemResourceAsStream(extension);
if (is.available() < 0) {
log.warn("Extension {} is not located in the classpath", extension);
is = null;
} else {
log.debug("Success. Loading extension {} from classpath", extension);
}
} catch (Throwable t) {
log.warn("Exception reading extension {} in the classpath ({} : {})", extension, t.getClass(),
t.getMessage());
is = null;
}
if (is == null) {
try {
log.debug("Trying to locate extension as URL ({}) ...", extension);
URL url = new URL(extension);
is = url.openStream();
log.debug("Success. Loading extension {} from URL", extension);
} catch (Throwable t) {
log.warn("Exception reading extension {} as URL ({} : {})", extension, t.getClass(),
t.getMessage());
}
}
if (is == null) {
throw new RuntimeException(
extension + " is not a valid extension (it is not located in project"
+ " classpath neither is a valid URL)");
}
return is;
}
public void changeTimeout(int timeoutSeconds) {
driver.manage().timeouts().implicitlyWait(timeoutSeconds, TimeUnit.SECONDS);
driver.manage().timeouts().setScriptTimeout(timeoutSeconds, TimeUnit.SECONDS);
}
public void createSaucelabsDriver(DesiredCapabilities capabilities) throws MalformedURLException {
assertPublicIpNotNull();
String sauceLabsUser = getProperty(SAUCELAB_USER_PROPERTY);
String sauceLabsKey = getProperty(SAUCELAB_KEY_PROPERTY);
if (sauceLabsUser == null || sauceLabsKey == null) {
throw new RuntimeException("Invalid Saucelabs credentials: " + SAUCELAB_USER_PROPERTY + "="
+ sauceLabsUser + " " + SAUCELAB_KEY_PROPERTY + "=" + sauceLabsKey);
}
capabilities.setCapability("version", browserVersion);
capabilities.setCapability("platform", platform);
String seleniumVersion = getProperty(SELENIUM_VERSION);
if (seleniumVersion != null) {
capabilities.setCapability("seleniumVersion", seleniumVersion);
}
if (parentTunnel != null) {
capabilities.setCapability("parent-tunnel", parentTunnel);
}
if (avoidProxy) {
capabilities.setCapability("avoid-proxy", avoidProxy);
}
int idleTimeout = getProperty(SAUCELAB_IDLE_TIMEOUT_PROPERTY, SAUCELAB_IDLE_TIMEOUT_DEFAULT);
int commandTimeout =
getProperty(SAUCELAB_COMMAND_TIMEOUT_PROPERTY, SAUCELAB_COMMAND_TIMEOUT_DEFAULT);
int maxDuration = getProperty(SAUCELAB_MAX_DURATION_PROPERTY, SAUCELAB_MAX_DURATION_DEFAULT);
capabilities.setCapability("idleTimeout", idleTimeout);
capabilities.setCapability("commandTimeout", commandTimeout);
capabilities.setCapability("maxDuration", maxDuration);
if (name != null) {
capabilities.setCapability("name", name);
}
driver = new RemoteWebDriver(
new URL(
"http://" + sauceLabsUser + ":" + sauceLabsKey + "@ondemand.saucelabs.com:80/wd/hub"),
capabilities);
jobId = ((RemoteWebDriver) driver).getSessionId().toString();
log.debug("%%%%%%%%%%%%% Saucelabs URL job for {} ({} {} in {}) %%%%%%%%%%%%%", id, browserType,
browserVersion, platform);
log.debug("https://saucelabs.com/tests/{}", jobId);
}
public void createRemoteDriver(final DesiredCapabilities capabilities)
throws MalformedURLException {
assertPublicIpNotNull();
String remoteHubUrl = getProperty(SELENIUM_REMOTE_HUB_URL_PROPERTY);
GridNode gridNode = null;
if (remoteHubUrl == null) {
log.debug("Creating remote webdriver for {}", id);
if (!GridHandler.getInstance().containsSimilarBrowserKey(id)) {
if (login != null) {
System.setProperty(TEST_NODE_LOGIN_PROPERTY, login);
}
if (passwd != null) {
System.setProperty(TEST_NODE_PASSWD_PROPERTY, passwd);
}
if (pem != null) {
System.setProperty(TEST_NODE_PEM_PROPERTY, pem);
}
// Filtering valid nodes (just the first time will be effective)
GridHandler.getInstance().filterValidNodes();
if (!node.equals(host) && login != null && !login.isEmpty()
&& (passwd != null && !passwd.isEmpty() || pem != null && !pem.isEmpty())) {
gridNode = new GridNode(node, browserType, browserPerInstance, login, passwd, pem);
GridHandler.getInstance().addNode(id, gridNode);
} else {
gridNode =
GridHandler.getInstance().getRandomNodeFromList(id, browserType, browserPerInstance);
}
// Start Hub (just the first time will be effective)
GridHandler.getInstance().startHub();
// Start node
GridHandler.getInstance().startNode(gridNode);
// Copy video (if necessary)
if (video != null && browserType == BrowserType.CHROME) {
GridHandler.getInstance().copyRemoteVideo(gridNode, video);
}
} else {
// Wait for node
boolean started = false;
do {
gridNode = GridHandler.getInstance().getNode(id);
if (gridNode != null) {
started = gridNode.isStarted();
}
if (!started) {
log.debug("Node {} is not started ... waiting 1 second", id);
waitSeconds(1);
}
} while (!started);
}
// At this moment we are able to use the argument for remote video
if (video != null && browserType == BrowserType.CHROME) {
ChromeOptions options =
(ChromeOptions) capabilities.getCapability(ChromeOptions.CAPABILITY);
options.addArguments("--use-file-for-fake-video-capture="
+ GridHandler.getInstance().getFirstNode(id).getRemoteVideo(video));
capabilities.setCapability(ChromeOptions.CAPABILITY, options);
}
final int hubPort = GridHandler.getInstance().getHubPort();
final String hubHost = GridHandler.getInstance().getHubHost();
log.debug("Creating remote webdriver of {} ({})", id, gridNode.getHost());
remoteHubUrl = "http://" + hubHost + ":" + hubPort + "/wd/hub";
}
final String remoteHub = remoteHubUrl;
Thread t = new Thread() {
@Override
public void run() {
boolean exception = false;
do {
try {
driver = new RemoteWebDriver(new URL(remoteHub), capabilities);
exception = false;
} catch (MalformedURLException | WebDriverException e) {
log.error("Exception {} creating RemoteWebDriver ... retrying in 1 second",
e.getClass());
waitSeconds(1);
exception = true;
}
} while (exception);
}
};
t.start();
int timeout =
getProperty(SELENIUM_REMOTEWEBDRIVER_TIME_PROPERTY, SELENIUM_REMOTEWEBDRIVER_TIME_DEFAULT);
String nodeMsg = gridNode != null ? " (" + gridNode.getHost() + ")" : "";
for (int i = 0; i < timeout; i++) {
if (t.isAlive()) {
log.debug("Waiting for RemoteWebDriver {}{}", id, nodeMsg);
} else {
log.debug("Remote webdriver of {}{} created", id, nodeMsg);
return;
}
waitSeconds(1);
}
String exceptionMessage =
"Remote webdriver of " + id + nodeMsg + " not created in " + timeout + "seconds";
log.error(">>>>>>>>>> " + exceptionMessage);
throw new RuntimeException(exceptionMessage);
}
private void waitSeconds(long timeInSeconds) {
try {
Thread.sleep(TimeUnit.SECONDS.toMillis(timeInSeconds));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void assertPublicIpNotNull() {
if (host == null) {
throw new RuntimeException("Public IP must be available to run remote test. "
+ "You can do it by adding the paramter -D" + TEST_HOST_PROPERTY
+ "=<public_ip> or with key 'host' in " + "the JSON configuration file.");
}
}
public void injectKurentoTestJs() throws IOException {
if (this.getBrowserType() != BrowserType.IEXPLORER) {
String kurentoTestJsContent = "";
String kurentoTestPath = "static/lib/kurento-test.min.js";
try {
File pageFile =
new File(this.getClass().getClassLoader().getResource(kurentoTestPath).getFile());
kurentoTestJsContent = new String(Files.readAllBytes(pageFile.toPath()));
} catch (NoSuchFileException nsfe) {
InputStream inputStream =
this.getClass().getClassLoader().getResourceAsStream(kurentoTestPath);
StringWriter writer = new StringWriter();
IOUtils.copy(inputStream, writer, Charset.defaultCharset());
kurentoTestJsContent = writer.toString();
}
String kurentoTestJs = "var kurentoScript=window.document.createElement('script');";
kurentoTestJs += "kurentoScript.type='text/javascript';";
kurentoTestJs += "kurentoScript.text='" + kurentoTestJsContent + "';";
kurentoTestJs += "window.document.head.appendChild(kurentoScript);";
kurentoTestJs += "return true;";
this.executeScript(kurentoTestJs);
String recordingJs = "var recScript=window.document.createElement('script');";
recordingJs += "recScript.type='text/javascript';";
recordingJs += "recScript.src='https://cdn.webrtc-experiment.com/RecordRTC.js';";
recordingJs += "window.document.head.appendChild(recScript);";
recordingJs += "return true;";
this.executeScript(recordingJs);
}
}
public int getRecordAudio() {
return recordAudio;
}
public int getAudioSampleRate() {
return audioSampleRate;
}
public AudioChannel getAudioChannel() {
return audioChannel;
}
public String getAudio() {
return audio;
}
public int getTimeout() {
return timeout;
}
public long getTimeoutMs() {
return TimeUnit.SECONDS.toMillis(timeout);
}
public WebDriver getWebDriver() {
return driver;
}
public JavascriptExecutor getJs() {
return (JavascriptExecutor) driver;
}
public Object executeScriptAndWaitOutput(final String command) {
WebDriverWait wait = new WebDriverWait(driver, timeout);
wait.withMessage("Timeout executing script: " + command);
final Object[] out = new Object[1];
wait.until(new ExpectedCondition<Boolean>() {
@Override
public Boolean apply(WebDriver d) {
try {
out[0] = executeScript(command);
} catch (WebDriverException we) {
log.warn("Exception executing script", we);
out[0] = null;
}
return out[0] != null;
}
});
return out[0];
}
public Object executeScript(final String command) {
return ((JavascriptExecutor) driver).executeScript(command);
}
public double getColorDistance() {
return colorDistance;
}
public int getThresholdTime() {
return thresholdTime;
}
public void setThresholdTime(int thresholdTime) {
this.thresholdTime = thresholdTime;
}
public boolean isLocal() {
return BrowserScope.LOCAL.equals(this.scope);
}
public boolean isRemote() {
return BrowserScope.REMOTE.equals(this.scope);
}
public boolean isSauceLabs() {
return BrowserScope.SAUCELABS.equals(this.scope);
}
public boolean isDocker() {
return BrowserScope.DOCKER.equals(this.scope);
}
public BrowserType getBrowserType() {
return browserType;
}
public BrowserScope getScope() {
return scope;
}
public String getBrowserVersion() {
return browserVersion;
}
public Platform getPlatform() {
return platform;
}
public String getVideo() {
return video;
}
public int getServerPort() {
return serverPort;
}
public WebPageType getWebPageType() {
return webPageType;
}
public boolean isUsePhysicalCam() {
return usePhysicalCam;
}
public boolean isEnableScreenCapture() {
return enableScreenCapture;
}
public String getName() {
return name;
}
public String getId() {
return id;
}
public String getNode() {
return node;
}
public void setName(String name) {
this.name = name;
}
public void setId(String id) {
this.id = id;
}
public int getNumInstances() {
return numInstances;
}
public Builder getBuilder() {
return builder;
}
public String getLogin() {
return login;
}
public String getPasswd() {
return passwd;
}
public String getPem() {
return pem;
}
public int getBrowserPerInstance() {
return browserPerInstance;
}
public String getHost() {
return host;
}
public void setTimeout(int timeoutSeconds) {
this.timeout = timeoutSeconds;
}
public Protocol getProtocol() {
return protocol;
}
public URL getUrl() {
URL url = null;
try {
if (this.url != null) {
url = this.url.toURL();
} else {
String ip = this.getHost();
int port = this.getServerPort();
String protocol = this.getProtocol().toString();
String path = this.getWebPageType().toString();
url = new URL(protocol, ip, port, path);
}
} catch (MalformedURLException e) {
log.error("Malformed URL", e);
throw new RuntimeException(e);
}
return url;
}
@Override
public void close() {
// WebDriver
if (driver != null) {
try {
log.debug("Closing webdriver of {} ", id);
driver.quit();
driver = null;
} catch (Throwable t) {
log.warn("** Exception closing webdriver {} : {}", t.getClass(), t.getMessage());
}
}
// Stop Selenium Grid (if necessary)
if (GridHandler.getInstance().useRemoteNodes()) {
log.debug("Closing Grid of {} ", id);
GridHandler.getInstance().stopGrid();
}
// Stop docker containers (if necessary)
if (scope == BrowserScope.DOCKER) {
Path logFile = Paths.get(KurentoTest.getDefaultOutputFile(""));
try {
if (!Files.exists(logFile)) {
Files.createDirectories(logFile);
}
getDockerManager().setDownloadLogsPath(logFile);
} catch (IOException e) {
log.warn("Exception creating path {} for logs", logFile);
}
getDockerManager().closeDriver(id);
}
}
public String getJobId() {
return jobId;
}
public static class Builder {
private int timeout = 60; // seconds
private int thresholdTime = 10; // seconds
private double colorDistance = 60;
private String node = getProperty(TEST_HOST_PROPERTY,
getProperty(TEST_PUBLIC_IP_PROPERTY, TEST_PUBLIC_IP_DEFAULT));
private String host = node;
private int serverPort = getProperty(TEST_PORT_PROPERTY,
getProperty(TEST_PUBLIC_PORT_PROPERTY, WebServerService.getAppHttpsPort()));
private BrowserScope scope = BrowserScope.LOCAL;
private BrowserType browserType = BrowserType.CHROME;
private Protocol protocol =
Protocol.valueOf(getProperty(TEST_PROTOCOL_PROPERTY, TEST_PROTOCOL_DEFAULT).toUpperCase());
private WebPageType webPageType =
WebPageType.value2WebPageType(getProperty(TEST_PATH_PROPERTY, TEST_PATH_DEFAULT));
private boolean usePhysicalCam = false;
private boolean enableScreenCapture = false;
private int recordAudio = 0; // seconds
private int audioSampleRate; // samples per seconds (e.g. 8000, 16000)
private AudioChannel audioChannel; // stereo, mono
private int numInstances = 0;
private int browserPerInstance = 1;
private String video;
private String audio;
private String browserVersion;
private Platform platform;
private String login;
private String passwd;
private String pem;
private boolean avoidProxy;
private String parentTunnel;
private List<Map<String, String>> extensions;
private URI url;
private String webPagePath;
public Builder browserPerInstance(int browserPerInstance) {
this.browserPerInstance = browserPerInstance;
return this;
}
public Builder login(String login) {
this.login = login;
return this;
}
public Builder passwd(String passwd) {
this.passwd = passwd;
return this;
}
public Builder pem(String pem) {
this.pem = pem;
return this;
}
public Builder protocol(Protocol protocol) {
this.protocol = protocol;
return this;
}
public Builder numInstances(int numInstances) {
this.numInstances = numInstances;
return this;
}
public Builder serverPort(int serverPort) {
this.serverPort = serverPort;
return this;
}
public Builder node(String node) {
this.node = node;
return this;
}
public Builder scope(BrowserScope scope) {
String scopeProp = getProperty(SELENIUM_SCOPE_PROPERTY);
if (scopeProp != null) {
scope = BrowserScope.valueOf(scopeProp.toUpperCase());
}
this.scope = scope;
String appAutostart = getProperty(TestConfiguration.TEST_APP_AUTOSTART_PROPERTY,
TestConfiguration.TEST_APP_AUTOSTART_DEFAULT);
if (BrowserScope.DOCKER.equals(scope)
&& !appAutostart.equals(TestConfiguration.AUTOSTART_FALSE_VALUE)) {
if (docker.isRunningInContainer()) {
this.node = docker.getContainerIpAddress();
} else {
this.node = docker.getHostIpForContainers();
}
}
return this;
}
public Builder timeout(int timeout) {
this.timeout = timeout;
return this;
}
public Builder thresholdTime(int thresholdTime) {
this.thresholdTime = thresholdTime;
return this;
}
public Builder colorDistance(double colorDistance) {
this.colorDistance = colorDistance;
return this;
}
public Builder video(String video) {
this.video = video;
return this;
}
public Builder webPageType(WebPageType webPageType) {
this.webPageType = webPageType;
return this;
}
public Builder browserType(BrowserType browser) {
this.browserType = browser;
return this;
}
public Builder usePhysicalCam() {
this.usePhysicalCam = true;
return this;
}
public Builder avoidProxy() {
this.avoidProxy = true;
return this;
}
public Builder parentTunnel(String parentTunnel) {
this.parentTunnel = parentTunnel;
return this;
}
public Builder enableScreenCapture() {
this.enableScreenCapture = true;
return this;
}
public Builder audio(String audio, int recordAudio, int audioSampleRate,
AudioChannel audioChannel) {
this.audio = audio;
this.recordAudio = recordAudio;
this.audioSampleRate = audioSampleRate;
this.audioChannel = audioChannel;
return this;
}
public Builder browserVersion(String browserVersion) {
this.browserVersion = browserVersion;
return this;
}
public Builder platform(Platform platform) {
this.platform = platform;
return this;
}
public Builder host(String host) {
this.host = host;
return this;
}
public Builder extensions(List<Map<String, String>> extensions) {
this.extensions = extensions;
return this;
}
public Builder url(String url) {
try {
this.url = new URI(url);
} catch (URISyntaxException e) {
throw new KurentoException("Could not parse URI " + url);
}
return this;
}
public Builder webPagePath(String webPagePath) {
this.webPagePath = webPagePath;
return this;
}
public Browser build() {
return new Browser(this);
}
}
}