How to Write Parameterized JUnit Tests for Selenium
Run one Selenium test with multiple inputs using JUnit Jupiter. Compare value, CSV, and method sources, with setup, lifecycle, and troubleshooting guidance.
Use JUnit Jupiter’s @ParameterizedTest with an argument source such as @ValueSource, @CsvSource, or @MethodSource. JUnit runs the test method once for each supplied case. Add junit-jupiter-params, make sure your build runs the Jupiter engine, and close every Selenium session with driver.quit().
This guide uses Java, JUnit Jupiter, and Selenium WebDriver. The examples show the test structure; replace the sample URL, selectors, and expected results with values from your application.
1. Add JUnit and Selenium dependencies
JUnit supplies parameterized invocations, while Selenium’s Java binding sends commands to the browser. Parameterized tests require the JUnit parameter-support artifact. Keep JUnit components on a consistent release, for example by using the JUnit BOM. Check current releases and Java compatibility for your project before selecting versions.
Maven
<properties>
<junit.version>5.14.1</junit.version>
<selenium.version>REPLACE_WITH_CURRENT_SELENIUM_VERSION</selenium.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.junit</groupId>
<artifactId>junit-bom</artifactId>
<version>${junit.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>${selenium.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
The JUnit BOM example uses a documented release as an illustration, not as a claim that it is the latest. Set selenium.version to a current version supported by your project. The JUnit guide describes parameterized tests and their sources in the JUnit User Guide; Selenium’s official install documentation shows Java dependency setup.
Gradle
dependencies {
testImplementation platform("org.junit:junit-bom:5.14.1")
testImplementation "org.junit.jupiter:junit-jupiter"
testImplementation "org.junit.jupiter:junit-jupiter-params"
testImplementation "org.seleniumhq.selenium:selenium-java:REPLACE_WITH_CURRENT_SELENIUM_VERSION"
}
tasks.test {
useJUnitPlatform()
}
Use your project’s current JUnit and Selenium versions in place of the illustrative versions. useJUnitPlatform() configures Gradle’s test task to discover Jupiter tests.
2. Write a parameterized Selenium test
For a small table of inputs and expected outputs, @CsvSource keeps the cases next to the test. The display name includes each query so a failed invocation is easier to identify.
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
class SearchFormTest {
@ParameterizedTest(name = "search for {0} gives {1}")
@CsvSource({
"selenium, Selenium results",
"junit, JUnit results"
})
void searchShowsExpectedHeading(String query, String expectedHeading) {
WebDriver driver = new ChromeDriver();
try {
driver.get("https://example.test/search");
driver.findElement(By.name("q")).sendKeys(query);
driver.findElement(By.cssSelector("button[type='submit']")).click();
assertEquals(expectedHeading,
driver.findElement(By.cssSelector("h1")).getText());
} finally {
driver.quit();
}
}
}
This is a schematic test, not an executable test against a live application: example.test is a placeholder. Replace it and the selectors and expected headings with your application’s actual values. A parameterized method must have a source and cannot be private or static; its parameters must match the arguments supplied by that source. See the JUnit parameterized-test API for method requirements.
3. Pick the right argument source
| Source | Best fit | Example shape |
|---|---|---|
@ValueSource |
One argument per invocation | A set of search terms or paths |
@CsvSource |
A few inline rows with multiple values | Input and expected heading |
@MethodSource |
Longer, computed, or structured cases | Java-built argument objects |
One input with @ValueSource
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
class SearchInputTest {
@ParameterizedTest(name = "query is {0}")
@ValueSource(strings = {"selenium", "junit"})
void queryIsNotBlank(String query) {
if (query.isBlank()) {
throw new AssertionError("Query must not be blank");
}
}
}
For a browser test, use the supplied value to navigate or fill a form, then assert an observable result. Value sources are intended for a single argument; use a different source when each case needs several columns.
Several inline values with @CsvSource
CSV rows map to method arguments in order. Keep the number and types of columns aligned with the method signature. CSV is convenient for a short list; for values containing complicated quoting or structures, move case construction into Java with a method source.
Constructed cases with @MethodSource
import static org.junit.jupiter.params.provider.Arguments.arguments;
import java.util.stream.Stream;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
class SearchCasesTest {
static Stream<Arguments> searchCases() {
return Stream.of(
arguments("selenium", "Selenium results"),
arguments("junit", "JUnit results")
);
}
@ParameterizedTest(name = "search for {0} gives {1}")
@MethodSource("searchCases")
void acceptsCase(String query, String expectedHeading) {
// Use query and expectedHeading in the browser actions and assertions.
}
}
Connect this method to the same Selenium actions and assertions shown in the CSV example to make it a browser test. Method sources are useful when data is computed or when inline rows have become hard to read. Follow the current guide for provider return types, visibility, conversions, and source-specific rules.
4. Manage the browser lifecycle
Creating a driver in each test invocation and quitting it in a finally block makes cleanup explicit, including when navigation or an assertion fails. Another common pattern uses JUnit hooks:
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
class BrowserTest {
private WebDriver driver;
@BeforeEach
void startBrowser() {
driver = new ChromeDriver();
}
@AfterEach
void stopBrowser() {
if (driver != null) {
driver.quit();
}
}
}
In a parameterized test, per-invocation setup and teardown provide isolation at the cost of starting a browser repeatedly. Reusing a session may reduce startup work, but then test state can leak between cases. Choose deliberately, reset state where needed, and avoid concurrent invocations sharing one driver or mutable browser state. Selenium’s Java example demonstrates JUnit lifecycle hooks and quitting the driver in its test organization guidance.
5. Run locally or on a browser grid
Start locally to validate the test and selectors. Selenium Manager is bundled with Selenium and can act as a fallback when no driver has been supplied, so a separate manual driver download is not required in every setup. Browser availability and local environment configuration still matter. See Selenium Manager documentation.
When cases need to cover different browsers or operating systems, a Selenium Grid or hosted grid can distribute execution. That adds environment and infrastructure configuration; choose it when the coverage or parallel execution need justifies the setup. Selenium’s Grid documentation describes distributed browser execution.
6. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| JUnit does not discover the test | The build is not using the Jupiter engine/platform, or parameter support is absent. | Include Jupiter dependencies and configure the build to run the Jupiter platform; for Gradle, use useJUnitPlatform(). |
| Error says no argument source is provided | @ParameterizedTest is present without a source. |
Add a compatible source annotation such as @ValueSource, @CsvSource, or @MethodSource. |
| Invocation argument count or conversion fails | The source columns do not match method parameters or cannot be converted to their types. | Match argument count and order to the signature; simplify CSV values or use a method source to construct typed arguments. |
| Browser fails to start | Browser installation, driver availability, or environment setup is missing or incompatible. | Confirm the browser is installed and review Selenium Manager output and current Selenium setup guidance. |
| Browser stays open after a failed case | Cleanup only runs on the success path. | Put quit() in finally or a JUnit @AfterEach hook. |
| Cases fail intermittently when run together | Invocations may share browser state, application data, or mutable test fixtures. | Use isolated sessions or reset state between cases; review parallel execution and shared resources. |
| Element lookup fails after navigation | The sample selector or URL does not match the real page, or the element is not ready yet. | Use selectors from the application and wait for the relevant condition before interacting; do not assume sample selectors exist. |
7. Performance, reliability, and cost
- Runtime: Starting a browser for each invocation costs startup time. A shared browser can be faster, but raises state-isolation and concurrency risks. Measure in your own suite before changing lifecycle behavior.
- Reliability: Keep each case independent, use meaningful invocation names, make cleanup unconditional, and avoid coupling test data to order. Selenium tests also depend on the browser and application environment.
- Scaling: A local run is simpler to operate; a grid adds browser and operating-system coverage and distributed execution capability with additional setup.
- Cost: Local runs use your own machine and infrastructure. Hosted grids may have provider-specific pricing; compare current terms directly because no provider pricing is established here.
8. Capture a page for visual review
Selenium is for browser automation and assertions. If a failing case needs a page image for a bug report, visual review, or a separate record, a screenshot API can capture the URL without setting up another browser script. ScreenshotNeo is a website screenshot API and MCP server for developers, made by Yorker Media. Its response identifies whether a page was clean, blocked, blank, timed out, or loaded unsuccessfully, which helps distinguish a useful capture from a failed page.
Or skip the browser setup
Make one GET request with a URL. See the ScreenshotNeo API documentation for the API details.
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}`);
- Cookie banners and consent prompts are accepted and removed before capture; newsletter popups and chat widgets are removed too. Each cleanup step can be turned off.
- Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing. Response headers report the page verdict and whether the request was billed.
- An MCP server exposes
take_screenshot,get_page_info, andcapture_pdfto Claude, Cursor, and other MCP clients. - The free plan includes 1,000 screenshots a month with no card. Paid plans start at $5 for 3,000 screenshots; every feature is on every plan.
Create a free ScreenshotNeo account to get 1,000 screenshots a month with no card.
9. Frequently asked questions
Can one parameterized method test several browsers?
Yes. Include browser choice in the case data and create the matching driver, or distribute browser coverage through a grid. Keep each invocation’s session isolated.
Should every input case get its own browser?
That is a straightforward isolation pattern and makes cleanup easy to reason about. Reuse can save startup time, but requires careful state reset and concurrency control.
Can the test data come from outside the source file?
JUnit has multiple argument-source mechanisms. For external data, choose a supported source or load it in a method source, and consult the current JUnit guide for the relevant format and rules.
Do I always need to download a browser driver manually?
No. Selenium Manager can provide a fallback when a driver is not supplied. The browser and execution environment must still be available and configured appropriately.


