封装自动化方法:selenuimtools.py
from selenium.common import TimeoutException, InvalidArgumentException, JavascriptException
from selenium.webdriver.common.by import By
from selenium.webdriver.remote.webelement import WebElement
from selenium.webdriver.support.wait import WebDriverWait
import tools.log as Log
from tools.log import MyException
from time import sleep
# selenuim 自动化方法脚本
# 封装组件相关操作
class ElementOperate:
def __init__(self, driver, find_timeout: float = 5, sleep_time: float = 1.5):
self.driver = driver
# 等待组件最长时间
self.find_timeout = find_timeout
# 操作之间的间隔时间
self.sleepT = sleep_time
# 获取组件
def find_element(self, find_type, element: str) -> WebElement:
el = None
by = None
driver = self.driver
# 类型转换
if find_type == "id":
by = By.ID
if find_type == "xpath":
by = By.XPATH
elif find_type == "link":
by = By.LINK_TEXT
elif find_type == "partial_link":
by = By.PARTIAL_LINK_TEXT
elif find_type == "name":
by = By.NAME
elif find_type == "tag":
by = By.TAG_NAME
elif find_type == "class":
by = By.CLASS_NAME
elif find_type == "css":
by = By.CSS_SELECTOR
try:
if by is None:
# 如果用户未按要求输入,则停止查询
raise MyException(f"查找类型错误,类型【{find_type}】不符合要求!!!")
# 显式等待组件出现,
el = WebDriverWait(driver=driver, timeout=self.find_timeout).until(
lambda dv: dv.find_element(by, element), f"查找组件【{element}】超时,组件未在指定时间内出现"
)
except TimeoutException as e:
Log.error(e.msg)
except MyException as e:
Log.error(e)
finally:
return el
# 点击组件
def click(self, by: str, element: str) -> bool:
el_button = self.find_element(by, element)
# 判断是否找到组件
if el_button:
el_button.click()
return True
return False
# 输入框输入
def send_keys(self, by: str, element: str, context: str) -> bool:
el_input = self.find_element(by, element)
# 判断是否找到组件
if el_input:
# 先清除输入框内容
el_input.clear()
el_input.send_keys(context)
return True
return False
# 页面滚动
# scroll_y: 滑动高度百分比,不选则滑动置底
# times: 完成滑动所需时间,不选则立即完成滑动
# sleep_time: 多少秒后开始执行
# is_await:是否等待滑动结束
def scrollTo(self, scroll_y: float = None,
times: int = 0,
sleep_time: float = None,
is_await: bool = True) -> bool:
# 等待页面加载完成,如果页面未加载完就执行,会导致scrollHeight属性错误,变成上一页面的高度而不是当前页面的高度
sleep(self.sleepT if sleep_time is None else sleep_time)
# 时间为负数时设置为0
times = 0 if times < 0 else (times * 1000)
# 滚动条高度
if scroll_y is None:
# 获取页面内容高度
scroll_y = self.execute_script("return document.body.scrollHeight")
# 滚动条滑动脚本
js_code = '''
const ScrollTop = (number = 0, time) => {
if (!time) {
document.body.scrollTop = document.documentElement.scrollTop = number;
return number;
}
const spacingTime = 20;
let spacingInex = time / spacingTime;
let nowTop = document.body.scrollTop + document.documentElement.scrollTop;
let everTop = (number - nowTop) / spacingInex;
let scrollTimer = setInterval(() => {
if (spacingInex > 0) {
spacingInex--;
ScrollTop(nowTop += everTop);
} else {
clearInterval(scrollTimer);
}
}, spacingTime);
};
'''
js_code = js_code + f"ScrollTop({scroll_y}, {times});"
self.execute_script(js_code=js_code)
position = 0
# 判断滚动条是否已停止滚动
while is_await:
# 每隔0.1s检测一次
sleep(0.1)
# 获取当前位置
new_position = self.execute_script(
'return document.body.scrollTop + document.documentElement.scrollTop;')
if new_position == position:
is_await = False
position = new_position
return True
async def async_scrollTo(self):
pass
# js 脚本执行,默认同步
def execute_script(self, js_code: str, is_async: bool = False):
if js_code is None or js_code == "":
raise MyException("js脚本为空")
try:
if not is_async:
# 同步执行:适合执行时间较短的js。webdriver 会等待执行结果,然后继续执行后续代码
return self.driver.execute_script(js_code)
else:
# 异步执行:通常执行时间比较长的js。webdriver 不需要等待执行结果,直接执行后续代码
return self.driver.execute_async_script(js_code)
except (InvalidArgumentException, JavascriptException):
Log.error(f"js脚本异常,无法被执行,当前执行脚本内容如下:\n {js_code}")
except MyException as e:
Log.error(e)
自定义工具类:tools.py
# 输出文本修饰
def error(msgs):
print(f"\033[31m****异常报错:{msgs}\033[0m")
def info(msgs):
print(f"\033[32m****信息输出:{msgs}\033[0m")
def warn(msgs):
print(f"\033[33m****警告信息:{msgs}\033[0m")
# 自定义异常类 MyException,配合log使用
class MyException(Exception): # 继承异常类
def __init__(self, msg): # 重写父类的__init__方法
self.msg = msg
测试代码
from selenium import webdriver
from tools.selenuimtools import ElementOperate
class AutoWebTest:
def __init__(self):
# 设置浏览器不自动关闭
options = webdriver.ChromeOptions()
options.add_experimental_option('detach', True)
self.driver = webdriver.Chrome(options=options)
# 设置浏览器全屏
self.driver.maximize_window()
# 打开网站
def open_web(self, request):
self.driver.get(request)
driver = ElementOperate(self.driver)
driver.send_keys('id', "kw", "selenium")
driver.click('id', 'su')
driver.scrollTo(times=10,is_await=False)
# 当设置is_await为FALSE时,会看到浏览器还在滑动滚动条,但程序已向下执行
# 而为TRUE或者不设置时,会看到程序等待浏览器滑动滚动条完毕后,才会向下执行
print("已执行")
if __name__ == "__main__":
str = "https://www.baidu.com"
AutoWebTest().open_web(str)
js滑动脚本解析
const ScrollTop = (number = 0, time) => {
if (!time) {
document.body.scrollTop = document.documentElement.scrollTop = number;
return number;
}
const spacingTime = 20; // 设置循环的间隔时间 值越小消耗性能越高
let spacingInex = time / spacingTime; // 计算循环的次数
let nowTop = document.body.scrollTop + document.documentElement.scrollTop; // 获取当前滚动条位置
let everTop = (number - nowTop) / spacingInex; // 计算每次滑动的距离
let scrollTimer = setInterval(() => {
if (spacingInex > 0) {
spacingInex--;
ScrollTop(nowTop += everTop);
} else {
clearInterval(scrollTimer); // 清除计时器
}
}, spacingTime);
};
滚动条滑动主要实现在scrollTo方法中,其他方法只是对selenium操作方法的二次封装;
脚本执行调用execute_script方法,值得注意的是,虽然execute_script是同步执行脚本,但执行滑动js脚本,触发定时器方法后,走完后续代码就会返回结果,而不会等定时器(滑动效果)结束;
如果希望方法能等待滑动结束,可通过循环执行js脚本赋值document.body.scrollTop
或其他方式实现,也可以像我一样,在后面写个循环不停去判断当前滑动条位置,以此判断滑动动画是否已结束