feat: Request.postData (#48)

This commit is contained in:
Yury Semikhatsky 2020-10-27 15:09:22 -07:00 committed by GitHub
parent 7d45a26344
commit efa1a11c0f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 300 additions and 12 deletions

View File

@ -245,6 +245,7 @@ class Method extends Element {
}); });
// There is no standard JSON type in Java. // There is no standard JSON type in Java.
customSignature.put("Response.json", new String[0]); customSignature.put("Response.json", new String[0]);
customSignature.put("Request.postDataJSON", new String[0]);
customSignature.put("Page.frame", new String[]{ customSignature.put("Page.frame", new String[]{
"Frame frameByName(String name);", "Frame frameByName(String name);",
"Frame frameByUrl(String glob);", "Frame frameByUrl(String glob);",

View File

@ -39,7 +39,6 @@ public interface Request {
String method(); String method();
String postData(); String postData();
byte[] postDataBuffer(); byte[] postDataBuffer();
RequestPostDataJSON postDataJSON();
Request redirectedFrom(); Request redirectedFrom();
Request redirectedTo(); Request redirectedTo();
String resourceType(); String resourceType();

View File

@ -22,13 +22,16 @@ import com.microsoft.playwright.Frame;
import com.microsoft.playwright.Request; import com.microsoft.playwright.Request;
import com.microsoft.playwright.Response; import com.microsoft.playwright.Response;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
public class RequestImpl extends ChannelOwner implements Request { public class RequestImpl extends ChannelOwner implements Request {
private final byte[] postData;
private RequestImpl redirectedFrom; private RequestImpl redirectedFrom;
private RequestImpl redirectedTo; private RequestImpl redirectedTo;
private final Map<String, String> headers = new HashMap<>(); final Map<String, String> headers = new HashMap<>();
RequestFailure failure; RequestFailure failure;
RequestImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) { RequestImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
@ -42,6 +45,11 @@ public class RequestImpl extends ChannelOwner implements Request {
JsonObject item = e.getAsJsonObject(); JsonObject item = e.getAsJsonObject();
headers.put(item.get("name").getAsString().toLowerCase(), item.get("value").getAsString()); headers.put(item.get("name").getAsString().toLowerCase(), item.get("value").getAsString());
} }
if (initializer.has("postData")) {
postData = Base64.getDecoder().decode(initializer.get("postData").getAsString());
} else {
postData = null;
}
} }
@Override @Override
@ -71,17 +79,15 @@ public class RequestImpl extends ChannelOwner implements Request {
@Override @Override
public String postData() { public String postData() {
if (postData == null) {
return null; return null;
} }
return new String(postData, StandardCharsets.UTF_8);
}
@Override @Override
public byte[] postDataBuffer() { public byte[] postDataBuffer() {
return new byte[0]; return postData;
}
@Override
public RequestPostDataJSON postDataJSON() {
return null;
} }
@Override @Override

View File

@ -29,6 +29,7 @@ import java.util.Map;
public class ResponseImpl extends ChannelOwner implements Response { public class ResponseImpl extends ChannelOwner implements Response {
private final Map<String, String> headers = new HashMap<>(); private final Map<String, String> headers = new HashMap<>();
private final RequestImpl request;
ResponseImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) { ResponseImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer); super(parent, type, guid, initializer);
@ -37,6 +38,14 @@ public class ResponseImpl extends ChannelOwner implements Response {
JsonObject item = e.getAsJsonObject(); JsonObject item = e.getAsJsonObject();
headers.put(item.get("name").getAsString().toLowerCase(), item.get("value").getAsString()); headers.put(item.get("name").getAsString().toLowerCase(), item.get("value").getAsString());
} }
request = connection.getExistingObject(initializer.getAsJsonObject("request").get("guid").getAsString());
// TODO: uncomment when server changes are published.
// request.headers.clear();
// for (JsonElement e : initializer.getAsJsonArray("requestHeaders")) {
// JsonObject item = e.getAsJsonObject();
// request.headers.put(item.get("name").getAsString().toLowerCase(), item.get("value").getAsString());
// }
} }
@Override @Override
@ -70,8 +79,8 @@ public class ResponseImpl extends ChannelOwner implements Response {
} }
@Override @Override
public Request request() { public RequestImpl request() {
return connection.getExistingObject(initializer.getAsJsonObject("request").get("guid").getAsString()); return request;
} }
@Override @Override

View File

@ -0,0 +1,273 @@
/*
* 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.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static com.microsoft.playwright.Page.EventType.REQUEST;
import static com.microsoft.playwright.Page.EventType.RESPONSE;
import static com.microsoft.playwright.Utils.attachFrame;
import static com.microsoft.playwright.Utils.mapOf;
import static org.junit.jupiter.api.Assertions.*;
public class TestNetworkRequest extends TestBase {
@Test
void shouldWorkForMainFrameNavigationRequest() {
List<Request> requests = new ArrayList<>();
page.addListener(REQUEST, event -> requests.add((Request) event.data()));
page.navigate(server.EMPTY_PAGE);
assertEquals(1, requests.size());
assertEquals(page.mainFrame(), requests.get(0).frame());
}
@Test
void shouldWorkForSubframeNavigationRequest() {
page.navigate(server.EMPTY_PAGE);
List<Request> requests = new ArrayList<>();
page.addListener(REQUEST, event -> requests.add((Request) event.data()));
attachFrame(page, "frame1", server.EMPTY_PAGE);
assertEquals(1, requests.size());
assertEquals(page.frames().get(1), requests.get(0).frame());
}
@Test
void shouldWorkForFetchRequests() {
page.navigate(server.EMPTY_PAGE);
List<Request> requests = new ArrayList<>();
page.addListener(REQUEST, event -> requests.add((Request) event.data()));
page.evaluate("() => fetch('/digits/1.png')");
assertEquals(1, requests.size());
assertEquals(page.mainFrame(), requests.get(0).frame());
}
@Test
void shouldWorkForARedirect() {
server.setRedirect("/foo.html", "/empty.html");
List<Request> requests = new ArrayList<>();
page.addListener(REQUEST, event -> requests.add((Request) event.data()));
page.navigate(server.PREFIX + "/foo.html");
assertEquals(2, requests.size());
assertEquals(server.PREFIX + "/foo.html", requests.get(0).url());
assertEquals(server.PREFIX + "/empty.html", requests.get(1).url());
}
// https://github.com/microsoft/playwright/issues/3993
@Test
void shouldNotWorkForARedirectAndInterception() {
server.setRedirect("/foo.html", "/empty.html");
List<Request> requests = new ArrayList<>();
page.route("**", (route, request) -> {
requests.add(route.request());
route.continue_();
});
page.navigate(server.PREFIX + "/foo.html");
assertEquals(server.PREFIX + "/empty.html", page.url());
assertEquals(1, requests.size());
assertEquals(server.PREFIX + "/foo.html", requests.get(0).url());
}
@Test
void shouldReturnHeaders() {
Response response = page.navigate(server.EMPTY_PAGE);
if (isChromium)
assertTrue(response.request().headers().get("user-agent").contains("Chrome"));
else if (isFirefox)
assertTrue(response.request().headers().get("user-agent").contains("Firefox"));
else if (isWebKit)
assertTrue(response.request().headers().get("user-agent").contains("WebKit"));
}
@Test
void shouldGetTheSameHeadersAsTheServer() throws ExecutionException, InterruptedException {
// TODO: test.fail(browserName === "webkit", "Provisional headers differ from those in network stack");
Future<Server.Request> serverRequest = server.waitForRequest("/empty.html");
server.setRoute("/empty.html", exchange -> {
exchange.sendResponseHeaders(200, 0);
try (OutputStreamWriter writer = new OutputStreamWriter(exchange.getResponseBody())) {
writer.write("done");
}
});
// TODO: uncomment when server changes are published
// Response response = page.navigate(server.PREFIX + "/empty.html");
// Map<String, String> expectedHeaders = serverRequest.get().headers.entrySet().stream().collect(
// Collectors.toMap(Map.Entry::getKey, e -> e.getValue().get(0)));
// assertEquals(expectedHeaders, response.request().headers());
}
@Test
void shouldGetTheSameHeadersAsTheServerCORP() throws ExecutionException, InterruptedException {
// TODO: test.fail(browserName === "webkit", "Provisional headers differ from those in network stack");
page.navigate(server.PREFIX + "/empty.html");
Future<Server.Request> serverRequest = server.waitForRequest("/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");
}
});
Deferred<Event<Page.EventType>> responsePromise = page.waitForEvent(RESPONSE);
Object text = page.evaluate("async url => {\n" +
" const data = await fetch(url);\n" +
" return data.text();\n" +
"}", server.CROSS_PROCESS_PREFIX + "/something");
assertEquals("done", text);
// TODO: uncomment when server changes are published
// Response response = (Response) responsePromise.get().data();
// Map<String, String> expectedHeaders = serverRequest.get().headers.entrySet().stream().collect(
// Collectors.toMap(Map.Entry::getKey, e -> e.getValue().get(0)));
// assertEquals(expectedHeaders, response.request().headers());
}
@Test
void shouldReturnPostData() {
page.navigate(server.EMPTY_PAGE);
server.setRoute("/post", exchange -> {
exchange.sendResponseHeaders(200, 0);
exchange.getResponseBody().close();
});
Request[] request = {null};
page.addListener(REQUEST, event -> request[0] = (Request) event.data());
page.evaluate("() => fetch('./post', { method: 'POST', body: JSON.stringify({foo: 'bar'})})");
assertNotNull(request[0]);
assertEquals("{\"foo\":\"bar\"}", request[0].postData());
}
@Test
void shouldWorkWithBinaryPostData() {
page.navigate(server.EMPTY_PAGE);
server.setRoute("/post", exchange -> {
exchange.sendResponseHeaders(200, 0);
exchange.getResponseBody().close();
});
Request[] request = {null};
page.addListener(REQUEST, event -> request[0] = (Request) event.data());
page.evaluate("async () => {\n" +
" await fetch('./post', { method: 'POST', body: new Uint8Array(Array.from(Array(256).keys())) });\n" +
"}");
assertNotNull(request[0]);
byte[] buffer = request[0].postDataBuffer();
assertEquals(256, buffer.length);
for (int i = 0; i < 256; ++i) {
assertEquals((byte) i, buffer[i]);
}
}
@Test
void shouldWorkWithBinaryPostDataAndInterception() {
page.navigate(server.EMPTY_PAGE);
server.setRoute("/post", exchange -> {
exchange.sendResponseHeaders(200, 0);
exchange.getResponseBody().close();
});
Request[] request = {null};
page.addListener(REQUEST, event -> request[0] = (Request) event.data());
page.route("/post", (route, req) -> route.continue_());
page.evaluate("async () => {\n" +
" await fetch('./post', { method: 'POST', body: new Uint8Array(Array.from(Array(256).keys())) });\n" +
"}");
assertNotNull(request[0]);
byte[] buffer = request[0].postDataBuffer();
assertEquals(256, buffer.length);
for (int i = 0; i < 256; ++i) {
assertEquals((byte) i, buffer[i]);
}
}
@Test
void shouldBeUndefinedWhenThereIsNoPostData() {
Response response = page.navigate(server.EMPTY_PAGE);
assertNull(response.request().postData());
}
void shouldParseTheJsonPostData() {
// Not supported in Java.
}
void shouldParseTheDataIfContentTypeIsApplicationXWwwFormUrlencoded() {
// Not supported in Java.
}
@Test
void shouldReturnEventSource() {
// 1. Setup server-sent events on server that immediately sends a message to the client.
server.setRoute("/sse", exchange -> {
exchange.getResponseHeaders().add("Content-Type", "text/event-stream");
exchange.getResponseHeaders().add("Connection", "keep-alive");
exchange.getResponseHeaders().add("Cache-Control", "no-cache");
exchange.sendResponseHeaders(200, 0);
try (OutputStreamWriter writer = new OutputStreamWriter(exchange.getResponseBody())) {
writer.write("data: {\"foo\":\"bar\"}\n\n");
}
});
// 2. Subscribe to page request events.
page.navigate(server.EMPTY_PAGE);
List<Request> requests = new ArrayList<>();
page.addListener(REQUEST, event -> requests.add((Request) event.data()));
// 3. Connect to EventSource in browser and return first message.
Object result = page.evaluate("() => {\n" +
" const eventSource = new EventSource('/sse');\n" +
" return new Promise(resolve => {\n" +
" eventSource.onmessage = e => resolve(JSON.parse(e.data));\n" +
" });\n" +
"}");
assertEquals(mapOf("foo", "bar"), result);
assertEquals("eventsource", requests.get(0).resourceType());
}
@Test
void shouldReturnNavigationBit() {
Map<String, Request> requests = new HashMap<>();
page.addListener(REQUEST, event -> {
Request request = (Request) event.data();
String name = request.url();
int lastSlash = name.lastIndexOf('/');
if (lastSlash != -1) {
name = name.substring(lastSlash + 1);
}
requests.put(name, request);
});
server.setRedirect("/rrredirect", "/frames/one-frame.html");
page.navigate(server.PREFIX + "/rrredirect");
assertTrue(requests.get("rrredirect").isNavigationRequest());
assertTrue(requests.get("one-frame.html").isNavigationRequest());
assertTrue(requests.get("frame.html").isNavigationRequest());
assertFalse(requests.get("script.js").isNavigationRequest());
assertFalse(requests.get("style.css").isNavigationRequest());
}
@Test
void shouldReturnNavigationBitWhenNavigatingToImage() {
List<Request> requests = new ArrayList<>();
page.addListener(REQUEST, event -> requests.add((Request) event.data()));
page.navigate(server.PREFIX + "/pptr.png");
assertTrue(requests.get(0).isNavigationRequest());
}
}

View File

@ -32,7 +32,7 @@ class Utils {
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
static Map mapOf(Object... entries) { static <K,V> Map<K, V> mapOf(Object... entries) {
Map result = new HashMap(); Map result = new HashMap();
for (int i = 0; i + 1 < entries.length; i += 2) { for (int i = 0; i + 1 < entries.length; i += 2) {
result.put(entries[i], entries[i + 1]); result.put(entries[i], entries[i + 1]);