使用 selenium 进行 web 自动化测试对我来说是个常规操作。用了很多次后,我经常会抱怨 selenium 封装的操作实在是太少了,比 cypress 差远了。
比如说 selenium 没有对页面的滚动提供丰富 API , 有的只有一个孤零零的 location_once_scrolled_into_view 方法,把一个元素滚动到可视范围之内。 这远远不能满足日常的需求啊!要知道,现在大多数的网页都需要不停的滚动鼠标加载新内容。
如果是单个窗口的滚动还好,但是遇到一些变态的 ERP 系统就为难了。 每一个页面都像是战国时代的诸侯国,被割裂成一块块相互独立的模块,每个模块都需要滚动操作, 全局的窗口滚动根本就不好使,就像周王无法再调令其他诸侯国一样。
我们来看一个例子。打开禅道的演示页面:https://demo.zentao.net/my/, 除了窗口本身需要滚动以获取底部的更多内容以外, “最新动态” 和 “我的待办” 都有自己独立的滚动条。selenium 如何控制这样的诸侯国呢?
这样的模块实现的技术有几种。有的会把一个区域的内容放在一个 iframe 当中,有的是包裹在一个 div 标签当中。不管是哪一种方式,实际上,我们都可以把他们当成是单个元素进行处理,如何来操作这些可以被滚动的元素呢?
上面说过,官方的 Selenium API当中只有一个 el.location_once_scrolled_into_view 方法把某个元素滚动到可视范围内。这个方法并不够灵活,如果想要更加灵活的操作,比如滑动到底部、滑动一半等,就需要借助 JavaScript 。 基本上,如果 selenium 没有实现的操作,都可以借助 JS 操作。
对于一个网页的 DOM 对象来说呢,有全局的 window.scrollTo 方法,可以对整个窗口实现滚动操作。如果想把网页滚动到最底部,可以在任意网页按 F12 进入开发者工具,选择 console 输入 JS 代码:
window.scrollTo(0, document.body.scrollHeight);
同样的, 在 DOM 对象当中,也实现了对单个元素的滚动操作。 直接设置元素的 scrollTop 属性,表示讲可滚动元素的高度设置到多高。以禅道为例,如果要把 "最新动态" 这个元素向下滚动 800px,可以编写对应的 JS 代码:
// 定位元素
el = document.querySelector("div[data-name=最新动态]>div.panel-body");
// 向下滚动 800 像素
el.scrollTop = 800;
如果想滚动到一半,也可以通过元素的 scrollHeight 属性控制:
el.scrollTop = el.scrollHeight * 0.5;
如果元素可以左右滚动,则可以设置元素的 scrollLeft 属性:
el.scrollLeft = el.scrollWidth * 0.5;
通过设置 scrollTop 和 scrollLeft 属性有时候比较麻烦,可以适用 scrollTo 方法简化操作, 表示滚动到一半的宽度,一半的高度;
el.scrollTo(el.scrollWidth * 0.5, el.scrollHeight * 0.5);
也可以适用 scrollBy 表示从现在的位置往右边和下边滑动多少像素点:
el.scrollBy(el.scrollWidth * 0.1, el.scrollHeight * 0.1);
如果希望通过 Selenium 实现滚动操作,可以调用 selenium 当中的 execute_script 方法去执行对应的js语句。要实现上面的效果,可以访问页面之后,执行 JS 语句:
from selenium import webdriver
driver = webdriver.Chrome()
driver.get("https://demo.zentao.net/my/")
js_code = """
el = document.querySelector("div[data-name=最新动态]>div.panel-body");
el.scrollTo(0, el.scrollHeight);
"""
driver.execute_script(js_code);
对 selenium 比较熟悉的同学可以封装一个上层 API, 当需要执行滚动操作时,直接调用方法就可以了:
class SeleniumUpAPI:
"""Selenium的上层封装"""
def __init__(self, driver:Remote):
self.driver = driver
def element_scroll_to(self, el:WebElement, x_percent=0, y_percent=0):
"""滚动元素到百分比位置.
x: 宽度的比率,0.5
y: 高度的比率,0.5
"""
jscode = """arguments[0].scrollTo(
arguments[1] * arguments[0].scrollWidth,
arguments[2] * arguments[0].scrollHeight
);
"""
self.driver.execute_script(jscode, el, x_percent, y_percent)
最后贴上完整的代码:
import time
from selenium.webdriver import Remote, Chrome
from selenium.webdriver.remote.webelement import WebElement
class SeleniumUpAPI:
"""Selenium的上层封装"""
def __init__(self, driver:Remote):
self.driver = driver
def element_scroll_to(self, el:WebElement, x_percent=0, y_percent=0):
"""滚动元素到百分比位置.
x: 宽度的比率,0.5
y: 高度的比率,0.5
"""
jscode = """arguments[0].scrollTo(
arguments[1] * arguments[0].scrollWidth,
arguments[2] * arguments[0].scrollHeight
);
"""
self.driver.execute_script(jscode, el, x_percent, y_percent)
def element_scroll_by(self, el:WebElement, x_percent=0, y_percent=0):
"""每次滚动多少个像素点,以元素百分比操作
x: 宽度的比率,0.1
y: 高度的比率,0.1
"""
jscode = """arguments[0].scrollBy(
arguments[1] * arguments[0].scrollWidth,
arguments[2] * arguments[0].scrollHeight
);
"""
self.driver.execute_script(jscode, el, x_percent, y_percent)
class Element:
def scroll(self, x_percent=0, y_percent=0):
"""滚动.
x: 宽度的比率,0.5
y: 高度的比率,0.5
"""
jscode = """arguments[0].scroll({
top:arguments[1] * arguments[0].scrollHeight,
left:arguments[1] * arguments[0].scrollWidth
});
"""
self.driver.execute_script(self, el, y_percent, x_percent)
if __name__ == "__main__":
driver = Chrome()
driver.get("https://example.cypress.io/commands/actions")
d = SeleniumUpAPI(driver)
el = d.driver.find_element("id", "scroll-vertical")
el.location_once_scrolled_into_view
time.sleep(2)
d.scroll_element(el, 0.5, 0.5)
欢迎来到testingpai.com!
注册 关于