Monday, 3 March 2014

Initializing the WebDriver object as a Thread Local for Parallel Test Execution:

When you have decided to run your selenium’s tests in parallel, your Webdriver object should be thread-safe i.e. a single object can be used with multiple threads at the same time without causing problems. To achieve thread-safe have your choices are to pass that object around to every method that needs (or may need) it, or to associate the object with the thread.
You may not want to fill up your method signatures with an additional parameter. In a non-threaded world, you could solve the problem with the Java equivalent of a global variable. In a threaded word, the equivalent of a global variable is a thread-local variable. 

In a single-threaded environment, Webdriver object will be a static field and return it as is. However, this will certainly not work in a multiple-threaded environment. Imagine if multiple threads used Webdriver object. Object used by each thread could overwrite each other since there is only one static instance of Webdriver

In order to solve this problem, ThreadLocal provides a very good solution,



Now, you have your Thread local Webdriver object. Let's see how can you use it in your tests.


Just call 
getDriver() method from above class in the blocks or methods where ever you want. This will invoke your Webdriver and launches the browser. usage,


You have to initialize your Webdriver object local to the method or block only. Do not initialize it as a field or global object. If you want to use driver object in your test it should be,


After finishing your test you have to remove or quit the driver object and close the browser session. You need to be very careful about cleaning up any ThreadLocal's you get()by using the ThreadLocal's remove() method. Like, you might want to remove or quit your ThreadLocal driver in teardown method like this,


That's it. You are done. You can call getDriver() any number of times and any where, it will return you the same object all the time and the scope is limited to that thread only.

17 comments:

  1. If I have three tests and run two in parallel this fails. The first two run fine but the third fails with a SessionNotFoundException. Any idea why I would be getting this error?

    ReplyDelete
    Replies
    1. Ah! Thank you for identifying. It's fixed now.

      Delete
    2. Where is this fixed? Where can i find the "fixed" code?

      Delete
  2. How can I add your solution to my code?Where can I add path to my IE Driver?
    File file = new File("C:/IEDriverServer_Win32_2.44.0/IEDriverServer.exe");
    System.setProperty("webdriver.ie.driver", file.getAbsolutePath());
    WebDriver driver = new InternetExplorerDriver();

    ReplyDelete
  3. This is incredibly clever. Thanks to you I understand the factory design pattern better.

    ReplyDelete
  4. not to mention, a good example of ThreadLocal

    ReplyDelete
  5. What was fixed for @conner t comments... Please let us know..

    ReplyDelete
  6. Could anyone kindly suggest what to do if I have to close the browser and relaunch it again

    ReplyDelete
  7. Hi,

    Thanks for the article it seems like the solution I am looking for to implementing my driver. However, I do need to initialize my driver with desired capabilities that will change between methods/classes. Do you have any suggestions on how to do this?

    Thanks

    Jamie

    ReplyDelete
  8. This comment has been removed by the author.

    ReplyDelete
  9. How to make cross browser executable?

    ReplyDelete
  10. how to make this work for setting up dynamically for any type of browser and passing this as parameter before running the tests ?

    Normally the test class will have many test cases that share same instance of the driver: how to achieve this using ThreadLocal (@override ..?).

    Thanks

    ReplyDelete
  11. This is what I am using for parallel execution but not sure if this is the correct way even though is working.

    // Usage
    In the test do :
    @BeforeTest
    void setup(){
    LocalDriverManager.createDriver(MyBrowserType.CHROME, 15);
    }

    @Test
    test1(){
    //get the driver
    LocalDriverManager.getDriver().dosomething...
    }
    @Test
    test2(){
    //get the driver
    LocalDriverManager.getDriver().dosomething...
    }

    /**
    * if you need to execute many test cases in paraled in the same class
    * change @BeforeTest to @BeforeMethod and use TestNG xml to run : [e.g thread-count="2" parallel="methods" ]
    *
    */

    public class LocalDriverManager {
    private static final Logger logger = LoggerFactory.getLogger(LocalDriverManager.class);
    private static final ThreadLocal threadLocalWebDriver = new ThreadLocal();

    public static void createDriver(MyBrowserType browserType, int waitTimeSec){
    logger.info("createDriver() :"+browserType.getBrowser());
    WebDriver driver = LocalWebDriverFactory.getBrowser(browserType, waitTimeSec);
    LocalDriverManager.setWebDriver(driver);
    }

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

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

    public static void unset() {
    threadLocalWebDriver.remove();
    }

    public static void destroyLocalDriver(){
    logger.info("destroyLocalDriver ...!");
    if (getDriver() != null) {
    getDriver().quit();
    logger.info("Destroyed ...!");
    }else {
    logger.info("Not Destroyed ...is NULL ...!");
    }
    }

    }


    public class LocalWebDriverFactory extends RemoteWebDriver {

    public static WebDriver getBrowser(MyBrowserType myBrowserType, int timeoutSeconds){
    WebDriver driver = null;
    try {
    switch (myBrowserType) {
    case FIREFOX:
    driver = ThreadGuard.protect(new FirefoxDriver());
    return driver;
    case CHROME:
    System.setProperty("webdriver.chrome.driver", CHROME_EXE_FILE);
    driver = ThreadGuard.protect(new ChromeDriver());
    return driver;
    default:
    logger.error("Cant find setup for browser : " + myBrowserType.getBrowser());
    }

    driver.manage().timeouts().implicitlyWait(timeoutSeconds, TimeUnit.SECONDS);
    }catch (Exception e){
    e.printStackTrace();
    logger.error("Can't create browser ....! "+e.getMessage());
    BaseCommunityPage.failTest(e, "Can not create browser :"+ myBrowserType.getBrowser());
    }

    return driver;
    }

    }

    ReplyDelete
  12. This works great for me with raw selenium in the test, but fails with cross-talk between concurrent tests when I pass the driver object to a page object to use methods from that object. Is there a way to make page objects thread safe too? Thanks for the great solution in any case!

    -Kent

    ReplyDelete
  13. This works great for me with raw selenium in the test, but fails with cross-talk between concurrent tests when I pass the driver object to a page object to use methods from that object. Is there a way to make page objects thread safe too? Thanks for the great solution in any case!

    -Kent

    ReplyDelete
  14. Thank you Krishna Kokkula, I dedicate the below lines for you:

    "Imparting knowledge is one of the greatest help to mankind"

    ReplyDelete
  15. Hi-

    I have a cucumber framework in which I am trying to implement parallel execution. I have a SingletonDriver class which has a threadlocal variable as shown above. When I run 2 Test Runners parallely using testng.xml, two browsers open and navigate to the login page but after that point, the login process happens only in one browser and I get the message that 'The browser may have died.' Please help

    ReplyDelete