0

I am trying to run Selenium Webdriver tests in parallel on a single machine, using TestNG. I have 3 @Test methods, where 3 different users log in to the same application and reach the home page. I need @Test methods to run in parallel, and write to an ExtentReports report.

My problem is, despite 3 completely different methods in different classes, one of the users will be logged into 2 out of 3 of the browsers, leaving a user out.

The login method is located in a PageFactory page object class.

Here are my 3 test methods:

@Test(enabled = true, priority = 0) 
public void JohnLogin() throws Exception {
    ExtentTest t = ClientReportFactory.getTest();
    try {       
        Login objLogin = new Login(getDriver());
        String username = "John";
        String password = "Password";
        objLogin.SignIn(username, password);
        HomePage objHomePage = new HomePage(getDriver());   
        assertTrue(objHomePage.clientName.getText().c‌​ontains("John"));
    } catch (Exception e) {
    }
}

@Test(enabled = true, priority = 1) 
public void BobLogin() throws Exception {
    ExtentTest t = ClientReportFactory.getTest();
    try {       
        Login objLogin = new Login(getDriver());
        String username = "Bob";
        String password = "Password";
        objLogin.SignIn(username, password);
        HomePage objHomePage = new HomePage(getDriver());       
        assertTrue(objHomePage.clientName.getText().c‌​ontains("Bob"));
    } catch (Exception e) {
    }
}

@Test(enabled = true, priority = 2) 
public void SamLogin() throws Exception {
    ExtentTest t = ClientReportFactory.getTest();
    try {       
        Login objLogin = new Login(getDriver());
        String username = "Sam";
        String password = "Password";
        objLogin.SignIn(username, password);
        HomePage objHomePage = new HomePage(getDriver());       
        assertTrue(objHomePage.clientName.getText().c‌​ontains("Sam"));
    } catch (Exception e) {
    }
}

So, if I pause the tests on the Homepage. I will have 2 browser windows opened as "John", one "Bob" and no "Sam"... Causing failures.

Here's the PageFactory Object's login method.

 public void SignIn(String strUsername, String strPassword) throws InterruptedException {
    WebDriverWait wait = new WebDriverWait(driver, 15); 
    username.clear();
    username.sendKeys(strUsername);
    password.clear();
    password.sendKeys(strPassword);
    submit.click();
    wait.until(ExpectedConditions.visibilityOf(homePagePanel));
}

At first I was sure the problem was in the @BeforeMethod threading (As in, the tests were in a different thread than the @Before and @After). But I don't see how that could be the case. The Base Test method successfully opens and closes 3 browsers. It just seems like the @Test methods use each other's data! But just in case, here's my @Before and @After, with my Threading code.

public class BaseTest {
    public String browser;
    private ThreadLocal<WebDriver> threadedDriver = new ThreadLocal<WebDriver>();

@BeforeMethod(alwaysRun = true)
@Parameters({ "browser"})
public void setup(String browser)throws MalformedURLException,
InterruptedException {

        WebDriver driver = null;
        if (browser.equalsIgnoreCase("Internet Explorer")) {
            System.setProperty("webdriver.ie.driver", "C:\\Selenium\\IEDriverServer.exe");
            driver = new InternetExplorerDriver();
        } else if (browser.equalsIgnoreCase("Firefox")) {
            System.setProperty("webdriver.gecko.driver", "C:\\Selenium\\geckodriver.exe");
            driver = new FirefoxDriver();
        } else if (browser.equalsIgnoreCase("chrome")) {
            System.setProperty("webdriver.chrome.driver", "C:\\Selenium\\chromedriver.exe");
            driver = new ChromeDriver();
        } else if (browser.equalsIgnoreCase("MicrosoftEdge")) {
            System.setProperty("webdriver.edge.driver", "C:\\Selenium\\MicrosoftWebDriver.exe");
            driver = new EdgeDriver();
        }
        setWebDriver(driver);
        this.browser = browser;
        ClientReportFactory.getTest(ExtentTestName, ExtentTestDescription);

baseURL = "testApp.com";
driver.get(baseURL);
        driver.manage().window().maximize();
        }

public WebDriver getDriver(){
    return threadedDriver.get();
}
public void setWebDriver(WebDriver driver) {
    threadedDriver.set(driver);
}

@AfterMethod 
public void afterMethod() {
    ClientReportFactory.closeTest(ExtentTestName, ExtentTestDescription);
    getDriver().quit();
    threadedDriver.set(null);
}

@AfterSuite
public void afterSuite() {
    ClientReportFactory.closeReport();
    if (getDriver() != null) {
        getDriver().quit();
    } else {
        System.out.println("Drivers already closed");
    }
}
dsidler
  • 489
  • 3
  • 13
  • 23

2 Answers2

2

Assuming that all of your @Test methods are in different classes, I am guessing that the problem is perhaps due to the fact that your ThreadLocal variable is NOT STATIC but is an instance variable. This causes the behaviour to be per thread per instance rather than the desired behaviour viz., per thread across all instances. You can refer to this StackOverFlow thread for a better explanation on this.

You would resort to using an instance variant of ThreadLocal if and only if all your @Test methods belong to the same test class (Because now you are only trying to ensure that the class level data member WebDriver is shared in a thread safe manner across all the test methods that belong to the same test class)

So if each of your @Test methods reside in its own Test class, then please try changing:

private ThreadLocal<WebDriver> threadedDriver = new ThreadLocal<WebDriver>();

to

private static ThreadLocal<WebDriver> threadedDriver = new ThreadLocal<WebDriver>();
Community
  • 1
  • 1
Krishnan Mahadevan
  • 14,121
  • 6
  • 34
  • 66
  • Krishnan, what's wrong in my answer below ? I would like to learn. – Dineshmohan Mar 10 '17 at 15:08
  • @Dineshmohan - I have fixed the down vote part. On a side note, I think your solution may be plagued by a weird behaviour that Java exhibits when you try to work with ThreadLocals wherein the initialValue() is being defined within a method scope and when that value depends on an enclosing scope. You can read more about it in my blog post https://rationaleemotions.wordpress.com/2016/05/16/a-date-with-threadlocal/ (I don't know for sure if your code will also exhibit the same problem, but it does have the same sort of code which could cause issues) – Krishnan Mahadevan Mar 11 '17 at 03:22
0

You could try this.

public class DriverFactory(){

private static ThreadLocal<WebDriver> driverThread;
public WebDriver driver;

@Parameters("browser")
public WebDriver instantiateDriverObject(String browser) {
DriverFactory factory = new DriverFactory();
driver = factory.createInstance(browser); //Driver instantiation goes here
driverThread = new ThreadLocal<WebDriver>() {
    @Override
    protected WebDriver initialValue() {
    webDriverPool.add(driver);
    return driver;
    }
};
return driver;
}

public WebDriver getDriver() {
return driverThread.get();
}

}

  • I by mistake ended up clicking that. My apologies. Since yesterday I have been trying to fix it but with little luck. I have now fixed it. Sorry for the confusion. – Krishnan Mahadevan Mar 11 '17 at 03:18