Selenium WebDriver Como resolver a exceção de referência do elemento obsoleto?

Eu tenho o seguinte código em um teste de driver da Web Selenium 2 que funciona quando eu estou depurando, mas na maioria das vezes falha quando eu executá-lo na compilation. Eu sei que deve ser algo a ver com a maneira como a página não está sendo atualizada, mas não sei como resolvê-la, portanto, quaisquer sugestões sobre o que eu fiz de errado são bem-vindas. Eu estou usando os primfaces JSF como meu framework de aplicações web. Quando clico no link Adicionar novo, aparece uma checkbox de diálogo pop-up com uma checkbox de input na qual posso inserir uma data e, em seguida, clicar em salvar. Está em obter o elemento de input para inserir texto em que recebo uma exceção de referência de elemento obsoleto.

desde já, obrigado

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++; } } } 

Quando eu executo isso eu recebo a seguinte exceção, mas pode ocorrer em

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

ou

 if (button.isDisplayed()) 

rastreamento de exceção

 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) 

Primeiro de tudo, vamos ser claros sobre o que é um WebElement.

Um WebElement é uma referência a um elemento no DOM.

Uma StaleElementException é lançada quando o elemento que você está interagindo é destruído e recriado. A maioria das páginas web complexas nos dias de hoje irá mudar as coisas enquanto o usuário interage com ele e isso requer que elementos do DOM sejam destruídos e recriados.

Quando isso acontece, a referência ao elemento no DOM que você tinha anteriormente se torna obsoleta e você não pode mais usar essa referência para interagir com o elemento no DOM. Quando isso acontecer, você precisará atualizar sua referência ou, em termos do mundo real, encontrar o elemento novamente.

Isso não é um problema. Se você encapsular sua chamada .findElement em um bloco try-catch e capturar o StaleElementReferenceException, poderá executar o loop e tentar novamente quantas vezes precisar até obter êxito.

Aqui estão alguns exemplos que escrevi .

Outro exemplo do projeto 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; } } }; 

O que estava acontecendo comigo era que o webdriver encontraria uma referência a um elemento DOM e, em algum momento depois que essa referência fosse obtida, o javascript removeria esse elemento e o adicionaria novamente (porque a página estava fazendo um redesenho, basicamente).

Tente isso. Descobrir a ação que faz com que o elemento dom seja removido do DOM. No meu caso, era uma chamada ajax assíncrona, e o elemento estava sendo removido do DOM quando a chamada do ajax foi concluída. Logo após essa ação, espere o elemento ficar obsoleto:

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

Neste ponto, você tem certeza de que o elemento está agora obsoleto. Então, da próxima vez que você fizer referência ao elemento, aguarde novamente, desta vez aguardando que seja adicionado novamente ao DOM:

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

Tente esperar por um 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")); } }); 

Duas razões para o elemento obsoleto

  1. Um elemento que é encontrado em uma página da Web referenciada como um WebElement no WebDriver, em seguida, o DOM muda (provavelmente devido a funções JavaScript) que WebElement vai obsoleto.

  2. O elemento foi totalmente eliminado.

Quando você tenta interagir com o WebElement staled [qualquer caso acima], o StaleElementException é lançado.

Como evitar / resolver a exceção obsoleta?

  1. Armazenando localizadores em seus elementos em vez de referências
 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. Aproveite os ganchos nas bibliotecas JS usadas
  # 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. Movendo suas ações para injeção de JavaScript
  self.driver.execute_script("$(\"li:contains('Narendra')\").click()"); 
  1. Esperar proativamente o elemento ficar obsoleto
  # Wait till the element goes stale, this means the list has updated wait_until(lambda: is_element_stale(old_link_reference)) 

Esta solução, que funcionou para mim, eu mencionei aqui se você tem algum cenário adicional, que funcionou para você, em seguida, comente abaixo

StaleElementReferenceException é devido à indisponibilidade de um elemento que está sendo acessado pelo método findelement.

Você precisa ter certeza antes de realizar quaisquer operações em um elemento (se você tiver alguma dúvida sobre a disponibilidade desse elemento)

Aguardando a visibilidade de um elemento

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

Ou então Use esta lógica para verificar se o elemento está presente ou não.

Use as Condições esperadas fornecidas pelo Selenium para aguardar o WebElement.

