diff --git a/README.md b/README.md index 2831c78ae..b6289e1ed 100644 --- a/README.md +++ b/README.md @@ -10,9 +10,9 @@ Playwright is a Java library to automate [Chromium](https://www.chromium.org/Hom | | Linux | macOS | Windows | | :--- | :---: | :---: | :---: | -| Chromium 140.0.7339.16 | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| Chromium 141.0.7390.37 | :white_check_mark: | :white_check_mark: | :white_check_mark: | | WebKit 26.0 | ✅ | ✅ | ✅ | -| Firefox 141.0 | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| Firefox 142.0.1 | :white_check_mark: | :white_check_mark: | :white_check_mark: | ## Documentation diff --git a/examples/pom.xml b/examples/pom.xml index 5b55e1735..63ea119f8 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -10,7 +10,7 @@ Playwright Client Examples UTF-8 - 1.55.0 + 1.56.0 diff --git a/playwright/src/main/java/com/microsoft/playwright/BrowserContext.java b/playwright/src/main/java/com/microsoft/playwright/BrowserContext.java index 695a38a6f..1e651aa91 100644 --- a/playwright/src/main/java/com/microsoft/playwright/BrowserContext.java +++ b/playwright/src/main/java/com/microsoft/playwright/BrowserContext.java @@ -46,14 +46,7 @@ public interface BrowserContext extends AutoCloseable { /** - * NOTE: Only works with Chromium browser's persistent context. - * - *

Emitted when new background page is created in the context. - *

{@code
-   * context.onBackgroundPage(backgroundPage -> {
-   *   System.out.println(backgroundPage.url());
-   * });
-   * }
+ * @deprecated Background pages have been removed from Chromium together with Manifest V2 extensions. */ void onBackgroundPage(Consumer handler); /** @@ -588,9 +581,7 @@ public WaitForPageOptions setTimeout(double timeout) { */ void addInitScript(Path script); /** - * NOTE: Background pages are only supported on Chromium-based browsers. - * - *

All existing background pages in the context. + * @deprecated Background pages have been removed from Chromium together with Manifest V2 extensions. * * @since v1.11 */ diff --git a/playwright/src/main/java/com/microsoft/playwright/Page.java b/playwright/src/main/java/com/microsoft/playwright/Page.java index 8a00b22ff..aa2ecd0c4 100644 --- a/playwright/src/main/java/com/microsoft/playwright/Page.java +++ b/playwright/src/main/java/com/microsoft/playwright/Page.java @@ -5729,6 +5729,20 @@ default boolean isVisible(String selector) { * @since v1.8 */ Keyboard keyboard(); + /** + * Returns up to (currently) 200 last console messages from this page. See {@link + * com.microsoft.playwright.Page#onConsoleMessage Page.onConsoleMessage()} for more details. + * + * @since v1.56 + */ + List consoleMessages(); + /** + * Returns up to (currently) 200 last page errors from this page. See {@link com.microsoft.playwright.Page#onPageError + * Page.onPageError()} for more details. + * + * @since v1.56 + */ + List pageErrors(); /** * The method returns an element locator that can be used to perform actions on this page / frame. Locator is resolved to * the element immediately before performing an action, so a series of actions on the same locator can in fact be performed @@ -6053,6 +6067,21 @@ default ElementHandle querySelector(String selector) { * @since v1.9 */ List querySelectorAll(String selector); + /** + * Returns up to (currently) 100 last network request from this page. See {@link com.microsoft.playwright.Page#onRequest + * Page.onRequest()} for more details. + * + *

Returned requests should be accessed immediately, otherwise they might be collected to prevent unbounded memory growth + * as new requests come in. Once collected, retrieving most information about the request is impossible. + * + *

Note that requests reported through the {@link com.microsoft.playwright.Page#onRequest Page.onRequest()} request are not + * collected, so there is a trade off between efficient memory usage with {@link com.microsoft.playwright.Page#requests + * Page.requests()} and the amount of available information reported through {@link com.microsoft.playwright.Page#onRequest + * Page.onRequest()}. + * + * @since v1.56 + */ + List requests(); /** * When testing a web page, sometimes unexpected overlays like a "Sign up" dialog appear and block actions you want to * automate, e.g. clicking a button. These overlays don't always show up in the same way or at the same time, making them diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/AssertionsBase.java b/playwright/src/main/java/com/microsoft/playwright/impl/AssertionsBase.java index c97796032..48c6c3f7e 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/AssertionsBase.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/AssertionsBase.java @@ -62,12 +62,15 @@ void expectImpl(String expression, FrameExpectOptions expectOptions, Object expe if (!log.isEmpty()) { log = "\nCall log:\n" + log; } + if (result.errorMessage != null) { + message += "\n" + result.errorMessage; + } if (expected == null) { throw new AssertionFailedError(message + log); } ValueWrapper expectedValue = formatValue(expected); ValueWrapper actualValue = formatValue(actual); - message += ": " + expectedValue.getStringRepresentation() + "\nReceived: " + actualValue.getStringRepresentation() + "\n"; + message += "\nExpected: " + expectedValue.getStringRepresentation() + "\nReceived: " + actualValue.getStringRepresentation() + "\n"; throw new AssertionFailedError(message + log, expectedValue, actualValue); } } diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java index 55a1aec42..4edc31fc4 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java @@ -34,8 +34,7 @@ import java.util.function.Predicate; import java.util.regex.Pattern; -import static com.microsoft.playwright.impl.Serialization.addHarUrlFilter; -import static com.microsoft.playwright.impl.Serialization.gson; +import static com.microsoft.playwright.impl.Serialization.*; import static com.microsoft.playwright.impl.Utils.*; import static java.nio.charset.StandardCharsets.UTF_8; import static java.nio.file.Files.readAllBytes; @@ -731,9 +730,12 @@ protected void handleEvent(String event, JsonObject params) { bindingCall.call(binding); } } else if ("console".equals(event)) { - ConsoleMessageImpl message = new ConsoleMessageImpl(connection, params); + PageImpl page = null; + if (params.has("page")) { + page = connection.getExistingObject(params.getAsJsonObject("page").get("guid").getAsString()); + } + ConsoleMessageImpl message = new ConsoleMessageImpl(connection, params, page); listeners.notify(BrowserContextImpl.EventType.CONSOLE, message); - PageImpl page = message.page(); if (page != null) { page.listeners.notify(PageImpl.EventType.CONSOLE, message); } @@ -781,14 +783,7 @@ protected void handleEvent(String event, JsonObject params) { page.listeners.notify(PageImpl.EventType.RESPONSE, response); } } else if ("pageError".equals(event)) { - SerializedError error = gson().fromJson(params.getAsJsonObject("error"), SerializedError.class); - String errorStr = ""; - if (error.error != null) { - errorStr = error.error.name + ": " + error.error.message; - if (error.error.stack != null && !error.error.stack.isEmpty()) { - errorStr += "\n" + error.error.stack; - } - } + String errorStr = parseError(params.getAsJsonObject("error")); PageImpl page; try { page = connection.getExistingObject(params.getAsJsonObject("page").get("guid").getAsString()); diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/ConsoleMessageImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/ConsoleMessageImpl.java index f199c26f8..9097027ae 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/ConsoleMessageImpl.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/ConsoleMessageImpl.java @@ -26,17 +26,12 @@ public class ConsoleMessageImpl implements ConsoleMessage { private final Connection connection; - private PageImpl page; + private final PageImpl page; private final JsonObject initializer; - public ConsoleMessageImpl(Connection connection, JsonObject initializer) { + public ConsoleMessageImpl(Connection connection, JsonObject initializer, PageImpl page) { this.connection = connection; - // Note: currently, we only report console messages for pages and they always have a page. - // However, in the future we might report console messages for service workers or something else, - // where page() would be null. - if (initializer.has("page")) { - page = connection.getExistingObject(initializer.getAsJsonObject("page").get("guid").getAsString()); - } + this.page = page; this.initializer = initializer; } diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/PageImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/PageImpl.java index 3b8ae6883..de73ffae3 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/PageImpl.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/PageImpl.java @@ -31,6 +31,7 @@ import java.util.regex.Pattern; import static com.microsoft.playwright.impl.Serialization.gson; +import static com.microsoft.playwright.impl.Serialization.parseError; import static com.microsoft.playwright.impl.Utils.*; import static com.microsoft.playwright.options.ScreenshotType.JPEG; import static com.microsoft.playwright.options.ScreenshotType.PNG; @@ -567,6 +568,17 @@ public List querySelectorAll(String selector) { return mainFrame.querySelectorAll(selector); } + @Override + public List requests() { + JsonObject json = sendMessage("requests", new JsonObject(), NO_TIMEOUT).getAsJsonObject(); + JsonArray requests = json.getAsJsonArray("requests"); + List result = new ArrayList<>(); + for (JsonElement item : requests) { + result.add(connection.getExistingObject(item.getAsJsonObject().get("guid").getAsString())); + } + return result; + } + @Override public void addLocatorHandler(Locator locator, Consumer handler, AddLocatorHandlerOptions options) { LocatorImpl locatorImpl = (LocatorImpl) locator; @@ -983,6 +995,29 @@ public Keyboard keyboard() { return keyboard; } + @Override + public List consoleMessages() { + JsonObject json = sendMessage("consoleMessages", new JsonObject(), NO_TIMEOUT).getAsJsonObject(); + JsonArray messages = json.getAsJsonArray("messages"); + List result = new ArrayList<>(); + for (JsonElement item : messages) { + result.add(new ConsoleMessageImpl(connection, item.getAsJsonObject(), this)); + } + return result; + } + + @Override + public List pageErrors() { + JsonObject json = sendMessage("pageErrors", new JsonObject(), NO_TIMEOUT).getAsJsonObject(); + JsonArray errors = json.getAsJsonArray("errors"); + List result = new ArrayList<>(); + for (JsonElement item : errors) { + String errorStr = parseError(item.getAsJsonObject()); + result.add(errorStr); + } + return result; + } + @Override public Locator locator(String selector, LocatorOptions options) { return mainFrame.locator(selector, convertType(options, Frame.LocatorOptions.class)); diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/Protocol.java b/playwright/src/main/java/com/microsoft/playwright/impl/Protocol.java index c1a1768b1..e3363f20a 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/Protocol.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/Protocol.java @@ -114,6 +114,7 @@ class FrameExpectOptions { class FrameExpectResult { boolean matches; SerializedValue received; + String errorMessage; List log; } diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/Serialization.java b/playwright/src/main/java/com/microsoft/playwright/impl/Serialization.java index 8be93f1c1..b639887f9 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/Serialization.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/Serialization.java @@ -423,6 +423,18 @@ static List parseStringList(JsonArray array) { return result; } + static String parseError(JsonObject object) { + SerializedError error = gson().fromJson(object, SerializedError.class); + String errorStr = ""; + if (error.error != null) { + errorStr = error.error.name + ": " + error.error.message; + if (error.error.stack != null && !error.error.stack.isEmpty()) { + errorStr += "\n" + error.error.stack; + } + } + return errorStr; + } + private static class OptionalSerializer implements JsonSerializer> { private static boolean isSupported(Type type) { return new TypeToken>() {}.getType().getTypeName().equals(type.getTypeName()) || diff --git a/playwright/src/test/java/com/microsoft/playwright/TestExpectMisc.java b/playwright/src/test/java/com/microsoft/playwright/TestExpectMisc.java new file mode 100644 index 000000000..9c9790101 --- /dev/null +++ b/playwright/src/test/java/com/microsoft/playwright/TestExpectMisc.java @@ -0,0 +1,46 @@ +/* + * 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 org.opentest4j.AssertionFailedError; + +import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class TestExpectMisc extends TestBase { + @Test + void strictModeViolationErrorFormat() { + page.setContent("

hello
hi
"); + AssertionFailedError error = assertThrows(AssertionFailedError.class, () -> + assertThat(page.locator("div")).isVisible()); + assertTrue(error.getMessage().contains("Locator expected to be visible"), error.getMessage()); + assertTrue(error.getMessage().contains("Error: strict mode violation: locator(\"div\") resolved to 2 elements:"), error.getMessage()); + } + + @Test + void invalidSelectorErrorFormat() { + page.setContent("
hello
hi
"); + AssertionFailedError error = assertThrows(AssertionFailedError.class, () -> + assertThat(page.locator("##")).isVisible()); + assertTrue(error.getMessage().contains("Locator expected to be visible"), error.getMessage()); + assertTrue(error.getMessage().contains("Error: Unexpected token \"#\" while parsing css selector \"##\"."), error.getMessage()); + } + +} diff --git a/playwright/src/test/java/com/microsoft/playwright/TestLocatorAssertions.java b/playwright/src/test/java/com/microsoft/playwright/TestLocatorAssertions.java index dd048c9a3..cc87d6673 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestLocatorAssertions.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestLocatorAssertions.java @@ -223,7 +223,7 @@ void hasTextWTextArrayFail() { }); assertEquals("[Text 1, Text 3, Extra]", e.getExpected().getStringRepresentation()); assertEquals("[Text 1, Text 3]", e.getActual().getStringRepresentation()); - assertTrue(e.getMessage().contains("Locator expected to have text: [Text 1, Text 3, Extra]"), e.getMessage()); + assertTrue(e.getMessage().contains("Locator expected to have text\nExpected: [Text 1, Text 3, Extra]"), e.getMessage()); assertTrue(e.getMessage().contains("Received: [Text 1, Text 3]"), e.getMessage()); } @@ -272,7 +272,7 @@ void hasAttributeTextFail() { }); assertEquals("foo", e.getExpected().getStringRepresentation()); assertEquals("node", e.getActual().getStringRepresentation()); - assertTrue(e.getMessage().contains("Locator expected to have attribute 'id': foo\nReceived: node"), e.getMessage()); + assertTrue(e.getMessage().contains("Locator expected to have attribute 'id'\nExpected: foo\nReceived: node"), e.getMessage()); } @Test @@ -291,7 +291,7 @@ void hasAttributeRegExpFail() { }); assertEquals(".Nod..", e.getExpected().getStringRepresentation()); assertEquals("node", e.getActual().getStringRepresentation()); - assertTrue(e.getMessage().contains("Locator expected to have attribute 'id' matching regex: .Nod..\nReceived: node"), e.getMessage()); + assertTrue(e.getMessage().contains("Locator expected to have attribute 'id' matching regex\nExpected: .Nod..\nReceived: node"), e.getMessage()); } @Test @@ -629,7 +629,7 @@ void hasValuesFailsWhenMultipleNotSpecified() { " "); Locator locator = page.locator("select"); locator.selectOption(new String[] {"B"}); - PlaywrightException e = assertThrows(PlaywrightException.class, () -> { + AssertionFailedError e = assertThrows(AssertionFailedError.class, () -> { assertThat(locator).hasValues(new Pattern[]{ Pattern.compile("R"), Pattern.compile("G")}); }); assertTrue(e.getMessage().contains("Not a select element with a multiple attribute"), e.getMessage()); @@ -639,7 +639,7 @@ void hasValuesFailsWhenMultipleNotSpecified() { void hasValuesFailsWhenNotASelectElement() { page.setContent(""); Locator locator = page.locator("input"); - PlaywrightException e = assertThrows(PlaywrightException.class, () -> { + AssertionFailedError e = assertThrows(AssertionFailedError.class, () -> { assertThat(locator).hasValues(new Pattern[]{ Pattern.compile("R"), Pattern.compile("G")}, new LocatorAssertions.HasValuesOptions().setTimeout(1000)); }); assertTrue(e.getMessage().contains("Not a select element with a multiple attribute"), e.getMessage()); @@ -661,7 +661,7 @@ void isCheckedFail() { }); assertEquals("checked", e.getExpected().getStringRepresentation()); assertEquals("unchecked", e.getActual().getStringRepresentation()); - assertTrue(e.getMessage().contains("Locator expected to be: checked"), e.getMessage()); + assertTrue(e.getMessage().contains("Locator expected to be\nExpected: checked"), e.getMessage()); } @Test @@ -674,7 +674,7 @@ void notIsCheckedFail() { assertEquals("checked", e.getExpected().getStringRepresentation()); assertEquals("checked", e.getActual().getStringRepresentation()); - assertTrue(e.getMessage().contains("Locator expected not to be: checked"), e.getMessage()); + assertTrue(e.getMessage().contains("Locator expected not to be\nExpected: checked"), e.getMessage()); } @Test @@ -690,7 +690,7 @@ void isCheckedFalseFail() { Locator locator = page.locator("input"); AssertionFailedError error = assertThrows(AssertionFailedError.class, () -> assertThat(locator).isChecked(new LocatorAssertions.IsCheckedOptions().setChecked(false).setTimeout(1000))); - assertTrue(error.getMessage().contains("Locator expected to be: unchecked"), error.getMessage()); + assertTrue(error.getMessage().contains("Locator expected to be\nExpected: unchecked"), error.getMessage()); } @Test @@ -796,7 +796,7 @@ void isEditableWithNotAndEditableFalse() { void isEditableThrowsOnNonInputElement() { page.setContent("