QA Interview Hub · Knowledge base · Quizzes
Selenium + Java
Framework and runs
JUnit 5 versus TestNG
Selenium only drives the browser; a test framework runs the tests, prepares data and gathers reports. Two are common in Java: JUnit 5 and TestNG.
| JUnit 5 | TestNG |
| Setup/teardown | @BeforeEach, @AfterEach, @BeforeAll, @AfterAll (static unless PER_CLASS) | @BeforeMethod, @AfterMethod, @BeforeClass, @BeforeSuite, @BeforeTest |
| Parameterisation | @ParameterizedTest + @ValueSource, @CsvSource, @MethodSource | @DataProvider (can be parallel = true) |
| Grouping | @Tag + filters in the build | groups, testng.xml |
| Test dependencies | deliberately none (tests should be independent) | dependsOnMethods, dependsOnGroups |
| Extension | the Extension API model (@ExtendWith, TestWatcher, ParameterResolver) | listeners (ITestListener, IRetryAnalyzer) |
| Parallelism | junit-platform.properties | parallel and thread-count attributes in testng.xml |
@ParameterizedTest
@CsvSource({"admin,Admin panel", "guest,Welcome"})
void showsRoleHome(String role, String heading) {
// ...
}
New projects most often pick JUnit Jupiter (JUnit 5, now JUnit 6 with the same API): a wider ecosystem, IDE and build integration, handy extensions. TestNG stays in large existing suites that need groups, dependencies and the built-in testng.xml. JUnit 5 has no built-in retry: it is added by an extension or by rerunning failed tests at the Surefire or CI level.
Every test must create its own state and not depend on order: that is the first condition of a parallel run.
Selenium Grid 4 and Docker: how it is built and how to run it
Selenium Grid routes client commands to remote browsers: parallel runs on many machines, different browser versions and operating systems. A test connects through RemoteWebDriver to one URL (port 4444 by default), and Grid does the rest.
Three Grid 4 deployment modes:
- Standalone: everything in one process; the simplest for development and small suites;
- Hub and Node: one Hub and several Nodes (each with its own browsers, possibly different operating systems);
- Distributed: components as separate processes for large scale.
Grid 4 components: Router (accepts requests and directs them), Distributor (finds a free slot on a Node), Session Map (which session is on which Node), New Session Queue (a queue of new session requests when there are no slots), Node (runs browsers), Event Bus (event exchange between components). The state is visible at /status and in the UI at /ui.
Docker. Ready images selenium/standalone-chrome, selenium/standalone-firefox, selenium/hub + node-chrome give a browser without installing it on the machine.
docker run -d -p 4444:4444 -p 7900:7900 --shm-size="2g" selenium/standalone-chrome
--shm-size (or /dev/shm) is needed because Chrome makes heavy use of shared memory; with a small size the browser crashes on heavy pages;
- port 7900 is a session view through noVNC in a browser, handy for debugging;
- by default a container runs one session (
SE_NODE_MAX_SESSIONS); the variable allows more, but container resources set the limit.
In CI Grid is usually started as a service (Docker Compose or a job service container), readiness is awaited on /status, and tests run against the Grid URL. Uploading files to a remote machine needs a LocalFileDetector.
Parallel runs: a driver per thread, isolation, CI
WebDriver is not thread-safe: one session cannot be shared between threads. The rule of parallelism: one driver per test (thread), created before the test and closed after it.
A typical trap is a static WebDriver driver in a base class or Page Object: parallel threads overwrite one reference and drive someone else's browser. Solutions:
- create the driver in
@BeforeEach (JUnit) / @BeforeMethod (TestNG) and pass it to Page Objects through the constructor;
- or keep it in a
ThreadLocal<WebDriver>, so each thread sees its own instance and remove() after the test prevents a leak;
- in JUnit 5 it is convenient to make the driver an extension with a
ParameterResolver that creates and closes it.
private static final ThreadLocal<WebDriver> DRIVER = new ThreadLocal<>();
static WebDriver driver() { return DRIVER.get(); }
@BeforeEach void start() { DRIVER.set(new ChromeDriver()); }
@AfterEach void stop() { DRIVER.get().quit(); DRIVER.remove(); }
Enabling parallelism: in JUnit 5, junit.jupiter.execution.parallel.enabled=true and mode.default=concurrent in junit-platform.properties; in TestNG, parallel="methods" (or classes, tests) and thread-count in testng.xml.
Parallelism demands data isolation: tests do not share accounts, orders, files. Otherwise flakes appear that do not reproduce one at a time. The number of threads is matched to the resources of Grid or the CI runner.
In CI: headless or Grid in a container, a Selenium Manager cache between runs, artifacts (screenshots, logs, a report) kept even for failed jobs, and a rerun of only failed tests marked in the report as flaky rather than hidden.