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

我有一个关于“元素不再附加到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

问题

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

这通常发生在:

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

解决方案

我知道有四种解决方法:

  1. 使用适当的等待

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

  2. 你这样做的方式

    我已经使用了你的方法的变体两年了(与解决方案1中的上述技术一起使用)并且它绝大部分时间都可以工作,并且仅在奇怪的WebDriver错误上失败。 尝试通过.isDisplayed()方法之后找到找到的元素(在从方法返回之前.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 } 

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

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

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

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

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

    这显然需要付出很多努力,如果不能很好地思考,可能会很快适得其反。 在我手动修复它们所在的页面后,我使用了一个(更复杂和更强大)的变体来恢复失败的测试。 在某些情况下(例如,在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); } } 

使用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); } } 

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

我通过以下方法解决这个问题:1。保持陈旧元素并轮询它直到它抛出exception,然后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"))); 

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

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

解决它们的解决方案:

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

这个解决方案对我有用

当发生陈旧元素exception时!!

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

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

对于Fitnesse,您可以使用:

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

@Fixture(name =“Smart Web Driver”)公共类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; } 

}

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

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

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

  • “return jQuery.active == 0”
  • return angular.element(document).injector()。get(’$ http’)。pendingRequests.length === 0“
  • “return document.readyState”
  • “return angular.element(document).injector()=== undefined”

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

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