0

Why is the StaleElementReferenceException still raised even though I ignored the exception? The code will eventually run successfully after a few tries but how can I make it wait until the element is clickable to avoid any exception?

import sys
from selenium.common.exceptions import StaleElementReferenceException

    while True:
           try:
               WebDriverWait(driver, 10, ignored_exceptions=[StaleElementReferenceException]).until(ec.element_to_be_clickable((By.XPATH,"//button[contains(text(), 'Performance Summary')]"))).click()
               break              
           except Exception as e:
                  exc_type, exc_obj, exc_tb = sys.exc_info()                                
                  print(e, exc_type, exc_tb.tb_lineno)
                  print('Retrying...')
JeffC
  • 22,180
  • 5
  • 32
  • 55
jguy
  • 161
  • 1
  • 11
  • Does this question help? https://stackoverflow.com/questions/25680295/how-to-make-a-my-method-that-is-using-wait-until-ignore-throwing-exceptions-in-s – Maria Feb 04 '23 at 20:29

2 Answers2

1

From the source code of WebDriverWait:

class WebDriverWait:
    def __init__(
    self,
    driver,
    timeout: float,
    poll_frequency: float = POLL_FREQUENCY,
    ignored_exceptions: typing.Optional[WaitExcTypes] = None,
    ):
    """Constructor, takes a WebDriver instance and timeout in seconds.

    :Args:
     - driver - Instance of WebDriver (Ie, Firefox, Chrome or Remote)
     - timeout - Number of seconds before timing out
     - poll_frequency - sleep interval between calls
       By default, it is 0.5 second.
     - ignored_exceptions - iterable structure of exception classes ignored during calls.
       By default, it contains NoSuchElementException only.

    Example::

     from selenium.webdriver.support.wait import WebDriverWait \n
     element = WebDriverWait(driver, 10).until(lambda x: x.find_element(By.ID, "someId")) \n
     is_disappeared = WebDriverWait(driver, 30, 1, (ElementNotVisibleException)).\\ \n
             until_not(lambda x: x.find_element(By.ID, "someId").is_displayed())
    """
    self._driver = driver
    self._timeout = float(timeout)
    self._poll = poll_frequency
    # avoid the divide by zero
    if self._poll == 0:
        self._poll = POLL_FREQUENCY
    exceptions = list(IGNORED_EXCEPTIONS)
    if ignored_exceptions:
        try:
        exceptions.extend(iter(ignored_exceptions))
        except TypeError:  # ignored_exceptions is not iterable
        exceptions.append(ignored_exceptions)
    self._ignored_exceptions = tuple(exceptions)
    

It is worth to notice that ignored_exceptions is not iterable.

So NoSuchElementException being the default exception and StaleElementReferenceException being purposely added, can be ignored only once. Hence the second time StaleElementReferenceException is no more handled.

undetected Selenium
  • 183,867
  • 41
  • 278
  • 352
1

The problem is that once you get a StaleElementReferenceException, you can't just wait it out. Here's how stale elements work.

element = driver.find_element(By.ID, "someId")
driver.refresh() # or anything that changes the portion of the page that 'element' is on
element.click() # throws StaleElementReferenceException

At this point a StaleElementReferenceException is thrown because you had a reference to the element and then lost it (the element reference pointer points to nothing). No amount of waiting is going to restore that reference.

The way to "fix" this is to grab the reference again after the page has changed,

element = driver.find_element(By.ID, "someId")
driver.refresh()
element = driver.find_element(By.ID, "someId") # refetch the reference after the page refreshes
element.click()

Now the .click() will work without error.

Most people that run into this issue are looping through a collection of elements and in the middle of the loop they click a link that navigates to a new page or something else that reloads or changes the page. They later return to the original page and click on the next link but they get a StaleElementReferenceException.

elements = driver.find_elements(locator)
for element in elements
    element.click() # navigates to new page
    # do other stuff and return to first page

The first loop works fine but in the second loop the element reference is dead because of the page change. You can change this loop to force the elements collection to be refetched at the start of each loop

for element in driver.find_elements(locator)
    element.click() # navigates to new page
    # do other stuff and return to first page

Now the loop will work. This is just an example but hopefully it will point you in the right direction to fix your code.

JeffC
  • 22,180
  • 5
  • 32
  • 55
  • but in my case why would my code cause the issue as I believe that "element_to_be_clickable" and "click()" are performed at the same time in same line of code so it shouldn't be referencing any element I searched previously, am I correct? – jguy Feb 05 '23 at 20:21
  • Another possible reason is that the DOM is temporarily unavailable cuz after several loops, the code will continue running..if that the case, why the DOM is gone at that time? – jguy Feb 05 '23 at 20:42
  • I can't say for sure without seeing the code you have around the code you posted. It's possible you clicked something before this code which leads to a page change or refresh and then your posted code runs and then times out because once an elements is stale it can never be fixed. – JeffC Feb 05 '23 at 21:25
  • Is there a way to make sure that the code will only move to next line after the page finish loading to avoid “previous click causing page loading”? – jguy Feb 06 '23 at 09:15
  • I can't say that's the issue without seeing more of the code but I don't think it is. My best guess is that you are clicking an element that causes a page refresh/change then trying to click an element from a list you pulled earlier in the code. This will cause a stale element exception. The way to fix that is to do what I described in my answer. – JeffC Feb 06 '23 at 15:18