TestNG XML Suite Files

8 min read

If annotations are the engine of a TestNG suite, testng.xml is the steering wheel. It answers every question about how your tests run — which classes are included, which groups are active, how many threads to use, what parameters to inject, and which tests belong in a smoke run vs a full regression. Without it, Maven's Surefire just scans for files named *Test.java and runs everything in no particular order. With it, you have precise control. This lesson covers the full XML structure, the suite attributes that matter most, and the three real-world suite files every project should ship.

The four-level hierarchy

<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="Full Regression Suite" verbose="1">
 
    <test name="Login Module">
        <classes>
            <class name="com.mycompany.tests.tests.LoginTest"/>
            <class name="com.mycompany.tests.tests.RegistrationTest"/>
        </classes>
    </test>
 
    <test name="Product Module">
        <classes>
            <class name="com.mycompany.tests.tests.ProductSearchTest"/>
            <class name="com.mycompany.tests.tests.ProductDetailTest"/>
        </classes>
    </test>
 
</suite>

The four levels: <suite><test><classes><class>. Each layer can hold its own configuration. A <parameter> at the <suite> level applies everywhere; the same <parameter> at the <test> level overrides it for that block only.

<test> does not mean "a test method". It is a named block of classes — an organisational unit. @BeforeTest / @AfterTest fire once per <test> block. When you want different setup for the "Login" block vs the "Product" block, put that setup in @BeforeTest.

Suite-level attributes

<suite
    name="Regression Suite"
    verbose="1"
    parallel="methods"
    thread-count="4"
    preserve-order="true">
AttributeWhat it controls
nameLabel in reports
verboseConsole output: 0=quiet, 1=test names, 2=method names, 10=everything
parallelnone (default), methods, classes, tests, instances
thread-countMax concurrent threads when parallel is set
preserve-ordertrue runs <class> entries in the order they appear in the XML

parallel and thread-count are covered in depth in chapter 4. For now: set parallel="none" until you've made each test independently set up and tear down its own state.

Including specific methods; excluding others

<class name="com.mycompany.tests.tests.LoginTest">
    <methods>
        <include name="testValidLogin"/>
        <include name="testInvalidCredentials"/>
        <exclude name="testForgotPassword"/>
    </methods>
</class>

Use this when a class has 20 tests but you want only 3 in a particular suite. The exclude list is handy for temporarily pulling a flaky test out of the run without deleting or disabling it in code.

Package-level auto-discovery

<test name="All Tests">
    <packages>
        <package name="com.mycompany.tests.tests"/>
    </packages>
</test>

Package discovery is almost always better than listing individual classes. You never have to update the XML when you add a test file — TestNG finds it on the next run. The downside is that every class in the package runs; use <groups> to filter instead of listing classes explicitly.

Running different suites from the CLI

mvn test -DsuiteXmlFile=src/test/resources/smoke.xml
mvn test -DsuiteXmlFile=src/test/resources/regression.xml

This requires Surefire to read the XML file path from a property. In pom.xml:

<configuration>
    <suiteXmlFiles>
        <suiteXmlFile>src/test/resources/${suiteXmlFile}</suiteXmlFile>
    </suiteXmlFiles>
</configuration>

With this one change, CI can run smoke, regression, or cross-browser suites using the same pom.xml — just different -DsuiteXmlFile= arguments.

Three suite files every project ships

smoke.xml — runs in under 5 minutes; gates every commit:

<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="Smoke" parallel="methods" thread-count="4" verbose="1">
    <test name="Smoke">
        <groups>
            <run>
                <include name="smoke"/>
            </run>
        </groups>
        <packages>
            <package name="com.mycompany.tests.tests"/>
        </packages>
    </test>
</suite>

regression.xml — full suite, nightly or on PR merge:

<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="Regression" parallel="methods" thread-count="4" verbose="1">
    <test name="Regression">
        <groups>
            <run>
                <include name="regression"/>
                <exclude name="wip"/>
            </run>
        </groups>
        <packages>
            <package name="com.mycompany.tests.tests"/>
        </packages>
    </test>
