Cómo evitar “StaleElementReferenceException” en Selenium?

Estoy implementando muchas pruebas de Selenium usando Java. A veces, mis pruebas fallan debido a una StaleElementReferenceException . ¿Podría sugerir algunos enfoques para hacer las pruebas más estables?

Esto puede suceder si una operación DOM que ocurre en la página está causando temporalmente que el elemento sea inaccesible. Para permitir esos casos, puede intentar acceder al elemento varias veces en un bucle antes de lanzar finalmente una excepción.

Pruebe esta excelente solución de darrelgrainger.blogspot.com :

 public boolean retryingFindClick(By by) { boolean result = false; int attempts = 0; while(attempts < 2) { try { driver.findElement(by).click(); result = true; break; } catch(StaleElementException e) { } attempts++; } return result; } 

Estaba teniendo este problema intermitentemente. Desconocido para mí, BackboneJS se estaba ejecutando en la página y reemplazando el elemento que estaba tratando de hacer clic. Mi código se veía así.

 driver.findElement(By.id("checkoutLink")).click(); 

Lo cual es, por supuesto, funcionalmente el mismo que este.

 WebElement checkoutLink = driver.findElement(By.id("checkoutLink")); checkoutLink.click(); 

Lo que sucedería ocasionalmente sería que el javascript reemplazaría al elemento checkoutLink entre encontrarlo y hacer clic en él, es decir.

 WebElement checkoutLink = driver.findElement(By.id("checkoutLink")); // javascript replaces checkoutLink checkoutLink.click(); 

Que legítimamente llevó a una StaleElementReferenceException al tratar de hacer clic en el enlace. No pude encontrar ninguna manera confiable de decirle a WebDriver que espere hasta que el javascript haya terminado de ejecutarse, así es como finalmente lo resolví.

 new WebDriverWait(driver, timeout) .ignoring(StaleElementReferenceException.class) .until(new Predicate() { @Override public boolean apply(@Nullable WebDriver driver) { driver.findElement(By.id("checkoutLink")).click(); return true; } }); 

Este código intentará continuamente hacer clic en el enlace, ignorando StaleElementReferenceExceptions hasta que el clic tenga éxito o se scope el tiempo de espera. Me gusta esta solución porque te ahorra tener que escribir cualquier lógica de rebash y utiliza solo las construcciones integradas de WebDriver.

En general, esto se debe a que el DOM está siendo actualizado e intenta acceder a un elemento actualizado / nuevo, pero el DOM se actualizó, por lo que es una referencia no válida que tiene.

Para evitar esto, primero use una espera explícita en el elemento para asegurarse de que la actualización esté completa y luego vuelva a tomar una nueva referencia al elemento.

