From c9d4993a2c40a5d9ec3b726c85e72cb04418b137 Mon Sep 17 00:00:00 2001 From: Develop-KIM Date: Wed, 16 Sep 2026 15:35:00 +0900 Subject: [PATCH 1/3] fix(junit): create a new Playwright when the previous launcher run closed it Surefire's rerunFailingTestsCount (and similar retry tooling) executes the launcher again in the same JVM and thread. The first run's PlaywrightRegistry has already closed every Playwright it created, but the ThreadLocal in PlaywrightExtension still holds one of them, so the rerun launches a browser on a closed connection and fails with "Playwright connection closed". Reuse the ThreadLocal instance only when it belongs to the current run's registry and create a fresh one otherwise. Fixes: https://github.com/microsoft/playwright-java/issues/1751 --- playwright/pom.xml | 5 ++ .../impl/junit/PlaywrightExtension.java | 8 ++- .../playwright/junit/RerunFixture.java | 33 ++++++++++ .../playwright/junit/TestFixturesRerun.java | 64 +++++++++++++++++++ pom.xml | 7 ++ 5 files changed, 115 insertions(+), 2 deletions(-) create mode 100644 playwright/src/test/java/com/microsoft/playwright/junit/RerunFixture.java create mode 100644 playwright/src/test/java/com/microsoft/playwright/junit/TestFixturesRerun.java diff --git a/playwright/pom.xml b/playwright/pom.xml index e97c92a24..2362d86f2 100644 --- a/playwright/pom.xml +++ b/playwright/pom.xml @@ -89,6 +89,11 @@ org.junit.jupiter junit-jupiter-engine + + org.junit.platform + junit-platform-launcher + test + org.junit.jupiter junit-jupiter-params diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/junit/PlaywrightExtension.java b/playwright/src/main/java/com/microsoft/playwright/impl/junit/PlaywrightExtension.java index d1873bb61..37ad373b8 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/junit/PlaywrightExtension.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/junit/PlaywrightExtension.java @@ -55,6 +55,9 @@ Playwright createPlaywright(Playwright.CreateOptions options) { return playwright; } + boolean owns(Playwright playwright) { + return playwrightList.contains(playwright); + } // This is a workaround for JUnit's lack of an "AfterTestRun" hook // This will be called once after all tests have completed. @@ -85,13 +88,14 @@ public Object resolveParameter(ParameterContext parameterContext, ExtensionConte * @return The Playwright that belongs to the current test. */ public static Playwright getOrCreatePlaywright(ExtensionContext extensionContext) { + PlaywrightRegistry registry = PlaywrightRegistry.getOrCreateFor(extensionContext); Playwright playwright = threadLocalPlaywright.get(); - if (playwright != null) { + // A previous launcher run on this thread (e.g. a surefire rerun) has already closed its Playwright. + if (playwright != null && registry.owns(playwright)) { return playwright; } Options options = OptionsExtension.getOptions(extensionContext); - PlaywrightRegistry registry = PlaywrightRegistry.getOrCreateFor(extensionContext); playwright = registry.createPlaywright(options.playwrightCreateOptions); threadLocalPlaywright.set(playwright); diff --git a/playwright/src/test/java/com/microsoft/playwright/junit/RerunFixture.java b/playwright/src/test/java/com/microsoft/playwright/junit/RerunFixture.java new file mode 100644 index 000000000..d03aa89e4 --- /dev/null +++ b/playwright/src/test/java/com/microsoft/playwright/junit/RerunFixture.java @@ -0,0 +1,33 @@ +/* + * 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.junit; + +import com.microsoft.playwright.Page; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +// Not picked up by surefire; TestFixturesRerun runs it through the launcher. +@UsePlaywright +public class RerunFixture { + @Test + void usesPage(Page page) { + page.setContent("rerun"); + assertEquals("rerun", page.title()); + } +} diff --git a/playwright/src/test/java/com/microsoft/playwright/junit/TestFixturesRerun.java b/playwright/src/test/java/com/microsoft/playwright/junit/TestFixturesRerun.java new file mode 100644 index 000000000..99f17106c --- /dev/null +++ b/playwright/src/test/java/com/microsoft/playwright/junit/TestFixturesRerun.java @@ -0,0 +1,64 @@ +/* + * 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.junit; + +import org.junit.jupiter.api.Test; +import org.junit.platform.launcher.LauncherDiscoveryRequest; +import org.junit.platform.launcher.core.LauncherFactory; +import org.junit.platform.launcher.listeners.SummaryGeneratingListener; +import org.junit.platform.launcher.listeners.TestExecutionSummary; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; +import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; + +public class TestFixturesRerun { + // Surefire's rerunFailingTestsCount runs the launcher again on the same thread after + // the first run has already closed every Playwright it created. + @Test + void shouldCreateNewPlaywrightForEachLauncherRun() throws Exception { + ExecutorService thread = Executors.newSingleThreadExecutor(); + try { + for (int run = 1; run <= 2; run++) { + Future summary = thread.submit(() -> runOnLauncher(RerunFixture.class)); + assertEquals(1, summary.get().getTestsSucceededCount(), "run " + run + ": " + describeFailures(summary.get())); + } + } finally { + thread.shutdownNow(); + } + } + + private static String describeFailures(TestExecutionSummary summary) { + StringWriter out = new StringWriter(); + summary.printFailuresTo(new PrintWriter(out)); + return out.toString(); + } + + private static TestExecutionSummary runOnLauncher(Class testClass) { + LauncherDiscoveryRequest request = request().selectors(selectClass(testClass)).build(); + SummaryGeneratingListener listener = new SummaryGeneratingListener(); + LauncherFactory.create().execute(request, listener); + return listener.getSummary(); + } +} diff --git a/pom.xml b/pom.xml index 6e33592e8..4a4bbfa2e 100644 --- a/pom.xml +++ b/pom.xml @@ -57,6 +57,13 @@ + + org.junit + junit-bom + ${junit.version} + pom + import + com.microsoft.playwright driver From 3d8efa03413e1f9821d5d793444768fa982c6b7f Mon Sep 17 00:00:00 2001 From: Develop-KIM Date: Fri, 18 Sep 2026 09:03:07 +0900 Subject: [PATCH 2/3] test(docker): declare junit-platform-launcher in the local installation project The Docker job copies playwright/src/test into tools/test-local-installation and compiles it with that project's own pom, which does not know the junit-platform-launcher dependency TestFixturesRerun needs. --- tools/test-local-installation/pom.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tools/test-local-installation/pom.xml b/tools/test-local-installation/pom.xml index 88fb2c63a..987dcb4db 100644 --- a/tools/test-local-installation/pom.xml +++ b/tools/test-local-installation/pom.xml @@ -11,6 +11,7 @@ 1.8 2.11.0 5.11.0 + 1.11.0 UTF-8 1.5.7 @@ -44,6 +45,12 @@ ${junit.version} test + + org.junit.platform + junit-platform-launcher + ${junit.platform.version} + test + org.java-websocket Java-WebSocket From d9a82a6f793c7e57e4439df7ef90e15b5f2b7a9a Mon Sep 17 00:00:00 2001 From: Develop-KIM Date: Mon, 21 Sep 2026 10:44:47 +0900 Subject: [PATCH 3/3] fix(junit): close the registry on JUnit < 5.13 and drop the browser from the rerun fixture PlaywrightRegistry implements ExtensionContext.Store.CloseableResource again next to AutoCloseable. JUnit 5.9-5.12 only close store values through CloseableResource, so without it the ownership check would create a new Playwright on every rerun while the previous registry is never closed. 5.13+ closes the registry once through AutoCloseable. RerunFixture resolves an APIRequestContext instead of a Page: resolving the parameter already makes the driver round-trip that fails on a closed Playwright, and no browser is launched. tools/test-local-installation imports junit-bom so the launcher version follows junit.version instead of a second hand-maintained property. --- .../impl/junit/PlaywrightExtension.java | 8 ++++++-- .../microsoft/playwright/junit/RerunFixture.java | 11 ++++++----- tools/test-local-installation/pom.xml | 15 +++++++++++---- 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/junit/PlaywrightExtension.java b/playwright/src/main/java/com/microsoft/playwright/impl/junit/PlaywrightExtension.java index 37ad373b8..81ebe6eaf 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/junit/PlaywrightExtension.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/junit/PlaywrightExtension.java @@ -36,7 +36,10 @@ public class PlaywrightExtension implements ParameterResolver { // There should be at most one instance of PlaywrightRegistry per test run, it keeps // track of all created Playwright instances and calls `close()` on each of them after // the tests finished. - static class PlaywrightRegistry implements AutoCloseable { + // CloseableResource is kept alongside AutoCloseable because JUnit < 5.13 only closes + // store values that implement it; newer versions close it once through AutoCloseable. + @SuppressWarnings("deprecation") + static class PlaywrightRegistry implements AutoCloseable, ExtensionContext.Store.CloseableResource { private final List playwrightList = Collections.synchronizedList(new ArrayList<>()); static synchronized PlaywrightRegistry getOrCreateFor(ExtensionContext extensionContext) { @@ -90,7 +93,8 @@ public Object resolveParameter(ParameterContext parameterContext, ExtensionConte public static Playwright getOrCreatePlaywright(ExtensionContext extensionContext) { PlaywrightRegistry registry = PlaywrightRegistry.getOrCreateFor(extensionContext); Playwright playwright = threadLocalPlaywright.get(); - // A previous launcher run on this thread (e.g. a surefire rerun) has already closed its Playwright. + // Reuse only while the current run's registry still owns it: a previous launcher run on this + // thread (e.g. a surefire rerun) has already closed the Playwright it left in the thread local. if (playwright != null && registry.owns(playwright)) { return playwright; } diff --git a/playwright/src/test/java/com/microsoft/playwright/junit/RerunFixture.java b/playwright/src/test/java/com/microsoft/playwright/junit/RerunFixture.java index d03aa89e4..3605798f4 100644 --- a/playwright/src/test/java/com/microsoft/playwright/junit/RerunFixture.java +++ b/playwright/src/test/java/com/microsoft/playwright/junit/RerunFixture.java @@ -17,17 +17,18 @@ package com.microsoft.playwright.junit; -import com.microsoft.playwright.Page; +import com.microsoft.playwright.APIRequestContext; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; // Not picked up by surefire; TestFixturesRerun runs it through the launcher. @UsePlaywright public class RerunFixture { + // Resolving the parameter already makes a driver round-trip (request().newContext()), + // which is what fails when the Playwright left on the thread has been closed. @Test - void usesPage(Page page) { - page.setContent("rerun"); - assertEquals("rerun", page.title()); + void usesAPIRequestContext(APIRequestContext request) { + assertNotNull(request); } } diff --git a/tools/test-local-installation/pom.xml b/tools/test-local-installation/pom.xml index 987dcb4db..6ce2ed4fa 100644 --- a/tools/test-local-installation/pom.xml +++ b/tools/test-local-installation/pom.xml @@ -11,10 +11,20 @@ 1.8 2.11.0 5.11.0 - 1.11.0 UTF-8 1.5.7 + + + + org.junit + junit-bom + ${junit.version} + pom + import + + + com.microsoft.playwright @@ -36,19 +46,16 @@ org.junit.jupiter junit-jupiter-engine - ${junit.version} test org.junit.jupiter junit-jupiter-params - ${junit.version} test org.junit.platform junit-platform-launcher - ${junit.platform.version} test