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.
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.