Estou implementando muitos testes Selenium usando Java. Às vezes, meus testes falham devido a a StaleElementReferenceException
. Você poderia sugerir algumas abordagens para tornar os testes mais estáveis?
java
selenium-webdriver
Hoang Nguyen
fonte
fonte
Eu estava tendo esse problema de forma intermitente. Sem que eu soubesse, o BackboneJS estava sendo executado na página e substituindo o elemento que eu estava tentando clicar. Meu código era assim.
driver.findElement(By.id("checkoutLink")).click();
Que é funcionalmente igual a isso.
WebElement checkoutLink = driver.findElement(By.id("checkoutLink")); checkoutLink.click();
O que ocasionalmente acontecia era que o javascript substituía o elemento checkoutLink entre localizá-lo e clicar nele, ou seja,
WebElement checkoutLink = driver.findElement(By.id("checkoutLink")); // javascript replaces checkoutLink checkoutLink.click();
O que levou a uma StaleElementReferenceException ao tentar clicar no link. Não consegui encontrar nenhuma maneira confiável de dizer ao WebDriver para esperar até que o javascript terminasse de ser executado, então aqui está como eu resolvi isso.
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 tentará continuamente clicar no link, ignorando StaleElementReferenceExceptions até que o clique seja bem-sucedido ou o tempo limite seja atingido. Eu gosto dessa solução porque ela evita que você tenha que escrever qualquer lógica de repetição e usa apenas as construções integradas do WebDriver.
fonte
Geralmente, isso ocorre porque o DOM está sendo atualizado e você está tentando acessar um elemento atualizado / novo - mas o DOM foi atualizado, portanto é uma referência inválida que você tem.
Contorne isso usando primeiro uma espera explícita no elemento para garantir que a atualização seja concluída e, em seguida, obtenha uma nova referência ao elemento novamente.
Aqui está um código psuedo para ilustrar (adaptado de algum código C # que uso EXATAMENTE 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 isto ajude!
fonte
A solução de Kenny é boa, mas pode ser escrita de uma forma mais elegante
new WebDriverWait(driver, timeout) .ignoring(StaleElementReferenceException.class) .until((WebDriver d) -> { d.findElement(By.id("checkoutLink")).click(); return true; });
Ou também:
new WebDriverWait(driver, timeout).ignoring(StaleElementReferenceException.class).until(ExpectedConditions.elementToBeClickable(By.id("checkoutLink"))); driver.findElement(By.id("checkoutLink")).click();
Mas de qualquer forma, a melhor solução é contar com a biblioteca Selenide, ela lida com esse tipo de coisas e muito mais. (em vez de referências de elemento, ele lida com proxies para que você nunca tenha que lidar com elementos obsoletos, o que pode ser bastante difícil). Selenide
fonte
A razão pela qual o
StaleElementReferenceException
ocorre já foi definida: atualizações para o DOM entre encontrar e fazer algo com o elemento.Para o problema do clique, recentemente usei uma solução 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(); }
A parte crucial é o "encadeamento" do próprio Selenium
ExpectedConditions
através doExpectedConditions.refreshed()
. Na verdade, isso espera e verifica se o elemento em questão foi atualizado durante o tempo limite especificado e, adicionalmente, espera que o elemento se torne clicável.Dê uma olhada na documentação do método atualizado .
fonte
Em meu projeto, apresentei uma noção de StableWebElement. É um wrapper para WebElement que é capaz de detectar se o elemento está desatualizado e encontrar uma nova referência para o elemento original. Eu adicionei métodos auxiliares para localizar elementos que retornam StableWebElement em vez de WebElement e o problema com StaleElementReference desapareceu.
public static IStableWebElement FindStableElement(this ISearchContext context, By by) { var element = context.FindElement(by); return new StableWebElement(context, element, by, SearchApproachType.First); }
O código em C # está disponível na página do meu projeto, mas pode ser facilmente transferido para java https://github.com/cezarypiatek/Tellurium/blob/master/Src/MvcPages/SeleniumUtils/StableWebElement.cs
fonte
Uma solução em C # seria:
Aula auxiliar:
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; } } }
Invocação:
DriverHelper driverHelper = new DriverHelper("http://www.seleniumhq.org/docs/04_webdriver_advanced.jsp", 10); driverHelper.ClickElement("input[value='csharp']:first-child");
Da mesma forma pode ser usado para Java.
fonte
A solução de Kenny está obsoleta, use isso, estou usando a classe Actions para clicar duas vezes, mas você pode fazer qualquer coisa.
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; } });
fonte
findByAndroidId
Método limpo que lida com elegânciaStaleElementReference
.Isso é muito baseado na resposta de jspcal, mas tive que modificar essa resposta para que funcionasse perfeitamente com nossa configuração e, portanto, queria adicioná-la aqui, caso seja útil para outras pessoas. Se esta resposta ajudou você, por favor, vote a resposta 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. } } }
fonte
Isso funciona para mim (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; }
fonte
O problema é que, no momento em que você passa o elemento de Javascript para Java de volta para Javascript, ele pode ter saído do DOM.
Tente fazer tudo em Javascript:
driver.executeScript("document.querySelector('#my_id').click()")
fonte
Tente isto
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 } }
fonte
Eu encontrei solução aqui . No meu caso, o elemento fica inacessível no caso de sair da janela, guia ou página atual e voltar novamente.
.ignoring (StaleElement ...), .refreshed (...) e elementToBeClicable (...) não ajudavam e eu estava recebendo uma exceção na
act.doubleClick(element).build().perform();
string.Usando a função na minha classe de teste principal:
Minha função 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; } }
Minha função 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)))); }
fonte
Pode haver um problema potencial que leva à StaleElementReferenceException que ninguém mencionou até agora (em relação às ações).
Eu explico em Javascript, mas é o mesmo em Java.
Isso não vai 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()
Mas instanciar as ações novamente vai 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()
fonte
Normalmente StaleElementReferenceException quando o elemento que tentamos acessar apareceu, mas outros elementos podem afetar a posição do elemento em que estamos interessados, portanto, quando tentamos clicar ou obterText ou tentar fazer alguma ação em WebElement, obtemos uma exceção que geralmente diz que o elemento não está anexado ao DOM .
A solução que tentei é a seguinte:
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)); }
No código acima, primeiro tento esperar e, em seguida, clicar no elemento se ocorrer uma exceção, então eu pego e tento fazer um loop, pois existe a possibilidade de que ainda todos os elementos não possam ser carregados e novamente a exceção pode ocorrer.
fonte
Talvez tenha sido adicionado mais recentemente, mas outras respostas deixam de mencionar o recurso de espera implícito do Selenium, que faz tudo o que foi descrito acima para você, e é integrado ao Selenium.
driver.manage().timeouts().implicitlyWait(10,TimeUnit.SECONDS);
Isso vai tentar novamente
findElement()
chamar até que o elemento seja encontrado, ou por 10 segundos.Fonte - http://www.seleniumhq.org/docs/04_webdriver_advanced.jsp
fonte