Aquí hay un código de psuedo para ilustrar (Adaptado de algún código de C # que uso EXACTAMENTE para este problema):

 WebDriverWait wait = new WebDriverWait(browser, TimeSpan.FromSeconds(10)); IWebElement aRow = browser.FindElement(By.XPath(SOME XPATH HERE); IWebElement editLink = aRow.FindElement(By.LinkText("Edit")); //this Click causes an AJAX call editLink.Click(); //must first wait for the call to complete wait.Until(ExpectedConditions.ElementExists(By.XPath(SOME XPATH HERE)); //you've lost the reference to the row; you must grab it again. aRow = browser.FindElement(By.XPath(SOME XPATH HERE); //now proceed with asserts or other actions. 

¡Espero que esto ayude!

La solución de Kenny es buena, sin embargo, se puede escribir de una manera más elegante

 new WebDriverWait(driver, timeout) .ignoring(StaleElementReferenceException.class) .until((WebDriver d) -> { d.findElement(By.id("checkoutLink")).click(); return true; }); 

O también:

 new WebDriverWait(driver, timeout).ignoring(StaleElementReferenceException.class).until(ExpectedConditions.elementToBeClickable(By.id("checkoutLink"))); driver.findElement(By.id("checkoutLink")).click(); 

La razón por la StaleElementReferenceException ocurre StaleElementReferenceException se ha establecido: actualizaciones en el DOM entre encontrar y hacer algo con el elemento.

Para el clic, Problema, recientemente utilicé una solución como esta:

 public void clickOn(By locator, WebDriver driver, int timeout) { final WebDriverWait wait = new WebDriverWait(driver, timeout); wait.until(ExpectedConditions.refreshed( ExpectedConditions.elementToBeClickable(locator))); driver.findElement(locator).click(); } 

La parte crucial es la “encadenación” de las propias condiciones ExpectedConditions de Selenium a través de las condicionesesperadas.refreshed ExpectedConditions.refreshed() . De hecho, esto espera y comprueba si el elemento en cuestión se ha actualizado durante el tiempo de espera especificado y, además, espera a que se pueda hacer clic en el elemento.

Eche un vistazo a la documentación del método actualizado .

Una solución en C # sería:

Clase de ayuda:

 internal class DriverHelper { private IWebDriver Driver { get; set; } private WebDriverWait Wait { get; set; } public DriverHelper(string driverUrl, int timeoutInSeconds) { Driver = new ChromeDriver(); Driver.Url = driverUrl; Wait = new WebDriverWait(Driver, TimeSpan.FromSeconds(timeoutInSeconds)); } internal bool ClickElement(string cssSelector) { //Find the element IWebElement element = Wait.Until(d=>ExpectedConditions.ElementIsVisible(By.CssSelector(cssSelector)))(Driver); return Wait.Until(c => ClickElement(element, cssSelector)); } private bool ClickElement(IWebElement element, string cssSelector) { try { //Check if element is still included in the dom //If the element has changed a the OpenQA.Selenium.StaleElementReferenceException is thrown. bool isDisplayed = element.Displayed; element.Click(); return true; } catch (StaleElementReferenceException) { //wait until the element is visible again element = Wait.Until(d => ExpectedConditions.ElementIsVisible(By.CssSelector(cssSelector)))(Driver); return ClickElement(element, cssSelector); } catch (Exception) { return false; } } } 

Invocación:

  DriverHelper driverHelper = new DriverHelper("http://www.seleniumhq.org/docs/04_webdriver_advanced.jsp", 10); driverHelper.ClickElement("input[value='csharp']:first-child"); 

Del mismo modo se puede utilizar para Java.

En mi proyecto, presenté una noción de StableWebElement. Es un contenedor para WebElement que puede detectar si el elemento está a tiempo y encontrar una nueva referencia al elemento original. He agregado un método de ayuda para localizar elementos que devuelven StableWebElement en lugar de WebElement y el problema con StaleElementReference desapareció.

 public static IStableWebElement FindStableElement(this ISearchContext context, By by) { var element = context.FindElement(by); return new StableWebElement(context, element, by, SearchApproachType.First); } 

El código en C # está disponible en la página de mi proyecto, pero podría ser fácilmente portado a java https://github.com/cezarypiatek/Tellurium/blob/master/Src/MvcPages/SeleniumUtils/StableWebElement.cs

Esto funciona para mí (100% de trabajo) usando C #

 public Boolean RetryingFindClick(IWebElement webElement) { Boolean result = false; int attempts = 0; while (attempts < 2) { try { webElement.Click(); result = true; break; } catch (StaleElementReferenceException e) { Logging.Text(e.Message); } attempts++; } return result; } 

Tal vez fue agregado más recientemente, pero otras respuestas no mencionan la función de espera implícita de Selenium, que hace todo lo anterior por ti, y está integrada en Selenium.

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

Esto reintentará las llamadas a findElement() hasta que se encuentre el elemento, o por 10 segundos.

Fuente – http://www.seleniumhq.org/docs/04_webdriver_advanced.jsp