自动化

selenium执行js滑动不生效的原因和解决办法

有个老的web项目,在升级了UI后,自动化脚本出现了问题,调试后发现主要是selenium执行js代码进行scroll操作时,没有生效但也没有报错,于是后面的操作和检查点出了问题。不过scrollIntoView是可以的,改成send_keys发送end或arrow down的键盘事件时也是能生效的。

在浏览器devtool的console里输入window.scrollTo(0, 1000);能看到无报错但确实没有生效。

python语句比如是:

driver.execute_script("window.scrollBy(0,document.body.scrollHeight);")
driver.execute_script("window.scrollTo(0, 0);")
driver.execute_script("arguments[0].scrollIntoView();", element)

检查后发现原因:

这种情况在UI升级(尤其是引入React、Vue、Angular等现代前端框架或新版组件库,如Ant Design、Element Plus 等)后非常常见。

页面的滚动条已经不再属于全局的window或document.body,而是转移到了某个特定的内部全局容器(div)上,或者新UI采用了虚拟列表(Virtual List)技术。

当执行window.scrollTo时,由于window本身没有滚动条,所以它无法产生任何位移,同时由于JS语法本身是合法的,浏览器并不会报错。而send_keys能生效,是因为它将焦点聚焦在了页面元素上,键盘事件通过事件冒泡或浏览器原生焦点系统强行触发了内部容器的滚动。

解决的办法:

先要明确是哪个区域是可以滑动的,在console里执行下面这段js,其中被红色框住的就是可滑动区域。

document.querySelectorAll('*').forEach(el => {
    const style = window.getComputedStyle(el);
    if (style.overflowY === 'auto' || style.overflowY === 'scroll') {
        el.style.border = '3px solid red'; 
        console.log('带有滚动属性的元素:', el);
    }
});

找到了对应的节点信息后,就可以针对它进行滑动操作:

# 1. 定位到你在控制台里抓出来的那个真正带滚动条的 div
true_container = driver.find_element(By.CLASS_NAME, "xx-container")

# 2. 用 JS 直接操控这个容器的 scrollTop,页面就会丝滑下滑
driver.execute_script("arguments[0].scrollTop = 1000;", true_container)

# 或者直接滚到底部
driver.execute_script("arguments[0].scrollTop = arguments[0].scrollHeight;", true_container)

# 将特定元素滚动到该容器的视野中心,让它自动在父级容器内居中对齐
sub_element = driver.find_element(By.XPATH, "//your-target-element-xpath")
driver.execute_script("arguments[0].scrollIntoView({block: 'center'});", sub_element)

或者先在console里用js验证一下

// 直接飞跃到垂直向下 1000 像素的位置
document.querySelector('.xx-container').scrollTop = 1000;

// 在当前滚动位置的基础上,再向下滚动 500 像素
document.querySelector('.xx-container').scrollBy({ top: 500, behavior: 'smooth' });

不过这需要先明确是哪个容器节点是可滑动区域,不是一个通用的方法,所以最好的方式还是把之前的scroll down / scroll up等方法改一下实现,让它自己判断是老式的还是新式的ui,自己寻找可滚动的区域, 那么原有的自动化脚本都不需要改:

# 原先的scroll by
    def scroll_by(self, x=0, y=500):
        self.driver.execute_script(f"window.scrollBy({x}, {y});")

# 修改后的
    def _get_scroll_container(self):
        """
        私有辅助方法:动态检测并返回当前页面实际有效的局部滚动 div 容器。
        如果是老式页面或全局滚动,则返回 None。
        """
        js_detect_script = """
        let allElements = document.querySelectorAll('*');
        for (let el of allElements) {
            let style = window.getComputedStyle(el);
            let hasScrollHeight = el.scrollHeight > el.clientHeight && el.clientHeight > 0;
            let isScrollable = style.overflowY === 'auto' || style.overflowY === 'scroll' || 
                               style.overflow === 'auto' || style.overflow === 'scroll';

            // 排除全局的 html 和 body
            if (hasScrollHeight && isScrollable && el !== document.documentElement && el !== document.body) {
                return el;
            }
        }
        return null;
        """
        return self.driver.execute_script(js_detect_script)

    def scroll_by(self, x=0, y=500):
        container = self._get_scroll_container()
        if container:
            # 新式页面:在当前容器滚动的 X 和 Y 轴基础上进行累加
            self.driver.execute_script("arguments[0].scrollLeft += arguments[1]; arguments[0].scrollTop += arguments[2];", container, x, y)
        else:
            # 老式页面:沿用原本逻辑
            self.driver.execute_script(f"window.scrollBy({x}, {y});")

文章评论 (0)