How to Run Selenium Tests in Parallel Using TestNG

In today’s fast-paced software development world, speed and efficiency in testing are no longer optional, they are must-haves. Running tests sequentially often becomes a bottleneck when test suites grow larger. Imagine having hundreds (or even thousands) of test cases and waiting hours for execution to complete. This is where parallel test execution in TestNG comes into play.

TestNG, a powerful testing framework for Java, offers built-in support for parallel execution of tests, making it possible to run multiple tests simultaneously. This reduces execution time, maximizes resource utilization, and helps teams get faster feedback.

In this article, we’ll cover everything you need to know about running tests in parallel with TestNG, from the basic configurations to advanced scenarios. Whether you’re new to TestNG or looking to fine-tune your parallel strategy, this article will act as your go-to resource.

Before jumping into configurations, let’s understand the real benefits of parallel execution:

  1. Reduced Execution Time – Large suites can finish in minutes instead of hours.
  2. Efficient Resource Utilization – Parallelism leverages CPU cores and test infrastructure effectively.
  3. Scalable Testing – Crucial for cross-browser testing, multi-device validation, and cloud-based execution platforms like BrowserStack, Sauce Labs, or Selenium Grid.
  4. Faster Feedback Loops – Quick identification of bugs and faster CI/CD pipelines.

Let’s start by creating a few simple classes, each containing some methods. We’ll run these methods one after another in a sequential order first. Later, we’ll explore how to execute them in parallel to improve efficiency.

We will go to the URL “https://testkru.com/Elements/TextMessages” and select random labels from the page to print in the console. Along with these labels, we will also print the ThreadId so we can identify which thread handled each case. This will help us verify whether the test cases are running in parallel or sequentially.

package codekru;

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.testng.annotations.Test;

public class TestTextField {

    @Test
    public void test1() {
        WebDriver driver = new ChromeDriver();
        System.out.println("Running Test 1");
        driver.get("https://testkru.com/Elements/TextFields");
        WebElement element = driver.findElement(By.id("firstNamePlaceholder"));
        System.out.println("First name label: " + element.getText());
        System.out.println("Thread Id: " + Thread.currentThread().getId());
        driver.quit();
    }

    @Test
    public void test2() {
        WebDriver driver = new ChromeDriver();
        System.out.println("Running Test 2");
        driver.get("https://testkru.com/Elements/TextFields");
        WebElement element = driver.findElement(By.id("lastNamePlaceholder"));
        System.out.println("Last name label: " + element.getText());
        System.out.println("Thread Id: " + Thread.currentThread().getId());
        driver.quit();
    }

}
package codekru;

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.testng.annotations.Test;

public class TestButtonPage {

    @Test
    public void test3() {
        WebDriver driver = new ChromeDriver();
        System.out.println("Running Test 3");
        driver.get("https://testkru.com/Elements/Buttons");
        WebElement element = driver.findElement(By.id("doubleClickButtonLabel"));
        System.out.println("Double click button label: " + element.getText());
        System.out.println("Thread Id: " + Thread.currentThread().getId());
        driver.quit();
    }

    @Test
    public void test4() {
        WebDriver driver = new ChromeDriver();
        System.out.println("Running Test 4");
        driver.get("https://testkru.com/Elements/Buttons");
        WebElement element = driver.findElement(By.id("rightClickButtonLabel"));
        System.out.println("Right click button label: " + element.getText());
        System.out.println("Thread Id: " + Thread.currentThread().getId());
        driver.quit();
    }

}
package codekru;

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.testng.annotations.Test;

public class TestTextMessages {

    @Test
    public void test5() {
        WebDriver driver = new ChromeDriver();
        System.out.println("Running Test 5");
        driver.get("https://testkru.com/Elements/TextMessages");
        WebElement element = driver.findElement(By.id("plainTextLabel"));
        System.out.println("Plain text label: " + element.getText());
        System.out.println("Thread Id: " + Thread.currentThread().getId());
        driver.quit();
    }

