如何重新编写此方法以给我一个精确的xpath或定位器? (避免陈旧元素异常)

时间:2017-01-05 20:59:20

标签: selenium xpath groovy webdriver geb

我一直在使用GEB和selenium一段时间了,很多时候我遇到了可怕的陈旧元素异常,因为我必须动态测试其中一个页面加载,从而导致过时的元素异常。

我已经非常接近为陈旧的元素异常创建一个捕获所有解决方案但是唉不够接近这就是我需要帮助的原因。

我的解决方案是覆盖GEB附带的NonEmptyNavigator类。我将以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
            }
        }
    }
}

现在你可能会想“哇这是一个非常好的解决方案”(并且确实如此)但是有些情况下这不起作用,而且我不确定如何克服。让我举个例子。

如果我这样做(简化了可读性):

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)] - &gt; xpath:// table / tbody]]“

你可以看到我能够提取xpath并创建一个很棒的新导航器对象。

遇到问题的时候遇到这样的情况:

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)] - &gt; xpath:// table / tbody]] - &gt; xpath:child :: *]] - &gt; xpath:child :: *]]”

正如您所看到的,有一些信息显示我正在尝试访问子节点的子节点,但我不再需要重新定义该特定元素的引用。我没有我想要的特定孩子(或孩子)的索引。

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

总而言之,我基本上希望创建一个捕获StaleElementException的所有解决方案。我认为我非常接近,需要一点点轻推才能克服最后的驼峰。

1 个答案:

答案 0 :(得分:0)

我能够自己解决这个问题。现在我不再获得StaleElementReferenceException。我做了一些更重要的NonEmptyNavigator和EmptyNavigator类。我添加了一个名为children的自定义ArrayList字段。每当调用getAt()时,被访问子的索引都存储在children数组中。所有后续调用都将传递子数组&#34;沿链#34;这样,当陈旧元素出现时,可以使用索引。我会告诉你我的代码。为了节省空间我只显示了单击方法,但我最终覆盖了这个类中的大部分方法。

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的最佳方法。大多数人会说Exception不应该被抑制,但我知道在这种情况下我不关心Exception,这是如何阻止它结束你的测试。希望你喜欢。