Selenium WebDriver如何解决陈旧的元素引用exception?

我在Selenium 2 Web Drivertesting中有以下代码,当我在debugging时工作,但大部分时间在构build中运行时失败。 我知道这是一定要做的事情,而不是刷新页面的方式,但不知道如何解决它,所以任何指针,我已经做错了赞赏。 我正在使用JSF primefaces作为我的Web应用程序框架。 当我点击添加新链接popup对话框出现一个input框,我可以input一个date,然后点击保存。 它是获取input元素input文本,我得到一个陈旧的元素参考例外。

提前致谢

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<String, String>> tableData = new HashMap<String, Map<String, String>>(); @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<Boolean>() { 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<WebElement> 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<String, String>()); } Map<String, String> subTable = tableData.get(id); for(WebElement trElement : tr_collection) { List<WebElement> 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++; } } } 

当我运行这个我得到以下exception,但它可能会发生

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

要么

 if (button.isDisplayed()) 

exception追踪

 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) 

首先让我们清楚一下WebElement是什么。

WebElement是对DOM中元素的引用。

如果您正在交互的元素被销毁并重新创build,则会引发StaleElementException。 如今,大多数复杂的网页都会随着用户的交互而dynamic地移动,这就要求DOM中的元素被销毁和重新创build。

当发生这种情况时,DOM中对元素的引用变成陈旧,并且不再能够使用此引用与DOM中的元素进行交互。 发生这种情况时,您需要刷新参考,或者在现实世界中再次find元素。

这不是问题。 如果您将.findElement调用包装在try-catch块中,并捕获StaleElementReferenceException,那么可以根据需要循环和重试多次,直到成功为止。

这里是我写的一些例子 。

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

发生在我身上的事情是,webdriver会find一个DOM元素的引用,然后在获得引用后的某个时刻,javascript会删除该元素并重新添加它(因为页面基本上正在重绘)。

尝试这个。 找出导致dom元素从DOM中删除的操作。 在我的情况下,这是一个asynchronous的Ajax调用,并且当Ajax调用完成时元素被从DOM中删除。 在该行动之后,等待元素陈旧:

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

在这一点上,你确定元素现在是陈旧的。 所以,下一次你引用这个元素时,再等一下,这次等待它被重新添加到DOM:

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

尝试等待这样的元素:

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

陈旧元素的两个原因

  1. 在WebDriver中作为WebElement引用的Web页面上find的元素,然后DOM更改(可能由于JavaScript函数)WebElement过时。

  2. 该元素已被完全删除。

当您尝试与staled WebElement [任何上述情况]进行交互时,会引发StaleElementException。

如何避免/解决陈旧exception?

  1. 将定位器存储到您的元素而不是引用
 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. 利用所使用的JS库中的钩子
  # 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. 将您的行为转化为JavaScript注入
  self.driver.execute_script("$(\"li:contains('Narendra')\").click()"); 
  1. 积极等待元素陈旧
  # Wait till the element goes stale, this means the list has updated wait_until(lambda: is_element_stale(old_link_reference)) 

这个解决scheme对我很有帮助,我在这里提到,如果你有任何额外的情况下,为你工作,然后下面评论

StaleElementReferenceException是由于findelement方法访问的元素不可用。

您需要确保在对元素执行任何操作之前(如果您对该元素的可用性有疑问)

等待元素的可见性

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

否则使用这个逻辑来validation元素是否存在。

使用Selenium提供的预期条件等待WebElement。

在你debugging的时候,客户端并不像运行一个unit testing或Maven构build一样快。 这意味着在debugging模式下客户端有更多的时间来准备元素,但是如果构build运行相同的代码,他会更快,WebElement您所寻找的可能不会在页面的DOM中可见。

相信我,我有同样的问题。

例如:

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

这个简单的方法调用WebElement DOM上的可见性后调用等待2秒。

