0

I get the following when running my Selenium framework in parallel mode using TestNG.

org.openqa.selenium.NoSuchSessionException: invalid session id

The issue only happens in parallel mode and when it runs in serial mode I have no issues what so ever.

Base Test

package training.testComponents;

import java.io.FileInputStream;
import java.io.IOException;
import java.time.Duration;
import java.util.Properties;

import org.openqa.selenium.Dimension;
import org.openqa.selenium.Point;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.edge.EdgeDriver;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.testng.Assert;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;

import training.pageobjects.CartPage;
import training.pageobjects.CheckOutPage;
import training.pageobjects.HomePage;
import training.pageobjects.LandingPage;
import training.pageobjects.OrderConfirmationPage;
import training.pageobjects.OrdersPage;

public class BaseTest {

    public WebDriver driver;
    FileInputStream file;   
    Properties properties;
    
    final String landingURL = "https://rahulshettyacademy.com/client";
    final String landingTitle = "Let's Shop";
    final String loginSuccessMessage ="Login Successfully";
    final String loginFailMessage ="Incorrect email or password.";
    final String cartPageTitle = "My Cart"; 
    final String paymentPageTitle = "Payment Method";
    final String successfulOrderText = "THANKYOU FOR THE ORDER.";
    final int maxTimeOut = 1;
    
    public LandingPage landing;
    public HomePage home;
    public CartPage cart;
    public OrderConfirmationPage orderConfirmation;
    public CheckOutPage checkOut;
    public OrdersPage orders;
    

    void initializeDriver() throws IOException {

        properties = new Properties();
        file = new FileInputStream(
                System.getProperty("user.dir") + "\\src\\main\\java\\training\\resources\\GlobalData.properties");
        properties.load(file);
        String browserName = properties.getProperty("browser");

        if (browserName.equalsIgnoreCase("chrome")) {
            driver = new ChromeDriver();            
        }
        if (browserName.equalsIgnoreCase("firefox")) {
            driver = new FirefoxDriver();
        }
        if (browserName.equalsIgnoreCase("edge")) {
            driver = new EdgeDriver();
        }
        driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(maxTimeOut));
        driver.manage().window().setPosition(new Point(0, 0));
        driver.manage().window().setSize(new Dimension(1920, 1080));
    }
    
    @BeforeMethod(alwaysRun=true)//to run in all groups
    public void lunchBrowser() throws IOException {
        initializeDriver();
        landing = new LandingPage(driver);
        home = new HomePage(driver);
        cart = new CartPage (driver);
        orderConfirmation = new OrderConfirmationPage(driver);
        checkOut = new CheckOutPage(driver);
        orders = new OrdersPage(driver);
        
        landing.goToLandingPage(landingURL);        
        Assert.assertTrue(landing.getLandingPageTitle().equalsIgnoreCase(landingTitle));
        
    }
    @AfterMethod(alwaysRun=true)//to run in all groups
    public void closeBrowser() {
        driver.close();
    }
    public String cartPageHeader() {
        return cartPageTitle;
    }
    
    public String checkOutHeather() {
        return paymentPageTitle;
    }
    
    public String orederConfirmationText() {
        return successfulOrderText;
    }
    
    public String loginSuccessText() {
        return loginSuccessMessage;
    }   
    public String loginFailText() {
        return loginFailMessage;
    }
    
}
        landing = new LandingPage(driver);
        home = new HomePage(driver);
        cart = new CartPage (driver);
        orderConfirmation = new OrderConfirmationPage(driver);
        checkOut = new CheckOutPage(driver);
        orders = new OrdersPage(driver);
        
        landing.goToLandingPage(landingURL);        
        Assert.assertTrue(landing.getLandingPageTitle().equalsIgnoreCase(landingTitle));
        
    }
    @AfterMethod(alwaysRun=true)//to run in all groups
    public void closeBrowser() {
        driver.close();
    }
    public String cartPageHeader() {
        return cartPageTitle;
    }
    
    public String checkOutHeather() {
        return paymentPageTitle;
    }
    
    public String orederConfirmationText() {
        return successfulOrderText;
    }
    
    public String loginSuccessText() {
        return loginSuccessMessage;
    }   
    public String loginFailText() {
        return loginFailMessage;
    }
    
}

Test Class 1

package training.tests;

import java.io.IOException;

import org.testng.Assert;
import org.testng.annotations.Test;

import training.testComponents.BaseTest;

public class PlaceOrderTests extends BaseTest {

