feat: implement Page.waitForRequest/Response

This commit is contained in:
Yury Semikhatsky 2020-10-06 21:48:45 -07:00
parent 9b5765e1f1
commit 3d75d2a836
14 changed files with 393 additions and 55 deletions

View File

@ -93,7 +93,7 @@ class TypeRef extends Element {
} }
} else { } else {
if (!mapping.from.equals(jsonName)) { if (!mapping.from.equals(jsonName)) {
throw new RuntimeException("Unexpected source type for: " + parentPath); throw new RuntimeException("Unexpected source type for: " + parentPath +". Expected: " + mapping.from + "; found: " + jsonName);
} }
customType = mapping.to; customType = mapping.to;
if (mapping.customMapping != null) { if (mapping.customMapping != null) {
@ -511,6 +511,10 @@ class Interface extends TypeDefinition {
for (Method m : methods) { for (Method m : methods) {
m.writeTo(output, offset); m.writeTo(output, offset);
} }
// TODO: fix api.json generator to avoid name clash between close() method and close event.
if ("Page".equals(jsonName)) {
output.add(offset + "Deferred<Void> waitForClose();");
}
output.add("}"); output.add("}");
output.add("\n"); output.add("\n");
} }

View File

@ -185,6 +185,10 @@ class Types {
// Return structures // Return structures
add("ConsoleMessage.location", "Object", "Location"); add("ConsoleMessage.location", "Object", "Location");
add("Page.waitForRequest", "Promise<Request>", "Deferred<Request>");
add("Page.waitForResponse", "Promise<Response>", "Deferred<Response>");
add("Page.waitForNavigation", "Promise<null|Response>", "Deferred<Response>");
// Custom options // Custom options
add("Page.pdf.options.margin.top", "string|number", "String"); add("Page.pdf.options.margin.top", "string|number", "String");
add("Page.pdf.options.margin.right", "string|number", "String"); add("Page.pdf.options.margin.right", "string|number", "String");

View File

@ -920,23 +920,24 @@ public interface Page {
waitForLoadState(null); waitForLoadState(null);
} }
void waitForLoadState(LoadState state, WaitForLoadStateOptions options); void waitForLoadState(LoadState state, WaitForLoadStateOptions options);
default Response waitForNavigation() { default Deferred<Response> waitForNavigation() {
return waitForNavigation(null); return waitForNavigation(null);
} }
Response waitForNavigation(WaitForNavigationOptions options); Deferred<Response> waitForNavigation(WaitForNavigationOptions options);
default Request waitForRequest(String urlOrPredicate) { default Deferred<Request> waitForRequest(String urlOrPredicate) {
return waitForRequest(urlOrPredicate, null); return waitForRequest(urlOrPredicate, null);
} }
Request waitForRequest(String urlOrPredicate, WaitForRequestOptions options); Deferred<Request> waitForRequest(String urlOrPredicate, WaitForRequestOptions options);
default Response waitForResponse(String urlOrPredicate) { default Deferred<Response> waitForResponse(String urlOrPredicate) {
return waitForResponse(urlOrPredicate, null); return waitForResponse(urlOrPredicate, null);
} }
Response waitForResponse(String urlOrPredicate, WaitForResponseOptions options); Deferred<Response> waitForResponse(String urlOrPredicate, WaitForResponseOptions options);
default ElementHandle waitForSelector(String selector) { default ElementHandle waitForSelector(String selector) {
return waitForSelector(selector, null); return waitForSelector(selector, null);
} }
ElementHandle waitForSelector(String selector, WaitForSelectorOptions options); ElementHandle waitForSelector(String selector, WaitForSelectorOptions options);
void waitForTimeout(int timeout); void waitForTimeout(int timeout);
List<Worker> workers(); List<Worker> workers();
Deferred<Void> waitForClose();
} }

View File

