tutorial test example español java selenium-webdriver webdriver automated-tests

java - example - test case selenium



"Elemento aleatorio ya no está adjunto al DOM" StaleElementReferenceException (9)

A veces recibo este error cuando las actualizaciones de AJAX están a mitad de camino. El capibara parece ser bastante listo para esperar los cambios DOM (ver por qué wait_until fue eliminado de Capybara ), pero el tiempo de espera predeterminado de 2 segundos simplemente no fue suficiente en mi caso. Modificado en _spec_helper.rb_ con, por ejemplo,

Capybara.default_wait_time = 5

Espero que sea solo yo, pero Selenium Webdriver parece una pesadilla completa. El controlador de Chrome actualmente no se puede usar, y los otros controladores son poco confiables, o eso parece. Estoy batallando con muchos problemas, pero aquí hay uno.

Aleatoriamente, mis pruebas fallarán con un

"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''"

Estoy usando las versiones de webdriver 2.0b3. He visto esto suceder con los controladores FF e IE. La única forma en que puedo evitar esto es agregar una llamada real a Thread.sleep antes de que Thread.sleep la excepción. Sin embargo, no es una buena solución, así que espero que alguien pueda señalar un error de mi parte que mejorará todo esto.


En Java 8 puedes usar un método muy simple para eso:

private Object retryUntilAttached(Supplier<Object> callable) { try { return callable.get(); } catch (StaleElementReferenceException e) { log.warn("/tTrying once again"); return retryUntilAttached(callable); } }


Estaba enfrentando el mismo problema hoy e hice una clase contenedora, que verifica antes de cada método si la referencia del elemento sigue siendo válida. Mi solución para recuperar el elemento es bastante simple, así que pensé que simplemente lo compartiría.

private void setElementLocator() { this.locatorVariable = "selenium_" + DateTimeMethods.GetTime().ToString(); ((IJavaScriptExecutor)this.driver).ExecuteScript(locatorVariable + " = arguments[0];", this.element); } private void RetrieveElement() { this.element = (IWebElement)((IJavaScriptExecutor)this.driver).ExecuteScript("return " + locatorVariable); }

Verás que "ubico" o mejor guardo el elemento en una variable js global y recupero el elemento si es necesario. Si la página se vuelve a cargar, esta referencia ya no funcionará. Pero mientras solo se hagan cambios a la condena, la referencia permanece. Y eso debería hacer el trabajo en la mayoría de los casos.

También evita re-buscar el elemento.

John


Me acabo de pasar al intentar enviar_keys a un cuadro de entrada de búsqueda, que se ha autoactualizado según lo que escriba. Como lo mencionó Eero, esto puede suceder si su elemento actualiza algunos Ajax mientras está escribiendo su texto dentro del elemento de entrada . La solución es enviar un personaje a la vez y buscar nuevamente el elemento de entrada . (Ej. En ruby ​​se muestra a continuación)

def send_keys_eachchar(webdriver, elem_locator, text_to_send) text_to_send.each_char do |char| input_elem = webdriver.find_element(elem_locator) input_elem.send_keys(char) end end


Para agregar a la respuesta de @ jarib, hice varios métodos de extensión que ayudan a eliminar la condición de carrera.

He aquí mi arreglo:

Tengo una clase llamada "Driver.cs". Contiene una clase estática llena de métodos de extensión para el controlador y otras funciones estáticas útiles.

Para elementos que comúnmente necesito recuperar, creo un método de extensión como el siguiente:

public static IWebElement SpecificElementToGet(this IWebDriver driver) { return driver.FindElement(By.SomeSelector("SelectorText")); }

Esto le permite recuperar ese elemento de cualquier clase de prueba con el código:

driver.SpecificElementToGet();

Ahora, si esto da como resultado una StaleElementReferenceException , tengo el siguiente método estático en mi clase de controlador:

public static void WaitForDisplayed(Func<IWebElement> getWebElement, int timeOut) { for (int second = 0; ; second++) { if (second >= timeOut) Assert.Fail("timeout"); try { if (getWebElement().Displayed) break; } catch (Exception) { } Thread.Sleep(1000); } }

El primer parámetro de esta función es cualquier función que devuelva un objeto IWebElement. El segundo parámetro es un tiempo de espera en segundos (el código del tiempo de espera se copió del Selenium IDE para FireFox). El código se puede usar para evitar la excepción del elemento obsoleto de la siguiente manera:

MyTestDriver.WaitForDisplayed(driver.SpecificElementToGet,5);

El código anterior llamará a driver.SpecificElementToGet().Displayed driver.SpecificElementToGet() hasta que driver.SpecificElementToGet() no arroje excepciones y .Displayed evalúa como true y 5 segundos no han pasado. Después de 5 segundos, la prueba fallará.

Por otro lado, para esperar que un elemento no esté presente, puede usar la siguiente función de la misma manera:

