diff --git a/api-generator/src/main/java/com/microsoft/playwright/tools/ApiGenerator.java b/api-generator/src/main/java/com/microsoft/playwright/tools/ApiGenerator.java index 6406c5f3..d703e9ea 100644 --- a/api-generator/src/main/java/com/microsoft/playwright/tools/ApiGenerator.java +++ b/api-generator/src/main/java/com/microsoft/playwright/tools/ApiGenerator.java @@ -93,7 +93,7 @@ class TypeRef extends Element { } } else { 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; if (mapping.customMapping != null) { @@ -511,6 +511,10 @@ class Interface extends TypeDefinition { for (Method m : methods) { 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 waitForClose();"); + } output.add("}"); output.add("\n"); } diff --git a/api-generator/src/main/java/com/microsoft/playwright/tools/Types.java b/api-generator/src/main/java/com/microsoft/playwright/tools/Types.java index 2d0d67ea..b7f7896f 100644 --- a/api-generator/src/main/java/com/microsoft/playwright/tools/Types.java +++ b/api-generator/src/main/java/com/microsoft/playwright/tools/Types.java @@ -185,6 +185,10 @@ class Types { // Return structures add("ConsoleMessage.location", "Object", "Location"); + add("Page.waitForRequest", "Promise", "Deferred"); + add("Page.waitForResponse", "Promise", "Deferred"); + add("Page.waitForNavigation", "Promise", "Deferred"); + // Custom options add("Page.pdf.options.margin.top", "string|number", "String"); add("Page.pdf.options.margin.right", "string|number", "String"); diff --git a/lib/src/main/java/com/microsoft/playwright/Page.java b/lib/src/main/java/com/microsoft/playwright/Page.java index fd47f381..c88fbb30 100644 --- a/lib/src/main/java/com/microsoft/playwright/Page.java +++ b/lib/src/main/java/com/microsoft/playwright/Page.java @@ -920,23 +920,24 @@ public interface Page { waitForLoadState(null); } void waitForLoadState(LoadState state, WaitForLoadStateOptions options); - default Response waitForNavigation() { + default Deferred waitForNavigation() { return waitForNavigation(null); } - Response waitForNavigation(WaitForNavigationOptions options); - default Request waitForRequest(String urlOrPredicate) { + Deferred waitForNavigation(WaitForNavigationOptions options); + default Deferred waitForRequest(String urlOrPredicate) { return waitForRequest(urlOrPredicate, null); } - Request waitForRequest(String urlOrPredicate, WaitForRequestOptions options); - default Response waitForResponse(String urlOrPredicate) { + Deferred waitForRequest(String urlOrPredicate, WaitForRequestOptions options); + default Deferred waitForResponse(String urlOrPredicate) { return waitForResponse(urlOrPredicate, null); } - Response waitForResponse(String urlOrPredicate, WaitForResponseOptions options); + Deferred waitForResponse(String urlOrPredicate, WaitForResponseOptions options); default ElementHandle waitForSelector(String selector) { return waitForSelector(selector, null); } ElementHandle waitForSelector(String selector, WaitForSelectorOptions options); void waitForTimeout(int timeout); List workers(); + Deferred waitForClose(); } diff --git a/lib/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java b/lib/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java index 6fff4890..cbeccd12 100644 --- a/lib/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java +++ b/lib/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java @@ -24,6 +24,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.CompletableFuture; import java.util.function.BiConsumer; import java.util.function.Supplier; import java.util.regex.Pattern; @@ -34,7 +35,7 @@ import static com.microsoft.playwright.impl.Utils.isFunctionBody; class BrowserContextImpl extends ChannelOwner implements BrowserContext { private final BrowserImpl browser; - private final List pages = new ArrayList<>(); + final List pages = new ArrayList<>(); private List routes = new ArrayList<>(); private boolean isClosedOrClosing; final Map bindings = new HashMap(); @@ -206,9 +207,9 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext { @Override public Deferred waitForPage() { - Supplier pageSupplier = waitForProtocolEvent("page"); + CompletableFuture pageFuture = futureForEvent("page"); return () -> { - JsonObject params = pageSupplier.get(); + JsonObject params = waitForCompletion(pageFuture); String guid = params.getAsJsonObject("page").get("guid").getAsString(); return connection.getExistingObject(guid); }; diff --git a/lib/src/main/java/com/microsoft/playwright/impl/ChannelOwner.java b/lib/src/main/java/com/microsoft/playwright/impl/ChannelOwner.java index 7412a4c2..2634995f 100644 --- a/lib/src/main/java/com/microsoft/playwright/impl/ChannelOwner.java +++ b/lib/src/main/java/com/microsoft/playwright/impl/ChannelOwner.java @@ -66,11 +66,11 @@ class ChannelOwner { return connection.sendMessage(guid, method, params); } - protected void sendMessageNoWait(String method, JsonObject params) { + void sendMessageNoWait(String method, JsonObject params) { connection.sendMessageNoWait(guid, method, params); } - protected Supplier waitForProtocolEvent(String event) { + CompletableFuture futureForEvent(String event) { ArrayList> futures = futureEvents.get(event); if (futures == null) { futures = new ArrayList<>(); @@ -78,23 +78,27 @@ class ChannelOwner { } CompletableFuture result = new CompletableFuture<>(); futures.add(result); - return () -> { - while (!result.isDone()) { - connection.processOneMessage(); - } - try { - return result.get(); - } catch (InterruptedException | ExecutionException e) { - throw new RuntimeException(e); - } - }; + return result; + } + + T waitForCompletion(CompletableFuture future) { + while (!future.isDone()) { + connection.processOneMessage(); + } + // TODO: ensure it's been removed from futureEvents + try { + return future.get(); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } } final void onEvent(String event, JsonObject parameters) { handleEvent(event, parameters); ArrayList> futures = futureEvents.remove(event); - if (futures == null) + if (futures == null) { return; + } for (CompletableFuture f : futures) { f.complete(parameters); } diff --git a/lib/src/main/java/com/microsoft/playwright/impl/FrameImpl.java b/lib/src/main/java/com/microsoft/playwright/impl/FrameImpl.java index 2674cc08..4c653395 100644 --- a/lib/src/main/java/com/microsoft/playwright/impl/FrameImpl.java +++ b/lib/src/main/java/com/microsoft/playwright/impl/FrameImpl.java @@ -240,7 +240,11 @@ public class FrameImpl extends ChannelOwner implements Frame { params.addProperty("waitUntil", toProtocol(options.waitUntil)); } 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 diff --git a/lib/src/main/java/com/microsoft/playwright/impl/PageImpl.java b/lib/src/main/java/com/microsoft/playwright/impl/PageImpl.java index 16ae39cd..8b778b55 100644 --- a/lib/src/main/java/com/microsoft/playwright/impl/PageImpl.java +++ b/lib/src/main/java/com/microsoft/playwright/impl/PageImpl.java @@ -17,12 +17,13 @@ package com.microsoft.playwright.impl; import com.google.gson.Gson; +import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.microsoft.playwright.*; import java.util.*; +import java.util.concurrent.CompletableFuture; import java.util.function.BiConsumer; -import java.util.function.Supplier; 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 KeyboardImpl keyboard; private final MouseImpl mouse; + private Viewport viewport; // TODO: do not rely on the frame order in the tests private final Set frames = new LinkedHashSet<>(); private final List> consoleListeners = new ArrayList<>(); private final List> dialogListeners = new ArrayList<>(); + private final List> closeListeners = new ArrayList<>(); + private final List eventHelpers = new ArrayList<>(); final Map bindings = new HashMap(); BrowserContextImpl ownedContext; + private boolean isClosed; PageImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) { super(parent, type, guid, initializer); @@ -59,6 +64,14 @@ public class PageImpl extends ChannelOwner implements Page { consoleListeners.remove(listener); } + public void addCloseListener(Listener listener) { + closeListeners.add(listener); + } + + public void removeCloseListener(Listener listener) { + closeListeners.remove(listener); + } + @Override public void addDialogListener(Listener listener) { dialogListeners.add(listener); @@ -71,21 +84,25 @@ public class PageImpl extends ChannelOwner implements Page { @Override public Deferred waitForPopup() { - Supplier popupSupplier = waitForProtocolEvent("popup"); + CompletableFuture popupFuture = futureForEvent("popup"); return () -> { - JsonObject params = popupSupplier.get(); + JsonObject params = waitForCompletion(popupFuture); String guid = params.getAsJsonObject("page").get("guid").getAsString(); return connection.getExistingObject(guid); }; } + private static void notifyListeners(List> listeners, T subject) { + for (Listener listener: new ArrayList<>(listeners)) { + listener.handle(subject); + } + } + protected void handleEvent(String event, JsonObject params) { if ("dialog".equals(event)) { String guid = params.getAsJsonObject("dialog").get("guid").getAsString(); DialogImpl dialog = connection.getExistingObject(guid); - for (Listener listener: new ArrayList<>(dialogListeners)) { - listener.handle(dialog); - } + notifyListeners(dialogListeners, dialog); // If no action taken dismiss dialog to not hang. if (!dialog.isHandled()) { dialog.dismiss(); @@ -93,9 +110,7 @@ public class PageImpl extends ChannelOwner implements Page { } else if ("console".equals(event)) { String guid = params.getAsJsonObject("message").get("guid").getAsString(); ConsoleMessageImpl message = connection.getExistingObject(guid); - for (Listener listener: new ArrayList<>(consoleListeners)) { - listener.handle(message); - } + notifyListeners(consoleListeners, message); } else if ("frameAttached".equals(event)) { String guid = params.getAsJsonObject("frame").get("guid").getAsString(); FrameImpl frame = connection.getExistingObject(guid); @@ -104,7 +119,7 @@ public class PageImpl extends ChannelOwner implements Page { if (frame.parentFrame != null) { frame.parentFrame.childFrames.add(frame); } - } else if ("'frameDetached'".equals(event)) { + } else if ("frameDetached".equals(event)) { String guid = params.getAsJsonObject("frame").get("guid").getAsString(); FrameImpl frame = connection.getExistingObject(guid); frames.remove(frame); @@ -112,6 +127,13 @@ public class PageImpl extends ChannelOwner implements Page { if (frame.parentFrame != null) { 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 public void check(String selector, CheckOptions options) { - + mainFrame.check(selector, convertViaJson(options, Frame.CheckOptions.class)); } @Override @@ -181,7 +203,7 @@ public class PageImpl extends ChannelOwner implements Page { @Override public String content() { - return null; + return mainFrame.content(); } @Override @@ -296,7 +318,7 @@ public class PageImpl extends ChannelOwner implements Page { @Override public boolean isClosed() { - return false; + return isClosed; } @Override @@ -316,7 +338,11 @@ public class PageImpl extends ChannelOwner implements Page { @Override public Page opener() { - return null; + JsonObject result = sendMessage("opener", new JsonObject()).getAsJsonObject(); + if (!result.has("page")) { + return null; + } + return connection.getExistingObject(result.getAsJsonObject("page").get("guid").getAsString()); } @Override @@ -376,17 +402,15 @@ public class PageImpl extends ChannelOwner implements Page { @Override public void setViewportSize(int width, int height) { - JsonObject size = new JsonObject(); - size.addProperty("width", width); - size.addProperty("height", height); + viewport = new Viewport(width, height); JsonObject params = new JsonObject(); - params.add("viewportSize", size); + params.add("viewportSize", new Gson().toJsonTree(viewport)); sendMessage("setViewportSize", params); } @Override public String textContent(String selector, TextContentOptions options) { - return null; + return mainFrame.textContent(selector, convertViaJson(options, Frame.TextContentOptions.class)); } @Override @@ -396,12 +420,12 @@ public class PageImpl extends ChannelOwner implements Page { @Override public void type(String selector, String text, TypeOptions options) { - + mainFrame.type(selector, text, convertViaJson(options, Frame.TypeOptions.class)); } @Override public void uncheck(String selector, UncheckOptions options) { - + mainFrame.uncheck(selector, convertViaJson(options, Frame.UncheckOptions.class)); } @Override @@ -411,19 +435,17 @@ public class PageImpl extends ChannelOwner implements Page { @Override public String url() { - return null; + return mainFrame.url(); } @Override public Viewport viewportSize() { - return null; + return viewport; } @Override public Object waitForEvent(String event, String optionsOrPredicate) { // TODO: do we want to keep this method ? - Supplier popupSupplier = waitForProtocolEvent(event); - popupSupplier.get(); return null; } @@ -438,18 +460,51 @@ public class PageImpl extends ChannelOwner implements Page { } @Override - public Response waitForNavigation(WaitForNavigationOptions options) { + public Deferred waitForNavigation(WaitForNavigationOptions options) { return null; } - @Override - public Request waitForRequest(String urlOrPredicate, WaitForRequestOptions options) { - return null; + private class WaitEventHelper implements Deferred { + private final CompletableFuture result = new CompletableFuture<>(); + 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 - public Response waitForResponse(String urlOrPredicate, WaitForResponseOptions options) { - return null; + public Deferred waitForRequest(String urlOrPredicate, WaitForRequestOptions options) { + return new WaitEventHelper<>("request", "request"); + } + + @Override + public Deferred waitForResponse(String urlOrPredicate, WaitForResponseOptions options) { + return new WaitEventHelper<>("response", "response"); } @Override @@ -466,4 +521,9 @@ public class PageImpl extends ChannelOwner implements Page { public List workers() { return null; } + + @Override + public Deferred waitForClose() { + return new WaitEventHelper<>("close", null); + } } diff --git a/lib/src/test/java/com/microsoft/playwright/Server.java b/lib/src/test/java/com/microsoft/playwright/Server.java index 49e1807f..41b3a73c 100644 --- a/lib/src/test/java/com/microsoft/playwright/Server.java +++ b/lib/src/test/java/com/microsoft/playwright/Server.java @@ -101,6 +101,12 @@ public class Server implements HttpHandler { routes.put(path, handler); } + void reset() { + requestSubscribers.clear(); + auths.clear(); + routes.clear(); + } + @Override public void handle(HttpExchange exchange) throws IOException { String path = exchange.getRequestURI().getPath(); diff --git a/lib/src/test/java/com/microsoft/playwright/TestClick.java b/lib/src/test/java/com/microsoft/playwright/TestClick.java index d017f420..30d13ef5 100644 --- a/lib/src/test/java/com/microsoft/playwright/TestClick.java +++ b/lib/src/test/java/com/microsoft/playwright/TestClick.java @@ -62,6 +62,7 @@ public class TestClick { @BeforeEach void setUp() { + server.reset(); context = browser.newContext(); page = context.newPage(); } diff --git a/lib/src/test/java/com/microsoft/playwright/TestDialog.java b/lib/src/test/java/com/microsoft/playwright/TestDialog.java index 3a59444d..e01838a4 100644 --- a/lib/src/test/java/com/microsoft/playwright/TestDialog.java +++ b/lib/src/test/java/com/microsoft/playwright/TestDialog.java @@ -50,6 +50,7 @@ public class TestDialog { @BeforeEach void setUp() { + server.reset(); context = browser.newContext(); page = context.newPage(); } diff --git a/lib/src/test/java/com/microsoft/playwright/TestElementHandleClick.java b/lib/src/test/java/com/microsoft/playwright/TestElementHandleClick.java index d5528603..c68bc871 100644 --- a/lib/src/test/java/com/microsoft/playwright/TestElementHandleClick.java +++ b/lib/src/test/java/com/microsoft/playwright/TestElementHandleClick.java @@ -49,6 +49,7 @@ public class TestElementHandleClick { @BeforeEach void setUp() { + server.reset(); context = browser.newContext(); page = context.newPage(); } diff --git a/lib/src/test/java/com/microsoft/playwright/TestFrameNavigate.java b/lib/src/test/java/com/microsoft/playwright/TestFrameNavigate.java index 16b2dad5..6394bcd5 100644 --- a/lib/src/test/java/com/microsoft/playwright/TestFrameNavigate.java +++ b/lib/src/test/java/com/microsoft/playwright/TestFrameNavigate.java @@ -51,6 +51,7 @@ public class TestFrameNavigate { @BeforeEach void setUp() { + server.reset(); context = browser.newContext(); page = context.newPage(); } diff --git a/lib/src/test/java/com/microsoft/playwright/TestPageBasic.java b/lib/src/test/java/com/microsoft/playwright/TestPageBasic.java new file mode 100644 index 00000000..f7a9436d --- /dev/null +++ b/lib/src/test/java/com/microsoft/playwright/TestPageBasic.java @@ -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 = newPage.waitForRequest(server.EMPTY_PAGE); + Deferred 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 popupEvent = page.waitForPopup(); + page.evaluate("() => window.open('about:blank')"); + Page opener = popupEvent.get().opener(); + assertEquals(page, opener); + } + + @Test + void shouldReturnNullIfParentPageHasBeenClosed() { + Deferred 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 newPagePromise = page.waitForPopup(); + page.evaluate("() => window['newPage'] = window.open('about:blank')"); + Page newPage = newPagePromise.get(); + Deferred closedPromise = newPage.waitForClose(); + page.evaluate("() => window['newPage'].close()"); + closedPromise.get(); + } + + @Test + void pageCloseShouldWorkWithPageClose() { + Page newPage = context.newPage(); + Deferred closedPromise = newPage.waitForClose(); + newPage.close(); + closedPromise.get(); + } + + @Test + void pageContextShouldReturnTheCorrectInstance() { + assertEquals(context, page.context()); + } +} diff --git a/lib/src/test/java/com/microsoft/playwright/TestPopup.java b/lib/src/test/java/com/microsoft/playwright/TestPopup.java index 795453cb..d4ccdc46 100644 --- a/lib/src/test/java/com/microsoft/playwright/TestPopup.java +++ b/lib/src/test/java/com/microsoft/playwright/TestPopup.java @@ -54,6 +54,7 @@ public class TestPopup { @BeforeEach void setUp() { + server.reset(); context = browser.newContext(); page = context.newPage(); }