    @Test
    public void test6() {
        WebDriver driver = new ChromeDriver();
        System.out.println("Running Test 6");
        driver.get("https://testkru.com/Elements/TextMessages");
        WebElement element = driver.findElement(By.id("hiddenTextLabel"));
        System.out.println("Hidden text label: " + element.getText());
        System.out.println("Thread Id: " + Thread.currentThread().getId());
        driver.quit();
    }


}

Let’s now create an testng.xml file, that will execute the test cases –

<suite name="codekru">
    <test name="Text Field">
        <classes>
            <class name="codekru.TestTextField" />
        </classes>
    </test>

    <test name="Buttons">
        <classes>
            <class name="codekru.TestButtonPage" />
        </classes>
    </test>

    <test name="Text Messages">
        <classes>
            <class name="codekru.TestTextMessages" />
        </classes>
    </test>
</suite>

Output after running the XML file –

Running Test 1
First name label: 1) First Name Without Placeholder
Thread Id: 1
Running Test 2
Last name label: 2) Last Name With Placeholder
Thread Id: 1
Running Test 3
Double click button label: 1) Double-Click on button
Thread Id: 1
Running Test 4
Right click button label: 2) Right-Click on Button
Thread Id: 1
Running Test 5
Plain text label: 1) Plain Text
Thread Id: 1
Running Test 6
Hidden text label: 2) Hidden Text
Thread Id: 1

If you look at the output, you’ll see that the ThreadId for all test cases is 1. This means only a single thread was used, so the test cases ran one after another, in sequence. You can try this yourself to observe the same result.

TestNG provides multiple levels of parallelism. Understanding each one is crucial for choosing the right strategy for your project. We will now run the above example with the different type of parallelism.

Let’s go step by step through each level, with explanations and examples.

Now, what exactly do we mean by a “Test” here?

In TestNG, a Test is defined using the <test> tag in the XML configuration file. So, the configuration shown below represents a single test.

<test name="Text Field">
        <classes>
            <class name="codekru.TestTextField" />
        </classes>
</test>

If you recall, in our example testng.xml file, we had 3 <test> tags. We can execute all of these tests in parallel by setting the parallel attribute to “tests“, as shown below:

<suite name="codekru" parallel = "tests" >
    <test name="Text Field">
        <classes>
            <class name="codekru.TestTextField" />
        </classes>
    </test>

    <test name="Buttons">
        <classes>
            <class name="codekru.TestButtonPage" />
        </classes>
    </test>

    <test name="Text Messages">
        <classes>
            <class name="codekru.TestTextMessages" />
        </classes>
    </test>
</suite>

Now, when we run the XML file, all the <test> sections will execute in parallel. The output may appear mixed since the tests run simultaneously, so keep an eye on the ThreadId to identify which output belongs to which test.

Running Test 5
Running Test 1
Running Test 3
First name label: 1) First Name Without Placeholder
Thread Id: 17
Running Test 2
Plain text label: 1) Plain Text
Thread Id: 19
Double click button label: 1) Double-Click on button
Thread Id: 18
Right click button label: 2) Right-Click on Button
Thread Id: 18
Running Test 6
Last name label: 2) Last Name With Placeholder
Thread Id: 17
Running Test 4
Hidden text label: 2) Hidden Text
Thread Id: 19

Now, there are a few important points to note in the output.

  • In the output, you’ll notice two distinct ThreadIds – 17, 18, and 19. This shows that all the <test> tags ran in parallel. However, the test methods (those annotated with @Test) within a single <test> tag ran sequentially on the same thread. That’s why each ThreadId appears twice in the output.
  • You might wonder why all the tests ran in parallel even though we didn’t specify how many should run concurrently
    • This happens because, by default, TestNG assumes up to 5 tests can run in parallel if not explicitly configured.
    • We can change this behavior by setting the thread-count attribute in the XML file, as shown below.
<suite name="codekru" parallel="tests" thread-count = "2">
    <test name="Text Field">
        <classes>
            <class name="codekru.TestTextField" />
        </classes>
    </test>

    <test name="Buttons">
        <classes>
            <class name="codekru.TestButtonPage" />
        </classes>
    </test>

    <test name="Text Messages">
        <classes>
            <class name="codekru.TestTextMessages" />
        </classes>
    </test>
</suite>

