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