Search code examples
selenium-webdriverselenium-chromedriverautomated-teststestng

Weird issues running tests parallely with Selenium, Java, TestNG


When i run my test (not parallel) everything works fine. But when i tried running same test twice there is alot of weird stuff going on. Either blank page or if one of them closes they both close and other weird stuff that i simply cannot explain. First step (login) usually gets triggered on both browsers but after that it seems like they start sharing an instance of something.

How can i fix this and what is the issue with this? Because when im printing out Thread ID and Session ID in my Hooks and inside the test all the test have unique Thread ID and Session ID and they are not duplicated.

I've been googling and trying to find a solution for past 2 days and i'm stuck i just can't figure it out. Please help me i already wasted too much time on this and i do not want to procede with writing new tests untill i have this parallel running issue fixed because its gonna get even worse later i imagine.

I am gonna share my full code below if there is any tips/fixes/improvements please let me down below i already have a headache from this...

My project structure is nothing complex yet.

Inside my src/main/java/com.company i have 3 packages. Config, pages and utils package.

Inside src/main/resources i have my runner.xml and Config folder where i have configuration files for QA environment only for now.

And inside src/test/resources ihave my allure.properties for allure reports.

Inside my Config package i have EnvironmentConfig interface because i am using owner to read properties from my properties files:

@Config.Sources({"classpath:Config/qa.properties"})
public interface EnvironmentConfig extends Config {

    String baseURL();

    @Config.Key("email")
    String email();
    @Config.Key("password")
    String password();

    @Config.Key("email2")
    String email2();
    @Config.Key("password2")
    String password2();

    @Config.Key("ldap.username")
    String ldapUsername();
    @Config.Key("ldap.password")
    String ldapPassword();

    String jwtTokenUrl();

}

Inside my utils package i have my DriverManager and Functions:

public class DriverManager {

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

    public static synchronized WebDriver getDriver() {
        if (driver.get() == null) {
            setDriver();
        }
        return driver.get();
    }

    private static synchronized void setDriver() {
        ChromeOptions options = new ChromeOptions();
        options.addArguments("--start-maximized");
        options.addArguments("--remote-allow-origins=*");
        options.addArguments("window-size=1920,1080");

        WebDriver webDriver = new ChromeDriver(options);
        driver.set(webDriver);

        webDriver.manage().timeouts().implicitlyWait(Duration.ofSeconds(30));
    }

    public static synchronized void quitDriver() {
        if (driver.get() != null) {
            driver.get().quit();
            driver.remove();
        }
    }
public class Functions {

    private WebDriver driver;

    public Functions(WebDriver driver) {
        this.driver = driver;
    }

    public void ScrollAndClick(WebElement ele) throws InterruptedException {
        int viewportHeight = ((Long) ((JavascriptExecutor) driver).executeScript("return window.innerHeight")).intValue();
        int halfViewport = viewportHeight / 2;

        int ele_Position = ele.getLocation().getY();
        ((JavascriptExecutor) driver).executeScript("window.scroll(0, " + (ele_Position - halfViewport) + ");");
        Thread.sleep(1000);
        ele.click();
        Thread.sleep(1000);

    }

}

Inside my tests package i have my test and hooks class:

public class MyTest extends Hooks {

    private Login1 login1;
    private HomePage homePage;
    private Prestep preStep;
    private Step1 step1;
    private Step2 step2;
    private Step3 step3;
    private Payment payment;
    private Login2 login2;
    private VerifyPayment verifyPayment;

    @Test
    public void test1() throws Exception {

        login1 = new Login1(driver);
        login2 = new Login2(driver);
        homePage = new HomePage(driver);
        preStep = new Prestep(driver);
        step1 = new Step1(driver);
        step2 = new tep2(driver);
        step3 = new Step3(driver);
        payment = new Payment(driver);
        verifyPayment = new VerifyPayment(driver);

        // PRESTEP

        login1.login1(config.ldapUsername(), config.ldapPassword());

        homePage.clickBancneKarticeButton();

        preStep.waitForPreStepPageToLoad(config.baseURL());

        preStep.clickNeButton();

        // KORAK 1

        step1.waitForStep1PageToBeDisplayed(config.baseURL());

        step1.clickButtonNaprej();

        // KORAK 2

        step2.waitForStep2PageToBeDisplayed(config.baseURL());

        step2.clickPogoj1();
        step2.clickPogoj2();
        step2.clickButtonNaprej();

        // KORAK 3

        step3.waitForStep1PageToBeDisplayed(config.baseURL());

        step3.clickPrijaviteSeButton();

        login2.login2(config.emailTier30(), config.passwordTier30());

        step3.waitForStep1PageToBeDisplayed(config.baseURL());

        step3.clickPogoj1();
        step3.clickPogoj2();

        step3.clickButtonpayment();

        // PREPARE PAYMENT

        payment.waitForpaymentPageToBeDisplayed(config.baseURL());

        payment.clickKreditnaKartica();

        payment.clickSimulirajPlacilo();

        // VALIDATE PAYMENT

        verifyPayment.waitForSklenitevUspesnaTextToBeVisible();

    }

