如何解决,陈旧元素异常?如果元素不再附加到DOM? [英] How to resolve, Stale element exception? if element is no longer attached to the DOM?

查看:196
本文介绍了如何解决,陈旧元素异常?如果元素不再附加到DOM?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have a question regarding "Element is no longer attached to the DOM".

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

I tried different solutions but they are working intermittent. Please suggest a solution that could be permanent.

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

Thanks, Anu

推荐答案

问题



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

The problem

The problem you are probably facing is that the method returns the right (and valid!) element, but when you're trying to access it a second later, it is stale and throws.

这通常在以下时间出现:

This usually arises when:


  1. 您点击异步加载新页面或至少更改它的内容。

  2. 您立即(在此之前)页面加载可以完成)搜索一个元素......你找到了它!

  3. 页面最终卸载并且新页面加载。

  4. 您尝试访问以前找到的元素,但现在它已过时,即使新页面也包含它。

  1. You click something that loads a new page asynchronously or at least changes it.
  2. You immediatelly (before the page load could finish) search for an element ... and you find it!
  3. The page finally unloads and the new one loads up.
  4. You try to access your previously found element, but now it's stale, even though the new page contains it, too.






解决方案



我知道有四种解决方法:


The solutions

There are four ways to solve it I know about:


  1. 使用正确的等待



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

  1. Use proper waits

    Use proper waits after every anticipated page-load when facing asynchronous pages. Insert an explicit wait after the initial click and wait for the new page / new content to load. Only after that you can try to search for the element you want. This should be the first thing you'll do. It will increase the robustness of your tests greatly.

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

I have been using a variant of your method for two years now (together with the technique above in solution 1) and it absolutely works most of the time and fails only on strange WebDriver bugs. Try to access the found element right after it is found (before returning from the method) via a .isDisplayed() method or something. If it throws, you already know how to search again. If it passes, you have one more (false) assurance.

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

Write a WebElement decorator that remembers how it was found and re-find it when it's accessed and throws. This obviously forces you to use custom findElement() methods that would return instances of your decorator (or, better yet, a decorated WebDriver that would return your instances from usual findElement() and findElemens() methods). Do it like this:

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

}

请注意,虽然我认为可以从通用WebElements访问 foundBy info以使这更容易,Selenium开发人员认为尝试这样的事情并选择不公开此信息是错误的。重新找到陈旧元素可能是一种不好的做法,因为你在没有任何机制检查它是否合理的情况下隐式重新找到元素。重新找到机制可能会找到一个完全不同的元素而不是同一个元素。此外,当有很多找到的元素时,它会因 findElements()而失败(您需要禁止重新查找 findElements()找到的元素,或者记住你的元素来自返回的 List 的多少钱。

Note that while I think that the foundBy info should be accessible from the generic WebElements to make this easier, Selenium developers consider it a mistake to try something like this and have chosen not to make this information public. It's arguably a bad practice to re-find on stale elements, because you're re-finding elements implicitly without any mechanism for checking whether it's justified. The re-finding mechanism could potentially find a completely different element and not the same one again. Also, it fails horribly with findElements() when there are many found elements (you either need to disallow re-finding on elements found by findElements(), or remember the how-manyeth your element was from the returned List).

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

I think it would be useful sometimes, but it's true that nobody would ever use options 1 and 2 which are obviously much better solutions for the robustness of your tests. Use them and only after you're sure you need this, go for it.

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

Implement your whole workflow in a new way!


  • 创建一个中央运行队列。让这个队列记住过去的工作。

  • 通过命令模式方式实现所有需要的任务(查找元素并单击它,查找元素并向其发送密钥等) 。调用时,将任务添加到中央队列,然后(同步或异步,无关紧要)运行它。

  • 使用 @LoadsNewPage注释每个任务 @Reversible 等等。

  • 您的大多数任务都会自行处理异常,它们应该是独立的。

  • 当队列遇到过时的元素异常时,它将从任务历史记录中取出最后一个任务并重新运行它再试一次。

  • Make a central queue of jobs to run. Make this queue remember past jobs.
  • Implement every needed task ("find an element and click it", "find an element and send keys to it" etc.) via the Command pattern way. When called, add the task to the central queue which will then (either synchronously or asynchronously, doesn't matter) run it.
  • Annotate every task with @LoadsNewPage, @Reversible etc. as needed.
  • Most of your tasks will handle their exceptions by themselves, they should be stand-alone.
  • When the queue would encounter a stale element exception, it would take the last task from the task history and re-run it to try again.

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

This would obviously take a lot of effort and if not thought through very well, could backfire soon. I used a (lot more complex and powerful) variant of this for resuming failed tests after I manually fixed the page they were on. Under some conditions (for example, on a StaleElementException), a fail would not end the test right away, but would wait (before finally time-outing after 15 seconds), popping up an informative window and giving the user an option to manually refresh the page / click the right button / fix the form / whatever. It would then re-run the failed task or even give a possibility to go some steps back in history (e.g. to the last @LoadsNewPage job).






最终淘汰赛



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


Final nitpicks

All that said, your original solution could use some polishing. You could combine the two methods into one, more general (or at least make them delegate to this one to reduce code repetition):

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

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

With Java 7, even a single multicatch block would be sufficient:

WebElement getStaleElem(By by, WebDriver driver) {
    try {
        return driver.findElement(by);
    } catch (StaleElementReferenceException | NoSuchElementException e) {
        System.out.println("Attempting to recover from " + e.getClass().getSimpleName() + "...");
        return getStaleElem(by, driver);
    }
}

这样,你可以大大减少代码量你需要维持。

This way, you can greatly reduce the amount of code you need to maintain.

这篇关于如何解决,陈旧元素异常?如果元素不再附加到DOM?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