ScreenshotNeo

BlogGuides

TestNG Annotations for Selenium WebDriver: A Practical Guide

Learn how TestNG annotations organize Selenium setup, teardown, data-driven tests, and parallel execution, with Java examples and practical troubleshooting.

By the ScreenshotNeo team4 October 20269 min read

TestNG owns test execution and lifecycle; Selenium WebDriver controls the browser. For an isolated browser session for each test method, create the driver in @BeforeMethod and call driver.quit() in @AfterMethod. Use @DataProvider to run the same test method with multiple input rows. Choose a wider lifecycle hook only when sharing setup or browser state is intentional.

This guide shows the annotation scopes, a practical Java pattern, data-driven tests, XML parameters, execution notes, and fixes for common failures. The examples are illustrative; match dependency and annotation details to the versions in your project.

1. What TestNG annotations do in a Selenium test

TestNG marks test methods and controls when configuration methods run. Selenium’s WebDriver API starts and controls a browser session. A typical flow is:

  1. TestNG runs the applicable setup method.
  2. The test method uses WebDriver to navigate, interact, and assert results.
  3. TestNG runs teardown so the browser session is closed.

This separation helps make lifecycle decisions explicit: annotation scope determines how often setup and cleanup run, while your driver ownership determines which test owns each browser session. See the TestNG documentation and Selenium’s guides to test organization and WebDriver.

2. Choose an annotation by lifecycle scope

Annotation When it runs Typical Selenium use
@BeforeSuite / @AfterSuite At suite boundaries Suite-wide prerequisites or cleanup. Avoid putting an individual test’s browser session here unless the suite intentionally shares that lifetime.
@BeforeTest / @AfterTest Around methods associated with a <test> element in testng.xml Configuration for an XML test grouping. “Test” here means the XML concept, not one Java method annotated with @Test.
@BeforeGroups / @AfterGroups Before the first and after the last relevant method in named groups Group-specific prerequisites or cleanup.
@BeforeClass / @AfterClass Before the first and after all test methods in a class Class-wide setup. A shared driver may reduce startup work, but methods then share session state and need careful isolation.
@BeforeMethod / @AfterMethod Before and after each test method A straightforward default for one browser session per test method.
@Test Marks a test method or class Holds test actions and assertions; can specify groups, dependencies, provider names, and other attributes.
@DataProvider Supplies argument rows to a test Runs a test method for multiple input cases.
@Parameters Maps named XML parameters to methods or constructors Supplies environment or configuration values from testng.xml.
@Listeners Registers listener classes for TestNG events Reporting or suite event handling. Annotation transformers have registration timing constraints; follow the TestNG documentation for that case.

For ordinary UI tests, method-scoped setup and teardown make it easier to reason about independence: each test gets a fresh browser session, and cleanup belongs to the same method boundary. Class-scoped setup can be appropriate when shared state is deliberate, but one test can then affect another through cookies, local storage, open tabs, or navigation.

3. Create and close a driver for each test method

The following example shows the common lifecycle pattern. It assumes Selenium and TestNG dependencies, a compatible browser and driver setup, and a test page whose expected title is Login. Exact dependency versions and driver provisioning depend on your project.

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 LoginTest {
    private WebDriver driver;

    @BeforeMethod
    public void setUp() {
        driver = new ChromeDriver();
    }

    @Test
    public void loginPageHasExpectedTitle() {
        driver.get("https://example.test/login");
        Assert.assertEquals(driver.getTitle(), "Login");
    }

    @AfterMethod(alwaysRun = true)
    public void tearDown() {
        if (driver != null) {
            driver.quit();
            driver = null;
        }
    }
}

The null check covers cases where setup fails before assigning a driver. alwaysRun = true is available for after-configuration methods so cleanup can still run when earlier methods failed or were skipped. Confirm annotation attributes against the TestNG version used by your project.

Use quit() when a session is finished: Selenium documents that it closes the session’s associated windows and ends the browser and driver processes. close() closes the current window; it is not a substitute for ending the full session when several windows are open. See Selenium’s window guidance and driver guidance.