Since we set the thread-count to 2, only two <test> tags will run in parallel at any given time.

Output after executing the above XML file –

Running Test 3
Running Test 1
Double click button label: 1) Double-Click on button
Thread Id: 18
Running Test 4
First name label: 1) First Name Without Placeholder
Thread Id: 17
Running Test 2
Right click button label: 2) Right-Click on Button
Thread Id: 18
Plain text label: 1) Plain Text
Thread Id: 18
Running Test 5
Last name label: 2) Last Name With Placeholder
Thread Id: 17
Running Test 6
Hidden text label: 2) Hidden Text
Thread Id: 18

Notice that there are only two distinct ThreadIds, 17 and 18, which confirms that only two <test> ran in parallel.

Earlier, we ran multiple <test> tags in parallel. Now, we will configure TestNG to run multiple <class> tags in parallel.

Consider the XML file below, where we have placed multiple <class> tags inside a single <test> tag.

<suite name="codekru">
    <test name="ClassExecution">
        <classes>
            <class name="codekru.TestButtonPage" />
            <class name="codekru.TestTextField" />
            <class name="codekru.TestTextMessages" />
        </classes>
    </test>
</suite>

Now, we’ll set the parallel attribute to “classes“, which will allow all the classes to run in parallel. We can also control the level of parallelism by specifying the thread-count attribute.

<suite name="codekru" parallel="classes" thread-count="2">
    <test name="ClassExecution" >
        <classes>
            <class name="codekru.TestButtonPage" />
            <class name="codekru.TestTextField" />
            <class name="codekru.TestTextMessages" />
        </classes>
    </test>
</suite>

Output after executing the above XML file –

Running Test 3
Running Test 1
First name label: 1) First Name Without Placeholder
Thread Id: 21
Running Test 2
Double click button label: 1) Double-Click on button
Thread Id: 20
Running Test 4
Last name label: 2) Last Name With Placeholder
Thread Id: 21
Plain text label: 1) Plain Text
Thread Id: 21
Running Test 5
Right click button label: 2) Right-Click on Button
Thread Id: 20
Running Test 6
Hidden text label: 2) Hidden Text
Thread Id: 21

We can see that only two distinct threadId’s was used, showing that only 2 classes executed in parallel.

If we look closely, we can see that the TestButtonPage class ran in “ThreadId: 20“, and the TestTextField class ran in “ThreadId: 21“. This shows that the first two classes inside the tag executed in parallel. Once one of them finished, the available thread was assigned to the third class, TestTextMessages.

Now, let’s discuss parallel execution at the method level. In some cases, a class contains many test methods (methods annotated with @Test), and running them in parallel can significantly improve efficiency.

To run test methods at the same time, set the parallel attribute to “methods“. We can again use the thread-count attribute to decide how many methods should be executed concurrently.

<suite name="codekru" parallel="methods" thread-count="2">
    <test name="ClassExecution" >
        <classes>
            <class name="codekru.TestButtonPage" />
            <class name="codekru.TestTextField" />
            <class name="codekru.TestTextMessages" />
        </classes>
    </test>
</suite>

Output after running the XML file

Running Test 3
Running Test 4
Right click button label: 2) Right-Click on Button
Thread Id: 20
Double click button label: 1) Double-Click on button
Thread Id: 19

Running Test 2
Running Test 1
Last name label: 2) Last Name With Placeholder
Thread Id: 19
First name label: 1) First Name Without Placeholder
Thread Id: 20

Hidden text label: 2) Hidden Text
Thread Id: 20
Running Test 5
Running Test 6
Plain text label: 1) Plain Text
Thread Id: 19

We just segregated the output in 3 blocks. This is to show that the methods inside the class, actually executed in parallel.

If you look closely at the output, you’ll see that “test3” and “test4” in the “TestButtonPage” class have different thread IDs. This means they ran at the same time, in parallel. The same pattern can be observed for the other classes too, confirming that parallel execution happened at method level this time.

We hope that you have liked the article. If you have any doubts or concerns, please write to us in the comments or mail us at admin@codekru.com.

Liked the article? Share this on

Leave a Comment

Your email address will not be published. Required fields are marked *