【问题标题】:StaleElementReferenceException occurs when clicking on the buttons of a changed table (Python > Selenium Webdriver)单击已更改表的按钮时发生 StaleElementReferenceException(Python > Selenium Webdriver)
【发布时间】:2015-11-14 06:19:22
【问题描述】:

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

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

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

我在代码中添加了 cmets 以帮助遵循我的流程:

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

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

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(),但问题仍然存在。我该如何解决这个问题?

【问题讨论】:

    标签: python selenium selenium-webdriver webdriver


    【解决方案1】:

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

    出现问题是因为当对表格进行更改时,被引用的元素不再附加到 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")
    

    希望,如果有人遇到与我相同的问题,这将有助于他们。

    【讨论】:

      【解决方案2】:

      如果您的问题是您单击了一个尚不存在的元素并且您想验证该元素是否存在,您可以执行以下操作:

      1. 使用搜索元素列表的方法查找元素(也返回列表)
      2. 检查列表中是否有任何元素(计数> 0)
      3. 如果 count 为 0,则没有找到任何元素,因此它不存在

      您也可以尝试使用 try-catch,但它更复杂。

      【讨论】:

      • 感谢@Denis Koreyba 的回复。是的,该元素确实存在。那不是问题。异常是由于页面上列出的表的更改而引起的。元素存在,但元素中的数据有时会发生变化,这导致了我看到的异常。
      • 您是否尝试过每次使用 xPath 请求您的元素?我的意思是您必须再次找到它才能获得该元素的当前版本。我认为当您使用 PageObject 模式时,您不会遇到这个问题。我可能错了。所以,在你使用它之前,请尽量找到你的元素。
      猜你喜欢
      • 1970-01-01
      • 2014-11-15
      • 1970-01-01
      • 2016-05-31
      • 1970-01-01
      • 1970-01-01
      • 2021-09-03
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多