Enquanto você depura, o cliente não é tão rápido como se você simplesmente executasse um teste de unidade ou uma compilation de maven. Isso significa que no modo de debugging o cliente tem mais tempo para preparar o elemento, mas se a compilation estiver executando o mesmo código, ele é muito mais rápido e o WebElement que você está procurando pode não estar visível no DOM da Página.

Confie em mim com isso, eu tive o mesmo problema.

por exemplo:

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

Este método fácil chama espera após sua chamada por 2 segundos sobre a visibilidade do seu WebElement no DOM.

Eu resolvi esse problema com o seguinte 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; } 

Eu sugiro não usar @CachelookUp para Selenium WebDriver para StaleElementReferenceException .

Se você estiver usando a anotação @FindBy e tiver @CacheLookUp , apenas comente e verifique.

Isso funcionou para mim (fonte aqui ):

  /** * 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; } } } } 

O WebDriver tem que esperar até que o Elemento esteja localizado e um tempo limite seja após 10 segundos.

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

Por favor, não confunda os outros entre nós, se não tivermos certeza das respostas. É muito frustrante para o usuário final. A resposta simples e curta é usar a anotação @CacheLookup no webdriver. Por favor, consulte o link abaixo para isso. Como o @CacheLookup funciona no WebDriver?

Use webdriverwait com ExpectedCondition no bloco try catch com o 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 " 

Com referência à resposta dada por @djangofan, parece que a solução viável é manter seu código dentro do bloco try catch , onde ocorre um possível Staleness. Quando uso este código abaixo, não obtive o problema a qualquer momento.

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

Eu tentei usar o ExpectedConditions.presenceOfElementLocated(By) mas as exceções de staleness ainda lança intermitentemente.

Espero que esta solução ajude.

Esta solução funcionou bem para mim:

Adicionando a function de tratamento de erros e tente novamente

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

Após uma investigação profunda do problema, descobri que ocorreu um erro ao selecionar elementos DIV que foram incluídos apenas para o Bootstrap. O navegador Chrome remove esse DIVS e o erro ocorre. É o suficiente para descer e selecionar o elemento real para corrigir um erro. Por exemplo, minha checkbox de modal dialog tem estrutura:

  

Selecionar div class = “modal-body” gera um erro, selecionar o formulário … funciona como seria.

No meu caso, esse erro foi causado pelo fato de que eu estava definindo o elemento ActionChains fora do

 def parse(self, response): 

método ao usar uma combinação de selenium e Scrapy, por exemplo:

Não funciona:

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

Movendo action_chains = ActionChains(self.driver) dentro do def parse(self, response): resolveu o problema, por exemplo:

Trabalho:

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

Basta baixar a nova extensão do Chrome e usar o servidor de selenium 3 que funcionará bem.

A melhor maneira que eu encontrei para evitar referências de elementos obsoletos é não usar o PageFactory, mas armazenar os localizadores (ou seja, 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 classs 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(); } } 

Eu tenho uma class cheia de methods estáticos WebDriverWait que eu uso. E não me lembro se o uso anterior do WebDriver wait manipulará ou não a exceção StaleElement. Se não, você poderia usar uma espera fluente em vez disso, como na resposta do DjangoFan. Mas o princípio que estou exibindo funcionará (mesmo se essa linha específica com o WebDriverWait explodir.

Então o tldr;

  1. Use localizadores e uma combinação de WebDriverWait / Fluent wait / realocando o elemento você mesmo, portanto, se seu elemento ficar obsoleto, você poderá realocá-lo sem precisar duplicar o localizador em um @FindBy (para um elemento inicializado pagefactory), já que não há WebElement método .relocate ().
  2. Para simplificar a vida, tenha uma class Abstract BasePage com methods convenientes para localizar um elemento / lista de elementos.

Eu tentei muitas das sugestões acima, mas a mais simples funcionou. No meu caso, o uso de @CachelookUp para o elemento web causou a exceção do elemento obsoleto. Eu acho que depois de atualizar a página, a referência do elemento não foi recarregada e não conseguiu encontrar o elemento. Desativando a linha @CachelookUp para o elemento trabalhado.

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

Quando uma exceção de elemento obsoleto ocorre !!

A exceção do elemento obsoleto pode acontecer quando as bibliotecas que suportam essas checkboxs de texto / botões / links foram alteradas, o que significa que os elementos são iguais, mas a referência foi alterada no site sem afetar os localizadores. Assim, a referência que armazenamos em nosso cache, incluindo a referência da biblioteca, agora se tornou antiga ou obsoleta, porque a página foi atualizada com bibliotecas atualizadas.

 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”);