3

根据“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 刷新?
  • 为什么在使用 CSS 选择器时仍然会收到“StaleElementReferenceException”?

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

4

3 回答 3

6

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

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

因此,一旦您通过测试调用您的页面“to”方法,或者您在页面上使用它的任何方法,都会等待然后执行您的页面操作。

to MyPage
buttonSend.click()

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

不。实际上,静态定义是闭包的。因此,实际发生的情况是,每次您使用 Pages 静态组件时,您都在调用一个在当前页面(webElements 集合)上动态运行的闭包。了解这一点是使用 Geb 并发现您将遇到的问题的关键。

Geb 在什么时间或事件刷新其 DOM?如何触发 DOM 刷新?

当您调用:to、go、at、click、withFrame(frame, page)、withWindow 和浏览器驱动方法时,它将刷新当前的 WebElement 集。Geb 有一个很好的实用程序集合,可以轻松地在页面之间切换和等待页面操作。注意: Geb 实际上是基于WebDriver WebElements构建的。

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

页面可能尚未完成加载,已通过 ajax 调用进行操作或已以其他方式刷新。有时,'at' PAGE 方法调用可以解决这些问题。对我来说,它们在使用框架时最常见,因为 Geb 似乎在页面和框架之间有点混淆了。有解决方法。

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

  • 到(页)
  • 在(页)
  • Navigator.click(页面)
  • withFrame(框架,页面){}
于 2014-06-24T07:54:52.857 回答
4

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

  1. 很多时候我发现手动写出你的选择器比依赖页面中定义的内容更好。即使默认情况下不应该缓存您的页面内容,它们仍然有时会从我身边溜走。这在处理动态内容或迭代时尤其普遍。

    例如:假设我们要从动态创建的下拉列表中单击一个元素。

    通常,您可能想做类似...

    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. 对于大型迭代和方便的闭包方法,例如 each{} 或 collect{},您可能希望在每次迭代中添加一个 waitFor{}。

    例如:假设我们想要获取一个大表的所有行

    通常,您可能想做类似...

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

    有时我发现自己必须迭代地执行此操作,并且每次迭代之间都需要等待{},以避免 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)
          ]
        }
    }
    
于 2015-07-09T15:07:57.213 回答
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()
        }
    }
于 2018-08-31T13:25:37.447 回答