diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/junit/BrowserContextExtension.java b/playwright/src/main/java/com/microsoft/playwright/impl/junit/BrowserContextExtension.java index e27b1c448..695fe69cb 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/junit/BrowserContextExtension.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/junit/BrowserContextExtension.java @@ -22,9 +22,12 @@ import org.junit.jupiter.api.extension.*; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import static com.microsoft.playwright.impl.junit.ExtensionUtils.*; import static com.microsoft.playwright.impl.junit.PageExtension.cleanUpPage; @@ -130,9 +133,44 @@ private static Path getOutputPath(ExtensionContext extensionContext) { String outputDirName = extensionContext.getRequiredTestClass().getName() + "." + extensionContext.getRequiredTestMethod().getName() + "-" + browserType.name(); + if (isTestTemplateInvocation(extensionContext)) { + // Each invocation of a parameterized or repeated test gets its own directory. + outputDirName += "-" + trimLongString(sanitizeForFilePath(extensionContext.getDisplayName()), 60); + } return defaultOutputPath.resolve(outputDirName); } + private static boolean isTestTemplateInvocation(ExtensionContext extensionContext) { + return extensionContext.getParent().flatMap(ExtensionContext::getTestMethod).isPresent(); + } + + private static String sanitizeForFilePath(String s) { + return s.replaceAll("[\\x00-\\x2C\\x2E-\\x2F\\x3A-\\x40\\x5B-\\x60\\x7B-\\x7F]+", "-"); + } + + private static String trimLongString(String s, int length) { + if (s.length() <= length) { + return s; + } + String middle = "-" + calculateSha1(s).substring(0, 5) + "-"; + int start = (length - middle.length()) / 2; + int end = length - middle.length() - start; + return s.substring(0, start) + middle + s.substring(s.length() - end); + } + + private static String calculateSha1(String s) { + try { + byte[] digest = MessageDigest.getInstance("SHA-1").digest(s.getBytes(StandardCharsets.UTF_8)); + StringBuilder hex = new StringBuilder(); + for (byte b : digest) { + hex.append(String.format("%02x", b)); + } + return hex.toString(); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } + private static Path getDefaultOutputPath(ExtensionContext extensionContext) { Options options = OptionsExtension.getOptions(extensionContext); Path outputPath = options.outputDir; diff --git a/playwright/src/test/java/com/microsoft/playwright/junit/TestFixtureTraceOutputDir.java b/playwright/src/test/java/com/microsoft/playwright/junit/TestFixtureTraceOutputDir.java new file mode 100644 index 000000000..041f0c0c2 --- /dev/null +++ b/playwright/src/test/java/com/microsoft/playwright/junit/TestFixtureTraceOutputDir.java @@ -0,0 +1,88 @@ +/* + * 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.Browser; +import com.microsoft.playwright.Page; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.RepeatedTest; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@FixtureTest +@UsePlaywright(TestFixtureTraceOutputDir.TraceOptions.class) +public class TestFixtureTraceOutputDir { + @TempDir + static Path outputDir; + private static String browserName; + + public static class TraceOptions implements OptionsFactory { + @Override + public Options getOptions() { + return new Options().setTrace(Options.Trace.ON).setOutputDir(outputDir); + } + } + + @Test + void shouldSaveTrace(Browser browser, Page page) { + browserName = browser.browserType().name(); + page.setContent("
test
"); + } + + @ParameterizedTest(name = "[{index}] {0}") + @ValueSource(strings = {"foo", "bar"}) + void shouldSaveTracePerParameterizedInvocation(String value, Page page) { + page.setContent("
" + value + "
"); + } + + @RepeatedTest(2) + void shouldSaveTracePerRepetition(Page page) { + page.setContent("
repeated
"); + } + + @AfterAll + static void shouldWriteTraceOfEachInvocationToSeparateDir() throws IOException { + String prefix = TestFixtureTraceOutputDir.class.getName() + "."; + List expected = Stream.of( + "shouldSaveTrace-" + browserName, + "shouldSaveTracePerParameterizedInvocation-" + browserName + "--1-foo", + "shouldSaveTracePerParameterizedInvocation-" + browserName + "--2-bar", + "shouldSaveTracePerRepetition-" + browserName + "-repetition-1-of-2", + "shouldSaveTracePerRepetition-" + browserName + "-repetition-2-of-2" + ).map(name -> prefix + name).sorted().collect(Collectors.toList()); + List actual; + try (Stream dirs = Files.list(outputDir)) { + actual = dirs.map(dir -> dir.getFileName().toString()).sorted().collect(Collectors.toList()); + } + assertEquals(expected, actual); + for (String dir : actual) { + assertTrue(Files.exists(outputDir.resolve(dir).resolve("trace.zip")), dir); + } + } +}