Geb的一般问题(StaleElementReferenceException& Wait Timeouts)

时间:2014-03-27 09:32:00

标签: groovy exception-handling geb staleobjectstate

根据" Geb"我开始映射门户网站的网页。我更喜欢使用静态内容闭包块中定义的变量,然后在页面方法中访问它们:

static content = {
    buttonSend { $("input", type: "submit", nicetitle: "Senden") }
}
def sendLetter() {
    waitFor { buttonSend.isDisplayed() }
    buttonSend.click()
}

不幸的是,有时候我会得到Geb等待超时异常(60秒后)或更糟糕的是我收到了众所周知的" StaleElementReferenceException"。

使用" isEnabled"我可以避免等待超时而不是" isDisplayed"但对于" StaleElementReferenceException"我只能应用以下解决方案:

def sendLetter() {
    waitFor { buttonSend.isEnabled() }
    try {
        buttonSend.click()
    } catch (StaleElementReferenceException e) {
        log.info(e.getMessage())
        buttonSend.click()
    }
}

我想,这个解决方案并不是很好但是我无法应用另一篇文章中描述的显式等待。因此,我有一些一般性的问题:

  • 当页面是动态的时候,我应该避免使用静态内容定义吗?
  • Geb在什么时间或事件中刷新了它的DOM?如何触发DOM刷新?
  • 为什么我仍然得到" StaleElementReferenceException"什么时候使用CSS选择器?

我很感激每一个有助于理解或解决这个问题的提示。最好的方法是拥有一个简单的代码示例,因为我还是初学者。谢谢!

3 个答案:

答案 0 :(得分:6)

如果您在页面类中定义了一个检查,页面将首先验证该条件并等待前n秒。这是在您的gebConfig文件中分配的。默认值为30秒。

static at = {
    waitFor { buttonSend.isDisplayed() }
}

因此,一旦您通过测试将您的页面调用为'方法或者您正在使用的任何方法,页面将等待然后执行您的页面操作。

to MyPage
buttonSend.click()

当页面是动态的时候,我应该避免使用静态内容定义吗?

  

没有。实际上,静态定义是闭包。那是什么   实际发生的是每次你使用页面静态   你正在调用一个动态运行的闭包的组件   当前页面(webElements的集合)。了解这一点至关重要   使用Geb并发现您将遇到的问题。

Geb在什么时间或事件中刷新了它的DOM?如何触发DOM刷新?

  

当您致电:to,go,at,click,withFrame(frame,page),withWindow   和浏览器驱动方法一样,它会刷新当前的一组   WebElements。 Geb有一个很好的收集用于切换   页面之间和等待页面操作很容易。注意:Geb是   实际建立在WebDriver WebElements上。

为什么在使用CSS选择器时仍然会出现“StaleElementReferenceException”?

  

页面可能尚未完成加载,已被操纵   使用ajax调用或以其他方式刷新。有时一个   'at'PAGE方法调用可以解决这些问题。它们对我来说最重要   使用框架时常见的Geb似乎在页面之间变得混乱   和框架一点点。有解决方法。

简而言之,如果您使用页面模式,您可以使用您使用以下内容静态内容,at和url闭包定义的Page类轻松切换预期页面:

  • 到(页)
  • 在(页)
  • Navigator.click(页)
  • withFrame(frame,Page){}

答案 1 :(得分:4)

除了twinj的回答之外,我还想指出一些其他的解决方法,以防您遇到StaleElementReferenceException。

  1. 我经常发现最好手动写出你的选择器,而不是依赖于页面中定义的内容。即使您的页面内容不应由default缓存,但他们仍然设法有时会离开我。在处理动态内容或迭代时,这尤其普遍。

    例如:我们想要点击动态创建的下拉列表中的元素。

    通常你可能想要做......

    static content = {
         dropdown { $("#parentDiv").find("ul") }
    }
    
    void clickDesiredElement(String elementName) {
         dropdown.click()
         def desiredElement = dropdown.find("li", text:elementName)
         waitFor { desiredElement.displayed }
         desiredElement.click()
    }
    

    如果这不起作用,请尝试完全删除内容,然后手动写出选择器...

    void clickDesiredElement(String elementName) {
         $("#parentDiv").find("ul").click()
         def desiredElement = $("#parentDiv").find("ul").find("li", text:elementName)
         waitFor { desiredElement.displayed }
         desiredElement.click()
    }
    

    在非常讨厌的情况下,您可能必须使用手动计时器,如this answer中所述,您的代码可能如下所示......

    void clickDesiredElement(String elementName) {
         $("#parentDiv").find("ul").click()
         sleepForNSeconds(2)
         def desiredElement = $("#parentDiv").find("ul").find("li", text:elementName)
         waitFor { desiredElement.displayed }
         desiredElement.click()
    }
    

    请记住,这是一种解决方法:)

  2. 对于大型迭代和方便的闭包方法,例如每个{}或collect {},您可能希望在每次迭代中添加waitFor {}。

    Ex:我们想说我们想得到一张大表的所有行

    通常你可能想要做......

    def rows = $("#table1").find("tr").collect {
       [
          name: it.find("td",0),
          email: it.find("td",1)
       ]
    }
    

    有时我发现自己必须迭代地执行此操作,并在每次迭代之间使用waitFor {}以避免StaleElementReferentException。它可能看起来像这样......

    def rows = []
    int numRows = $("#table1").find("tr").size()
    int i
    for(i=0; i < numRows; i++) {
        waitFor {
          def row = $("#table1").find("tr",i)
          rows << [
              name: row.find("td",0),
              email: row.find("td",1)
          ]
        }
    }
    

答案 2 :(得分:0)

我发现,动态加载时丢失的是导航器。 我通过使用以下代码重新初始化页面或模块来本地解决此问题:

void waitForDynamically(Double timeout = 20, Closure closure) {
        closure.resolveStrategy = Closure.DELEGATE_FIRST
        switch (this) {
            case Module:
                init(browser, browser.navigatorFactory)
                break
            case Page:
                init(browser)
                break
            default:
                throw new UnsupportedOperationException()
        }

        waitFor {
            closure()
        }
    }