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 @@ -902,6 +902,7 @@ protected String getHistoryId(final ITestNGMethod method, final List<Parameter>
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));
Expand Down Expand Up @@ -1009,17 +1010,25 @@ private List<Parameter> 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");
}
Expand Down Expand Up @@ -1068,6 +1077,17 @@ private List<Parameter> getParameters(final ITestContext context,
.collect(Collectors.toList());
}

private static List<Class<?>> getClassHierarchy(final Class<?> type) {
final List<Class<?>> 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(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -23,15 +25,38 @@

/**
* 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)
@Target({ElementType.FIELD})
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;

}
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, String> 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")
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}

}
Original file line number Diff line number Diff line change
@@ -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() {
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >

<suite name="Test instance parameters">
<test name="test-instance-parameters">
<classes>
<class name="io.qameta.allure.testng.samples.TestInstanceParameterInheritanceTests"/>
</classes>
</test>
</suite>
Loading