Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Opening a remote browser with custom web driver timeouts #87

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,12 @@
<artifactId>commons-exec</artifactId>
<version>1.3</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,18 @@
package com.github.markusbernhardt.selenium2library.keywords;

import com.github.markusbernhardt.selenium2library.RunOnFailureKeywordsAdapter;
import com.github.markusbernhardt.selenium2library.Selenium2LibraryFatalException;
import com.github.markusbernhardt.selenium2library.Selenium2LibraryNonFatalException;
import com.github.markusbernhardt.selenium2library.locators.ElementFinder;
import com.github.markusbernhardt.selenium2library.locators.WindowManager;
import com.github.markusbernhardt.selenium2library.utils.CustomHttpClientFactory;
import com.github.markusbernhardt.selenium2library.utils.Robotframework;
import com.github.markusbernhardt.selenium2library.utils.TimeUtils;
import com.github.markusbernhardt.selenium2library.utils.WebDriverCache;
import com.github.markusbernhardt.selenium2library.utils.WebDriverCache.SessionIdAliasWebDriverTuple;
import com.opera.core.systems.OperaDriver;
import io.appium.java_client.ios.IOSDriver;
import io.selendroid.client.SelendroidDriver;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;

import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.NTCredentials;
Expand All @@ -36,25 +33,31 @@
import org.openqa.selenium.ie.InternetExplorerDriver;
import org.openqa.selenium.phantomjs.PhantomJSDriver;
import org.openqa.selenium.remote.Augmenter;
import org.openqa.selenium.remote.CommandInfo;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.remote.HttpCommandExecutor;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.openqa.selenium.remote.http.HttpClient;
import org.openqa.selenium.safari.SafariDriver;
import org.robotframework.javalib.annotation.ArgumentNames;
import org.robotframework.javalib.annotation.Autowired;
import org.robotframework.javalib.annotation.RobotKeyword;
import org.robotframework.javalib.annotation.RobotKeywordOverload;
import org.robotframework.javalib.annotation.RobotKeywords;

import com.github.markusbernhardt.selenium2library.RunOnFailureKeywordsAdapter;
import com.github.markusbernhardt.selenium2library.Selenium2LibraryFatalException;
import com.github.markusbernhardt.selenium2library.Selenium2LibraryNonFatalException;
import com.github.markusbernhardt.selenium2library.locators.ElementFinder;
import com.github.markusbernhardt.selenium2library.locators.WindowManager;
import com.github.markusbernhardt.selenium2library.utils.Robotframework;
import com.github.markusbernhardt.selenium2library.utils.WebDriverCache;
import com.github.markusbernhardt.selenium2library.utils.WebDriverCache.SessionIdAliasWebDriverTuple;
import com.opera.core.systems.OperaDriver;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;

@SuppressWarnings("deprecation")
@RobotKeywords
Expand Down Expand Up @@ -211,6 +214,16 @@ public String openBrowser(String url, String browserName, String alias, String r
return openBrowser(url, browserName, alias, remoteUrl, desiredCapabilities, null);
}

@RobotKeywordOverload
public String openBrowser(String url, String browserName, String alias, String remoteUrl,
String desiredCapabilities, String browserOptions) throws Throwable {
// Magic constants '2 minutes' and '3 hours' are defined at HttpClientFactory, and hard-coded by default:
// HttpClientFactory.TIMEOUT_TWO_MINUTES
// HttpClientFactory.TIMEOUT_THREE_HOURS
return openBrowser(url, browserName, alias, remoteUrl, desiredCapabilities, browserOptions,
"2 minutes", "3 hours");
}