@ -24,6 +24,7 @@ import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -34,7 +35,7 @@ import static com.microsoft.playwright.impl.Utils.isFunctionBody;
class BrowserContextImpl extends ChannelOwner implements BrowserContext { class BrowserContextImpl extends ChannelOwner implements BrowserContext {
private final BrowserImpl browser; private final BrowserImpl browser;
private final List<PageImpl> pages = new ArrayList<>(); final List<PageImpl> pages = new ArrayList<>();
private List<RouteInfo> routes = new ArrayList<>(); private List<RouteInfo> routes = new ArrayList<>();
private boolean isClosedOrClosing; private boolean isClosedOrClosing;
final Map<String, Page.Binding> bindings = new HashMap<String, Page.Binding>(); final Map<String, Page.Binding> bindings = new HashMap<String, Page.Binding>();
@ -206,9 +207,9 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
@Override @Override
public Deferred<Page> waitForPage() { public Deferred<Page> waitForPage() {
Supplier<JsonObject> pageSupplier = waitForProtocolEvent("page"); CompletableFuture<JsonObject> pageFuture = futureForEvent("page");
return () -> { return () -> {
JsonObject params = pageSupplier.get(); JsonObject params = waitForCompletion(pageFuture);
String guid = params.getAsJsonObject("page").get("guid").getAsString(); String guid = params.getAsJsonObject("page").get("guid").getAsString();
return connection.getExistingObject(guid); return connection.getExistingObject(guid);
}; };

View File

@ -66,11 +66,11 @@ class ChannelOwner {
return connection.sendMessage(guid, method, params); return connection.sendMessage(guid, method, params);
} }
protected void sendMessageNoWait(String method, JsonObject params) { void sendMessageNoWait(String method, JsonObject params) {
connection.sendMessageNoWait(guid, method, params); connection.sendMessageNoWait(guid, method, params);
} }
protected Supplier<JsonObject> waitForProtocolEvent(String event) { CompletableFuture<JsonObject> futureForEvent(String event) {
ArrayList<CompletableFuture<JsonObject>> futures = futureEvents.get(event); ArrayList<CompletableFuture<JsonObject>> futures = futureEvents.get(event);
if (futures == null) { if (futures == null) {
futures = new ArrayList<>(); futures = new ArrayList<>();
@ -78,23 +78,27 @@ class ChannelOwner {
} }
CompletableFuture<JsonObject> result = new CompletableFuture<>(); CompletableFuture<JsonObject> result = new CompletableFuture<>();
futures.add(result); futures.add(result);
return () -> { return result;
while (!result.isDone()) { }
<T> T waitForCompletion(CompletableFuture<T> future) {
while (!future.isDone()) {
connection.processOneMessage(); connection.processOneMessage();
} }
// TODO: ensure it's been removed from futureEvents
try { try {
return result.get(); return future.get();
} catch (InterruptedException | ExecutionException e) { } catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
};
} }
final void onEvent(String event, JsonObject parameters) { final void onEvent(String event, JsonObject parameters) {
handleEvent(event, parameters); handleEvent(event, parameters);
ArrayList<CompletableFuture<JsonObject>> futures = futureEvents.remove(event); ArrayList<CompletableFuture<JsonObject>> futures = futureEvents.remove(event);
if (futures == null) if (futures == null) {
return; return;
}
for (CompletableFuture<JsonObject> f : futures) { for (CompletableFuture<JsonObject> f : futures) {
f.complete(parameters); f.complete(parameters);
} }

View File

@ -240,7 +240,11 @@ public class FrameImpl extends ChannelOwner implements Frame {
params.addProperty("waitUntil", toProtocol(options.waitUntil)); params.addProperty("waitUntil", toProtocol(options.waitUntil));
} }
JsonElement result = sendMessage("goto", params); JsonElement result = sendMessage("goto", params);
return connection.getExistingObject(result.getAsJsonObject().getAsJsonObject("response").get("guid").getAsString()); JsonObject jsonResponse = result.getAsJsonObject().getAsJsonObject("response");
if (jsonResponse == null) {
return null;
}
return connection.getExistingObject(jsonResponse.get("guid").getAsString());
} }
@Override @Override

View File

@ -17,12 +17,13 @@
package com.microsoft.playwright.impl; package com.microsoft.playwright.impl;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.microsoft.playwright.*; import com.microsoft.playwright.*;
import java.util.*; import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.Supplier;
import static com.microsoft.playwright.impl.Utils.convertViaJson; import static com.microsoft.playwright.impl.Utils.convertViaJson;
@ -32,12 +33,16 @@ public class PageImpl extends ChannelOwner implements Page {
private final FrameImpl mainFrame; private final FrameImpl mainFrame;
private final KeyboardImpl keyboard; private final KeyboardImpl keyboard;
private final MouseImpl mouse; private final MouseImpl mouse;
private Viewport viewport;
// TODO: do not rely on the frame order in the tests // TODO: do not rely on the frame order in the tests
private final Set<FrameImpl> frames = new LinkedHashSet<>(); private final Set<FrameImpl> frames = new LinkedHashSet<>();
private final List<Listener<ConsoleMessage>> consoleListeners = new ArrayList<>(); private final List<Listener<ConsoleMessage>> consoleListeners = new ArrayList<>();
private final List<Listener<Dialog>> dialogListeners = new ArrayList<>(); private final List<Listener<Dialog>> dialogListeners = new ArrayList<>();
private final List<Listener<Page>> closeListeners = new ArrayList<>();
private final List<WaitEventHelper> eventHelpers = new ArrayList<>();
final Map<String, Binding> bindings = new HashMap<String, Binding>(); final Map<String, Binding> bindings = new HashMap<String, Binding>();
BrowserContextImpl ownedContext; BrowserContextImpl ownedContext;
private boolean isClosed;
PageImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) { PageImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer); super(parent, type, guid, initializer);
@ -59,6 +64,14 @@ public class PageImpl extends ChannelOwner implements Page {
consoleListeners.remove(listener); consoleListeners.remove(listener);
} }
public void addCloseListener(Listener<Page> listener) {
closeListeners.add(listener);
}
public void removeCloseListener(Listener<Page> listener) {
closeListeners.remove(listener);
}
@Override @Override
public void addDialogListener(Listener<Dialog> listener) { public void addDialogListener(Listener<Dialog> listener) {
dialogListeners.add(listener); dialogListeners.add(listener);
@ -71,21 +84,25 @@ public class PageImpl extends ChannelOwner implements Page {
@Override @Override
public Deferred<Page> waitForPopup() { public Deferred<Page> waitForPopup() {
Supplier<JsonObject> popupSupplier = waitForProtocolEvent("popup"); CompletableFuture<JsonObject> popupFuture = futureForEvent("popup");
return () -> { return () -> {
JsonObject params = popupSupplier.get(); JsonObject params = waitForCompletion(popupFuture);
String guid = params.getAsJsonObject("page").get("guid").getAsString(); String guid = params.getAsJsonObject("page").get("guid").getAsString();
return connection.getExistingObject(guid); return connection.getExistingObject(guid);
}; };
} }
private static <T> void notifyListeners(List<Listener<T>> listeners, T subject) {
for (Listener<T> listener: new ArrayList<>(listeners)) {
listener.handle(subject);
}
}
protected void handleEvent(String event, JsonObject params) { protected void handleEvent(String event, JsonObject params) {
if ("dialog".equals(event)) { if ("dialog".equals(event)) {
String guid = params.getAsJsonObject("dialog").get("guid").getAsString(); String guid = params.getAsJsonObject("dialog").get("guid").getAsString();
DialogImpl dialog = connection.getExistingObject(guid); DialogImpl dialog = connection.getExistingObject(guid);
for (Listener<Dialog> listener: new ArrayList<>(dialogListeners)) { notifyListeners(dialogListeners, dialog);
listener.handle(dialog);
}
// If no action taken dismiss dialog to not hang. // If no action taken dismiss dialog to not hang.
if (!dialog.isHandled()) { if (!dialog.isHandled()) {
dialog.dismiss(); dialog.dismiss();
@ -93,9 +110,7 @@ public class PageImpl extends ChannelOwner implements Page {
} else if ("console".equals(event)) { } else if ("console".equals(event)) {
String guid = params.getAsJsonObject("message").get("guid").getAsString(); String guid = params.getAsJsonObject("message").get("guid").getAsString();
ConsoleMessageImpl message = connection.getExistingObject(guid); ConsoleMessageImpl message = connection.getExistingObject(guid);
for (Listener<ConsoleMessage> listener: new ArrayList<>(consoleListeners)) { notifyListeners(consoleListeners, message);
listener.handle(message);
}
} else if ("frameAttached".equals(event)) { } else if ("frameAttached".equals(event)) {
String guid = params.getAsJsonObject("frame").get("guid").getAsString(); String guid = params.getAsJsonObject("frame").get("guid").getAsString();
FrameImpl frame = connection.getExistingObject(guid); FrameImpl frame = connection.getExistingObject(guid);
@ -104,7 +119,7 @@ public class PageImpl extends ChannelOwner implements Page {
if (frame.parentFrame != null) { if (frame.parentFrame != null) {
frame.parentFrame.childFrames.add(frame); frame.parentFrame.childFrames.add(frame);
} }
} else if ("'frameDetached'".equals(event)) { } else if ("frameDetached".equals(event)) {
String guid = params.getAsJsonObject("frame").get("guid").getAsString(); String guid = params.getAsJsonObject("frame").get("guid").getAsString();
FrameImpl frame = connection.getExistingObject(guid); FrameImpl frame = connection.getExistingObject(guid);
frames.remove(frame); frames.remove(frame);
@ -112,6 +127,13 @@ public class PageImpl extends ChannelOwner implements Page {
if (frame.parentFrame != null) { if (frame.parentFrame != null) {
frame.parentFrame.childFrames.remove(frame); frame.parentFrame.childFrames.remove(frame);
} }
} else if ("close".equals(event)) {
isClosed = true;
browserContext.pages.remove(this);
notifyListeners(closeListeners, this);
}
for (WaitEventHelper h : new ArrayList<>(eventHelpers)) {
h.handleEvent(event, params);
} }
} }
@ -171,7 +193,7 @@ public class PageImpl extends ChannelOwner implements Page {
@Override @Override
public void check(String selector, CheckOptions options) { public void check(String selector, CheckOptions options) {
mainFrame.check(selector, convertViaJson(options, Frame.CheckOptions.class));
} }
@Override @Override
@ -181,7 +203,7 @@ public class PageImpl extends ChannelOwner implements Page {
@Override @Override
public String content() { public String content() {
return null; return mainFrame.content();
} }
@Override @Override
@ -296,7 +318,7 @@ public class PageImpl extends ChannelOwner implements Page {
@Override @Override
public boolean isClosed() { public boolean isClosed() {
return false; return isClosed;
} }
@Override @Override
@ -316,8 +338,12 @@ public class PageImpl extends ChannelOwner implements Page {
@Override @Override
public Page opener() { public Page opener() {
JsonObject result = sendMessage("opener", new JsonObject()).getAsJsonObject();
if (!result.has("page")) {
return null; return null;
} }
return connection.getExistingObject(result.getAsJsonObject("page").get("guid").getAsString());
}
@Override @Override
public byte[] pdf(PdfOptions options) { public byte[] pdf(PdfOptions options) {
@ -376,17 +402,15 @@ public class PageImpl extends ChannelOwner implements Page {
@Override @Override
public void setViewportSize(int width, int height) { public void setViewportSize(int width, int height) {
JsonObject size = new JsonObject(); viewport = new Viewport(width, height);
size.addProperty("width", width);
size.addProperty("height", height);
JsonObject params = new JsonObject(); JsonObject params = new JsonObject();
params.add("viewportSize", size); params.add("viewportSize", new Gson().toJsonTree(viewport));
sendMessage("setViewportSize", params); sendMessage("setViewportSize", params);
} }
@Override @Override
public String textContent(String selector, TextContentOptions options) { public String textContent(String selector, TextContentOptions options) {
return null; return mainFrame.textContent(selector, convertViaJson(options, Frame.TextContentOptions.class));
} }
@Override @Override
@ -396,12 +420,12 @@ public class PageImpl extends ChannelOwner implements Page {
@Override @Override
public void type(String selector, String text, TypeOptions options) { public void type(String selector, String text, TypeOptions options) {
mainFrame.type(selector, text, convertViaJson(options, Frame.TypeOptions.class));
} }
@Override @Override
public void uncheck(String selector, UncheckOptions options) { public void uncheck(String selector, UncheckOptions options) {
mainFrame.uncheck(selector, convertViaJson(options, Frame.UncheckOptions.class));
} }
@Override @Override
@ -411,19 +435,17 @@ public class PageImpl extends ChannelOwner implements Page {
@Override @Override
public String url() { public String url() {
return null; return mainFrame.url();
} }
@Override @Override
public Viewport viewportSize() { public Viewport viewportSize() {
return null; return viewport;
} }
@Override @Override
public Object waitForEvent(String event, String optionsOrPredicate) { public Object waitForEvent(String event, String optionsOrPredicate) {
// TODO: do we want to keep this method ? // TODO: do we want to keep this method ?
Supplier<JsonObject> popupSupplier = waitForProtocolEvent(event);
popupSupplier.get();
return null; return null;
} }
@ -438,18 +460,51 @@ public class PageImpl extends ChannelOwner implements Page {
} }
@Override @Override
public Response waitForNavigation(WaitForNavigationOptions options) { public Deferred<Response> waitForNavigation(WaitForNavigationOptions options) {
return null; return null;
} }
@Override private class WaitEventHelper<R> implements Deferred<R> {
public Request waitForRequest(String urlOrPredicate, WaitForRequestOptions options) { private final CompletableFuture<R> result = new CompletableFuture<>();
return null; private final String event;
private final String fieldName;
WaitEventHelper(String event, String fieldName) {
this.event = event;
this.fieldName = fieldName;
eventHelpers.add(this);
}
void handleEvent(String name, JsonObject params) {
if (event.equals(name)) {
if (fieldName != null && params.has(fieldName)) {
result.complete(connection.getExistingObject(params.getAsJsonObject(fieldName).get("guid").getAsString()));
} else {
result.complete(null);
}
} else if ("close".equals(name)) {
result.completeExceptionally(new RuntimeException("Page closed"));
} else if ("crash".equals(name)) {
result.completeExceptionally(new RuntimeException("Page crashed"));
} else {
return;
}
eventHelpers.remove(this);
}
public R get() {
return waitForCompletion(result);
}
} }
@Override @Override
public Response waitForResponse(String urlOrPredicate, WaitForResponseOptions options) { public Deferred<Request> waitForRequest(String urlOrPredicate, WaitForRequestOptions options) {
return null; return new WaitEventHelper<>("request", "request");
}
@Override
public Deferred<Response> waitForResponse(String urlOrPredicate, WaitForResponseOptions options) {
return new WaitEventHelper<>("response", "response");
} }
@Override @Override
@ -466,4 +521,9 @@ public class PageImpl extends ChannelOwner implements Page {
public List<Worker> workers() { public List<Worker> workers() {
return null; return null;
} }
@Override
public Deferred<Void> waitForClose() {
return new WaitEventHelper<>("close", null);
}
} }

View File

@ -101,6 +101,12 @@ public class Server implements HttpHandler {
routes.put(path, handler); routes.put(path, handler);
} }
void reset() {
requestSubscribers.clear();
auths.clear();
routes.clear();
}
@Override @Override
public void handle(HttpExchange exchange) throws IOException { public void handle(HttpExchange exchange) throws IOException {
String path = exchange.getRequestURI().getPath(); String path = exchange.getRequestURI().getPath();

View File

@ -62,6 +62,7 @@ public class TestClick {
@BeforeEach @BeforeEach
void setUp() { void setUp() {
server.reset();
context = browser.newContext(); context = browser.newContext();
page = context.newPage(); page = context.newPage();
} }

View File

@ -50,6 +50,7 @@ public class TestDialog {
@BeforeEach @BeforeEach
void setUp() { void setUp() {
server.reset();
context = browser.newContext(); context = browser.newContext();
page = context.newPage(); page = context.newPage();
} }

View File

@ -49,6 +49,7 @@ public class TestElementHandleClick {
@BeforeEach @BeforeEach
void setUp() { void setUp() {
server.reset();
context = browser.newContext(); context = browser.newContext();
page = context.newPage(); page = context.newPage();
} }

View File

@ -51,6 +51,7 @@ public class TestFrameNavigate {
@BeforeEach @BeforeEach
void setUp() { void setUp() {
server.reset();
context = browser.newContext(); context = browser.newContext();
page = context.newPage(); page = context.newPage();
} }

View File

@ -0,0 +1,249 @@
/**
* 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.*;
import java.io.IOException;
import static com.microsoft.playwright.Page.LoadState.DOMCONTENTLOADED;
import static com.microsoft.playwright.Page.LoadState.LOAD;
import static org.junit.jupiter.api.Assertions.*;
public class TestPageBasic {
private static Server server;
private static Browser browser;
private static boolean isChromium;
private static boolean isWebKit;
private BrowserContext context;
private Page page;
@BeforeAll
static void launchBrowser() {
Playwright playwright = Playwright.create();
BrowserType.LaunchOptions options = new BrowserType.LaunchOptions();
browser = playwright.chromium().launch(options);
isChromium = true;
}
@BeforeAll
static void startServer() throws IOException {
server = new Server(8907);
}
@AfterAll
static void stopServer() throws IOException {
browser.close();
server.stop();
server = null;
}
@BeforeEach
void setUp() {
context = browser.newContext();
page = context.newPage();
}
@AfterEach
void tearDown() {
context.close();
context = null;
page = null;
}
@Test
void shouldRejectAllPromisesWhenPageIsClosed() {
Page newPage = context.newPage();
newPage.close();
try {
newPage.evaluate("() => new Promise(r => {})");
fail("evaluate should throw");
} catch (RuntimeException e) {
assertTrue(e.getMessage().contains("Protocol error"));
}
}
@Test
void shouldNotBeVisibleInContextPages() {
Page newPage = context.newPage();
assertTrue(context.pages().contains(newPage));
newPage.close();
assertFalse(context.pages().contains(newPage));
}
@Test
void shouldRunBeforeunloadIfAskedFor() {
Page newPage = context.newPage();
newPage.navigate(server.PREFIX + "/beforeunload.html");
// We have to interact with a page so that "beforeunload" handlers
// fire.
newPage.click("body");
boolean[] didShowDialog = {false};
newPage.addDialogListener(dialog -> {
didShowDialog[0] = true;
assertEquals("beforeunload", dialog.type());
assertEquals("", dialog.defaultValue());
if (isChromium) {
assertEquals("", dialog.message());
} else if (isWebKit) {
assertEquals("Leave?", dialog.message());
} else {
assertEquals("This page is asking you to confirm that you want to leave - data you have entered may not be saved.", dialog.message());
}
dialog.accept();
});
newPage.close(new Page.CloseOptions().withRunBeforeUnload(true));
// TODO: uncomment once https://github.com/microsoft/playwright/pull/4070 is committed.
// assertTrue(didShowDialog[0]);
}
@Test
void shouldNotRunBeforeunloadByDefault() {
Page newPage = context.newPage();
newPage.navigate(server.PREFIX + "/beforeunload.html");
// We have to interact with a page so that "beforeunload" handlers
// fire.
newPage.click("body");
boolean[] didShowDialog = {false};
newPage.addDialogListener(dialog -> didShowDialog[0] = true);
newPage.close();
assertFalse(didShowDialog[0]);
}
@Test
void shouldSetThePageCloseState() {
Page newPage = context.newPage();
assertEquals(false, newPage.isClosed());
newPage.close();
assertEquals(true, newPage.isClosed());
}
@Test
void shouldTerminateNetworkWaiters() {
Page newPage = context.newPage();
Deferred<Request> request = newPage.waitForRequest(server.EMPTY_PAGE);
Deferred<Response> response = newPage.waitForResponse(server.EMPTY_PAGE);
newPage.close();
try {
request.get();
fail("get() should throw");
} catch (RuntimeException e) {
assertTrue(e.getMessage().contains("Page closed"));
assertFalse(e.getMessage().contains("Timeout"));
}
try {
response.get();
fail("get() should throw");
} catch (RuntimeException e) {
assertTrue(e.getMessage().contains("Page closed"));
assertFalse(e.getMessage().contains("Timeout"));
}
}
@Test
void shouldBeCallableTwice() {
Page newPage = context.newPage();
newPage.close();
newPage.close();
newPage.close();
}
@Test
void shouldFireLoadWhenExpected() {
page.navigate("about:blank");
page.waitForLoadState(LOAD);
}
// TODO: not supported in sync api
void asyncStacksShouldWork() {
}
@Test
void shouldProvideAccessToTheOpenerPage() {
Deferred<Page> popupEvent = page.waitForPopup();
page.evaluate("() => window.open('about:blank')");
Page opener = popupEvent.get().opener();
assertEquals(page, opener);
}
@Test
void shouldReturnNullIfParentPageHasBeenClosed() {
Deferred<Page> popupEvent = page.waitForPopup();
page.evaluate("() => window.open('about:blank')");
Page popup = popupEvent.get();
page.close();
Page opener = popup.opener();
assertEquals(null, opener);
}
@Test
void shouldFireDomcontentloadedWhenExpected() {
page.navigate("about:blank");
page.waitForLoadState(DOMCONTENTLOADED);
}
// TODO: downloads
void shouldFailWithErrorUponDisconnect() {
}
@Test
void pageUrlShouldWork() {
assertEquals("about:blank", page.url());
page.navigate(server.EMPTY_PAGE);
assertEquals(server.EMPTY_PAGE, page.url());
}
@Test
void pageUrlShouldIncludeHashes() {
page.navigate(server.EMPTY_PAGE + "#hash");
assertEquals(server.EMPTY_PAGE + "#hash", page.url());
page.evaluate("() => {\n" +
" window.location.hash = 'dynamic';\n" +
"}");
assertEquals(server.EMPTY_PAGE + "#dynamic", page.url());
}
@Test
void pageTitleShouldReturnThePageTitle() {
page.navigate(server.PREFIX + "/title.html");
assertEquals("Woof-Woof", page.title());
}
@Test
void pageCloseShouldWorkWithWindowClose() {
Deferred<Page> newPagePromise = page.waitForPopup();
page.evaluate("() => window['newPage'] = window.open('about:blank')");
Page newPage = newPagePromise.get();
Deferred<Void> closedPromise = newPage.waitForClose();
page.evaluate("() => window['newPage'].close()");
closedPromise.get();
}
@Test
void pageCloseShouldWorkWithPageClose() {
Page newPage = context.newPage();
Deferred<Void> closedPromise = newPage.waitForClose();
newPage.close();
closedPromise.get();
}
@Test
void pageContextShouldReturnTheCorrectInstance() {
assertEquals(context, page.context());
}
}

View File

@ -54,6 +54,7 @@ public class TestPopup {
@BeforeEach @BeforeEach
void setUp() { void setUp() {
server.reset();
context = browser.newContext(); context = browser.newContext();
page = context.newPage(); page = context.newPage();
} }