如何解决,陈旧元素异常?如果元素不再附加到DOM?

时间:2013-06-18 16:47:04

标签: java selenium webdriver selenium-webdriver

我有一个关于“元素不再附加到DOM”的问题。

我尝试了不同的解决方案,但他们正在间歇性地工作。请建议一个永久性的解决方案。

WebElement getStaleElemById(String id, WebDriver driver) {
    try {
        return driver.findElement(By.id(id));
    } catch (StaleElementReferenceException e) {
        System.out.println("Attempting to recover from StaleElementReferenceException ...");
        return getStaleElemById(id, driver);
    }
}

WebElement getStaleElemByCss(String css, WebDriver driver) {
    try {
        return driver.findElement(By.cssSelector(css));
    } catch (StaleElementReferenceException e) {
        System.out.println("Attempting to recover from StaleElementReferenceException ...");
        return getStaleElemByCss(css, driver);
    } catch (NoSuchElementException ele) {
         System.out.println("Attempting to recover from NoSuchElementException ...");
         return getStaleElemByCss(css, driver);
    }
}

谢谢, ANU

7 个答案:

答案 0 :(得分:41)

问题

你可能遇到的问题是该方法返回正确的(并且有效!)元素,但是当你试图在一秒钟后访问它时,它就是陈旧的并且会抛出。

