From b5459c048b0cf267ace41529cb9f1fc3cb2ec9e8 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Thu, 3 Dec 2020 21:48:36 -0800 Subject: [PATCH] feat(proxy): support per context proxy configuration (#87) --- .../com/microsoft/playwright/TestBase.java | 29 ++- .../playwright/TestBrowserContextProxy.java | 216 ++++++++++++++++++ .../microsoft/playwright/TestPageFill.java | 2 +- .../microsoft/playwright/TestPageRoute.java | 11 +- 4 files changed, 243 insertions(+), 15 deletions(-) create mode 100644 playwright/src/test/java/com/microsoft/playwright/TestBrowserContextProxy.java diff --git a/playwright/src/test/java/com/microsoft/playwright/TestBase.java b/playwright/src/test/java/com/microsoft/playwright/TestBase.java index 74cfb8b8..c4500b9b 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestBase.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestBase.java @@ -16,10 +16,7 @@ package com.microsoft.playwright; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.*; import java.io.IOException; @@ -51,10 +48,17 @@ public class TestBase { return "firefox".equals(browserType.name()); } - @BeforeAll - static void launchBrowser() { - playwright = Playwright.create(); + static BrowserType.LaunchOptions createLaunchOptions() { + String headfulEnv = System.getenv("HEADFUL"); + headful = headfulEnv != null && !"0".equals(headfulEnv) && !"false".equals(headfulEnv); + BrowserType.LaunchOptions options; + options = new BrowserType.LaunchOptions(); + options.headless = !headful; + return options; + } + static void launchBrowser(BrowserType.LaunchOptions launchOptions) { + playwright = Playwright.create(); String browserName = System.getenv("BROWSER"); if (browserName == null) { @@ -73,11 +77,12 @@ public class TestBase { default: throw new IllegalArgumentException("Unknown browser: " + browserName); } - String headfulEnv = System.getenv("HEADFUL"); - headful = headfulEnv != null && !"0".equals(headfulEnv) && !"false".equals(headfulEnv); - BrowserType.LaunchOptions options = new BrowserType.LaunchOptions(); - options.headless = !headful; - browser = browserType.launch(options); + browser = browserType.launch(launchOptions); + } + + @BeforeAll + static void launchBrowser() { + launchBrowser(createLaunchOptions()); } @AfterAll diff --git a/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextProxy.java b/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextProxy.java new file mode 100644 index 00000000..b710d694 --- /dev/null +++ b/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextProxy.java @@ -0,0 +1,216 @@ +/* + * Copyright (c) Microsoft Corporation. + * + * 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 com.microsoft.playwright; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledIf; + +import java.io.OutputStreamWriter; +import java.util.Base64; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +public class TestBrowserContextProxy extends TestBase { + + @BeforeAll + // Hide base class method to provide extra option. + static void launchBrowser() { + BrowserType.LaunchOptions options = createLaunchOptions(); + options.setProxy().withServer("per-context").done(); + launchBrowser(options); + } + + @Test + void shouldThrowForMissingGlobalProxy() { + Browser browser = browserType.launch(createLaunchOptions()); + try { + browser.newContext(new Browser.NewContextOptions().setProxy().withServer("localhost:" + server.PORT).done()); + fail("did not throw"); + } catch (PlaywrightException e) { + assertTrue(e.getMessage().contains("Browser needs to be launched with the global proxy")); + } finally { + browser.close(); + } + } + + void shouldThrowForBadServerValue() { + // Enforced by compiler in Java + } + + @Test + void shouldUseProxy() { + server.setRoute("/target.html", exchange -> { + exchange.sendResponseHeaders(200, 0); + try (OutputStreamWriter writer = new OutputStreamWriter(exchange.getResponseBody())) { + writer.write("Served by the proxy"); + } + }); + BrowserContext context = browser.newContext(new Browser.NewContextOptions().setProxy() + .withServer("localhost:" + server.PORT).done()); + Page page = context.newPage(); + page.navigate("http://non-existent.com/target.html"); + assertEquals("Served by the proxy", page.title()); + context.close(); + } + + @Test + void shouldUseProxyTwice() { + server.setRoute("/target.html", exchange -> { + exchange.sendResponseHeaders(200, 0); + try (OutputStreamWriter writer = new OutputStreamWriter(exchange.getResponseBody())) { + writer.write("Served by the proxy"); + } + }); + BrowserContext context = browser.newContext(new Browser.NewContextOptions().setProxy() + .withServer("localhost:" + server.PORT).done()); + Page page = context.newPage(); + page.navigate("http://non-existent.com/target.html"); + page.navigate("http://non-existent-2.com/target.html"); + assertEquals("Served by the proxy", page.title()); + context.close(); + } + + @Test + void shouldUseProxyForSecondPage() { + server.setRoute("/target.html", exchange -> { + exchange.sendResponseHeaders(200, 0); + try (OutputStreamWriter writer = new OutputStreamWriter(exchange.getResponseBody())) { + writer.write("Served by the proxy"); + } + }); + BrowserContext context = browser.newContext(new Browser.NewContextOptions().setProxy() + .withServer("localhost:" + server.PORT).done()); + + Page page = context.newPage(); + page.navigate("http://non-existent.com/target.html"); + assertEquals("Served by the proxy", page.title()); + + Page page2 = context.newPage(); + page2.navigate("http://non-existent.com/target.html"); + assertEquals("Served by the proxy", page2.title()); + + context.close(); + } + + @Test + void shouldWorkWithIPPORTNotion() { + server.setRoute("/target.html", exchange -> { + exchange.sendResponseHeaders(200, 0); + try (OutputStreamWriter writer = new OutputStreamWriter(exchange.getResponseBody())) { + writer.write("Served by the proxy"); + } + }); + BrowserContext context = browser.newContext(new Browser.NewContextOptions().setProxy() + .withServer("127.0.0.1:" + server.PORT).done()); + + Page page = context.newPage(); + page.navigate("http://non-existent.com/target.html"); + assertEquals("Served by the proxy", page.title()); + context.close(); + } + + @Test + void shouldAuthenticate() { + server.setRoute("/target.html", exchange -> { + List auth = exchange.getRequestHeaders().get("proxy-authorization"); + if (auth == null) { + exchange.getResponseHeaders().add("Proxy-Authenticate", "Basic realm='Access to internal site'"); + exchange.sendResponseHeaders(407, 0); + exchange.getResponseBody().close(); + return; + } + exchange.sendResponseHeaders(200, 0); + try (OutputStreamWriter writer = new OutputStreamWriter(exchange.getResponseBody())) { + writer.write("" + auth.get(0) + ""); + } + }); + BrowserContext context = browser.newContext(new Browser.NewContextOptions().setProxy() + .withServer("localhost:" + server.PORT) + .withUsername("user") + .withPassword("secret").done()); + Page page = context.newPage(); + page.navigate("http://non-existent.com/target.html"); + assertEquals("Basic " + Base64.getEncoder().encodeToString("user:secret".getBytes()), page.title()); + context.close(); + } + + static boolean isChromiumHeadful() { + return isChromium() && isHeadful(); + } + + @Test + @DisabledIf(value="isChromiumHeadful", disabledReason="fixme") + void shouldExcludePatterns() { + server.setRoute("/target.html", exchange -> { + exchange.sendResponseHeaders(200, 0); + try (OutputStreamWriter writer = new OutputStreamWriter(exchange.getResponseBody())) { + writer.write("Served by the proxy"); + } + }); + BrowserContext context = browser.newContext(new Browser.NewContextOptions().setProxy() + .withServer("127.0.0.1:" + server.PORT) + // FYI: using long and weird domain names to avoid ATT DNS hijacking + // that resolves everything to some weird search results page. + // + // @see https://gist.github.com/CollinChaffin/24f6c9652efb3d6d5ef2f5502720ef00 + .withBypass("1.non.existent.domain.for.the.test, 2.non.existent.domain.for.the.test, .another.test").done()); + + Page page = context.newPage(); + page.navigate("http://0.non.existent.domain.for.the.test/target.html"); + assertEquals("Served by the proxy", page.title()); + + try { + page.navigate("http://1.non.existent.domain.for.the.test/target.html"); + fail("did not throw"); + } catch (PlaywrightException exception) { + } + + try { + page.navigate("http://2.non.existent.domain.for.the.test/target.html"); + fail("did not throw"); + } catch (PlaywrightException e) { + } + + try { + page.navigate("http://foo.is.the.another.test/target.html"); + fail("did not throw"); + } catch (PlaywrightException e) { + } + + page.navigate("http://3.non.existent.domain.for.the.test/target.html"); + assertEquals("Served by the proxy", page.title()); + + context.close(); + } + + void shouldUseSocksProxy() { + // TODO: implement socks server + } + + void shouldUseSocksProxyInSecondPage() { + // TODO: implement socks server + } + + @Test + void doesLaunchWithoutAPort() { + BrowserContext context = browser.newContext(new Browser.NewContextOptions().setProxy() + .withServer("http://localhost").done()); + context.close(); + } +} diff --git a/playwright/src/test/java/com/microsoft/playwright/TestPageFill.java b/playwright/src/test/java/com/microsoft/playwright/TestPageFill.java index a712ae3e..dbff0aa5 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestPageFill.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestPageFill.java @@ -41,7 +41,7 @@ public class TestPageFill extends TestBase { @Test void shouldThrowOnUnsupportedInputs() { page.navigate(server.PREFIX + "/input/textarea.html"); - for (String type : new String[]{"color", "file"}) { + for (String type : new String[]{"button", "checkbox", "file", "image", "radio", "range", "reset", "submit"}) { page.evalOnSelector("input", "(input, type) => input.setAttribute('type', type)", type); try { page.fill("input", ""); diff --git a/playwright/src/test/java/com/microsoft/playwright/TestPageRoute.java b/playwright/src/test/java/com/microsoft/playwright/TestPageRoute.java index 0a224511..a55ba70c 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestPageRoute.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestPageRoute.java @@ -460,8 +460,15 @@ public class TestPageRoute extends TestBase { } @Test - void shouldCreateARedirect() { - page.navigate(server.PREFIX + "/empty.html"); + @DisabledIf(value="com.microsoft.playwright.TestBase#isWebKit", disabledReason="fixme") + void shouldFulfillWithRedirectStatus() { + page.navigate(server.PREFIX + "/title.html"); + server.setRoute("/final", exchange -> { + exchange.sendResponseHeaders(200, 0); + try (OutputStreamWriter writer = new OutputStreamWriter(exchange.getResponseBody())) { + writer.write("foo"); + } + }); page.route("**/*", route -> { if (!route.request().url().equals(server.PREFIX + "/redirect_this")) { route.continue_();