DataProvider in TestNG: Selenium Parameterization Example

โšก Smart Summary

Parameterization in Selenium runs a single test method repeatedly against different data sets, turning one script into data-driven coverage. TestNG offers two mechanisms for this: the @Parameters annotation backed by testng.xml, and the @DataProvider annotation returning a two-dimensional object array.

  • ๐ŸŽฏ Core Purpose: Parameterization removes hardcoded values from test scripts so the same logic validates every input combination the application must support.
  • ๐Ÿ“„ XML Mechanism: The @Parameters annotation reads name-value pairs declared in testng.xml, which suits configuration-style inputs where the combination count stays small.
  • ๐Ÿท๏ธ Scope Precedence: Parameters declared at test level override identically named parameters at suite level, while classes outside that test continue to read the suite value.
  • ๐Ÿ—‚๏ธ DataProvider Mechanism: A method annotated @DataProvider returns Object[][], and TestNG invokes the test once per row, passing each column as an argument.
  • ๐Ÿ”— External Providers: Marking the provider static and setting dataProviderClass on @Test lets several test classes share one data source.
  • ๐Ÿงญ Dynamic Data Sets: Accepting Method or ITestContext as a provider argument returns different data per test method or per included group.
  • ๐Ÿ›ก๏ธ Common Failures: Type mismatches between XML values and method arguments, and missing parameters solved with @Optional, cause most annotation errors.

DataProvider in TestNG

As we create software, we always want it to work correctly with different sets of data. When it comes to testing that software, checking a single set of data is not enough. We need to verify that the system accepts every combination it is expected to support. For that, we need to parameterize our test scripts. This is where parameterization comes in.

Parameterization in Selenium

Parameterization in Selenium is a process to parameterize the test scripts in order to pass multiple data to the application at runtime. It is a strategy of execution which automatically runs test cases multiple times using different values. The concept achieved by parameterizing the test scripts is called Data Driven Testing.

Type of Parameterization in TestNG

To make parameterization clearer, we will go through the parameterization options in one of the most popular frameworks for Selenium WebDriver โ€” TestNG.

There are two ways by which we can achieve parameterization in TestNG:

  1. With the help of the Parameters annotation and the TestNG XML file.

Type Of Parameterization In TestNG

  1. With the help of the DataProvider annotation.

Type Of Parameterization In TestNG

Type Of Parameterization In TestNG

The diagram above summarizes the split: parameters from testng.xml can be declared at suite or test level, while a parameter from a DataProvider can accept Method and ITestContext as its own argument. Let us study them in detail.

Parameters Annotation in TestNG

Parameters Annotation in TestNG is a method used to pass values to the test methods as arguments using an .xml file. Users may be required to pass the values to the test methods during run time. The @Parameters annotation method can be used in any method having @Test, @Before, @After or @Factory annotation.

Parameters annotation with Testng.xml

Select parameterization using annotations when you do not want to deal with complexity and the number of input combinations is small.

Let us see how this works.

Test Scenario

Step 1) Launch the browser and go to Google.com

Step 2) Enter a search keyword

Parameters Annotation With Testng.Xml

Step 3) Verify the entered value is the same as the value provided by our test data

Step 4) Repeat steps 2 and 3 until all values are entered

Test Author SearchKey
Guru99 India
Krishna USA
Bhupesh China

Here is an example of how to do it without parameters:

package parameters;

import org.testng.annotations.Test;
import org.testng.AssertJUnit;
import java.time.Duration;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.firefox.FirefoxDriver;

public class NoParameterWithTestNGXML {
    WebDriver driver;

    @Test
    public void testNoParameter() throws InterruptedException {
        String author = "guru99";
        String searchKey = "india";

        driver = new FirefoxDriver();
        driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));

        driver.get("https://google.com");
        WebElement searchText = driver.findElement(By.name("q"));
        // Searching text in the Google text box
        searchText.sendKeys(searchKey);

        System.out.println("Welcome ->" + author + " Your search key is->" + searchKey);
        Thread.sleep(3000);
        System.out.println("Value in Google Search Box = " + searchText.getDomProperty("value")
                + " ::: Value given by input = " + searchKey);
        // Verifying the value in the Google search box
        AssertJUnit.assertTrue(searchText.getDomProperty("value").equalsIgnoreCase(searchKey));
    }
}

