How to Use Parameters in TestNG with Selenium
Learn when to use TestNG @Parameters or @DataProvider with Selenium, configure XML and JVM values, run data-driven tests, and fix common errors.
Use TestNG @Parameters for a small set of named configuration values, such as browser, base URL, locale, or environment. Use @DataProvider when the test must run against multiple rows, generated values, files, databases, or complex objects. In Selenium, the received values determine how the driver starts and which page the test opens.
TestNG supports parameters from testng.xml, programmatic values, and Java system properties. Parameter names must match exactly, and values are assigned in the order listed by @Parameters. The official documentation covers this model in TestNG parameters and data providers.
1. A complete Selenium example with @Parameters
The following test accepts a browser and URL from testng.xml. It uses Selenium Manager, included with modern Selenium 4 releases, to locate the browser driver.
package tests;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.testng.Assert;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.Optional;
import org.testng.annotations.Parameters;
import org.testng.annotations.Test;
public class HomeTest {
private WebDriver driver;
private WebDriver createDriver(String browser) {
switch (browser.toLowerCase()) {
case "firefox":
return new FirefoxDriver();
case "chrome":
return new ChromeDriver();
default:
throw new IllegalArgumentException("Unsupported browser: " + browser);
}
}
@Parameters({"browser", "baseUrl"})
@Test
public void openHomePage(String browser, String baseUrl) {
driver = createDriver(browser);
driver.get(baseUrl);
Assert.assertFalse(driver.getTitle().trim().isEmpty(), "The page title should not be empty");
}
@AfterMethod(alwaysRun = true)
public void closeBrowser() {
if (driver != null) {
driver.quit();
}
}
}
Place the values in a TestNG suite file:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="UI suite">
<parameter name="browser" value="chrome"/>
<parameter name="baseUrl" value="https://example.test"/>
<test name="smoke">
<classes>
<class name="tests.HomeTest"/>
</classes>
</test>
</suite>
Run the suite with your build tool, or pass the file to TestNG from your IDE. The annotation names and XML names must match character for character.
2. Parameter sources and scope
testng.xml
XML is the usual choice for environment settings that change between runs. TestNG permits parameters at suite, test, class, and methods scopes. A narrower scope overrides a broader one in this order:
<suite>provides the broadest default.<test>overrides the suite value for one test group.<class>overrides the value for one class.<methods>overrides the value for selected methods.
<suite name="cross-browser">
<parameter name="baseUrl" value="https://staging.example.test"/>
<test name="firefox tests">
<parameter name="browser" value="firefox"/>
<classes><class name="tests.HomeTest"/></classes>
</test>
</suite>
JVM system properties
System properties are useful in CI because the same XML file can run against different values:
mvn test -Dbrowser=firefox -DbaseUrl=https://staging.example.test
When a JVM property has the same name as a TestNG parameter, it can override the configured value for that run. Keep secrets out of committed XML; inject credentials through your CI secret store or environment and read them in Java.
Programmatic values
If you create a TestNG runner in Java, you can add values to the runner before execution. This is useful when a build tool or custom launcher computes the environment dynamically. Keep the parameter names identical to the names used by the test methods.
3. Defaults with @Optional
Use @Optional when a value may be absent:
@Parameters("browser")
@Test
public void smoke(@Optional("chrome") String browser) {
WebDriver driver = createDriver(browser);
try {
driver.get("https://example.test");
} finally {
driver.quit();
}
}
If TestNG cannot find the named parameter, it supplies the value from @Optional. A default prevents a missing optional setting from failing the invocation, but do not use it to hide a required environment error.
4. Passing more configuration to Selenium
Parameters can select credentials, locale, viewport, or feature flags in addition to the browser:
@Parameters({"browser", "baseUrl", "locale", "headless"})
@Test
public void localizedPage(String browser, String baseUrl, String locale, String headless) {
WebDriver driver = createDriver(browser);
try {
driver.get(baseUrl + "?lang=" + locale);
System.out.println("Headless setting requested: " + headless);
} finally {
driver.quit();
}
}
All XML values arrive as strings. Convert them explicitly and validate them early:
boolean runHeadless = Boolean.parseBoolean(headless);
if (!locale.matches("[a-z]{2}(-[A-Z]{2})?")) {
throw new IllegalArgumentException("Invalid locale: " + locale);
}
For actual headless execution, construct browser options inside createDriver based on the validated value. Keep driver construction in one place so every test uses the same rules.
5. Use @DataProvider for repeated test data
XML parameters represent named configuration. They are not a good fit for a table of logins or URLs. A data provider returns rows, and each row invokes the test once:
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
public class LoginTest {
@DataProvider(name = "loginCases")
public Object[][] loginCases() {
return new Object[][] {
{"alice", "correct-password"},
{"bob", "another-password"}
};
}
@Test(dataProvider = "loginCases")
public void login(String username, String password) {
// Create an isolated driver, open the login page, and exercise the case.
System.out.println(username + " / " + password);
}
}
Each Object[] row maps positionally to the test method arguments. Providers may also return an iterator, accept injected Method or ITestContext values, or live in another class:
@Test(dataProvider = "loginCases", dataProviderClass = LoginData.class)
public void login(String username, String password) {
// test body
}
Use a data provider when values come from Java, a CSV file, a database, generated combinations, or any source more complex than a few environment strings. You can combine both approaches: use @Parameters for baseUrl and a @DataProvider for test rows.
6. Parallel data providers and Selenium isolation
TestNG data providers run sequentially by default. Enable concurrency with parallel = true:
@DataProvider(name = "urls", parallel = true)
public Object[][] urls() {
return new Object[][] {
{"https://example.test/one"},
{"https://example.test/two"}
};
}
Every generated invocation must have isolated mutable state. Create one WebDriver per invocation, never share a driver between rows, avoid mutable static page objects, and always call quit() in an alwaysRun teardown. A ThreadLocal<WebDriver> factory can work for thread-bound frameworks, but it must remove and quit the driver after each invocation. Parallel browsers consume more CPU, memory, and grid capacity, so choose the concurrency level from the available infrastructure rather than setting it arbitrarily.
7. Common errors and fixes
| Error or symptom | Cause | Fix |
|---|---|---|
Parameter 'browser' is required by @Configuration... but has not been defined |
The XML name is missing or spelled differently. | Add the parameter at the correct scope and make the spelling match @Parameters; use @Optional only when absence is valid. |
| The wrong value reaches an argument | Annotation names are mapped in order. | Put names in the same order as the Java method signature. |
| A class receives a value from another test | A broader suite value is being inherited. | Check suite, test, class, and methods scopes; the narrower scope wins. |
| Data provider not found | The provider name differs from the @Test(dataProvider=...) value or the class is not specified. |
Match the names exactly or add dataProviderClass. |
| Parallel tests fail intermittently | Invocations share a WebDriver, cookies, files, or mutable test data. | Create and clean up state per invocation; isolate temporary resources. |
| Browser driver cannot start | Browser is missing, incompatible, or unavailable in the CI environment. | Install a supported browser, use Selenium Manager or a managed driver path, and print browser/version details in CI logs. |
| Credentials appear in reports or logs | Parameters are shown in TestNG reports and may be logged by the test. | Pass secret references instead of secret values where possible, mask logs, and review report retention. |
TestNG reports include the invocation parameters, which helps diagnose configuration mistakes. Treat those reports as sensitive when parameters contain private data.
8. Performance, reliability, and cost considerations
- Reuse immutable configuration, but do not reuse browser sessions between independent tests unless shared state is intentional.
- Use explicit waits for page conditions instead of fixed sleeps; this reduces idle time and timing failures.
- Limit parallelism to the capacity of the local machine or Selenium Grid. More workers can increase queueing and browser crashes.
- Keep data providers deterministic and record the row inputs so a failed invocation can be reproduced.
- Run a small smoke set on every change and the full matrix on a scheduled or release pipeline.
- Browser automation costs come from machine, grid, and CI time. Parameterizing browsers multiplies those costs by the number of combinations, so select a matrix that reflects supported environments.
Or skip the browser setup
If your goal is to capture a page image or PDF rather than interact with it, ScreenshotNeo provides a single HTTP request. Its API accepts the URL and returns PNG, JPEG, WebP, or PDF; the ScreenshotNeo API documentation lists the options.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
ScreenshotNeo accepts cookie and consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be disabled. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing result. It also provides an MCP server with take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.
FAQ
Can one test method receive both XML parameters and a data-provider row?
Yes. TestNG can inject configured parameters alongside data-provider arguments, but keep the method signature and injection rules explicit. Use XML values for environment configuration and provider columns for row data.
Should browser names be uppercase?
No requirement exists. Normalize the value, as the example does with toLowerCase(), and reject unsupported names with a clear exception.
Where should a provider live?
It can be in the test class or a separate class referenced with dataProviderClass. A separate class is useful when several tests share the same dataset builder.
How do I run the same XML suite against several browsers?
Create separate <test> blocks with different browser values, or supply the value as a JVM property from separate CI jobs.


