//
you're reading...
TestNG

Parallel WebDriver executions using TestNG

In this post, we will see how does one make use of TestNG to kick off parallel UI tests using WebDriver.

So lets try doing this with a typical cooking recipe style πŸ™‚

So here are the ingredients that are required.

  • A Factory class that will create WebDriver instances
  • A Manager class that can be accessed to retrieve a WebDriver instance
  • A TestNG listener that will be responsible for instantiating the WebDriver instance automatically

So without wasting any time lets see how this all blends in.

First lets look at our Factory class. This is a very simplified Factory class that will create instances of WebDriver based upon the browser flavour. I have purposefully kept it simple only for illustration purposes:
Here’s how the Factory class will look like:

package organized.chaos;

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.ie.InternetExplorerDriver;

class LocalDriverFactory {
    static WebDriver createInstance(String browserName) {
        WebDriver driver = null;
        if (browserName.toLowerCase().contains("firefox")) {
            driver = new FirefoxDriver();
            return driver;
        }
        if (browserName.toLowerCase().contains("internet")) {
            driver = new InternetExplorerDriver();
            return driver;
        }
        if (browserName.toLowerCase().contains("chrome")) {
            driver = new ChromeDriver();
            return driver;
        }
        return driver;
    }
}

As you can see its a very simple class with a static method that creates WebDriver instances. The one interesting part to be noted here is that the class has been purposefully given only package visibility [ notice how the keyword “public” is missing from the class declaration ]. One of the many aspects that are involved in designing APIs is “Hide what is not necessary to be visible to your user”. For you to be able to drive a car, you don’t need to know how the piston works or how the fuel injection happens do you πŸ™‚

Now lets take a look at how our Manager class would look like. The Manager class essentially uses a concept in java called ThreadLocal variables.

The code would look like below :

package organized.chaos;

import org.openqa.selenium.WebDriver;

public class LocalDriverManager {
    private static ThreadLocal<WebDriver> webDriver = new ThreadLocal<WebDriver>();

    public static WebDriver getDriver() {
        return webDriver.get();
    }

    static void setWebDriver(WebDriver driver) {
        webDriver.set(driver);
    }
}

Were you surprised that its such a small class ? πŸ™‚
So as you can see we basically have a static ThreadLocal variable wherein we are setting webDriver instances and also querying webdriver instances as well.

Next comes the TestNG listener. The role of the TestNG listener is to perform “Automatic webdriver instantiation” behind the scenes without your test code even realising it. For this we will make use of IInvokedMethodListener so that the WebDriver gets instantiated right before a Test Method gets invoked and the webDriver gets automatically quit right after the Test method.
You can improvize this by incorporating custom annotations as well and parsing for your custom annotations [ The current implementation that you will see basically spawns a browser irrespective of whether you want to use it or not. That’s not a nice idea all the time is it ]

package organized.chaos;

import org.openqa.selenium.WebDriver;
import org.testng.IInvokedMethod;
import org.testng.IInvokedMethodListener;
import org.testng.ITestResult;

public class WebDriverListener implements IInvokedMethodListener {

    @Override
    public void beforeInvocation(IInvokedMethod method, ITestResult testResult) {
        if (method.isTestMethod()) {
            String browserName = method.getTestMethod().getXmlTest().getLocalParameters().get("browserName");
            WebDriver driver = LocalDriverFactory.createInstance(browserName);
            LocalDriverManager.setWebDriver(driver);
        }
    }

    @Override
    public void afterInvocation(IInvokedMethod method, ITestResult testResult) {
        if (method.isTestMethod()) {
            WebDriver driver = LocalDriverManager.getDriver();
            if (driver != null) {
                driver.quit();
            }
        }
    }
}

Now that we have shown all of the ingredients, lets take a look at a sample test as well, which is going to use all of this.

package organized.chaos;

import org.testng.annotations.Test;

public class ThreadLocalDemo {
    @Test
    public void testMethod1() {
        invokeBrowser("http://www.ndtv.com");
    }

    @Test
    public void testMethod2() {
        invokeBrowser("http://www.facebook.com");

    }

    private void invokeBrowser(String url) {
        System.out.println("Thread id = " + Thread.currentThread().getId());
        System.out.println("Hashcode of webDriver instance = " + LocalDriverManager.getDriver().hashCode());
        LocalDriverManager.getDriver().get(url);

    }
}