โš ๏ธ Selenium 4 note: Three lines in the original version of this example no longer compile or are deprecated on current Selenium releases. System.setProperty("webdriver.gecko.driver", โ€ฆ) is unnecessary from Selenium 4.6 onward because Selenium Manager resolves the driver binary automatically. The implicitlyWait(10, TimeUnit.SECONDS) overload was removed in Selenium 4 and replaced by implicitlyWait(Duration.ofSeconds(10)). getAttribute() was deprecated in Selenium 4.27, so reading a text box uses getDomProperty("value"). Every example below applies the same three corrections.

Study the example above and imagine how complex the code becomes when this is repeated for three input combinations.

Now let us parameterize it using TestNG. To do so, you need to:

  • Create an XML file which will store the parameters
  • In the test, add the annotation @Parameters

Parameters Annotation With Testng.Xml

Here is the complete code.

Test Level TestNG.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="TestSuite" thread-count="3">
  <parameter name="author" value="Guru99" />
  <parameter name="searchKey" value="India" />
  <test name="testGuru">
    <parameter name="searchKey" value="UK" />
    <classes>
      <class name="parameters.ParameterWithTestNGXML"></class>
    </classes>
  </test>
</suite>

ParameterWithTestNGXML.java File

package parameters;

import org.testng.AssertJUnit;
import java.time.Duration;

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.firefox.FirefoxDriver;

import org.testng.annotations.Optional;
import org.testng.annotations.Parameters;
import org.testng.annotations.Test;

public class ParameterWithTestNGXML {
    WebDriver driver;

    @Test
    @Parameters({"author", "searchKey"})
    public void testParameterWithXML(@Optional("Abc") String author, String searchKey)
            throws InterruptedException {

        driver = new FirefoxDriver();
        driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));
        driver.get("https://google.com");

        WebElement searchText = driver.findElement(By.name("q"));
        // Searching text in the Google text box
        searchText.sendKeys(searchKey);

        System.out.println("Welcome ->" + author + " Your search key is->" + searchKey);
        Thread.sleep(3000);
        System.out.println("Value in Google Search Box = " + searchText.getDomProperty("value")
                + " ::: Value given by input = " + searchKey);
        // Verifying the value in the Google search box
        AssertJUnit.assertTrue(searchText.getDomProperty("value").equalsIgnoreCase(searchKey));
    }
}

To run the script, select the XML file and run it as a TestNG suite.

Right click on the .xml file -> Run as -> TestNG Suite (Note: Suite)

ParameterWithTestNGXML.java File

Parameters can be defined at two levels:

  1. Suite level โ€” the parameters inside the <suite> tag of the TestNG XML file are suite level parameters.
  2. Test level โ€” the parameters inside the <test> tag of the TestNG XML file are test level parameters.

Here is the same test with suite level parameters:

ParameterWithTestNGXML.java File

NOTE: If the parameter name is the same at suite level and test level, the test level parameter takes preference over the suite level one. In that case, all classes inside that test level share the overridden parameter, and classes outside that test level continue to use the suite level parameter.

ParameterWithTestNGXML.java File

Troubleshooting

Issue # 1: A parameter value in testng.xml that cannot be typecast to the corresponding test method’s parameter throws an error.

Consider the following example:

TroubleShooting

Here, the โ€˜authorโ€™ attribute equals โ€˜Guru99โ€™, which is a String, while the corresponding test method expects an integer value, so an exception is raised.

Issue # 2: Your @Parameters do not have a corresponding value in testng.xml.

You can solve this by adding the @Optional annotation to the corresponding parameter in the test method.

TroubleShooting

Issue # 3: You want to test multiple values of the same parameter using testng.xml.

The simple answer is that this cannot be done. You can have multiple different parameters, but each parameter can only hold a single value. This prevents hardcoding values into the script and keeps the code reusable โ€” think of it as a config file for your script. If you need multiple values for one parameter, use a DataProvider instead.

Data Provider in TestNG

Data Provider in TestNG is a method used when a user needs to pass complex parameters. Complex parameters need to be created from Java โ€” complex objects, objects from property files, or objects from a database can be passed by the data provider method. The method is annotated with @DataProvider and it returns an array of objects.

Parameters using Dataprovider

The @Parameters annotation is easy, but to test with multiple sets of data we need to use a Data Provider.

