~/beginnersguidefortesting ← README

The TestNG Ladder

A field guide to building a Selenium + TestNG framework one deliberate step at a time, from a five-line script to a base class other tests can inherit.

Most TestNG write-ups open with a diagram of nine annotations and expect you to memorize it before you've run a single test. This guide goes the other way. You write a script that does one thing, watch it pass, then take away exactly one convenience and rebuild it properly. By the fourth pass you'll have a small framework, and you'll understand every line of it, because you're the one who put it there.

setup

Two things need to be installed before any of this runs: the Java JDK and Maven.

Confirm both from the command line before you touch a test file:

shell
java -version
mvn -version

If either command prints a version number, you're set. If not, fix your PATH first — a broken toolchain is the most common reason a "simple" first test won't run, and it has nothing to do with your test code.

execution order

TestNG runs your setup and teardown code in a strict, scoped order: a suite contains tests, a test contains classes, a class contains methods. Nine annotations mark those boundaries, and they fire in this sequence:

  1. @BeforeSuite runs once, before anything else in the suite.
  2. @BeforeTest runs before any method in the classes under this <test> tag.
  3. @BeforeClass runs before the first test method in the current class.
  4. @BeforeMethod runs before each individual test method.
  5. @Test is the method itself.
  6. @AfterMethod runs after each test method.
  7. @AfterClass runs once every test method in the class has finished.
  8. @AfterTest runs once every class under the <test> tag has finished.
  9. @AfterSuite runs once, after the whole suite finishes.
SUITE TEST CLASS METHOD 1 @BeforeSuite 2 @BeforeTest 3 @BeforeClass 4 @BeforeMethod 6 @AfterMethod 7 @AfterClass 8 @AfterTest 9 @AfterSuite @Test 5 repeats per @Test method
Execution nests like brackets: Suite wraps Test wraps Class wraps Method, and the @Test sits at the center. BeforeMethod and AfterMethod (steps 4 and 6) repeat once for every test method in the class — everything else fires once per scope.

These annotations are also inherited. Put @BeforeClass on a superclass and every subclass honors it without redeclaring anything — that's exactly how the base class in step 4 below hands a working WebDriver to every test that extends it.

three more annotations

Parameters can come from three places, and it's worth knowing which one you're looking at when you read a report: values declared in testng.xml, values generated by a @DataProvider, and whatever value actually landed in the test report next to a given result.

rule that survives every refactor

Never declare test data as a class-level variable. It reads fine at ten tests. At a hundred, spread across a dozen packages, nobody can tell which method owns which value, and changing one input means grepping the whole codebase to find what else depends on it. Isolate it from day one.

step 1 — the rough draft

com.guide.beginners.testng.theinternet.individualtestclass.Step1

Before designing anything, write a test that achieves one simple goal and prove the interaction works end to end. This step is rough on purpose. It exists to confirm the browser flow is correct before you spend a minute on structure.

step 2 — pulling data out

com.guide.beginners.testng.theinternet.individualtestclass.Step2

Data and page objects separate from the test itself, though everything is still declared private at the class level here. You're not fully isolating it yet, but you've started chaining TestNG annotations to control setup order instead of doing it all inline.

step 3 — isolating data for real

com.guide.beginners.testng.theinternet.individualtestclass.Step3

Test data moves out of the class entirely and into src/test/resources/testngsuite/seleniumsuite.xml, passed in through TestNG's own parameter system. This is where @Optional earns its keep: it marks a parameter as skippable and gives it a default (or null) when the suite file doesn't supply one.

step 4 — a base class worth inheriting

com.guide.beginners.testng.theinternet.frameworktestng.base

Driver setup and teardown move to a base class: @BeforeClass and @AfterClass own the WebDriver's lifecycle, and @BeforeMethod owns per-test setup: launching the URL, resetting whatever state needs resetting. Each test class becomes a scenario with one or more @Test cases; it just extends this base and inherits the plumbing for free.

running in parallel

TestNG can spread your tests across threads five different ways, and each one solves a different problem:

testng.xml
<suite name="My suite" parallel="methods" thread-count="5">

For suite-file-level parallelism specifically, the thread pool size is a command-line flag rather than an XML attribute:

shell
java org.testng.TestNG -suitethreadpoolsize 3 testng1.xml testng2.xml testng3.xml

One detail worth flagging in your own docs: @Test's timeOut attribute works the same way in both parallel and non-parallel runs, so it's one less thing to re-check when you change the parallel mode above.

running from the command line

shell
mvn clean test -DsuiteXmlFiles=CrossBrowserParallelSuite.xml

Swap the file name for anything under src/test/resources/testngsuite/ and Maven's Surefire plugin picks it up — that property is already wired into this project's pom.xml.

selenium grid

Local runs are fine until you need more than one browser, or you want the suite running somewhere that isn't your laptop. Grid solves that: a hub takes your test's requests and hands them to registered nodes, which is where the browsers actually launch. Everything below runs from /src/test/resources/binaries.

Standard configuration, no config file:

hub
java -jar selenium-server-standalone.jar -role hub
node
java -jar selenium-server-standalone.jar -role webdriver -hub http://192.168.0.5:4444/grid/register/

Custom configuration, with a config file per role:

hub
java -jar selenium-server-standalone.jar -role hub --hubConfig hubconfig.json
node
java -Dwebdriver.chrome.driver="chromedriver.exe" \
     -Dwebdriver.ie.driver="IEDriverServer.exe" \
     -Dwebdriver.gecko.driver="geckodriver.exe" \
     -jar selenium-server-standalone.jar -role node -nodeConfig nodeconfig.json

That's the standalone-jar version of Grid. This project pins selenium-server-standalone-3.141.59.jar, and it still runs fine for a couple of nodes on one machine. If you're setting Grid up fresh today, look at Selenium Grid 4 instead. It keeps the hub/node topology for real distributed setups but adds a single-process "standalone" mode for exactly this one-machine case, shipped as one jar or a Docker Compose file instead of separate downloads — the more natural next step once you outgrow this setup.

None of this is complicated on its own. What makes it click is the order: prove the interaction works, then separate the data, then isolate the data for real, then extract the plumbing into something reusable. Skip straight to step 4 and you'll inherit a framework you can't explain. Climb it one rung at a time and you'll have built one instead.