    /* ============================================================================================== */

    @Test
    public void test2() throws Exception {

                login1 = new Login1(driver);
        login2 = new Login2(driver);
        homePage = new HomePage(driver);
        preStep = new Prestep(driver);
        step1 = new Step1(driver);
        step2 = new tep2(driver);
        step3 = new Step3(driver);
        payment = new Payment(driver);
        verifyPayment = new VerifyPayment(driver);

        // PRESTEP

        login1.login1(config.ldapUsername(), config.ldapPassword());

        homePage.clickBancneKarticeButton();

        preStep.waitForPreStepPageToLoad(config.baseURL());

        preStep.clickNeButton();

        // KORAK 1

        step1.waitForStep1PageToBeDisplayed(config.baseURL());

        step1.clickButtonNaprej();

        // KORAK 2

        step2.waitForStep2PageToBeDisplayed(config.baseURL());

        step2.clickPogoj1();
        step2.clickPogoj2();
        step2.clickButtonNaprej();

        // KORAK 3

        step3.waitForStep1PageToBeDisplayed(config.baseURL());

        step3.clickPrijaviteSeButton();

        login2.login2(config.emailTier30(), config.passwordTier30());

        step3.waitForStep1PageToBeDisplayed(config.baseURL());

        step3.clickPogoj1();
        step3.clickPogoj2();

        step3.clickButtonpayment();

        // PREPARE PAYMENT

        payment.waitForpaymentPageToBeDisplayed(config.baseURL());

        payment.clickKreditnaKartica();

        payment.clickSimulirajPlacilo();

        // VALIDATE PAYMENT

        verifyPayment.waitForSklenitevUspesnaTextToBeVisible();

    }
}

And hooks:

public class Hooks {

    protected WebDriver driver;
    protected EnvironmentConfig config;

    @BeforeSuite
    public void beforeSuite() {
        config = ConfigFactory.create(EnvironmentConfig.class);
    }

    @BeforeMethod
    public void setUp() {
        driver = DriverManager.getDriver();

        driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(30));

        long threadId = Thread.currentThread().getId();
        String sessionId = ((ChromeDriver) driver).getSessionId().toString();
        System.out.println("Thread ID: " + threadId + ", Session ID: " + sessionId);
    }

    @AfterMethod
    public void tearDown() {
        DriverManager.quitDriver();
    }

    @AfterSuite
    public void afterSuite() {
        // to be defined
    }

}

My runner.xml

<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >

<suite name = "Suite" parallel = "methods" verbose="8" thread-count="7">

    <test name = "Tests">
        <classes>
            <class name = "com.company.tests.MyTest" />
        </classes>
    </test>

</suite>

I am gonna put all of my POM Pages down here. Since i am thinking at some point both tests share instance maybe one of the pages is only broken. Probably it is not needed to put all of them but i will just in case:

Login1:

public class Login1 {

    private WebDriver driver;
   // private WebDriverWait wait;

    @FindBy(xpath = "//*[@id='username']")
    public WebElement inputUsername;

    @FindBy(xpath = "//*[@id='password']")
    public WebElement inputPassword;

    @FindBy(xpath = "//button[@id='onetrust-accept-btn-handler']")
    public WebElement buttonAcceptCookies;

    @FindBy(xpath = "//*[@id='onetrust-reject-all-handler']")
    public WebElement buttonRejectCookies;

    @FindBy(xpath = "//button[contains(@class, 'onetrust-close-btn-ui')]")
    public WebElement buttonCloseCookies;

    @FindBy(xpath = "//input[contains(@class, 'nextStep')]")
    public WebElement buttonPrijava;


    public Login1(WebDriver driver) {
        this.driver = driver;
        //this.wait = new WebDriverWait(driver, Duration.ofSeconds(10));
        PageFactory.initElements(driver, this);
    }

    public void login1(String username, String password) {
        driver.get("URL");
        clickAcceptCookies();
        sendUsername(username);
        sendPassword(password);
        clickPrijava();
    }

    public void sendUsername(String username) {
        inputUsername.sendKeys(username);
    }

    public void sendPassword(String password) {
        inputPassword.sendKeys(password);
    }

    public void clickAcceptCookies() {

        //wait.until(ExpectedConditions.visibilityOf(buttonAcceptCookies));

        buttonAcceptCookies.click();
    }

