/* * 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 com.microsoft.playwright.impl.PlaywrightImpl; import java.util.HashMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.regex.Pattern; import static org.junit.jupiter.api.Assertions.*; public class TestPageInterception extends TestBase { @Test void shouldWorkWithNavigationSmoke() { HashMap requests = new HashMap<>(); page.route("**/*", route -> { String[] parts = route.request().url().split("/"); requests.put(parts[parts.length - 1], route.request()); route.resume(); }); server.setRedirect("/rrredirect", "/frames/one-frame.html"); page.navigate(server.PREFIX + "/rrredirect"); assertTrue(requests.get("rrredirect").isNavigationRequest()); assertTrue(requests.get("frame.html").isNavigationRequest()); assertFalse(requests.get("script.js").isNavigationRequest()); assertFalse(requests.get("style.css").isNavigationRequest()); } @Test void shouldInterceptAfterAServiceWorker() { page.navigate(server.PREFIX + "/serviceworkers/fetchdummy/sw.html"); page.evaluate("() => window['activationPromise']"); // Sanity check. Object swResponse = page.evaluate("() => window['fetchDummy']('foo')"); assertEquals("responseFromServiceWorker:foo", swResponse); page.route("**/foo", route -> { int slash = route.request().url().lastIndexOf("/"); String name = route.request().url().substring(slash + 1); route.fulfill(new Route.FulfillOptions() .setStatus(200) .setContentType("text/css").setBody("responseFromInterception:" + name)); }); // Page route is applied after service worker fetch event. Object swResponse2 = page.evaluate("() => window['fetchDummy']('foo')"); assertEquals("responseFromServiceWorker:foo", swResponse2); // Page route is not applied to service worker initiated fetch. Object nonInterceptedResponse = page.evaluate("() => window['fetchDummy']('passthrough')"); assertEquals("FAILURE: Not Found", nonInterceptedResponse); // Firefox does not want to fetch the redirect for some reason. if (!isFirefox()) { // Page route is not applied to service worker initiated fetch with redirect. server.setRedirect("/serviceworkers/fetchdummy/passthrough", "/simple.json"); Object redirectedResponse = page.evaluate("() => window['fetchDummy']('passthrough')"); assertEquals("{\"foo\": \"bar\"}\n", redirectedResponse); } } @Test void shouldFulfillInterceptedResponseUsingAlias() { page.route("**/*", route -> { APIResponse response = route.fetch(); route.fulfill(new Route.FulfillOptions().setResponse(response)); }); Response response = page.navigate(server.PREFIX + "/empty.html"); assertEquals(200, response.status()); assertTrue(response.headers().get("content-type").contains("text/html")); } @Test void shouldSupportTimeoutOptionInRouteFetch() { server.setRoute("/slow", exchange -> { exchange.getResponseHeaders().set("Content-type", "text/plain"); exchange.sendResponseHeaders(200, 4096); }); page.route("**/*", route -> { PlaywrightException error = assertThrows(PlaywrightException.class, () -> route.fetch(new Route.FetchOptions().setTimeout(1000))); assertTrue(error.getMessage().contains("Timeout 1000ms exceeded"), error.getMessage()); }); PlaywrightException error = assertThrows(PlaywrightException.class, () -> page.navigate(server.PREFIX + "/slow", new Page.NavigateOptions().setTimeout(2000))); assertTrue(error.getMessage().contains("Timeout 2000ms exceeded"), error.getMessage()); } @Test void shouldInterceptWithUrlOverride() { page.route("**/*.html", route -> { APIResponse response = route.fetch(new Route.FetchOptions().setUrl(server.PREFIX + "/one-style.html")); route.fulfill(new Route.FulfillOptions().setResponse(response)); }); Response response = page.navigate(server.PREFIX + "/empty.html"); assertEquals(200, response.status()); assertTrue(response.text().contains("one-style.css"), response.text()); } @Test void shouldInterceptWithPostDataOverride() throws ExecutionException, InterruptedException { Future request = server.futureRequest("/empty.html"); page.route("**/*.html", route -> { APIResponse response = route.fetch(new Route.FetchOptions().setPostData("{ \"foo\": \"bar\" }")); route.fulfill(new Route.FulfillOptions().setResponse(response)); }); page.navigate(server.PREFIX + "/empty.html"); assertEquals("{ \"foo\": \"bar\" }", new String(request.get().postBody)); } @Test void shouldNotFollowRedirectsWhenMaxRedirectsIsSetTo0InRouteFetch() { server.setRedirect("/foo", "/empty.html"); page.route("**/*", route -> { APIResponse response = route.fetch(new Route.FetchOptions().setMaxRedirects(0)); assertEquals("/empty.html", response.headers().get("location")); assertEquals(302, response.status()); route.fulfill(new Route.FulfillOptions().setBody("hello")); }); page.navigate(server.PREFIX + "/foo"); assertTrue(page.content().contains("hello")); } @Test void shouldWorkWithGlob() { assertTrue(globToRegex("**/*.js").matcher("https://localhost:8080/foo.js").find()); assertFalse(globToRegex("**/*.css").matcher("https://localhost:8080/foo.js").find()); assertFalse(globToRegex("*.js").matcher("https://localhost:8080/foo.js").find()); assertTrue(globToRegex("https://**/*.js").matcher("https://localhost:8080/foo.js").find()); assertTrue(globToRegex("http://localhost:8080/simple/path.js").matcher("http://localhost:8080/simple/path.js").find()); assertTrue(globToRegex("**/{a,b}.js").matcher("https://localhost:8080/a.js").find()); assertTrue(globToRegex("**/{a,b}.js").matcher("https://localhost:8080/b.js").find()); assertFalse(globToRegex("**/{a,b}.js").matcher("https://localhost:8080/c.js").find()); assertTrue(globToRegex("**/*.{png,jpg,jpeg}").matcher("https://localhost:8080/c.jpg").find()); assertTrue(globToRegex("**/*.{png,jpg,jpeg}").matcher("https://localhost:8080/c.jpeg").find()); assertTrue(globToRegex("**/*.{png,jpg,jpeg}").matcher("https://localhost:8080/c.png").find()); assertFalse(globToRegex("**/*.{png,jpg,jpeg}").matcher("https://localhost:8080/c.css").find()); assertTrue(globToRegex("foo*").matcher("foo.js").find()); assertFalse(globToRegex("foo*").matcher("foo/bar.js").find()); assertFalse(globToRegex("http://localhost:3000/signin-oidc*").matcher("http://localhost:3000/signin-oidc/foo").find()); assertTrue(globToRegex("http://localhost:3000/signin-oidc*").matcher("http://localhost:3000/signin-oidcnice").find()); // range [] is NOT supported assertTrue(globToRegex("**/api/v[0-9]").matcher("http://example.com/api/v[0-9]").find()); assertFalse(globToRegex("**/api/v[0-9]").matcher("http://example.com/api/version").find()); // query params assertTrue(globToRegex("**/api\\?param").matcher("http://example.com/api?param").find()); assertFalse(globToRegex("**/api\\?param").matcher("http://example.com/api-param").find()); assertTrue(globToRegex("**/three-columns/settings.html\\?**id=settings-**").matcher("http://mydomain:8080/blah/blah/three-columns/settings.html?id=settings-e3c58efe-02e9-44b0-97ac-dd138100cf7c&blah").find()); assertEquals("^\\?$", globToRegex("\\?").pattern()); assertEquals("^\\\\$", globToRegex("\\").pattern()); assertEquals("^\\\\$", globToRegex("\\\\").pattern()); assertEquals("^\\[$", globToRegex("\\[").pattern()); assertEquals("^\\[a-z\\]$", globToRegex("[a-z]").pattern()); assertEquals("^\\$\\^\\+\\.\\*\\(\\)\\|\\?\\{\\}\\[\\]$", globToRegex("$^+.\\*()|\\?\\{\\}\\[\\]").pattern()); assertTrue(urlMatches(null, "http://playwright.dev/", "http://playwright.dev")); assertTrue(urlMatches(null, "http://playwright.dev/?a=b", "http://playwright.dev?a=b")); assertTrue(urlMatches(null, "http://playwright.dev/", "h*://playwright.dev")); assertTrue(urlMatches(null, "http://api.playwright.dev/?x=y", "http://*.playwright.dev?x=y")); assertTrue(urlMatches(null, "http://playwright.dev/foo/bar", "**/foo/**")); assertTrue(urlMatches("http://playwright.dev", "http://playwright.dev/?x=y", "?x=y")); assertTrue(urlMatches("http://playwright.dev/foo/", "http://playwright.dev/foo/bar?x=y", "./bar?x=y")); // Case insensitive matching assertTrue(urlMatches(null, "https://playwright.dev/fooBAR", "HtTpS://pLaYwRiGhT.dEv/fooBAR")); assertTrue(urlMatches("http://ignored", "https://playwright.dev/fooBAR", "HtTpS://pLaYwRiGhT.dEv/fooBAR")); // Path and search query are case-sensitive assertFalse(urlMatches(null, "https://playwright.dev/foobar", "https://playwright.dev/fooBAR")); assertFalse(urlMatches(null, "https://playwright.dev/foobar?a=b", "https://playwright.dev/foobar?A=B")); // This is not supported, we treat ? as a query separator. assertFalse(urlMatches(null, "http://localhost:8080/Simple/path.js", "http://localhost:8080/?imple/path.js")); assertFalse(urlMatches(null, "http://playwright.dev/", "http://playwright.?ev")); assertTrue(urlMatches(null, "http://playwright./?ev", "http://playwright.?ev")); assertFalse(urlMatches(null, "http://playwright.dev/foo", "http://playwright.dev/f??")); assertTrue(urlMatches(null, "http://playwright.dev/f??", "http://playwright.dev/f??")); assertTrue(urlMatches(null, "http://playwright.dev/?x=y", "http://playwright.dev\\\\?x=y")); assertTrue(urlMatches(null, "http://playwright.dev/?x=y", "http://playwright.dev/\\\\?x=y")); assertTrue(urlMatches("http://playwright.dev/foo", "http://playwright.dev/foo?bar", "?bar")); assertTrue(urlMatches("http://playwright.dev/foo", "http://playwright.dev/foo?bar", "\\\\?bar")); assertTrue(urlMatches("http://first.host/", "http://second.host/foo", "**/foo")); assertTrue(urlMatches("http://playwright.dev/", "http://localhost/", "*//localhost/")); String[] customPrefixes = {"about", "data", "chrome", "edge", "file"}; for (String prefix : customPrefixes) { assertTrue(urlMatches("http://playwright.dev/", prefix + ":blank", prefix + ":blank")); assertFalse(urlMatches("http://playwright.dev/", prefix + ":blank", "http://playwright.dev/")); assertTrue(urlMatches(null, prefix + ":blank", prefix + ":blank")); assertTrue(urlMatches(null, prefix + ":blank", prefix + ":*")); assertFalse(urlMatches(null, "not" + prefix + ":blank", prefix + ":*")); } } Pattern globToRegex(String glob) { return globToRegex(glob, null, false); } Pattern globToRegex(String glob, String baseURL, boolean webSocketUrl) { return ((PlaywrightImpl) playwright).localUtils().globToRegex(glob, baseURL, webSocketUrl); } boolean urlMatches(String baseURL, String urlString, String match) { if (match == null) { return true; } String glob = (String) match; if (glob.isEmpty()) { return true; } return globToRegex(glob, baseURL, false).matcher(urlString).find(); } }