    String pass = "Password123";
    String user = "randomemail@random.com";
    String itemName = "ZARA";

    @Test
    public void submitOrder() throws IOException {      
        String inputText = "IND";
        String wantedCountry = "india";

        // Landing page login
        Assert.assertTrue(landing.logInSecuence(user, pass).equalsIgnoreCase(loginSuccessText()));
        // Product Catalog Page
        Assert.assertTrue(home.addProductToCart(itemName));
        home.goToCart();
        // my cart page
        Assert.assertTrue(cart.getPageHeadther().equalsIgnoreCase(cartPageHeader()));
        Assert.assertTrue(cart.verifyItemInCart(itemName));
        cart.goToCheckout();
        // Checkout page
        Assert.assertTrue(checkOut.getPageHeadther().equalsIgnoreCase(checkOutHeather()));
        Assert.assertTrue(checkOut.selectCountry(inputText, wantedCountry));
        checkOut.placeOrder();
        // Order Confirmation
        Assert.assertTrue(orderConfirmation.verification().equalsIgnoreCase(orederConfirmationText()));
    }
    @Test(dependsOnMethods= {"submitOrder"})
    public void wrongProductName() throws IOException {
        // Landing page login
        Assert.assertTrue(landing.logInSecuence(user, pass).equalsIgnoreCase(loginSuccessText()));
        // Product Catalog Page
        Assert.assertTrue(home.addProductToCart(itemName));
        home.goToCart();
        // my cart page
        Assert.assertTrue(cart.getPageHeadther().equalsIgnoreCase(cartPageHeader()));
        Assert.assertFalse(cart.verifyItemInCart(itemName+"AAA"));      
    }
    @Test
    public void verifyOrder() throws IOException {
        // Landing page login
        Assert.assertTrue(landing.logInSecuence(user, pass).equalsIgnoreCase(loginSuccessText()));
        // Product Catalog Page
        Assert.assertTrue(home.addProductToCart(itemName));
        home.goToOrders();
        // my cart page
        Assert.assertTrue(orders.orderIsDisplayed(itemName));           
    }

}

Test Class 2

package training.tests;

import org.testng.Assert;
import org.testng.annotations.Test;

import training.testComponents.BaseTest;

public class ErrorValidationsTests extends BaseTest {
    
    String pass = "Password";
    String user = "randomemail@random.com";

    @Test(groups= {"Error Handling"})
    public void wrongPassword() {
        Assert.assertTrue(landing.logInSecuence(user, pass).equalsIgnoreCase(loginFailText()));
    }
}

Page Objects

package training.pageobjects;

import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;

import training.abstractcomponents.AbstractComponents;

public class LandingPage extends AbstractComponents {
    // local variables
    WebDriver driver;

    // constructor
    public LandingPage(WebDriver driver) {
        // driver initialization
        super(driver);
        this.driver = driver;
        PageFactory.initElements(driver, this);
    }

    // Page Factory

    // UserID box
    @FindBy(id = "userEmail")
    WebElement userID;

    // Password box
    @FindBy(id = "userPassword")
    WebElement password;

    // Login button
    @FindBy(id = "login")
    WebElement loginButton;

    // Login confirmation
    @FindBy(xpath = "//*[@aria-label='Login Successfully']")
    WebElement loginToast;

    // Login failed
    @FindBy(className = "toast-error")
    WebElement loginToastFail;

    // Action Methods

    public void goToLandingPage(String landingURL) {
        driver.get(landingURL);
    }

    public String getLandingPageTitle() {
        return driver.getTitle();
    }

    public String logInSecuence(String id, String pass) {
        String text = null;
        userID.sendKeys(id);
        password.sendKeys(pass);
        loginButton.click();

        try {
            if (loginToast.isDisplayed()) {
                text = loginToast.getText();
                waitForElementInvisibility(loginToast);
            }
        } catch (NoSuchElementException e) {
            if (loginToastFail.isDisplayed() == true) {
                text = loginToastFail.getText();
                waitForElementInvisibility(loginToastFail);
            }
        }
        return text;
    }
}
package training.pageobjects;

import java.util.List;

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;

import training.abstractcomponents.AbstractComponents;

public class HomePage extends AbstractComponents {

    private WebDriver driver;

    public HomePage(WebDriver driver) {
        // TODO Auto-generated constructor stub
        super(driver);
        this.driver = driver;
        PageFactory.initElements(driver, this);
    }

    // By locators
    // Displayed product name
    static By productBy = By.tagName("b");
    // Add to cart button
    static By addToCart = By.className("w-10");

