Selenium WebDriver ¿Cómo resolver la excepción de referencia de elemento obsoleto?

Tengo el siguiente código en una prueba de Selenium 2 Web Driver que funciona cuando estoy depurando pero la mayoría de las veces falla cuando lo ejecuto en la comstackción. Sé que debe ser algo relacionado con la forma en que la página no está siendo renovada, pero no sé cómo resolverla, por lo que se agradece cualquier sugerencia sobre lo que hice mal. Estoy utilizando Primefas de JSF como mi marco de aplicaciones web. Cuando hago clic en el enlace agregar nuevo, aparece un cuadro de diálogo emergente con un cuadro de entrada en el que puedo ingresar una fecha y luego hago clic en guardar. Al obtener el elemento de entrada para ingresar texto, obtengo una excepción de elemento obsoleto.

Gracias por adelantado

import static org.junit.Assert.assertEquals; import java.util.HashMap; import java.util.List; import java.util.Map; import org.junit.Test; import org.openqa.selenium.By; import org.openqa.selenium.StaleElementReferenceException; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.chrome.ChromeDriver; import org.openqa.selenium.support.ui.ExpectedCondition; import org.openqa.selenium.support.ui.WebDriverWait; public class EnterActiveSubmissionIntegrationTest { Map<String, Map> tableData = new HashMap<String, Map>(); @Test public void testEnterActiveSubmission() throws Exception { // Create a new instance of the Firefox driver // Notice that the remainder of the code relies on the interface, // not the implementation. System.setProperty("webdriver.chrome.driver", "C:/apps/chromedriver.exe"); WebDriver driver = new ChromeDriver(); // And now use this to visit Google driver.get("http://localhost:8080/strfingerprinting"); // Alternatively the same thing can be done like this // driver.navigate().to("http://www.google.com"); // Find the text input element by its name WebElement element = driver.findElement(By.linkText("Manage Submissions")); element.click(); parseTableData(driver, "form:submissionDataTable_data", 1); assertEquals(tableData.get("form:submissionDataTable_data").get("12"), "Archived"); WebElement newElement = driver.findElement(By.linkText("Add new")); newElement.click(); WebDriverWait wait = new WebDriverWait(driver,10); wait.until(new ExpectedCondition() { public Boolean apply(WebDriver driver) { WebElement button = driver.findElement(By .name("createForm:dateInput_input")); if (button.isDisplayed()) return true; else return false; } }); WebElement textElement = driver.findElement(By.name("createForm:dateInput_input")); textElement.sendKeys("24/04/2013"); WebElement saveElement = driver.findElement(By.name("createForm:saveButton")); saveElement.click(); driver.navigate().refresh(); parseTableData(driver, "form:submissionDataTable_data", 2); //Close the browser driver.quit(); } private void parseTableData(WebDriver driver, String id, int expectedRows) { // Check the title of the page or expected element on page WebElement subTableElement = driver.findElement(By.id(id)); List tr_collection=subTableElement.findElements(By.xpath("id('"+ id + "')/tr")); assertEquals("incorrect number of rows returned", expectedRows, tr_collection.size()); int row_num,col_num; row_num=1; if(tableData.get(id) == null) { tableData.put(id, new HashMap()); } Map subTable = tableData.get(id); for(WebElement trElement : tr_collection) { List td_collection=trElement.findElements(By.xpath("td")); col_num=1; for(WebElement tdElement : td_collection) { subTable.put(row_num + "" + col_num, tdElement.getText()); col_num++; } row_num++; } } } 

Cuando ejecuto esto obtengo la siguiente excepción, pero puede ocurrir en

 WebElement textElement = driver.findElement(By.name("createForm:dateInput_input")); 

o

 if (button.isDisplayed()) 

rastro de excepción

 org.openqa.selenium.StaleElementReferenceException: stale element reference: element is not attached to the page document (Session info: chrome=26.0.1410.64) (Driver info: chromedriver=0.8,platform=Windows NT 6.0 SP2 x86) (WARNING: The server did not provide any stacktrace information) Command duration or timeout: 56 milliseconds For documentation on this error, please visit: http://seleniumhq.org/exceptions/stale_element_reference.html Build info: version: '2.32.0', revision: '6c40c187d01409a5dc3b7f8251859150c8af0bcb', time: '2013-04-09 10:39:28' System info: os.name: 'Windows Vista', os.arch: 'x86', os.version: '6.0', java.version: '1.6.0_10' Session ID: 784c53b99ad83c44d089fd04e9a42904 Driver info: org.openqa.selenium.chrome.ChromeDriver Capabilities [{platform=XP, acceptSslCerts=true, javascriptEnabled=true, browserName=chrome, rotatable=false, driverVersion=0.8, locationContextEnabled=true, version=26.0.1410.64, cssSelectorsEnabled=true, databaseEnabled=true, handlesAlerts=true, browserConnectionEnabled=false, nativeEvents=true, webStorageEnabled=true, applicationCacheEnabled=false, takesScreenshot=true}] at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39) at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27) at java.lang.reflect.Constructor.newInstance(Constructor.java:513) at org.openqa.selenium.remote.ErrorHandler.createThrowable(ErrorHandler.java:187) at org.openqa.selenium.remote.ErrorHandler.throwIfResponseFailed(ErrorHandler.java:145) at org.openqa.selenium.remote.RemoteWebDriver.execute(RemoteWebDriver.java:554) at org.openqa.selenium.remote.RemoteWebElement.execute(RemoteWebElement.java:268) at org.openqa.selenium.remote.RemoteWebElement.isDisplayed(RemoteWebElement.java:320) at com.integration.web.EnterActiveSubmissionIntegrationTest$1.apply(EnterActiveSubmissionIntegrationTest.java:58) at com.integration.web.EnterActiveSubmissionIntegrationTest$1.apply(EnterActiveSubmissionIntegrationTest.java:1) at org.openqa.selenium.support.ui.FluentWait.until(FluentWait.java:208) at com.integration.web.EnterActiveSubmissionIntegrationTest.testEnterActiveSubmission(EnterActiveSubmissionIntegrationTest.java:53) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 

Antes que nada, seamos claros sobre lo que es un WebElement.

Un WebElement es una referencia a un elemento en el DOM.

Una StaleElementException se lanza cuando el elemento que estaba interactuando se destruye y luego se vuelve a crear. La mayoría de las páginas web complejas en estos días moverán las cosas sobre la marcha a medida que el usuario interactúa con ella y esto requiere que los elementos en el DOM sean destruidos y recreados.

Cuando esto sucede, la referencia al elemento en el DOM que tenía previamente se vuelve obsoleta y ya no puede usar esta referencia para interactuar con el elemento en el DOM. Cuando esto suceda, deberá actualizar su referencia o, en términos del mundo real, encontrar el elemento nuevamente.

Esto no es un problema. Si ajusta su llamada .findElement en un bloque try-catch y atrapa la excepción StaleElementReferenceException, puede repetir y repetir tantas veces como necesite hasta que tenga éxito.

Aquí hay algunos ejemplos que escribí .

Otro ejemplo del proyecto Selenide :

 public static final Condition hidden = new Condition("hidden", true) { @Override public boolean apply(WebElement element) { try { return !element.isDisplayed(); } catch (StaleElementReferenceException elementHasDisappeared) { return true; } } }; 

Lo que me estaba sucediendo era que el controlador web encontraría una referencia a un elemento DOM y luego, en algún momento después de obtener esa referencia, javascript eliminaría ese elemento y lo volvería a agregar (porque la página estaba haciendo un redibujado, básicamente).

Prueba esto. Calcule la acción que hace que el elemento dom se elimine del DOM. En mi caso, se trataba de una llamada async ajax, y el elemento se estaba eliminando del DOM cuando se completó la llamada ajax. Justo después de esa acción, espere a que el elemento quede obsoleto:

 ... do a thing, possibly async, that should remove the element from the DOM ... wait.until(ExpectedConditions.stalenessOf(theElement)); 

En este punto, está seguro de que el elemento ahora está desactualizado. Por lo tanto, la próxima vez que haga referencia al elemento, espere de nuevo, esta vez esperando a que se vuelva a agregar al DOM:

 wait.until(ExpectedConditions.presenceOfElementLocated(By.id("whatever"))) 

Intenta esperar por un elemento como este:

 // Waiting 30 seconds for an element to be present on the page, checking // for its presence once every 5 seconds. Wait stubbornWait = new FluentWait(driver) .withTimeout(30, SECONDS) .pollingEvery(5, SECONDS) .ignoring(NoSuchElementException.class) .ignoring(StaleElementReferenceException.class); WebElement foo = stubbornWait.until(new Function() { public WebElement apply(WebDriver driver) { return driver.findElement(By.id("foo")); } }); 

Dos razones para el elemento viciado

  1. Un elemento que se encuentra en una página web a la que se hace referencia como WebElement en WebDriver, cambia el DOM (probablemente debido a las funciones de JavaScript) que WebElement queda obsoleto.

  2. El elemento ha sido eliminado por completo.

Cuando intenta interactuar con WebElement ranurado [cualquiera de los casos anteriores], se lanza StaleElementException.

¿Cómo evitar / resolver Stale Exception?

  1. Almacenamiento de localizadores en sus elementos en lugar de referencias
 driver = webdriver.Firefox(); driver.get("http://www.github.com"); search_input = lambda: driver.find_element_by_name('q'); search_input().send_keys('hello world\n'); time.sleep(5); search_input().send_keys('hello frank\n') // no stale element exception 
  1. Ganchos de apalancamiento en las bibliotecas JS utilizadas
  # Using Jquery queue to get animation queue length. animationQueueIs = """ return $.queue( $("#%s")[0], "fx").length; """ % element_id wait_until(lambda: self.driver.execute_script(animationQueueIs)==0) 
  1. Transfiere tus acciones a la inyección de JavaScript
  self.driver.execute_script("$(\"li:contains('Narendra')\").click()"); 
  1. Espere de manera proactiva para que el elemento quede obsoleto
  # Wait till the element goes stale, this means the list has updated wait_until(lambda: is_element_stale(old_link_reference)) 

Esta solución, que funcionó para mí, la he mencionado aquí si tiene alguna situación adicional, que funcionó para usted y luego comenta a continuación

StaleElementReferenceException se debe a la falta de disponibilidad de un elemento al que se accede mediante el método findelement.

Debe asegurarse antes de realizar cualquier operación en un elemento (si tiene dudas sobre la disponibilidad de ese elemento)

Esperando la visibilidad de un elemento

 (new WebDriverWait(driver, 10)).until(new ExpectedCondition() { public Boolean apply(WebDriver d) { return d.findElement(By.name("createForm:dateInput_input")).isDisplayed(); }}); 

O bien, use esta lógica para verificar si el elemento está presente o no.

Use las Condiciones esperadas provistas por Selenium para esperar al WebElement.

Mientras depura, el cliente no es tan rápido como si solo ejecutara una prueba de unidad o una comstackción maven. Esto significa que en el modo de depuración el cliente tiene más tiempo para preparar el elemento, pero si la comstackción ejecuta el mismo código, es mucho más rápido y es posible que el WebElement que busca no esté visible en el DOM de la página.

Créeme con esto, tuve el mismo problema.

por ejemplo:

 inClient.waitUntil(ExpectedConditions.visibilityOf(YourElement,2000)) 

Este sencillo método llama a esperar después de su llamada durante 2 segundos sobre la visibilidad de su WebElement en DOM.

He resuelto este problema con el siguiente código.

 public WebElement waitForElement(final By findBy, final int waitTime) { Wait wait = new FluentWait<>((AppiumDriver) driver) .withTimeout(waitTime, TimeUnit.SECONDS) .pollingEvery(POLL_TIME, TimeUnit.SECONDS) .ignoring(NoSuchElementException.class,StaleElementReferenceException.class); WebElement webElement = wait.until(new Function() { @Override public WebElement apply(AppiumDriver driver) { System.out.println("Trying to find element " + findBy.toString()); WebElement element = driver.findElement(findBy); return element; } }); return webElement; } 

Sugeriría que no use @CachelookUp para Selenium WebDriver para StaleElementReferenceException .

Si está utilizando la anotación @FindBy y tiene @CacheLookUp , simplemente @CacheLookUp y verifique.

Esto funcionó para mí (fuente aquí ):

  /** * Attempts to click on an element multiple times (to avoid stale element * exceptions caused by rapid DOM refreshes) * * @param d * The WebDriver * @param by * By element locator */ public static void dependableClick(WebDriver d, By by) { final int MAXIMUM_WAIT_TIME = 10; final int MAX_STALE_ELEMENT_RETRIES = 5; WebDriverWait wait = new WebDriverWait(d, MAXIMUM_WAIT_TIME); int retries = 0; while (true) { try { wait.until(ExpectedConditions.elementToBeClickable(by)).click(); return; } catch (StaleElementReferenceException e) { if (retries < MAX_STALE_ELEMENT_RETRIES) { retries++; continue; } else { throw e; } } } } 

El WebDriver tiene que esperar hasta que se encuentre el elemento y un tiempo de espera sea después de 10 segundos.

 WebElement myDynamicElement1 = new WebDriverWait(driver, 10).until( ExpectedConditions.presenceOfElementLocated( By.name("createForm:dateInput_input") ) ); 

Por favor, no confundas a los demás entre nosotros, si no estamos seguros de las respuestas. Es bastante frustrante para el usuario final. La respuesta simple y la breve es usar la anotación @CacheLookup en webdriver. Por favor, consulte el enlace a continuación para ello. ¿Cómo funciona @CacheLookup en WebDriver?

Utilice webdriverwait con ExpectedCondition en try catch block con for loop EX: para python

 for i in range(4): try: element = WebDriverWait(driver, 120).until( \ EC.presence_of_element_located((By.XPATH, 'xpath'))) element.click() break except StaleElementReferenceException: print "exception " 

Con referencia a la respuesta dada por @djangofan, parece que la solución viable es mantener el código dentro de un bloque de try catch donde se produce un posible estancamiento. Cuando uso este código a continuación, no entendí el problema en ningún momento.

 public void inputName(String name) { try { waitForVisibilityElement(name);//My own visibility function findElement(By.name("customerName")).sendKeys(name); } catch (StaleElementReferenceException e) { e.getMessage(); } } 

He intentado usar el ExpectedConditions.presenceOfElementLocated(By) pero las excepciones de ranciedad todavía se lanzan intermitentemente.

Espero que esta solución ayude.

Esta solución funcionó bien para mí:

Agregar la función de manejo de errores e intentar de nuevo

 var pollLoop = function () { element(by.id('spinnerElem')).getAttribute('class').then(function (cls) { if (cls.indexOf('spinner-active') > -1) { // wait for the spinner } else { //do your logic promise.defer().fulfill(); } }, function () { // This err handling function is to handle the {StaleElementReferenceError} and makes sure we find the element always. pollLoop(); }); }; 

Después de una profunda investigación del problema, descubrí que se produce un error al seleccionar los elementos DIV que se agregaron solo para Bootstrap. El navegador Chrome elimina dicho DIVS y se produce el error. Es suficiente renunciar y seleccionar un elemento real para corregir un error. Por ejemplo, mi diálogo modal tiene estructura:

  

Seleccionar div class = “modal-body” genera un error, seleccionando la forma … funciona como lo haría.

En mi caso, este error fue causado por el hecho de que estaba definiendo el elemento ActionChains fuera del

 def parse(self, response): 

método cuando se usa una combinación de Selenio y Scrapy, por ejemplo:

No funciona:

 class MySpider(scrapy.Spider): action_chains = ActionChains(self.driver) 

Moviendo action_chains = ActionChains(self.driver) dentro del def parse(self, response): resolvió el problema, por ejemplo:

Trabajos:

 def parse(self, response): self.driver.get(response.url) action_chains = ActionChains(self.driver) 

Solo descarga la nueva extensión de Chrome y utiliza el servidor de selenium 3, funcionará bien.

La mejor manera que he encontrado para evitar referencias obsoletas es no usar PageFactory, sino almacenar los localizadores (es decir, por elementos).

 public class WebDriverFactory { // if you want to multithread tests, use a ThreadLocal // instead. // This also makes it so you don't have to pass around WebDriver objects // when instantiating new Page classes private static WebDriver driver = null; public static WebDriver getDriver() { return driver; } public static void setDriver(WebDriver browser) { driver = browser; } } // class to let me avoid typing out the lengthy driver.findElement(s) so // much public Abstract class PageBase { private WebDriver driver = WebDriverFactory.getDriver(); // using var args to let you easily chain locators protected By getBy(By... locator) { return new ByChained(locator); } protected WebElement find(By... locators) { return driver.findElement(getBy(locators)); } protected List findEm(By... locators) { return driver.findElements(getBy(locators)); } protected Select select(By... locators) { return new Select(getBy(locators)); } } public class somePage extends PageBase { private static WebDriver driver = WebDriverFactory.getDriver(); private static final By buttonBy = By.cssSelector(".btn-primary"); public void clickButton() { WebDriverWait wait = new WebDriverWait(driver, 10); wait.until(ExpectedConditions.elementToBeClickable(buttonBy)); find(buttonBy).click(); } } 

Tengo una clase llena de métodos estáticos de WebDriverWait que uso. Y no recuerdo si el uso anterior de WebDriver espera manejará la excepción StaleElement o no. De lo contrario, podría usar una espera fluida como en la respuesta de DjangoFan. Pero el principio que estoy mostrando funcionará (incluso si esa línea específica con WebDriverWait explota).

Entonces el tldr;

  1. Use localizadores y una combinación de WebDriverWait / Fluent wait / relocating the yourself, de modo que si su elemento se queda obsoleto, puede reubicarlo sin tener que duplicar el localizador en @FindBy (para un elemento inicializado de pagefactory), ya que no hay WebElement .relocate () método.
  2. Para simplificar la vida, tenga una clase Abstract BasePage con métodos de conveniencia para ubicar un elemento / lista de elementos.

Intenté muchas de las sugerencias anteriores, pero la más simple funcionó. En mi caso, fue el uso de @CachelookUp para el elemento web la excepción del elemento obsoleto. Supongo que después de actualizar la página, la referencia del elemento no se volvió a cargar y no se pudo encontrar el elemento. Desactivar la línea @CachelookUp para el elemento trabajado.

  //Search button @FindBy(how=How.XPATH, using =".//input[@value='Search']") //@CachelookUp WebElement BtnSearch; 

Cuando se produce una excepción de elemento obsoleto

La excepción de elemento obsoleto puede ocurrir cuando las bibliotecas que admiten esos cuadros de texto / botones / enlaces han cambiado, lo que significa que los elementos son iguales, pero la referencia ahora ha cambiado en el sitio web sin afectar a los localizadores. Por lo tanto, la referencia que almacenamos en nuestro caché, incluida la referencia de la biblioteca, ahora se ha vuelto obsoleta porque la página se actualizó con las bibliotecas actualizadas.

 for(int j=0; j<5;j++) try { WebElement elementName=driver.findElement(By.name(“createForm:dateInput_input”)); break; } catch(StaleElementReferenceException e){ e.toString(); System.out.println(“Stale element error, trying :: ” + e.getMessage()); } elementName.sendKeys(“20/06/2018”);