16

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

谢谢, 阿努

4

7 回答 7

45

问题

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

这通常出现在以下情况:

  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
    
    }
    

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

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

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

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

    • 制作要运行的作业的中央队列。让这个队列记住过去的工作。
    • 通过命令模式的方式实现每个需要的任务(“找到一个元素并单击它”、“找到一个元素并将键发送给它”等)。调用时,将任务添加到中央队列,然后(同步或异步,无关紧要)运行它。
    • 根据需要用 等@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);
    }
}

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

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

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

于 2013-06-18T18:14:02.443 回答
2

我通过以下方式解决此问题: 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")));
于 2014-01-26T17:45:56.403 回答
1

如果您尝试单击链接,则会将您带到新页面。之后返回并单击其他链接。他们下面的代码可能会对您有所帮助。

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);
    }
}
于 2013-06-19T05:16:34.757 回答
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”);
于 2018-06-25T14:01:23.650 回答
0

对于 Fitnesse,您可以使用:

|开始 |智能网络驱动程序| 硒.属性|

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

}

于 2018-09-06T10:34:07.217 回答
0

解决方案:

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

这个解决方案对我有用

于 2016-07-15T11:13:30.110 回答
-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"
  • “返回文档.readyState”
  • “返回 angular.element(document).injector() === undefined”

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

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

于 2018-08-24T11:06:58.600 回答