26

I get this error when running my tests: org.openqa.selenium.StaleElementReferenceException: Element is no longer attached to the DOM

any idea on how to solve the above exception? this happen in my grid which has a ref Xpath expression which is dynamic

Ripon Al Wasim
  • 36,924
  • 42
  • 155
  • 176
Maalamaal
  • 963
  • 3
  • 11
  • 23

11 Answers11

33

I ran across this same problem and could not find any solutions. Came up with a solution and posting it here, hope this helps someone with the same problem. I created a class to handle stale elements depending on their type, cssselector, id, etc and simply call it like I would any other page object.

public void StaleElementHandleByID (String elementID)
{
    int count = 0;
    boolean clicked = false;
    while (count < 4 && !clicked)
    {
        try 
        {
            WebElement yourSlipperyElement= driver.findElement(By.id(elementID));
            yourSlipperyElement.click(); 
            clicked = true;
        } 
        catch (StaleElementReferenceException e)
        {
            e.toString();
            System.out.println("Trying to recover from a stale element :" + e.getMessage());
            count = count+1;
        }
    }
}

I'd recommend only using this on elements you know cause problems for WebDriver.

Jakob Busk Sørensen
  • 5,599
  • 7
  • 44
  • 96
Armando
  • 622
  • 8
  • 14
  • 1
    I don't like this solution much because of the large potential timeout for each iteration that fails to find the element. It is usually better to create a more precise locator that finds the new/updated element but not the old/stale one. – dokaspar Nov 07 '12 at 08:48
  • 2
    I came across this yesterday, since I'm having a similar problem. Wrapping the relevant code in a try/catch block and just retrying it worked for me. But I'm surprised that no-one has pointed out the flaw in the above code. It will only ever repeat once, because the `count = count+4` line will be executed even if the catch block is executed. I think the idea was probably to include that line in the try block, after `yourSlipperyElement.click();`. That way it will only be executed if the Exception is not thrown. – Martin McCallion Dec 12 '13 at 09:42
  • @MartinMcCallion I think that's the idea - if the click succeeded you don't need to retry. A break statement would have been clearer. – Kenny Aug 24 '14 at 09:42
  • 1
    I propose a solution that avoids having to write all this retry logic in this answer http://stackoverflow.com/a/25470347/34859 – Kenny Aug 24 '14 at 09:43
  • @Kenny My point was that the +4 will happen even if the Exception is caught, so the retry will never happen. if the +4 was after the `yourSlipperyElement.click();` line, then it would retry up to four times, which I think is the intent. – Martin McCallion Aug 27 '14 at 17:36
  • Instead of trying (just) 4 times why not wait a specific amount of time, using an explicit wait through `WebDriverWait.until`? – dialex Jun 18 '16 at 14:40
8

We get around this issue by doing something called WebdriverWrapper and WebElementWrapper.

What these wrappers do is handle the StaleElementException within and then use the locator to re-evaluate and get the new WebElement object. This way, you need to spread the code handling the exception all over your codebase and localize it to one class.

I will look into open sourcing just these couple of classes soon and add a link here if you folk are interested.

Ajinkya
  • 22,324
  • 33
  • 110
  • 161
Pavan
  • 1,245
  • 7
  • 10
4

That exception is thrown when you try to use a method of a WebElement that is not longer on the page. If your grid is dynamically loading data and you refresh the grid, any references to elements on that grid would be 'stale'. Double check that the element you're trying to reference is on the page in your tests, and you may need to re-instantiate the object.

pnewhook
  • 4,048
  • 2
  • 31
  • 49
  • The condition that I've had this problem with is something like: - Wait until there is a value in a table cell, under the heading 'someHeader', for table 'tableLocator', and if found return its row index. (I'm trying to see if an entity appears in the table by finding its ID, then I want to operate on that row). The twist is that I have to work with dynamic tables where the columns can be shown or hidden, so I cannot know the index of the column in advance. So first I have to find the index of the column and then get the s with the value. – Maalamaal Jan 31 '11 at 21:55
  • I get the error at this line _genderDropdown = new Select( _driver.findElement( By.xpath( String.format( SELECT_COLUMN_VALUE_LOCATOR, _rowNumber, GENDER_COLUMN ) ) ) ); where xpath of SELECT_COLUMN_VALUE_LOCATOR = public static final String SELECT_COLUMN_VALUE_LOCATOR = "//table/tbody/tr[%d]/td[%d]/div/select"; – Maalamaal Jan 31 '11 at 22:13
2

It also encountered this issue, It looks very obvious the modal panel loading falls into a race condition and keeps waiting till time out.

I have tried many times, and found the solution is to hold the modal panel loading till it can be exactly found by webDriver and at the same time keep refresh the webDriver instance , then try to find WebElements within the modal panel.

So the solution is like follows: e.g. MyModalPanel is your ModalPanel Id, then do the following

page.openModalPanel();
Assert.assertTrue(page.waitTillDisplay( "MyModalPanelContentDiv"), Wait.MODAL_PANEL));
page.fillInFormInModalpanel(formObj);

and the waitTillDisplay code can be found on WebDriver website, I will just paste my code here for your reference:

public Boolean waitTillDisplay(final String id, int waitSeconds){

    WebDriverWait wait = new WebDriverWait(driver, waitSeconds);
        Boolean displayed = wait.until(new ExpectedCondition<Boolean>() {
              public Boolean apply(WebDriver driver) {
                  return driver.findElement(By.id(id)).isDisplayed();
              }

        });
        return displayed;

}

divjscr
  • 51
  • 3
1

You might be trying to get any of element properties after clicking an element.

I had the same issue, I was trying to getText() of button after it was clicked. In my case, once button is clicked, new window comes.

ram_c
  • 87
  • 2
  • 11
1

I used a FluentWait and also the ExpectedCondition apply override: https://gist.github.com/djangofan/5112655 . This one handles the exception inside the finalized block, unlike how other people answer this, and allows the successive tries to be wrapped in that block. I think this is more elegant.

public static void clickByLocator( final By locator ) {
  final long startTime = System.currentTimeMillis();
  driver.manage().timeouts().implicitlyWait( 5, TimeUnit.SECONDS );
  Wait<WebDriver> wait = new FluentWait<WebDriver>( driver )
        .withTimeout(90000, TimeUnit.MILLISECONDS)
        .pollingEvery(5500, TimeUnit.MILLISECONDS);
        //.ignoring( StaleElementReferenceException.class );        
  wait.until( new ExpectedCondition<Boolean>() { 
    @Override 
    public Boolean apply( WebDriver webDriver ) {
      try {
        webDriver.findElement( locator ).click();
        return true;
      } catch ( StaleElementReferenceException e ) {                      // try again
        return false;
      }     
    } 
  } );      
  driver.manage().timeouts().implicitlyWait( DEFAULT_IMPLICIT_WAIT, TimeUnit.SECONDS );
  long endTime   = System.currentTimeMillis();
  long totalTime = endTime - startTime;
  log("Finished click after waiting for " + totalTime + " milliseconds.");
}
djangofan
  • 28,471
  • 61
  • 196
  • 289
  • 1
    Hi, I checked the page you mentioned here and your functions just return the element but there is always chance that clicking on the returned element might throw the same exception again...for example method4 and method5 on your page returns webelement and if the calling method clicks on this element and it throws StateElementReferenceException..am i missing something here?? – javanoob Sep 15 '14 at 19:44
  • The way that it is written above, i dont think it would be able to throw a StaleElementReferenceException because I swallow the error and return the boolean. – djangofan Sep 16 '14 at 02:02
  • 1
    I was referring to the method4 and method5 on the link you posted. In the above answer i can see that you are catching the exception but what about the methods which are returning the webelement? – javanoob Sep 16 '14 at 02:43
  • sorry. those were just thoughts/ideas . not necessarily tested. your milage may vary on that. you can draw inspiration from that link but really you will likely end up with your own solution. – djangofan Sep 17 '14 at 03:06
1
public static Boolean executeElementSendKeys
(WebDriver driver, WebElement element, String sInputParameters) throws Exception {
    return (Boolean) executeElementMaster
            (driver, element, "sendKeys", sInputParameters, 30, true);
}

public static Boolean executeElementClear
(WebDriver driver, WebElement element) throws Exception {
    return (Boolean) executeElementMaster (driver, element, "clear", "", 30, true);
}

public static String executeElementGetText
(WebDriver driver, WebElement element) throws Exception {
    return (String) executeElementMaster (driver, element, "getText", "", 30, true);
}

public static Boolean executeElementClick
(WebDriver driver, WebElement element) throws Exception {
    return (Boolean) executeElementMaster (driver, element, "click", "", 30, true);
}

public static boolean executeElementIsDisplayed
(WebDriver driver, WebElement element) throws Exception {
    return (Boolean) executeElementMaster (driver, element, "isDisplayed", "", 30, true);
}

public static String executeElementGetAttribute
(WebDriver driver, WebElement element, String sInputParameters) throws Exception {
    return (String) executeElementMaster
            (driver, element, "getAttribute", sInputParameters, 30, true);
}

// And below is the master method that handles the StaleElementReferenceException and other exceptions.

// In the catch section, replace (Exception e) with (StaleElementReferenceException e) if you want this method to retry actions (like click, sendkeys etc.) for only StaleElementReferenceException and not other exceptions.

private static Object executeElementMaster(WebDriver driver, WebElement element, String sExecuteAction, String sInputParametersOptional, int MaxTimeToWait,
        boolean bExpectedElementState) throws Exception {
    try {
        // Local variable declaration
        String sElementString = "";
        String sElementXpath = "";
        Object ReturnValue = "";
        int Index = 0;
        boolean bCurrentElementState = true;
        boolean bWebDriverWaitUntilElementClickableFlag = false;

        System.out.println("**** Execute method '" + sExecuteAction + "' on '" + sElementString + "' - Expected : '" + bExpectedElementState + "'");
        System.out.println("**** MaxTimeToWait ='" + MaxTimeToWait + "' seconds");

        // Set browser timeout to 1 second. Will be reset to default later
        driver.manage().timeouts().implicitlyWait(1, TimeUnit.SECONDS);

        // Keep trying until 'MaxTimeToWait' is reached 
        for (int i = 0; i < MaxTimeToWait; i++) {
            try {
                // Get element xPath - and find element again
                if (element != null && i < 2 && sElementString == "") {
                    sElementString = (element).toString();
                    if (sElementString.contains("xpath: ")) {
                        // Retrieve xPath from element, if available
                        Index = sElementString.indexOf("xpath: ");
                        sElementXpath = sElementString.substring(Index + 7, sElementString.length());
                    }
                }

                // Find Element again
                if (sElementXpath != "" && i > 0) {
                    element = driver.findElement(By.xpath(sElementXpath));
                }

                // Execute the action requested
                switch (sExecuteAction) {
                    case ("isDisplayed"):
                        // Check if element is displayed and save in bCurrentElementState variable
                        ReturnValue = element.isDisplayed();
                        bCurrentElementState = (Boolean) ReturnValue;
                        bWebDriverWaitUntilElementClickableFlag = true;
                        break;
                    case ("getText"):
                        ReturnValue = element.getText();
                        bCurrentElementState = true;
                        bWebDriverWaitUntilElementClickableFlag = false;
                        break;
                    case ("sendKeys"):
                        // Scroll element into view before performing any action

                        element.sendKeys(sInputParametersOptional);
                        ReturnValue = true;
                        bCurrentElementState = true;
                        bWebDriverWaitUntilElementClickableFlag = false;
                        break;
                    case ("clear"):
                        // Scroll element into view before performing any action

                        element.clear();
                        ReturnValue = true;
                        bCurrentElementState = true;
                        bWebDriverWaitUntilElementClickableFlag = false;
                        break;
                    case ("click"):
                        // Scroll element into view before performing any action

                        element.click();
                        ReturnValue = true;
                        bCurrentElementState = true;
                        bWebDriverWaitUntilElementClickableFlag = false;
                        break;
                    default:
                        ReturnValue = element.getAttribute(sInputParametersOptional);
                        bCurrentElementState = true;
                        break;
                }
            } catch (Exception e) {
                Thread.sleep(500);
                bCurrentElementState = false;
                ReturnValue = false;
            }
            if (bCurrentElementState == bExpectedElementState) {
                // If element's actual and expected states match, log result and return value
                System.out.println("**** PASSED: Execute method '" + sExecuteAction + "' on '" + sElementString + "' - Returned '" + ReturnValue + "' ****   \n"
                        + "Actual element status: '" + bCurrentElementState + "'  Expected element status: '" + bExpectedElementState + "'");
                break;
            } else {
                // If element's actual and expected states do NOT match, loop until they match or timeout is reached
                Thread.sleep(500);
            }
        }
        // Reset browser timeout to default
        driver.manage().timeouts().implicitlyWait(3, TimeUnit.SECONDS);
        // Return value before exiting
        if (bCurrentElementState != bExpectedElementState) {
            // If element's actual and expected states do NOT match, log result and return value
            System.out.println("**** FAILED: Execute method '" + sExecuteAction + "' on '" + sElementString + "' - Returned '" + ReturnValue + "' ****   \n"
                    + "Actual element status: '" + bCurrentElementState + "'  Expected element status: '" + bExpectedElementState + "'");
            if (sExecuteAction.equalsIgnoreCase("findElement")) {
                ReturnValue = null;
            }
        }

        return ReturnValue;
    } catch (Exception e) {
        System.out.println("Exception in executeElementMaster - " + e.getMessage());
        throw (e);
    }
}
Nash N
  • 342
  • 2
  • 17
0

I made some changes to be more flexible:

   delegate void StaleFunction(IWebElement elt);
        private static void StaleElementHandleByID(By by, StaleFunction func )
        {
            int count = 0;
            while (count < 4)
            {
                try
                {
                    IWebElement yourSlipperyElement = Driver.FindElement(by);
                    func(yourSlipperyElement);
                    count = count + 4;
                }
                catch (StaleElementReferenceException e)
                {
                    count = count + 1;
                }

            }
        }

 StaleElementHandleByID(By.Id("identDdl"),
                    delegate(IWebElement elt)
                {
                    SelectElement select = new SelectElement(elt);
                    select.SelectByText(tosave.ItemEquipamentoCem.CodigoCne.ToString());
                });
WoF_Angel
  • 2,569
  • 9
  • 35
  • 54
0

a quick & dirty solution:

el.click()

time.sleep(1)

then continue to parse in iteration way

netz75
  • 57
  • 1
  • 6
  • Using static sleeps should be avoided as much as possible , as they make the tests brittle – sri85 Feb 26 '16 at 14:58
  • How is this a "solution" ? `el` is the `WebElement` that might have become stale after retrieving it, so you `click()` it, that throws `StaleElementReferenceException` (so your `time.sleep(1)`, whatever it is, is not even executed), and then ? – SantiBailors Feb 06 '18 at 12:49
0

In this case, the tests are looking for an element that is not yet loaded, or has been refreshed. As a result, the StaleElementException. A simple solution would be to add a fluentWait.

Kermit_ice_tea
  • 508
  • 2
  • 8
  • 16
0

@netz75 : Thank you. Had this problem when a click redirects to second page. This worked for me:

 //.. (first page asserts)
 //...
 element.Click();   
 Thread.Sleep(200);
 //.. (second page asserts)
 //...
Watth
  • 406
  • 5
  • 12