Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@
import static io.qameta.allure.util.ResultsUtils.createHostLabel;
import static io.qameta.allure.util.ResultsUtils.createLanguageLabel;
import static io.qameta.allure.util.ResultsUtils.createPackageLabel;
import static io.qameta.allure.util.ResultsUtils.createParentSuiteLabel;
import static io.qameta.allure.util.ResultsUtils.createSubSuiteLabel;
import static io.qameta.allure.util.ResultsUtils.createSuiteLabel;
import static io.qameta.allure.util.ResultsUtils.createTestClassLabel;
import static io.qameta.allure.util.ResultsUtils.createTestMethodLabel;
Expand Down Expand Up @@ -597,10 +599,7 @@ private void startTest(final TestIdentifier testIdentifier,

testSource.flatMap(AllureJunitPlatformUtils::getFullName).ifPresent(result::setFullName);
testSource.map(this::getSourceLabels).ifPresent(result.getLabels()::addAll);
testClass.ifPresent(aClass -> {
final String suiteName = getDisplayName(aClass).orElse(aClass.getCanonicalName());
result.getLabels().add(createSuiteLabel(suiteName));
});
testClass.map(this::getSuiteLabels).ifPresent(result.getLabels()::addAll);

final Optional<String> classDescription = testClass.flatMap(this::getDescription);
final Optional<String> methodDescription = testMethod.flatMap(this::getDescription);
Expand Down Expand Up @@ -712,14 +711,59 @@ private List<String> getClassTitlePath(final Class<?> testClass) {
.map(Package::getName)
.orElse("");
final List<String> result = ResultsUtils.createTitlePathFromPackage(packageName);
final List<String> classNames = new ArrayList<>();
getClassChain(testClass).stream()
.map(clazz -> getDisplayName(clazz).orElse(clazz.getSimpleName()))
.forEach(result::add);
return result;
}

/**
* Returns suite labels for the given test class, derived from the class nesting chain.
* A top-level class maps to the suite label only. Nested classes shift the chain across
* the xUnit labels: the top-level class becomes the parent suite, the first nested class
* the suite, and any deeper classes the sub suite, so the class nesting stays visible
* in label-based report trees.
*
* @param testClass the test class
* @return the suite labels
*/
private List<Label> getSuiteLabels(final Class<?> testClass) {
final List<Class<?>> classChain = getClassChain(testClass);
final Class<?> topLevelClass = classChain.get(0);
final String topLevelName = getDisplayName(topLevelClass).orElse(topLevelClass.getCanonicalName());
if (classChain.size() == 1) {
return Collections.singletonList(createSuiteLabel(topLevelName));
}
final List<Label> labels = new ArrayList<>();
labels.add(createParentSuiteLabel(topLevelName));
labels.add(createSuiteLabel(getNestedDisplayName(classChain.get(1))));
if (classChain.size() > 2) {
final String subSuiteName = classChain.subList(2, classChain.size()).stream()
.map(this::getNestedDisplayName)
.collect(Collectors.joining(" > "));
labels.add(createSubSuiteLabel(subSuiteName));
}
return labels;
}

private String getNestedDisplayName(final Class<?> nestedClass) {
return getDisplayName(nestedClass).orElse(nestedClass.getSimpleName());
}

/**
* Returns the class with its declaring classes, outermost first.
*
* @param testClass the test class
* @return the class chain
*/
private static List<Class<?>> getClassChain(final Class<?> testClass) {
final List<Class<?>> result = new ArrayList<>();
Class<?> current = testClass;
while (Objects.nonNull(current)) {
classNames.add(getDisplayName(current).orElse(current.getSimpleName()));
result.add(current);
current = current.getDeclaringClass();
}
Collections.reverse(classNames);
result.addAll(classNames);
Collections.reverse(result);
return result;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import io.qameta.allure.junitplatform.features.FailedTests;
import io.qameta.allure.junitplatform.features.JupiterUniqueIdTest;
import io.qameta.allure.junitplatform.features.MarkerAnnotationSupport;
import io.qameta.allure.junitplatform.features.NestedDisplayNameTests;
import io.qameta.allure.junitplatform.features.NestedTests;
import io.qameta.allure.junitplatform.features.OneTest;
import io.qameta.allure.junitplatform.features.OwnerTest;
Expand Down Expand Up @@ -97,8 +98,10 @@
import static io.qameta.allure.util.ResultsUtils.HOST_LABEL_NAME;
import static io.qameta.allure.util.ResultsUtils.OWNER_LABEL_NAME;
import static io.qameta.allure.util.ResultsUtils.PACKAGE_LABEL_NAME;
import static io.qameta.allure.util.ResultsUtils.PARENT_SUITE_LABEL_NAME;
import static io.qameta.allure.util.ResultsUtils.SEVERITY_LABEL_NAME;
import static io.qameta.allure.util.ResultsUtils.STORY_LABEL_NAME;
import static io.qameta.allure.util.ResultsUtils.SUB_SUITE_LABEL_NAME;
import static io.qameta.allure.util.ResultsUtils.SUITE_LABEL_NAME;
import static io.qameta.allure.util.ResultsUtils.TAG_LABEL_NAME;
import static io.qameta.allure.util.ResultsUtils.TEST_CLASS_LABEL_NAME;
Expand Down Expand Up @@ -984,6 +987,126 @@ void shouldSupportNestedClasses() {
);
}

@AllureFeatures.Trees
@Issue("1234")
@Issue("1052")
@Test
void shouldSetSuiteLabelsForNestedClasses() {
final AllureResults allureResults = runClasses(NestedTests.class);

assertThat(allureResults.getTestResults())
.filteredOn("name", "story1Test()")
.flatExtracting(TestResult::getLabels)
.extracting(Label::getName, Label::getValue)
.contains(
tuple(PARENT_SUITE_LABEL_NAME, "io.qameta.allure.junitplatform.features.NestedTests"),
tuple(SUITE_LABEL_NAME, "Feature2"),
tuple(SUB_SUITE_LABEL_NAME, "Story1")
);

assertThat(allureResults.getTestResults())
.filteredOn("name", "feature1Test()")
.flatExtracting(TestResult::getLabels)
.extracting(Label::getName, Label::getValue)
.contains(
tuple(PARENT_SUITE_LABEL_NAME, "io.qameta.allure.junitplatform.features.NestedTests"),
tuple(SUITE_LABEL_NAME, "Feature1")
);

assertThat(allureResults.getTestResults())
.filteredOn("name", "feature1Test()")
.flatExtracting(TestResult::getLabels)
.extracting(Label::getName)
.doesNotContain(SUB_SUITE_LABEL_NAME);

assertThat(allureResults.getTestResults())
.filteredOn("name", "parentTest()")
.flatExtracting(TestResult::getLabels)
.extracting(Label::getName, Label::getValue)
.contains(tuple(SUITE_LABEL_NAME, "io.qameta.allure.junitplatform.features.NestedTests"));

assertThat(allureResults.getTestResults())
.filteredOn("name", "parentTest()")
.flatExtracting(TestResult::getLabels)
.extracting(Label::getName)
.doesNotContain(PARENT_SUITE_LABEL_NAME, SUB_SUITE_LABEL_NAME);
}

@AllureFeatures.Trees
@Issue("1234")
@Issue("1052")
@Test
void shouldUseDisplayNamesForNestedClassesSuiteStructure() {
final AllureResults allureResults = runClasses(NestedDisplayNameTests.class);

assertThat(allureResults.getTestResults())
.extracting(TestResult::getName)
.containsExactlyInAnyOrder(
"can be created with the dao",
"it must be saved to the dao",
"it can be fetched from the dao",
"it cannot be deleted by wrong id",
"it is still present"
);

assertThat(allureResults.getTestResults())
.filteredOn("name", "can be created with the dao")
.flatExtracting(TestResult::getLabels)
.extracting(Label::getName, Label::getValue)
.contains(tuple(SUITE_LABEL_NAME, "A customer object"));

assertThat(allureResults.getTestResults())
.filteredOn("name", "can be created with the dao")
.flatExtracting(TestResult::getLabels)
.extracting(Label::getName)
.doesNotContain(PARENT_SUITE_LABEL_NAME, SUB_SUITE_LABEL_NAME);

assertThat(allureResults.getTestResults())
.filteredOn("name", "it must be saved to the dao")
.flatExtracting(TestResult::getLabels)
.extracting(Label::getName, Label::getValue)
.contains(
tuple(PARENT_SUITE_LABEL_NAME, "A customer object"),
tuple(SUITE_LABEL_NAME, "when created")
);

assertThat(allureResults.getTestResults())
.filteredOn("name", "it must be saved to the dao")
.flatExtracting(TestResult::getLabels)
.extracting(Label::getName)
.doesNotContain(SUB_SUITE_LABEL_NAME);

assertThat(allureResults.getTestResults())
.filteredOn("name", "it can be fetched from the dao")
.flatExtracting(TestResult::getLabels)
.extracting(Label::getName, Label::getValue)
.contains(
tuple(PARENT_SUITE_LABEL_NAME, "A customer object"),
tuple(SUITE_LABEL_NAME, "when created"),
tuple(SUB_SUITE_LABEL_NAME, "after saving a customer")
);

assertThat(allureResults.getTestResults())
.filteredOn("name", "it is still present")
.flatExtracting(TestResult::getLabels)
.extracting(Label::getName, Label::getValue)
.contains(
tuple(PARENT_SUITE_LABEL_NAME, "A customer object"),
tuple(SUITE_LABEL_NAME, "when created"),
tuple(SUB_SUITE_LABEL_NAME, "after saving a customer > and reloading the dao")
);

assertThat(allureResults.getTestResults())
.filteredOn("name", "it can be fetched from the dao")
.extracting(TestResult::getTitlePath)
.containsExactly(
Arrays.asList(
"io", "qameta", "allure", "junitplatform", "features",
"A customer object", "when created", "after saving a customer"
)
);
}

@AllureFeatures.Attachments
@SuppressWarnings("OptionalGetWithoutIsPresent")
@Test
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* Copyright 2016-2026 Qameta Software Inc
*
* 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 io.qameta.allure.junitplatform.features;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

@DisplayName("A customer object")
public class NestedDisplayNameTests {

@Test
@DisplayName("can be created with the dao")
void canBeCreated() {
}

@Nested
@DisplayName("when created")
class WhenCreated {

@Test
@DisplayName("it must be saved to the dao")
void mustBeSaved() {
}

@Nested
@DisplayName("after saving a customer")
class AfterSaving {

@Test
@DisplayName("it can be fetched from the dao")
void canBeFetched() {
}

@Test
@DisplayName("it cannot be deleted by wrong id")
void cannotBeDeletedByWrongId() {
}

@Nested
@DisplayName("and reloading the dao")
class AfterReload {

@Test
@DisplayName("it is still present")
void isStillPresent() {
}
}
}
}
}
Loading