Selenium WebDriver如何解决过时的元素参考异常?

时间:2013-04-23 09:52:34

标签: java selenium webdriver selenium-webdriver

我在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)

22 个答案:

答案 0 :(得分:53)

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

WebElement是对DOM中元素的引用。

当您正在交互的元素被销毁然后重新创建时,抛出StaleElementException。如今,大多数复杂的网页都会在用户与之交互时动态移动,这需要销毁和重新创建DOM中的元素。

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

答案 1 :(得分:19)

这不是问题。如果将.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;
      }
    }
  };

答案 2 :(得分:17)

我发生的事情是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")))

答案 3 :(得分:9)

尝试等待这样的元素:

// 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"));
    }
});

答案 4 :(得分:8)

陈旧元素的两个原因

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

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

  3. 当您尝试与staled WebElement [任何上述情况]进行交互时,将抛出StaleElementException。

    如何避免/解决陈旧例外?

    1. 将定位器存储到元素而不是引用
    2. 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库中的钩子
      2.    # 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注入
        2.  self.driver.execute_script("$(\"li:contains('Narendra')\").click()");
          
          1. 主动等待元素过时
          2.   # Wait till the element goes stale, this means the list has updated
              wait_until(lambda: is_element_stale(old_link_reference))
            

            此解决方案对我有用,如果您有任何其他方案,我已在此处提及,这对您有用,然后在下面发表评论

答案 5 :(得分:4)

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

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

等待元素的可见性

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

或者使用this逻辑来验证元素是否存在。

答案 6 :(得分:2)

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

在调试时,客户端没有像运行单元测试或maven构建那样快。 这意味着在调试模式下,客户端有更多时间来准备元素,但是如果构建运行相同的代码,那么他的速度要快得多,并且您所寻找的WebElement可能在页面的DOM中不可见。

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

例如:

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

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

答案 7 :(得分:1)

发生陈旧元素异常时!!

当支持这些文本框/按钮/链接的库发生更改时,可能会发生过时的元素异常,这意味着元素相同,但现在网站中的引用已更改,而不会影响定位符。因此,由于页面已使用更新的库刷新,因此存储在缓存中的参考(包括库参考)现在变得过时或过时。

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

答案 8 :(得分:1)

我建议不要将@CachelookUp用于StaleElementReferenceException的Selenium WebDriver。

如果您使用@FindBy注释并拥有@CacheLookUp,请将其注释并检查。

答案 9 :(得分:1)

我已使用以下代码解决了这个问题。

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

答案 10 :(得分:0)

在 Selenium 3 中,您可以使用 ExpectedConditions.refreshed(your condition)

它将处理 StaleElementReferenceException 并重试条件。

答案 11 :(得分:0)

我尝试了很多上述建议,但最简单的建议。在我的例子中,使用@CachelookUp作为web元素导致了陈旧的元素异常。我想刷新页面后,元素引用没有重新加载,也找不到元素。禁用@CachelookUp行的元素工作。

$( "#page_shop" ).load( "http://localhost/test/shop.php" );

答案 12 :(得分:0)

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

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等待是否会处理StaleElement异常。如果没有,你可以使用流畅的等待,就像在DjangoFan的回答中一样。但我显示的原则将起作用(即使与WebDriverWait的特定行爆炸。

所以tldr;

  1. 使用定位器,以及WebDriverWait / Fluent的组合自己等待/重定位元素,因此如果元素过时,您可以重新定位它而无需在@FindBy中复制定位器(对于pagefactory初始化元素) ),因为没有WebElement.relocate()方法。
  2. 为了简化生活,请使用一个Abstract BasePage类,其中包含用于查找元素/元素列表的便捷方法。

答案 13 :(得分:0)

只需下载新的Chrome扩展程序并使用selenium server 3即可正常使用。

答案 14 :(得分:0)

在我的情况下,这个错误是由于我在

之外定义ActionChains元素的事实引起的
def parse(self, response):

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

不起作用:

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

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

使用:

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

答案 15 :(得分:0)

在对问题进行深入调查后,我发现选择仅为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”会产生错误,选择表单... 可以正常工作。

答案 16 :(得分:0)

这个解决方案对我来说很好:

Adding error handling function and try again

var x >= 0  ;
var y  >= 0;

minimize cost : x^2 + y^4;

subject to constraint1 : x+2*y >= 100;

subject to constraint1 : x+2*y <= 10;

---------------------------------

"constraint1 is already defined"

答案 17 :(得分:0)

参考@djangofan给出的答案,看起来可行的解决方案是将代码保留在try catch块内,以便发生可能的Staleness。当我使用下面的代码时,我没有随时得到问题。

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),但过时异常仍会间歇性地抛出。

希望这个解决方案有所帮助。

答案 18 :(得分:0)

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

答案 19 :(得分:0)

如果我们不确定答案,请不要在别人之间混淆。这对最终用户来说非常令人沮丧。简单而简短的答案是 在webdriver中使用@CacheLookup注释。请参考以下链接。 How does @CacheLookup work in WebDriver?

答案 20 :(得分:0)

WebDriver必须等到元素找到并且10秒后超时。

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

答案 21 :(得分:0)

这对我有用(来源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;
                }
            }
        }
    }