How to resolve, Stale element exception? if element is no longer attached to the DOM?

后端 未结 7 1103
盖世英雄少女心
盖世英雄少女心 2020-11-30 23:01

I have a question regarding \"Element is no longer attached to the DOM\".

I tried different solutions but they are working intermittent. Please suggest a solution th

相关标签:
7条回答
  • 2020-11-30 23:07

    Solutions to resolve them:

    1. Storing locators to your elements instead of references
    driver = webdriver.Firefox();
    driver.get("http://www.github.com");
    search_input = lambda: driver.find_element_by_name('q');
    search_input().send_keys('hello world\n'); 
    time.sleep(5);
    
    
    search_input().send_keys('hello frank\n') // no stale element exception
    
    1. Leverage hooks in the JS libraries used
       # Using Jquery queue to get animation queue length.
        animationQueueIs = """
        return $.queue( $("#%s")[0], "fx").length;
        """ % element_id
        wait_until(lambda: self.driver.execute_script(animationQueueIs)==0)
    
    1. Moving your actions into JavaScript injection
     self.driver.execute_script("$(\"li:contains('Narendra')\").click()");
    
    1. Proactively wait for the element to go stale
      # Wait till the element goes stale, this means the list has updated
      wait_until(lambda: is_element_stale(old_link_reference))
    

    This solution, which worked for me

    0 讨论(0)
  • 2020-11-30 23:14

    https://www.swtestacademy.com/selenium-wait-javascript-angular-ajax/ here is a good article about dynamic waiter strategies. Your problem is not waiting properly all the ajax, jquery or angular calls. Then you end up with StaleElementException.

    If your approach is to use Try-Catch mechanism, I guess it has a flaw. You shouldn't rely on that structure as you'll never know it's gonna work in the catch clause.

    Selenium gives you the opportunity to make javascript calls. You can execute

    • "return jQuery.active==0"
    • return angular.element(document).injector().get('$http').pendingRequests.length === 0"
    • "return document.readyState"
    • "return angular.element(document).injector() === undefined"

    commands just to check the existence and states of those calls.

    You can do that before any findBy operation so you always work with the latest page

    0 讨论(0)
  • 2020-11-30 23:15

    I solve this by 1. keeping the stale element and poll it until it throws an exception, and then 2. wait until the element is visible again.

        boolean isStillOnOldPage = true;
        while (isStillOnOldPage) {
            try {
                theElement.getAttribute("whatever");
            } catch (StaleElementReferenceException e) {
                isStillOnOldPage = false;
            }
        }
        WebDriverWait wait = new WebDriverWait(driver, 15);
        wait.until(ExpectedConditions.visibilityOfElementLocated(By.id("theElementId")));
    
    0 讨论(0)
  • 2020-11-30 23:17

    If you are trying to Click on link, that taking you to new page. After that navigating back and clicking on other links. They below code may help you.

    public int getNumberOfElementsFound(By by) {
        return  driver.findElements(by).size();
      }
    
    public WebElement getElementWithIndex(By by, int pos) {
        return driver.findElements(by).get(pos);
      }
    
    /**click on each link */
    public void getLinks()throws Exception{
    try {
    List<WebElement> componentList = driver.findElements(By.tagName("a"));
    System.out.println(componentList.size()); 
    
        for (WebElement component : componentList)
        {
            //click1();
            System.out.println(component.getAttribute("href"));
        }
     int numberOfElementsFound = getNumberOfElementsFound(By.tagName("a"));
    for (int pos = 0; pos < numberOfElementsFound; pos++) {
         if (getElementWithIndex(By.tagName("a"), pos).isDisplayed()){
    
      getElementWithIndex(By.tagName("a"), pos).click();
      Thread.sleep(200);
      driver.navigate().back();
      Thread.sleep(200);                                                       
    }
      }
        }catch (Exception e){
            System.out.println("error in getLinks "+e);
        }
    }
    
    0 讨论(0)
  • 2020-11-30 23:22

    The problem

    The problem you are probably facing is that the method returns the right (and valid!) element, but when you're trying to access it a second later, it is stale and throws.

    This usually arises when:

    1. You click something that loads a new page asynchronously or at least changes it.
    2. You immediatelly (before the page load could finish) search for an element ... and you find it!
    3. The page finally unloads and the new one loads up.
    4. You try to access your previously found element, but now it's stale, even though the new page contains it, too.

    The solutions

    There are four ways to solve it I know about:

    1. Use proper waits

      Use proper waits after every anticipated page-load when facing asynchronous pages. Insert an explicit wait after the initial click and wait for the new page / new content to load. Only after that you can try to search for the element you want. This should be the first thing you'll do. It will increase the robustness of your tests greatly.

    2. The way you did it

      I have been using a variant of your method for two years now (together with the technique above in solution 1) and it absolutely works most of the time and fails only on strange WebDriver bugs. Try to access the found element right after it is found (before returning from the method) via a .isDisplayed() method or something. If it throws, you already know how to search again. If it passes, you have one more (false) assurance.

    3. Use a WebElement that re-finds itself when stale

      Write a WebElement decorator that remembers how it was found and re-find it when it's accessed and throws. This obviously forces you to use custom findElement() methods that would return instances of your decorator (or, better yet, a decorated WebDriver that would return your instances from usual findElement() and findElemens() methods). Do it like this:

      public class NeverStaleWebElement implements WebElement {
          private WebElement element;
          private final WebDriver driver;
          private final By foundBy;
      
          public NeverStaleWebElement(WebElement element, WebDriver driver, By foundBy) {
              this.element = element;
              this.driver = driver;
              this.foundBy = foundBy;
          }
      
          @Override
          public void click() {
              try {
                  element.click();
              } catch (StaleElementReferenceException e) {
                  // log exception
      
                  // assumes implicit wait, use custom findElement() methods for custom behaviour
                  element = driver.findElement(foundBy);
      
                  // recursion, consider a conditioned loop instead
                  click();
              }
          }
      
          // ... similar for other methods, too
      
      }
      

      Note that while I think that the foundBy info should be accessible from the generic WebElements to make this easier, Selenium developers consider it a mistake to try something like this and have chosen not to make this information public. It's arguably a bad practice to re-find on stale elements, because you're re-finding elements implicitly without any mechanism for checking whether it's justified. The re-finding mechanism could potentially find a completely different element and not the same one again. Also, it fails horribly with findElements() when there are many found elements (you either need to disallow re-finding on elements found by findElements(), or remember the how-manyeth your element was from the returned List).

      I think it would be useful sometimes, but it's true that nobody would ever use options 1 and 2 which are obviously much better solutions for the robustness of your tests. Use them and only after you're sure you need this, go for it.

    4. Use a task queue (that can rerun past tasks)

      Implement your whole workflow in a new way!

      • Make a central queue of jobs to run. Make this queue remember past jobs.
      • Implement every needed task ("find an element and click it", "find an element and send keys to it" etc.) via the Command pattern way. When called, add the task to the central queue which will then (either synchronously or asynchronously, doesn't matter) run it.
      • Annotate every task with @LoadsNewPage, @Reversible etc. as needed.
      • Most of your tasks will handle their exceptions by themselves, they should be stand-alone.
      • When the queue would encounter a stale element exception, it would take the last task from the task history and re-run it to try again.

      This would obviously take a lot of effort and if not thought through very well, could backfire soon. I used a (lot more complex and powerful) variant of this for resuming failed tests after I manually fixed the page they were on. Under some conditions (for example, on a StaleElementException), a fail would not end the test right away, but would wait (before finally time-outing after 15 seconds), popping up an informative window and giving the user an option to manually refresh the page / click the right button / fix the form / whatever. It would then re-run the failed task or even give a possibility to go some steps back in history (e.g. to the last @LoadsNewPage job).


    Final nitpicks

    All that said, your original solution could use some polishing. You could combine the two methods into one, more general (or at least make them delegate to this one to reduce code repetition):

    WebElement getStaleElem(By by, WebDriver driver) {
        try {
            return driver.findElement(by);
        } catch (StaleElementReferenceException e) {
            System.out.println("Attempting to recover from StaleElementReferenceException ...");
            return getStaleElem(by, driver);
        } catch (NoSuchElementException ele) {
            System.out.println("Attempting to recover from NoSuchElementException ...");
            return getStaleElem(by, driver);
        }
    }
    

    With Java 7, even a single multicatch block would be sufficient:

    WebElement getStaleElem(By by, WebDriver driver) {
        try {
            return driver.findElement(by);
        } catch (StaleElementReferenceException | NoSuchElementException e) {
            System.out.println("Attempting to recover from " + e.getClass().getSimpleName() + "...");
            return getStaleElem(by, driver);
        }
    }
    

    This way, you can greatly reduce the amount of code you need to maintain.

    0 讨论(0)
  • 2020-11-30 23:22

    For Fitnesse you can use:

    |start |Smart Web Driver| selenium.properties|

    @Fixture(name = "Smart Web Driver") public class SmartWebDriver extends SlimWebDriver {

    private final static Logger LOG = LoggerFactory.getLogger(SmartWebDriver.class);
    
    /**
     * Constructs a new SmartWebDriver.
     */
    @Start(name = "Start Smart Web Driver", arguments = {"configuration"}, example = "|start |Smart Web Driver| selenium.properties|")
    public SmartWebDriver(String configuration) {
        super(configuration);
    }
    
    /**
     * Waits for an element to become invisible (meaning visible and width and height != 0).
     *
     * @param locator the locator to use to find the element.
     */
    @Command(name = "smartWaitForNotVisible", arguments = {"locator"}, example = "|smartWaitForNotVisible; |//path/to/input (of css=, id=, name=, classname=, link=, partiallink=)|")
    public boolean smartWaitForNotVisible(String locator) {
        try {
            waitForNotVisible(locator);
        } catch (StaleElementReferenceException sere) {
            LOG.info("Element with locator '%s' did not become invisible (visible but also width and height != 0), a StaleElementReferenceException occurred, trying to continue...", locator);
        } catch (NoSuchElementException ele) {
            LOG.info("Element with locator '%s' did not become invisible (visible but also width and height != 0), a NoSuchElementException occurred, trying to continue...", locator);
        } catch (AssertionError ae) {
            if (ae.getMessage().contains("No element found")) {
                LOG.info("Element with locator '%s' did not become invisible (visible but also width and height != 0), a AssertionError occurred, trying to continue...", locator);
            } else {
                throw ae;
            }
        }
        return true;
    }
    

    }

    0 讨论(0)
提交回复
热议问题