    // Page Factory
    // Catalog elements
    @FindBy(className = "mb-3")
    private List<WebElement> products;

    // waiting spinner
    @FindBy(className = "ng-animating")
    private WebElement spinner;

    // added to cart toast
    @FindBy(className = "toast-success")
    private WebElement toastAdded;

    // Action Methods
    List<WebElement> getProductCatalog() {
        waitForElementVisibility(products.get(1));
        return products;
    }

    public boolean addProductToCart(String productName) {
        WebElement prod = getProductCatalog().stream()
                .filter(product -> product.findElement(productBy).getText().contains(productName)).findFirst()
                .orElse(null);
        if (prod != null) {
            prod.findElement(addToCart).click();
            waitForElementVisibility(spinner);
            // waitForElementInvisibility(spinner);
            waitForElementVisibility(toastAdded);
            waitForElementInvisibility(toastAdded);
            return true;
        } else {
            return false;
        }
    }
}
package training.pageobjects;

import java.util.List;

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;

import training.abstractcomponents.AbstractComponents;

public class CartPage extends AbstractComponents {

    private WebDriver driver;
    
    // By locators
    // Items in cart
    static By cartSection = By.xpath("//ul[contains(@class,'cartWrap')]");

    public CartPage(WebDriver driver) {
        super(driver);
        this.driver = driver;
        PageFactory.initElements(driver, this);
    }

    @FindBy(xpath = "//ul[contains(@class,'cartWrap')]")
    private List<WebElement> cart;

    @FindBy(xpath = "//*[contains(@class, 'totalRow')]/button")
    private WebElement checkOutButton;

    @FindBy(xpath = "//h1[contains(text(), 'My Cart')]")
    static WebElement myCartTitle;

    // Action Methods
    List<WebElement> getCartItems() {
        waitForElementVisibility(cart.get(0));
        return cart;
    }

    public boolean verifyItemInCart(String productName) {
        boolean match = getCartItems().stream()
                .anyMatch(items -> items.findElement(cartSection).getText().contains(productName));
        return match;
    }

    public void goToCheckout() {
        checkOutButton.click();
    }

    public String getPageHeadther() {
        return myCartTitle.getText();
    }

}
package training.pageobjects;

import java.util.List;

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;

import training.abstractcomponents.AbstractComponents;

public class CheckOutPage extends AbstractComponents {

    private WebDriver driver;

    public CheckOutPage(WebDriver driver) {
        super(driver);
        this.driver = driver;
        PageFactory.initElements(driver, this);
    }

    // country text box
    @FindBy(xpath = "//input[@placeholder='Select Country']")
    private WebElement countryInput;

    // county options
    @FindBy(xpath = "//section/button")
    private List<WebElement> countryOptions;

    // place order button
    @FindBy(xpath = "//a[contains(@class, 'submit')]")
    private WebElement placeOrder;

    @FindBy(xpath = "//*[@class='payment__title']")
    static WebElement paymentTitle;

    List<WebElement> getCountryOptions(String input) {
        countryInput.sendKeys(input);
        waitForElementVisibility(countryOptions.get(0));
        return countryOptions;
    }

    public boolean selectCountry(String input, String option) {
        List<WebElement> countries = getCountryOptions(input);
        suggestiveTextBox(countries, option).get(0).click();
        return (getInputText(countryInput).equalsIgnoreCase(option));
    }

    public void placeOrder() {
        placeOrder.click();
    }

    public String getPageHeadther() {
        return paymentTitle.getText();
    }
}
package training.pageobjects;

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;

import training.abstractcomponents.AbstractComponents;

public class OrderConfirmationPage extends AbstractComponents {

    private WebDriver driver;   

    public OrderConfirmationPage(WebDriver driver) {
        super(driver);
        this.driver = driver;
        PageFactory.initElements(driver, this);
    }

    @FindBy(tagName = "h1")
    private WebElement confirmationText;

    public String verification() {
        return confirmationText.getText();
    }
}
package training.pageobjects;

import java.util.List;

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;

import training.abstractcomponents.AbstractComponents;

public class OrdersPage extends AbstractComponents{
    private WebDriver driver;
    
    @FindBy(xpath = "//tr/td[2]")
    private List<WebElement> productNames;

    public OrdersPage(WebDriver driver) {
        // TODO Auto-generated constructor stub
        super(driver);
        this.driver = driver;
        PageFactory.initElements(driver, this);
    }
    