    public void clickRejectCookies() {
        buttonRejectCookies.click();
    }

    public void clickCloseCookies() {
        buttonCloseCookies.click();
    }

    public void clickPrijava() {
        buttonPrijava.click();
    }

}

Login2:

public class Login2 {

    private WebDriver driver;
    private WebDriverWait wait;

    @FindBy(id = "email")
    public WebElement inputEmail;

    @FindBy(id = "Password")
    public WebElement inputPassword;

    @FindBy(id = "next")
    public WebElement buttonNext;

    public void login2(String email, String password) {
        inputItriglavEmail(email);
        inputItriglavPassword(password);
        clickNext();
    }


    public void inputItriglavEmail(String email) {
       // wait.until(ExpectedConditions.visibilityOf(inputEmail));
        inputEmail.sendKeys(email);
    }

    public void inputItriglavPassword(String password) {
        inputPassword.sendKeys(password);
    }

    public void clickNext() {
        buttonNext.click();
    }
    
    public Login2(WebDriver driver) {
        this.driver = driver;
        //this.wait = new WebDriverWait(driver, Duration.ofSeconds(30));
        PageFactory.initElements(driver, this);
    }
}

HomePage:

public class HomePage {

    private WebDriver driver;
    //private WebDriverWait wait;

    @FindBy(xpath = "//a[text()='Avtomobil']")
    public WebElement avtomobilButton;

    @FindBy(xpath = "//a[text()='Bančne kartice']")
    public WebElement bancneKarticeButton;

    public HomePage(WebDriver driver) {
        this.driver = driver;
        //this.wait  = new WebDriverWait(driver, Duration.ofSeconds(30));
        PageFactory.initElements(driver, this);
    }

    public void clickAvtomobilButton() {
        avtomobilButton.click();
    }

    public void clickBancneKarticeButton() {
      //  wait.until(ExpectedConditions.elementToBeClickable(bancneKarticeButton));

        bancneKarticeButton.click();
    }
}

Prestep:

public class Prestep {

    private WebDriver driver;
    private WebDriverWait wait;
    private Functions f;

    public Prestep(WebDriver driver) {
        this.driver = driver;
        this.wait = new WebDriverWait(driver, Duration.ofSeconds(30));
        this.f = new Functions(driver);
        PageFactory.initElements(driver, this);
    }

    @FindBy(xpath = "//strong[contains(@class, 'labelLeft')]")
    public WebElement neButton;

    @FindBy(xpath = "//strong[@class='labelRight']")
    public WebElement daButton;

    @FindBy(xpath = "//input[contains(@class, 'nextStep')]")
    public WebElement buttonNaprej;

    public void waitForPreStepPageToLoad(String url) {
       wait.until(ExpectedConditions.urlToBe(url + "/kartice/kartice_zavarovanje"));
    }

    public void clickNeButton() {
        neButton.click();
    }

    public void clickDaButton() {
        daButton.click();
    }

    public void clickButtonNaprej() throws InterruptedException {
        f.ScrollAndClick(buttonNaprej);
    }

}

Step1:

public class Step1 {

    private WebDriver driver;
    private WebDriverWait wait;
    private Functions f;

    String urlStep1 = "/kartice/kartice_zavarovanje_1";

    @FindBy(xpath = "//input[contains(@class, 'nextStep')]")
    public WebElement buttonNaprej;

    public Step1(WebDriver driver) {
        this.driver = driver;
        this.wait = new WebDriverWait(driver, Duration.ofSeconds(30));
        this.f = new Functions(driver);
        PageFactory.initElements(driver, this);
    }

    public void clickButtonNaprej() throws InterruptedException {

        f.ScrollAndClick(buttonNaprej);
    }
    
    public void waitForStep1PageToBeDisplayed(String url) {
        wait.until(ExpectedConditions.urlToBe(url + urlStep1));
    }

}

Step2:

public class Step2 {

    private WebDriver driver;
    private WebDriverWait wait;
    private Functions f;

    String urlStep2 = "/kartice/kartice_zavarovanje_2";

    @FindBy(xpath = "(//*[@class='cs-slider'])[1]")
    public WebElement pogoj1;

    @FindBy(xpath = "(//*[@class='cs-slider'])[2]")
    public WebElement pogoj2;

    @FindBy(xpath = "//input[contains(@class, 'nextStep')]")
    public WebElement buttonNaprej;
    
    public void clickButtonNaprej() {
        buttonNaprej.click();
    }

    public void clickPogoj1() throws InterruptedException {
        f.ScrollAndClick(pogoj1);
    }

    public void clickPogoj2() throws InterruptedException {
        f.ScrollAndClick(pogoj2);
    }

    public void waitForStep2PageToBeDisplayed(String url) {
        wait.until(ExpectedConditions.urlToBe(url + urlStep2));
    }


