From c8eb4f9eebe9ce14175e8f138131104c22e0460c Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Sat, 11 Jun 2022 11:24:30 -0700 Subject: [PATCH] feat: route chaining (#950) --- .../playwright/impl/BrowserContextImpl.java | 13 +-- .../microsoft/playwright/impl/PageImpl.java | 9 +- .../microsoft/playwright/impl/RouteImpl.java | 15 +++ .../com/microsoft/playwright/impl/Router.java | 18 +++- .../playwright/TestBrowserContextRoute.java | 97 ++++++++++++++++++- .../microsoft/playwright/TestPageRoute.java | 96 +++++++++++++++++- 6 files changed, 227 insertions(+), 21 deletions(-) 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 79d6e6c6..0f8eddb1 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java @@ -474,12 +474,13 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext { } } - void handleRoute(Route route) { - boolean handled = routes.handle(route); - if (handled) { + void handleRoute(RouteImpl route) { + Router.HandleResult handled = routes.handle(route); + if (handled != Router.HandleResult.NoMatchingHandler) { maybeDisableNetworkInterception(); - } else { - route.resume(); + } + if (handled != Router.HandleResult.Handled){ + route.resume(null); } } @@ -490,7 +491,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext { @Override protected void handleEvent(String event, JsonObject params) { if ("route".equals(event)) { - Route route = connection.getExistingObject(params.getAsJsonObject("route").get("guid").getAsString()); + RouteImpl route = connection.getExistingObject(params.getAsJsonObject("route").get("guid").getAsString()); handleRoute(route); } else if ("page".equals(event)) { PageImpl page = connection.getExistingObject(params.getAsJsonObject("page").get("guid").getAsString()); 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 d441242b..84bb3a2a 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/PageImpl.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/PageImpl.java @@ -198,11 +198,12 @@ public class PageImpl extends ChannelOwner implements Page { } listeners.notify(EventType.FRAMEDETACHED, frame); } else if ("route".equals(event)) { - Route route = connection.getExistingObject(params.getAsJsonObject("route").get("guid").getAsString()); - boolean handled = routes.handle(route); - if (handled) { + RouteImpl route = connection.getExistingObject(params.getAsJsonObject("route").get("guid").getAsString()); + Router.HandleResult handled = routes.handle(route); + if (handled != Router.HandleResult.NoMatchingHandler) { maybeDisableNetworkInterception(); - } else { + } + if (handled != Router.HandleResult.Handled) { browserContext.handleRoute(route); } } else if ("video".equals(event)) { 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 665aeb03..18664ae9 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/RouteImpl.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/RouteImpl.java @@ -29,6 +29,7 @@ import java.util.Map; 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); @@ -44,6 +45,20 @@ public class RouteImpl extends ChannelOwner implements Route { }); } + boolean takeLastHandlerGaveUp() { + boolean result = lastHandlerGaveUp; + lastHandlerGaveUp = false; + return result; + } + + @Override + public void resume() { + if (lastHandlerGaveUp) { + throw new PlaywrightException("Route is already handled!"); + } + lastHandlerGaveUp = true; + } + @Override public void resume(ResumeOptions 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 7ab04df6..93a123bc 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/Router.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/Router.java @@ -19,6 +19,7 @@ package com.microsoft.playwright.impl; import com.microsoft.playwright.Route; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -70,15 +71,22 @@ class Router { return routes.size(); } - boolean handle(Route route) { - for (RouteInfo info : routes) { + enum HandleResult { NoMatchingHandler, MatchedHandlerButNotHandled, Handled } + HandleResult handle(RouteImpl route) { + HandleResult result = HandleResult.NoMatchingHandler; + for (Iterator it = routes.iterator(); it.hasNext();) { + RouteInfo info = it.next(); if (info.handle(route)) { if (info.isDone()) { - routes.remove(info); + it.remove(); } - return true; + if (route.takeLastHandlerGaveUp()) { + result = HandleResult.MatchedHandlerButNotHandled; + continue; + } + return HandleResult.Handled; } } - return false; + return result; } } diff --git a/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextRoute.java b/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextRoute.java index 5fb7b6e6..1948d699 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextRoute.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextRoute.java @@ -19,6 +19,7 @@ package com.microsoft.playwright; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutionException; @@ -78,12 +79,12 @@ public class TestBrowserContextRoute extends TestBase { }; context.route("**/empty.html", handler4); page.navigate(server.EMPTY_PAGE); - assertEquals(asList(4), intercepted); + assertEquals(asList(4, 3, 2, 1), intercepted); intercepted.clear(); context.unroute("**/empty.html", handler4); page.navigate(server.EMPTY_PAGE); - assertEquals(asList(3), intercepted); + assertEquals(asList(3, 2, 1), intercepted); intercepted.clear(); context.unroute("**/empty.html"); @@ -207,4 +208,96 @@ public class TestBrowserContextRoute extends TestBase { assertTrue(e.getMessage().contains("New URL must have same protocol as overridden URL"), e.getMessage()); } } + + + @Test + void shouldChainContinue() { + List intercepted = new ArrayList<>(); + context.route("**/empty.html", route -> { + intercepted.add(1); + route.resume(); + }); + context.route("**/empty.html", route -> { + intercepted.add(2); + route.resume(); + }); + context.route("**/empty.html", route -> { + intercepted.add(3); + route.resume(); + }); + page.navigate(server.EMPTY_PAGE); + assertEquals(asList(3, 2, 1), intercepted); + } + + @Test + void shouldNotChainFulfill() { + boolean[] failed = {false}; + context.route("**/empty.html", route -> { + failed[0] = true; + }); + context.route("**/empty.html", route -> { + route.fulfill(new Route.FulfillOptions().setStatus(200).setBody("fulfilled")); + }); + context.route("**/empty.html", route -> { + route.resume(); + }); + 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}; + context.route("**/empty.html", route -> { + failed[0] = true; + }); + context.route("**/empty.html", route -> { + route.abort(); + }); + context.route("**/empty.html", route -> { + route.resume(); + }); + + try { + page.navigate(server.EMPTY_PAGE); + fail("did not throw"); + } catch (PlaywrightException e) { + assertNotNull(e); + } + assertFalse(failed[0]); + } + + @Test + void shouldChainContinueIntoPage() { + List intercepted = new ArrayList<>(); + context.route("**/empty.html", route -> { + intercepted.add(1); + route.resume(); + }); + context.route("**/empty.html", route -> { + intercepted.add(2); + route.resume(); + }); + context.route("**/empty.html", route -> { + intercepted.add(3); + route.resume(); + }); + page.route("**/empty.html", route -> { + intercepted.add(4); + route.resume(); + }); + page.route("**/empty.html", route -> { + intercepted.add(5); + route.resume(); + }); + page.route("**/empty.html", route -> { + intercepted.add(6); + route.resume(); + }); + page.navigate(server.EMPTY_PAGE); + assertEquals(asList(6, 5, 4, 3, 2, 1), intercepted); + } + } diff --git a/playwright/src/test/java/com/microsoft/playwright/TestPageRoute.java b/playwright/src/test/java/com/microsoft/playwright/TestPageRoute.java index 4bcf3884..87c52f10 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestPageRoute.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestPageRoute.java @@ -21,6 +21,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.DisabledIf; import java.io.OutputStreamWriter; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -83,12 +84,12 @@ public class TestPageRoute extends TestBase { }; page.route("**/empty.html", handler4); page.navigate(server.EMPTY_PAGE); - assertEquals(asList(4), intercepted); + assertEquals(asList(4, 3, 2, 1), intercepted); intercepted.clear(); page.unroute("**/empty.html", handler4); page.navigate(server.EMPTY_PAGE); - assertEquals(asList(3), intercepted); + assertEquals(asList(3, 2, 1), intercepted); intercepted.clear(); page.unroute("**/empty.html"); @@ -115,12 +116,12 @@ public class TestPageRoute extends TestBase { page.route(predicate, handler3); page.navigate(server.EMPTY_PAGE); - assertEquals(asList(3), intercepted); + assertEquals(asList(3, 2, 1), intercepted); intercepted.clear(); page.unroute(predicate, handler3); page.navigate(server.EMPTY_PAGE); - assertEquals(asList(2), intercepted); + assertEquals(asList(2, 1), intercepted); intercepted.clear(); page.unroute(predicate); @@ -723,4 +724,91 @@ public class TestPageRoute extends TestBase { }); assertEquals(server.PREFIX, response.headerValue("Access-Control-Allow-Origin")); } + + @Test + void shouldChainContinue() { + List intercepted = new ArrayList<>(); + page.route("**/empty.html", route -> { + intercepted.add(1); + route.resume(); + }); + page.route("**/empty.html", route -> { + intercepted.add(2); + route.resume(); + }); + page.route("**/empty.html", route -> { + intercepted.add(3); + route.resume(); + }); + 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.resume(); + }); + 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.resume(); + }); + 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.resume(); + }, 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)); + } + }