    public boolean orderIsDisplayed(String productName) {
        boolean match;      
        match = productNames.stream().anyMatch(product->product.getText().contains(productName.toLowerCase()));
        return match;
    }       

}

Common methods for the Page Objects

package training.abstractcomponents;

import java.time.Duration;
import java.util.List;
import java.util.stream.Collectors;

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;

import training.pageobjects.CartPage;
import training.pageobjects.HomePage;
import training.pageobjects.OrdersPage;

public class AbstractComponents {

    protected WebDriver driver;
    int maxDuration = 5;
    
    HomePage home;
    OrdersPage orders;
    CartPage cart;

    public AbstractComponents(WebDriver driver) {
        this.driver = driver;
        PageFactory.initElements(driver, this);
    }

    // Banner Elements  
    @FindBy(xpath = "//button[@routerlink = '/dashboard/']")
    WebElement homeButton;
    
    @FindBy(xpath = "//button[contains(@routerlink,'myorders')]")
    WebElement ordersButton;

    @FindBy(xpath = "//button[contains(@routerlink,'cart')]")
    WebElement cartButton;

    @FindBy(className = "fa-sign-out")
    WebElement signOutButton;

    public HomePage goToHome() {
        homeButton.click();
        home = new HomePage(driver);
        return home;
    }
    
    public OrdersPage goToOrders() {
        ordersButton.click();
        orders = new OrdersPage(driver);
        return orders;
    }
    
    public CartPage goToCart() {
        cartButton.click();
        cart = new CartPage(driver);
        return cart;
    }
    
    public void signOut() {
        signOutButton.click();
    }

    public void waitForElementVisibility(WebElement element) {
        WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(maxDuration));
        wait.until(ExpectedConditions.visibilityOf(element));
    }

    public void waitForElementInvisibility(WebElement element) {
        WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(maxDuration));
        wait.until(ExpectedConditions.invisibilityOf(element));
    }

    public List<WebElement> suggestiveTextBox(List<WebElement> elements, String option) {
        return elements.stream().filter(country -> country.getText().equalsIgnoreCase(option))
                .collect(Collectors.toList());

    }

    public String getInputText(WebElement countryInput) {
        return countryInput.getAttribute("value");
    }

}

TestNG XML file

?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="Suite" parallel="methods">
  <test thread-count="5" name="Place Order Tests">
    <classes>
      <class name="training.tests.PlaceOrderTests"/>      
    </classes>
  </test> <!-- Test -->
  <test thread-count="5" name="Login Error Validations Tests">
    <classes>      
      <class name="training.tests.ErrorValidationsTests"/>
    </classes>
  </test> <!-- Test -->
</suite> <!-- Suite -->

I have tried changing the driver to static in both the base class and the PO I even tried with ThreadLocal, but wasn't able to make it work.

If anyone wants the full code, it can be found here

https://github.com/MReyes-1/seleniumTraining/tree/main/SeleniumFramework

Looks like there is something with the driver initialization but not sure what

2 Answers2

0

The problem lies in your code training.testComponents.BaseTest

  • You have multiple test methods that would be relying on the driver object that gets initialised via your @BeforeMethod backed by the method training.testComponents.BaseTest#lunchBrowser
  • Your driver object is a static data member.
  • When you run @Test1 methods in parallel, then the @BeforeMethod also run in parallel causing multiple @BeforeMethod methods to try and set different WebDriver objects to the same driver.

To fix this, you would need to use ThreadLocal container to hold your WebDriver objects.

You can read more about how to work with ThreadLocal and how to go about building this in detail in my blog here

Sometime back I built a simple library that can basically get this done for you.

Please refer to autospawn for more details

Krishnan Mahadevan
  • 14,121
  • 6
  • 34
  • 66
  • Thank you for your answer. I will check the information and confirm how it goes. – michel reyes May 24 '23 at 13:12
  • @michelreyes - If the answer helps, please don't forget to accept the answer so that the question gets closed as well. – Krishnan Mahadevan May 25 '23 at 04:10
  • I tried and ended up making more of a mess than what I solved. One thing is that I cant run a single test case when the driver is initialized in the Listeners. I'm also using ITestListener instead of InvokedMethodListener not sure if that makes any difference. Is it possible to create the ThreadLocal web drivers in the @before test annotation instead? – michel reyes May 26 '23 at 19:56
  • One more thing I noticed is that data providers don't seem to work – michel reyes May 26 '23 at 23:07
0

Here is what I ended up doing.

