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

查看:35
本文介绍了如何解决,陈旧元素异常?如果元素不再附加到 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);
    }
}

谢谢,阿努

推荐答案

问题

您可能面临的问题是该方法返回了正确(且有效!)的元素,但是当您稍后尝试访问它时,它已过时并抛出.

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.

这通常发生在:

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

<小时>

解决方案

我知道有四种解决方法:


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 信息以使其更容易,但 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 等注释每个任务.
  • 您的大部分任务都会自行处理异常,它们应该是独立的.
  • 当队列遇到陈旧元素异常时,它会从任务历史记录中取出最后一个任务并重新运行它以重试.

这显然需要很多努力,如果没有好好考虑,很快就会适得其反.在我手动修复它们所在的页面后,我使用了一个(更复杂和强大的)变体来恢复失败的测试.在某些情况下(例如,在 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,即使是一个 multicatch 块也足够了:

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天全站免登陆