Skip to content

Commit

Permalink
Merge pull request #787 from codeborne/rosolko-784
Browse files Browse the repository at this point in the history
Fix #784 and close #785
  • Loading branch information
asolntsev committed Aug 27, 2018
2 parents fe32335 + 1fa0ba0 commit 2a928a0
Show file tree
Hide file tree
Showing 24 changed files with 587 additions and 118 deletions.
2 changes: 1 addition & 1 deletion config/checkstyle/checkstyle.xml
Expand Up @@ -57,7 +57,7 @@
<module name="UnnecessaryParentheses"/> <module name="UnnecessaryParentheses"/>
<!--<module name="MutableException"/>--> <!--<module name="MutableException"/>-->
<module name="ClassFanOutComplexity"> <module name="ClassFanOutComplexity">
<property name="max" value="28"/> <property name="max" value="30"/>
</module> </module>
<module name="CyclomaticComplexity"> <module name="CyclomaticComplexity">
<property name="max" value="20"/> <property name="max" value="20"/>
Expand Down
25 changes: 25 additions & 0 deletions src/main/java/com/codeborne/selenide/AuthenticationType.java
@@ -0,0 +1,25 @@
package com.codeborne.selenide;

/**
* Authentication schemes.
*
* @see <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication#Authentication_schemes">Web HTTP reference</a>
*/
public enum AuthenticationType {
BASIC("Basic"),
BEARER("Bearer"),
DIGEST("Digest"),
HOBA("HOBA"),
MUTUAL("Mutual"),
AWS4_HMAC_SHA256("AWS4-HMAC-SHA256");

private final String value;

AuthenticationType(String value) {
this.value = value;
}

public String getValue() {
return value;
}
}
34 changes: 34 additions & 0 deletions src/main/java/com/codeborne/selenide/Credentials.java
@@ -0,0 +1,34 @@
package com.codeborne.selenide;

import java.util.Base64;

import static java.nio.charset.StandardCharsets.UTF_8;

