From 46466d0b58c9d1db2ffe4c982533c8a58c55bc82 Mon Sep 17 00:00:00 2001 From: Dmitry Baev Date: Fri, 10 Jul 2026 16:57:05 +0100 Subject: [PATCH] testng instance params --- .../io/qameta/allure/testng/AllureTestNg.java | 28 ++++++-- .../allure/testng/TestInstanceParameter.java | 27 +++++++- .../allure/testng/AllureTestNgTest.java | 64 +++++++++++++++++++ .../samples/TestInstanceParameterBase.java | 43 +++++++++++++ ...TestInstanceParameterInheritanceTests.java | 46 +++++++++++++ .../suites/test-instance-parameters.xml | 10 +++ 6 files changed, 213 insertions(+), 5 deletions(-) create mode 100644 allure-testng/src/test/java/io/qameta/allure/testng/samples/TestInstanceParameterBase.java create mode 100644 allure-testng/src/test/java/io/qameta/allure/testng/samples/TestInstanceParameterInheritanceTests.java create mode 100644 allure-testng/src/test/resources/suites/test-instance-parameters.xml diff --git a/allure-testng/src/main/java/io/qameta/allure/testng/AllureTestNg.java b/allure-testng/src/main/java/io/qameta/allure/testng/AllureTestNg.java index b39473553..7be019023 100644 --- a/allure-testng/src/main/java/io/qameta/allure/testng/AllureTestNg.java +++ b/allure-testng/src/main/java/io/qameta/allure/testng/AllureTestNg.java @@ -902,6 +902,7 @@ protected String getHistoryId(final ITestNGMethod method, final List digest.update(testClassName.getBytes(UTF_8)); digest.update(methodName.getBytes(UTF_8)); parameters.stream() + .filter(parameter -> !Boolean.TRUE.equals(parameter.getExcluded())) .sorted(comparing(Parameter::getName).thenComparing(Parameter::getValue)) .forEachOrdered(parameter -> { digest.update(parameter.getName().getBytes(UTF_8)); @@ -1009,17 +1010,25 @@ private List getParameters(final ITestContext context, .forEach((name, value) -> result.put(name, createParameter(name, value))); final Object instance = method.getInstance(); if (nonNull(instance)) { - Stream.of(instance.getClass().getDeclaredFields()) + getClassHierarchy(instance.getClass()).stream() + .flatMap(type -> Stream.of(type.getDeclaredFields())) .filter(field -> field.isAnnotationPresent(TestInstanceParameter.class)) .forEach(field -> { - final String name = Optional.ofNullable(field.getAnnotation(TestInstanceParameter.class)) - .map(TestInstanceParameter::value) + final TestInstanceParameter annotation = field.getAnnotation(TestInstanceParameter.class); + final String name = Optional.of(annotation.value()) .filter(s -> !s.isEmpty()) .orElseGet(field::getName); try { field.setAccessible(true); final String value = ObjectUtils.toString(field.get(instance)); - result.put(name, createParameter(name, value)); + result.put( + name, createParameter( + name, + value, + annotation.excluded(), + annotation.mode() + ) + ); } catch (IllegalAccessException e) { LOGGER.debug("Could not access field value"); } @@ -1068,6 +1077,17 @@ private List getParameters(final ITestContext context, .collect(Collectors.toList()); } + private static List> getClassHierarchy(final Class type) { + final List> hierarchy = new ArrayList<>(); + Class current = type; + while (nonNull(current) && !Object.class.equals(current)) { + hierarchy.add(current); + current = current.getSuperclass(); + } + Collections.reverse(hierarchy); + return hierarchy; + } + private String getMethodName(final ITestNGMethod method) { return firstNonEmpty( method.getDescription(), diff --git a/allure-testng/src/main/java/io/qameta/allure/testng/TestInstanceParameter.java b/allure-testng/src/main/java/io/qameta/allure/testng/TestInstanceParameter.java index abd0690ca..bafb60bc2 100644 --- a/allure-testng/src/main/java/io/qameta/allure/testng/TestInstanceParameter.java +++ b/allure-testng/src/main/java/io/qameta/allure/testng/TestInstanceParameter.java @@ -15,6 +15,8 @@ */ package io.qameta.allure.testng; +import io.qameta.allure.model.Parameter; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -23,6 +25,12 @@ /** * You can use this annotation to add test instance parameters to your TestNG tests. + * Fields declared by the test class and its superclasses are supported. + * If multiple fields resolve to the same parameter name, the field declared by + * the most-derived class takes precedence. + * + * @see Parameter + * @see Parameter.Mode */ @Documented @Retention(RetentionPolicy.RUNTIME) @@ -30,8 +38,25 @@ public @interface TestInstanceParameter { /** - * The name of parameter. + * The name of parameter. If empty, the field name will be used. + * + * @return the name of parameter. */ String value() default ""; + /** + * The parameter mode. It controls how the value is displayed and does not + * exclude the parameter from history ID generation. + * + * @return the parameter mode. + */ + Parameter.Mode mode() default Parameter.Mode.DEFAULT; + + /** + * Set it to true to exclude the parameter from history ID generation. + * + * @return true if parameter is excluded, false otherwise. + */ + boolean excluded() default false; + } diff --git a/allure-testng/src/test/java/io/qameta/allure/testng/AllureTestNgTest.java b/allure-testng/src/test/java/io/qameta/allure/testng/AllureTestNgTest.java index 0c155af8f..c7edb602f 100644 --- a/allure-testng/src/test/java/io/qameta/allure/testng/AllureTestNgTest.java +++ b/allure-testng/src/test/java/io/qameta/allure/testng/AllureTestNgTest.java @@ -1639,6 +1639,70 @@ public void shouldSupportFactoryOnConstructor() { ); } + @SuppressWarnings("unchecked") + @AllureFeatures.Parameters + @Test + public void shouldSupportInheritedTestInstanceParameterMetadata() { + final AllureResults results = runTestNgSuites("suites/test-instance-parameters.xml"); + + assertThat(results.getTestResults()).hasSize(3); + assertThat(results.getTestResults()) + .allSatisfy( + testResult -> assertThat(testResult.getParameters()) + .hasSize(3) + .extracting( + Parameter::getName, + Parameter::getValue, + Parameter::getExcluded, + Parameter::getMode + ) + .contains( + tuple("overridden", "child-value", false, Parameter.Mode.DEFAULT) + ) + ); + assertThat(results.getTestResults()) + .flatExtracting(TestResult::getParameters) + .filteredOn(parameter -> "iteration".equals(parameter.getName())) + .extracting(Parameter::getValue, Parameter::getExcluded, Parameter::getMode) + .containsExactlyInAnyOrder( + tuple("first", true, Parameter.Mode.DEFAULT), + tuple("second", true, Parameter.Mode.DEFAULT), + tuple("third", true, Parameter.Mode.DEFAULT) + ); + assertThat(results.getTestResults()) + .flatExtracting(TestResult::getParameters) + .filteredOn(parameter -> "hidden".equals(parameter.getName())) + .extracting(Parameter::getValue, Parameter::getExcluded, Parameter::getMode) + .containsExactlyInAnyOrder( + tuple("hidden-value", false, Parameter.Mode.HIDDEN), + tuple("hidden-value", false, Parameter.Mode.HIDDEN), + tuple("different-hidden-value", false, Parameter.Mode.HIDDEN) + ); + } + + @AllureFeatures.Parameters + @AllureFeatures.History + @Test + public void shouldExcludeTestInstanceParameterFromHistoryId() { + final AllureResults results = runTestNgSuites("suites/test-instance-parameters.xml"); + + assertThat(results.getTestResults()).hasSize(3); + final Map historyIds = results.getTestResults().stream() + .collect( + Collectors.toMap( + testResult -> testResult.getParameters().stream() + .filter(parameter -> "iteration".equals(parameter.getName())) + .map(Parameter::getValue) + .findFirst() + .orElseThrow(), + TestResult::getHistoryId + ) + ); + assertThat(historyIds.values()).doesNotContainNull(); + assertThat(historyIds.get("first")).isEqualTo(historyIds.get("second")); + assertThat(historyIds.get("first")).isNotEqualTo(historyIds.get("third")); + } + @SuppressWarnings("unchecked") @AllureFeatures.Parameters @Issue("893") diff --git a/allure-testng/src/test/java/io/qameta/allure/testng/samples/TestInstanceParameterBase.java b/allure-testng/src/test/java/io/qameta/allure/testng/samples/TestInstanceParameterBase.java new file mode 100644 index 000000000..b174679c0 --- /dev/null +++ b/allure-testng/src/test/java/io/qameta/allure/testng/samples/TestInstanceParameterBase.java @@ -0,0 +1,43 @@ +/* + * 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.testng.samples; + +import io.qameta.allure.model.Parameter; +import io.qameta.allure.testng.TestInstanceParameter; + +public abstract class TestInstanceParameterBase { + + @TestInstanceParameter( + value = "iteration", + excluded = true + ) + private final String iteration; + + @TestInstanceParameter( + value = "hidden", + mode = Parameter.Mode.HIDDEN + ) + private final String hidden; + + @TestInstanceParameter("overridden") + private final String overridden = "base-value"; + + protected TestInstanceParameterBase(final String iteration, final String hidden) { + this.iteration = iteration; + this.hidden = hidden; + } + +} diff --git a/allure-testng/src/test/java/io/qameta/allure/testng/samples/TestInstanceParameterInheritanceTests.java b/allure-testng/src/test/java/io/qameta/allure/testng/samples/TestInstanceParameterInheritanceTests.java new file mode 100644 index 000000000..7b3d57cc4 --- /dev/null +++ b/allure-testng/src/test/java/io/qameta/allure/testng/samples/TestInstanceParameterInheritanceTests.java @@ -0,0 +1,46 @@ +/* + * 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.testng.samples; + +import io.qameta.allure.testng.TestInstanceParameter; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Factory; +import org.testng.annotations.Test; + +public class TestInstanceParameterInheritanceTests extends TestInstanceParameterBase { + + @TestInstanceParameter("overridden") + private final String overridden = "child-value"; + + @Factory(dataProvider = "instances") + public TestInstanceParameterInheritanceTests(final String iteration, final String hidden) { + super(iteration, hidden); + } + + @DataProvider + public static Object[][] instances() { + return new Object[][]{ + new Object[]{"first", "hidden-value"}, + new Object[]{"second", "hidden-value"}, + new Object[]{"third", "different-hidden-value"}, + }; + } + + @Test + public void factoryTest() { + } + +} diff --git a/allure-testng/src/test/resources/suites/test-instance-parameters.xml b/allure-testng/src/test/resources/suites/test-instance-parameters.xml new file mode 100644 index 000000000..15e11fa6d --- /dev/null +++ b/allure-testng/src/test/resources/suites/test-instance-parameters.xml @@ -0,0 +1,10 @@ + + + + + + + + + +