To fill thousands of web forms using our testing framework, we need a different methodology which can supply a very large dataset in a single execution flow.

This data driven concept is achieved by the @DataProvider annotation in TestNG.

Parameters Using Dataprovider

It has only one attribute, โ€˜nameโ€™. If you do not specify the name attribute, the DataProvider’s name is the same as the corresponding method name.

A data provider returns a two-dimensional Java object to the test method, and the test method is invoked M times for an Mร—N object array. For example, if the DataProvider returns an array of 2ร—3 objects, the corresponding test case is invoked 2 times with 3 parameters each time.

Parameters Using Dataprovider

Complete Example

Parameters Using Dataprovider

package parameters;

import java.time.Duration;

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.firefox.FirefoxDriver;

import org.testng.Assert;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

public class ParameterByDataprovider {
    WebDriver driver;

    @BeforeTest
    public void setup() {
        // Create the Firefox driver object
        driver = new FirefoxDriver();
        driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));
        driver.get("https://google.com");
    }

    /** Test case to verify the Google search box */
    @Test(dataProvider = "SearchProvider")
    public void testMethod(String author, String searchKey) throws InterruptedException {
        WebElement searchText = driver.findElement(By.name("q"));
        // Search value in the Google search box
        searchText.sendKeys(searchKey);
        System.out.println("Welcome ->" + author + " Your search key is->" + searchKey);
        Thread.sleep(3000);
        String testValue = searchText.getDomProperty("value");
        System.out.println(testValue + "::::" + searchKey);
        searchText.clear();
        // Verify if the value in the Google search box is correct
        Assert.assertTrue(testValue.equalsIgnoreCase(searchKey));
    }

    /**
     * @return Object[][] where the first column contains 'author'
     * and the second column contains 'searchKey'
     */
    @DataProvider(name = "SearchProvider")
    public Object[][] getDataFromDataprovider() {
        return new Object[][] {
            { "Guru99", "India" },
            { "Krishna", "UK" },
            { "Bhupesh", "USA" }
        };
    }
}

Invoke DataProvider from different class

By default, a DataProvider resides in the same class as the test method, or in its base class. To place it in another class, make the data provider method static and add the attribute dataProviderClass to the @Test annotation.

Invoke DataProvider From Different Class

Code Example

Invoke DataProvider From Different Class

TestClass ParameterDataproviderWithClassLevel.java

package parameters;

import java.time.Duration;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.testng.Assert;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;

public class ParameterDataproviderWithClassLevel {
    WebDriver driver;

    @BeforeTest
    public void setup() {
        driver = new FirefoxDriver();
        driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));
        driver.get("https://google.com");
    }

    @Test(dataProvider = "SearchProvider", dataProviderClass = DataproviderClass.class)
    public void testMethod(String author, String searchKey) throws InterruptedException {
        WebElement searchText = driver.findElement(By.name("q"));
        // Search text in the Google text box
        searchText.sendKeys(searchKey);
        System.out.println("Welcome ->" + author + " Your search key is->" + searchKey);
        Thread.sleep(3000);
        // Get text from the search box
        String testValue = searchText.getDomProperty("value");
        System.out.println(testValue + "::::" + searchKey);
        searchText.clear();
        // Verify if the search box has the correct value
        Assert.assertTrue(testValue.equalsIgnoreCase(searchKey));
    }
}

DataproviderClass.java

package parameters;

import org.testng.annotations.DataProvider;

public class DataproviderClass {

    @DataProvider(name = "SearchProvider")
    public static Object[][] getDataFromDataprovider() {
        return new Object[][] {
            { "Guru99", "India" },
            { "Krishna", "UK" },
            { "Bhupesh", "USA" }
        };
    }
}

Types of Parameters in Dataprovider

There are two types of parameters supported by the DataProvider method.

Method โ€” if the same DataProvider should behave differently for different test methods, use the Method parameter.

Types of Parameters In Dataprovider

In the following example:

  • We check if the method name is testMethodA
  • If yes, return one set of values
  • Otherwise return another set of values
package parameters;

import java.lang.reflect.Method;
import java.time.Duration;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.testng.Assert;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

public class ParameterByMethodInDataprovider {

    WebDriver driver;