As you can see its a very simple test class with two test methods. Each of the test methods opens up a different website. I have also add print statements for printing the thread id [yes thats the only reliable way of figuring out if your test method is running in parallel or in sequential mode. If you see unique values for Thread.currentThread().getId() then you can rest assured that TestNG is invoking your test methods in parallel.
We are printing the hashCode() values for the browser to demonstrate the fact that there are unique and different webDriver instances being created for every test method. [ Remember hashCode() value for an object would always be unique ]

Now lets take a look at how our suite file looks like :

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="Suite" parallel="methods">
<listeners>
<listener class-name="organized.chaos.WebDriverListener"></listener>
</listeners>
	<test name="Test">
		<parameter name="browserName" value="firefox"></parameter>
		<classes>
			<class name="organized.chaos.ThreadLocalDemo" />
		</classes>
	</test> <!-- Test -->
</suite> <!-- Suite -->

So when you run this test this is how your output would look like [ apart from you seeing two firefox windows popup on your desktop ]

[TestNG] Running:
  /githome/PlayGround/testbed/src/test/resources/threadLocalDem.xml

Thread id = 10
Hashcode of webDriver instance = 1921042184
Thread id = 9
Hashcode of webDriver instance = 2017986718

===============================================
Suite
Total tests run: 2, Failures: 0, Skips: 0
===============================================

And thus we have managed to leverage TestNG and run WebDriver tests in parallel without having to worry about race conditions or leaving browsers open etc.,

Hope that clears out some of the confusions and helps you get started with WebDriver automation powered by TestNG.

Advertisements

Discussion

47 thoughts on “Parallel WebDriver executions using TestNG

  1. I Am getting NPE
    [TestNG] Running:
    C:\Users\knaik\AppData\Local\Temp\testng-eclipse–1928173114\testng-customsuite.xml

    Thread id = 1
    Thread id = 1
    FAILED: testMethod1
    java.lang.NullPointerException
    at organized.chaos.ThreadLocalDemo.invokeBrowser(ThreadLocalDemo.java:19)
    at organized.chaos.ThreadLocalDemo.testMethod1(ThreadLocalDemo.java:8)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at org.testng.internal.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:84)
    at org.testng.internal.Invoker.invokeMethod(Invoker.java:714)
    at org.testng.internal.Invoker.invokeTestMethod(Invoker.java:901)
    at org.testng.internal.Invoker.invokeTestMethods(Invoker.java:1231)
    at org.testng.internal.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:127)
    at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:111)
    at org.testng.TestRunner.privateRun(TestRunner.java:767)
    at org.testng.TestRunner.run(TestRunner.java:617)
    at org.testng.SuiteRunner.runTest(SuiteRunner.java:335)
    at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:330)
    at org.testng.SuiteRunner.privateRun(SuiteRunner.java:291)
    at org.testng.SuiteRunner.run(SuiteRunner.java:240)
    at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:52)
    at org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java:86)
    at org.testng.TestNG.runSuitesSequentially(TestNG.java:1224)
    at org.testng.TestNG.runSuitesLocally(TestNG.java:1149)
    at org.testng.TestNG.run(TestNG.java:1057)
    at org.testng.remote.RemoteTestNG.run(RemoteTestNG.java:111)
    at org.testng.remote.RemoteTestNG.initAndRun(RemoteTestNG.java:204)
    at org.testng.remote.RemoteTestNG.main(RemoteTestNG.java:175)

    FAILED: testMethod2
    java.lang.NullPointerException
    at organized.chaos.ThreadLocalDemo.invokeBrowser(ThreadLocalDemo.java:19)
    at organized.chaos.ThreadLocalDemo.testMethod2(ThreadLocalDemo.java:13)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at org.testng.internal.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:84)
    at org.testng.internal.Invoker.invokeMethod(Invoker.java:714)
    at org.testng.internal.Invoker.invokeTestMethod(Invoker.java:901)
    at org.testng.internal.Invoker.invokeTestMethods(Invoker.java:1231)
    at org.testng.internal.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:127)
    at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:111)
    at org.testng.TestRunner.privateRun(TestRunner.java:767)
    at org.testng.TestRunner.run(TestRunner.java:617)
    at org.testng.SuiteRunner.runTest(SuiteRunner.java:335)
    at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:330)
    at org.testng.SuiteRunner.privateRun(SuiteRunner.java:291)
    at org.testng.SuiteRunner.run(SuiteRunner.java:240)
    at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:52)
    at org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java:86)
    at org.testng.TestNG.runSuitesSequentially(TestNG.java:1224)
    at org.testng.TestNG.runSuitesLocally(TestNG.java:1149)
    at org.testng.TestNG.run(TestNG.java:1057)
    at org.testng.remote.RemoteTestNG.run(RemoteTestNG.java:111)
    at org.testng.remote.RemoteTestNG.initAndRun(RemoteTestNG.java:204)
    at org.testng.remote.RemoteTestNG.main(RemoteTestNG.java:175)

    ===============================================
    Default test
    Tests run: 2, Failures: 2, Skips: 0
    ===============================================

    Posted by Kishor | October 1, 2013, 8:38 pm
    • Do not try to run the .java classes. Click the XML file with the right mouse button and select “run as TestNG suite”

      Posted by dave | January 16, 2014, 8:24 pm
      • i just did it from right click on the XML and also fails on same error……what should i do?
        do u have an email to contact u?
        thanks

        Posted by amit | October 19, 2014, 6:02 pm
      • I see this issue when run at XML
        java.lang.NoClassDefFoundError: com/google/common/base/Function
        at organized.chaos.LocalDriverFactory.createInstance(LocalDriverFactory.java:17)
        at organized.chaos.WebDriverListener.beforeInvocation(WebDriverListener.java:14)
        at org.testng.internal.invokers.InvokedMethodListenerInvoker$InvokeBeforeInvocationWithoutContextStrategy.callMethod(InvokedMethodListenerInvoker.java:84)
        at org.testng.internal.invokers.InvokedMethodListenerInvoker.invokeListener(InvokedMethodListenerInvoker.java:62)
        at org.testng.internal.Invoker.runInvokedMethodListeners(Invoker.java:619)
        at org.testng.internal.Invoker.invokeMethod(Invoker.java:687)
        at org.testng.internal.Invoker.invokeTestMethod(Invoker.java:901)
        at org.testng.internal.Invoker.invokeTestMethods(Invoker.java:1231)
        at org.testng.internal.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:127)
        at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:111)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
        at java.lang.Thread.run(Unknown Source)
        Caused by: java.lang.ClassNotFoundException: com.google.common.base.Function
        at java.net.URLClassLoader$1.run(Unknown Source)
        at java.net.URLClassLoader$1.run(Unknown Source)
        at java.security.AccessController.doPrivileged(Native Method)
        at java.net.URLClassLoader.findClass(Unknown Source)
        at java.lang.ClassLoader.loadClass(Unknown Source)
        at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
        at java.lang.ClassLoader.loadClass(Unknown Source)
        … 13 more

        Posted by AshokP | May 18, 2015, 3:18 pm
      • @AshokP
        You are experiencing a problem with your CLASSPATH. Based on what build tool you are using, you would need to address it via it.

        Posted by Confusions Personified | July 12, 2015, 8:55 am
  2. I wish someone could post a blog about doing this in C#:) Thanks for a great entry!

    Posted by Austin | December 19, 2013, 1:19 pm
  3. How much scalability can you have with this? Can you manage in some ways how many tests are run concomitantly in parallel?

    Posted by ovibelu | April 4, 2014, 1:41 pm
  4. Nice and useful post…

    Posted by madhu | April 6, 2014, 4:51 pm
  5. I tried similar approach but face problem with WebDriverManager.class in storing WebDriver instance ThreadLocal class.

    When I set WebDriver instance in WebDriverListener#beforeInvocation it’s in main thread. But when I trying to get WebDriver in @Test WebDriverManager.getDriver() returns null, that is because of @Test it’s running in anouthe Thread – “TestNGInvoker-” + methodName + “()” but not main.

    Below log output where I log Thread.currentThread().getName() before, in, and after the test.

    2014-04-17 14:26:22,142 [main] INFO [WebDriverListener] BEFORE TEST = main
    2014-04-17 14:26:22,145 [main] INFO [WebDriverFactory] >>> creating instance for chrome browser
    Starting ChromeDriver (v2.8.241075) on port 32225
    2014-04-17 14:26:24,843 [TestNGInvoker-myTest()] INFO [MyTest] IN TEST METHOD = TestNGInvoker-myTest()
    2014-04-17 14:26:30,547 [main] INFO [WebDriverListener] AFTER TEST = main

    Posted by mn | April 17, 2014, 4:17 pm
  6. I am trying to login my application with same above aproch but somewhere it is wrong that few thread is running properly and some thread are not able to login the app. Could you please tell me the reason ?

    Thanks,
    Sonu.

    Posted by sonu | May 15, 2014, 3:31 pm
  7. Thanx for this! Just to clarify and not meaning in a -ve way – IE Driver still does not like being run in parallel mode on the same m/c w/out the grid right? As in if we run 5 IE browsers on one desktop m/c and keep them on for 30 minutes, eventually we’ll start hitting some underlying exceptions…

    Need a clarification. thanx!

    Posted by Harsh | May 23, 2014, 9:39 am
  8. It really luks great. Here I need that my each test suite runs in a single thread. That is one driver instance per suite. How can I make it possible

    Posted by Shobhit | May 23, 2014, 11:33 pm
  9. Hi , great article.
    i didn’t understand how to define the parallel tests.
    if i have for example this xml:


    the name in the parameter: “methods” – where is it?
    random name? is it matter?

    the listener is importatnt?
    it is possible to run class1-5 in one browser?
    and the rest in second browser?

    how to define which one run in specific browser because some have depends on…..

    thank u!

    amit

    Posted by amit | September 11, 2014, 7:31 pm
  10. I have a side question related to how you did this implementation. I’ve used it and everything worked great for creating webdriver instances for running against a Grid environment. This was my first priority, supporting parallel execution. My question is related to handling browser coverage (different browser types). So I’m looking at a Java/TestNG implementation running against a Grid environment and I would like to support multiple browser versions. I would like to do it in a way that everything gets executed overnight.

    In your example, you accomplish this by specifying parameters in your testng.xml files, which your LocalDriverFactory referemces. I’ve seen this approach used in other examples, using parameters for browser type in the testng.xml file. The negative to this, if I understand it correctly, is that you need to create multiple entries for each individual test in the xml file. One for each browser type you want to support – hence changing the browser parameter value to the different browser types you want to test.

    I never really liked this approach. Seems like a hack when your use to data-driving things.

    Given your approach to creating the webdriver instance, is it possible to data drive the browser type? or can’t you since you create the webdriver instance in the beforeInvocation method of the listener.

    I assume you just use the testng.xml – parameter approach to account for different browser types?

    thanks for your expertise and advice.

    Greg

    Posted by Greg | September 29, 2014, 8:04 am
  11. Hi!
    Thanks allot for this post!
    We try to implement this, but in our case we get that the threadID in the listener is different from the threadID in the test method. Do you have any idea why and how to fix it?

    Thanks!

    Posted by Shula | October 23, 2014, 12:36 pm
  12. Hi,

    Can we run multiple xml files using parallel test?

    Description:
    in my case i have a testng.xml which has login class file with 3 methods of 3 customer logins and in each method i am calling a XMl file which data test cases for each customer.In the testng.xml i have given parallel=”tests” thread-count=”3″. and login to 3 customers works but the xml file give after each customer loing doesnt work.

    Posted by deepu | November 21, 2014, 4:17 pm
  13. Excellent information, This is what I was looking for.

    Posted by Milind | January 20, 2015, 12:14 pm
  14. hey can i negotiate between this two webdrivers ?
    if yes then they both are using same session or different session?
    i want to use the two drivers one after another?

    Posted by Ajinkya | May 20, 2015, 6:07 pm
  15. Instead of each method how can modify it to create driver for a class.

    Posted by Ajay | July 8, 2015, 9:47 am
  16. Works perfect!!! Thanks a lot!

    Posted by James | December 17, 2015, 11:00 pm
  17. Is anyone else getting an error with LocalDriverManager.getDriver()? When I call it within the invokeBrowser() method it returns null.

    Posted by Jordan Ponce | February 13, 2016, 3:16 am
  18. Can we read page object webelement using LocalDriver instance using parallel execution same as above.
    I am getting NullPointer Exception for the same. Any help would really be helpful??

    Posted by shweta | June 13, 2016, 4:30 pm
  19. When I run this code by including the above classes .I was getting NullPointer exception.
    The firefox browser is getting opened but it is not navigating to desired url. Can you help me with the problem.

    Posted by Vishali | October 3, 2016, 5:16 pm
  20. Thank you for breaking this down! Helps a ton! πŸ™‚

    Posted by lexiecita | January 31, 2017, 7:12 pm
  21. “Remember hashCode() value for an object would always be unique.”
    I do not think it’s true.

    Posted by Grzegorz | February 3, 2017, 6:12 pm
  22. Not sure where exactly are you going with this. From what I know any new objects created in the heap are supposed to have a unique hashcode by default. Your example makes use of boxing in Java. I don’t know if boxing leads to creation of a new reference object all the time. I do know that webdriver instances when created newly do have a unique hashcode.

    Posted by Confusions Personified | February 3, 2017, 6:40 pm

Trackbacks/Pingbacks

  1. Pingback: Run your Selenium driven tests in parallel using TestNG. | HULK ANGRY! HULK SMASH! - March 29, 2014

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: