我怎样才能 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 的最佳方法。大多数人会说不应该抑制异常,但我确信在这种情况下我不关心异常,这就是阻止它结束测试的方法。希望你喜欢。
我使用 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 的最佳方法。大多数人会说不应该抑制异常,但我确信在这种情况下我不关心异常,这就是阻止它结束测试的方法。希望你喜欢。