From 5baac53538efb9348dd81b9269406981f39b9fe5 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Wed, 28 Oct 2026 14:20:14 -0700 Subject: [PATCH 1/4] test: follow the upstream rewrites of the tests with pending evaluate promises Port the new versions of CA-011, EV-063, DD-019, RO-012, RO-013 and RO-100 from microsoft/playwright#43656 line by line, and drop the Java notes that explained how the old ports differed. TR-041 already matches. --- .../tests/library/TestCapabilities.java | 10 +++---- .../playwright/tests/page/TestPageDrag.java | 1 - .../tests/page/TestPageEvaluate.java | 18 ++++++------- .../tests/page/TestPageRequestFulfill.java | 2 -- .../playwright/tests/page/TestPageRoute.java | 27 +++++++++---------- 5 files changed, 24 insertions(+), 34 deletions(-) diff --git a/playwright/src/test/java/com/microsoft/playwright/tests/library/TestCapabilities.java b/playwright/src/test/java/com/microsoft/playwright/tests/library/TestCapabilities.java index 818cd1da7..fb4cec0c3 100644 --- a/playwright/src/test/java/com/microsoft/playwright/tests/library/TestCapabilities.java +++ b/playwright/src/test/java/com/microsoft/playwright/tests/library/TestCapabilities.java @@ -190,17 +190,15 @@ void shouldNotCrashOnShowDirectoryPicker(Page page, TestServer server) { page.navigate(server.EMPTY_PAGE); // "User activation is required to show a file picker." - so we click first. page.locator("body").click(); - // Java: upstream does not await the evaluate; the picker is opened from the page without returning its promise. page.evaluate("() => {\n" + - " window.__picker = (async () => {\n" + - " const dir = await window.showDirectoryPicker();\n" + - " return dir.name;\n" + - " // In headless it throws (aborted), in headed it stalls (Test ended) and waits for the picker to be accepted.\n" + - " })().catch(e => e.message);\n" + + " window.showDirectoryPicker().catch(e => { window.__pickerError = String(e); });\n" + "}"); // The dialog will not be accepted, so we just wait for some time to // to give the browser a chance to crash. page.waitForTimeout(3_000); + // In headless it throws (aborted), in headed it stalls and waits for the picker to be accepted. + String error = (String) page.evaluate("() => window.__pickerError ?? 'stalled'"); + assertTrue(Pattern.compile("((DOMException|AbortError): .*The user aborted a request|stalled)").matcher(error).find(), error); } @PlaywrightTest diff --git a/playwright/src/test/java/com/microsoft/playwright/tests/page/TestPageDrag.java b/playwright/src/test/java/com/microsoft/playwright/tests/page/TestPageDrag.java index 22c36b2f3..101ae809e 100644 --- a/playwright/src/test/java/com/microsoft/playwright/tests/page/TestPageDrag.java +++ b/playwright/src/test/java/com/microsoft/playwright/tests/page/TestPageDrag.java @@ -572,7 +572,6 @@ void shouldHandleCustomDataTransfer(Page page, BrowserName browserName) { assumeFalse(browserName == BrowserName.WEBKIT && OS.WINDOWS.isCurrentOs(), "fixme"); page.setContent(""); - // Java: the promise is kept on window and awaited after the drag, so that the evaluate returns at once. page.evaluate("() => {\n" + " window.__result = new Promise(resolve => {\n" + " document.addEventListener('dragstart', event => {\n" + diff --git a/playwright/src/test/java/com/microsoft/playwright/tests/page/TestPageEvaluate.java b/playwright/src/test/java/com/microsoft/playwright/tests/page/TestPageEvaluate.java index f69d6cb00..89866ac66 100644 --- a/playwright/src/test/java/com/microsoft/playwright/tests/page/TestPageEvaluate.java +++ b/playwright/src/test/java/com/microsoft/playwright/tests/page/TestPageEvaluate.java @@ -632,16 +632,14 @@ void shouldSimulateAUserGesture(Page page) { @PlaywrightTest @DisplayName("EV-063 should throw a nice error after a navigation") void shouldThrowANiceErrorAfterANavigation(Page page) { - // Java: the first evaluate blocks, so the reload that upstream triggers with a second evaluate - // is scheduled from the page before the promise is returned. - PlaywrightException error = assertThrows(PlaywrightException.class, () -> page.evaluate("() => {\n" + - " setTimeout(() => {\n" + - " window.location.reload();\n" + - " setTimeout(() => window['__resolve'](42), 1000);\n" + - " }, 0);\n" + - " return new Promise(f => window['__resolve'] = f);\n" + - "}")); - assertTrue(error.getMessage().contains("navigation"), error.getMessage()); + PlaywrightException[] error = {null}; + page.waitForNavigation(() -> { + error[0] = assertThrows(PlaywrightException.class, () -> page.evaluate("() => new Promise(f => setTimeout(() => {\n" + + " window.location.reload();\n" + + " setTimeout(() => f(42), 1000);\n" + + "}, 0))")); + }); + assertTrue(error[0].getMessage().contains("navigation"), error[0].getMessage()); } @PlaywrightTest diff --git a/playwright/src/test/java/com/microsoft/playwright/tests/page/TestPageRequestFulfill.java b/playwright/src/test/java/com/microsoft/playwright/tests/page/TestPageRequestFulfill.java index a527f4817..371e8c29b 100644 --- a/playwright/src/test/java/com/microsoft/playwright/tests/page/TestPageRequestFulfill.java +++ b/playwright/src/test/java/com/microsoft/playwright/tests/page/TestPageRequestFulfill.java @@ -142,8 +142,6 @@ void shouldNotThrowIfRequestWasCancelledByThePage(Page page, TestServer server) Route[] interceptedRoute = {null}; page.route("**/data.json", route -> interceptedRoute[0] = route); page.navigate(server.EMPTY_PAGE); - // Java: upstream does not await the evaluate; the fetch is started in the page without returning - // its promise, so that evaluate returns while the route is pending. page.evaluate("url => {\n" + " globalThis.controller = new AbortController();\n" + " fetch(url, { signal: globalThis.controller.signal }).catch(() => {});\n" + diff --git a/playwright/src/test/java/com/microsoft/playwright/tests/page/TestPageRoute.java b/playwright/src/test/java/com/microsoft/playwright/tests/page/TestPageRoute.java index e17592ed8..a5f24bbe9 100644 --- a/playwright/src/test/java/com/microsoft/playwright/tests/page/TestPageRoute.java +++ b/playwright/src/test/java/com/microsoft/playwright/tests/page/TestPageRoute.java @@ -291,26 +291,26 @@ void shouldPauseInterceptedXHRUntilContinue(Page page, TestServer server) { page.navigate(server.EMPTY_PAGE); Route[] routePromise = {null}; page.route("**/global-var.html", route -> routePromise[0] = route); - // Java: the synchronous request blocks the page, so it is started from a page timer and its status - // is kept on window; the evaluate returns at once and the route stays pending. - CompletableFuture serverRequest = server.waitForRequest("/global-var.html"); + boolean[] xhrFinished = {false}; + page.onRequestFinished(request -> { + if (request.url().endsWith("/global-var.html")) + xhrFinished[0] = true; + }); + // The synchronous request blocks the page, so it is started from a timer. page.evaluate("() => {\n" + - " window.__status = undefined;\n" + - " setTimeout(() => {\n" + + " window.__statusPromise = new Promise(resolve => setTimeout(() => {\n" + " const request = new XMLHttpRequest();\n" + " request.open('GET', '/global-var.html', false); // `false` makes the request synchronous\n" + " request.send(null);\n" + - " window.__status = request.status;\n" + - " }, 0);\n" + + " resolve(request.status);\n" + + " }, 0));\n" + "}"); page.waitForCondition(() -> routePromise[0] != null); // Check that intercepted request is actually paused. - // Java: the page is blocked by the synchronous request, so the check is that the server has not seen it. page.waitForTimeout(500); - assertFalse(serverRequest.isDone()); + assertFalse(xhrFinished[0]); routePromise[0].resume(); - page.waitForFunction("() => window.__status !== undefined"); - assertEquals(200, page.evaluate("() => window.__status")); + assertEquals(200, page.evaluate("() => window.__statusPromise")); } @PlaywrightTest @@ -319,8 +319,6 @@ void shouldPauseInterceptedFetchRequestUntilContinue(Page page, TestServer serve page.navigate(server.EMPTY_PAGE); Route[] routePromise = {null}; page.route("**/global-var.html", route -> routePromise[0] = route); - // Java: the fetch is started in the page and its promise kept on window, so that the evaluate returns - // while the route is pending; the promise is awaited by a second evaluate. page.evaluate("() => {\n" + " window.__fetchFinished = false;\n" + " window.__statusPromise = fetch('/global-var.html').then(response => {\n" + @@ -333,8 +331,7 @@ void shouldPauseInterceptedFetchRequestUntilContinue(Page page, TestServer serve page.waitForTimeout(500); assertEquals(false, page.evaluate("() => window.__fetchFinished")); routePromise[0].resume(); - Object status = page.evaluate("() => window.__statusPromise"); - assertEquals(200, status); + assertEquals(200, page.evaluate("() => window.__statusPromise")); } @PlaywrightTest From 07d6c7ce3456bc47f21b5fc75b7bec0fcc9660a9 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Tue, 27 Oct 2026 17:41:56 -0700 Subject: [PATCH 2/4] test: collect only log messages in the click tests, Chrome logs the favicon 404 --- .../test/java/com/microsoft/playwright/TestClick.java | 7 ++++++- .../microsoft/playwright/tests/page/TestPageClick.java | 9 +++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/playwright/src/test/java/com/microsoft/playwright/TestClick.java b/playwright/src/test/java/com/microsoft/playwright/TestClick.java index a3841fd9d..0396c3be1 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestClick.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestClick.java @@ -141,7 +141,12 @@ void shouldSelectTheTextByTripleClicking(Page page, Server server) { void shouldClickOffscreenButtons(Page page, Server server) { page.navigate(server.PREFIX + "/offscreenbuttons.html"); List messages = new ArrayList<>(); - page.onConsoleMessage(message -> messages.add(message.text())); + // Only log messages, Chrome can log the 404 error of /favicon.ico while the test runs. + page.onConsoleMessage(message -> { + if ("log".equals(message.type())) { + messages.add(message.text()); + } + }); for (int i = 0; i < 11; ++i) { // We might've scrolled to click a button - reset to (0, 0). page.evaluate("() => window.scrollTo(0, 0)"); diff --git a/playwright/src/test/java/com/microsoft/playwright/tests/page/TestPageClick.java b/playwright/src/test/java/com/microsoft/playwright/tests/page/TestPageClick.java index 2ee8dad81..b12acae1e 100644 --- a/playwright/src/test/java/com/microsoft/playwright/tests/page/TestPageClick.java +++ b/playwright/src/test/java/com/microsoft/playwright/tests/page/TestPageClick.java @@ -223,7 +223,7 @@ void shouldSelectTheTextByTripleClicking(Page page, TestServer server) { void shouldClickOffscreenButtons(Page page, TestServer server) { page.navigate(server.PREFIX + "/offscreenbuttons.html"); List messages = new ArrayList<>(); - // Java: only log messages, Edge on macOS can log the 404 error of /favicon.ico first. + // Java: only log messages, Chrome can log the 404 error of /favicon.ico while the test runs. page.onConsoleMessage(msg -> { if ("log".equals(msg.type())) { messages.add(msg.text()); @@ -1087,7 +1087,12 @@ void shouldFireContextmenuEventOnRightClickInCorrectOrder(Page page, TestServer " document.addEventListener('contextmenu', logEvent);\n" + "}"); List entries = new ArrayList<>(); - page.onConsoleMessage(message -> entries.add(message.text())); + // Java: only log messages, Chrome can log the 404 error of /favicon.ico while the test runs. + page.onConsoleMessage(message -> { + if ("log".equals(message.type())) { + entries.add(message.text()); + } + }); page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Click me")).click(new Locator.ClickOptions().setButton(MouseButton.RIGHT)); if (browserName == BrowserName.CHROMIUM && Env.isWindows) { expectPollToEqual(page, () -> entries, Arrays.asList("mousedown", "mouseup", "contextmenu")); From 28200ca966decfbaa3a8cf3453053dba63393027 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Wed, 28 Oct 2026 16:09:14 -0700 Subject: [PATCH 3/4] test: answer favicon requests in the test servers with 204 Headless Chrome and Edge request /favicon.ico after a load and log the 404 as a console error, which got into the console messages of NA-085, RO-157 and other tests. Without the 404 the ports of TZ-006, CL-017 and CL-083 and the old click test can collect all messages, as upstream does. --- .../test/java/com/microsoft/playwright/Server.java | 6 ++++++ .../java/com/microsoft/playwright/TestClick.java | 7 +------ .../microsoft/playwright/fixtures/TestServer.java | 4 ++++ .../library/TestBrowsercontextTimezoneId.java | 3 +-- .../playwright/tests/page/TestPageClick.java | 14 ++------------ 5 files changed, 14 insertions(+), 20 deletions(-) diff --git a/playwright/src/test/java/com/microsoft/playwright/Server.java b/playwright/src/test/java/com/microsoft/playwright/Server.java index 3c1946068..64867a1ea 100644 --- a/playwright/src/test/java/com/microsoft/playwright/Server.java +++ b/playwright/src/test/java/com/microsoft/playwright/Server.java @@ -196,6 +196,12 @@ public void handle(HttpExchange exchange) throws IOException { handler.handle(exchange); return; } + // Headless Chrome requests the favicon after a load and logs a 404 to the console of the page. + if ("/favicon.ico".equals(path)) { + exchange.sendResponseHeaders(204, -1); + exchange.close(); + return; + } if (csp.containsKey(path)) { exchange.getResponseHeaders().add("Content-Security-Policy", csp.get(path)); diff --git a/playwright/src/test/java/com/microsoft/playwright/TestClick.java b/playwright/src/test/java/com/microsoft/playwright/TestClick.java index 0396c3be1..a3841fd9d 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestClick.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestClick.java @@ -141,12 +141,7 @@ void shouldSelectTheTextByTripleClicking(Page page, Server server) { void shouldClickOffscreenButtons(Page page, Server server) { page.navigate(server.PREFIX + "/offscreenbuttons.html"); List messages = new ArrayList<>(); - // Only log messages, Chrome can log the 404 error of /favicon.ico while the test runs. - page.onConsoleMessage(message -> { - if ("log".equals(message.type())) { - messages.add(message.text()); - } - }); + page.onConsoleMessage(message -> messages.add(message.text())); for (int i = 0; i < 11; ++i) { // We might've scrolled to click a button - reset to (0, 0). page.evaluate("() => window.scrollTo(0, 0)"); diff --git a/playwright/src/test/java/com/microsoft/playwright/fixtures/TestServer.java b/playwright/src/test/java/com/microsoft/playwright/fixtures/TestServer.java index d0d0c0732..e9c09cc6d 100644 --- a/playwright/src/test/java/com/microsoft/playwright/fixtures/TestServer.java +++ b/playwright/src/test/java/com/microsoft/playwright/fixtures/TestServer.java @@ -414,6 +414,10 @@ private void onRequest(Request request, Response response) { try { if (handler != null) { handler.handle(request, response); + } else if (pathWithSearch.equals("/favicon.ico")) { + // Java: headless Chrome requests the favicon after a load and logs a 404 to the console of the page. + response.statusCode = 204; + response.end(); } else { serveFile(request, response); } diff --git a/playwright/src/test/java/com/microsoft/playwright/tests/library/TestBrowsercontextTimezoneId.java b/playwright/src/test/java/com/microsoft/playwright/tests/library/TestBrowsercontextTimezoneId.java index 877ec93f9..32d352721 100644 --- a/playwright/src/test/java/com/microsoft/playwright/tests/library/TestBrowsercontextTimezoneId.java +++ b/playwright/src/test/java/com/microsoft/playwright/tests/library/TestBrowsercontextTimezoneId.java @@ -147,8 +147,7 @@ void shouldPropagateTimezoneToWorkers(Browser browser, TestServer server) { BrowserContext context = browser.newContext(new Browser.NewContextOptions().setTimezoneId("America/Jamaica")); Page page = context.newPage(); page.navigate(server.EMPTY_PAGE); - // Java: only a log message, Chrome on Windows can log the 404 error of /favicon.ico first. - ConsoleMessage msg = page.waitForConsoleMessage(new Page.WaitForConsoleMessageOptions().setPredicate(m -> "log".equals(m.type())), () -> + ConsoleMessage msg = page.waitForConsoleMessage(() -> page.evaluate("() => new Worker(URL.createObjectURL(new Blob(['console.log(Intl.DateTimeFormat().resolvedOptions().timeZone)'], { type: 'application/javascript' })))")); assertEquals("America/Jamaica", msg.text()); context.close(); diff --git a/playwright/src/test/java/com/microsoft/playwright/tests/page/TestPageClick.java b/playwright/src/test/java/com/microsoft/playwright/tests/page/TestPageClick.java index b12acae1e..bf4849a89 100644 --- a/playwright/src/test/java/com/microsoft/playwright/tests/page/TestPageClick.java +++ b/playwright/src/test/java/com/microsoft/playwright/tests/page/TestPageClick.java @@ -223,12 +223,7 @@ void shouldSelectTheTextByTripleClicking(Page page, TestServer server) { void shouldClickOffscreenButtons(Page page, TestServer server) { page.navigate(server.PREFIX + "/offscreenbuttons.html"); List messages = new ArrayList<>(); - // Java: only log messages, Chrome can log the 404 error of /favicon.ico while the test runs. - page.onConsoleMessage(msg -> { - if ("log".equals(msg.type())) { - messages.add(msg.text()); - } - }); + page.onConsoleMessage(msg -> messages.add(msg.text())); for (int i = 0; i < 11; ++i) { // We might've scrolled to click a button - reset to (0, 0). page.evaluate("() => window.scrollTo(0, 0)"); @@ -1087,12 +1082,7 @@ void shouldFireContextmenuEventOnRightClickInCorrectOrder(Page page, TestServer " document.addEventListener('contextmenu', logEvent);\n" + "}"); List entries = new ArrayList<>(); - // Java: only log messages, Chrome can log the 404 error of /favicon.ico while the test runs. - page.onConsoleMessage(message -> { - if ("log".equals(message.type())) { - entries.add(message.text()); - } - }); + page.onConsoleMessage(message -> entries.add(message.text())); page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Click me")).click(new Locator.ClickOptions().setButton(MouseButton.RIGHT)); if (browserName == BrowserName.CHROMIUM && Env.isWindows) { expectPollToEqual(page, () -> entries, Arrays.asList("mousedown", "mouseup", "contextmenu")); From 3d1d2fd8f21e76304767c86cd87beaa42abba1c6 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Wed, 28 Oct 2026 16:09:14 -0700 Subject: [PATCH 4/4] test: read the frame URL after the evaluate in OO-029, as upstream does The frame of a nested OOPIF gets its URL from its own session, together with its execution context. Reading the URL first could give "". --- .../playwright/tests/library/chromium/TestOopif.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/playwright/src/test/java/com/microsoft/playwright/tests/library/chromium/TestOopif.java b/playwright/src/test/java/com/microsoft/playwright/tests/library/chromium/TestOopif.java index 6c2821cb7..80a8deeb7 100644 --- a/playwright/src/test/java/com/microsoft/playwright/tests/library/chromium/TestOopif.java +++ b/playwright/src/test/java/com/microsoft/playwright/tests/library/chromium/TestOopif.java @@ -555,7 +555,9 @@ void shouldReportTheWholeFrameTreeWhenConnectingOverCDPToNestedOOPIFs(BrowserTyp } private static List dumpFrames(Frame frame, String indent) { - assertEquals(frame.url(), frame.evaluate("() => '' + location.href")); + // Java: the URL is read after the evaluate, as upstream does; the frame of a new OOPIF gets it with its context. + Object href = frame.evaluate("() => '' + location.href"); + assertEquals(frame.url(), href); List result = new ArrayList<>(); result.add(indent + frame.url()); for (Frame child : frame.childFrames())