I am working on a project where I need to invoke TestNG programatically(using data providers). Things are fine except that in the report, we are getting the name of the @Tes
The answer by artdanil did not completely solve my problem, the test name is not updated in emailable report.
Answer posted by @jersey-city-ninja does update the name in Emailable report but it repeats the same-last updated testname for all the Dataprovider values because what pilotg2 posted is true for tests that consume Dataprovider i.e. the getTestName method keeps returning the last set name for a method and all the testnames for a dataprovider are same.
So here is the answer that is combination of answer posted by @pilotg2 and @jersey-city-ninja and with additional step to overcome duplicate method names.
Note this updates the testname in Emailable report, XML report, HTML report, Junit report. I don't see it updating the Eclipse - TestNg execution view - will update if I found something
import org.testng.Assert;
import org.testng.ITest;
import org.testng.ITestContext;
import org.testng.ITestResult;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
public class NewDataProviderTest implements ITest {
//The Java ThreadLocal class enables you to create variables that can only be read and written by the same thread
private ThreadLocal testName = new ThreadLocal<>();
/*
* TestNG, for some reason, when building different reports, calls getName() on the test while building the report.
* This is fine if you are not using a data provider to generate different runs and set a unique name for each run by using the ITest strategy.
* If you are using a data provider to generate multiple runs of the same test and want each run to have a unique name then there is a problem.
* As the ITest strategy returns the name for the test as the name set by the last run.
* */
private int emailNameIndex = 0;
private int htmlNameIndex = 0;
private int xmlNameIndex = 0;
private ArrayList allTests = new ArrayList();
/*
* TestHTMLReporter gets the name by first getting all the names for failed tests and then the names for passing tests
* Hence keeping them in 2 separate lists
* */
private ArrayList passedTests = new ArrayList();
private ArrayList failedTests = new ArrayList();
@BeforeClass(alwaysRun = true)
public void initialize() {
this.testName.set("");
}
@BeforeMethod(alwaysRun = true)
public void setCustomTestcaseName(Method method, Object[] testData) {
//Set the default name
this.testName.set(method.getName());
//Change the test name only if Dataprovider is used
//Check if data provider is used in the test
if (testData != null && testData.length > 0) {
System.out.println("\n\nParameters "+testData[0]+" are passed to the test - "+method.getName());
//Taking 1st field in the Dataprovider for updating testname - can be changed as desired maybe using a variable
//I'm changing the name only if the Dataprovider field is String
if (testData[0] instanceof String) {
//Taking 1st field in the Dataprovider for updating testname - can be changed as desired
System.out.println("I'm setting custom name to the test as "+method.getName() + "_" + testData[0]);
this.testName.set(method.getName() + "_" + testData[0]);
}
}
//Add the name to the collection that stores all list names
allTests.add(testName.get());
}
@AfterMethod (alwaysRun = true)
public void setTheTestcaseNameInResult(ITestResult result, Method method) {
//Fill the Passed and Failed tests collections
try {
if(result.getStatus() == ITestResult.SUCCESS) {
System.out.println("Adding "+ result.getTestName() + " to passed tests collection");
passedTests.add(result.getTestName());
}
if(result.getStatus() == ITestResult.FAILURE) {
System.out.println("Adding " + result.getTestName() + " to FAILURE tests collection");
failedTests.add(result.getTestName());
}
} catch (Exception e) {
e.printStackTrace();
}
// To change display name in HTML report
//Only changing the name if the parameter is instance of String
if(iTestResult.getParameters().length > 0) {
if (iTestResult.getParameters()[0] instanceof String) {
System.out.println("Updating the name as Parameters are passed to the test-"+method.getName());
try {
/* This helps in setting unique name to method for each test instance for a data provider*/
Field resultMethod = TestResult.class.getDeclaredField("m_method");
resultMethod.setAccessible(true);
resultMethod.set(iTestResult, iTestResult.getMethod().clone());
Field methodName = org.testng.internal.BaseTestMethod.class.getDeclaredField("m_methodName");
methodName.setAccessible(true);
methodName.set(iTestResult.getMethod(), this.getTestName());
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("New Name is - " + iTestResult.getMethod().getMethodName());
}
}
}
@Override
public String getTestName() {
String name = testName.get();
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();// .toString();
//This is called
if (isCalledFromMethod(stackTrace, "XMLSuiteResultWriter")) {
//System.out.println("Got called from XMLSuiteResultWriter");
if (allTestNames.size() > 0) {
if (xmlNameIndex < allTestNames.size()) {
name = allTestNames.get(xmlNameIndex);
} else {
name = allTestNames.get(0);
}
} else {
name = "undefined";
}
xmlNameIndex++;
if (xmlNameIndex >= allTestNames.size()) {
xmlNameIndex = 0;
}
// System.out.println("Got called from XMLSuiteResultWriter returning name - "+name);
} else if (isCalledFromMethod(stackTrace, "EmailableReporter")) {
if (allTestNames.size() > 0) {
if (emailNameIndex < allTestNames.size()) {
name = allTestNames.get(emailNameIndex);
} else {
name = allTestNames.get(0);
}
} else {
name = "undefined";
}
emailNameIndex++;
if (emailNameIndex >= allTestNames.size()) {
emailNameIndex = 0;
}
System.out.println("Got called from EmailableReporter returning name -"+name);
}
if (isCalledFromMethod(stackTrace, "TestHTMLReporter")) {
if (allTestNames.size() <= 0) {
name = "undefined";
} else {
if (htmlNameIndex < failedTestNames.size()) {
name = failedTestNames.get(htmlNameIndex);
} else {
int htmlPassedIndex = htmlNameIndex - failedTestNames.size();
if (htmlPassedIndex < passedTestNames.size()) {
name = passedTestNames.get(htmlPassedIndex);
} else {
name = "undefined";
}
}
}
htmlNameIndex++;
if (htmlNameIndex >= allTestNames.size()) {
htmlNameIndex = 0;
}
System.out.println("Got called from TestHTMLReporter returning name - "+name);
}
System.out.println("Returning testname as-"+name);
return name;
}
private boolean isCalledFromMethod(StackTraceElement[] stackTrace, String checkForMethod) {
boolean calledFrom = false;
for (StackTraceElement element : stackTrace) {
String stack = element.toString();
// System.out.println("Rohit the called from value is:"+stack);
if (stack.contains(checkForMethod))
calledFrom = true;
}
return calledFrom;
}
@Test(groups= {"L1", "L2", "L3"}, dataProvider = "dp1")
public void dataProviderTest(String username) {
System.out.println("\n\nI'm in dataProviderTest with data-"+username);
/* Fail the test if value is L2 - deliberately so that we have failed test in report */
if(username.contains("L2")) {
Assert.fail();
}
}
@Test(dependsOnMethods = "dataProviderTest", groups= {"L1", "L2", "L3"}, dataProvider = "dp1")
public void dataProviderDependentTest(String username) {
System.out.println("\n\nI'm in dataProvider DEPENDENT Test with data-"+username);
}
//This test consumes data of type list so the name will not be updated in report
@Test(groups= {"L1", "L2", "L3"}, dataProvider = "dp2")
public void dataListProviderTest(List list) {
Object[] arr = list.get(0);
List