JUnit and TestNG Examples for Selenium Tests
Build Selenium tests in Java with JUnit Jupiter or TestNG, including setup, assertions, cleanup, Maven configuration, and practical troubleshooting.
Selenium WebDriver controls a browser; JUnit or TestNG organizes the tests, runs setup and cleanup, and provides assertions. The examples below use Java, Maven, and Chrome. Each test opens a browser, checks the Selenium sample form, and quits the browser even if the test fails.
1. Choose a runner and prepare the project
Use the framework already supported by your Java project unless you need a specific capability or convention. Selenium’s Java guide includes JUnit and TestNG among its runner choices. TestNG documents data providers and parallel execution configuration; consider those when they meet a concrete suite need. Neither framework is universally best.
| Need | JUnit Jupiter | TestNG |
|---|---|---|
| Per-test setup and cleanup | @BeforeEach, @AfterEach |
@BeforeMethod, @AfterMethod |
| Test method | @Test |
@Test |
| Assertions | JUnit assertion methods such as assertEquals(expected, actual) |
Assert.assertEquals(actual, expected) |
| Inputs and parallelism | Use the facilities supported by your JUnit and build setup | TestNG documents data providers and parallel configuration |
Install Selenium through Maven or Gradle as described in the Selenium library installation guide. The dependency versions below are placeholders: choose versions compatible with your project and consult current project documentation before pinning them. Selenium’s release information changes over time.
Maven dependencies for JUnit Jupiter
<properties>
<maven.compiler.release>17</maven.compiler.release>
<selenium.version>YOUR_SELENIUM_VERSION</selenium.version>
<junit.version>YOUR_JUNIT_VERSION</junit.version>
</properties>
<dependencies>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>${selenium.version}</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>YOUR_SUREFIRE_VERSION</version>
</plugin>
</plugins>
</build>
Maven dependencies for TestNG
For a TestNG project, use TestNG in place of JUnit and configure the Maven test runner for the project’s chosen TestNG version. Keep Selenium as a test dependency if it is only used by tests.
<properties>
<maven.compiler.release>17</maven.compiler.release>
<selenium.version>YOUR_SELENIUM_VERSION</selenium.version>
<testng.version>YOUR_TESTNG_VERSION</testng.version>
</properties>
<dependencies>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>${selenium.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>${testng.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
Use a TestNG-aware runner configuration for your build. Exact plugin and runner settings depend on the project and selected versions; follow the current TestNG documentation and the build’s existing conventions rather than assuming that adding the dependency alone configures execution.
2. Selenium test with JUnit Jupiter
Put this class under src/test/java. It follows Selenium’s documented lifecycle pattern: create the driver before each test and quit it afterward. The form interaction checks both page navigation and a visible result.
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
class SeleniumFormJUnitTest {
private WebDriver driver;
@BeforeEach
void setUp() {
driver = new ChromeDriver();
}
@Test
void submitsTheSampleForm() {
driver.get("https://www.selenium.dev/selenium/web/web-form.html");
assertEquals("Web form", driver.getTitle());
driver.findElement(By.name("my-text")).sendKeys("Selenium");
driver.findElement(By.cssSelector("button")).click();
assertTrue(driver.getPageSource().contains("Received!"));
}
@AfterEach
void tearDown() {
if (driver != null) {
driver.quit();
}
}
}
Run it with mvn test from the project root. The command asks Maven to run tests configured for that project; it does not prove a particular test ran unless the output reports that test and its result.
3. Selenium test with TestNG
This class uses the same browser actions with TestNG lifecycle annotations and assertions. It is an adaptation of the shared Selenium workflow; check annotation options against the TestNG version selected for your project.
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.testng.Assert;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
public class SeleniumFormTestNGTest {
private WebDriver driver;
@BeforeMethod
public void setUp() {
driver = new ChromeDriver();
}
@Test
public void submitsTheSampleForm() {
driver.get("https://www.selenium.dev/selenium/web/web-form.html");
Assert.assertEquals(driver.getTitle(), "Web form");
driver.findElement(By.name("my-text")).sendKeys("Selenium");
driver.findElement(By.cssSelector("button")).click();
Assert.assertTrue(driver.getPageSource().contains("Received!"));
}
@AfterMethod(alwaysRun = true)
public void tearDown() {
if (driver != null) {
driver.quit();
}
}
}
The alwaysRun setting makes the cleanup method eligible to run even when a preceding configuration or test method failed, subject to TestNG’s lifecycle behavior. The null check also handles a driver that was never successfully created.
4. Make tests reliable and isolated
Driver lifecycle
- Create a new WebDriver per test when independent browser state matters. This avoids cookies, local storage, or navigation state leaking from one test into another.
- Call
quit()in teardown to end the whole WebDriver session. Use a null check because browser startup itself can fail. - Avoid sharing a mutable driver across parallel tests. Each concurrent test should own its own session.
Wait for observable conditions
Pages and network activity are asynchronous. Avoid fixed sleeps as the default synchronization strategy: they slow fast runs and can still be too short on slow ones. For a result element, wait for the condition the test needs:
import java.time.Duration;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
new WebDriverWait(driver, Duration.ofSeconds(10))
.until(ExpectedConditions.titleIs("Web form"));
Choose a condition that represents readiness for the next action, such as visibility, clickability, or a changed title. Keep the timeout bounded and meaningful for the environment.
Assertions and failure diagnosis
Assert user-visible outcomes, not merely that a click command completed. Include useful expected and actual values in assertion failures. When a test fails, inspect the failing locator, current URL, title, and available browser or driver logs before increasing timeouts.
5. Parameterized inputs and parallel runs
TestNG documents data providers for feeding multiple input sets to a test and configuration for parallel execution. A small illustrative provider looks like this:
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
public class SearchInputsTest {
@DataProvider(name = "queries")
public Object[][] queries() {
return new Object[][] {
{ "Selenium" },
{ "WebDriver" }
};
}
@Test(dataProvider = "queries")
public void acceptsAQuery(String query) {
// Create or obtain a driver for this invocation,
// navigate, exercise the page, and assert its result.
}
}
This example only shows input delivery. Do not add parallel execution until each invocation has independent driver state and the target environment can handle concurrent sessions. Consult the TestNG documentation for the selected version’s data provider and parallel configuration details. JUnit lifecycle choices also matter: @BeforeAll and @AfterAll are normally static unless using a per-class test-instance lifecycle. For most browser tests, per-test setup and cleanup are easier to isolate.
6. Troubleshooting common failures
| Symptom | Likely cause | What to check or change |
|---|---|---|
| No tests discovered | Class naming, source placement, provider configuration, or test engine mismatch | Place the class in the test source tree, check the runner and naming conventions, and confirm the correct JUnit engine or TestNG provider is configured. |
| Driver cannot start | Browser unavailable, incompatible environment, or driver startup problem | Check that the browser is installed and runnable in the environment, then review Selenium’s current setup guidance and startup error details. |
| Element not found | Wrong locator, page not ready, or unexpected navigation | Confirm the current URL and DOM, verify the locator, and wait for the specific element condition before interacting. |
| Click or assertion fails intermittently | Timing dependency or shared browser state | Wait for an observable condition and isolate browser sessions and test data. |
| Browser remains running after failure | Teardown did not execute or cleanup lacked a null guard | Use the framework’s cleanup hook, guard a possibly uninitialized driver, and call quit(). |
| Parallel tests interfere | Driver or mutable test data shared between invocations | Give each test invocation its own driver and data; reduce or disable parallelism if the environment cannot sustain it. |
| Maven reports compilation errors | Dependency versions, Java target, imports, or framework API mismatch | Align Java and library versions with the project, confirm imports and runner configuration, and consult current official docs. |
7. Performance, reliability, and cost
Browser startup and page loading usually dominate a small test’s runtime. Reuse a driver only when the suite deliberately accepts shared state and guarantees safe sequencing; otherwise, per-test sessions improve isolation at a startup cost. Parallelism can shorten wall-clock time, but increases concurrent browser and application load and can expose shared-state bugs. Stable waits and deterministic test data improve reliability more than arbitrary long sleeps.
Local Selenium test cost depends on the machines and browser infrastructure you run. Remote browser grids or hosted environments have their own pricing and limits; these examples make no cost or speed claims about them. Keep timeouts finite, ensure teardown executes, and collect enough failure context to distinguish application defects from environment problems.
8. Capture a screenshot when a test fails
A browser screenshot can help explain a visual or navigation failure. Selenium can save a screenshot from the current driver session:
import java.nio.file.Files;
import java.nio.file.Path;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.TakesScreenshot;
byte[] png = ((TakesScreenshot) driver).getScreenshotAs(OutputType.BYTES);
Files.write(Path.of("target", "failure.png"), png);
Call this before driver.quit(), typically from a failure hook supported by your test framework. Create the destination directory if your build does not create it. A screenshot captures the browser’s current visual state; it does not replace logs or assertions.
Or skip the browser setup
For a standalone page capture outside a test’s interactive browser session, ScreenshotNeo provides a website screenshot API and MCP server. Its API can return PNG, JPEG, WebP, or PDF, with options including full-page capture, selected elements, custom viewport and device presets, and waits for a selector or network idle. It does not replace WebDriver when the test needs to click through an application or assert its live state.
See the ScreenshotNeo API documentation. Example request:
curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://www.selenium.dev/selenium/web/web-form.html \
-o shot.webp
Cookie banners, newsletter popups, and chat widgets are removed before the shot. Bot checks, blank pages, and failed loads are never billed; response headers identify the page verdict and billing status. An MCP server lets AI agents use screenshot tools. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots.
Sign up free for 1,000 screenshots a month, with no card.
Frequently asked questions
Does Selenium provide the assertions?
No. WebDriver automates browser communication; JUnit or TestNG supplies the test runner and assertion APIs.
Should a test use ChromeDriver directly?
It is a concise starting point for a Chrome example. A larger suite can inject drivers or use a shared factory when that fits its environment and isolation model.
Can I use both JUnit and TestNG in one project?
Builds can be configured with multiple test frameworks, but keep test discovery and runner configuration explicit. Most suites are simpler when they follow one framework’s conventions.
Where can I confirm Selenium’s current Java setup?
Use the Selenium project’s execution guide and installation guide, then align them with the versions and build configuration in your repository.