public static void WaitForNotPresent(Func<IWebElement> getWebElement, int timeOut) { for (int second = 0;; second++) { if (second >= timeOut) Assert.Fail("timeout"); try { if (!getWebElement().Displayed) break; } catch (ElementNotVisibleException) { break; } catch (NoSuchElementException) { break; } catch (StaleElementReferenceException) { break; } catch (Exception) { } Thread.Sleep(1000); } }


Pude utilizar un método como este con cierto éxito:

WebElement getStaleElemById(String id) { try { return driver.findElement(By.id(id)); } catch (StaleElementReferenceException e) { System.out.println("Attempting to recover from StaleElementReferenceException ..."); return getStaleElemById(id); } }

Sí, solo sigue sondeando el elemento hasta que ya no se considere obsoleto (¿es fresco?). Realmente no llega a la raíz del problema, pero he descubierto que WebDriver puede ser bastante quisquilloso con esta excepción, a veces la obtengo, y otras veces no. O podría ser que el DOM realmente está cambiando.

Por lo tanto, no estoy totalmente de acuerdo con la respuesta anterior de que esto necesariamente indica una prueba mal escrita. Lo tengo en páginas nuevas con las que no he interactuado de ninguna manera. Creo que hay algunas fallas en cómo se representa el DOM, o en lo que WebDriver considera obsoleto.


Sí, si tiene problemas con StaleElementReferenceExceptions es porque sus pruebas están mal escritas. Es una condición de carrera. Considere la siguiente situación:

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

Ahora, en el punto en que está haciendo clic en el elemento, la referencia del elemento ya no es válida. Es casi imposible para WebDriver adivinar todos los casos en que esto podría suceder, por lo que levanta las manos y le da el control, quien como autor de la prueba / aplicación debe saber exactamente lo que puede suceder o no. Lo que quiere hacer es esperar explícitamente hasta que el DOM esté en un estado en el que sepa que las cosas no cambiarán. Por ejemplo, usar un WebDriverWait para esperar a que exista un elemento específico:

// 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();

El método presenceOfElementLocated () se vería así:

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

Tiene razón en que el controlador Chrome actual es bastante inestable, y le alegrará saber que el troncal de Selenium tiene un controlador Chrome reescrito, donde la mayoría de la implementación fue realizada por los desarrolladores de Chromium como parte de su árbol.

PD. Alternativamente, en lugar de esperar explícitamente como en el ejemplo anterior, puede habilitar esperas implícitas: de esta forma, WebDriver siempre repetirá hasta el tiempo de espera especificado esperando que el elemento se presente:

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

En mi experiencia, sin embargo, esperar explícitamente siempre es más confiable.


Tuve el mismo problema y el mío fue causado por una versión antigua de selenio. No puedo actualizar a una versión más nueva debido al entorno de desarrollo. El problema está causado por HTMLUnitWebElement.switchFocusToThisIfNeeded (). Cuando navega hacia una nueva página, puede ocurrir que el elemento en el que hizo clic en la página anterior sea el oldActiveElement (vea más abajo). El selenio intenta obtener el contexto del elemento anterior y falla. Es por eso que construyeron una captura de prueba en lanzamientos futuros.

Código de Selenium-htmlunit-driver versión <2.23.0:

private void switchFocusToThisIfNeeded() { HtmlUnitWebElement oldActiveElement = ((HtmlUnitWebElement)parent.switchTo().activeElement()); boolean jsEnabled = parent.isJavascriptEnabled(); boolean oldActiveEqualsCurrent = oldActiveElement.equals(this); boolean isBody = oldActiveElement.getTagName().toLowerCase().equals("body"); if (jsEnabled && !oldActiveEqualsCurrent && !isBody) { oldActiveElement.element.blur(); element.focus(); } }

Código de selenium-htmlunit-driver version> = 2.23.0:

private void switchFocusToThisIfNeeded() { HtmlUnitWebElement oldActiveElement = ((HtmlUnitWebElement)parent.switchTo().activeElement()); boolean jsEnabled = parent.isJavascriptEnabled(); boolean oldActiveEqualsCurrent = oldActiveElement.equals(this); try { boolean isBody = oldActiveElement.getTagName().toLowerCase().equals("body"); if (jsEnabled && !oldActiveEqualsCurrent && !isBody) { oldActiveElement.element.blur(); } } catch (StaleElementReferenceException ex) { // old element has gone, do nothing } element.focus(); }

Sin actualizar a 2.23.0 o posterior, solo puede dar cualquier elemento en el foco de la página. Acabo de usar element.click() por ejemplo.


FirefoxDriver _driver = new FirefoxDriver(); // create webdriverwait WebDriverWait wait = new WebDriverWait(_driver, TimeSpan.FromSeconds(10)); // create flag/checker bool result = false; // wait for the element. IWebElement elem = wait.Until(x => x.FindElement(By.Id("Element_ID"))); do { try { // let the driver look for the element again. elem = _driver.FindElement(By.Id("Element_ID")); // do your actions. elem.SendKeys("text"); // it will throw an exception if the element is not in the dom or not // found but if it didn''t, our result will be changed to true. result = !result; } catch (Exception) { } } while (result != true); // this will continue to look for the element until // it ends throwing exception.