diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml index 3e5c3555f6..fa9e77dbf9 100644 --- a/config/checkstyle/checkstyle.xml +++ b/config/checkstyle/checkstyle.xml @@ -57,7 +57,7 @@ - + diff --git a/src/main/java/com/codeborne/selenide/AuthenticationType.java b/src/main/java/com/codeborne/selenide/AuthenticationType.java new file mode 100644 index 0000000000..e8f38dd464 --- /dev/null +++ b/src/main/java/com/codeborne/selenide/AuthenticationType.java @@ -0,0 +1,25 @@ +package com.codeborne.selenide; + +/** + * Authentication schemes. + * + * @see Web HTTP reference + */ +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; + } +} diff --git a/src/main/java/com/codeborne/selenide/Credentials.java b/src/main/java/com/codeborne/selenide/Credentials.java new file mode 100644 index 0000000000..f72b29a46d --- /dev/null +++ b/src/main/java/com/codeborne/selenide/Credentials.java @@ -0,0 +1,29 @@ +package com.codeborne.selenide; + +import java.util.Base64; + +import static java.nio.charset.StandardCharsets.UTF_8; + +public final class Credentials { + private final String login; + private 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); + } +} diff --git a/src/main/java/com/codeborne/selenide/Selenide.java b/src/main/java/com/codeborne/selenide/Selenide.java index 06815a8a54..0a27416eee 100644 --- a/src/main/java/com/codeborne/selenide/Selenide.java +++ b/src/main/java/com/codeborne/selenide/Selenide.java @@ -83,24 +83,56 @@ public static void open(URL absoluteUrl) { /** * The main starting point in your tests. + *

* Open a browser window with given URL and credentials for basic authentication - * + *

* If browser window was already opened before, it will be reused. - * + *

* Don't bother about closing the browser - it will be closed automatically when all your tests are done. - * - * @param relativeOrAbsoluteUrl - * @param domain - * @param login - * @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 + *

+ * 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) { navigator.open(relativeOrAbsoluteUrl, domain, login, password); mockModalDialogs(); } + /** + * The main starting point in your tests. + *

+ * Open browser and pass authentication using build-in proxy. + *

+ * A common authenticationType is "Basic". See Web HTTP reference for other types. + *

+ * This method can only work if - {@code Configuration.fileDownload == Configuration.FileDownloadMode.PROXY;} + * + * @see Web HTTP reference + * @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. + *

+ * Open browser and pass authentication using build-in proxy. + *

+ * A common authenticationType is "Basic". See Web HTTP reference for other types. + *

+ * This method can only work if - {@code Configuration.fileDownload == Configuration.FileDownloadMode.PROXY;} + * + * @see Web HTTP reference + * @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) */ diff --git a/src/main/java/com/codeborne/selenide/impl/Navigator.java b/src/main/java/com/codeborne/selenide/impl/Navigator.java index 2ac471af09..8829175931 100644 --- a/src/main/java/com/codeborne/selenide/impl/Navigator.java +++ b/src/main/java/com/codeborne/selenide/impl/Navigator.java @@ -1,7 +1,11 @@ package com.codeborne.selenide.impl; +import com.codeborne.selenide.AuthenticationType; +import com.codeborne.selenide.Credentials; import com.codeborne.selenide.logevents.SelenideLog; import com.codeborne.selenide.logevents.SelenideLogger; +import com.codeborne.selenide.proxy.SelenideProxyServer; +import io.netty.handler.codec.http.HttpHeaders; import org.openqa.selenium.JavascriptExecutor; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebDriverException; @@ -12,6 +16,7 @@ import static com.codeborne.selenide.Configuration.baseUrl; import static com.codeborne.selenide.Configuration.captureJavascriptErrors; 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.isIE; import static com.codeborne.selenide.logevents.LogEvent.EventStatus.PASS; @@ -39,6 +44,20 @@ public void open(URL url, String domain, String login, String password) { navigateToAbsoluteUrl(url.toExternalForm()); } + public void open(String relativeOrAbsoluteUrl, AuthenticationType authenticationType, Credentials credentials) { + getAndCheckWebDriver(); + String authorization = String.format("%s %s", authenticationType.getValue(), credentials.encode()); + SelenideProxyServer selenideProxy = getSelenideProxy(); + selenideProxy.addRequestFilter("headers.request", (request, contents, messageInfo) -> { + HttpHeaders headers = request.headers(); + headers.add("Authorization", authorization); + headers.add("Proxy-Authorization", authorization); + return null; + }); + open(relativeOrAbsoluteUrl); + selenideProxy.removeRequestFilter("headers.request"); + } + protected String absoluteUrl(String relativeUrl) { return baseUrl + relativeUrl; } diff --git a/src/main/java/com/codeborne/selenide/proxy/SelenideProxyServer.java b/src/main/java/com/codeborne/selenide/proxy/SelenideProxyServer.java index 683ac4d128..6b30bb47ad 100644 --- a/src/main/java/com/codeborne/selenide/proxy/SelenideProxyServer.java +++ b/src/main/java/com/codeborne/selenide/proxy/SelenideProxyServer.java @@ -77,7 +77,6 @@ public void start() { port = proxy.getPort(); } - /** * Add a custom request filter which allows to track/modify all requests from browser to server * @@ -85,14 +84,30 @@ public void start() { * @param requestFilter the filter */ public void addRequestFilter(String name, RequestFilter requestFilter) { - if (requestFilters.containsKey(name)) { + if (isRequestFilterAdded(name)) { throw new IllegalArgumentException("Duplicate request filter: " + name); } - proxy.addRequestFilter(requestFilter); requestFilters.put(name, requestFilter); } + private boolean isRequestFilterAdded(String name) { + return requestFilters.containsKey(name); + } + + /** + * Remove a custom request filter by name + * + * @param name unique name of filter + */ + public void removeRequestFilter(String name) { + if (isRequestFilterAdded(name)) { + requestFilters.remove(name); + } else { + throw new IllegalArgumentException("Missing request filter: " + name); + } + } + /** * Add a custom response filter which allows to track/modify all server responses to browser * diff --git a/src/test/java/integration/BasicAuthTest.java b/src/test/java/integration/BasicAuthTest.java index c08e413e9c..0353c6c125 100644 --- a/src/test/java/integration/BasicAuthTest.java +++ b/src/test/java/integration/BasicAuthTest.java @@ -1,5 +1,9 @@ package integration; +import com.codeborne.selenide.AuthenticationType; +import com.codeborne.selenide.Configuration; +import com.codeborne.selenide.Configuration.FileDownloadMode; +import com.codeborne.selenide.Credentials; import org.junit.jupiter.api.Test; import static com.codeborne.selenide.Condition.text; @@ -12,4 +16,19 @@ void canPassBasicAuth() { open("/basic-auth/hello", "", "scott", "tiger"); $("body").shouldHave(text("Hello, scott:tiger!")); } + + @Test + void canAuthUsingProxyWithLoginAndPassword() { + Configuration.fileDownload = FileDownloadMode.PROXY; + open("/basic-auth/hello", AuthenticationType.BASIC, "scott", "tiger"); + $("body").shouldHave(text("Hello, scott:tiger!")); + } + + @Test + void canAuthUsingProxyWithCredentials() { + Configuration.fileDownload = FileDownloadMode.PROXY; + Credentials credentials = new Credentials("scott", "tiger"); + open("/basic-auth/hello", AuthenticationType.BASIC, credentials); + $("body").shouldHave(text("Hello, scott:tiger!")); + } }