From 7e285ffe445726ca3e972c823baa99964c75c688 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Thu, 23 Jun 2022 16:48:32 -0700 Subject: [PATCH] feat: route.fallback with overrides (#958) --- .../playwright/impl/BrowserContextImpl.java | 2 +- .../microsoft/playwright/impl/FrameImpl.java | 8 +- .../microsoft/playwright/impl/PageImpl.java | 4 - .../playwright/impl/RequestImpl.java | 54 +++- .../microsoft/playwright/impl/RouteImpl.java | 74 +++-- .../com/microsoft/playwright/impl/Router.java | 11 +- .../com/microsoft/playwright/impl/Utils.java | 11 + .../playwright/TestPageAutowaitingBasic.java | 66 +++++ .../microsoft/playwright/TestPageBasic.java | 18 ++ .../playwright/TestPageRequestContinue.java | 46 ++- .../playwright/TestPageRequestFallback.java | 276 ++++++++++++++++++ .../microsoft/playwright/TestPageRoute.java | 92 +----- 12 files changed, 528 insertions(+), 134 deletions(-) create mode 100644 playwright/src/test/java/com/microsoft/playwright/TestPageAutowaitingBasic.java create mode 100644 playwright/src/test/java/com/microsoft/playwright/TestPageRequestFallback.java diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java index 735f9e4c..b4f48233 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java @@ -485,7 +485,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext { maybeDisableNetworkInterception(); } if (handled != Router.HandleResult.Handled){ - route.resume(null); + route.resume(); } } diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/FrameImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/FrameImpl.java index 047be9ca..0f11f655 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/FrameImpl.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/FrameImpl.java @@ -23,7 +23,6 @@ import com.microsoft.playwright.*; import com.microsoft.playwright.options.*; import java.io.IOException; -import java.io.OutputStream; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; @@ -1048,6 +1047,13 @@ public class FrameImpl extends ChannelOwner implements Frame { if (add != null) { WaitUntilState state = loadStateFromProtocol(add.getAsString()); loadStates.add(state); + if (parentFrame == null && page != null) { + if (state == LOAD) { + page.listeners.notify(PageImpl.EventType.LOAD, page); + } else if (state == DOMCONTENTLOADED) { + page.listeners.notify(PageImpl.EventType.DOMCONTENTLOADED, page); + } + } internalListeners.notify(InternalEventType.LOADSTATE, state); } JsonElement remove = params.get("remove"); diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/PageImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/PageImpl.java index e6f73faf..927aa8cc 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/PageImpl.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/PageImpl.java @@ -175,10 +175,6 @@ public class PageImpl extends ChannelOwner implements Page { } } } - } else if ("load".equals(event)) { - listeners.notify(EventType.LOAD, this); - } else if ("domcontentloaded".equals(event)) { - listeners.notify(EventType.DOMCONTENTLOADED, this); } else if ("frameAttached".equals(event)) { String guid = params.getAsJsonObject("frame").get("guid").getAsString(); FrameImpl frame = connection.getExistingObject(guid); diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/RequestImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/RequestImpl.java index 41633861..902d39e0 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/RequestImpl.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/RequestImpl.java @@ -42,6 +42,14 @@ public class RequestImpl extends ChannelOwner implements Request { String failure; Timing timing; boolean didFailOrFinish; + private FallbackOverrides fallbackOverrides; + + static class FallbackOverrides { + String url; + String method; + byte[] postData; + Map headers; + } RequestImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) { super(parent, type, guid, initializer); @@ -75,6 +83,9 @@ public class RequestImpl extends ChannelOwner implements Request { @Override public Map headers() { + if (fallbackOverrides != null && fallbackOverrides.headers != null) { + return new RawHeaders(Utils.toHeadersList(fallbackOverrides.headers)).headers(); + } return headers.headers(); } @@ -95,19 +106,26 @@ public class RequestImpl extends ChannelOwner implements Request { @Override public String method() { + if (fallbackOverrides != null && fallbackOverrides.method != null) { + return fallbackOverrides.method; + } return initializer.get("method").getAsString(); } @Override public String postData() { - if (postData == null) { + byte[] buffer = postDataBuffer(); + if (buffer == null) { return null; } - return new String(postData, StandardCharsets.UTF_8); + return new String(buffer, StandardCharsets.UTF_8); } @Override public byte[] postDataBuffer() { + if (fallbackOverrides != null && fallbackOverrides.postData != null) { + return fallbackOverrides.postData; + } return postData; } @@ -156,6 +174,13 @@ public class RequestImpl extends ChannelOwner implements Request { @Override public String url() { + if (fallbackOverrides != null && fallbackOverrides.url != null) { + return fallbackOverrides.url; + } + return originalUrl(); + } + + String originalUrl() { return initializer.get("url").getAsString(); } @@ -164,6 +189,9 @@ public class RequestImpl extends ChannelOwner implements Request { } private RawHeaders getRawHeaders() { + if (fallbackOverrides != null && fallbackOverrides.headers != null) { + return new RawHeaders(Utils.toHeadersList(fallbackOverrides.headers)); + } if (rawHeaders != null) { return rawHeaders; } @@ -176,4 +204,26 @@ public class RequestImpl extends ChannelOwner implements Request { rawHeaders = new RawHeaders(asList(gson().fromJson(rawHeadersJson, HttpHeader[].class))); return rawHeaders; } + + void applyFallbackOverrides(FallbackOverrides overrides) { + if (fallbackOverrides == null) { + fallbackOverrides = new FallbackOverrides(); + } + if (overrides.url != null) { + fallbackOverrides.url = overrides.url; + } + if (overrides.method != null) { + fallbackOverrides.method = overrides.method; + } + if (overrides.headers != null) { + fallbackOverrides.headers = overrides.headers; + } + if (overrides.postData != null) { + fallbackOverrides.postData = overrides.postData; + } + } + + FallbackOverrides fallbackOverridesForResume() { + return fallbackOverrides; + } } diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/RouteImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/RouteImpl.java index 083f80c4..f859335f 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/RouteImpl.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/RouteImpl.java @@ -17,6 +17,7 @@ package com.microsoft.playwright.impl; import com.google.gson.JsonObject; +import com.microsoft.playwright.Frame; import com.microsoft.playwright.PlaywrightException; import com.microsoft.playwright.Route; @@ -27,9 +28,10 @@ import java.util.Base64; import java.util.LinkedHashMap; import java.util.Map; +import static com.microsoft.playwright.impl.Utils.convertType; + public class RouteImpl extends ChannelOwner implements Route { private boolean handled; - private boolean lastHandlerGaveUp; public RouteImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) { super(parent, type, guid, initializer); @@ -45,55 +47,69 @@ public class RouteImpl extends ChannelOwner implements Route { }); } - boolean takeLastHandlerGaveUp() { - boolean result = lastHandlerGaveUp; - lastHandlerGaveUp = false; - return result; + boolean isHandled() { + return handled; } @Override public void resume(ResumeOptions options) { startHandling(); - withLogging("Route.resume", () -> resumeImpl(options)); + applyOverrides(convertType(options, FallbackOptions.class)); + withLogging("Route.resume", () -> resumeImpl(request().fallbackOverridesForResume())); } @Override public void fallback(FallbackOptions options) { - if (lastHandlerGaveUp) { + if (handled) { throw new PlaywrightException("Route is already handled!"); } - lastHandlerGaveUp = true; + applyOverrides(options); } - private void resumeImpl(ResumeOptions options) { + private void applyOverrides(FallbackOptions options) { if (options == null) { - options = new ResumeOptions(); - } - JsonObject params = new JsonObject(); - if (options.url != null) { - params.addProperty("url", options.url); - } - if (options.method != null) { - params.addProperty("method", options.method); - } - if (options.headers != null) { - params.add("headers", Serialization.toProtocol(options.headers)); + return; } + RequestImpl.FallbackOverrides overrides = new RequestImpl.FallbackOverrides(); + overrides.url = options.url; + overrides.method = options.method; + overrides.headers = options.headers; if (options.postData != null) { - byte[] bytes = null; - if (options.postData instanceof byte[]) { - bytes = (byte[]) options.postData; - } else if (options.postData instanceof String) { - bytes = ((String) options.postData).getBytes(StandardCharsets.UTF_8); - } else { - throw new PlaywrightException("postData must be either String or byte[], found: " + options.postData.getClass().getName()); + overrides.postData = getPostDataBytes(options.postData); + } + request().applyFallbackOverrides(overrides); + } + + private void resumeImpl(RequestImpl.FallbackOverrides options) { + JsonObject params = new JsonObject(); + if (options != null) { + if (options.url != null) { + params.addProperty("url", options.url); + } + if (options.method != null) { + params.addProperty("method", options.method); + } + if (options.headers != null) { + params.add("headers", Serialization.toProtocol(options.headers)); + } + if (options.postData != null) { + String base64 = Base64.getEncoder().encodeToString(options.postData); + params.addProperty("postData", base64); } - String base64 = Base64.getEncoder().encodeToString(bytes); - params.addProperty("postData", base64); } sendMessageAsync("continue", params); } + private static byte[] getPostDataBytes(Object postData) { + if (postData instanceof byte[]) { + return (byte[]) postData; + } + if (postData instanceof String) { + return ((String) postData).getBytes(StandardCharsets.UTF_8); + } + throw new PlaywrightException("postData must be either String or byte[], found: " + postData.getClass().getName()); + } + @Override public void fulfill(FulfillOptions options) { startHandling(); diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/Router.java b/playwright/src/main/java/com/microsoft/playwright/impl/Router.java index 93a123bc..897eadd0 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/Router.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/Router.java @@ -38,11 +38,11 @@ class Router { this.times = times; } - boolean handle(Route route) { + boolean handle(RouteImpl route) { if (times != null && times <= 0) { return false; } - if (!matcher.test(route.request().url())) { + if (!matcher.test(route.request().originalUrl())) { return false; } if (times != null) { @@ -80,11 +80,10 @@ class Router { if (info.isDone()) { it.remove(); } - if (route.takeLastHandlerGaveUp()) { - result = HandleResult.MatchedHandlerButNotHandled; - continue; + if (route.isHandled()) { + return HandleResult.Handled; } - return HandleResult.Handled; + result = HandleResult.MatchedHandlerButNotHandled; } } return result; diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/Utils.java b/playwright/src/main/java/com/microsoft/playwright/impl/Utils.java index 49ef161b..5d464f37 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/Utils.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/Utils.java @@ -277,6 +277,17 @@ class Utils { return map; } + static List toHeadersList(Map headers) { + List list = new ArrayList<>(); + for (Map.Entry entry: headers.entrySet()) { + HttpHeader header = new HttpHeader(); + header.name = entry.getKey(); + header.value = entry.getValue(); + list.add(header); + } + return list; + } + static String toJsRegexFlags(Pattern pattern) { String regexFlags = ""; if ((pattern.flags() & Pattern.CASE_INSENSITIVE) != 0) { diff --git a/playwright/src/test/java/com/microsoft/playwright/TestPageAutowaitingBasic.java b/playwright/src/test/java/com/microsoft/playwright/TestPageAutowaitingBasic.java new file mode 100644 index 00000000..748813f6 --- /dev/null +++ b/playwright/src/test/java/com/microsoft/playwright/TestPageAutowaitingBasic.java @@ -0,0 +1,66 @@ +/* + * 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 com.microsoft.playwright.options.LoadState; +import org.junit.jupiter.api.Test; + +import java.io.OutputStreamWriter; +import java.util.ArrayList; +import java.util.List; + +import static java.util.Arrays.asList; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class TestPageAutowaitingBasic extends TestBase { + @Test + void shouldWorkWithNoWaitAfterTrue() { + server.setRoute("/empty.html", exchange -> {}); + page.setContent("empty.html"); + page.click("a", new Page.ClickOptions().setNoWaitAfter(true)); + } + + @Test + void shouldWorkWithDblclickNoWaitAfterTrue() { + server.setRoute("/empty.html", exchange -> {}); + page.setContent("empty.html"); + page.dblclick("a", new Page.DblclickOptions().setNoWaitAfter(true)); + } + + @Test + void shouldWorkWithWaitForLoadStateLoad() { + List messages = new ArrayList<>(); + server.setRoute("/empty.html", exchange -> { + messages.add("route"); + exchange.getResponseHeaders().add("content-type", "text/html"); + exchange.sendResponseHeaders(200, 0); + try (OutputStreamWriter writer = new OutputStreamWriter(exchange.getResponseBody())) { + writer.write(""); + } catch (RuntimeException e) { + e.printStackTrace(); + throw e; + } + }); + page.setContent("empty.html"); + + page.onLoad(p -> messages.add("clickload")); + page.click("a"); + page.waitForLoadState(LoadState.LOAD); + messages.add("load"); + assertEquals(asList("route", "clickload", "load"), messages); + } +} diff --git a/playwright/src/test/java/com/microsoft/playwright/TestPageBasic.java b/playwright/src/test/java/com/microsoft/playwright/TestPageBasic.java index 884740d8..e3028f6a 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestPageBasic.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestPageBasic.java @@ -164,6 +164,24 @@ public class TestPageBasic extends TestBase { page.waitForLoadState(DOMCONTENTLOADED); } + @Test + void shouldPassSelfAsArgumentToDomcontentloadedEvent() { + Page[] eventPage = {null}; + page.onDOMContentLoaded(p -> eventPage[0] = p); + page.navigate("about:blank"); + page.waitForLoadState(DOMCONTENTLOADED); + assertEquals(page, eventPage[0]); + } + + @Test + void shouldPassSelfAsArgumentToLoadEvent() { + Page[] eventPage = {null}; + page.onLoad(p -> eventPage[0] = p); + page.navigate("about:blank"); + page.waitForLoadState(LOAD); + assertEquals(page, eventPage[0]); + } + // TODO: downloads void shouldFailWithErrorUponDisconnect() { } diff --git a/playwright/src/test/java/com/microsoft/playwright/TestPageRequestContinue.java b/playwright/src/test/java/com/microsoft/playwright/TestPageRequestContinue.java index 5543f6d7..b980187a 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestPageRequestContinue.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestPageRequestContinue.java @@ -18,10 +18,52 @@ package com.microsoft.playwright; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; +import java.io.OutputStreamWriter; +import java.util.Map; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; + +import static java.util.Arrays.asList; +import static org.junit.jupiter.api.Assertions.*; public class TestPageRequestContinue extends TestBase { + @Test + void shouldDeleteHeaderWithUndefinedValue() throws ExecutionException, InterruptedException { + // https://github.com/microsoft/playwright/issues/13106 + page.navigate(server.PREFIX + "/empty.html"); + Future serverRequest = server.futureRequest("/something"); + server.setRoute("/something", exchange -> { + exchange.getResponseHeaders().add("Access-Control-Allow-Origin", "*"); + exchange.sendResponseHeaders(200, 0); + try (OutputStreamWriter writer = new OutputStreamWriter(exchange.getResponseBody())) { + writer.write("done"); + } + }); + + Request[] interceptedRequest = {null}; + page.route(server.PREFIX + "/something", route -> { + interceptedRequest[0] = route.request(); + Map headers = route.request().allHeaders(); + headers.remove("foo"); + route.resume(new Route.ResumeOptions().setHeaders(headers)); + }); + + Object text = page.evaluate("async url => {\n" + + " const data = await fetch(url, {\n" + + " headers: {\n" + + " foo: 'a',\n" + + " bar: 'b',\n" + + " }\n" + + " });\n" + + " return data.text();\n" + + " }", server.PREFIX + "/something"); + + assertEquals("done", text); + assertNull(interceptedRequest[0].headers().get("foo")); + assertNull(serverRequest.get().headers.get("foo")); + assertEquals(asList("b"), serverRequest.get().headers.get("bar")); + } + @Test void shouldNotThrowWhenContinuingAfterPageIsClosed() { boolean[] done = {false}; diff --git a/playwright/src/test/java/com/microsoft/playwright/TestPageRequestFallback.java b/playwright/src/test/java/com/microsoft/playwright/TestPageRequestFallback.java new file mode 100644 index 00000000..dbe20d2d --- /dev/null +++ b/playwright/src/test/java/com/microsoft/playwright/TestPageRequestFallback.java @@ -0,0 +1,276 @@ +/* + * 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.Test; + +import java.io.OutputStreamWriter; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; + +import static java.util.Arrays.asList; +import static org.junit.jupiter.api.Assertions.*; + +public class TestPageRequestFallback extends TestBase { + @Test + void shouldWork() { + page.route("**/*", route -> route.fallback()); + page.navigate(server.EMPTY_PAGE); + } + + @Test + void shouldFallBack() { + List intercepted = new ArrayList<>(); + page.route("**/empty.html", route -> { + intercepted.add(1); + route.fallback(); + }); + page.route("**/empty.html", route -> { + intercepted.add(2); + route.fallback(); + }); + page.route("**/empty.html", route -> { + intercepted.add(3); + route.fallback(); + }); + page.navigate(server.EMPTY_PAGE); + assertEquals(asList(3, 2, 1), intercepted); + } + + @Test + void shouldFallBackAsync() { + List intercepted = new ArrayList<>(); + page.route("**/empty.html", route -> { + intercepted.add(1); + page.waitForTimeout(50); + route.fallback(); + }); + page.route("**/empty.html", route -> { + intercepted.add(2); + page.waitForTimeout(100); + route.fallback(); + }); + page.route("**/empty.html", route -> { + intercepted.add(3); + page.waitForTimeout(150); + route.fallback(); + }); + page.navigate(server.EMPTY_PAGE); + assertEquals(asList(3, 2, 1), intercepted); + } + + @Test + void shouldNotChainFulfill() { + boolean[] failed = {false}; + page.route("**/empty.html", route -> { + failed[0] = true; + }); + page.route("**/empty.html", route -> { + route.fulfill(new Route.FulfillOptions().setStatus(200).setBody("fulfilled")); + }); + page.route("**/empty.html", route -> { + route.fallback(); + }); + Response response = page.navigate(server.EMPTY_PAGE); + assertEquals("fulfilled", response.text()); + assertFalse(failed[0]); + } + + @Test + void shouldNotChainAbort() { + boolean[] failed = {false}; + page.route("**/empty.html", route -> { + failed[0] = true; + }); + page.route("**/empty.html", route -> { + route.abort(); + }); + page.route("**/empty.html", route -> { + route.fallback(); + }); + try { + Response response = page.navigate(server.EMPTY_PAGE); + fail("did not throw"); + assertEquals("fulfilled", response.text()); + } catch (PlaywrightException e) { + } + assertFalse(failed[0]); + } + + @Test + void shouldFallBackAfterException() { + page.route("**/empty.html", route -> { + route.resume(); + }); + page.route("**/empty.html", route -> { + try { + route.fulfill(new Route.FulfillOptions()); + } catch (PlaywrightException e) { + route.fallback(); + } + }); + page.navigate(server.EMPTY_PAGE); + } + + @Test + void shouldChainOnce() { + page.route("**/empty.html", route -> { + route.fulfill(new Route.FulfillOptions().setStatus(200).setBody("fulfilled one")); + }, new Page.RouteOptions().setTimes(1)); + page.route("**/empty.html", route -> { + route.fallback(); + }, new Page.RouteOptions().setTimes(1)); + Response response = page.navigate(server.EMPTY_PAGE); + assertEquals("fulfilled one", response.text()); + } + + @Test + void shouldAmendHTTPHeaders() throws ExecutionException, InterruptedException { + List values = new ArrayList<>(); + page.route("**/sleep.zzz", route -> { + values.add(route.request().headers().get("foo")); + values.add(route.request().headerValue("FOO")); + route.resume(); + }); + page.route("**/*", route -> { + Map headers = route.request().headers(); + headers.put("FOO", "bar"); + route.fallback(new Route.FallbackOptions().setHeaders(headers)); + }); + page.navigate(server.EMPTY_PAGE); + Future request = server.futureRequest("/sleep.zzz"); + page.evaluate("() => fetch('/sleep.zzz')"); + values.addAll(request.get().headers.get("foo")); + assertEquals(asList("bar", "bar", "bar"), values); + } + + @Test + void shouldDeleteHeaderWithUndefinedValue() throws ExecutionException, InterruptedException { + // https://github.com/microsoft/playwright/issues/13106 + page.navigate(server.PREFIX + "/empty.html"); + Future serverRequest = server.futureRequest("/something"); + server.setRoute("/something", exchange -> { + exchange.getResponseHeaders().add("Access-Control-Allow-Origin", "*"); + exchange.sendResponseHeaders(200, 0); + try (OutputStreamWriter writer = new OutputStreamWriter(exchange.getResponseBody())) { + writer.write("done"); + } + }); + + Request[] interceptedRequest = {null}; + page.route(server.PREFIX + "/something", route -> { + interceptedRequest[0] = route.request(); + route.resume(); + }); + page.route(server.PREFIX + "/something", route -> { + Map headers = route.request().allHeaders(); + headers.remove("foo"); + route.fallback(new Route.FallbackOptions().setHeaders(headers)); + }); + + Object text = page.evaluate("async url => {\n" + + " const data = await fetch(url, {\n" + + " headers: {\n" + + " foo: 'a',\n" + + " bar: 'b',\n" + + " }\n" + + " });\n" + + " return data.text();\n" + + " }", server.PREFIX + "/something"); + + assertEquals("done", text); + assertNull(interceptedRequest[0].headers().get("foo")); + assertNull(serverRequest.get().headers.get("foo")); + assertEquals(asList("b"), serverRequest.get().headers.get("bar")); + } + + @Test + void shouldAmendMethod() throws ExecutionException, InterruptedException { + Future sRequest = server.futureRequest("/sleep.zzz"); + page.navigate(server.EMPTY_PAGE); + String[] method = {null}; + page.route("**/*", route -> { + method[0] = route.request().method(); + route.resume(); + }); + page.route("**/*", route -> route.fallback(new Route.FallbackOptions().setMethod("POST"))); + Request request = page.waitForRequest("**/sleep.zzz", () -> page.evaluate("() => fetch('/sleep.zzz')")); + assertEquals("POST", method[0]); + assertEquals("POST", request.method()); + assertEquals("POST", sRequest.get().method); + } + + @Test + void shouldOverrideRequestUrl() throws ExecutionException, InterruptedException { + Future request = server.futureRequest("/global-var.html"); + String[] url = {null}; + page.route("**/foo", route -> { + url[0] = route.request().url(); + route.resume(); + }); + page.route("**/foo", route -> route.fallback(new Route.FallbackOptions().setUrl(server.PREFIX + "/global-var.html"))); + Response response = page.waitForResponse("**/*", () -> page.navigate(server.PREFIX + "/foo")); + assertEquals(server.PREFIX + "/global-var.html", url[0]); + assertEquals(server.PREFIX + "/foo", response.url()); + assertEquals(123, page.evaluate("() => window['globalVar']")); + assertEquals("GET", request.get().method); + } + + @Test + void shouldAmendPostData() throws ExecutionException, InterruptedException { + page.navigate(server.EMPTY_PAGE); + String[] postData = {null}; + page.route("**/*", route -> { + postData[0] = route.request().postData(); + route.resume(); + }); + page.route("**/*", route -> { + route.fallback(new Route.FallbackOptions().setPostData("doggo")); + }); + Future serverRequest = server.futureRequest("/sleep.zzz"); + page.evaluate("() => fetch('/sleep.zzz', { method: 'POST', body: 'birdy' })"); + assertEquals("doggo", postData[0]); + assertEquals("doggo", new String(serverRequest.get().postBody, StandardCharsets.UTF_8)); + } + + @Test + void shouldAmendBinaryPostData() throws ExecutionException, InterruptedException { + page.navigate(server.EMPTY_PAGE); + byte[] arr = new byte[256]; + for (int i = 0; i < arr.length; i++) { + arr[i] = (byte) i; + } + byte[][] postDataBuffer = {null}; + page.route("**/*", route -> { + postDataBuffer[0] = route.request().postDataBuffer(); + route.resume(); + }); + page.route("**/*", route -> { + route.fallback(new Route.FallbackOptions().setPostData(arr)); + }); + Future serverRequest = server.futureRequest("/sleep.zzz"); + page.evaluate("() => fetch('/sleep.zzz', { method: 'POST', body: 'birdy' })"); + byte[] buffer = serverRequest.get().postBody; + assertArrayEquals(arr, buffer); + assertArrayEquals(arr, postDataBuffer[0]); + } + +} diff --git a/playwright/src/test/java/com/microsoft/playwright/TestPageRoute.java b/playwright/src/test/java/com/microsoft/playwright/TestPageRoute.java index 81bc1249..583e5548 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestPageRoute.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestPageRoute.java @@ -476,7 +476,7 @@ public class TestPageRoute extends TestBase { } @Test - void shouldNotThrowInvalidInterceptionIdIfTheRequestWasCancelled() { + void shouldThrowIfResumeIsCalledAfterRouteHandlerFinished() { page.setContent(""); Route[] route = {null}; page.route("**/*", r -> route[0] = r); @@ -486,8 +486,9 @@ public class TestPageRoute extends TestBase { page.evalOnSelector("iframe", "frame => frame.remove()"); try { route[0].resume(); + fail("did not throw"); } catch (PlaywrightException e) { - fail("Should not throw"); + assertTrue(e.getMessage().contains("Route is already handled!"), e.getMessage()); } } @@ -724,91 +725,4 @@ public class TestPageRoute extends TestBase { }); assertEquals(server.PREFIX, response.headerValue("Access-Control-Allow-Origin")); } - - @Test - void shouldChainFallback() { - List intercepted = new ArrayList<>(); - page.route("**/empty.html", route -> { - intercepted.add(1); - route.fallback(); - }); - page.route("**/empty.html", route -> { - intercepted.add(2); - route.fallback(); - }); - page.route("**/empty.html", route -> { - intercepted.add(3); - route.fallback(); - }); - page.navigate(server.EMPTY_PAGE); - assertEquals(asList(3, 2, 1), intercepted); - } - - @Test - void shouldNotChainFulfill() { - boolean[] failed = {false}; - page.route("**/empty.html", route -> { - failed[0] = true; - }); - page.route("**/empty.html", route -> { - route.fulfill(new Route.FulfillOptions().setStatus(200).setBody("fulfilled")); - }); - page.route("**/empty.html", route -> { - route.fallback(); - }); - Response response = page.navigate(server.EMPTY_PAGE); - byte[] body = response.body(); - assertEquals("fulfilled", new String(body, StandardCharsets.UTF_8)); - assertFalse(failed[0]); - } - - @Test - void shouldNotChainAbort() { - boolean[] failed = {false}; - page.route("**/empty.html", route -> { - failed[0] = true; - }); - page.route("**/empty.html", route -> { - route.abort(); - }); - page.route("**/empty.html", route -> { - route.fallback(); - }); - try { - page.navigate(server.EMPTY_PAGE); - fail("did not throw"); - } catch (PlaywrightException e) { - assertNotNull(e); - } - assertFalse(failed[0]); - } - -// @Test -// void shouldContinueAfterException() { -// page.route("**/empty.html", route -> { -// route.resume(); -// }); -// page.route("**/empty.html", route -> { -// try { -// route.fulfill(new Route.FulfillOptions().setHarPath(Paths.get("file")).setResponse("")); -// } catch (PlaywrightException e) { -// route.resume(); -// } -// }); -// page.navigate(server.EMPTY_PAGE); -// } - - @Test - void shouldChainOnce() { - page.route("**/empty.html", route -> { - route.fulfill(new Route.FulfillOptions().setStatus(200).setBody("fulfilled one")); - }, new Page.RouteOptions().setTimes(1)); - page.route("**/empty.html", route -> { - route.fallback(); - }, new Page.RouteOptions().setTimes(1)); - Response response = page.navigate(server.EMPTY_PAGE); - byte[] body = response.body(); - assertEquals("fulfilled one", new String(body, StandardCharsets.UTF_8)); - } - }