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
相关产品推荐
相关产品推荐

