2

I keep geting a StaleElementReferenceException when I'm trying to test a webpage that contains a table. The table has points [as well as some other info] and the status of two separate Blocking points, each of which has a toggle state button for 'Yes' and 'No'.

In this particular code, the process is:

  1. Click the checkbox to reveal only the blocking points.
  2. If there are no blocking point in the table, you're DONE. Otherwise...
  3. Save the name of the point in the first row & check the first blocking status. If set to 'Yes', change it to 'No.'
  4. Check to see if the point still remains. if so, change the second blocking status to 'No' and confirm the point has been deleted.

I added comments in the code to help follow my process:

    # << Setup >>
    driver.get(url("/PointsTable/"))
    assertExpectedConditionTrue(driver, "By.XPATH", "//td")

    # < Confirm that the points blocking checkbox is enabled >
    if not driver.find_element_by_id("BlockedPoints").is_selected():
        assertExpectedConditionTrue(driver, "By.ID", "BlockedPoints")
        driver.find_element_by_id("BlockedPoints").click()
        assertCheckBoxEnabled(driver, "BlockedPoints")

    # < First check if any points have a blocking state >
    try:
        assertExpectedConditionTrue(driver, "By.XPATH", "//td[contains(text(), 'No data available in table')]", None, 3)
    except (NoSuchElementException):
        # < Until all the points are out of blocking state, begin changing blocking statuses
        #   to all the points >
        while True:
            # < Check if all the points are set to have no blocking statuses set to Yes >
            try:
                assertExpectedConditionFalse(driver, "By.XPATH", "//td[contains(text(), 'No data available in table')]", None, 2)
            except (NoSuchElementException, TimeoutException):
                break

            # < Save the name of the point
            # Check the first blocking status.  If it is blocking, set the block to No >
            assertExpectedConditionTrue(driver, "By.XPATH", "//td")
            myPointVal = driver.find_element_by_xpath("//td").text

            try:
                assertExpectedConditionTrue(driver, "By.XPATH", "//tbody/tr[1]/td[5]/div/button[@class='btn active btn-success btn-small point-button']", None, 2)
            except (NoSuchElementException):
                assertExpectedConditionTrue(driver, "By.XPATH", "//tbody/tr[1]/td[5]/div/button[@class='btn btn-small point-button']")
                driver.find_element_by_xpath("//tbody/tr[1]/td[5]/div/button[@class='btn btn-small point-button']").click()

            # < Save the name of the point again.  Compare it to the original saved point
            #    If the name is the same, then the second blocking status needs to be set to No
            #    If the name is different, that means the point in question is no longer blocked >
            assertExpectedConditionTrue(driver, "By.XPATH", "//td")
            if myPointVal == driver.find_element_by_xpath("//td").text:
                assertExpectedConditionTrue(driver, "By.XPATH", "//tbody/tr[1]/td[6]/div/button[@class='btn btn-small point-button']")
                driver.find_element_by_xpath("//tbody/tr[1]/td[6]/div/button[@class='btn btn-small point-button']").click()
                assertExpectedConditionFalse(driver, "By.XPATH", "//td", myPointVal)

When a point has had all its Blocking states removed, it literally disappears from the table, which is the cause of my exception. The code doesn't always fail on the same line, but when it fails, it's ALWAYS on a line where I attempt to click on the 'Yes' or 'No' button, more than likely due to the table changing after a point has been successfully removed from the table.

i.e. driver.find_element_by_xpath("//tbody/tr[1]/td[6]/div/button[@class='btn btn-small point-button']").click()

It sometimes makes it past this portion of the code & fails in a different portion where I'm attempting to click on a button either after I've.. (1) refreshed the page, or (2) have navigated to page two, where the XPATH addresses are the same, but the objects in the XPATH address have changed. I do understand the reason I'm having this issue for the reasons listed here. My issue seems to be consistent with, "The element is no longer attached to the DOM."

Up to this point, I've tried using both time.sleep() and driver.implicitly_wait() in locations that could cause change to the table, but the issue still remains. How can I resolve this issue?

rwbyrd
  • 416
  • 5
  • 24

2 Answers2

0

Using an inplicitly_wait(), if the time is set high enough, will resolve StaleElementReferenceException issue. However, the implicit wait also caused the test case to take a very long time to run. I resolved this issue using ideas I found here, here, and here.

The issue occurred because elements that were being referenced were no longer be attached to the DOM when changes were made to the table. Therefore, I created definitions specifically for dealing with elements that may be stale.

def waitForNonStaleElement(driver, type, element):
    strategy = {
            "id":           driver.find_element_by_id,
            "link_text":    driver.find_element_by_link_text,
            "name":         driver.find_element_by_name,
            "xpath":        driver.find_element_by_xpath
            }
    lhsType, rhsType = type.split(".", 1)
    find_element = strategy.get(rhsType.lower())

    try:
        find_element(element)
    except StaleElementReferenceException:
        waitForNonStaleElement(driver, type, element)
    except TypeError:
        raise TypeError("ERROR : CODE TO HANDLE \""+element+"\" TYPE NEEDS TO BE CREATED")