    public Step2(WebDriver driver) {
        this.driver = driver;
        this.wait = new WebDriverWait(driver, Duration.ofSeconds(30));
        this.f = new Functions(driver);
        PageFactory.initElements(driver, this);
    }
}

Step3:

public class Step3 {

    private WebDriver driver;
    private WebDriverWait wait;
    private Functions f;

    String urlStep3 = "/kartice/kartice_zavarovanje_3";

    @FindBy(xpath = "//a[text()='Prijavite se']")
    public WebElement prijaviteSeButton;

    @FindBy(xpath = "(//*[@class='cs-slider'])[1]")
    public WebElement pogoj1;

    @FindBy(xpath = "(//*[@class='cs-slider'])[2]")
    public WebElement pogoj2;

    @FindBy(id = "preparePaymentButton")
    public WebElement buttonPreparePayment;

    public void clickPrijaviteSeButton() {
        prijaviteSeButton.click();
    }

    public void clickPogoj1() throws InterruptedException {
        f.ScrollAndClick(pogoj1);
    }

    public void clickPogoj2() throws InterruptedException {
        f.ScrollAndClick(pogoj2);
    }

    public void clickButtonPreparePayment() throws InterruptedException {
        f.ScrollAndClick(buttonPreparePayment);
    }

    public void waitForStep1PageToBeDisplayed(String url) {
        wait.until(ExpectedConditions.urlToBe(url + urlStep3));
    }
    
    
    public Step3(WebDriver driver) {
        this.driver = driver;
        this.f = new Functions(driver);
        this.wait = new WebDriverWait(driver, Duration.ofSeconds(30));
        PageFactory.initElements(driver, this);
    }
}

Payment:

public class Payment {

    private WebDriver driver;
    private WebDriverWait wait;
    private Functions f;

    String urlPreparePayment = "/kartice/preparePayment";

    @FindBy(xpath = "//*[text() = 'Kreditna kartica 1']")
    public WebElement kreditnaKartica;

    @FindBy(xpath = "//*[text() = 'Simuliraj plačilo']")
    public WebElement buttonSimulirajPlacilo;

    public void clickKreditnaKartica() {
        kreditnaKartica.click();
    }

    public void clickSimulirajPlacilo() throws InterruptedException {
        f.ScrollAndClick(buttonSimulirajPlacilo);
    }

    public void waitForPreparePaymentPageToBeDisplayed(String url) {
       wait.until(ExpectedConditions.urlToBe(url + urlPreparePayment));
    }

    public Payment(WebDriver driver) {
        this.driver = driver;
        this.f = new Functions(driver);
        this.wait = new WebDriverWait(driver, Duration.ofSeconds(30));
        PageFactory.initElements(driver, this);
    }

And finally last one. VerifyPayment:

public class VerifyPayment {

    private WebDriver driver;
    private WebDriverWait wait;

    @FindBy(xpath = "//*[text() = 'SKLENITEV USPEŠNA']")
    public WebElement sklenitevUspesnaText;


    public void waitForSklenitevUspesnaTextToBeVisible() throws Exception {

        wait.until(ExpectedConditions.visibilityOf(sklenitevUspesnaText));

        if(sklenitevUspesnaText.isDisplayed()) {
            System.out.println("successfull");
        } else {
            throw new Exception("failed");
        }

    }
    
    public VerifyPayment(WebDriver driver) {
        this.driver = driver;
        this.wait = new WebDriverWait(driver, Duration.ofSeconds(30));
        PageFactory.initElements(driver, this);
    }
}

Solution

  • Please fix the below problems in your code.

    • Please get rid of the WebDriver data member in your Hooks java class and instead define a method as below, so that all the child classes of Hooks use this new method to get access to the WebDriver instance.
    public WebDriver driver() {
        return Optional.ofNullable(DriverManager.driver())
                .orElseThrow(
                        () -> new IllegalStateException("No valid webdriver instance found for current thread")
                );
    }
    

    This will ensure that when you run tests in a concurrent fashion (your child classes of Hooks seem to have multiple @Test methods within them), they all would get the proper webdriver instance for that particular thread.

    • Please get rid of all these class level data members from the child classes of Hooks

    private Login1 login1; private HomePage homePage; private Prestep preStep; private Step1 step1; private Step2 step2; private Step3 step3; private Payment payment; private Login2 login2; private VerifyPayment verifyPayment;

    These all need to become local variables within each of your @Test methods. The reason why this is needed because, when you have 2 or more @Test methods in a test class and when you run these tests in parallel, you end up with your test methods referring to the wrong webdriver instance which causes all your erratic behavior. Your test classes should have ONLY local variables. That way, they all will get reference to the right contextual webdriver for the specific thread.