</suite>

cross-browser.xml — same tests across Chrome, Firefox, Edge (chapter 2 covers @Parameters):

<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="Cross Browser" parallel="tests" thread-count="3" verbose="1">
    <test name="Chrome Tests">
        <parameter name="browser" value="chrome"/>
        <packages><package name="com.mycompany.tests.tests"/></packages>
    </test>
    <test name="Firefox Tests">
        <parameter name="browser" value="firefox"/>
        <packages><package name="com.mycompany.tests.tests"/></packages>
    </test>
    <test name="Edge Tests">
        <parameter name="browser" value="edge"/>
        <packages><package name="com.mycompany.tests.tests"/></packages>
    </test>
</suite>

The XML structure as a concept map

testng.xml
  • – name — label in reports
  • – parallel — none / methods / classes / tests
  • – thread-count — pool size
  • – verbose — console detail level
  • – Multiple blocks per suite
  • – @BeforeTest fires once per block
  • – Each block can have its own parameters
  • – parallel=tests runs blocks concurrently
  • – <class> — explicit class registration
  • – <package> — auto-discover all classes
  • – <methods> include/exclude within a class
  • – Prefer <package> — survives new files
  • <include> / <exclude> group names –
  • Cuts across classes and packages –
  • <parameter> injects values via @Parameters –
  • Suite-level params inherited by all <test>s –

Generating testng.xml from IntelliJ

You don't have to write testng.xml by hand for an existing project. In IntelliJ: right-click a test class → More Run/Debug → Create TestNG XML. IntelliJ generates a minimal suite file scoped to that class. It won't have group or parallel config, but it's a valid starting point to edit from.

⚠️ Common mistakes

  • Listing every class with <class name="..."/> instead of using <packages>. It works — until someone adds a new test class, forgets to register it, and the suite silently misses it for two weeks. Use <packages> and let TestNG auto-discover.
  • Confusing the <test> element with a test method. A <test> element is a named collection of classes. It has nothing to do with @Test methods. @BeforeTest fires per XML block, not per Java test method. This confusion causes real bugs: teams put class-level setup in @BeforeTest and then wonder why it runs multiple times when they split into two <test> blocks.
  • Setting parallel="methods" in testng.xml before tests are isolated. TestNG will happily run your tests in parallel, including two tests that share a WebDriver instance. The result is a flaky, intermittent failure that only reproduces at certain thread counts. Add parallelism only after verifying each test creates and destroys its own resources.

🎯 Practice task

Ship three suite files. 30–40 minutes.

  1. Create src/test/resources/smoke.xml, regression.xml, and wip.xml. For now, wip.xml will find zero tests — that's correct; it proves the group filter works.
  2. Tag your existing tests with @Test(groups = {"smoke"}) or @Test(groups = {"regression"}). At least two tests should be smoke.
  3. Update your pom.xml Surefire config to read ${suiteXmlFile}. Run:
    mvn clean test -DsuiteXmlFile=smoke.xml
    mvn clean test -DsuiteXmlFile=regression.xml
    Confirm each runs only the expected tests.
  4. Test <methods> include/exclude. Add <methods><exclude name="testSetupWorks"/></methods> inside a <class> entry. Confirm that test no longer runs.
  5. Verify verbose levels. Run with verbose="0" and then verbose="2". Notice the difference in console output — verbose="2" prints every method name as it runs, useful for debugging order issues.
  6. Stretch — cross-browser skeleton. Create cross-browser.xml with two <test> blocks (Chrome and Firefox) each with a <parameter name="browser" value="..."/>. Add a @BeforeTest @Parameters("browser") method to BaseTest that prints the browser name. Run the suite — you'll see it print "chrome" and "firefox" before their respective test blocks.

Next chapter: test configuration. The full story of @BeforeMethod, @BeforeClass, @BeforeSuite, @BeforeTest — when to use each, and what goes wrong when you pick the wrong level.

// tip to track lessons you complete and pick up where you left off across devices.