我怎样才能 re-write 这个方法给我一个精确的 xpath 或定位器? (避免陈旧元素异常)

How can I re-write this method to give me a precise xpath or locator? (Avoiding Stale Element Exception)

我使用 GEB 和 selenium 已经有一段时间了,很多时候我 运行 陷入可怕的陈旧元素异常,因为我必须动态测试其中一个页面,从而导致陈旧元素异常。

我已经非常接近为陈旧元素异常创建一个包罗万象的解决方案,但可惜还不够接近,这就是我需要帮助的原因。

我的解决方案是覆盖 GEB 附带的 NonEmptyNavigator class。我将以我的 click() 方法为例:

class NonEmptyNavigator extends geb.navigator.NonEmptyNavigator {
    def NonEmptyNavigator() {
        super()
    }


    NonEmptyNavigator(Browser browser, Collection<? extends WebElement> contextElements) {
        super(browser, contextElements)
    }

    //overridden click method (all of the methods are overridden though
    Navigator click(count = 0){
        if (count >= 60) {
            return super.click()
        }
        else{
            try{
                return super.click()
            }
            catch (StaleElementReferenceException s) {
                def oData = this.toString()
                def matcher = Pattern.compile("-> ([^:]+): (.*)]").matcher(oData) //Parses out the xPath
                matcher.find() //Again more Parsing 
                def newXpath = matcher.group(2) //final Parsing step
                newNav = browser.$(By.xpath(newXpath)) //create a new NonEmptyNavigator from the Stale Navigator's xpath 
                return newNav.click(count + 1) //attempt to click the new Navigator
            }
        }
    }
}

现在您可能会想 "Wow this is a really good solution"(确实如此),但在某些情况下这不起作用,我不确定如何克服。我举个例子。

如果我这样做(为便于阅读而简化):

class SomePage extends Page{
    static content = {
        table(required: false) {$(By.xpath("//table/tbody"))}
    }

    //assume this method gets called in a test script
    def someMethod(){
        table.click() //assume this throws a StaleElementException
    }
}

引用上面我重写的方法,oData.toString() 最终是这样的:“[[[ChromeDriver: chrome on XP (2cd0a7132456fa2c71d1f798ef32c234)] -> xpath: //table/tbody]]"

如您所见,我能够提取 xpath 并创建一个新的导航器 object,这很棒。

我 运行 遇到问题的地方是遇到这样的情况:

class SomePage extends Page{
    static content = {
        table(required: false) {$(By.xpath("//table/tbody"))}
    }

    //assume this method gets called in a test script
    def someMethod(){
        table.children().getAt(1).children().getAt(2).click() //assume this throws a StaleElementException
    }
}

当执行 click() 抛出陈旧元素时,oData.toString() 显示如下: “[[[[[ChromeDriver: chrome on XP (2cd0a7132456fa2c71d1f798ef32c234)] -> xpath: //table/tbody] -> xpath: child::*]] -> xpath: child::*]]"

如您所见,有一些信息显示我目前正在尝试访问 child 节点的 child,但我不再拥有需要重新定义该特定元素的引用.我没有我想要的具体child(或children)的索引。

我想知道在我当前的框架下是否有任何方法可以获得该信息。我也愿意接受其他想法和建议。

总而言之,我主要是想为 StaleElementException 创建一个包罗万象的解决方案。我想我已经很接近了,需要一点点推动才能度过最后的难关。

我能够自己解决这个问题。现在我不再收到 StaleElementReferenceException。我对 NonEmptyNavigator 和 EmptyNavigator classes 做了更多的覆盖。我添加了一个名为 children 的自定义 ArrayList 字段。每当调用 getAt() 时,被访问的子项的索引都存储在子数组中。所有后续调用都将传递子数组 "down the chain" 以便在出现陈旧元素时以及如果出现陈旧元素时可以使用索引。下面我将向您展示我的代码。保存 space 我只显示了点击方法,但我最终覆盖了这个 class.

中的大部分方法
class NonEmptyNavigator extends geb.navigator.NonEmptyNavigator {

    public children = [];

    def NonEmptyNavigator() {
        super()
    }


    NonEmptyNavigator(Browser browser, Collection<? extends WebElement> contextElements) {
        super(browser, contextElements)
    }

    def ogClick(){
        ensureContainsSingleElement("click")
        contextElements.first().click()
        this
    }

    NonEmptyNavigator click(count=0) {
        if (count >= 60) {
            return ogClick()
        } else {
            try {
                return ogClick()
            }
            catch (StaleElementReferenceException s) {
                println("Click StaleElement was caught this many times = ${count + 1}")
                def oData = this.toString()
                println("attempting to parse this string's xpath")
                println(oData)
                def matcher = Pattern.compile("-> ([^:]+): (.*)]").matcher(oData);
                matcher.find()
                def orgXpath = matcher.group(2)
                def type = matcher.group(1)
                println("original XPath")
                println(orgXpath)
                def newNav
                def numberOfChildren = StringUtils.countMatches(orgXpath, "-> xpath: child::*")
                if(!(numberOfChildren>0)){
                    try{
                        if (type=="css") {
                            newNav = (NonEmptyNavigator) browser.$(orgXpath)
                            newNav.children.addAll(this.children)
                            return newNav.click(count + 1)

                        } else if (type=="xpath") {
                            newNav = (NonEmptyNavigator) browser.$(By.xpath(orgXpath))
                            newNav.children.addAll(this.children)
                            return newNav.click(count + 1)
                        } else {
                            return ogClick()
                        }
                    }
                    catch(Throwable t){
                        println("Unable to create new navigator from the stale element")
                        return ogClick()
                    }
                }
                else{
                    println("this had a child")
                    println("number of children on record: ${children.size()}")
                    def newXpath = orgXpath.substring(0, orgXpath.indexOf("]]"))
                    children.each{
                        newXpath = newXpath + "/child::*[${it+1}]"
                    }
                    println("New Xpath here")
                    println(newXpath)

                    newNav = browser.$(By.xpath(newXpath))
                    if(!newNav.isEmpty()){
                        newNav = (NonEmptyNavigator) newNav
                    }
                    else{
                        newNav = (EmptyNavigator) newNav
                    }
                    newNav.children.addAll(this.children)
                    return newNav.click(count + 1)
                }
            }
            catch (Throwable t) {
                def loseOfConnection = $(By.xpath("<REDACTED>"))
                def reconnect = $(By.xpath("<REDACTED>"))
                if(loseOfConnection.displayed||reconnect.displayed){
                    println("Loss Of Connection waiting ${count} out of 60 seconds to regain connection")
                    Thread.sleep(1000)
                    return this.click(count+1)
                }
                else{
                    return ogClick()
                }
            }
        }
    }

    NonEmptyNavigator addChild(index){
        println("a child was stored")
        this.children << index
        return this
    }

    NonEmptyNavigator getAt(int index){
        println("getAt was called")
        this.navigatorFor(Collections.singleton(getElement(index))).addChild(index)
    }

    NonEmptyNavigator navigatorFor(Collection<WebElement> contextElements) {
        println("navigateFor was called")
        def answer = browser.navigatorFactory.createFromWebElements(contextElements)
        if(answer.isEmpty()){
            answer = (EmptyNavigator) answer
        }
        else{
            answer = (NonEmptyNavigator) answer
        }
        answer.children.addAll(this.children)
        return answer
    }
}

如果您愿意,我相信这是抑制 StaleElementReferenceException 的最佳方法。大多数人会说不应该抑制异常,但我确信在这种情况下我不关心异常,这就是阻止它结束测试的方法。希望你喜欢。