def waitForNonStaleElementClick(driver, type, element):
    strategy = {
            "id":           driver.find_element_by_id,
            "link_text":    driver.find_element_by_link_text,
            "name":         driver.find_element_by_name,
            "xpath":        driver.find_element_by_xpath
            }
    lhsType, rhsType = type.split(".", 1)
    find_element = strategy.get(rhsType.lower())

    try:
        waitForNonStaleElement(driver, type, element)
        find_element(element).click()
    except StaleElementReferenceException:
        waitForNonStaleElementClick(driver, type, element)
    except TypeError:
        raise TypeError("ERROR : CODE TO HANDLE \""+element+"\" TYPE NEEDS TO BE CREATED")


def waitForNonStaleElementText(driver, type, element):
    strategy = {
            "id":           driver.find_element_by_id,
            "link_text":    driver.find_element_by_link_text,
            "name":         driver.find_element_by_name,
            "xpath":        driver.find_element_by_xpath
            }
    lhsType, rhsType = type.split(".", 1)
    find_element = strategy.get(rhsType.lower())

    try:
        return find_element(element).text
    except StaleElementReferenceException:
        waitForNonStaleElementText(driver, type, element)
    except TypeError:
        raise TypeError("ERROR : CODE TO HANDLE \""+element+"\" TYPE NEEDS TO BE CREATED")

waitForNonStaleElement() is used for confirming the an element is no longer stale. waitForNonStaleElementClick() allows me click on an element that could be stale. waitForNonStaleElementText() allows me to retrieve text from an element that may be stale.

I then rewrote the search code using these methods:

# << Setup >>
driver.get(url("/PointsBlocking/"))
assertExpectedConditionTrue(driver, "By.XPATH", "//td")

if not driver.find_element_by_id("BlockedOnlyCheckbox").is_selected():
    assertExpectedConditionTrue(driver, "By.ID", "BlockedOnlyCheckbox")
    driver.find_element_by_id("BlockedOnlyCheckbox").click()
    assertCheckBoxEnabled(driver, "BlockedOnlyCheckbox")

waitForNonStaleElement(driver, "By.XPATH", "//td")

try:
    assertExpectedConditionTrue(driver, "By.XPATH", "//td", "No data available in table", 1)
except (TimeoutException):
    while True:
        try:
            assertExpectedConditionFalse(driver, "By.XPATH", "//td", "No data available in table", 1)
        except (TimeoutException):
            break

        assertExpectedConditionTrue(driver, "By.XPATH", "//td")
        pointName = waitForNonStaleElementText(driver, "By.XPATH", "//td")

        try:
            assertExpectedConditionTrue(driver, "By.XPATH", "//tbody/tr[1]/td[5]/div/button[@class='btn active btn-success btn-small point-button']", None, 1)
        except NoSuchElementException:
            assertExpectedConditionTrue(driver, "By.XPATH", "//tbody/tr[1]/td[5]/div/button[@class='btn btn-small point-button']")
            waitForNonStaleElementClick(driver, "By.XPATH", "//tbody/tr[1]/td[5]/div/button[@class='btn btn-small point-button']")

        tmp = waitForNonStaleElementText(driver, "By.XPATH", "//td")

        if pointName == tmp:
            assertExpectedConditionTrue(driver, "By.XPATH", "//tbody/tr[1]/td[6]/div/button[@class='btn btn-small point-button']")
            waitForNonStaleElementClick(driver, "By.XPATH", "//tbody/tr[1]/td[6]/div/button[@class='btn btn-small point-button']")
            waitForNonStaleElementClick(driver, "By.XPATH", "//td")

Hopefully, this will help someone if they run across the same issue I did.

Community
  • 1
  • 1
rwbyrd
  • 416
  • 5
  • 24
-1

If your problem is that you click on an element that doesn't already exist and you want to verify if element is present you can do the next:

  1. Find element using method that search for list of elements (returns also list)
  2. Check if there is any elements in the list (count > 0)
  3. If count is 0 then there was no elements found so it doesn't exist

Also you can try using try-catch but it's more complicated.

Denis Koreyba
  • 3,144
  • 1
  • 31
  • 49
  • Thanks for the reply @Denis Koreyba. Yes, the element does exist. That's not the issue. The exception is being caused due to a change in the table listed on the page. The element exists, but the data within the element changes sometimes, which is causing the exception I'm seeing. – rwbyrd Aug 20 '15 at 15:32
  • Have you tried to request your element each time using your xPath? I mean you have to find it again in order to get current version of the element. I think that when you use PageObject pattern you won't face this problem. I might be wrong. So, please try to find your element exactly a line before you work with it. – Denis Koreyba Aug 21 '15 at 08:27