【问题标题】:Prevent issues with stale elements due to ajax loads防止由于 ajax 加载而导致的陈旧元素问题
【发布时间】:2019-10-01 00:07:51
【问题描述】:

我有一个尝试使用 selenium 驱动的页面(在 python 中,但我知道这可能适用于已实现 Selenium 的其他语言)。

该页面包含一个分页表和一个过滤行。

我要执行的操作是在表中找到一行并单击该行中的链接。为了避免分页问题(我的行可能在表格中不可见),我填充过滤器,操作过滤器,然后合理地期望我的行会在那里。如果不是,那么我应该得到一个例外,这是可以接受的。

我的问题有两个。

首先是我可能从任何地方导航到此页面,包括同一页面。因此,当我尝试查找过滤器元素时,它可能会在导航完成之前完成,因此定位的过滤器元素最终会过时。我考虑过进行有限的重试循环,以应对过时元素异常,但据我所知,虽然我找不到可行的替代方案,但不建议这样做。

第二个是,一旦过滤器被操作,我就会使用wait.until 查找我的行。但是,该行可能已经在分页表的第一页上,因此它会立即找到它,但随后过滤器执行了它的 ajax 魔法并且元素变得陈旧。有没有办法检测内容更改,所以我的wait.until 直到它有更改要执行时才会启动。目前,我在过滤器操作后添加了一个thread.sleep(1),以便让 ajax 有机会运行,但我读到这也不是很好。

我的过滤器代码如下,并通过is_call_route_setup显示它的用法。

我的BasePage 课程为我提供了一些基础知识,例如访问self.driver 上的Web 驱动程序和self.wait 上提供的预配置WebDriverWait

应用程序可能根本不使用 Ajax 来应用过滤器,而可能只是使用普通的 POST 来获取结果。

有没有办法在页面完成 Ajax 或 POST 操作之前暂停操作?

class CallRoutingPage(BasePage):
    def filter(self, filters):
        """Use the filter, but be sure to clear the filters first"""
        default_filters = {
            'filter_rule': None,
            'filter_name': None,
        }

        new_filters = default_filters.copy()
        new_filters.update(filters)
        element = None
        for name, value in new_filters.items():
            element = self.wait.until(EC.element_to_be_clickable((By.NAME, name)),
                                      message='Unable to locate filter input with name {}'.format(name))
            element.clear()
            if value is not None:
                element.send_keys(value)

        element.send_keys(Keys.ENTER)  # can't use .submit, due to invalid form element in table markup

        sleep(1)  # wait for ajax to kick in so we don't locate stale elements

    def is_call_route_setup(self, source: str, destination: str, clazz: str, host: str, status: str,
                            first_connection: str, route_name: str) -> Tuple[bool, Union[str, None]]:
        """Generic function to test if a call route has been set up"""

        logger.debug('Verifying call route setup for route_name {}'.format(route_name))

        # find a route by route name
        self.filter({'filter_name': route_name})

        try:
            sleep(1)  # manual sleep to prevent picking up the table cell before the refresh has executed
            route_name_element = self.wait.until(EC.presence_of_element_located(
                (By.XPATH, "//td[@class='tblcell'][text() = '{}']".format(route_name))
            ))
        except TimeoutException:
            return False, 'ROUTE_NAME_NOT_FOUND'

        # more stuff happens after

【问题讨论】:

标签: python ajax selenium


【解决方案1】:

我想到的一些事情是 requestIdleCallback:

driver.execute_async_script("""
  window.requestIdleCallback( () => arguments[0]() )
""")

但如果 ajax 需要一段时间,它可能会提前触发。还有MutationObserver:

m = driver.execute_async_script("""
  new MutationObserver( m => arguments[0](m) ).observe(document.body, { childList: true } )
""")

如果你知道 dom 的哪一部分会发生变化,那它就可以很好地工作。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-01-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-04-04
    • 1970-01-01
    相关资源
    最近更新 更多