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

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

谢谢,Anu

Pet*_*ček 41

问题

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

这通常出现在:

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

解决方案

我知道有四种解决方法:

  1. 使用适当的等待

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

  2. 你这样做的方式

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

  3. 使用在陈旧时重新找到自己的WebElement

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

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

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

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

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

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

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

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


最后的挑剔

总而言之,您的原始解决方案可以使用一些抛光.您可以将这两种方法合并为一种,更通用(或者至少使它们委托给这一种方法以减少代码重复):

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

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

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

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