5

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.

2
  • I wrote a full answer about that here : stackoverflow.com/questions/41737974/… Commented Jul 19, 2017 at 9:22
  • I had the same problem - for finding the rows, instead of table.find_elements_by_tag_name('tr'), we can do self.browser.find_elements_by_tag_name('tr'). In other words, we can work with the browser every time we try to find an element instead of using an element to look for other elements. Commented Feb 23, 2021 at 18:01

5 Answers 5

2

Add these imports:

from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions

Change these lines

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

to:

inputbox.send_keys(Keys.ENTER)

WebDriverWait(self.browser, 10).until(
            expected_conditions.text_to_be_present_in_element(
                (By.ID, 'id_list_table'), 'Buy peacock feathers'))

self.check_for_row('1: Buy peacock feathers')

This replaces the time.sleep(1) with something more "reasonable"

Sign up to request clarification or add additional context in comments.

Comments

0

I have been using selenium for a while now so I understand the struggles of the Stale Element Exception. While not perfect, selenium provides a series of "wait" commands to allow for the website to load complete. Unfortunately, its not perfect as loading can take different time on each run, but these are the tools provided by selenium.

Comments

0

I haven't worked in python but have worked on java/selenium. But,I can give you the idea to overcome staleness.

Generally we will be getting the Stale Exception if the element attributes or something is changed after initiating the webelement. For example, in some cases if user tries to click on the same element on the same page but after page refresh, gets staleelement exception.

To overcome this, we can create the fresh webelement in case if the page is changed or refreshed. Below code can give you some idea.(It's in java but the concept will be same)

Example:

webElement element = driver.findElement(by.xpath("//*[@id='StackOverflow']"));
element.click();
//page is refreshed
element.click();//This will obviously throw stale exception

To overcome this, we can store the xpath in some string and use it create a fresh webelement as we go.

String xpath = "//*[@id='StackOverflow']";
driver.findElement(by.xpath(xpath)).click();
//page has been refreshed. Now create a new element and work on it
driver.fineElement(by.xpath(xpath)).click();   //This works

Another example:

for(int i = 0; i<5; i++)
{
  String value = driver.findElement(by.xpath("//.....["+i+"]")).getText);
  System.out.println(value);
} 

Hope this helps you. Thanks

2 Comments

Santhosh, thank you for your suggestion. However, after implementing this xpath solution, I am receiving the same error as before, that the element I'm referencing is stale.
Ok... This is the universal solution for staleness that i am aware of. If you dont mind, can you check your updated code again and confirm you have created the webelement only after the page settled down(such as after refresh /not using the webelement created earlier).
0

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...)

Comments

0

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)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.