A TestNG suite is only useful if it runs reliably for everyone and in CI. This guide covers running TestNG from the Maven command line, wiring it into Jenkins and GitHub Actions, and the best practices and common mistakes that decide whether a suite stays trustworthy as it grows.

Running TestNG from the Command Line with Maven

# Run default testng.xml
mvn clean test
# Run specific testng.xml
mvn clean test -DsuiteXmlFile=testng_smoke.xml
# Override parameters
mvn clean test -Dbrowser=firefox -Durl=https://production.myapp.com
# Run specific groups
mvn clean test -Dgroups=smoke
# Run one test method
mvn clean test -Dtest=LoginTest#testValidLogin
# Run in parallel with custom thread count
mvn clean test -DthreadCount=5 -Dparallel=methods

Properties like -DsuiteXmlFile and -Dbrowser only work if your pom.xml or code reads them. For example, set <suiteXmlFile>${suiteXmlFile}</suiteXmlFile> in the Surefire configuration with a default value in <properties>, and read the browser with System.getProperty("browser", "chrome"). When a suite file is used, the parallel settings inside testng.xml take priority over command-line ones.

Advertisement

Jenkins Integration

// Jenkinsfile — declarative pipeline
pipeline {
    agent any
    tools { maven 'Maven_3.9'; jdk 'JDK_17' }
    parameters {
        choice(name: 'BROWSER',  choices: ['chrome','firefox','edge'], description: 'Browser')
        choice(name: 'SUITE',    choices: ['testng.xml','testng_smoke.xml'], description: 'Suite')
        string(name: 'BASE_URL', defaultValue: 'https://staging.myapp.com', description: 'URL')
    }
    stages {
        stage('Checkout') {
            steps { git branch: 'main', url: 'https://github.com/org/project.git' }
        }
        stage('Run Tests') {
            steps {
                sh "mvn clean test -DsuiteXmlFile=${params.SUITE} -Dbrowser=${params.BROWSER} -Durl=${params.BASE_URL}"
            }
        }
    }
    post {
        always {
            junit 'target/surefire-reports/*.xml'   // or the TestNG Results plugin
            publishHTML([reportDir: 'reports', reportFiles: 'TestReport.html', reportName: 'Extent Report'])
        }
        failure { emailext to: 'team@co.com', subject: 'Tests Failed', body: '${BUILD_URL}' }
    }
}

The post { always { … } } block publishes results and the Extent report even when tests fail, which is exactly when you need them.

GitHub Actions Integration

# .github/workflows/testng-suite.yml
name: TestNG Automation
on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]
  schedule:
    - cron: '0 1 * * *'  # nightly at 1 AM
jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        browser: [chrome, firefox]  # run on both browsers in parallel
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with: { java-version: '17', distribution: 'temurin' }
      - uses: browser-actions/setup-chrome@latest
      - uses: browser-actions/setup-firefox@latest
      - name: Run TestNG Suite
        run: mvn clean test -DsuiteXmlFile=testng.xml -Dbrowser=${{ matrix.browser }}
      - name: Upload Test Reports
        uses: actions/upload-artifact@v4
        if: always()
        with:
          name: test-reports-${{ matrix.browser }}
          path: |
            reports/
            target/surefire-reports/
      - name: Publish TestNG Results
        uses: EnricoMi/publish-unit-test-result-action@v2
        if: always()
        with:
          files: target/surefire-reports/TEST-*.xml

Best Practices

Test Design

  • One test method = one behaviour — do not test multiple features in one @Test
  • Use @Test(description) for every test — improves reports and traceability
  • Name tests descriptively: testLogin_validCredentials_shouldRedirectToDashboard
  • Every test must be independent — never rely on another test's side effects
  • Use SoftAssert when verifying multiple properties of the same page/object
  • Always add a failure message to assertions: Assert.assertEquals(a, e, 'message')

Framework Design

  • Use ThreadLocal<WebDriver> for all parallel execution — never static WebDriver
  • Call driver.quit() and then ThreadLocal.remove() in @AfterMethod — prevents ThreadLocal memory leaks
  • Use @BeforeMethod(alwaysRun=true) for setup — runs even if previous test failed
  • Use @AfterMethod(alwaysRun=true) for teardown — always close browser
  • Store all locators in Page Object classes — never in test classes
  • Store all configuration in config.properties — never hardcode URLs/credentials

Common Mistakes and How to Fix Them

MistakeProblemCorrect Approach
Static WebDriver fieldParallel tests share one browser — chaosThreadLocal<WebDriver> in DriverFactory
Not calling softAssert.assertAll()Test always passes — silently swallows failuresALWAYS call assertAll() at end of test
Mixing @BeforeMethod and @BeforeClass wronglyDriver not ready / browser launched too many timesBeforeMethod for per-test setup, BeforeClass for expensive once-only setup
Hard Assert for multiple checksFirst failure hides all othersSoftAssert for validating multiple properties
Test order dependency without dependsOnMethodsTests fail when run alone or in different orderUse dependsOnMethods or make tests truly independent
forgetting alwaysRun=true on teardown@AfterMethod skipped if test throws unexpected exception@AfterMethod(alwaysRun=true) — always clean up
Printing sensitive data in logsCredentials appear in CI logsUse config files, mask in CI environment variables
No groups on testsCannot run smoke/regression subsetsAlways add at minimum one group to each test

FAQs

How do I run a specific testng.xml with Maven?

Reference a property in the Surefire configuration, for example <suiteXmlFile>${suiteXmlFile}</suiteXmlFile>, then run mvn test -DsuiteXmlFile=testng_smoke.xml.

How do you publish TestNG results in Jenkins?

Surefire writes JUnit-format XML to target/surefire-reports; publish it with the junit step (or the TestNG Results plugin) in a post { always { } } block, and publish HTML reports with the HTML Publisher plugin.

Why use alwaysRun = true on teardown methods?

So cleanup such as quitting the browser still runs when a test or configuration method fails, preventing leftover browsers and cascading failures.

Should every TestNG test belong to a group?

Yes, at least one (smoke, regression and so on), so pipelines can run fast subsets on every commit and the full suite on a schedule.