You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Selenium如何正确等待列表完成按键触发的滚动?

为什么Selenium模拟向下键滚动后必须加sleep才能断言成功?如何避免使用sleep?

问题描述

我写了一段JavaScript版的Selenium代码,通过模拟按下键盘向下键来滚动列表。虽然我知道用JavaScript命令滚动更简单,但我希望采用按键的方式。现有代码如下:

let list = await this.driver.wait(until.elementLocated(By.css('#scrollableList')), 2000);
// 初始状态下列表未滚动:
let scrolledY = await this.driver.executeScript('return arguments[0].scrollTop;', list);
await assert.deepEqual(scrolledY, 0);
// 点击列表并多次按下向下键滚动
await this.driver.actions({ bridge: true }).click(list).perform();
await this.driver.actions({ bridge: true })
  .keyDown(Key.DOWN).keyUp(Key.DOWN)
  .keyDown(Key.DOWN).keyUp(Key.DOWN)
  .keyDown(Key.DOWN).keyUp(Key.DOWN)
  .perform();
// 需添加该行才能让后续断言通过:
await sleep(100);
// 检查是否成功滚动:
scrolledY = await this.driver.executeScript('return arguments[0].scrollTop;', list);
await assert.ok(scrolledY > 0);

滚动操作其实是成功的(休眠10秒时能直观看到滚动效果),但必须加await sleep(100);等待几毫秒,否则最后一个断言会失败。请问这是什么原因?有没有办法不用sleep命令实现需求?


回答

这是个很常见的Selenium异步执行问题,我来给你拆解一下:

为什么必须加sleep?

Selenium的actions().perform()方法只是发送了按键指令给浏览器,但并不等待浏览器完成滚动的所有后续操作——比如列表的滚动动画、DOM中scrollTop属性的更新,这些都是浏览器端的异步行为,会滞后于Selenium指令的执行。当你直接跳过sleep去获取scrollTop时,浏览器可能还没完成滚动,所以拿到的还是初始的0值,导致断言失败。

sleep虽然能解决问题,但它是硬等待,不够可靠:如果浏览器性能波动,100毫秒可能不够,反之又会浪费时间。

替代方案:用显式等待替代硬sleep

我们可以利用Selenium的显式等待,自定义一个条件,直到列表的scrollTop确实大于0了再执行断言,这样既可靠又不会浪费多余的等待时间。

这里有两种实现方式:

方式1:自定义等待条件,监听scrollTop变化
let list = await this.driver.wait(until.elementLocated(By.css('#scrollableList')), 2000);
// 初始校验
let scrolledY = await this.driver.executeScript('return arguments[0].scrollTop;', list);
await assert.deepEqual(scrolledY, 0);

// 执行滚动操作
await this.driver.actions({ bridge: true }).click(list).perform();
await this.driver.actions({ bridge: true })
  .keyDown(Key.DOWN).keyUp(Key.DOWN)
  .keyDown(Key.DOWN).keyUp(Key.DOWN)
  .keyDown(Key.DOWN).keyUp(Key.DOWN)
  .perform();

// 替换sleep,用显式等待直到scrollTop > 0
await this.driver.wait(async () => {
  const currentScrollTop = await this.driver.executeScript('return arguments[0].scrollTop;', list);
  return currentScrollTop > 0;
}, 2000); // 设置超时时间,比如2秒,防止无限等待

// 再执行断言
scrolledY = await this.driver.executeScript('return arguments[0].scrollTop;', list);
await assert.ok(scrolledY > 0);
方式2:如果滚动后有新元素出现,等待新元素可见

如果你的列表滚动后会加载新的元素(比如懒加载列表),也可以直接等待某个原本不可见的元素变得可见,这样更贴合业务场景:

// 假设滚动后会出现id为"item-4"的元素
await this.driver.wait(until.elementIsVisible(By.css('#item-4')), 2000);

// 之后再断言scrollTop
scrolledY = await this.driver.executeScript('return arguments[0].scrollTop;', list);
await assert.ok(scrolledY > 0);

额外小优化:简化actions链式调用

你可以把按键操作合并成更简洁的链式写法,可读性更好:

await this.driver.actions({ bridge: true })
  .click(list)
  .sendKeys(Key.DOWN, Key.DOWN, Key.DOWN) // 直接连续发送三次DOWN键
  .perform();

这样就完全不需要依赖硬sleep了,测试会更稳定。

内容的提问来源于stack exchange,提问作者hardfork

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 08:22:40