From c18071db6e677badbef1363efba75a79ed0572ac Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Fri, 29 Nov 2024 19:31:39 +0100 Subject: [PATCH 1/6] Relativize paths to target HTML file --- .../core/htmlreport/CoreContributor.java | 27 +-- .../htmlreport/DefaultHtmlReportWriter.java | 170 ++++++++++-------- .../core/htmlreport/GitContributor.java | 6 +- .../core/htmlreport/JavaContributor.java | 8 +- .../tooling/spi/htmlreport/Contributor.java | 22 ++- 5 files changed, 141 insertions(+), 92 deletions(-) diff --git a/tooling-core/src/main/java/org/opentest4j/reporting/tooling/core/htmlreport/CoreContributor.java b/tooling-core/src/main/java/org/opentest4j/reporting/tooling/core/htmlreport/CoreContributor.java index be5fd2e1..0fe415f1 100644 --- a/tooling-core/src/main/java/org/opentest4j/reporting/tooling/core/htmlreport/CoreContributor.java +++ b/tooling-core/src/main/java/org/opentest4j/reporting/tooling/core/htmlreport/CoreContributor.java @@ -48,19 +48,19 @@ public class CoreContributor implements Contributor { @Override - public List
contributeSectionsForExecution(Element executionElement) { + public List
contributeSectionsForExecution(Context context) { var sections = new ArrayList
(); - createInfrastructureSection(executionElement).ifPresent(sections::add); + createInfrastructureSection(context.element()).ifPresent(sections::add); return sections; } @Override - public List
contributeSectionsForTestNode(Element testNodeElement) { + public List
contributeSectionsForTestNode(Context context) { var sections = new ArrayList
(); - createTagsSection(testNodeElement).ifPresent(sections::add); - createSourcesSection(testNodeElement).ifPresent(sections::add); - createReasonSection(testNodeElement).ifPresent(sections::add); - createAttachmentsSection(testNodeElement).ifPresent(sections::add); + createTagsSection(context.element()).ifPresent(sections::add); + createSourcesSection(context.element()).ifPresent(sections::add); + createReasonSection(context.element()).ifPresent(sections::add); + createAttachmentsSection(context).ifPresent(sections::add); return sections; } @@ -134,8 +134,8 @@ private static Optional
createReasonSection(Element element) { return Optional.of(Section.builder().title("Reason").order(20).addBlock(paragraph).build()); } - private static Optional
createAttachmentsSection(Element element) { - var children = $(element).children("attachments").children().get(); + private static Optional
createAttachmentsSection(Context context) { + var children = $(context.element()).children("attachments").children().get(); if (children.isEmpty()) { return Optional.empty(); } @@ -143,7 +143,14 @@ private static Optional
createAttachmentsSection(Element element) { var subsections = Subsections.builder(); children.stream().map(child -> { var attributes = KeyValuePairs.builder(); - stream(child.getAttributes()).forEach(it -> attributes.putContent(it.getNodeName(), it.getNodeValue())); + if ("file".equals(child.getLocalName())) { + attributes.putContent("time", $(child).attr("time")); + var originalPath = context.sourceXmlFile().getParent().resolve($(child).attr("path")); + var relativeToTarget = context.targetHtmlFile().getParent().relativize(originalPath); + attributes.putContent("path", "link:" + relativeToTarget); + } else { + stream(child.getAttributes()).forEach(it -> attributes.putContent(it.getNodeName(), it.getNodeValue())); + } if ("data".equals(child.getLocalName())) { $(child).children("entry").forEach(entry -> { var key = entry.getAttribute("key"); diff --git a/tooling-core/src/main/java/org/opentest4j/reporting/tooling/core/htmlreport/DefaultHtmlReportWriter.java b/tooling-core/src/main/java/org/opentest4j/reporting/tooling/core/htmlreport/DefaultHtmlReportWriter.java index 932d123d..25ca82b6 100644 --- a/tooling-core/src/main/java/org/opentest4j/reporting/tooling/core/htmlreport/DefaultHtmlReportWriter.java +++ b/tooling-core/src/main/java/org/opentest4j/reporting/tooling/core/htmlreport/DefaultHtmlReportWriter.java @@ -52,6 +52,7 @@ import java.util.ServiceLoader; import java.util.Set; import java.util.function.BiFunction; +import java.util.function.Function; import static java.util.Comparator.comparing; import static java.util.Objects.requireNonNull; @@ -70,24 +71,25 @@ public class DefaultHtmlReportWriter implements HtmlReportWriter { @Override public void writeHtmlReport(List xmlFiles, Path htmlFile) throws Exception { + var javaScript = toJavaScript(extractData(xmlFiles, htmlFile)); + var content = readTemplate().replace("", + ""); + Files.writeString(htmlFile, content); + } + private List extractData(List xmlFiles, Path htmlFile) throws Exception { var idGenerator = new IdGenerator(); var executions = new ArrayList(); for (Path xmlFile : xmlFiles) { var rootElement = parseDom(xmlFile); var name = String.format(xmlFile.getFileName().toString()); - executions.add(extractData(idGenerator, rootElement, name)); + executions.add(extractData(idGenerator, rootElement, name, element -> new DefaultContext(element, xmlFile, htmlFile))); } - - var javaScript = toJavascript(executions); - - var content = readTemplate().replace("", - ""); - Files.writeString(htmlFile, content); + return executions; } - private static String toJavascript(List executions) { + private static String toJavaScript(List executions) { var gson = new GsonBuilder() // .registerTypeHierarchyAdapter(Section.class, (JsonSerializer
) (section, typeOfSrc, context) -> { @@ -116,13 +118,13 @@ private static JsonSerializer> blockSerializer(String type) { }; } - private Execution extractData(IdGenerator idGenerator, Element rootElement, String name) { + private Execution extractData(IdGenerator idGenerator, Element rootElement, String name, Function contextCreator) { var execution = new Execution(idGenerator.next(), name); - addSections(rootElement, execution); + addSections(contextCreator.apply(rootElement), execution); for (Node child = rootElement.getFirstChild(); child != null; child = child.getNextSibling()) { if (child instanceof Element && matches(ROOT_ELEMENT, (Element) child)) { - var root = visitNode((Element) child, idGenerator, new ArrayDeque<>(), execution); + var root = visitNode((Element) child, idGenerator, new ArrayDeque<>(), execution, contextCreator); execution.durationMillis += root.durationMillis; execution.roots.add(root.id); } @@ -130,57 +132,7 @@ private Execution extractData(IdGenerator idGenerator, Element rootElement, Stri return execution; } - private static class Execution { - - public final String id; - public final String name; - public long durationMillis; - - public final List
sections = new ArrayList<>(); - - public final List roots = new ArrayList<>(); - public final Map children = new LinkedHashMap<>(); - public final List testNodes = new ArrayList<>(); - - Execution(String id, String name) { - this.id = id; - this.name = name; - } - } - - private static class ChildMetadata { - - public final List ids = new ArrayList<>(); - public final Set childStatuses = new HashSet<>(); - - private void addChild(String childId, String status) { - ids.add(childId); - if (status != null) { - childStatuses.add(status); - } - } - - } - - private static class TestNode { - - public final String id; - public final String name; - public final long durationMillis; - - public final String status; - - public final List
sections = new ArrayList<>(); - - TestNode(String id, String name, long durationMillis, String status) { - this.id = id; - this.name = name; - this.durationMillis = durationMillis; - this.status = status; - } - } - - private TestNode visitNode(Element node, IdGenerator idGenerator, Deque parentIds, Execution execution) { + private TestNode visitNode(Element node, IdGenerator idGenerator, Deque parentIds, Execution execution, Function contextCreator) { String currentId = idGenerator.next(); String status = stream(node.getChildNodes()) // @@ -191,7 +143,7 @@ private TestNode visitNode(Element node, IdGenerator idGenerator, Deque var testNode = new TestNode(currentId, node.getAttribute("name"), duration.toMillis(), status); execution.testNodes.add(testNode); - addSections(node, testNode); + addSections(contextCreator.apply(node), testNode); ChildMetadata parentChildMetadata = null; if (!parentIds.isEmpty()) { @@ -202,7 +154,7 @@ private TestNode visitNode(Element node, IdGenerator idGenerator, Deque parentIds.push(currentId); for (Node child = node.getFirstChild(); child != null; child = child.getNextSibling()) { if (child instanceof Element && matches(CHILD_ELEMENT, (Element) child)) { - visitNode((Element) child, idGenerator, parentIds, execution); + visitNode((Element) child, idGenerator, parentIds, execution, contextCreator); } } parentIds.pop(); @@ -214,17 +166,17 @@ private TestNode visitNode(Element node, IdGenerator idGenerator, Deque return testNode; } - private void addSections(Element element, Execution execution) { - contributeSections(element, Contributor::contributeSectionsForExecution, execution.sections); + private void addSections(Contributor.Context context, Execution execution) { + contributeSections(context, Contributor::contributeSectionsForExecution, execution.sections); } - private void addSections(Element element, TestNode node) { - contributeSections(element, Contributor::contributeSectionsForTestNode, node.sections); + private void addSections(Contributor.Context context, TestNode node) { + contributeSections(context, Contributor::contributeSectionsForTestNode, node.sections); } - private void contributeSections(Element element, BiFunction> call, + private void contributeSections(Contributor.Context context, BiFunction> call, List
sections) { - contributors.stream().flatMap(it -> call.apply(it.get(), element).stream()).sorted( + contributors.stream().flatMap(it -> call.apply(it.get(), context).stream()).sorted( comparing(Section::getOrder)).forEach(sections::add); } @@ -256,4 +208,82 @@ private String readTemplate() throws IOException { return new String(requireNonNull(template).readAllBytes()); } } + + private static class Execution { + + public final String id; + public final String name; + public long durationMillis; + + public final List
sections = new ArrayList<>(); + + public final List roots = new ArrayList<>(); + public final Map children = new LinkedHashMap<>(); + public final List testNodes = new ArrayList<>(); + + Execution(String id, String name) { + this.id = id; + this.name = name; + } + } + + private static class ChildMetadata { + + public final List ids = new ArrayList<>(); + public final Set childStatuses = new HashSet<>(); + + private void addChild(String childId, String status) { + ids.add(childId); + if (status != null) { + childStatuses.add(status); + } + } + + } + + private static class TestNode { + + public final String id; + public final String name; + public final long durationMillis; + + public final String status; + + public final List
sections = new ArrayList<>(); + + TestNode(String id, String name, long durationMillis, String status) { + this.id = id; + this.name = name; + this.durationMillis = durationMillis; + this.status = status; + } + } + + private static class DefaultContext implements Contributor.Context { + + private final Element element; + private final Path sourceXmlFile; + private final Path targetHtmlFile; + + DefaultContext(Element element, Path sourceXmlFile, Path targetHtmlFile) { + this.element = element; + this.sourceXmlFile = sourceXmlFile; + this.targetHtmlFile = targetHtmlFile; + } + + @Override + public Element element() { + return element; + } + + @Override + public Path sourceXmlFile() { + return sourceXmlFile; + } + + @Override + public Path targetHtmlFile() { + return targetHtmlFile; + } + } } diff --git a/tooling-core/src/main/java/org/opentest4j/reporting/tooling/core/htmlreport/GitContributor.java b/tooling-core/src/main/java/org/opentest4j/reporting/tooling/core/htmlreport/GitContributor.java index ff8e9166..6e680f61 100644 --- a/tooling-core/src/main/java/org/opentest4j/reporting/tooling/core/htmlreport/GitContributor.java +++ b/tooling-core/src/main/java/org/opentest4j/reporting/tooling/core/htmlreport/GitContributor.java @@ -27,7 +27,6 @@ import org.opentest4j.reporting.tooling.spi.htmlreport.PreFormattedOutput; import org.opentest4j.reporting.tooling.spi.htmlreport.Section; import org.opentest4j.reporting.tooling.spi.htmlreport.Subsections; -import org.w3c.dom.Element; import java.util.ArrayList; import java.util.List; @@ -43,12 +42,11 @@ public class GitContributor implements Contributor { @Override - public List
contributeSectionsForExecution(Element executionElement) { + public List
contributeSectionsForExecution(Context context) { var sections = new ArrayList
(); - var infrastructure = $(executionElement).child("infrastructure"); + var infrastructure = $(context.element()).child("infrastructure"); createGitSection(infrastructure).ifPresent(sections::add); return sections; - } private static Optional
createGitSection(Match infrastructure) { diff --git a/tooling-core/src/main/java/org/opentest4j/reporting/tooling/core/htmlreport/JavaContributor.java b/tooling-core/src/main/java/org/opentest4j/reporting/tooling/core/htmlreport/JavaContributor.java index 1c37f390..04c53927 100644 --- a/tooling-core/src/main/java/org/opentest4j/reporting/tooling/core/htmlreport/JavaContributor.java +++ b/tooling-core/src/main/java/org/opentest4j/reporting/tooling/core/htmlreport/JavaContributor.java @@ -43,17 +43,17 @@ public class JavaContributor implements Contributor { @Override - public List
contributeSectionsForExecution(Element executionElement) { + public List
contributeSectionsForExecution(Context context) { var sections = new ArrayList
(); - var infrastructure = $(executionElement).child("infrastructure"); + var infrastructure = $(context.element()).child("infrastructure"); createJvmSection(infrastructure).ifPresent(sections::add); return sections; } @Override - public List
contributeSectionsForTestNode(Element testNodeElement) { + public List
contributeSectionsForTestNode(Context context) { var sections = new ArrayList
(); - createResultSection(testNodeElement).ifPresent(sections::add); + createResultSection(context.element()).ifPresent(sections::add); return sections; } diff --git a/tooling-spi/src/main/java/org/opentest4j/reporting/tooling/spi/htmlreport/Contributor.java b/tooling-spi/src/main/java/org/opentest4j/reporting/tooling/spi/htmlreport/Contributor.java index ca3529f3..979b0dca 100644 --- a/tooling-spi/src/main/java/org/opentest4j/reporting/tooling/spi/htmlreport/Contributor.java +++ b/tooling-spi/src/main/java/org/opentest4j/reporting/tooling/spi/htmlreport/Contributor.java @@ -18,6 +18,7 @@ import org.w3c.dom.Element; +import java.nio.file.Path; import java.util.List; import static java.util.Collections.emptyList; @@ -31,21 +32,34 @@ public interface Contributor { /** * Contribute additional sections for the given execution element. * - * @param executionElement the DOM element representing the execution + * @param context the DOM element representing the execution * @return additional sections to be rendered */ - default List
contributeSectionsForExecution(Element executionElement) { + default List
contributeSectionsForExecution(Context context) { return emptyList(); } /** * Contribute additional sections for the given test node element. * - * @param testNodeElement the DOM element representing the test node + * @param context the DOM element representing the test node * @return additional sections to be rendered */ - default List
contributeSectionsForTestNode(Element testNodeElement) { + default List
contributeSectionsForTestNode(Context context) { return emptyList(); } + /** + * The context to contribute sections for. + */ + interface Context { + + Element element(); + + Path sourceXmlFile(); + + Path targetHtmlFile(); + + } + } From 11104c5bb8dcb46c56706afdcc970fda20de52f8 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Fri, 29 Nov 2024 19:31:59 +0100 Subject: [PATCH 2/6] Render links for key-value pairs --- html-report/src/components/Block.vue | 6 +++- .../core/htmlreport/CoreContributor.java | 3 +- .../htmlreport/DefaultHtmlReportWriter.java | 31 ++++++++++--------- .../tooling/spi/htmlreport/Contributor.java | 9 ++++++ 4 files changed, 33 insertions(+), 16 deletions(-) diff --git a/html-report/src/components/Block.vue b/html-report/src/components/Block.vue index 23b55f0c..d0361f42 100644 --- a/html-report/src/components/Block.vue +++ b/html-report/src/components/Block.vue @@ -12,7 +12,11 @@ defineProps<{ block: BlockData, depth: number }>() 'border-b': index < Object.entries(block.content).length - 1 }" class="border-neutral-200 dark:border-neutral-700 w-full"> {{ pair[0] }} - {{ pair[1] }} + + + {{ (pair[1] as string).substring(5) }} + + {{ pair[1] }} diff --git a/tooling-core/src/main/java/org/opentest4j/reporting/tooling/core/htmlreport/CoreContributor.java b/tooling-core/src/main/java/org/opentest4j/reporting/tooling/core/htmlreport/CoreContributor.java index 0fe415f1..d9ef8c07 100644 --- a/tooling-core/src/main/java/org/opentest4j/reporting/tooling/core/htmlreport/CoreContributor.java +++ b/tooling-core/src/main/java/org/opentest4j/reporting/tooling/core/htmlreport/CoreContributor.java @@ -148,7 +148,8 @@ private static Optional
createAttachmentsSection(Context context) { var originalPath = context.sourceXmlFile().getParent().resolve($(child).attr("path")); var relativeToTarget = context.targetHtmlFile().getParent().relativize(originalPath); attributes.putContent("path", "link:" + relativeToTarget); - } else { + } + else { stream(child.getAttributes()).forEach(it -> attributes.putContent(it.getNodeName(), it.getNodeValue())); } if ("data".equals(child.getLocalName())) { diff --git a/tooling-core/src/main/java/org/opentest4j/reporting/tooling/core/htmlreport/DefaultHtmlReportWriter.java b/tooling-core/src/main/java/org/opentest4j/reporting/tooling/core/htmlreport/DefaultHtmlReportWriter.java index 25ca82b6..2dbf6150 100644 --- a/tooling-core/src/main/java/org/opentest4j/reporting/tooling/core/htmlreport/DefaultHtmlReportWriter.java +++ b/tooling-core/src/main/java/org/opentest4j/reporting/tooling/core/htmlreport/DefaultHtmlReportWriter.java @@ -71,7 +71,7 @@ public class DefaultHtmlReportWriter implements HtmlReportWriter { @Override public void writeHtmlReport(List xmlFiles, Path htmlFile) throws Exception { - var javaScript = toJavaScript(extractData(xmlFiles, htmlFile)); + var javaScript = toJavaScript(extractData(xmlFiles, htmlFile)); var content = readTemplate().replace("", ""); Files.writeString(htmlFile, content); @@ -84,7 +84,8 @@ private List extractData(List xmlFiles, Path htmlFile) throws E for (Path xmlFile : xmlFiles) { var rootElement = parseDom(xmlFile); var name = String.format(xmlFile.getFileName().toString()); - executions.add(extractData(idGenerator, rootElement, name, element -> new DefaultContext(element, xmlFile, htmlFile))); + executions.add( + extractData(idGenerator, rootElement, name, element -> new DefaultContext(element, xmlFile, htmlFile))); } return executions; } @@ -118,7 +119,8 @@ private static JsonSerializer> blockSerializer(String type) { }; } - private Execution extractData(IdGenerator idGenerator, Element rootElement, String name, Function contextCreator) { + private Execution extractData(IdGenerator idGenerator, Element rootElement, String name, + Function contextCreator) { var execution = new Execution(idGenerator.next(), name); addSections(contextCreator.apply(rootElement), execution); @@ -132,7 +134,8 @@ private Execution extractData(IdGenerator idGenerator, Element rootElement, Stri return execution; } - private TestNode visitNode(Element node, IdGenerator idGenerator, Deque parentIds, Execution execution, Function contextCreator) { + private TestNode visitNode(Element node, IdGenerator idGenerator, Deque parentIds, Execution execution, + Function contextCreator) { String currentId = idGenerator.next(); String status = stream(node.getChildNodes()) // @@ -174,8 +177,8 @@ private void addSections(Contributor.Context context, TestNode node) { contributeSections(context, Contributor::contributeSectionsForTestNode, node.sections); } - private void contributeSections(Contributor.Context context, BiFunction> call, - List
sections) { + private void contributeSections(Contributor.Context context, + BiFunction> call, List
sections) { contributors.stream().flatMap(it -> call.apply(it.get(), context).stream()).sorted( comparing(Section::getOrder)).forEach(sections::add); } @@ -261,15 +264,15 @@ private static class TestNode { private static class DefaultContext implements Contributor.Context { - private final Element element; - private final Path sourceXmlFile; - private final Path targetHtmlFile; + private final Element element; + private final Path sourceXmlFile; + private final Path targetHtmlFile; - DefaultContext(Element element, Path sourceXmlFile, Path targetHtmlFile) { - this.element = element; - this.sourceXmlFile = sourceXmlFile; - this.targetHtmlFile = targetHtmlFile; - } + DefaultContext(Element element, Path sourceXmlFile, Path targetHtmlFile) { + this.element = element; + this.sourceXmlFile = sourceXmlFile; + this.targetHtmlFile = targetHtmlFile; + } @Override public Element element() { diff --git a/tooling-spi/src/main/java/org/opentest4j/reporting/tooling/spi/htmlreport/Contributor.java b/tooling-spi/src/main/java/org/opentest4j/reporting/tooling/spi/htmlreport/Contributor.java index 979b0dca..96e58d9f 100644 --- a/tooling-spi/src/main/java/org/opentest4j/reporting/tooling/spi/htmlreport/Contributor.java +++ b/tooling-spi/src/main/java/org/opentest4j/reporting/tooling/spi/htmlreport/Contributor.java @@ -54,10 +54,19 @@ default List
contributeSectionsForTestNode(Context context) { */ interface Context { + /** + * {@return the DOM element representing the execution or test node} + */ Element element(); + /** + * {@return the path to the source XML file} + */ Path sourceXmlFile(); + /** + * {@return the path to the target HTML file} + */ Path targetHtmlFile(); } From be9ee1fb61510c687f5c0bce3411fc498b11dff8 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Sun, 1 Dec 2024 16:48:11 +0100 Subject: [PATCH 3/6] Add browser test for opening attachments --- sample-project/build.gradle.kts | 6 ++- .../java/org/example/KitchenSinkTests.java | 3 +- tooling-core/build.gradle.kts | 12 +++--- .../DefaultHtmlReportWriterTests.java | 39 ++++++++++++++----- 4 files changed, 42 insertions(+), 18 deletions(-) diff --git a/sample-project/build.gradle.kts b/sample-project/build.gradle.kts index 83be3a5b..f39856c8 100644 --- a/sample-project/build.gradle.kts +++ b/sample-project/build.gradle.kts @@ -61,9 +61,11 @@ tasks { outputs.cacheIf { true } } - configurations.consumable("htmlReport") { - outgoing.artifact(generateHtmlReport.map { it.outputs.files.singleFile }) + configurations.consumable("xmlReport") { attributes { + outgoing.artifact(provider { eventXmlFiles.singleFile }) { + builtBy(test) + } attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(LibraryElements.RESOURCES)) } } diff --git a/sample-project/src/test/java/org/example/KitchenSinkTests.java b/sample-project/src/test/java/org/example/KitchenSinkTests.java index 3698c83e..1b17f4c6 100644 --- a/sample-project/src/test/java/org/example/KitchenSinkTests.java +++ b/sample-project/src/test/java/org/example/KitchenSinkTests.java @@ -34,7 +34,6 @@ static void beforeAll(TestReporter reporter) { @Order(1) void successful(TestReporter reporter) { reporter.publishEntry("✅"); - reporter.publishFile("test.txt", file -> Files.write(file, singletonList("Hello, World!"))); } @Test @@ -63,6 +62,8 @@ void failed(TestReporter reporter) { map.put("baz", "qux"); reporter.publishEntry(map); + reporter.publishFile("test.txt", file -> Files.write(file, singletonList("Hello, World!"))); + assertEquals("foo", "bar"); } } diff --git a/tooling-core/build.gradle.kts b/tooling-core/build.gradle.kts index 16275273..0a8c1292 100644 --- a/tooling-core/build.gradle.kts +++ b/tooling-core/build.gradle.kts @@ -3,7 +3,7 @@ plugins { } val htmlReportTemplate = configurations.dependencyScope("htmlReportTemplate") -val sampleHtmlReport = configurations.dependencyScope("sampleHtmlReport") +val sampleXmlReport = configurations.dependencyScope("sampleXmlReport") dependencies { api(projects.schema) @@ -19,7 +19,7 @@ dependencies { testCompileOnly(libs.jetbrains.annotations) htmlReportTemplate(projects.htmlReport) - sampleHtmlReport(project(mapOf("path" to projects.sampleProject.identityPath, "configuration" to "htmlReport"))) + sampleXmlReport(project(mapOf("path" to projects.sampleProject.identityPath, "configuration" to "xmlReport"))) } tasks.compileJava { @@ -34,14 +34,14 @@ val installPlaywrightDeps by tasks.registering { doFirst(playwrightInstallationAction) } -val sampleHtmlReportFiles = configurations.resolvable("sampleHtmlReportFiles") { - extendsFrom(sampleHtmlReport.get()) +val sampleXmlReportFiles = configurations.resolvable("sampleXmlReportFiles") { + extendsFrom(sampleXmlReport.get()) } tasks.test { - inputs.files(sampleHtmlReportFiles).withPathSensitivity(PathSensitivity.NONE) + inputs.files(sampleXmlReportFiles).withPathSensitivity(PathSensitivity.NONE) jvmArgumentProviders.add(CommandLineArgumentProvider { - listOf("-DsampleHtmlReport=${sampleHtmlReportFiles.get().singleFile.absolutePath}") + listOf("-DsampleXmlReport=${sampleXmlReportFiles.get().singleFile.absolutePath}") }) if (System.getenv("CI") != null) { doFirst(playwrightInstallationAction) diff --git a/tooling-core/src/test/java/org/opentest4j/reporting/tooling/core/htmlreport/DefaultHtmlReportWriterTests.java b/tooling-core/src/test/java/org/opentest4j/reporting/tooling/core/htmlreport/DefaultHtmlReportWriterTests.java index 5aed3766..e79e0075 100644 --- a/tooling-core/src/test/java/org/opentest4j/reporting/tooling/core/htmlreport/DefaultHtmlReportWriterTests.java +++ b/tooling-core/src/test/java/org/opentest4j/reporting/tooling/core/htmlreport/DefaultHtmlReportWriterTests.java @@ -16,16 +16,20 @@ package org.opentest4j.reporting.tooling.core.htmlreport; -import com.microsoft.playwright.BrowserContext; +import com.microsoft.playwright.Page; import com.microsoft.playwright.Page.GetByRoleOptions; import com.microsoft.playwright.Page.ScreenshotOptions; import com.microsoft.playwright.junit.UsePlaywright; import com.microsoft.playwright.options.AriaRole; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestReporter; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.io.TempDir; import java.nio.file.Path; +import java.util.List; import java.util.regex.Pattern; import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat; @@ -36,30 +40,47 @@ @ExtendWith(PlaywrightTracePublisher.class) class DefaultHtmlReportWriterTests { - @Test - void sampleHtmlReportIsRenderedCorrectly(BrowserContext context, TestReporter reporter) { - context.setDefaultTimeout(5_000); + @TempDir + static Path tempDir; + static Path sampleHtmlReport; - var sampleHtmlReport = Path.of(requireNonNull(System.getProperty("sampleHtmlReport"))); - assertThat(sampleHtmlReport).exists().hasExtension("html"); + @BeforeAll + static void createHtmlReport() throws Exception { + var sampleXmlReport = Path.of(requireNonNull(System.getProperty("sampleXmlReport"))); + assertThat(sampleXmlReport).exists().hasExtension("xml"); - var page = context.newPage(); + sampleHtmlReport = tempDir.resolve("open-test-report.html"); + new DefaultHtmlReportWriter().writeHtmlReport(List.of(sampleXmlReport), sampleHtmlReport); + } + @BeforeEach + void openHtmlReport(Page page) { + page.setViewportSize(1920, 1080); page.navigate(sampleHtmlReport.toUri().toString()); assertThat(page).hasTitle(Pattern.compile("Open Test Report")); + } - page.setViewportSize(1920, 1080); + @Test + void sampleHtmlReportIsRenderedCorrectly(Page page, TestReporter reporter) { reporter.publishFile("screenshot-failure.png", file -> page.screenshot(new ScreenshotOptions().setPath(file))); assertThat( page.getByRole(AriaRole.HEADING, new GetByRoleOptions().setName("failed(TestReporter)"))).isVisible(); assertThat(page.getByRole(AriaRole.STATUS)).containsText("FAILED"); - page.getByRole(AriaRole.LINK, new GetByRoleOptions().setName("KitchenSinkTests")).click(); + page.getByRole(AriaRole.LINK, new GetByRoleOptions().setName("KitchenSinkTests").setExact(true)).click(); reporter.publishFile("screenshot-success.png", file -> page.screenshot(new ScreenshotOptions().setPath(file))); assertThat(page.getByRole(AriaRole.HEADING, new GetByRoleOptions().setName("KitchenSinkTest"))).isVisible(); assertThat(page.getByRole(AriaRole.STATUS)).containsText("SUCCESSFUL"); } + + @Test + void canOpenAttachments(Page page) { + page.navigate(sampleHtmlReport.toUri().toString()); + + page.getByText("test.txt", new Page.GetByTextOptions().setExact(false)).click(); + assertThat(page.getByText("Hello, World!")).isVisible(); + } } From 52db822e7dbc036f4dfdc88e0b2aa3a40d77fa4f Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Sun, 1 Dec 2024 17:05:27 +0100 Subject: [PATCH 4/6] Try to fix build on CI --- sample-project/build.gradle.kts | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/sample-project/build.gradle.kts b/sample-project/build.gradle.kts index f39856c8..43be40d5 100644 --- a/sample-project/build.gradle.kts +++ b/sample-project/build.gradle.kts @@ -31,18 +31,18 @@ tasks { options.release = 8 } - val eventXmlFiles = - files(test.map { it.reports.junitXml.outputLocation.get().asFileTree.matching { include("open-test-report.xml") } }).builtBy(test) + val eventXmlFile = + test.map { it.reports.junitXml.outputLocation.get().file("open-test-report.xml") } val convertTestResultXmlToHierarchicalFormat by registering(JavaExec::class) { mainClass.set("org.opentest4j.reporting.cli.ReportingCli") args("convert") classpath(cliClasspath) - inputs.files(eventXmlFiles).withPathSensitivity(NONE).skipWhenEmpty() + inputs.file(eventXmlFile).withPathSensitivity(NONE).skipWhenEmpty() argumentProviders += CommandLineArgumentProvider { - listOf(eventXmlFiles.singleFile.absolutePath) + listOf(eventXmlFile.get().asFile.absolutePath) } - outputs.files(provider { eventXmlFiles.firstOrNull()?.resolveSibling("hierarchy.xml") }) + outputs.files(eventXmlFile.map { it.asFile.resolveSibling("hierarchy.xml") }) outputs.cacheIf { true } } @@ -51,21 +51,19 @@ tasks { args("html-report") classpath(cliClasspath) outputs.file(htmlReportFile) - inputs.files(eventXmlFiles).withPathSensitivity(NONE).skipWhenEmpty() + inputs.files(eventXmlFile).withPathSensitivity(NONE).skipWhenEmpty() argumentProviders += CommandLineArgumentProvider { listOf( "--output", htmlReportFile.get().asFile.absolutePath - ) + eventXmlFiles.files.map { it.absolutePath }.toList() + ) + eventXmlFile.get().asFile.absolutePath } outputs.cacheIf { true } } configurations.consumable("xmlReport") { attributes { - outgoing.artifact(provider { eventXmlFiles.singleFile }) { - builtBy(test) - } + outgoing.artifact(eventXmlFile) attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(LibraryElements.RESOURCES)) } } From e17eaef21edd1c0014008f6a3d39f5c9b44e6788 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Mon, 2 Dec 2024 08:27:13 +0100 Subject: [PATCH 5/6] Improve error message for failed relativization --- .../tooling/core/htmlreport/CoreContributor.java | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/tooling-core/src/main/java/org/opentest4j/reporting/tooling/core/htmlreport/CoreContributor.java b/tooling-core/src/main/java/org/opentest4j/reporting/tooling/core/htmlreport/CoreContributor.java index d9ef8c07..68d17f9f 100644 --- a/tooling-core/src/main/java/org/opentest4j/reporting/tooling/core/htmlreport/CoreContributor.java +++ b/tooling-core/src/main/java/org/opentest4j/reporting/tooling/core/htmlreport/CoreContributor.java @@ -30,6 +30,7 @@ import org.opentest4j.reporting.tooling.spi.htmlreport.Subsections; import org.w3c.dom.Element; +import java.nio.file.Path; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; @@ -146,8 +147,7 @@ private static Optional
createAttachmentsSection(Context context) { if ("file".equals(child.getLocalName())) { attributes.putContent("time", $(child).attr("time")); var originalPath = context.sourceXmlFile().getParent().resolve($(child).attr("path")); - var relativeToTarget = context.targetHtmlFile().getParent().relativize(originalPath); - attributes.putContent("path", "link:" + relativeToTarget); + attributes.putContent("path", "link:" + relativize(context.targetHtmlFile().getParent(), originalPath)); } else { stream(child.getAttributes()).forEach(it -> attributes.putContent(it.getNodeName(), it.getNodeValue())); @@ -166,6 +166,15 @@ private static Optional
createAttachmentsSection(Context context) { return Optional.of(Section.builder().title("Attachments").order(30).addBlock(subsections.build()).build()); } + private static Path relativize(Path parent, Path path) { + try { + return parent.relativize(path); + } + catch (IllegalArgumentException e) { + throw new IllegalArgumentException("Cannot relativize " + path + " against " + parent, e); + } + } + static void addToTable(Match march, QualifiedName elementName, String label, BiConsumer table) { var value = findChild(march, elementName).text(); if (value != null) { From 49989ba7020120f5755cac028c1e473b95b98bd6 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Mon, 2 Dec 2024 08:35:25 +0100 Subject: [PATCH 6/6] Fall back to absolute path if no relative one exists --- .../tooling/core/htmlreport/CoreContributor.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tooling-core/src/main/java/org/opentest4j/reporting/tooling/core/htmlreport/CoreContributor.java b/tooling-core/src/main/java/org/opentest4j/reporting/tooling/core/htmlreport/CoreContributor.java index 68d17f9f..32bae159 100644 --- a/tooling-core/src/main/java/org/opentest4j/reporting/tooling/core/htmlreport/CoreContributor.java +++ b/tooling-core/src/main/java/org/opentest4j/reporting/tooling/core/htmlreport/CoreContributor.java @@ -146,8 +146,9 @@ private static Optional
createAttachmentsSection(Context context) { var attributes = KeyValuePairs.builder(); if ("file".equals(child.getLocalName())) { attributes.putContent("time", $(child).attr("time")); - var originalPath = context.sourceXmlFile().getParent().resolve($(child).attr("path")); - attributes.putContent("path", "link:" + relativize(context.targetHtmlFile().getParent(), originalPath)); + var originalPath = context.sourceXmlFile().getParent().resolve($(child).attr("path")).toAbsolutePath(); + attributes.putContent("path", + "link:" + tryRelativize(context.targetHtmlFile().getParent(), originalPath)); } else { stream(child.getAttributes()).forEach(it -> attributes.putContent(it.getNodeName(), it.getNodeValue())); @@ -166,12 +167,13 @@ private static Optional
createAttachmentsSection(Context context) { return Optional.of(Section.builder().title("Attachments").order(30).addBlock(subsections.build()).build()); } - private static Path relativize(Path parent, Path path) { + private static Path tryRelativize(Path parent, Path path) { try { return parent.relativize(path); } catch (IllegalArgumentException e) { - throw new IllegalArgumentException("Cannot relativize " + path + " against " + parent, e); + // This can happen on Windows if the paths are on different drives + return path; } }