public final class Credentials {
public final String login;
public final String password;

public Credentials(String login, String password) {
this.login = login;
this.password = password;
}

/**
* The resulting string is base64 encoded (YWxhZGRpbjpvcGVuc2VzYW1l).
*
* @return encoded string
*/
public String encode() {
byte[] credentialsBytes = combine().getBytes(UTF_8);
return Base64.getEncoder().encodeToString(credentialsBytes);
}

private String combine() {
return String.format("%s:%s", login, password);
}

@Override
public String toString() {
return combine();
}
}
50 changes: 41 additions & 9 deletions src/main/java/com/codeborne/selenide/Selenide.java
Expand Up @@ -83,24 +83,56 @@ public static void open(URL absoluteUrl) {


/** /**
* The main starting point in your tests. * The main starting point in your tests.
* <p>
* Open a browser window with given URL and credentials for basic authentication * Open a browser window with given URL and credentials for basic authentication
* * <p>
* If browser window was already opened before, it will be reused. * If browser window was already opened before, it will be reused.
* * <p>
* Don't bother about closing the browser - it will be closed automatically when all your tests are done. * Don't bother about closing the browser - it will be closed automatically when all your tests are done.
* * <p>
* @param relativeOrAbsoluteUrl * If not starting with "http://" or "https://" or "file://", it's considered to be relative URL.
* @param domain * <p>
* @param login * In this case, it's prepended by baseUrl
* @param password
* If not starting with "http://" or "https://" or "file://", it's considered to be relative URL.
* In this case, it's prepended by baseUrl
*/ */
public static void open(String relativeOrAbsoluteUrl, String domain, String login, String password) { public static void open(String relativeOrAbsoluteUrl, String domain, String login, String password) {
navigator.open(relativeOrAbsoluteUrl, domain, login, password); navigator.open(relativeOrAbsoluteUrl, domain, login, password);
mockModalDialogs(); mockModalDialogs();
} }


/**
* The main starting point in your tests.
* <p>
* Open browser and pass authentication using build-in proxy.
* <p>
* A common authenticationType is "Basic". See Web HTTP reference for other types.
* <p>
* This method can only work if - {@code Configuration.fileDownload == Configuration.FileDownloadMode.PROXY;}
*
* @see <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Proxy-Authorization">Web HTTP reference</a>
* @see AuthenticationType
*/
public static void open(String relativeOrAbsoluteUrl, AuthenticationType authenticationType, String login, String password) {
Credentials credentials = new Credentials(login, password);
open(relativeOrAbsoluteUrl, authenticationType, credentials);
}

/**
* The main starting point in your tests.
* <p>
* Open browser and pass authentication using build-in proxy.
* <p>
* A common authenticationType is "Basic". See Web HTTP reference for other types.
* <p>
* This method can only work if - {@code Configuration.fileDownload == Configuration.FileDownloadMode.PROXY;}
*
* @see <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Proxy-Authorization">Web HTTP reference</a>
* @see AuthenticationType
* @see Credentials
*/
public static void open(String relativeOrAbsoluteUrl, AuthenticationType authenticationType, Credentials credentials) {
navigator.open(relativeOrAbsoluteUrl, authenticationType, credentials);
}

/** /**
* @see Selenide#open(URL, String, String, String) * @see Selenide#open(URL, String, String, String)
*/ */
Expand Down
82 changes: 58 additions & 24 deletions src/main/java/com/codeborne/selenide/impl/Navigator.java
@@ -1,17 +1,24 @@
package com.codeborne.selenide.impl; package com.codeborne.selenide.impl;


import com.codeborne.selenide.AuthenticationType;
import com.codeborne.selenide.Configuration;
import com.codeborne.selenide.Credentials;
import com.codeborne.selenide.logevents.SelenideLog; import com.codeborne.selenide.logevents.SelenideLog;
import com.codeborne.selenide.logevents.SelenideLogger; import com.codeborne.selenide.logevents.SelenideLogger;
import com.codeborne.selenide.proxy.AuthenticationFilter;
import com.codeborne.selenide.proxy.SelenideProxyServer;
import org.openqa.selenium.JavascriptExecutor; import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebDriverException; import org.openqa.selenium.WebDriverException;


import java.net.URL; import java.net.URL;
import java.util.logging.Logger; import java.util.logging.Logger;


import static com.codeborne.selenide.Configuration.FileDownloadMode.PROXY;
import static com.codeborne.selenide.Configuration.baseUrl; import static com.codeborne.selenide.Configuration.baseUrl;
import static com.codeborne.selenide.Configuration.captureJavascriptErrors; import static com.codeborne.selenide.Configuration.captureJavascriptErrors;
import static com.codeborne.selenide.WebDriverRunner.getAndCheckWebDriver; import static com.codeborne.selenide.WebDriverRunner.getAndCheckWebDriver;
import static com.codeborne.selenide.WebDriverRunner.getSelenideProxy;
import static com.codeborne.selenide.WebDriverRunner.getWebDriver; import static com.codeborne.selenide.WebDriverRunner.getWebDriver;
import static com.codeborne.selenide.WebDriverRunner.isIE; import static com.codeborne.selenide.WebDriverRunner.isIE;
import static com.codeborne.selenide.logevents.LogEvent.EventStatus.PASS; import static com.codeborne.selenide.logevents.LogEvent.EventStatus.PASS;
Expand All @@ -20,54 +27,67 @@ public class Navigator {
private static final Logger log = Logger.getLogger(Navigator.class.getName()); private static final Logger log = Logger.getLogger(Navigator.class.getName());


public void open(String relativeOrAbsoluteUrl) { public void open(String relativeOrAbsoluteUrl) {
open(relativeOrAbsoluteUrl, "", "", ""); navigateTo(relativeOrAbsoluteUrl, AuthenticationType.BASIC, "", "", "");
} }


public void open(URL url) { public void open(URL url) {
open(url, "", "", ""); navigateTo(url.toExternalForm(), AuthenticationType.BASIC, "", "", "");
} }


public void open(String relativeOrAbsoluteUrl, String domain, String login, String password) { public void open(String relativeOrAbsoluteUrl, String domain, String login, String password) {
if (isAbsoluteUrl(relativeOrAbsoluteUrl)) { navigateTo(relativeOrAbsoluteUrl, AuthenticationType.BASIC, domain, login, password);
navigateToAbsoluteUrl(relativeOrAbsoluteUrl, domain, login, password);
} else {
navigateToAbsoluteUrl(absoluteUrl(relativeOrAbsoluteUrl), domain, login, password);
}
} }


public void open(URL url, String domain, String login, String password) { public void open(URL url, String domain, String login, String password) {
navigateToAbsoluteUrl(url.toExternalForm()); navigateTo(url.toExternalForm(), AuthenticationType.BASIC, domain, login, password);
} }


protected String absoluteUrl(String relativeUrl) { public void open(String relativeOrAbsoluteUrl, AuthenticationType authenticationType, Credentials credentials) {
return baseUrl + relativeUrl; navigateTo(relativeOrAbsoluteUrl, authenticationType, "", credentials.login, credentials.password);
} }


protected void navigateToAbsoluteUrl(String url) { private AuthenticationFilter basicAuthRequestFilter() {
navigateToAbsoluteUrl(url, "", "", ""); getAndCheckWebDriver();
SelenideProxyServer selenideProxy = getSelenideProxy();
return selenideProxy.requestFilter("authentication");
} }


protected void navigateToAbsoluteUrl(String url, String domain, String login, String password) { String absoluteUrl(String relativeOrAbsoluteUrl) {
return isAbsoluteUrl(relativeOrAbsoluteUrl) ? relativeOrAbsoluteUrl : baseUrl + relativeOrAbsoluteUrl;
}

private void navigateTo(String url, AuthenticationType authenticationType, String domain, String login, String password) {
url = absoluteUrl(url);

if (isIE() && !isLocalFile(url)) { if (isIE() && !isLocalFile(url)) {
url = makeUniqueUrlToAvoidIECaching(url, System.nanoTime()); url = makeUniqueUrlToAvoidIECaching(url, System.nanoTime());
} }

boolean hasAuthentication = !domain.isEmpty() || !login.isEmpty() || !password.isEmpty();
if (hasAuthentication) {
if (Configuration.fileDownload == PROXY) {
basicAuthRequestFilter().setAuthentication(authenticationType, new Credentials(login, password));
}
else if (authenticationType == AuthenticationType.BASIC) {
url = appendBasicAuthToURL(url, domain, login, password);
}
else {
throw new UnsupportedOperationException("Cannot use " + authenticationType + " authentication without proxy server");
}
}
else { else {
if (!domain.isEmpty()) domain += "%5C"; if (Configuration.fileDownload == PROXY) {
if (!login.isEmpty()) login += ":"; basicAuthRequestFilter().removeAuthentication();
if (!password.isEmpty()) password += "@"; }
int idx1 = url.indexOf("://") + 3;
url = (idx1 < 3 ? "" : (url.substring(0, idx1 - 3) + "://"))
+ domain
+ login
+ password
+ (idx1 < 3 ? url : url.substring(idx1));
} }


SelenideLog log = SelenideLogger.beginStep("open", url); SelenideLog log = SelenideLogger.beginStep("open", url);
try { try {
WebDriver webdriver = getAndCheckWebDriver(); WebDriver webdriver = getAndCheckWebDriver();
webdriver.navigate().to(url); webdriver.navigate().to(url);
collectJavascriptErrors((JavascriptExecutor) webdriver); if (webdriver instanceof JavascriptExecutor) {
collectJavascriptErrors((JavascriptExecutor) webdriver);
}
SelenideLogger.commitStep(log, PASS); SelenideLogger.commitStep(log, PASS);
} catch (WebDriverException e) { } catch (WebDriverException e) {
SelenideLogger.commitStep(log, e); SelenideLogger.commitStep(log, e);
Expand All @@ -85,6 +105,20 @@ protected void navigateToAbsoluteUrl(String url, String domain, String login, St
} }
} }


String appendBasicAuthToURL(String url, String domain, String login, String password) {
if (!domain.isEmpty()) domain += "%5C";
if (!login.isEmpty()) login += ":";
if (!password.isEmpty()) password += "@";
int index = url.indexOf("://") + 3;
if (index < 3) return domain + login + password + url;

return url.substring(0, index - 3) + "://"
+ domain
+ login
+ password
+ url.substring(index);
}

protected void collectJavascriptErrors(JavascriptExecutor webdriver) { protected void collectJavascriptErrors(JavascriptExecutor webdriver) {
if (!captureJavascriptErrors) return; if (!captureJavascriptErrors) return;


Expand Down Expand Up @@ -125,7 +159,7 @@ boolean isAbsoluteUrl(String relativeOrAbsoluteUrl) {
isLocalFile(relativeOrAbsoluteUrl); isLocalFile(relativeOrAbsoluteUrl);
} }


protected boolean isLocalFile(String url) { private boolean isLocalFile(String url) {
return url.toLowerCase().startsWith("file:"); return url.toLowerCase().startsWith("file:");
} }


Expand Down
@@ -0,0 +1,35 @@
package com.codeborne.selenide.proxy;

import com.codeborne.selenide.AuthenticationType;
import com.codeborne.selenide.Credentials;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponse;
import net.lightbody.bmp.filters.RequestFilter;
import net.lightbody.bmp.util.HttpMessageContents;
import net.lightbody.bmp.util.HttpMessageInfo;

public class AuthenticationFilter implements RequestFilter {
private AuthenticationType authenticationType;
private Credentials credentials;

@Override
public HttpResponse filterRequest(HttpRequest request, HttpMessageContents contents, HttpMessageInfo messageInfo) {
if (authenticationType != null) {
String authorization = String.format("%s %s", authenticationType.getValue(), credentials.encode());
HttpHeaders headers = request.headers();
headers.add("Authorization", authorization);
headers.add("Proxy-Authorization", authorization);
}
return null;
}

public void setAuthentication(AuthenticationType authenticationType, Credentials credentials) {
this.authenticationType = authenticationType;
this.credentials = credentials;
}

public void removeAuthentication() {
setAuthentication(null, null);
}
}
Expand Up @@ -69,6 +69,7 @@ public void start() {
proxy.setChainedProxy(getProxyAddress(outsideProxy)); proxy.setChainedProxy(getProxyAddress(outsideProxy));
} }


addRequestFilter("authentication", new AuthenticationFilter());
addRequestFilter("requestSizeWatchdog", new RequestSizeWatchdog()); addRequestFilter("requestSizeWatchdog", new RequestSizeWatchdog());
addResponseFilter("responseSizeWatchdog", new ResponseSizeWatchdog()); addResponseFilter("responseSizeWatchdog", new ResponseSizeWatchdog());
addResponseFilter("download", new FileDownloadFilter()); addResponseFilter("download", new FileDownloadFilter());
Expand All @@ -77,22 +78,24 @@ public void start() {
port = proxy.getPort(); port = proxy.getPort();
} }



/** /**
* Add a custom request filter which allows to track/modify all requests from browser to server * Add a custom request filter which allows to track/modify all requests from browser to server
* *
* @param name unique name of filter * @param name unique name of filter
* @param requestFilter the filter * @param requestFilter the filter
*/ */
public void addRequestFilter(String name, RequestFilter requestFilter) { public void addRequestFilter(String name, RequestFilter requestFilter) {
if (requestFilters.containsKey(name)) { if (isRequestFilterAdded(name)) {
throw new IllegalArgumentException("Duplicate request filter: " + name); throw new IllegalArgumentException("Duplicate request filter: " + name);
} }

proxy.addRequestFilter(requestFilter); proxy.addRequestFilter(requestFilter);
requestFilters.put(name, requestFilter); requestFilters.put(name, requestFilter);
} }


private boolean isRequestFilterAdded(String name) {
return requestFilters.containsKey(name);
}

/** /**
* Add a custom response filter which allows to track/modify all server responses to browser * Add a custom response filter which allows to track/modify all server responses to browser
* *
Expand Down
6 changes: 4 additions & 2 deletions src/test/java/com/codeborne/selenide/WebDriverRunnerTest.java
@@ -1,7 +1,5 @@
package com.codeborne.selenide; package com.codeborne.selenide;


import java.net.URL;

import com.codeborne.selenide.extension.MockWebDriverExtension; import com.codeborne.selenide.extension.MockWebDriverExtension;
import com.codeborne.selenide.impl.WebDriverThreadLocalContainer; import com.codeborne.selenide.impl.WebDriverThreadLocalContainer;
import org.assertj.core.api.WithAssertions; import org.assertj.core.api.WithAssertions;
Expand All @@ -16,6 +14,9 @@
import org.openqa.selenium.remote.RemoteWebDriver; import org.openqa.selenium.remote.RemoteWebDriver;
import org.openqa.selenium.support.events.WebDriverEventListener; import org.openqa.selenium.support.events.WebDriverEventListener;


import java.net.URL;

import static com.codeborne.selenide.Configuration.FileDownloadMode.HTTPGET;
import static com.codeborne.selenide.Selenide.open; import static com.codeborne.selenide.Selenide.open;
import static com.codeborne.selenide.WebDriverRunner.FIREFOX; import static com.codeborne.selenide.WebDriverRunner.FIREFOX;
import static com.codeborne.selenide.WebDriverRunner.HTMLUNIT; import static com.codeborne.selenide.WebDriverRunner.HTMLUNIT;
Expand All @@ -42,6 +43,7 @@ void resetWebDriverContainer() {


WebDriverRunner.webdriverContainer = spy(new WebDriverThreadLocalContainer()); WebDriverRunner.webdriverContainer = spy(new WebDriverThreadLocalContainer());
doReturn(null).when((JavascriptExecutor) driver).executeScript(anyString(), any()); doReturn(null).when((JavascriptExecutor) driver).executeScript(anyString(), any());
Configuration.fileDownload = HTTPGET;
} }


@AfterEach @AfterEach
Expand Down

0 comments on commit 2a928a0

Please sign in to comment.