Selenium WebDriver 如何解决陈旧元素引用异常?
Posted
技术标签:
【中文标题】Selenium WebDriver 如何解决陈旧元素引用异常?【英文标题】:Selenium WebDriver How to Resolve Stale Element Reference Exception? 【发布时间】:2013-04-16 11:38:59 【问题描述】:我在 Selenium 2 Web 驱动程序测试中有以下代码,它在我调试时有效,但在我在构建中运行它时大部分时间都失败了。我知道这一定与页面未刷新的方式有关,但不知道如何解决它,所以任何关于我做错了什么的指针都值得赞赏。我使用 JSF primefaces 作为我的 Web 应用程序框架。当我单击添加新链接时,会出现一个弹出对话框,其中包含一个输入框,我可以在其中输入日期,然后单击保存。在让输入元素输入文本时,我得到了一个陈旧的元素引用异常。
提前致谢
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++;
当我运行它时,我得到以下异常,但它可能发生在
WebElement textElement = driver.findElement(By.name("createForm:dateInput_input"));
或
if (button.isDisplayed())
异常跟踪
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)
【问题讨论】:
有人可以就此提出建议吗? 【参考方案1】:StaleElementReferenceException 是由于 findelement 方法访问的元素不可用。
在对元素执行任何操作之前,您需要确保(如果您对该元素的可用性有疑问)
等待元素的可见性
(new WebDriverWait(driver, 10)).until(new ExpectedCondition()
public Boolean apply(WebDriver d)
return d.findElement(By.name("createForm:dateInput_input")).isDisplayed();
);
否则使用this逻辑来验证元素是否存在。
【讨论】:
我看不出您添加的代码与我在测试用例中已有的 ode 有何不同 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;否则返回 false; ); </boolean>
等待元素可见性不会阻止您收到 StaleElementException。 StaleElementException 是由您已经发现的元素过时引起的。
我遇到了类似的问题,我已经按照描述(等待显示)决定了这个问题。就我而言,问题是每次输入的模糊事件都会显示叠加层。【参考方案2】:
首先让我们弄清楚 WebElement 是什么。
WebElement 是对 DOM 中元素的引用。
当您正在交互的元素被销毁然后重新创建时,会引发 StaleElementException。如今,大多数复杂的网页都会在用户与之交互时动态移动内容,这需要销毁和重新创建 DOM 中的元素。
当这种情况发生时,您之前对 DOM 中元素的引用变得陈旧,并且您不再能够使用该引用与 DOM 中的元素进行交互。发生这种情况时,您将需要刷新您的参考,或者在现实世界中再次找到该元素。
【讨论】:
因此,根据该解释的定义,这意味着 Selenium 希望您在遇到此类异常的情况下手动处理重试。正确的?只要您正在测试的网络应用程序是稳定的并且实际上确实可以正确重新加载,那么对 findElement 的重试应该会很好,并且很可能在第一次重试时。 是的。您可以使用显式等待来等待元素变得陈旧,然后再尝试再次查找元素。这里要注意的关键是“你应该知道你的应用程序在做什么”。如果您不知道什么时候会销毁和重新创建元素,那么您就没有编写自动化测试所需的所有信息。 @Ardesco,所以我假设 PageFactory 和 initElements 在这种情况下不适合,因为对元素的引用是在开始和操作期间创建的,Dom 被破坏并重新创建并且引用也被销毁。那么,是不是说 PageFactory 模式不再适合这些类型的现代 Web 应用程序了? 使用 Java PageFactory 您不太可能看到 StaleElementReferenceException。默认情况下,Java PageFactory 类将在您每次尝试使用它时再次查找该元素。如果您使用 @CacheLookup 注释,则该规则的例外情况。在这种情况下,您将看到 StaleElementReference 异常,因为 PageFactory 将使用缓存的元素引用,而不是再次查找它。 如果你不幸在 PageFactory 找到元素之间的几毫秒内让一个元素过时,那么你很可能会在 java PageFactory 中得到一个 StaleElementReference 异常,然后使用它做某事。【参考方案3】:这不是问题。如果您将 .findElement 调用包装在 try-catch 块中并捕获 StaleElementReferenceException ,那么您可以根据需要循环并重试多次,直到成功为止。
这里是some examples I wrote。
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;
;
【讨论】:
这不是一个解决方案,你永远不想捕获一个 staleElementException。目标是没有。 staleElementException 表示您做错了可以修复的事情。 我并不总是同意在自动更新或复杂 CSS 转换的情况下怎么办?对于那些人来说,这种方法可能非常有益,并且在某些情况下可能是必需的。话虽这么说,大多数时候你是正确的,应该深思熟虑地使用这种方法。我认为有一个刷新参考的方法会很好。【参考方案4】:使用 Selenium 提供的预期条件等待 WebElement。
在您调试时,客户端的速度不如您只运行单元测试或 maven 构建那么快。 这意味着在调试模式下,客户端有更多时间准备元素,但如果构建运行相同的代码,他会更快,并且您要查找的 WebElement 在页面的 DOM 中可能不可见。
相信我,我遇到了同样的问题。
例如:
inClient.waitUntil(ExpectedConditions.visibilityOf(YourElement,2000))
这个简单的方法调用在他的调用后等待 2 秒你的 WebElement 在 DOM 上的可见性。
【讨论】:
这仅适用于设置了 CSS 属性“display”的元素。我认为您的意思是建议 ExpectedConditions.isElementPresent ? 一个元素可以存在,然后销毁并重新创建,使其过时。【参考方案5】:这对我有用(来源here):
/**
* 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;
【讨论】:
使用 FluentWait .ignoring 方法可以使这段代码更短。 @djangofan 我在我的应用程序中尝试过,但没有成功。您的里程可能会有所不同。 这可能是因为您需要将重试循环包裹起来,而不是在异常中计数。【参考方案6】:WebDriver 必须等到元素被定位并且在 10 秒后超时。
WebElement myDynamicElement1 = new WebDriverWait(driver, 10).until(
ExpectedConditions.presenceOfElementLocated(
By.name("createForm:dateInput_input")
)
);
【讨论】:
您在 ; 之前的末尾缺少一个 )像这样));【参考方案7】:尝试等待这样的元素:
// 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"));
);
【讨论】:
非常好......我会尽快在我的代码中配合它。 这将帮助您找到在页面初始加载时可能未呈现的元素,它不会为 StaleElementReferenceException 做任何事情。【参考方案8】:发生在我身上的是 webdriver 会找到对 DOM 元素的引用,然后在获得该引用后的某个时间点,javascript 会删除该元素并重新添加它(因为页面正在重绘,基本上)。
试试这个。找出导致 dom 元素从 DOM 中删除的操作。在我的例子中,这是一个异步 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")))
【讨论】:
谢谢。突出显示ExpectedConditions.StalenessOf
使我走上了在测试中处理此异常的道路。
@Mashton,您能否详细说明您是如何做到的?
我的情况是我有一个“加载更多”按钮,每次将结果集合扩展 9(直到所有结果都包含在内),我想要所有结果并检查集合中的特定项目。循环单击按钮直到它不存在会给出“陈旧”异常,直到我将代码更改为: var element = Driver.FindElements(By.XPath("MyPath")) .FirstOrDefault(); if (element != null) element.Click(); wait.Until(ExpectedConditions.StalenessOf(element));循环再做一次。【参考方案9】:
我建议不要将 @CachelookUp
用于 Selenium WebDriver for StaleElementReferenceException
。
如果您正在使用@FindBy
注释并拥有@CacheLookUp
,只需将其注释掉并检查。
【讨论】:
【参考方案10】:如果我们不确定答案,请不要混淆其他人。这对最终用户来说非常令人沮丧。简单而简短的答案是 在 webdriver 中使用 @CacheLookup 注释。请参阅下面的链接。 How does @CacheLookup work in WebDriver?
【讨论】:
“@CacheLookup”将导致 StaleElementReferenceExceptions。如果您不想拥有它们,则不应使用 @CacheLookup 注释。 Java Page Factory 可能会遇到竞争条件,您偶尔会在不使用“@CacheLookup”注释的情况下获得 StaleElementReferenceException,因为在后台它会找到一个元素然后尝试使用它。如果运气不好,元素将在找到元素和使用元素之间变得陈旧。【参考方案11】:我已经用下面的代码解决了这个问题。
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;
【讨论】:
您无法通过执行“driver.findElement(findBy);”获得 StaleElementReferenceException,您可以通过重新使用现已过时的现有 WebElement 获得 StaleElementReferenceException(请参阅下面的答案) .将“StaleElementReferenceException.class”添加到 .ignoring() 块是过时的代码。【参考方案12】:在带有 for 循环的 try catch 块中使用 webdriverwait 和 ExpectedCondition 例如:对于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 "
【讨论】:
【参考方案13】:参考@djangofan 给出的答案,看起来可行的解决方案是将您的代码保留在可能发生过时的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)
,但过时异常仍然间歇性地抛出。
希望此解决方案有所帮助。
【讨论】:
【参考方案14】:这个解决方案对我来说很好用:
Adding error handling function and try again
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();
);
;
【讨论】:
【参考方案15】:过时元素的两个原因
在 WebDriver 中作为 WebElement 引用的网页上找到的元素随后 DOM 发生变化(可能由于 JavaScript 函数),WebElement 变得陈旧。
元素已被完全删除。
当您尝试与过时的 WebElement [上述任何情况] 进行交互时,将引发 StaleElementException。
如何避免/解决过时异常?
-
将定位器存储到您的元素而不是引用中
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
-
利用所用 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)
-
将您的操作转移到 JavaScript 注入中
self.driver.execute_script("$(\"li:contains('Narendra')\").click()");
-
主动等待元素过时
# Wait till the element goes stale, this means the list has updated wait_until(lambda: is_element_stale(old_link_reference))
此解决方案对我有用,如果您有任何其他适用于您的方案,我在这里提到过,然后在下面评论
【讨论】:
这是最完整和最不老套的答案。投赞成票。【参考方案16】:在深入调查问题后,我发现选择仅为 Bootstrap 添加的 DIV 元素时出错。 Chrome 浏览器删除此类 DIVS 并发生错误。降级并选择真正的元素来修复错误就足够了。例如,我的模态对话框具有以下结构:
<div class="modal-content" uib-modal-transclude="">
<div class="modal-header">
...
</div>
<div class="modal-body">
<form class="form-horizontal ...">
...
</form>
<div>
<div>
选择 div class="modal-body" 会产生错误,选择 form ... 会正常工作。
【讨论】:
【参考方案17】:在我的例子中,这个错误是由于我在
之外定义 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):
self.driver.get(response.url)
action_chains = ActionChains(self.driver)
【讨论】:
【参考方案18】:只需下载新的 chrome 扩展并使用 selenium server 3 即可。
【讨论】:
【参考方案19】:我发现避免过时元素引用的最佳方法是不使用 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 wait 是否会处理 StaleElement 异常。如果没有,您可以像 DjangoFan 的回答那样使用流利的等待。但是我展示的原理会起作用(即使 WebDriverWait 的那条特定线爆炸了。
所以 tldr;
-
使用定位器,以及 WebDriverWait/Fluent 等待/自己重新定位元素的组合,因此如果您的元素过时,您可以重新定位它而无需在
@FindBy
中复制定位器(对于 pagefactory 初始化的元素),因为没有 WebElement.relocate() 方法。
为简化生活,请使用带有用于定位元素/元素列表的便捷方法的 Abstract BasePage 类。
【讨论】:
一段时间后,我了解到这种方法不太理想。相反,我会捕获有问题的块,使用显式等待条件,然后重新初始化正在运行的页面(initElements)。【参考方案20】:我尝试了上述许多建议,但最简单的一个有效。在我的情况下,是对 web 元素使用 @CachelookUp 导致了过时的元素异常。我猜刷新页面后,元素引用没有重新加载并且找不到元素。为元素禁用 @CachelookUp 行有效。
//Search button
@FindBy(how=How.XPATH, using =".//input[@value='Search']")
//@CachelookUp
WebElement BtnSearch;
【讨论】:
【参考方案21】:当发生过时元素异常时!!
当支持这些文本框/按钮/链接的库发生更改时,可能会发生陈旧元素异常,这意味着元素相同,但网站中的引用现在已更改,而不会影响定位器。因此,我们存储在缓存中的引用(包括库引用)现在已经过时或陈旧,因为页面已被更新的库刷新。
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”);
【讨论】:
【参考方案22】:在 Selenium 3 中,您可以使用 ExpectedConditions.refreshed(your condition)。
它将处理 StaleElementReferenceException 并重试条件。
【讨论】:
以上是关于Selenium WebDriver 如何解决陈旧元素引用异常?的主要内容,如果未能解决你的问题,请参考以下文章
如何解决Python selenium webdriver返回空白文件
如何解决 ElementNotInteractableException:元素在 Selenium webdriver 中不可见?
如何在 Selenium WebDriver Java 中选择下拉值
如何使用Selenium WebDriver处理Windows文件上传?