Search code examples
javaselenium-webdriverselenium-chromedriverwebdriverwaitthread-sleep

How to replace Thread.sleep with WebDriverWait to avoid StaleElementReferenceException


Problem 1: I am using https://candymapper.com/ demo page to practice automation tests and I encountered the following problem. When I am using Javascript Executor .scrollIntoView, afterwards my element becomes stale and test breaks.

  1. As using Thread.sleep is not a good practice, is there any other way I can resolve the issue using wdwait?
  2. Also, is there another way to scroll to the element without using Javascript Executor?

Here is my code:

@Test
    public void candyMapperTest() throws InterruptedException {
        driver.get("https://candymapper.com/");
        wdwait.until(ExpectedConditions.presenceOfElementLocated(By.id("popup-widget37723-close-icon")));
        driver.findElement(By.id("popup-widget37723-close-icon")).click();
        wdwait.until(ExpectedConditions.elementToBeClickable(driver.findElement(By.cssSelector("input[data-aid=\"CONTACT_FORM_EMAIL\"]"))));
        jse.executeScript("arguments[0].scrollIntoView(true)", driver.findElement(By.cssSelector("input[data-aid=\"CONTACT_FORM_EMAIL\"]")));
        //Thread.sleep(3000);
        //wdwait.until(ExpectedConditions.refreshed(ExpectedConditions.elementToBeClickable(By.cssSelector("input[data-aid=\"CONTACT_FORM_EMAIL\"]"))));
        //wdwait.until(ExpectedConditions.elementToBeClickable(By.cssSelector("input[data-aid=\"CONTACT_FORM_EMAIL\"]")));
        wdwait.until(ExpectedConditions.elementToBeClickable(driver.findElement(By.cssSelector("input[data-aid=\"CONTACT_FORM_EMAIL\"]"))));
        driver.findElement(By.cssSelector("input[data-aid=\"CONTACT_FORM_EMAIL\"]")).sendKeys("[email protected]");
        wdwait.until(ExpectedConditions.elementToBeClickable(By.cssSelector("button[data-aid=\"CONTACT_SUBMIT_BUTTON_REND\"]")));
        driver.findElement(By.cssSelector("button[data-aid=\"CONTACT_SUBMIT_BUTTON_REND\"]")).click();
        wdwait.until(ExpectedConditions.elementToBeClickable(By.xpath("//span[contains(text(), \"inquiry\")]")));
        Assert.assertEquals("Thank you for your inquiry! We will get back to you within 48 hours.", driver.findElement(By.xpath("//span[contains(text(), \"inquiry\")]")).getText());

Problem 2: I have a similar problem in another test on this page: (https://demoqa.com/automation-practice-form). I want to assert input field border color change to red. Selenium is too fast and does the assert before border color changes, so test breaks with rbg color mismatch. Also, I am using Javascript Executor to click on Submit button, because there is a google ads banner that cannot be closed or minimized (shows only on Chrome).

Submit button hiden behind the ad image

  1. As using Thread.sleep is not a good practice, is there any other way I can resolve the issue using wdwait?
  2. Also, is there another way to click the element in this case without using Javascript Executor?

My code for Problem 2:

@Test
    public void demoQANegativeRegistrationTest() throws InterruptedException {
        driver.get("https://demoqa.com/automation-practice-form");
        driver.findElement(By.id("userNumber")).sendKeys("12345");
        jse.executeScript("arguments[0].click();", driver.findElement(By.id("submit")));
        Thread.sleep(2000);
        //wdwait.until(ExpectedConditions.elementToBeClickable(By.xpath("//*[@class=\"was-validated\"]")));
        WebElement icon = driver.findElement(By.id("userNumber"));
        String userNumberBorderColor = icon.getCssValue("border-color");
        Assert.assertEquals("rgb(220, 53, 69)", userNumberBorderColor);

Problem 1: I tried introducting several different WebDriverWaits like presenceOfElement, elementToBeClickable, refreshed, but each time the result is the same (some of them are commented in the code above). If I add Thread.sleep after jse, test runs perfectly.

Problem 2: I identified class element with value "was-validated" appears after border color changes to red, so I tried waiting on that before doing the assert, but every time test gets wrong rbg color (default grey one). You can find it commented in the code. When I introduce a short Thread.sleep before the assert, test runs perfectly every time.

For clicking on Submit button I tried going fullscreen and using actions.moveToElement but in both cases click would get intercepted by google ads banner. I only managed to perform the click through jse script.


Solution

  • In your first case your are getting StaleElementReferenceException as far as input is re-rendered after load. To avoid it, you should scroll to from first and wait for email input to be rendered.

    Send button itself has debounce (that means that it has delay on click after data was entered by fronted logic), so you should wait for some time after data was entered.

    For scroll you can use moveToElement from Selenium Actions

    Actions actions = new Actions(driver);
    
    WebDriverWait wdwait = new WebDriverWait(driver, 10);
    driver.get("https://candymapper.com/");
    WebElement popupClose = driver.findElement(By.id("popup-widget37723-close-icon"));
    popupClose.click();
    wdwait.until(ExpectedConditions.invisibilityOf(popupClose));
    WebElement form = driver.findElement(By.cssSelector("[data-aid=CONTACT_FORM_CONTAINER_REND]"));
    actions.moveToElement(form).perform();
    WebElement formEmail = wdwait.until(ExpectedConditions.visibilityOfElementLocated(By.cssSelector("input[data-aid=\"CONTACT_FORM_EMAIL\"]")));
    actions.moveToElement(formEmail).click().perform();
    String email = "[email protected]";
    actions.sendKeys(email).sendKeys(Keys.TAB).sendKeys(Keys.TAB).pause(1000).sendKeys(Keys.ENTER).perform();
    

    In second case same as in first - you call assertion too early. Properties all applied async, so making assertion after changes can not work / be stable, as far as changes have not applied yet.

    The solution here is to wait for property to be equal or wait for property does not change during some time interval. There are many approaches how to do it in Java / JS, I share one of them to use combination of ExpectedConditions and JS functions

    This function in JS returns your border-color.

    return window.getComputedStyle(document.querySelector('#userNumber'))['border-color']
    

    So you can just call it inside jsReturnsValue expectations:

    WebElement icon = driver.findElement(By.id("userNumber"));
    wdwait.until(ExpectedConditions.jsReturnsValue("return window.getComputedStyle(document.querySelector('#userNumber'))['border-color'] === 'rgb(220, 53, 69)' ?? undefined"));