mirror of
https://github.com/microsoft/playwright-java.git
synced 2025-09-08 21:01:00 +00:00
feat: Route.fulfill (#55)
This commit is contained in:
parent
775b117277
commit
4a65757957
@ -484,18 +484,23 @@ class Field extends Element {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void writeTo(List<String> output, String offset, String access) {
|
void writeTo(List<String> output, String offset, String access) {
|
||||||
if (jsonPath.contains("Frame.waitForNavigation.options.url") ||
|
if (asList("Frame.waitForNavigation.options.url",
|
||||||
jsonPath.contains("Page.waitForNavigation.options.url")) {
|
"Page.waitForNavigation.options.url").contains(jsonPath)) {
|
||||||
output.add(offset + "public String glob;");
|
output.add(offset + "public String glob;");
|
||||||
output.add(offset + "public Pattern pattern;");
|
output.add(offset + "public Pattern pattern;");
|
||||||
output.add(offset + "public Predicate<String> predicate;");
|
output.add(offset + "public Predicate<String> predicate;");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (jsonPath.contains("Frame.waitForFunction.options.polling") ||
|
if (asList("Frame.waitForFunction.options.polling",
|
||||||
jsonPath.contains("Page.waitForFunction.options.polling")) {
|
"Page.waitForFunction.options.polling").contains(jsonPath)) {
|
||||||
output.add(offset + "public Integer pollingInterval;");
|
output.add(offset + "public Integer pollingInterval;");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if ("Route.fulfill.response.body".equals(jsonPath)) {
|
||||||
|
output.add(offset + "public String body;");
|
||||||
|
output.add(offset + "public byte[] bodyBytes;");
|
||||||
|
return;
|
||||||
|
}
|
||||||
output.add(offset + access + type.toJava() + " " + name + ";");
|
output.add(offset + access + type.toJava() + " " + name + ";");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -506,8 +511,8 @@ class Field extends Element {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void writeBuilderMethod(List<String> output, String offset, String parentClass) {
|
void writeBuilderMethod(List<String> output, String offset, String parentClass) {
|
||||||
if (jsonPath.contains("Frame.waitForNavigation.options.url") ||
|
if (asList("Frame.waitForNavigation.options.url",
|
||||||
jsonPath.contains("Page.waitForNavigation.options.url")) {
|
"Page.waitForNavigation.options.url").contains(jsonPath)) {
|
||||||
output.add(offset + "public WaitForNavigationOptions withUrl(String glob) {");
|
output.add(offset + "public WaitForNavigationOptions withUrl(String glob) {");
|
||||||
output.add(offset + " this.glob = glob;");
|
output.add(offset + " this.glob = glob;");
|
||||||
output.add(offset + " return this;");
|
output.add(offset + " return this;");
|
||||||
@ -522,8 +527,8 @@ class Field extends Element {
|
|||||||
output.add(offset + "}");
|
output.add(offset + "}");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (jsonPath.contains("Frame.waitForFunction.options.polling") ||
|
if (asList("Frame.waitForFunction.options.polling",
|
||||||
jsonPath.contains("Page.waitForFunction.options.polling")) {
|
"Page.waitForFunction.options.polling").contains(jsonPath)) {
|
||||||
output.add(offset + "public WaitForFunctionOptions withRequestAnimationFrame() {");
|
output.add(offset + "public WaitForFunctionOptions withRequestAnimationFrame() {");
|
||||||
output.add(offset + " this.pollingInterval = null;");
|
output.add(offset + " this.pollingInterval = null;");
|
||||||
output.add(offset + " return this;");
|
output.add(offset + " return this;");
|
||||||
@ -553,12 +558,18 @@ class Field extends Element {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (jsonPath.equals("Route.continue.overrides.postData")) {
|
if ("Route.continue.overrides.postData".equals(jsonPath)) {
|
||||||
output.add(offset + "public ContinueOverrides withPostData(String postData) {");
|
output.add(offset + "public ContinueOverrides withPostData(String postData) {");
|
||||||
output.add(offset + " this.postData = postData.getBytes(StandardCharsets.UTF_8);");
|
output.add(offset + " this.postData = postData.getBytes(StandardCharsets.UTF_8);");
|
||||||
output.add(offset + " return this;");
|
output.add(offset + " return this;");
|
||||||
output.add(offset + "}");
|
output.add(offset + "}");
|
||||||
}
|
}
|
||||||
|
if ("Route.fulfill.response.body".equals(jsonPath)) {
|
||||||
|
output.add(offset + "public FulfillResponse withBody(byte[] body) {");
|
||||||
|
output.add(offset + " this.bodyBytes = body;");
|
||||||
|
output.add(offset + " return this;");
|
||||||
|
output.add(offset + "}");
|
||||||
|
}
|
||||||
if (name.equals("httpCredentials")) {
|
if (name.equals("httpCredentials")) {
|
||||||
output.add(offset + "public " + parentClass + " with" + toTitle(name) + "(String username, String password) {");
|
output.add(offset + "public " + parentClass + " with" + toTitle(name) + "(String username, String password) {");
|
||||||
output.add(offset + " this." + name + " = new " + type.toJava() + "(username, password);");
|
output.add(offset + " this." + name + " = new " + type.toJava() + "(username, password);");
|
||||||
@ -632,11 +643,13 @@ class Interface extends TypeDefinition {
|
|||||||
if (jsonName.equals("Route")) {
|
if (jsonName.equals("Route")) {
|
||||||
output.add("import java.nio.charset.StandardCharsets;");
|
output.add("import java.nio.charset.StandardCharsets;");
|
||||||
}
|
}
|
||||||
if (asList("Page", "Frame", "ElementHandle", "FileChooser", "ChromiumBrowser", "Route").contains(jsonName)) {
|
if (asList("Page", "Frame", "ElementHandle", "FileChooser", "ChromiumBrowser").contains(jsonName)) {
|
||||||
output.add("import java.io.File;");
|
output.add("import java.io.File;");
|
||||||
}
|
}
|
||||||
if (jsonName.equals("Download")) {
|
if ("Download".equals(jsonName)) {
|
||||||
output.add("import java.io.InputStream;");
|
output.add("import java.io.InputStream;");
|
||||||
|
}
|
||||||
|
if (asList("Download", "Route").contains(jsonName)) {
|
||||||
output.add("import java.nio.file.Path;");
|
output.add("import java.nio.file.Path;");
|
||||||
}
|
}
|
||||||
output.add("import java.util.*;");
|
output.add("import java.util.*;");
|
||||||
|
@ -102,7 +102,8 @@ class Types {
|
|||||||
add("Frame.addScriptTag.options.path", "string", "File");
|
add("Frame.addScriptTag.options.path", "string", "File");
|
||||||
add("Frame.addStyleTag.options.path", "string", "File");
|
add("Frame.addStyleTag.options.path", "string", "File");
|
||||||
add("ElementHandle.screenshot.options.path", "string", "File");
|
add("ElementHandle.screenshot.options.path", "string", "File");
|
||||||
add("Route.fulfill.response.path", "string", "File");
|
add("Route.fulfill.response.path", "string", "Path");
|
||||||
|
add("Route.fulfill.response.status", "number", "int");
|
||||||
add("ChromiumBrowser.startTracing.options.path", "string", "File");
|
add("ChromiumBrowser.startTracing.options.path", "string", "File");
|
||||||
|
|
||||||
// Route
|
// Route
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
package com.microsoft.playwright;
|
package com.microsoft.playwright;
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.io.File;
|
import java.nio.file.Path;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
public interface Route {
|
public interface Route {
|
||||||
@ -44,13 +44,14 @@ public interface Route {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
class FulfillResponse {
|
class FulfillResponse {
|
||||||
public Integer status;
|
public int status;
|
||||||
public Map<String, String> headers;
|
public Map<String, String> headers;
|
||||||
public String contentType;
|
public String contentType;
|
||||||
public String body;
|
public String body;
|
||||||
public File path;
|
public byte[] bodyBytes;
|
||||||
|
public Path path;
|
||||||
|
|
||||||
public FulfillResponse withStatus(Integer status) {
|
public FulfillResponse withStatus(int status) {
|
||||||
this.status = status;
|
this.status = status;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@ -62,11 +63,15 @@ public interface Route {
|
|||||||
this.contentType = contentType;
|
this.contentType = contentType;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
public FulfillResponse withBody(byte[] body) {
|
||||||
|
this.bodyBytes = body;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
public FulfillResponse withBody(String body) {
|
public FulfillResponse withBody(String body) {
|
||||||
this.body = body;
|
this.body = body;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
public FulfillResponse withPath(File path) {
|
public FulfillResponse withPath(Path path) {
|
||||||
this.path = path;
|
this.path = path;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@ -16,11 +16,18 @@
|
|||||||
|
|
||||||
package com.microsoft.playwright.impl;
|
package com.microsoft.playwright.impl;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
|
import com.microsoft.playwright.PlaywrightException;
|
||||||
import com.microsoft.playwright.Request;
|
import com.microsoft.playwright.Request;
|
||||||
import com.microsoft.playwright.Route;
|
import com.microsoft.playwright.Route;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
public class RouteImpl extends ChannelOwner implements Route {
|
public class RouteImpl extends ChannelOwner implements Route {
|
||||||
public RouteImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
|
public RouteImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
|
||||||
@ -55,6 +62,53 @@ public class RouteImpl extends ChannelOwner implements Route {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void fulfill(FulfillResponse response) {
|
public void fulfill(FulfillResponse response) {
|
||||||
|
if (response == null) {
|
||||||
|
response = new FulfillResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
int status = response.status == 0 ? 200 : response.status;
|
||||||
|
String body = "";
|
||||||
|
boolean isBase64 = false;
|
||||||
|
int length = 0;
|
||||||
|
if (response.path != null) {
|
||||||
|
try {
|
||||||
|
byte[] buffer = Files.readAllBytes(response.path);
|
||||||
|
body = Base64.getEncoder().encodeToString(buffer);
|
||||||
|
isBase64 = true;
|
||||||
|
length = buffer.length;
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new PlaywrightException("Failed to read from file: " + response.path, e);
|
||||||
|
}
|
||||||
|
} else if (response.body != null) {
|
||||||
|
body = response.body;
|
||||||
|
isBase64 = false;
|
||||||
|
length = body.getBytes().length;
|
||||||
|
} else if (response.bodyBytes != null) {
|
||||||
|
body = Base64.getEncoder().encodeToString(response.bodyBytes);
|
||||||
|
isBase64 = true;
|
||||||
|
length = response.bodyBytes.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, String> headers = new LinkedHashMap<>();
|
||||||
|
if (response.headers != null) {
|
||||||
|
for (Map.Entry<String, String> h : response.headers.entrySet()) {
|
||||||
|
headers.put(h.getKey().toLowerCase(), h.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (response.contentType != null) {
|
||||||
|
headers.put("content-type", response.contentType);
|
||||||
|
} else if (response.path != null) {
|
||||||
|
headers.put("content-type", Utils.mimeType(response.path));
|
||||||
|
}
|
||||||
|
if (length != 0 && !headers.containsKey("content-length")) {
|
||||||
|
headers.put("content-length", Integer.toString(length));
|
||||||
|
}
|
||||||
|
JsonObject params = new JsonObject();
|
||||||
|
params.addProperty("status", status);
|
||||||
|
params.add("headers", Serialization.toProtocol(headers));
|
||||||
|
params.addProperty("isBase64", isBase64);
|
||||||
|
params.addProperty("body", body);
|
||||||
|
sendMessage("fulfill", params);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -26,6 +26,7 @@ import java.io.File;
|
|||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
class Utils {
|
class Utils {
|
||||||
@ -99,25 +100,29 @@ class Utils {
|
|||||||
return tokens.toString();
|
return tokens.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static String mimeType(Path path) {
|
||||||
|
String mimeType;
|
||||||
|
try {
|
||||||
|
mimeType = Files.probeContentType(path);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new PlaywrightException("Failed to determine mime type", e);
|
||||||
|
}
|
||||||
|
if (mimeType == null) {
|
||||||
|
mimeType = "application/octet-stream";
|
||||||
|
}
|
||||||
|
return mimeType;
|
||||||
|
}
|
||||||
|
|
||||||
static FileChooser.FilePayload[] toFilePayloads(File[] files) {
|
static FileChooser.FilePayload[] toFilePayloads(File[] files) {
|
||||||
List<FileChooser.FilePayload> payloads = new ArrayList<>();
|
List<FileChooser.FilePayload> payloads = new ArrayList<>();
|
||||||
for (File file : files) {
|
for (File file : files) {
|
||||||
String mimeType;
|
|
||||||
try {
|
|
||||||
mimeType = Files.probeContentType(file.toPath());
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new PlaywrightException("Failed to determine mime type", e);
|
|
||||||
}
|
|
||||||
if (mimeType == null) {
|
|
||||||
mimeType = "application/octet-stream";
|
|
||||||
}
|
|
||||||
byte[] buffer;
|
byte[] buffer;
|
||||||
try {
|
try {
|
||||||
buffer = Files.readAllBytes(file.toPath());
|
buffer = Files.readAllBytes(file.toPath());
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new PlaywrightException("Failed to read from file", e);
|
throw new PlaywrightException("Failed to read from file", e);
|
||||||
}
|
}
|
||||||
payloads.add(new FileChooser.FilePayload(file.getName(), mimeType, buffer));
|
payloads.add(new FileChooser.FilePayload(file.getName(), mimeType(file.toPath()), buffer));
|
||||||
}
|
}
|
||||||
return payloads.toArray(new FileChooser.FilePayload[0]);
|
return payloads.toArray(new FileChooser.FilePayload[0]);
|
||||||
}
|
}
|
||||||
|
@ -190,6 +190,13 @@ public class Server implements HttpHandler {
|
|||||||
exchange.getResponseHeaders().add("Content-Security-Policy", csp.get(path));
|
exchange.getResponseHeaders().add("Content-Security-Policy", csp.get(path));
|
||||||
}
|
}
|
||||||
File file = new File(resourcesDir, path.substring(1));
|
File file = new File(resourcesDir, path.substring(1));
|
||||||
|
if (!file.exists()) {
|
||||||
|
exchange.sendResponseHeaders(404, 0);
|
||||||
|
try (Writer writer = new OutputStreamWriter(exchange.getResponseBody())) {
|
||||||
|
writer.write("File not found: " + file.getCanonicalPath());
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
exchange.getResponseHeaders().add("Content-Type", mimeType(file));
|
exchange.getResponseHeaders().add("Content-Type", mimeType(file));
|
||||||
OutputStream output = exchange.getResponseBody();
|
OutputStream output = exchange.getResponseBody();
|
||||||
if (gzipRoutes.contains(path)) {
|
if (gzipRoutes.contains(path)) {
|
||||||
@ -202,10 +209,10 @@ public class Server implements HttpHandler {
|
|||||||
}
|
}
|
||||||
copy(input, output);
|
copy(input, output);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
exchange.sendResponseHeaders(404, 0);
|
|
||||||
try (Writer writer = new OutputStreamWriter(exchange.getResponseBody())) {
|
try (Writer writer = new OutputStreamWriter(exchange.getResponseBody())) {
|
||||||
writer.write("File not found: " + file.getCanonicalPath());
|
writer.write("Exception: " + e);
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
output.close();
|
output.close();
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,124 @@
|
|||||||
|
/*
|
||||||
|
* 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.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
|
||||||
|
import static java.util.Arrays.asList;
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
public class TestBrowserContextRoute extends TestBase {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldIntercept() {
|
||||||
|
BrowserContext context = browser.newContext();
|
||||||
|
boolean[] intercepted = {false};
|
||||||
|
Page page = context.newPage();
|
||||||
|
context.route("**/empty.html", (route, req) -> {
|
||||||
|
intercepted[0] = true;
|
||||||
|
Request request = route.request();
|
||||||
|
assertTrue(request.url().contains("empty.html"));
|
||||||
|
assertNotNull(request.headers().get("user-agent"));
|
||||||
|
assertEquals("GET", request.method());
|
||||||
|
assertNull(request.postData());
|
||||||
|
assertTrue(request.isNavigationRequest());
|
||||||
|
assertEquals("document", request.resourceType());
|
||||||
|
assertEquals(page.mainFrame(), request.frame());
|
||||||
|
assertEquals("about:blank", request.frame().url());
|
||||||
|
route.continue_();
|
||||||
|
});
|
||||||
|
Response response = page.navigate(server.EMPTY_PAGE);
|
||||||
|
assertTrue(response.ok());
|
||||||
|
assertTrue(intercepted[0]);
|
||||||
|
context.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldUnroute() {
|
||||||
|
BrowserContext context = browser.newContext();
|
||||||
|
Page page = context.newPage();
|
||||||
|
|
||||||
|
List<Integer> intercepted = new ArrayList<>();
|
||||||
|
BiConsumer<Route, Request> handler1 = (route, request) -> {
|
||||||
|
intercepted.add(1);
|
||||||
|
route.continue_();
|
||||||
|
};
|
||||||
|
context.route("**/empty.html", handler1);
|
||||||
|
context.route("**/empty.html", (route, request) -> {
|
||||||
|
intercepted.add(2);
|
||||||
|
route.continue_();
|
||||||
|
});
|
||||||
|
context.route("**/empty.html", (route, request) -> {
|
||||||
|
intercepted.add(3);
|
||||||
|
route.continue_();
|
||||||
|
});
|
||||||
|
context.route("**/*", (route, request) -> {
|
||||||
|
intercepted.add(4);
|
||||||
|
route.continue_();
|
||||||
|
});
|
||||||
|
page.navigate(server.EMPTY_PAGE);
|
||||||
|
assertEquals(asList(1), intercepted);
|
||||||
|
|
||||||
|
intercepted.clear();
|
||||||
|
context.unroute("**/empty.html", handler1);
|
||||||
|
page.navigate(server.EMPTY_PAGE);
|
||||||
|
assertEquals(asList(2), intercepted);
|
||||||
|
|
||||||
|
intercepted.clear();
|
||||||
|
context.unroute("**/empty.html");
|
||||||
|
page.navigate(server.EMPTY_PAGE);
|
||||||
|
assertEquals(asList(4), intercepted);
|
||||||
|
|
||||||
|
context.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldYieldToPageRoute() {
|
||||||
|
BrowserContext context = browser.newContext();
|
||||||
|
context.route("**/empty.html", (route, request) -> {
|
||||||
|
route.fulfill(new Route.FulfillResponse().withStatus(200).withBody("context"));
|
||||||
|
});
|
||||||
|
Page page = context.newPage();
|
||||||
|
page.route("**/empty.html", (route, request) -> {
|
||||||
|
route.fulfill(new Route.FulfillResponse().withStatus(200).withBody("page"));
|
||||||
|
});
|
||||||
|
Response response = page.navigate(server.EMPTY_PAGE);
|
||||||
|
assertTrue(response.ok());
|
||||||
|
assertEquals("page", response.text());
|
||||||
|
context.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldFallBackToContextRoute() {
|
||||||
|
BrowserContext context = browser.newContext();
|
||||||
|
context.route("**/empty.html", (route, request) -> {
|
||||||
|
route.fulfill(new Route.FulfillResponse().withStatus(200).withBody("context"));
|
||||||
|
});
|
||||||
|
Page page = context.newPage();
|
||||||
|
page.route("**/non-empty.html", (route, request) -> {
|
||||||
|
route.fulfill(new Route.FulfillResponse().withStatus(200).withBody("page"));
|
||||||
|
});
|
||||||
|
Response response = page.navigate(server.EMPTY_PAGE);
|
||||||
|
assertTrue(response.ok());
|
||||||
|
assertEquals("context", response.text());
|
||||||
|
context.close();
|
||||||
|
}
|
||||||
|
}
|
@ -18,11 +18,18 @@ package com.microsoft.playwright;
|
|||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.io.OutputStreamWriter;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import static com.microsoft.playwright.Page.EventType.REQUEST;
|
||||||
|
import static com.microsoft.playwright.Page.EventType.REQUESTFAILED;
|
||||||
|
import static com.microsoft.playwright.Utils.mapOf;
|
||||||
|
import static java.util.Arrays.asList;
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
public class TestPageRoute extends TestBase {
|
public class TestPageRoute extends TestBase {
|
||||||
@ -70,17 +77,17 @@ public class TestPageRoute extends TestBase {
|
|||||||
route.continue_();
|
route.continue_();
|
||||||
});
|
});
|
||||||
page.navigate(server.EMPTY_PAGE);
|
page.navigate(server.EMPTY_PAGE);
|
||||||
assertEquals(Arrays.asList(1), intercepted);
|
assertEquals(asList(1), intercepted);
|
||||||
|
|
||||||
intercepted.clear();
|
intercepted.clear();
|
||||||
page.unroute("**/empty.html", handler1);
|
page.unroute("**/empty.html", handler1);
|
||||||
page.navigate(server.EMPTY_PAGE);
|
page.navigate(server.EMPTY_PAGE);
|
||||||
assertEquals(Arrays.asList(2), intercepted);
|
assertEquals(asList(2), intercepted);
|
||||||
|
|
||||||
intercepted.clear();
|
intercepted.clear();
|
||||||
page.unroute("**/empty.html");
|
page.unroute("**/empty.html");
|
||||||
page.navigate(server.EMPTY_PAGE);
|
page.navigate(server.EMPTY_PAGE);
|
||||||
assertEquals(Arrays.asList(4), intercepted);
|
assertEquals(asList(4), intercepted);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -134,4 +141,486 @@ public class TestPageRoute extends TestBase {
|
|||||||
assertTrue(requests.get(1).headers().containsKey("referer"));
|
assertTrue(requests.get(1).headers().containsKey("referer"));
|
||||||
assertTrue(requests.get(1).headers().get("referer").contains("/one-style.html"));
|
assertTrue(requests.get(1).headers().get("referer").contains("/one-style.html"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldProperlyReturnNavigationResponseWhenURLHasCookies() {
|
||||||
|
// Setup cookie.
|
||||||
|
page.navigate(server.EMPTY_PAGE);
|
||||||
|
context.addCookies(asList(new BrowserContext.AddCookie()
|
||||||
|
.withUrl(server.EMPTY_PAGE).withName("foo").withValue("bar")));
|
||||||
|
// Setup request interception.
|
||||||
|
page.route("**/*", (route, request) -> route.continue_());
|
||||||
|
Response response = page.reload();
|
||||||
|
assertEquals(200, response.status());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldShowCustomHTTPHeaders() {
|
||||||
|
page.setExtraHTTPHeaders(mapOf("foo", "bar"));
|
||||||
|
page.route("**/*", (route, request) -> {
|
||||||
|
assertEquals("bar", request.headers().get("foo"));
|
||||||
|
route.continue_();
|
||||||
|
});
|
||||||
|
Response response = page.navigate(server.EMPTY_PAGE);
|
||||||
|
assertTrue(response.ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
// @see https://github.com/GoogleChrome/puppeteer/issues/4337
|
||||||
|
@Test
|
||||||
|
void shouldWorkWithRedirectInsideSyncXHR() {
|
||||||
|
page.navigate(server.EMPTY_PAGE);
|
||||||
|
server.setRedirect("/logo.png", "/pptr.png");
|
||||||
|
page.route("**/*", (route, request) -> route.continue_());
|
||||||
|
Object status = page.evaluate("async () => {\n" +
|
||||||
|
" const request = new XMLHttpRequest();\n" +
|
||||||
|
" request.open('GET', '/logo.png', false); // `false` makes the request synchronous\n" +
|
||||||
|
" request.send(null);\n" +
|
||||||
|
" return request.status;\n" +
|
||||||
|
"}");
|
||||||
|
assertEquals(200, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldWorkWithCustomRefererHeaders() {
|
||||||
|
page.setExtraHTTPHeaders(mapOf("referer", server.EMPTY_PAGE));
|
||||||
|
page.route("**/*", (route, request) -> {
|
||||||
|
assertEquals(server.EMPTY_PAGE, route.request().headers().get("referer"));
|
||||||
|
route.continue_();
|
||||||
|
});
|
||||||
|
Response response = page.navigate(server.EMPTY_PAGE);
|
||||||
|
assertTrue(response.ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldBeAbortable() {
|
||||||
|
page.route(Pattern.compile(".*\\.css$"), (route, request) -> route.abort());
|
||||||
|
boolean[] failed = {false};
|
||||||
|
page.addListener(REQUESTFAILED, event -> {
|
||||||
|
Request request = (Request) event.data();
|
||||||
|
if (request.url().contains(".css"))
|
||||||
|
failed[0] = true;
|
||||||
|
});
|
||||||
|
Response response = page.navigate(server.PREFIX + "/one-style.html");
|
||||||
|
assertTrue(response.ok());
|
||||||
|
assertNull(response.request().failure());
|
||||||
|
assertTrue(failed[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldBeAbortableWithCustomErrorCodes() {
|
||||||
|
page.route("**/*", (route, request) -> route.abort("internetdisconnected"));
|
||||||
|
Request[] failedRequest = {null};
|
||||||
|
page.addListener(REQUESTFAILED, event -> failedRequest[0] = (Request) event.data());
|
||||||
|
try {
|
||||||
|
page.navigate(server.EMPTY_PAGE);
|
||||||
|
} catch (PlaywrightException e) {
|
||||||
|
}
|
||||||
|
assertNotNull(failedRequest[0]);
|
||||||
|
if (isWebKit)
|
||||||
|
assertEquals("Request intercepted", failedRequest[0].failure().errorText());
|
||||||
|
else if (isFirefox)
|
||||||
|
assertEquals("NS_ERROR_OFFLINE", failedRequest[0].failure().errorText());
|
||||||
|
else
|
||||||
|
assertEquals("net::ERR_INTERNET_DISCONNECTED", failedRequest[0].failure().errorText());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldSendReferer() throws ExecutionException, InterruptedException {
|
||||||
|
page.setExtraHTTPHeaders(mapOf("referer", "http://google.com/"));
|
||||||
|
page.route("**/*", (route, request) -> route.continue_());
|
||||||
|
Future<Server.Request> request = server.waitForRequest("/grid.html");
|
||||||
|
page.navigate(server.PREFIX + "/grid.html");
|
||||||
|
assertEquals(asList("http://google.com/"), request.get().headers.get("referer"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldFailNavigationWhenAbortingMainResource() {
|
||||||
|
page.route("**/*", (route, request) -> route.abort());
|
||||||
|
try {
|
||||||
|
page.navigate(server.EMPTY_PAGE);
|
||||||
|
fail("did not throw");
|
||||||
|
} catch (PlaywrightException e) {
|
||||||
|
if (isWebKit)
|
||||||
|
assertTrue(e.getMessage().contains("Request intercepted"));
|
||||||
|
else if (isFirefox)
|
||||||
|
assertTrue(e.getMessage().contains("NS_ERROR_FAILURE"));
|
||||||
|
else
|
||||||
|
assertTrue(e.getMessage().contains("net::ERR_FAILED"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldNotWorkWithRedirects() {
|
||||||
|
List<Request> intercepted = new ArrayList<>();
|
||||||
|
page.route("**/*", (route, request) -> {
|
||||||
|
route.continue_();
|
||||||
|
intercepted.add(route.request());
|
||||||
|
});
|
||||||
|
server.setRedirect("/non-existing-page.html", "/non-existing-page-2.html");
|
||||||
|
server.setRedirect("/non-existing-page-2.html", "/non-existing-page-3.html");
|
||||||
|
server.setRedirect("/non-existing-page-3.html", "/non-existing-page-4.html");
|
||||||
|
server.setRedirect("/non-existing-page-4.html", "/empty.html");
|
||||||
|
|
||||||
|
Response response = page.navigate(server.PREFIX + "/non-existing-page.html");
|
||||||
|
assertEquals(200, response.status());
|
||||||
|
assertTrue(response.url().contains("empty.html"));
|
||||||
|
|
||||||
|
assertEquals(1, intercepted.size());
|
||||||
|
assertEquals("document", intercepted.get(0).resourceType());
|
||||||
|
assertTrue(intercepted.get(0).isNavigationRequest());
|
||||||
|
assertTrue(intercepted.get(0).url().contains("/non-existing-page.html"));
|
||||||
|
|
||||||
|
List<Request> chain = new ArrayList<>();
|
||||||
|
for (Request r = response.request(); r != null; r = r.redirectedFrom()) {
|
||||||
|
chain.add(r);
|
||||||
|
assertTrue(r.isNavigationRequest());
|
||||||
|
}
|
||||||
|
assertEquals(5, chain.size());
|
||||||
|
assertTrue(chain.get(0).url().contains("/empty.html"));
|
||||||
|
assertTrue(chain.get(1).url().contains("/non-existing-page-4.html"));
|
||||||
|
assertTrue(chain.get(2).url().contains("/non-existing-page-3.html"));
|
||||||
|
assertTrue(chain.get(3).url().contains("/non-existing-page-2.html"));
|
||||||
|
assertTrue(chain.get(4).url().contains("/non-existing-page.html"));
|
||||||
|
for (int i = 0; i < chain.size(); i++) {
|
||||||
|
assertEquals(i != 0 ? chain.get(i - 1) : null, chain.get(i).redirectedTo());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldWorkWithRedirectsForSubresources() {
|
||||||
|
List<Request> intercepted = new ArrayList<>();
|
||||||
|
page.route("**/*", (route, request) -> {
|
||||||
|
route.continue_();
|
||||||
|
intercepted.add(route.request());
|
||||||
|
});
|
||||||
|
server.setRedirect("/one-style.css", "/two-style.css");
|
||||||
|
server.setRedirect("/two-style.css", "/three-style.css");
|
||||||
|
server.setRedirect("/three-style.css", "/four-style.css");
|
||||||
|
server.setRoute("/four-style.css", exchange -> {
|
||||||
|
exchange.sendResponseHeaders(200, 0);
|
||||||
|
try (OutputStreamWriter writer = new OutputStreamWriter(exchange.getResponseBody())) {
|
||||||
|
writer.write("body {box-sizing: border-box; } ");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Response response = page.navigate(server.PREFIX + "/one-style.html");
|
||||||
|
assertEquals(200, response.status());
|
||||||
|
assertTrue(response.url().contains("one-style.html"));
|
||||||
|
|
||||||
|
assertEquals(2, intercepted.size());
|
||||||
|
assertEquals("document", intercepted.get(0).resourceType());
|
||||||
|
assertTrue(intercepted.get(0).url().contains("one-style.html"));
|
||||||
|
|
||||||
|
Request r = intercepted.get(1);
|
||||||
|
for (String url : asList("/one-style.css", "/two-style.css", "/three-style.css", "/four-style.css")) {
|
||||||
|
assertEquals("stylesheet", r.resourceType());
|
||||||
|
assertTrue(r.url().contains(url));
|
||||||
|
r = r.redirectedTo();
|
||||||
|
}
|
||||||
|
assertNull(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldWorkWithEqualRequests() {
|
||||||
|
page.navigate(server.EMPTY_PAGE);
|
||||||
|
AtomicInteger responseCount = new AtomicInteger(1);
|
||||||
|
server.setRoute("/zzz", exchange -> {
|
||||||
|
exchange.sendResponseHeaders(200, 0);
|
||||||
|
try (OutputStreamWriter writer = new OutputStreamWriter(exchange.getResponseBody())) {
|
||||||
|
writer.write((responseCount.getAndIncrement()) * 11 + "");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
boolean[] spinner = {false};
|
||||||
|
// Cancel 2nd request.
|
||||||
|
page.route("**/*", (route, request) -> {
|
||||||
|
if (spinner[0]) {
|
||||||
|
route.abort();
|
||||||
|
} else {
|
||||||
|
route.continue_();
|
||||||
|
}
|
||||||
|
spinner[0] = !spinner[0];
|
||||||
|
});
|
||||||
|
List<Object> results = new ArrayList<>();
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
results.add(page.evaluate("() => fetch('/zzz').then(response => response.text()).catch(e => 'FAILED')"));
|
||||||
|
}
|
||||||
|
assertEquals(asList("11", "FAILED", "22"), results);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldNavigateToDataURLAndNotFireDataURLRequests() {
|
||||||
|
List<Request> requests = new ArrayList<>();
|
||||||
|
page.route("**/*", (route, request) -> {
|
||||||
|
requests.add(route.request());
|
||||||
|
route.continue_();
|
||||||
|
});
|
||||||
|
String dataURL = "data:text/html,<div>yo</div>";
|
||||||
|
Response response = page.navigate(dataURL);
|
||||||
|
assertNull(response);
|
||||||
|
assertEquals(0, requests.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldBeAbleToFetchDataURLAndNotFireDataURLRequests() {
|
||||||
|
page.navigate(server.EMPTY_PAGE);
|
||||||
|
List<Request> requests = new ArrayList<>();
|
||||||
|
page.route("**/*", (route, request) -> {
|
||||||
|
requests.add(route.request());
|
||||||
|
route.continue_();
|
||||||
|
});
|
||||||
|
String dataURL = "data:text/html,<div>yo</div>";
|
||||||
|
Object text = page.evaluate("url => fetch(url).then(r => r.text())", dataURL);
|
||||||
|
assertEquals("<div>yo</div>", text);
|
||||||
|
assertEquals(0, requests.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldNavigateToURLWithHashAndAndFireRequestsWithoutHash() {
|
||||||
|
List<Request> requests = new ArrayList<>();
|
||||||
|
page.route("**/*", (route, request) -> {
|
||||||
|
requests.add(route.request());
|
||||||
|
route.continue_();
|
||||||
|
});
|
||||||
|
Response response = page.navigate(server.EMPTY_PAGE + "#hash");
|
||||||
|
assertEquals(200, response.status());
|
||||||
|
assertEquals(server.EMPTY_PAGE, response.url());
|
||||||
|
assertEquals(1, requests.size());
|
||||||
|
assertEquals(server.EMPTY_PAGE, requests.get(0).url());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldWorkWithEncodedServer() throws InterruptedException {
|
||||||
|
// The requestWillBeSent will report encoded URL, whereas interception will
|
||||||
|
// report URL as-is. @see crbug.com/759388
|
||||||
|
page.route("**/*", (route, request) -> route.continue_());
|
||||||
|
Response response = page.navigate(server.PREFIX + "/some nonexisting page");
|
||||||
|
assertEquals(404, response.status());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldWorkWithBadlyEncodedServer() {
|
||||||
|
server.setRoute("/malformed", exchange -> {
|
||||||
|
exchange.sendResponseHeaders(200, 0);
|
||||||
|
exchange.getResponseBody().close();
|
||||||
|
});
|
||||||
|
page.route("**/*", (route, request) -> route.continue_());
|
||||||
|
Response response = page.navigate(server.PREFIX + "/malformed?rnd=%911");
|
||||||
|
assertEquals(200, response.status());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldWorkWithEncodedServer2() {
|
||||||
|
// The requestWillBeSent will report URL as-is, whereas interception will
|
||||||
|
// report encoded URL for stylesheet. @see crbug.com/759388
|
||||||
|
List<Request> requests = new ArrayList<>();
|
||||||
|
page.route("**/*", (route, request) -> {
|
||||||
|
route.continue_();
|
||||||
|
requests.add(route.request());
|
||||||
|
});
|
||||||
|
Response response = page.navigate("data:text/html,<link rel='stylesheet' href='" + server.PREFIX + "/fonts?helvetica|arial'/>");
|
||||||
|
assertNull(response);
|
||||||
|
assertEquals(1, requests.size());
|
||||||
|
assertEquals(400, (requests.get(0).response()).status());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldNotThrowInvalidInterceptionIdIfTheRequestWasCancelled() {
|
||||||
|
page.setContent("<iframe></iframe>");
|
||||||
|
Route[] route = {null};
|
||||||
|
page.route("**/*", (r, req) -> route[0] = r);
|
||||||
|
// Wait for request interception.
|
||||||
|
Deferred<Event<Page.EventType>> event = page.waitForEvent(REQUEST);
|
||||||
|
page.evalOnSelector("iframe", "(frame, url) => frame.src = url", server.EMPTY_PAGE);
|
||||||
|
event.get();
|
||||||
|
// Delete frame to cause request to be canceled.
|
||||||
|
page.evalOnSelector("iframe", "frame => frame.remove()");
|
||||||
|
try {
|
||||||
|
route[0].continue_();
|
||||||
|
} catch (PlaywrightException e) {
|
||||||
|
fail("Should not throw");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldInterceptMainResourceDuringCrossProcessNavigation() {
|
||||||
|
page.navigate(server.EMPTY_PAGE);
|
||||||
|
boolean[] intercepted = {false};
|
||||||
|
page.route(server.CROSS_PROCESS_PREFIX + "/empty.html", (route, request) -> {
|
||||||
|
intercepted[0] = true;
|
||||||
|
route.continue_();
|
||||||
|
});
|
||||||
|
Response response = page.navigate(server.CROSS_PROCESS_PREFIX + "/empty.html");
|
||||||
|
assertTrue(response.ok());
|
||||||
|
assertTrue(intercepted[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldCreateARedirect() {
|
||||||
|
page.navigate(server.PREFIX + "/empty.html");
|
||||||
|
page.route("**/*", (route, request) -> {
|
||||||
|
System.out.println(request.url());
|
||||||
|
if (!request.url().equals(server.PREFIX + "/redirect_this")) {
|
||||||
|
route.continue_();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
route.fulfill(new Route.FulfillResponse()
|
||||||
|
.withStatus(301)
|
||||||
|
.withHeaders(mapOf("location", "/empty.html")));
|
||||||
|
});
|
||||||
|
Object text = page.evaluate("async url => {\n" +
|
||||||
|
" const data = await fetch(url);\n" +
|
||||||
|
" return data.text();\n" +
|
||||||
|
"}", server.PREFIX + "/redirect_this");
|
||||||
|
assertEquals("", text);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldSupportCorsWithGET() {
|
||||||
|
page.navigate(server.EMPTY_PAGE);
|
||||||
|
page.route("**/cars*", (route, request) -> {
|
||||||
|
Map<String, String> headers = new HashMap<>();
|
||||||
|
if (request.url().endsWith("allow")) {
|
||||||
|
headers.put("access-control-allow-origin", "*");
|
||||||
|
}
|
||||||
|
route.fulfill(new Route.FulfillResponse()
|
||||||
|
.withStatus(200)
|
||||||
|
.withContentType("application/json")
|
||||||
|
.withHeaders(headers)
|
||||||
|
.withBody("[\"electric\",\"gas\"]"));
|
||||||
|
});
|
||||||
|
{
|
||||||
|
// Should succeed
|
||||||
|
Object resp = page.evaluate("async () => {\n" +
|
||||||
|
" const response = await fetch('https://example.com/cars?allow', { mode: 'cors' });\n" +
|
||||||
|
" return response.json();\n" +
|
||||||
|
"}");
|
||||||
|
assertEquals(asList("electric", "gas"), resp);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// Should be rejected
|
||||||
|
try {
|
||||||
|
page.evaluate("async () => {\n" +
|
||||||
|
" const response = await fetch('https://example.com/cars?reject', { mode: 'cors' });\n" +
|
||||||
|
" return response.json();\n" +
|
||||||
|
"}");
|
||||||
|
fail("did not throw");
|
||||||
|
} catch (PlaywrightException e) {
|
||||||
|
assertTrue(e.getMessage().contains("failed"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldSupportCorsWithPOST() {
|
||||||
|
page.navigate(server.EMPTY_PAGE);
|
||||||
|
page.route("**/cars", (route, request) -> {
|
||||||
|
route.fulfill(new Route.FulfillResponse()
|
||||||
|
.withStatus(200)
|
||||||
|
.withContentType("application/json")
|
||||||
|
.withHeaders(mapOf("Access-Control-Allow-Origin", "*"))
|
||||||
|
.withBody("[\"electric\",\"gas\"]"));
|
||||||
|
});
|
||||||
|
Object resp = page.evaluate("async () => {\n" +
|
||||||
|
" const response = await fetch('https://example.com/cars', {\n" +
|
||||||
|
" method: 'POST',\n" +
|
||||||
|
" headers: { 'Content-Type': 'application/json' },\n" +
|
||||||
|
" mode: 'cors',\n" +
|
||||||
|
" body: JSON.stringify({ 'number': 1 })\n" +
|
||||||
|
" });\n" +
|
||||||
|
" return response.json();\n" +
|
||||||
|
"}");
|
||||||
|
assertEquals(asList("electric", "gas"), resp);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldSupportCorsWithCredentials() {
|
||||||
|
page.navigate(server.EMPTY_PAGE);
|
||||||
|
page.route("**/cars", (route, request) -> {
|
||||||
|
route.fulfill(new Route.FulfillResponse()
|
||||||
|
.withStatus(200)
|
||||||
|
.withContentType("application/json")
|
||||||
|
.withHeaders(mapOf("Access-Control-Allow-Origin", server.PREFIX,
|
||||||
|
"Access-Control-Allow-Credentials", "true"))
|
||||||
|
.withBody("[\"electric\",\"gas\"]"));
|
||||||
|
});
|
||||||
|
Object resp = page.evaluate("async () => {\n" +
|
||||||
|
" const response = await fetch('https://example.com/cars', {\n" +
|
||||||
|
" method: 'POST',\n" +
|
||||||
|
" headers: { 'Content-Type': 'application/json' },\n" +
|
||||||
|
" mode: 'cors',\n" +
|
||||||
|
" body: JSON.stringify({ 'number': 1 }),\n" +
|
||||||
|
" credentials: 'include'\n" +
|
||||||
|
" });\n" +
|
||||||
|
" return response.json();\n" +
|
||||||
|
"}");
|
||||||
|
assertEquals(asList("electric", "gas"), resp);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldRejectCorsWithDisallowedCredentials() {
|
||||||
|
page.navigate(server.EMPTY_PAGE);
|
||||||
|
page.route("**/cars", (route, request) -> {
|
||||||
|
route.fulfill(new Route.FulfillResponse()
|
||||||
|
.withStatus(200)
|
||||||
|
.withContentType("application/json")
|
||||||
|
// Should fail without this line below!
|
||||||
|
// "Access-Control-Allow-Credentials": "true"
|
||||||
|
.withHeaders(mapOf("Access-Control-Allow-Origin", server.PREFIX))
|
||||||
|
.withBody("[\"electric\",\"gas\"]"));
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
page.evaluate("async () => {\n" +
|
||||||
|
" const response = await fetch('https://example.com/cars', {\n" +
|
||||||
|
" method: 'POST',\n" +
|
||||||
|
" headers: { 'Content-Type': 'application/json' },\n" +
|
||||||
|
" mode: 'cors',\n" +
|
||||||
|
" body: JSON.stringify({ 'number': 1 }),\n" +
|
||||||
|
" credentials: 'include'\n" +
|
||||||
|
" });\n" +
|
||||||
|
" return response.json();\n" +
|
||||||
|
"}");
|
||||||
|
fail("did not throw");
|
||||||
|
} catch (PlaywrightException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldSupportCorsForDifferentMethods() {
|
||||||
|
page.navigate(server.EMPTY_PAGE);
|
||||||
|
page.route("**/cars", (route, request) -> {
|
||||||
|
route.fulfill(new Route.FulfillResponse()
|
||||||
|
.withStatus(200)
|
||||||
|
.withContentType("application/json")
|
||||||
|
.withHeaders(mapOf("Access-Control-Allow-Origin", "*"))
|
||||||
|
.withBody("[\"" + request.method() + "\",\"electric\",\"gas\"]"));
|
||||||
|
});
|
||||||
|
// First POST
|
||||||
|
{
|
||||||
|
Object resp = page.evaluate("async () => {\n" +
|
||||||
|
" const response = await fetch('https://example.com/cars', {\n" +
|
||||||
|
" method: 'POST',\n" +
|
||||||
|
" headers: { 'Content-Type': 'application/json' },\n" +
|
||||||
|
" mode: 'cors',\n" +
|
||||||
|
" body: JSON.stringify({ 'number': 1 })\n" +
|
||||||
|
" });\n" +
|
||||||
|
" return response.json();\n" +
|
||||||
|
"}");
|
||||||
|
assertEquals(asList("POST", "electric", "gas"), resp);
|
||||||
|
}
|
||||||
|
// Then DELETE
|
||||||
|
{
|
||||||
|
Object resp = page.evaluate("async () => {\n" +
|
||||||
|
" const response = await fetch('https://example.com/cars', {\n" +
|
||||||
|
" method: 'DELETE',\n" +
|
||||||
|
" headers: {},\n" +
|
||||||
|
" mode: 'cors',\n" +
|
||||||
|
" body: ''\n" +
|
||||||
|
" });\n" +
|
||||||
|
" return response.json();\n" +
|
||||||
|
"}");
|
||||||
|
assertEquals(asList("DELETE", "electric", "gas"), resp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,103 @@
|
|||||||
|
/*
|
||||||
|
* 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.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
|
||||||
|
import static com.microsoft.playwright.Utils.mapOf;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
public class TestRequestFulfill extends TestBase {
|
||||||
|
@Test
|
||||||
|
void shouldWork() {
|
||||||
|
page.route("**/*", (route, request) -> {
|
||||||
|
route.fulfill(new Route.FulfillResponse()
|
||||||
|
.withStatus(201)
|
||||||
|
.withContentType("text/html")
|
||||||
|
.withHeaders(mapOf("foo", "bar"))
|
||||||
|
.withBody("Yo, page!"));
|
||||||
|
});
|
||||||
|
Response response = page.navigate(server.EMPTY_PAGE);
|
||||||
|
assertEquals(201, response.status());
|
||||||
|
assertEquals("bar", response.headers().get("foo"));
|
||||||
|
assertEquals("Yo, page!", page.evaluate("() => document.body.textContent"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldWorkWithStatusCode422() {
|
||||||
|
page.route("**/*", (route, request) -> {
|
||||||
|
route.fulfill(new Route.FulfillResponse()
|
||||||
|
.withStatus(422)
|
||||||
|
.withBody("Yo, page!"));
|
||||||
|
});
|
||||||
|
Response response = page.navigate(server.EMPTY_PAGE);
|
||||||
|
assertEquals(422, response.status());
|
||||||
|
assertEquals("Unprocessable Entity", response.statusText());
|
||||||
|
assertEquals("Yo, page!", page.evaluate("document.body.textContent"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldAllowMockingBinaryResponses() {
|
||||||
|
// TODO: test.skip(browserName === "firefox" && headful, "// Firefox headful produces a different image.");
|
||||||
|
page.route("**/*", (route, request) -> {
|
||||||
|
byte[] imageBuffer;
|
||||||
|
try {
|
||||||
|
imageBuffer = Files.readAllBytes(new File("src/test/resources/pptr.png").toPath());
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
route.fulfill(new Route.FulfillResponse()
|
||||||
|
.withContentType("image/png")
|
||||||
|
.withBody(imageBuffer));
|
||||||
|
});
|
||||||
|
page.evaluate("PREFIX => {\n" +
|
||||||
|
" const img = document.createElement('img');\n" +
|
||||||
|
" img.src = PREFIX + '/does-not-exist.png';\n" +
|
||||||
|
" document.body.appendChild(img);\n" +
|
||||||
|
" return new Promise(fulfill => img.onload = fulfill);\n" +
|
||||||
|
"}", server.PREFIX);
|
||||||
|
ElementHandle img = page.querySelector("img");
|
||||||
|
// expect(img.screenshot()).toMatchSnapshot("mock-binary-response.png");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldAllowMockingSvgWithCharset() {
|
||||||
|
// TODO: test.skip(browserName === "firefox" && headful, "// Firefox headful produces a different image.");
|
||||||
|
// Firefox headful produces a different image.
|
||||||
|
page.route("**/*", (route, request) -> {
|
||||||
|
route.fulfill(new Route.FulfillResponse()
|
||||||
|
.withContentType("image/svg+xml ; charset=utf-8")
|
||||||
|
.withBody("<svg width=\"50\" height=\"50\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\"><rect x=\"10\" y=\"10\" width=\"30\" height=\"30\" stroke=\"black\" fill=\"transparent\" stroke-width=\"5\"/></svg>"));
|
||||||
|
});
|
||||||
|
page.evaluate("PREFIX => {\n" +
|
||||||
|
" const img = document.createElement('img');\n" +
|
||||||
|
" img.src = PREFIX + '/does-not-exist.svg';\n" +
|
||||||
|
" document.body.appendChild(img);\n" +
|
||||||
|
" return new Promise((f, r) => { img.onload = f; img.onerror = r; });\n" +
|
||||||
|
"}", server.PREFIX);
|
||||||
|
ElementHandle img = page.querySelector("img");
|
||||||
|
// expect(img.screenshot()).toMatchSnapshot("mock-svg.png");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user