4. Run the same test with a DataProvider

A provider returns argument rows, and a test selects it by name. This example passes a username and password to each invocation; in a real suite, use appropriate test credentials and expected outcomes.

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

public class LoginDataTest {
    @DataProvider(name = "credentials")
    public Object[][] credentials() {
        return new Object[][] {
            {"valid-user", "valid-password"},
            {"locked-user", "valid-password"}
        };
    }

    @Test(dataProvider = "credentials")
    public void loginCases(String username, String password) {
        // Use the current test's WebDriver session to submit these values.
        // Assert the expected result for this row.
    }
}

This is the provider shape for two method arguments. The versioned TestNG DataProvider API also documents iterator forms such as Iterator<Object[]>. Select the return shape supported by the TestNG version in your build and by how you produce test data. Keep each row’s values aligned with the test method’s argument count, order, and compatible types.

For browser tests, combine data providers with per-method driver ownership when each invocation must be isolated. If enabling parallel provider execution, treat every concurrent invocation as requiring its own WebDriver session. Do not let concurrent invocations mutate one shared driver unless your project has an explicit, verified ownership design.

5. Pass configuration through testng.xml

Use @Parameters when a small set of named values comes from the XML suite configuration rather than a row-based data provider. The names in XML and the annotation must match; Java arguments are received in declared method order.

<suite name="UI suite">
  <test name="staging login">
    <parameter name="baseUrl" value="https://example.test"/>
    <classes>
      <class name="example.LoginEnvironmentTest"/>
    </classes>
  </test>
</suite>
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Parameters;
import org.testng.annotations.Test;

public class LoginEnvironmentTest {
    private WebDriver driver;
    private final String baseUrl;

    @Parameters("baseUrl")
    public LoginEnvironmentTest(String baseUrl) {
        this.baseUrl = baseUrl;
    }

    @BeforeMethod
    public void setUp() {
        driver = new ChromeDriver();
    }

    @Test
    public void canOpenLoginPage() {
        driver.get(baseUrl + "/login");
        // Add assertions for the page under test.
    }

    @AfterMethod(alwaysRun = true)
    public void tearDown() {
        if (driver != null) {
            driver.quit();
            driver = null;
        }
    }
}

This constructor-parameter example assumes TestNG can resolve the XML parameter for the class in the suite. If you need optional values, consult the TestNG documentation for defaults and parameter rules applicable to your version. Use a @DataProvider instead when the goal is multiple test cases rather than one configuration value.

6. Run TestNG with Selenium dependencies

Your build needs the TestNG and Selenium Java dependencies, plus a browser and driver arrangement suitable for the execution environment. Selenium’s installation guide covers Java library setup. The TestNG Maven guide shows examples that vary by JDK; its example versions are not universal recommendations, so check the current documentation and your project’s Java baseline.

Maven Surefire can run TestNG tests. Surefire configuration is version- and mode-dependent; its current documentation describes a JUnit Platform path with specific version requirements. Do not apply those requirements to every Surefire/TestNG setup. Consult the Surefire TestNG documentation for the execution mode you use.

A typical local workflow is:

  1. Add compatible Selenium and TestNG dependencies to the build.
  2. Make a browser available and configure driver provisioning for the environment.
  3. Put tests in the source layout and naming pattern discovered by your runner.
  4. Run the project’s test command or its configured TestNG suite XML.
  5. Check the report and process output, especially when setup or teardown fails.

7. Parallel execution and browser isolation

TestNG supports parallel and parameterized execution configurations, including data-provider options. Parallelism can shorten elapsed suite time, but it increases simultaneous browser sessions and resource demand. Selenium sessions represent browser state; TestNG does not make a shared mutable WebDriver safe for concurrent use.

  • Give each concurrent test invocation a driver it owns.
  • Keep driver references isolated by invocation rather than storing one shared static driver.
  • Close each owned session in its corresponding teardown, including after failures.
  • Limit parallel concurrency to what the machine or remote browser service can support.
  • Check that test data, accounts, and server-side fixtures do not conflict across parallel cases.