通常在以下时间出现:

  1. 您点击异步加载新页面或至少更改它的内容。
  2. 你立刻(在页面加载完成之前)搜索一个元素......然后你找到了它!
  3. 页面最终卸载,新页面加载。
  4. 您尝试访问之前找到的元素,但现在它已过时,即使新页面也包含它。

  5. 解决方案

    我知道有四种解决方法:

    1. 使用正确的等待

      在面对异步页面时,在每次预期的页面加载后使用正确的等待。在初始单击后插入显式等待并等待加载新页面/新内容。只有在那之后你才能尝试搜索你想要的元素。这应该是你要做的第一件事。它将极大地提高测试的稳健性。

    2. 你做的方式

      我已经使用了你的方法的变体两年了(连同解决方案1中的上述技术)并且它绝对适用于大多数的时间,并且仅在奇怪的WebDriver错误上失败。尝试通过.isDisplayed()方法或其他方法在找到它之后(在从方法返回之前)访问找到的元素。如果它抛出,你已经知道如何再次搜索。如果它通过,你还有一个(错误的)保证。

    3. 使用在陈旧

      时重新找到自己的WebElement

      编写一个WebElement装饰器,记住它是如何找到的,并在访问和抛出时重新找到它。这显然会迫使您使用自定义findElement()方法来返回装饰器的实例(或者更好的是,装饰WebDriver会从常规findElement()和{{1}返回您的实例} 方法)。这样做:

      findElemens()

      请注意,虽然我认为可以从通用WebElements访问public class NeverStaleWebElement implements WebElement { private WebElement element; private final WebDriver driver; private final By foundBy; public NeverStaleWebElement(WebElement element, WebDriver driver, By foundBy) { this.element = element; this.driver = driver; this.foundBy = foundBy; } @Override public void click() { try { element.click(); } catch (StaleElementReferenceException e) { // log exception // assumes implicit wait, use custom findElement() methods for custom behaviour element = driver.findElement(foundBy); // recursion, consider a conditioned loop instead click(); } } // ... similar for other methods, too } 信息以使这更容易,但Selenium开发人员认为尝试这样的事情并选择not to make this information public是错误的。重新找到陈旧元素可能是一种不好的做法,因为你在没有任何机制检查它是否合理的情况下隐式重新找到元素。重新找到机制可能会找到一个完全不同的元素,而不是相同的元素。此外,当有很多找到的元素时,它会在foundBy时失败(您需要禁止重新查找findElements()找到的元素,或者记住您的元素来自返回的{{1} 1}})。

      我认为它有时会很有用,但是没有人会使用选项1和2,这显然是更好的解决方案,可以提高测试的稳健性。使用它们,并且只有在您确定需要它之后,才能使用它们。

    4. 使用任务队列(可以重新运行过去的任务)

      以全新方式实施整个工作流程!

      • 创建一个工作的中央队列。让这个队列记住过去的工作。
      • 通过Command模式方式实现所有需要的任务(“查找元素并单击它”,“查找元素并向其发送密钥”等)。调用时,将任务添加到中央队列,然后(同步或异步,无关紧要)运行它。
      • 根据需要使用findElements()List等注释每项任务。
      • 你的大多数任务都会自己处理它们的异常,它们应该是独立的。
      • 当队列遇到过时的元素异常时,它将从任务历史记录中取出最后一个任务并重新运行它再试一次。

      这显然需要付出很多努力,如果不能很好地思考,可能会很快适得其反。在我手动修复它们所在的页面后,我使用了一个(更复杂和更强大)的变体来恢复失败的测试。在某些情况下(例如,在@LoadsNewPage上),失败不会立即结束测试,而是等待(在15秒后最终超时之前),弹出信息窗口并向用户提供手动刷新页面的选项/单击右键/修复表单/等等。然后它会重新运行失败的任务,甚至可以在历史记录中返回一些步骤(例如,到最后的@Reversible工作。)


    5. 最终的挑剔

      所有这一切,你原来的解决方案可以使用一些抛光。您可以将这两种方法合并为一种,更通用(或者至少让它们委托给这一种方法以减少代码重复):

      StaleElementException

      使用Java 7,即使是单个多块块就足够了:

      @LoadsNewPage

      这样,您可以大大减少需要维护的代码量。

答案 1 :(得分:1)

我通过1.保持陈旧元素并轮询它直到它抛出异常,然后2.等待元素再次可见来解决这个问题。

    boolean isStillOnOldPage = true;
    while (isStillOnOldPage) {
        try {
            theElement.getAttribute("whatever");
        } catch (StaleElementReferenceException e) {
            isStillOnOldPage = false;
        }
    }
    WebDriverWait wait = new WebDriverWait(driver, 15);
    wait.until(ExpectedConditions.visibilityOfElementLocated(By.id("theElementId")));

答案 2 :(得分:0)

如果您尝试点击链接,则会转到新页面。之后导航回来并点击其他链接。他们下面的代码可以帮助你。

public int getNumberOfElementsFound(By by) {
    return  driver.findElements(by).size();
  }

public WebElement getElementWithIndex(By by, int pos) {
    return driver.findElements(by).get(pos);
  }

/**click on each link */
public void getLinks()throws Exception{
try {
List<WebElement> componentList = driver.findElements(By.tagName("a"));
System.out.println(componentList.size()); 

    for (WebElement component : componentList)
    {
        //click1();
        System.out.println(component.getAttribute("href"));
    }
 int numberOfElementsFound = getNumberOfElementsFound(By.tagName("a"));
for (int pos = 0; pos < numberOfElementsFound; pos++) {
     if (getElementWithIndex(By.tagName("a"), pos).isDisplayed()){

  getElementWithIndex(By.tagName("a"), pos).click();
  Thread.sleep(200);
  driver.navigate().back();
  Thread.sleep(200);                                                       
}
  }
    }catch (Exception e){
        System.out.println("error in getLinks "+e);
    }
}

答案 3 :(得分:0)

解决问题的解决方案:

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

          此解决方案对我有用

答案 4 :(得分:0)

发生陈旧元素异常时!!

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

for(int j=0; j<5;j++)
try {
    WebElement elementName=driver.findElement(By.xpath(“somexpath”));
    break;
} catch(StaleElementReferenceException e){
e.toString();
System.out.println(“Stale element error, trying ::  ” + e.getMessage());
}
elementName.sendKeys(“xyz”);

答案 5 :(得分:0)

对于Fitnesse,您可以使用:

|开始|智能Web驱动程序| selenium.properties |

@Fixture(名称=“智能Web驱动程序”) 公共类SmartWebDriver扩展了SlimWebDriver {

private final static Logger LOG = LoggerFactory.getLogger(SmartWebDriver.class);

/**
 * Constructs a new SmartWebDriver.
 */
@Start(name = "Start Smart Web Driver", arguments = {"configuration"}, example = "|start |Smart Web Driver| selenium.properties|")
public SmartWebDriver(String configuration) {
    super(configuration);
}

/**
 * Waits for an element to become invisible (meaning visible and width and height != 0).
 *
 * @param locator the locator to use to find the element.
 */
@Command(name = "smartWaitForNotVisible", arguments = {"locator"}, example = "|smartWaitForNotVisible; |//path/to/input (of css=, id=, name=, classname=, link=, partiallink=)|")
public boolean smartWaitForNotVisible(String locator) {
    try {
        waitForNotVisible(locator);
    } catch (StaleElementReferenceException sere) {
        LOG.info("Element with locator '%s' did not become invisible (visible but also width and height != 0), a StaleElementReferenceException occurred, trying to continue...", locator);
    } catch (NoSuchElementException ele) {
        LOG.info("Element with locator '%s' did not become invisible (visible but also width and height != 0), a NoSuchElementException occurred, trying to continue...", locator);
    } catch (AssertionError ae) {
        if (ae.getMessage().contains("No element found")) {
            LOG.info("Element with locator '%s' did not become invisible (visible but also width and height != 0), a AssertionError occurred, trying to continue...", locator);
        } else {
            throw ae;
        }
    }
    return true;
}

}

答案 6 :(得分:-2)

https://www.swtestacademy.com/selenium-wait-javascript-angular-ajax/这是一篇有关动态服务员策略的好文章。 您的问题是没有正确等待所有的ajax,jquery或angular调用。 然后,您最终遇到StaleElementException。

如果您的方法是使用Try-Catch机制,那么我想它有一个缺陷。您不应该依赖该结构,因为您永远不会知道它会在catch子句中起作用。

Selenium使您有机会进行javascript调用。 您可以执行

  • “返回jQuery.active == 0”
  • 返回 angular.element(document).injector()。get('$ http')。pendingRequests.length === 0“
  • “返回document.readyState”
  • “ return angular.element(document).injector()===未定义”

命令只是检查这些调用的存在和状态。

您可以在执行任何findBy操作之前执行此操作,以便始终使用最新页面