Taking into account Krishnan Mahadevan post, I followed some of his instructions and added my own modifications. I created a DriverThreadLocal class and a DriverManager following the previous answer.

Driver manager

the class is final so nothing can extend to it and there is a private constructor so no new objects can be created from this class

package training.driverresources;

import org.openqa.selenium.WebDriver;

public final class DriverManager {
    private DriverManager() {
    }

    private static ThreadLocal<WebDriver> driver = new ThreadLocal<WebDriver>();

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

    public static void setDriver(WebDriver driverRedf) {
        driver.set(driverRedf);
    }

    public static void unloadDriver() {
        driver.remove();
    }

}

Here is the driver class

package training.driverresources;

import java.io.IOException;
import java.time.Duration;

import org.openqa.selenium.Dimension;
import org.openqa.selenium.Point;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.edge.EdgeDriver;
import org.openqa.selenium.firefox.FirefoxDriver;

import training.utilities.ReadPropertiesFile;

//final prevents classes to extend to this to this class
public final class ThreadLocalDriver {
    
    //private constructor prevents the creation of objects of this class
    private ThreadLocalDriver() {
    }

    public static void initializeDriver() throws IOException {
        WebDriver driver = null;
        String browserName = ReadPropertiesFile.getValue("browser");
        int maxTimeOut = Integer.valueOf(ReadPropertiesFile.getValue("inplicitWait"));

        if (browserName.equalsIgnoreCase("chrome")) {
            driver = new ChromeDriver();
        }
        if (browserName.equalsIgnoreCase("firefox")) {
            driver = new FirefoxDriver();
        }
        if (browserName.equalsIgnoreCase("edge")) {
            driver = new EdgeDriver();
        }
        driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(maxTimeOut));
        driver.manage().window().setPosition(new Point(0, 0));
        driver.manage().window().setSize(new Dimension(1920, 1080));
        DriverManager.setDriver(driver);
    }

    public static void quitDriver() {
        DriverManager.getDriver().quit();
        DriverManager.unloadDriver();
    }
}

With that I realized I had some issues in my base class, which was modified as follows note that the driver is initialized in the @BeforeMethod annotation and not in the listener

package training.testcomponents;

import java.io.IOException;

import org.testng.Assert;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;

import training.data.Constants;
import training.driverresources.DriverManager;
import training.driverresources.ThreadLocalDriver;
import training.pageobjects.CartPage;
import training.pageobjects.CheckOutPage;
import training.pageobjects.CommonPageObjects;
import training.pageobjects.HomePage;
import training.pageobjects.LandingPage;
import training.pageobjects.OrderConfirmationPage;
import training.pageobjects.OrdersPage;
import training.utilities.ReadPropertiesFile;


public class BaseTest extends DataProviders {
    

    @BeforeMethod
    protected void lunchBrowser() throws IOException {
        ThreadLocalDriver.initializeDriver();       
        landing().goToLandingPage(ReadPropertiesFile.getValue("landingURL"));
        Assert.assertTrue(landing().getLandingPageTitle().equalsIgnoreCase(Constants.landingTitle()));
    }

    @AfterMethod
    protected void tearDown() throws IOException {
        ThreadLocalDriver.quitDriver();
    }
    
    protected LandingPage landing() {
        LandingPage landing = new LandingPage(DriverManager.getDriver());
        return landing;
        }
    protected HomePage home() {
        HomePage home = new HomePage(DriverManager.getDriver());
        return home;
        }
    protected CommonPageObjects commonObjects() {
        CommonPageObjects commonObjects = new CommonPageObjects(DriverManager.getDriver());
        return commonObjects;
        }
    protected CartPage cart() {
        CartPage cart = new CartPage(DriverManager.getDriver());
        return cart;
        }
    protected CheckOutPage checkOut() {
        CheckOutPage outPage = new CheckOutPage(DriverManager.getDriver());
        return outPage;
        }
    protected OrderConfirmationPage orderConfirmation() {
        OrderConfirmationPage orderConfirmation = new OrderConfirmationPage(DriverManager.getDriver());
        return orderConfirmation;
        }
    protected OrdersPage orders() {
        OrdersPage ordersPage = new OrdersPage(DriverManager.getDriver());
        return ordersPage;
        }
}

Mistakes I made previously: creating the page objects as global variables. It was fixed by creating methods that returned the page object when needed. This methods are called in the test classes.

With this solution, tests can work without the listeners, meaning that it can be debugged. By having the driver initialization in the listeners, the only way to run the cases was to run a suite.