Use class-scoped drivers only when the suite intentionally shares a browser and the tests are designed around that state. This trades less repeated browser startup for tighter coupling between methods and more complicated recovery after a failed test.

8. Troubleshooting common problems

Symptom Likely cause What to check or change
Browser does not start Browser/driver setup is unavailable or incompatible with the environment. Check the browser installation, driver provisioning, execution permissions, and Selenium setup for the project environment.
Later tests see stale page state A driver or browser state is shared beyond the intended test boundary. Use method-scoped setup and teardown for isolated sessions, or explicitly reset the state you intend to share.
Browser processes remain after a run The session was not quit, or teardown did not handle a failure path. Call quit() in teardown; make cleanup resilient to a null driver and review configuration-method failures.
Provider invocation fails with argument mismatch Provider rows do not match the test method’s parameter count, order, or types. Compare every row with the method signature and confirm the provider name matches dataProvider.
XML parameter is missing Parameter name, XML scope, or suite selection does not match the annotated constructor or method. Verify the spelling and placement of the <parameter> and that the intended XML suite is being run.
Parallel tests interfere with one another They share a mutable driver, account, or test data. Give each invocation its own driver and isolate conflicting data, or reduce parallel execution.
Cleanup is skipped after a failure Teardown configuration does not run for the failure/skip path in the project’s configuration. Review the TestNG version and after-configuration attributes; use the documented alwaysRun behavior where appropriate.
TestNG tests are not discovered by Maven Runner, plugin version, suite configuration, naming, or provider mode does not match the project setup. Inspect Surefire’s TestNG documentation for the exact plugin version and execution path configured in the build.

9. Performance, reliability, and cost tradeoffs

A fresh browser per method pays browser startup cost more often, but reduces state leakage and makes failures easier to reproduce. A shared class-level browser can reduce repeated startup, but adds state management and can make test order matter. Parallel execution may reduce elapsed time while increasing CPU, memory, browser-process, and remote-session demand. Measure these tradeoffs in your own environment; no universal speed ratio follows from the annotation choice.

For reliability, prioritize independent tests, deterministic setup, and guaranteed session cleanup. When running on remote WebDriver infrastructure, ending the session also releases it for reuse. A failed test should still leave enough output to identify whether the failure occurred during driver startup, navigation, assertion, or teardown.

For direct browser automation, cost depends on the browser infrastructure and runtime your team operates. If the task is to capture a page image rather than interactively test behavior, a screenshot API can avoid maintaining browser setup for that capture workflow.

10. Or skip the browser setup

If your goal is a page screenshot rather than a Selenium interaction test, ScreenshotNeo provides a website screenshot API and MCP server for developers. One GET request returns a PNG, JPEG, WebP, or PDF. See the ScreenshotNeo API documentation for request 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}`);

Cookie and consent banners are accepted before capture, and more than 60 known consent platforms, newsletter popups, and chat widgets can be removed; each step can be turned off. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers report the page verdict and billing status. The MCP server lets AI agents use take_screenshot, get_page_info, and capture_pdf. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 shots.

Sign up for ScreenshotNeo’s free 1,000 screenshots a month with no card.

11. Frequently asked questions

Is @BeforeTest the same as @BeforeMethod?

No. @BeforeTest applies around methods associated with an XML <test>; @BeforeMethod runs before each Java test method.

Should I call close() or quit()?

Use quit() to end the WebDriver session and close its associated windows. Use close() when you specifically mean to close the current window while continuing with the session.

When should I use a DataProvider rather than XML parameters?

Use a provider for multiple rows of test inputs. Use named XML parameters for suite-level configuration values such as a base URL.

Can I use annotations on a TestNG test class?

TestNG supports marking a class with @Test. Check the TestNG documentation for how class-level attributes apply to your suite and version.

Sources