    @BeforeTest
    public void setup() {
        driver = new FirefoxDriver();
        driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));
        driver.get("https://google.com");
    }

    @Test(dataProvider = "SearchProvider")
    public void testMethodA(String author, String searchKey) throws InterruptedException {
        WebElement searchText = driver.findElement(By.name("q"));
        searchText.sendKeys(searchKey);
        // Print author and search string
        System.out.println("Welcome ->" + author + " Your search key is->" + searchKey);
        Thread.sleep(3000);
        String testValue = searchText.getDomProperty("value");
        System.out.println(testValue + "::::" + searchKey);
        searchText.clear();
        Assert.assertTrue(testValue.equalsIgnoreCase(searchKey));
    }

    @Test(dataProvider = "SearchProvider")
    public void testMethodB(String searchKey) throws InterruptedException {
        WebElement searchText = driver.findElement(By.name("q"));
        searchText.sendKeys(searchKey);
        // Print only the search string
        System.out.println("Welcome ->Unknown user Your search key is->" + searchKey);
        Thread.sleep(3000);
        String testValue = searchText.getDomProperty("value");
        System.out.println(testValue + "::::" + searchKey);
        searchText.clear();
        Assert.assertTrue(testValue.equalsIgnoreCase(searchKey));
    }

    /** The DataProvider returns values based on the test method name */
    @DataProvider(name = "SearchProvider")
    public Object[][] getDataFromDataprovider(Method m) {
        if (m.getName().equalsIgnoreCase("testMethodA")) {
            return new Object[][] {
                { "Guru99", "India" },
                { "Krishna", "UK" },
                { "Bhupesh", "USA" }
            };
        } else {
            return new Object[][] {
                { "Canada" },
                { "Russia" },
                { "Japan" }
            };
        }
    }
}

Here is the output:

Types of Parameters In Dataprovider

ITestContext โ€” this can be used to create different parameters for test cases based on groups.

In real life, you can use ITestContext to vary parameter values based on test methods, hosts, or test configurations.

Types of Parameters In Dataprovider

In the following code example:

  • We have 2 groups, A and B
  • Each test method is assigned to a group
  • If the group value is A, one data set is returned
  • If the group value is B, another data set is returned
package parameters;

import java.time.Duration;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.testng.Assert;
import org.testng.ITestContext;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

public class ParameterByITestContextInDataprovider {
    WebDriver driver;

    @BeforeTest(groups = {"A", "B"})
    public void setup() {
        driver = new FirefoxDriver();
        driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));
        driver.get("https://google.com");
    }

    @Test(dataProvider = "SearchProvider", groups = "A")
    public void testMethodA(String author, String searchKey) throws InterruptedException {
        WebElement searchText = driver.findElement(By.name("q"));
        searchText.sendKeys(searchKey);
        System.out.println("Welcome ->" + author + " Your search key is->" + searchKey);
        Thread.sleep(3000);
        String testValue = searchText.getDomProperty("value");
        System.out.println(testValue + "::::" + searchKey);
        searchText.clear();
        Assert.assertTrue(testValue.equalsIgnoreCase(searchKey));
    }

    @Test(dataProvider = "SearchProvider", groups = "B")
    public void testMethodB(String searchKey) throws InterruptedException {
        WebElement searchText = driver.findElement(By.name("q"));
        searchText.sendKeys(searchKey);
        System.out.println("Welcome ->Unknown user Your search key is->" + searchKey);
        Thread.sleep(3000);
        String testValue = searchText.getDomProperty("value");
        System.out.println(testValue + "::::" + searchKey);
        searchText.clear();
        Assert.assertTrue(testValue.equalsIgnoreCase(searchKey));
    }

    /** The DataProvider supplies an Object array based on ITestContext */
    @DataProvider(name = "SearchProvider")
    public Object[][] getDataFromDataprovider(ITestContext c) {
        Object[][] groupArray = null;
        for (String group : c.getIncludedGroups()) {
            if (group.equalsIgnoreCase("A")) {
                groupArray = new Object[][] {
                    { "Guru99", "India" },
                    { "Krishna", "UK" },
                    { "Bhupesh", "USA" }
                };
                break;
            } else if (group.equalsIgnoreCase("B")) {
                groupArray = new Object[][] {
                    { "Canada" },
                    { "Russia" },
                    { "Japan" }
                };
                break;
            }
        }
        return groupArray;
    }
}

Note: If you run the TestNG class directly, it calls the data provider first, and the provider cannot read group information because the groups are not available yet. Calling the class through testng.xml makes the group information available through ITestContext. Use the following XML to run the test:

<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="test-parameter">

  <test name="example1">
    <groups>
      <run>
        <include name="A" />
      </run>
    </groups>
    <classes>
      <class name="parameters.ParameterByITestContextInDataprovider" />
    </classes>
  </test>

  <test name="example2">
    <groups>
      <run>
        <include name="B" />
      </run>
    </groups>
    <classes>
      <class name="parameters.ParameterByITestContextInDataprovider" />
    </classes>
  </test>

</suite>

Both mechanisms are now covered, so the practical question is which one to reach for.

@Parameters vs @DataProvider: Which One to Use

The two approaches are not interchangeable. The decision comes down to how many value combinations a test needs and where those values originate.

Criterion @Parameters + testng.xml @DataProvider
Values per parameter Exactly one Unlimited rows
Data source Static text in the XML file Java code, Excel, CSV, database, API
Supported types Values typecast from String Any Java object
Test invocations Once per configuration Once per data row
Changing data requires Editing the XML file Editing the source, often no recompile
Best suited to Environment and configuration values such as browser, URL, credentials Data driven testing across many input combinations

In practice most suites use both: @Parameters carries the environment settings that stay constant across a run, and @DataProvider supplies the varying business data. When the data set outgrows a hardcoded array, the next step is reading it from a spreadsheet.

How to Read Test Data From an Excel File Using DataProvider

Hardcoded arrays stop scaling once testers who do not write Java need to maintain the data. Moving the rows into a spreadsheet separates test data from test logic, which is the practical form of data driven testing. Apache POI reads the workbook, and the DataProvider converts each row into one test invocation.

  1. Add the dependency. Include org.apache.poi:poi-ooxml in your Maven or Gradle build. The ooxml artifact handles the .xlsx format; plain poi only reads legacy .xls files.
  2. Create the workbook. Put the header row in row 0 and one test case per row beneath it, with one column per test method argument.
  3. Read the sheet into an array. Size the Object[][] from the row and cell counts so new rows are picked up without code changes.
  4. Return it from the provider. The test method signature stays exactly as before.
import java.io.FileInputStream;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.testng.annotations.DataProvider;

public class ExcelDataProvider {

    @DataProvider(name = "SearchProvider")
    public Object[][] readSearchData() throws Exception {
        FileInputStream file = new FileInputStream("src/test/resources/testdata.xlsx");
        Workbook workbook = new XSSFWorkbook(file);
        Sheet sheet = workbook.getSheetAt(0);

        // Skip the header row, so subtract one from the physical row count
        int rows = sheet.getLastRowNum();
        int cols = sheet.getRow(0).getPhysicalNumberOfCells();
        Object[][] data = new Object[rows][cols];

        for (int i = 1; i <= rows; i++) {
            for (int j = 0; j < cols; j++) {
                data[i - 1][j] = sheet.getRow(i).getCell(j).getStringCellValue();
            }
        }
        workbook.close();
        file.close();
        return data;
    }
}

๐Ÿ’ก Tip: Call getStringCellValue() only on cells formatted as text. A numeric cell throws IllegalStateException, which is the most common failure when a spreadsheet contains IDs or amounts. Use a DataFormatter to read every cell type as a String, or switch on cell.getCellType() and convert explicitly.

With the data externalised, adding a hundred more test cases becomes a spreadsheet edit rather than a code change, and the same provider can feed several test classes through the dataProviderClass attribute shown earlier.

FAQs

Yes. Setting @DataProvider(name="x", parallel=true) runs the rows concurrently. Each thread needs its own WebDriver instance, so store the driver in a ThreadLocal, otherwise the tests interfere with one another.

Yes. TestNG accepts Iterator<Object[]>, which supplies rows lazily instead of building the whole array in memory. This suits very large data sets or rows streamed from a database cursor.

Yes. AI tools generate boundary values, invalid inputs, and realistic synthetic records from a field specification. Review the output for duplicates and confirm that expected results are correct before adding rows to the provider.

AI assistants flag redundant data rows that exercise the same code path, cluster failures by shared input characteristics, and suggest self-healing locators when the application markup changes.

Values in testng.xml are always read as text. TestNG converts them to the declared argument type, so a non-numeric value bound to an int argument fails. Match the XML value to the method signature, or accept a String and parse it.

Summarize this post with: