Random "Element is no longer attached to the DOM" StaleElementReferenceException

Ray Nicholus picture Ray Nicholus · Apr 18, 2011 · Viewed 116.7k times · Source

I'm hoping it's just me, but Selenium Webdriver seems like a complete nightmare. The Chrome webdriver is currently unusable, and the other drivers are quite unreliable, or so it seems. I am battling many problems, but here is one.

Randomly, my tests will fail with a

"org.openqa.selenium.StaleElementReferenceException: Element is no longer attached 
to the DOM    
System info: os.name: 'Windows 7', os.arch: 'amd64',
 os.version: '6.1', java.version: '1.6.0_23'"

I'm using webdriver versions 2.0b3. I have seen this happen with FF and IE drivers. The only way I can prevent this is to add an actual call to Thread.sleep before the exception occurs. That is a poor workaround though, so I'm hoping someone can point out an error on my part that will make this all better.

Answer

jarib picture jarib · Apr 19, 2011

Yes, if you're having problems with StaleElementReferenceExceptions it's because your tests are poorly written. It's a race condition. Consider the following scenario:

WebElement element = driver.findElement(By.id("foo"));
// DOM changes - page is refreshed, or element is removed and re-added
element.click();

Now at the point where you're clicking the element, the element reference is no longer valid. It's close to impossible for WebDriver to make a good guess about all the cases where this might happen - so it throws up its hands and gives control to you, who as the test/app author should know exactly what may or may not happen. What you want to do is explicitly wait until the DOM is in a state where you know things won't change. For example, using a WebDriverWait to wait for a specific element to exist:

// times out after 5 seconds
WebDriverWait wait = new WebDriverWait(driver, 5);

// while the following loop runs, the DOM changes - 
// page is refreshed, or element is removed and re-added
wait.until(presenceOfElementLocated(By.id("container-element")));        

// now we're good - let's click the element
driver.findElement(By.id("foo")).click();

The presenceOfElementLocated() method would look something like this:

private static Function<WebDriver,WebElement> presenceOfElementLocated(final By locator) {
    return new Function<WebDriver, WebElement>() {
        @Override
        public WebElement apply(WebDriver driver) {
            return driver.findElement(locator);
        }
    };
}

You're quite right about the current Chrome driver being quite unstable, and you'll be happy to hear that the Selenium trunk has a rewritten Chrome driver, where most of the implementation was done by the Chromium developers as part of their tree.

PS. Alternatively, instead of waiting explicitly like in the example above, you can enable implicit waits - this way WebDriver will always loop up until the specified timeout waiting for the element to become present:

driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS)

In my experience though, explicitly waiting is always more reliable.