我正在使用Java实现许多Selenium测试。有时,我的测试由于StaleElementReferenceException
而失败。您能否提出一些方法来使测试更稳定?
答案 0 :(得分:60)
如果页面上发生的DOM操作暂时导致元素无法访问,则会发生这种情况。为了允许这些情况,您可以尝试在循环中多次访问该元素,然后再抛出异常。
尝试this excellent solution from darrelgrainger.blogspot.com:
public boolean retryingFindClick(By by) {
boolean result = false;
int attempts = 0;
while(attempts < 2) {
try {
driver.findElement(by).click();
result = true;
break;
} catch(StaleElementException e) {
}
attempts++;
}
return result;
}
答案 1 :(得分:56)
我间歇性地遇到这个问题。我不知道,BackboneJS正在页面上运行并替换我试图点击的元素。我的代码看起来像这样。
driver.findElement(By.id("checkoutLink")).click();
当然,这在功能上与此相同。
WebElement checkoutLink = driver.findElement(By.id("checkoutLink"));
checkoutLink.click();
偶尔会发生javascript会在查找和点击它之间替换checkoutLink元素,即。
WebElement checkoutLink = driver.findElement(By.id("checkoutLink"));
// javascript replaces checkoutLink
checkoutLink.click();
尝试单击链接时,正确导致了StaleElementReferenceException。我无法找到任何可靠的方法来告诉WebDriver等到javascript运行完毕,所以这就是我最终如何解决它。
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;
}
});
此代码将不断尝试单击该链接,忽略StaleElementReferenceExceptions,直到单击成功或达到超时。我喜欢这个解决方案,因为它可以节省您编写任何重试逻辑,并且只使用WebDriver的内置构造。
答案 2 :(得分:13)
通常这是因为DOM正在更新并且您尝试访问更新的/新元素 - 但DOM已刷新,因此它是您的无效引用..
首先在元素上使用显式等待来确保更新完成,然后再次获取对该元素的新引用。
这里有一些伪代码来说明(改编自我用于完全这个问题的一些C#代码):
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.
希望这有帮助!
答案 3 :(得分:10)
Kenny的解决方案很好,但它可以用更优雅的方式编写
new WebDriverWait(driver, timeout)
.ignoring(StaleElementReferenceException.class)
.until((WebDriver d) -> {
d.findElement(By.id("checkoutLink")).click();
return true;
});
或者:
new WebDriverWait(driver, timeout).ignoring(StaleElementReferenceException.class).until(ExpectedConditions.elementToBeClickable(By.id("checkoutLink")));
driver.findElement(By.id("checkoutLink")).click();
但无论如何,最好的解决方案是依靠Selenide库,它处理这类事情等等。 (而不是元素引用它处理代理,所以你永远不必处理过时的元素,这可能是非常困难的)。 Selenide
答案 4 :(得分:6)
StaleElementReferenceException
发生的原因已经确定:在查找和使用元素执行某些操作之间更新DOM。
对于点击问题,我最近使用了这样的解决方案:
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();
}
关键部分是&#34;链接&#34;通过ExpectedConditions
获得Selenium自己的ExpectedConditions.refreshed()
。这实际上等待并检查在指定的超时期间是否刷新了有问题的元素,并另外等待元素变为可点击。
答案 5 :(得分:2)
在我的项目中,我介绍了StableWebElement的概念。它是WebElement的包装器,它能够检测元素是否为Stale并找到对原始元素的新引用。我添加了一个辅助方法来定位返回StableWebElement而不是WebElement的元素,并且StaleElementReference的问题消失了。
public static IStableWebElement FindStableElement(this ISearchContext context, By by)
{
var element = context.FindElement(by);
return new StableWebElement(context, element, by, SearchApproachType.First);
}
C#中的代码可以在我的项目页面上找到,但可以轻松移植到java https://github.com/cezarypiatek/Tellurium/blob/master/Src/MvcPages/SeleniumUtils/StableWebElement.cs
答案 6 :(得分:1)
C#中的解决方案是:
助手类:
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;
}
}
}
调用:
DriverHelper driverHelper = new DriverHelper("http://www.seleniumhq.org/docs/04_webdriver_advanced.jsp", 10);
driverHelper.ClickElement("input[value='csharp']:first-child");
同样可以用于Java。
答案 7 :(得分:1)
不赞成使用肯尼的解决方案,我正在使用动作类双击,但是您可以做任何事情。
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;
}
});
答案 8 :(得分:0)
这适用于我(100%工作)使用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;
}
答案 9 :(得分:0)
问题在于,当您将元素从Javascript传递回Java时,它可能已经离开了DOM。
尝试使用Javascript完成整个操作:
driver.executeScript("document.querySelector('#my_id').click()")
答案 10 :(得分:0)
尝试一下
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
}
}
答案 11 :(得分:0)
我找到了解决方法here。就我而言,如果离开当前窗口,选项卡或页面并再次返回,则元素不可访问。
.ignoring(StaleElement ...)、. refreshed(...)和elementToBeClicable(...)没有帮助,我在act.doubleClick(element).build().perform();
字符串上遇到异常。
在我的主要测试课程中使用函数:
openForm(someXpath);
我的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;
}
}
我的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))));
}
答案 12 :(得分:0)
可能存在导致StaleElementReferenceException的潜在问题,到目前为止(在操作方面)没有人提及。
我用Javascript进行了解释,但是在Java中是相同的。
这行不通:
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()
但是再次实例化动作将解决该问题:
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()
答案 13 :(得分:0)
findByAndroidId
处理StaleElementReference
的方法。这很大程度上基于jspcal's answer,但是我不得不修改该答案以使其与我们的设置完全兼容,因此我想在此处添加它,以防对他人有所帮助。如果这个答案对您有帮助,请升级jspcal's answer。
// 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.
}
}
}
答案 14 :(得分:0)
通常,当我们尝试访问的元素出现了StaleElementReferenceException,但是其他元素可能会影响我们所感兴趣的元素的位置,因此,当我们尝试单击或getText或尝试对WebElement进行操作时,我们会得到异常,通常表示元素不附有DOM。
我尝试的解决方案如下:
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));
}
在上面的代码中,我首先尝试等待,然后在发生异常的情况下单击元素,然后捕获它并尝试将其循环,因为有可能仍然无法加载所有元素并再次发生异常。
答案 15 :(得分:-4)
也许它最近被添加,但其他答案未提及Selenium的隐式等待功能,它为您完成上述所有操作,并内置于Selenium中。
SELECT Cast(Format(Sum(COALESCE(InstalledSubtotal, 0)), 'F') AS MONEY) AS TotalSoldNet,
BP.BoundProjectId AS ProjectId
FROM BoundProducts BP
WHERE ( BP.IsDeleted IS NULL
OR BP.IsDeleted = 0 )
GROUP BY BP.BoundProjectId
这将重试driver.manage().timeouts().implicitlyWait(10,TimeUnit.SECONDS);
次调用,直到找到元素,或者10秒钟。
来源 - http://www.seleniumhq.org/docs/04_webdriver_advanced.jsp