2017-01-05 1 views
0

Я работаю с GEB и селеном уже некоторое время, и много раз я столкнулся с ужасным исключением элементарного элемента, потому что одна из страниц, которую я должен тестировать, динамически загружается, тем самым вызывая устаревшие исключение элемента.Как я могу переписать этот метод, чтобы дать мне точное xpath или локатор? (Избегая исключения устаревших элементов)

Я пришел очень близко к созданию уловов всего решения для исключения устаревшего элемента, но, увы, недостаточно близко, поэтому мне нужна помощь.

Моим решением было переопределить класс NonEmptyNavigator, который поставляется с GEB. Я собираюсь показать мой метод нажмите() в качестве примера:

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 
    } 
} 

REFERENCING мой перекрытый метод выше, oData.toString() заканчивает тем, что-то вроде: «[[[ChromeDriver: хром на XP (2cd0a7132456fa2c71d1f798ef32c234)] -> 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 
    } 
} 

При выполнении щелчка() бросает несвежий элемент, oData.toString() появляется как это: «[[[ [[ChromeDriver: chrome on XP (2cd0a7132456fa2c71d1f798ef32c234)] -> xpath: // table/tbody]] -> xpath: child :: *]] -> xpath: child :: *]] "

Как вы можете см. информацию о том, что в настоящее время я пытаюсь получить доступ к дочернему узлу дочернего узла, но у меня больше нет ссылки, мне нужно переопределить этот конкретный элемент. У меня нет индекса конкретного ребенка (или детей), который я хочу.

Мне интересно, есть ли способ получить эту информацию, учитывая мои текущие рамки. Я также был бы открыт для других идей и предложений.

В целом я стараюсь создать уловить все решения для исключения StaleElementException. Я думаю, что я довольно близко и нуждаюсь в небольшом подталкивании, чтобы преодолеть последний горб.

+0

Знаете ли вы, что означает 'StaleElementReferenceException'? Я думаю, вы не должны пытаться бороться с этим безликим врагом во всем мире :), но попытайтесь исправить эти строки вашего кода, где вы можете использовать ссылку на элементы, которые больше не привязаны к 'DOM' – Andersson

+0

@Ansersson I new Я бы получил ответ как это :), и да, я хорошо разбираюсь в том, что означает StaleElementReferenceException. Как я отметил в своем описании, страница, которую я тестирую, обновляется динамически. В некоторых случаях он может обновляться более одного раза в секунду, даже если все элементы остаются в соответствующих местах. Вот почему мне нужно решение, которое мне нужно. Я знаю, что я мог бы попытаться поймать снова и снова на скрипте верхнего уровня, но это кажется беспорядочным для меня, я хочу написать что-то более надежное. – switch201

+0

Я собираюсь советовать против «поймать все решения». Вы пробовали предложение waitFor от Geb? Разве это не устранит ваши проблемы более элегантно? –

ответ

0

Я смог понять это самостоятельно. и теперь я больше не получаю исключение StaleElementReferenceException. Я сделал несколько переопределений классов NonEmptyNavigator и EmptyNavigator. Я добавил в пользовательское поле ArrayList, называемое дочерними. всякий раз, когда getAt() называется индексом доступного дочернего элемента, он хранится в массиве children. все последующие вызовы передают дочерний массив «вниз по цепочке», так что индекс может использоваться, когда и если появляется устаревший элемент. Ниже я покажу вам свой код. для экономии места. Я показываю только метод щелчка, но в итоге я оставил большинство методов в этом классе.

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, если вы хотите, чтобы сделать это. Большинство людей скажут, что исключение не должно быть подавлено, но я ЗНАЮ, что в этом случае меня не волнует Исключение, и это как остановить его от окончания тестов. Надеемся, вам понравится.

+0

Как именно вы используете этот класс? Если вы действительно хотите сделать его прозрачным, то есть использовать страницы и модули только через '$ (« mySelector »)', я полагаю, вам также придется расширять «страницу» и «модуль», убедившись, что их экземпляр «NavigableSupport» инициализируется специальным «NavigatorFactory», возвращающим ваш навигатор вместо обычных. Это кажется очень сложным. Но почему-то вы должны заставить Geb вернуть ваш переопределенный 'NonEmptyNavigator'. На этот ответ явно не хватает объяснений. – kriegaex

+0

@kriegarex Я использую этот класс последние несколько месяцев с тех пор, как я опубликовал этот ответ, и он работает очень хорошо. Это сделало бы намного более разумным, если бы GEB просто позволил вам подавить исключение Stale, но я отвлекся ..., чтобы заставить все вызовы Navigator ссылаться на мой новый класс, мне пришлось добавить эту строку в GebConfig каждого теста тестирования :: :: 'innerNavigatorFactory = {Браузер браузера, список элементов -> элементов? new NonEmptyNavigator (браузер, элементы): new EmptyNavigator (браузер) } ' – switch201

+0

Извините, но это так надуманно. Это может сработать, но мне интересно, как я пишу свои тесты без необходимости. Тем не менее, я хочу быть непредвзятым и попробовать ваше решение на странице, где, по вашему мнению, это полезно или даже необходимо для получения стабильных результатов теста.Укажите мне общедоступный URL-адрес и предоставьте мне простой объект страницы и тест Geb, демонстрирующий ваше решение. Я хочу увидеть для себя и сравнить с тем, что я обычно делаю в подобных ситуациях. Тогда, возможно, я смогу использовать ваше решение, а также предложить более элегантную альтернативу. Что ты говоришь? – kriegaex

Смежные вопросы