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..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) {
@@ -55,6 +58,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 +91,15 @@ 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) {
+ // 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;
}
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..3605798f4
--- /dev/null
+++ b/playwright/src/test/java/com/microsoft/playwright/junit/RerunFixture.java
@@ -0,0 +1,34 @@
+/*
+ * 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.APIRequestContext;
+import org.junit.jupiter.api.Test;
+
+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 usesAPIRequestContext(APIRequestContext request) {
+ assertNotNull(request);
+ }
+}
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
diff --git a/tools/test-local-installation/pom.xml b/tools/test-local-installation/pom.xml
index 88fb2c63a..6ce2ed4fa 100644
--- a/tools/test-local-installation/pom.xml
+++ b/tools/test-local-installation/pom.xml
@@ -14,6 +14,17 @@
UTF-8
1.5.7
+
+
+
+ org.junit
+ junit-bom
+ ${junit.version}
+ pom
+ import
+
+
+
com.microsoft.playwright
@@ -35,13 +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
test