Selenium with Python: Stale Element Reference Exception

Viewed 4306

Working from Test Driven Development with Python, and I'm currently encountering a 'StaleElementReferenceException' when running the functional test immediately after migration. Here's the full text of the error:

ERROR: test_start_and_retrieve_list (__main__.NewVisitorTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "functional_tests.py", line 53, in test_start_and_retrieve_list
    rows = table.find_elements_by_tag_name('tr')
  File "/usr/local/lib/python3.5/dist-packages/selenium/webdriver/remote/webelement.py", line 237, in find_elements_by_tag_name
    return self.find_elements(by=By.TAG_NAME, value=name)
  File "/usr/local/lib/python3.5/dist-packages/selenium/webdriver/remote/webelement.py", line 527, in find_elements
    {"using": by, "value": value})['value']
  File "/usr/local/lib/python3.5/dist-packages/selenium/webdriver/remote/webelement.py", line 493, in _execute
    return self._parent.execute(command, params)
  File "/usr/local/lib/python3.5/dist-packages/selenium/webdriver/remote/webdriver.py", line 256, in execute
    self.error_handler.check_response(response)
  File "/usr/local/lib/python3.5/dist-packages/selenium/webdriver/remote/errorhandler.py", line 194, in check_response
    raise exception_class(message, screen, stacktrace)
selenium.common.exceptions.StaleElementReferenceException: Message: The element reference of <table id="id_list_table"> stale: either the element is no longer attached to the DOM or the page has been refreshed


----------------------------------------------------------------------
Ran 1 test in 8.735s

FAILED (errors=1)

Here's the test:

from selenium import webdriver
from selenium.webdriver.common.keys import Keys
import unittest

class NewVisitorTest(unittest.TestCase): 

    def setUp(self):
        self.browser = webdriver.Firefox()
        self.browser.implicitly_wait(3)

    def tearDown(self):
        self.browser.close()

    def check_for_row(self, row_text):
        table = self.browser.find_element_by_id('id_list_table')
        rows = table.find_elements_by_tag_name('tr')
        self.assertIn(row_text, [row.text for row in rows])

    def test_start_and_retrieve_list(self):    
        self.browser.get('http://localhost:8000')

        self.assertIn('To-Do', self.browser.title)
        header_text = self.browser.find_element_by_tag_name('h1').text
        self.assertIn('To-Do', header_text)

        inputbox = self.browser.find_element_by_id('id_new_item')
        self.assertEqual(
            inputbox.get_attribute('placeholder'),
            'Enter a to-do item'
        )

        inputbox.send_keys('Buy peacock feathers')

        inputbox.send_keys(Keys.ENTER)
        self.check_for_row('1: Buy peacock feathers')

        inputbox = self.browser.find_element_by_id('id_new_item')
        inputbox.send_keys('Use peacock feathers to make a fly')
        inputbox.send_keys(Keys.ENTER)

        table = self.browser.find_element_by_id('id_list_table')
        rows = table.find_elements_by_tag_name('tr')
        self.check_for_row('1: Buy peacock feathers')
        self.check_for_row('2: Use peacock feathers to make a fly')

        self.fail('Finish the test!')

if __name__ == '__main__':
    unittest.main(warnings='ignore')

How do I configure the test to prevent this? Selenium's own page says this issue can occur when the page refreshes, but this is a necessary part of the application logic as it's configured so far.

5 Answers

To prevent an element to become stale, place a new element on the current page, hit the link and wait until the element is not available anymore. Then wait for an element on the new page to appear

    script_to_execute = """
            var new_element = document.createElement('span');
            new_element.setAttribute('id', 'wait4me');
            document.body.appendChild(new_element);"""
    self.driver.execute_script(script_to_execute)
    self.driver.find_element_by_xpath("{}".format(locator)).click()
    WebDriverWait(self.driver, self.time_out).until (
                lambda x: not x.find_elements_by_id("wait4me")) 

This issue happens when the loop starts before an updated page has fully loaded. Especially when you update a page in an application or a form. One workaround is to place an element on the current page, then update and use the WebDriverWait statement until the element is not found anymore. Then start your loop. (Otherwise the reload happens during the loop...)

I read the same book as you do and encountered the same problem (solutions from this page didn't work for me). Here's how I resolved it.

Problem

Exception is thrown whenever you try to access a stale object. So we have to wait for situation when this exception is NOT thrown anymore.

My solution

I created method that waits for my actions until they pass

from selenium.common.exceptions import StaleElementReferenceException
[...]
def stale_aware_for_action(self, action):
    while(True):
        try:
            action()
            break
        except StaleElementReferenceException:
            continue

And in test method I defined actions that I want to wait to finish:

def test_can_start_a_list_and_retrieve_it_later(self):
    [...]
    def insert_second_item_to_inputbox():
        inputbox = self.browser.find_element_by_id('id_new_item')
        inputbox.send_keys('Use peacock feathers to make a fly')
        inputbox.send_keys(Keys.ENTER)

    self.stale_aware_for_action(insert_second_item_to_inputbox)

    def check_for_first_item():
        self.check_for_row_in_list_table('1: Buy peacock feathers')
    def check_for_second_item():
        self.check_for_row_in_list_table('2: Use peacock feathers to make a fly')

    self.stale_aware_for_action(check_for_first_item)
    self.stale_aware_for_action(check_for_second_item)
Related