Estoy implementando muchas pruebas de Selenium usando Java. A veces, mis pruebas fallan debido a un StaleElementReferenceException
. ¿Podría sugerir algunos enfoques para hacer que las pruebas sean más estables?
java
selenium-webdriver
hoang nguyen
fuente
fuente
Tenía este problema de forma intermitente. Sin que yo lo supiera, BackboneJS se estaba ejecutando en la página y reemplazaba el elemento en el que estaba tratando de hacer clic. Mi código se veía así.
driver.findElement(By.id("checkoutLink")).click();
Lo que, por supuesto, es funcionalmente igual a esto.
WebElement checkoutLink = driver.findElement(By.id("checkoutLink")); checkoutLink.click();
Lo que ocurría ocasionalmente era que javascript reemplazaría el elemento checkoutLink entre encontrarlo y hacer clic en él, es decir.
WebElement checkoutLink = driver.findElement(By.id("checkoutLink")); // javascript replaces checkoutLink checkoutLink.click();
Lo que condujo legítimamente a una StaleElementReferenceException al intentar hacer clic en el enlace. No pude encontrar ninguna manera confiable de decirle a WebDriver que esperara hasta que el javascript terminara de ejecutarse, así que así es como finalmente lo resolví.
new WebDriverWait(driver, timeout) .ignoring(StaleElementReferenceException.class) .until(new Predicate<WebDriver>() { @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 sea exitoso o se alcance el tiempo de espera. Me gusta esta solución porque le ahorra tener que escribir cualquier lógica de reintento y usa solo las construcciones integradas de WebDriver.
fuente
En general, esto se debe a que el DOM se está actualizando y usted está intentando acceder a un elemento nuevo / actualizado, pero el DOM se actualizó, por lo que es una referencia no válida.
Solucione esto primero usando una espera explícita en el elemento para asegurarse de que la actualización esté completa, luego tome una nueva referencia al elemento nuevamente.
Aquí hay un código psuedo para ilustrar (adaptado de un código 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!
fuente
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();
Pero de todos modos, la mejor solución es confiar en la biblioteca Selenide, maneja este tipo de cosas y más. (en lugar de referencias de elementos, maneja proxies para que nunca tenga que lidiar con elementos obsoletos, lo que puede ser bastante difícil). Selenida
fuente
La razón por la que
StaleElementReferenceException
ocurre ya se ha establecido: actualizaciones del DOM entre encontrar y hacer algo con el elemento.Para el problema del clic, he usado recientemente 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 el "encadenamiento" del propio Selenium a
ExpectedConditions
través delExpectedConditions.refreshed()
. Esto realmente espera y verifica 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 .
fuente
En mi proyecto introduje una noción de StableWebElement. Es un contenedor para WebElement que puede detectar si el elemento está obsoleto y encontrar una nueva referencia al elemento original. He agregado métodos auxiliares 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 transferido fácilmente a java https://github.com/cezarypiatek/Tellurium/blob/master/Src/MvcPages/SeleniumUtils/StableWebElement.cs
fuente
Una solución en C # sería:
Clase de ayudante:
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.
fuente
La solución de Kenny está en desuso, use esto, estoy usando la clase de acciones para hacer doble clic, pero puede hacer cualquier cosa.
new FluentWait<>(driver).withTimeout(30, TimeUnit.SECONDS).pollingEvery(5, TimeUnit.SECONDS) .ignoring(StaleElementReferenceException.class) .until(new Function() { @Override public Object apply(Object arg0) { WebElement e = driver.findelement(By.xpath(locatorKey)); Actions action = new Actions(driver); action.moveToElement(e).doubleClick().perform(); return true; } });
fuente
findByAndroidId
Método limpio que se maneja con graciaStaleElementReference
.Esto se basa en gran medida en la respuesta de jspcal, pero tuve que modificar esa respuesta para que funcione correctamente con nuestra configuración, por lo que quería agregarla aquí en caso de que sea útil para otros. Si esta respuesta te ayudó, sube la respuesta de jspcal .
// This loops gracefully handles StateElementReference errors and retries up to 10 times. These can occur when an element, like a modal or notification, is no longer available. export async function findByAndroidId( id, { assert = wd.asserters.isDisplayed, timeout = 10000, interval = 100 } = {} ) { MAX_ATTEMPTS = 10; let attempt = 0; while( attempt < MAX_ATTEMPTS ) { try { return await this.waitForElementById( `android:id/${ id }`, assert, timeout, interval ); } catch ( error ) { if ( error.message.includes( "StaleElementReference" ) ) attempt++; else throw error; // Re-throws the error so the test fails as normal if the assertion fails. } } }
fuente
Esto funciona para mí (100% funcionando) 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; }
fuente
El problema es que cuando pasa el elemento de Javascript a Java de nuevo a Javascript, puede haber salido del DOM.
Intente hacer todo en Javascript:
driver.executeScript("document.querySelector('#my_id').click()")
fuente
Prueba esto
while (true) { // loops forever until break try { // checks code for exceptions WebElement ele= (WebElement)wait.until(ExpectedConditions.elementToBeClickable((By.xpath(Xpath)))); break; // if no exceptions breaks out of loop } catch (org.openqa.selenium.StaleElementReferenceException e1) { Thread.sleep(3000); // you can set your value here maybe 2 secs continue; // continues to loop if exception is found } }
fuente
He encontrado una solución aquí . En mi caso, el elemento se vuelve inaccesible en caso de salir de la ventana, pestaña o página actual y volver de nuevo.
.ignoring (StaleElement ...), .refreshed (...) y elementToBeClicable (...) no ayudaron y estaba obteniendo una excepción en la
act.doubleClick(element).build().perform();
cadena.Usando la función en mi clase de prueba principal:
Mi función BaseTest:
int defaultTime = 15; boolean openForm(String myXpath) throws Exception { int count = 0; boolean clicked = false; while (count < 4 || !clicked) { try { WebElement element = getWebElClickable(myXpath,defaultTime); act.doubleClick(element).build().perform(); clicked = true; print("Element have been clicked!"); break; } catch (StaleElementReferenceException sere) { sere.toString(); print("Trying to recover from: "+sere.getMessage()); count=count+1; } }
Mi función BaseClass:
protected WebElement getWebElClickable(String xpath, int waitSeconds) { wait = new WebDriverWait(driver, waitSeconds); return wait.ignoring(StaleElementReferenceException.class).until( ExpectedConditions.refreshed(ExpectedConditions.elementToBeClickable(By.xpath(xpath)))); }
fuente
Podría haber un problema potencial que lleve a la excepción StaleElementReferenceException que nadie mencionó hasta ahora (con respecto a las acciones).
Lo explico en Javascript, pero es lo mismo en Java.
Esto no funcionará:
let actions = driver.actions({ bridge: true }) let a = await driver.findElement(By.css('#a')) await actions.click(a).perform() // this leads to a DOM change, #b will be removed and added again to the DOM. let b = await driver.findElement(By.css('#b')) await actions.click(b).perform()
Pero instanciar las acciones nuevamente lo resolverá:
let actions = driver.actions({ bridge: true }) let a = await driver.findElement(By.css('#a')) await actions.click(a).perform() // this leads to a DOM change, #b will be removed and added again to the DOM. actions = driver.actions({ bridge: true }) // new let b = await driver.findElement(By.css('#b')) await actions.click(b).perform()
fuente
Por lo general, StaleElementReferenceException cuando el elemento al que intentamos acceder ha aparecido, pero otros elementos pueden afectar la posición del elemento en el que estamos interesados, por lo tanto, cuando intentamos hacer clic o getText o intentamos realizar alguna acción en WebElement, obtenemos una excepción que generalmente dice que el elemento no está adjunto con DOM .
La solución que probé es la siguiente:
protected void clickOnElement(By by) { try { waitForElementToBeClickableBy(by).click(); } catch (StaleElementReferenceException e) { for (int attempts = 1; attempts < 100; attempts++) { try { waitFor(500); logger.info("Stale element found retrying:" + attempts); waitForElementToBeClickableBy(by).click(); break; } catch (StaleElementReferenceException e1) { logger.info("Stale element found retrying:" + attempts); } } } protected WebElement waitForElementToBeClickableBy(By by) { WebDriverWait wait = new WebDriverWait(getDriver(), 10); return wait.until(ExpectedConditions.elementToBeClickable(by)); }
En el código anterior, primero trato de esperar y luego hago clic en el elemento si ocurre una excepción, luego lo atrapo y trato de repetirlo, ya que existe la posibilidad de que aún no se carguen todos los elementos y nuevamente se puede producir una excepción.
fuente
Tal vez se agregó más recientemente, pero otras respuestas no mencionan la función de espera implícita de Selenium, que hace todo lo anterior por usted y está integrada en Selenium.
driver.manage().timeouts().implicitlyWait(10,TimeUnit.SECONDS);
Esto reintentará las
findElement()
llamadas hasta que se encuentre el elemento o durante 10 segundos.Fuente: http://www.seleniumhq.org/docs/04_webdriver_advanced.jsp
fuente