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

use*_*753 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++;
    }
}
}
Run Code Online (Sandbox Code Playgroud)

当我运行它时,我得到以下异常,但它可以发生

WebElement textElement = driver.findElement(By.name("createForm:dateInput_input")); 
Run Code Online (Sandbox Code Playgroud)

要么

if (button.isDisplayed())
Run Code Online (Sandbox Code Playgroud)

异常追踪

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)
Run Code Online (Sandbox Code Playgroud)

Ard*_*sco 55

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

WebElement是对DOM中元素的引用.

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

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

  • 有一个很小的可能性,你会得到一个带有java PageFactory的StaleElementReference异常,如果你不幸让一个元素在找到元素的PageFactory之间的几毫秒内变得陈旧,然后用它来做某事. (3认同)
  • 是的。在尝试再次查找元素之前,您可以使用显式等待来等待元素变得陈旧。这里要注意的关键是“你应该知道你的应用程序在做什么”。如果您不知道它何时会销毁和重新创建元素,那么您就没有编写自动化测试所需的所有信息。 (2认同)
  • @Ardesco,所以我假设 PageFactory 和 initElements 不适合这种情况,因为对元素的引用是在开始和操作期间创建的,Dom 被销毁并重新创建,引用也被销毁。那么,PageFactory 模式是否不再适合这些类型的现代 Web 应用程序呢? (2认同)
  • 使用 Java PageFactory,您不太可能看到 StaleElementReferenceException。默认情况下,每次您尝试使用该元素时,Java PageFactory 类都会再次查找该元素。该规则的例外情况是您使用 @CacheLookup 注释。在这种情况下,您将看到 StaleElementReference 异常,因为 PageFactory 将使用对该元素的缓存引用,而不是再次查找它。 (2认同)

dja*_*fan 21

这不是问题.如果将.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;
      }
    }
  };
Run Code Online (Sandbox Code Playgroud)

  • 这不是一个解决方案,你永远不会想要捕获staleElementException.目标是没有.staleElementException表示您正在执行可以修复的错误操作. (6认同)
  • 在自动更新或复杂的CSS转换的情况下,我并不总是同意什么?对于那些类型的方法可能是非常有益的,并且可能在某些情况下需要.话虽如此,大多数时候你是正确的,并且应该仔细考虑使用这种方法.我认为有一个刷新refs的方法会很好. (5认同)

eee*_*aii 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));
Run Code Online (Sandbox Code Playgroud)

此时,您确定该元素现在已过时.因此,下次引用该元素时,请再次等待,这次等待将其重新添加到DOM:

wait.until(ExpectedConditions.presenceOfElementLocated(By.id("whatever")))
Run Code Online (Sandbox Code Playgroud)


小智 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"));
    }
});
Run Code Online (Sandbox Code Playgroud)

  • 这将帮助您找到在页面最初加载时可能无法呈现的元素,它不会对StaleElementReferenceException执行任何操作. (2认同)

Nar*_*raC 8

陈旧元素的两个原因

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

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

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

如何避免/解决陈旧异常?

  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
Run Code Online (Sandbox Code Playgroud)
  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)
Run Code Online (Sandbox Code Playgroud)
  1. 将您的操作转移到JavaScript注入
 self.driver.execute_script("$(\"li:contains('Narendra')\").click()");
Run Code Online (Sandbox Code Playgroud)
  1. 主动等待元素过时
  # Wait till the element goes stale, this means the list has updated
  wait_until(lambda: is_element_stale(old_link_reference))
Run Code Online (Sandbox Code Playgroud)

这个解决方案对我有用,我在这里提到过,如果您有任何其他方案,那么对您有用,然后在下面发表评论

  • 这是最完整且最少的答案。投个赞成票吧 (3认同)

San*_*rma 4

StaleElementReferenceException是由于 findelement 方法无法访问元素造成的。

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

等待元素的可见性

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

否则使用逻辑来验证元素是否存在。

  • 等待元素可见性不会阻止您获得 StaleElementException。StaleElementException 是由您发现已过时的元素引起的。 (4认同)