/**
* Opens a new browser instance to given URL.<br>
* <br>
Expand Down Expand Up @@ -345,7 +358,7 @@ public String openBrowser(String url, String browserName, String alias, String r
* href=
* "http://selenium-grid.seleniumhq.org/faq.html#i_get_some_strange_errors_when_i_run_multiple_internet_explorer_instances_on_the_same_machine"
* >Strange errors with multiple IE instances</a><br>
*
*
* @param url
* The URL to open in the newly created browser instance.
* @param browserName
Expand All @@ -369,18 +382,23 @@ public String openBrowser(String url, String browserName, String alias, String r
* >DesiredCapabilities</a>
* @param browserOptions
* Default=NONE. Extended browser options as JSON structure.
* @param connectionTimeout
* Default=2 minutes. Connection timeout value for (Remote) WebDriver.
* @param socketTimeout
* Default=3 hours. Socket timeout value for (Remote) WebDriver.
* @return The index of the newly created browser instance.
* @throws Throwable - if anything goes wrong
*
*
* @see BrowserManagement#closeAllBrowsers
* @see BrowserManagement#closeBrowser
* @see BrowserManagement#switchBrowser
*/
@RobotKeyword
@ArgumentNames({ "url", "browserName=firefox", "alias=NONE", "remoteUrl=False", "desiredCapabilities=NONE",
"browserOptions=NONE" })
"browserOptions=NONE", "connectionTimeout=2 minutes", "socketTimeout=3 hours" })
public String openBrowser(String url, String browserName, String alias, String remoteUrl,
String desiredCapabilities, String browserOptions) throws Throwable {
String desiredCapabilities, String browserOptions, String connectionTimeout,
String socketTimeout) throws Throwable {
try {
logging.info("browserName: " + browserName);
if (remoteUrl != null) {
Expand All @@ -389,8 +407,10 @@ public String openBrowser(String url, String browserName, String alias, String r
} else {
logging.info(String.format("Opening browser '%s' to base url '%s'", browserName, url));
}

WebDriver webDriver = createWebDriver(browserName, desiredCapabilities, remoteUrl, browserOptions);
final int connectionTimeoutMillis = TimeUtils.convertRobotTimeToMillis(connectionTimeout);
final int socketTimeoutMillis = TimeUtils.convertRobotTimeToMillis(socketTimeout);
WebDriver webDriver = createWebDriver(browserName, desiredCapabilities, remoteUrl, browserOptions,
CustomHttpClientFactory.createWithSpecificTimeout(connectionTimeoutMillis, socketTimeoutMillis));
webDriver.get(url);
String sessionId = webDriverCache.register(webDriver, alias);
logging.debug(String.format("Opened browser with session id %s", sessionId));
Expand Down Expand Up @@ -1350,14 +1370,14 @@ protected String getPasswordFromURL(URL url) {
}

protected WebDriver createWebDriver(String browserName, String desiredCapabilitiesString, String remoteUrlString,
String browserOptions) throws MalformedURLException {
String browserOptions, HttpClient.Factory factory) throws MalformedURLException {
browserName = browserName.toLowerCase().replace(" ", "");
DesiredCapabilities desiredCapabilities = createDesiredCapabilities(browserName, desiredCapabilitiesString,
browserOptions);

WebDriver webDriver;
if (remoteUrlString != null && !"False".equals(remoteUrlString)) {
webDriver = createRemoteWebDriver(desiredCapabilities, new URL(remoteUrlString));
webDriver = createRemoteWebDriver(desiredCapabilities, new URL(remoteUrlString), factory);
} else {
webDriver = createLocalWebDriver(browserName, desiredCapabilities);
}
Expand Down Expand Up @@ -1404,8 +1424,10 @@ protected WebDriver createLocalWebDriver(String browserName, DesiredCapabilities
throw new Selenium2LibraryFatalException(browserName + " is not a supported browser.");
}

protected WebDriver createRemoteWebDriver(DesiredCapabilities desiredCapabilities, URL remoteUrl) {
HttpCommandExecutor httpCommandExecutor = new HttpCommandExecutor(remoteUrl);
protected WebDriver createRemoteWebDriver(DesiredCapabilities desiredCapabilities, URL remoteUrl,
HttpClient.Factory factory) {
HttpCommandExecutor httpCommandExecutor = new HttpCommandExecutor(Collections.<String, CommandInfo>emptyMap(),
remoteUrl, factory);
setRemoteWebDriverProxy(httpCommandExecutor);
return new Augmenter().augment(new RemoteWebDriver(httpCommandExecutor, desiredCapabilities));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package com.github.markusbernhardt.selenium2library.utils;

import org.apache.http.auth.Credentials;
import org.apache.http.impl.client.CloseableHttpClient;
import org.openqa.selenium.remote.http.HttpClient;
import org.openqa.selenium.remote.internal.ApacheHttpClient;
import org.openqa.selenium.remote.internal.HttpClientFactory;

import java.net.URL;

/**
* Provides customized {@link HttpClient.Factory} instances.
* {@link HttpClient} creation is via delegation to {@link ApacheHttpClient.Factory} as default behavior does.
*/
public class CustomHttpClientFactory implements HttpClient.Factory {
private final HttpClient.Factory delegate;

private CustomHttpClientFactory(HttpClientFactory factory) {
delegate = new ApacheHttpClient.Factory(factory);
}

@Override
public HttpClient createClient(URL url) {
return delegate.createClient(url);
}

/**
* Creates a HttpClient.Factory with customized connection and socked timeouts.
* Default timeout values are 2 minutes for connection, and 3 hours for socket as hard-coded in default
* {@link HttpClientFactory} class.
*
* @param connectionTimeout the connection timeout in milliseconds
* @param socketTimeout the socket timeout in milliseconds
* @return a HttpClient.Factory instance that will create
*/
public static HttpClient.Factory createWithSpecificTimeout(int connectionTimeout, int socketTimeout) {
HttpClientFactory factory = new CustomTimeoutHttpClientFactory(connectionTimeout, socketTimeout);
return new CustomHttpClientFactory(factory);
}

/**
* {@link HttpClientFactory} is using hard-coded timeouts for connection timeout (2m) and socket timeout (3h).
* This behavior is undesired in some cases, when socket timeout keeps the webdriver blocked for 3 hours on too
* early connection attempt to opened port.
*/
private static class CustomTimeoutHttpClientFactory extends HttpClientFactory {
private final int connectionTimeout;
private final int socketTimeout;

private CustomTimeoutHttpClientFactory(int connectionTimeout, int socketTimeout) {
super(connectionTimeout, socketTimeout);
this.connectionTimeout = connectionTimeout;
this.socketTimeout = socketTimeout;
}

@Override
public CloseableHttpClient createHttpClient(Credentials credentials) {
return super.createHttpClient(credentials, connectionTimeout, socketTimeout);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package com.github.markusbernhardt.selenium2library.utils;

/**
* Utilities to convert Robot Framework time.
*/
public final class TimeUtils {
private static final int SECONDS_TO_MILLISECS = 1000;
private static final int MINUTES_TO_MILLISECS = 60 * SECONDS_TO_MILLISECS;
private static final int HOURS_TO_MILLISECS = 60 * MINUTES_TO_MILLISECS;
private static final int DAYS_TO_MILLISECS = 24 * HOURS_TO_MILLISECS;

private static final String DAYS_PATTERN = "(\\s*\\d+(\\.\\d+)?\\s*d(ays?)?)?";
private static final String HOURS_PATTERN = "(\\s*\\d+(\\.\\d+)?\\s*h(ours?)?)?";
private static final String MINUTES_PATTERN = "(\\s*\\d+(\\.\\d+)?\\s*m(in((ute)?s?)?)?)?";
private static final String SECONDS_PATTERN = "(\\s*\\d+(\\.\\d+)?\\s*s(ec((ond)?s?))?)?";
private static final String MILLISECONDS_PATTERN = "(\\s*\\d+(\\.\\d+)?\\s*(millis(ec((ond)?s?))?|ms))?";
private static final String TIME_STRING_PATTERN = "-?(\\s*\\d+(\\.\\d+)?\\s*|" + DAYS_PATTERN + HOURS_PATTERN +
MINUTES_PATTERN + SECONDS_PATTERN + MILLISECONDS_PATTERN + ")";

private TimeUtils() {
// this is a utility class
}

/**
* Converts a Robot Framework time string to milliseconds value.
* See http://robotframework.org/robotframework/latest/libraries/DateTime.html
*
* @param robotTimeString a valid time string
* @return the time in milliseconds
*/
public static int convertRobotTimeToMillis(String robotTimeString) {
int sum = 0;
if (!robotTimeString.matches(TIME_STRING_PATTERN)) {
throw new IllegalArgumentException("Invalid time string " + robotTimeString);
}
String[] values = robotTimeString.replaceAll("(\\d)([dhms])", "$1 $2").split("\\s+");
try {
if (values.length == 1) {
return Math.round(Float.parseFloat(values[0]) * SECONDS_TO_MILLISECS);
}
final int signum = values[0].startsWith("-") ? -1 : 1;
if (values.length % 2 != 0) {
throw new IllegalArgumentException("Invalid time string " + robotTimeString);
}
for (int i = 0; i < values.length - 1; i+=2) {
final float value = Math.abs(Float.parseFloat(values[i]));
final int multiplier = getMultiplier(values[i + 1]);
sum += signum * Math.round(value * multiplier);
}
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Invalid time string " + robotTimeString, e);
}
return sum;
}

private static int getMultiplier(String specifier) {
if ("days".equalsIgnoreCase(specifier)
|| "day".equalsIgnoreCase(specifier)
|| "d".equalsIgnoreCase(specifier)) {
return DAYS_TO_MILLISECS;
}
if ("hours".equalsIgnoreCase(specifier)
|| "hour".equalsIgnoreCase(specifier)
|| "h".equalsIgnoreCase(specifier)) {
return HOURS_TO_MILLISECS;
}
if ("minutes".equalsIgnoreCase(specifier)
|| "minute".equalsIgnoreCase(specifier)
|| "mins".equalsIgnoreCase(specifier)
|| "min".equalsIgnoreCase(specifier)
|| "m".equalsIgnoreCase(specifier)) {
return MINUTES_TO_MILLISECS;
}
if ("seconds".equalsIgnoreCase(specifier)
|| "second".equalsIgnoreCase(specifier)
|| "secs".equalsIgnoreCase(specifier)
|| "sec".equalsIgnoreCase(specifier)
|| "s".equalsIgnoreCase(specifier)) {
return SECONDS_TO_MILLISECS;
}
if ("milliseconds".equalsIgnoreCase(specifier)
|| "millisecond".equalsIgnoreCase(specifier)
|| "millis".equalsIgnoreCase(specifier)
|| "ms".equalsIgnoreCase(specifier)) {
return 1;
}
throw new IllegalArgumentException("Invalid time specifier " + specifier);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package com.github.markusbernhardt.selenium2library.utils;

import org.junit.Test;

import static org.junit.Assert.assertEquals;

public class TimeUtilsTest {

@Test
public void whenNoSpecifier_ShouldConvertSeconds() {
assertEquals(1000, TimeUtils.convertRobotTimeToMillis("1"));
assertEquals(-2000, TimeUtils.convertRobotTimeToMillis("-2"));
assertEquals(500, TimeUtils.convertRobotTimeToMillis("0.5"));
}

@Test
public void whenMillisecondsSpecified_ShouldConvertMillisecsonds() {
assertEquals(-10, TimeUtils.convertRobotTimeToMillis("-10ms"));
assertEquals(1, TimeUtils.convertRobotTimeToMillis("0.8 milliseconds"));
assertEquals(1, TimeUtils.convertRobotTimeToMillis("1 millisecond"));
assertEquals(1, TimeUtils.convertRobotTimeToMillis("1 millis"));
}

@Test
public void whenSecondsSpecified_ShouldConvertSeconds() {
assertEquals(-10000, TimeUtils.convertRobotTimeToMillis("-10seconds"));
assertEquals(800, TimeUtils.convertRobotTimeToMillis("0.8 s"));
assertEquals(1000, TimeUtils.convertRobotTimeToMillis("1 sec"));
assertEquals(2000, TimeUtils.convertRobotTimeToMillis("2second"));
}

@Test
public void whenMinutesSpecified_ShouldConvertMinutes() {
assertEquals(-600000, TimeUtils.convertRobotTimeToMillis("-10min"));
assertEquals(48000, TimeUtils.convertRobotTimeToMillis("0.8 m"));
assertEquals(60000, TimeUtils.convertRobotTimeToMillis("1 minute"));
assertEquals(120000, TimeUtils.convertRobotTimeToMillis("2minutes"));
}

@Test
public void whenHoursSpecified_ShouldConvertHours() {
assertEquals(-36000000, TimeUtils.convertRobotTimeToMillis("-10h"));
assertEquals(3600000, TimeUtils.convertRobotTimeToMillis("1 hour"));
assertEquals(5400000, TimeUtils.convertRobotTimeToMillis("1.5 hours"));
}

@Test
public void whenDaysSpecified_ShouldConvertDays() {
assertEquals(-864000000, TimeUtils.convertRobotTimeToMillis("-10days"));
assertEquals(129600000, TimeUtils.convertRobotTimeToMillis("1.5d"));
assertEquals(86400000, TimeUtils.convertRobotTimeToMillis("1 day"));
}

@Test
public void whenComplexSpecified_ShouldConvertComplex() {
assertEquals(151264015, TimeUtils.convertRobotTimeToMillis("1.5d 6 hours 1 minute 4 secs 15ms"));
assertEquals(-151264015, TimeUtils.convertRobotTimeToMillis("-1.5d 6 hours 1 minute 4 secs 15ms"));
}

}