相信我,StaleElementexception可能会非常令人沮丧,尤其是在使用chrome驱动程序的时候…但是到目前为止,唯一的解决办法就是使用我所谓的Raw等待:

 try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } 

虽然纯粹主义者可能会说我们正在使用魔法数字,但是一旦您知道了安全的等待数量,那么我知道它在超过95%的情况下是有效的。

我用下面的代码解决了这个问题。

 public WebElement waitForElement(final By findBy, final int waitTime) { Wait<AppiumDriver> 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<AppiumDriver, WebElement>() { @Override public WebElement apply(AppiumDriver driver) { System.out.println("Trying to find element " + findBy.toString()); WebElement element = driver.findElement(findBy); return element; } }); return webElement; } 

我build议不要使用@CachelookUp Selenium WebDriver的StaleElementReferenceException

如果您使用@FindBy批注并拥有@CacheLookUp ,只需将其注释掉并检查。

这工作对我来说(来源):

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

WebDriver必须等待,直到元素被find,并且超时在10秒之后。

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

如果我们不确定答案,请不要混淆他人。 这对最终用户来说是相当令人沮丧的。 简单和简短的答案是在webdriver中使用@CacheLookup注释。 请参阅下面的链接。 @CacheLookup如何在WebDriver中工作?

在for循环EX的try catch块中使用带有ExpectedCondition的webdriverwait:for 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 " 

参考@djangofan给出的答案,看起来像可行的解决scheme是让你的代码在可能的陈旧过程中try catch块。 当我使用下面的代码时,我没有任何问题。

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

我曾尝试使用ExpectedConditions.presenceOfElementLocated(By)但过时exception仍然间歇性抛出。

希望这个解决scheme有帮

这个解决scheme适合我:

添加error handlingfunction,然后重试

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

经过深入调查的问题,我发现selectDIV元素,只添加引导的错误发生。 Chrome浏览器删除这样的DIVS,并发生错误。 这是足够的下台,select真正的元素来修复一个错误。 例如,我的模式对话框有以下结构:

 <div class="modal-content" uib-modal-transclude=""> <div class="modal-header"> ... </div> <div class="modal-body"> <form class="form-horizontal ..."> ... </form> <div> <div> 

selectdiv class =“modal-body”会产生一个错误,selectform …会像以前那样工作。

在我的情况下,这个错误是由于我定义了ActionChains元素之外的事实

 def parse(self, response): 

方法时使用Selenium和Scrapy的组合,例如:

不起作用:

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

def parse(self, response):移动action_chains = ActionChains(self.driver) def parse(self, response):解决了这个问题,例如:

作品:

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

只需下载新的Chrome扩展,并使用selenium服务器3,它将正常工作。

我发现避免陈旧的元素引用的最佳方式是不使用PageFactory,而是存储定位器(即按元素)。

 public class WebDriverFactory { // if you want to multithread tests, use a ThreadLocal<WebDriver> // 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<WebElement> 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(); } } 

我有一个充满了我使用的静态WebDriverWait方法的类。 而我不记得上面的WebDriver等待是否会处理StaleElementexception。 如果没有,你可以使用stream畅的等待,而不是像在DjangoFan的答案。 但我显示的原则将工作(即使与WebDriverWait的特定线路爆炸。

所以tldr;

  1. 使用定位器,以及WebDriverWait / Fluent自己等待/重新定位元素的组合,所以如果你的元素过时了,你可以重定位它,而不必在@FindBy复制定位器(对于pagefactory初始化的元素),因为没有WebElement .relocate()方法。
  2. 为了简化生活,请使用便捷方法来定位元素/元素列表。

我尝试了许多上述build议,但最简单的一个工作。 在我的情况下,这是使用@CachelookUp为Web元素导致陈旧的元素exception。 我猜刷新页面后,元素引用不重新加载,无法find元素。 禁用工作元素的@CachelookUp行。

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

只是一个愚蠢的问题,是popup式对话框与input正常的警报窗口? 如果是这样,所有你需要做的是driver.switchTo()。alert