Skip to main content
 首页 » 编程设计

python之单击已更改表格的按钮时发生 StaleElementReferenceException(Python > Selenium Webdriver)

2025年05月04日35yxwkf

当我尝试测试包含表格的网页时,我不断收到 StaleElementReferenceException。该表包含点 [以及一些其他信息] 和两个单独的阻塞点的状态,每个阻塞点都有一个用于"is"和“否”的切换状态按钮。

在这个特定的代码中,过程是:

  1. 单击复选框以仅显示阻塞点。
  2. 如果表格中没有阻塞点,您就完成了。否则……
  3. 保存第一行的点名并检查第一个阻塞状态。如果设置为"is",请将其更改为“否”。
  4. 检查该点是否仍然存在。如果是,将第二个阻止状态更改为“否”并确认该点已被删除。

我在代码中添加了注释以帮助遵循我的过程:

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

当一个点的所有阻塞状态都被移除时,它就会从表中消失,这就是我的异常的原因。该代码并不总是在同一行上失败,但是当它失败时,它总是在我尝试单击"is"或“否”按钮的行上,很可能是由于表格在某个点后发生变化已成功从表中删除。

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

它有时会通过代码的这一部分,但在我尝试点击按钮的不同部分会失败......(1)刷新页面,或(2)导航到第二页,XPATH地址相同,但XPATH地址中的对象发生了变化。由于列出的原因,我确实理解我遇到此问题的原因 here .我的问题似乎与“该元素不再附加到 DOM”一致。

到目前为止,我已经尝试在可能导致表更改的位置同时使用 time.sleep() 和 driver.implicitly_wait(),但问题仍然存在。我该如何解决这个问题?

请您参考如下方法:

使用 inplicitly_wait(),如果时间设置得足够长,将解决 StaleElementReferenceException 问题。但是,隐式等待也导致测试用例需要很长时间才能运行。我使用我发现的想法解决了这个问题 here , here , 和 here .

出现此问题是因为在对表格进行更改时,被引用的元素不再附加到 DOM。因此,我创建了专门用于处理可能过时的元素的定义。

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() 用于确认元素不再陈旧。 waitForNonStaleElementClick() 允许我点击一个可能过时的元素。 waitForNonStaleElementText() 允许我从可能过时的元素中检索文本。

然后我使用这些方法重写了搜索代码:

# << 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") 

希望这对遇到我遇到的相同问题的人有所帮助。