UI自动化测试工具 - Selenium
Selenium的安装
安装Python3(默认安装pip并添加环境变量)
pip install selenium
安装最新版本的Chrome
下载对应chrome浏览器的驱动chromedriver.exe,放到Python安装目录的Scripts文件夹下,或者其他有环境变量的地方
脚本测试
1
2
3
4
5from selenium import webdriver
# 初始化浏览器操作
dr = webdriver.Chrome()
# 打开网页
dr.get("http://www.baidu.com")
Webdriver的工作原理
Webdriver会在本地启动一套WebService服务并绑定一个动态端口,脚本运行时通过selenium将请求发送到Webdriver服务端,然后经过不同的浏览器驱动,转换为浏览器指令。
浏览器基本操作
- 方法
- get():打开网页
- find_elements() 获取一组元素
- find_element() 获取一个元素,如果有多个则取第一个
- forward():前进
- back(): 后退
- refresh(): 刷新页面
- maximize_window():最大化窗口
- set_window_size():设置窗口大小
- close(): 关闭当前页面
- quit(): 退出浏览器
- 属性
- title:标题
- current_url:当前网址
- page_source:网页源代码
1 | from selenium import webdriver |
页面元素定位/ 操作
8种基本定位方式
- 通过id定位: find_element_by_id()
- 通过name定位: find_element_by_name()
- 通过class定位: find_element_by_class_name()
- 通过tag定位: find_element_by_tag_name()
- 通过link定位: find_element_by_link_text()
- 通过partial link定位: find_element_by_partial_link_text()
- 通过xpath定位: find_element_by_xpath()
- 通过css定位: find_element_by_css_selector()
find_element_by_*()方法的使用上。这个方法已经被弃用,建议使用find_element()或find_elements()方法替代。
!!! 需要额外导入 from selenium.webdriver.common.by import By
- 通过id定位: element = driver.find_element(By.ID, ‘element_id’)
- 通过name定位: element = driver.find_element(By.NAME, ‘element_name’)
- 通过class定位: element = driver.find_element(By.CLASS_NAME, ‘element_class’)
- 通过tag定位: element = driver.find_element(By.TAG_NAME, ‘element_tag’)
- 通过link定位: element = driver.find_element(By.LINK_TEXT, ‘link_text’)
- 通过partial link定位: element = driver.find_element(By.PARTIAL_LINK_TEXT, ‘partial_link_text’)
- 通过xpath定位: element = driver.find_element(By.XPATH, ‘xpath_expression’)
- 通过css定位: element = driver.find_element(By.CSS_SELECTOR, ‘css_selector’)
定位一组元素
1 | from selenium import webdriver |
分层定位
当一个元素不好定位时,可以先定位到容易定位的父级/祖先级元素,然后使用父级/祖先级元素继续定位
1 | from selenium import webdriver |
页面元素操作
- link 链接
- click()
- input 输入框
- send_keys(): 输入
- clear(): 清空输入框
- get_attribute(“value”): 获取输入框的值
- button 按钮
- click():
- isEnabled(): 是否可用
- submit():type=submit的按钮可以使用submit()同click()用于提交表单
- radio/checkbox 单选/复选框
- click(): 定位到选框可直接点击
- is_displayed(): 用于检查元素是否当前可见
- is_selected(): 检查元素是否选中状态
- 文件上传upload
- 文件下载 - 跳转
- 弹框 - 跳转
- 级联选择 - 一层一层,多级菜单 跳转
- 日期选择器 - 跳转
- select 下拉框(需要用Select) - 跳转
- select_by_index(): 按索引选择选项
- select_by_value(): 按value值选择选项
- select_by_visiable_text(): 按选项名选择选项
1 | from selenium import webdriver |
万能的XPath
XPath即XML路径语言,支持从xml或html中查找元素节点,使用XPath完全可以替代其他定位放式
driver.find_element(By.XPATH,'//*[@id=""]')
等同于find_element_by_id("")
driver.find_element(By.XPATH, '//*[@name=""]')
等同于find_element_by_name("")
driver.find_element(By.XPATH, '//*[@class=""]')
等同于find_element_by_class_name("")
driver.find_element(By.XPATH, '//标签名')
等同于find_element_by_tag_name("标签名")
- ``driver.find_element(By.XPATH, ‘//a[contains(text(),””)]’)
等同于
find_element_by_link_text(“”)` driver.find_element(By.XPATH, '//*[@id=""]')
等同于find_element_by_partial_link_text("")
定位标签
- 绝对路径+索引 /html/body/div/form/div[3] 逐层写 结合index index从1开始
- 相对路径+属性(推荐) //div[@id=”firstdiv”] 支持多属性结合定位
- 通过子标签 //div[a] 包含链接的div
- 通过文本定位 //[text()=”第二个div”] 包含 //[contains(text(), “username”)]
- 通过相对位置 //*[text()=”第二个div”]/../table
- 轴 //[text()=”王五”]/following::a following后面的
//[text()=”用户名”]/following::input 用户名后的第一个输入框
XPath 语法
路径
- 绝对路径: /html/body/div
- //相对路径: //div/form //*/form 路径中可以使用 *代表任意标签
- .当前路径: //div/form/. 等同于//div/form
- ..上级路径: //div/form/.. 等同于//div
索引
- 从1开始: /html/body/div[2] //div[1]/form
属性
- @属性名:定位包含特定属性名的标签, 如
//input[@class]
- @属性名=”属性值”:定位特定属性名=属性值的标签,如
//input[@id="kw"]
- @*=”属性值”:定位任意属性名=属性值的标签, 如
//input[@*='kw']
- 多属性结合定位:
//input[@id="kw" and @class='kw-class']
或//input[@id="kw"][@class="kw-class"]
(and处也支持使用or,表示或)
函数
- text():标签中的文本值,如
//a[text()="百度首页走起~"]
- contains(): 包含,如
//a[contains(text(), "百度首页")]
- starts-with(): 以**开头,如
//a[starts-with(text(), "百度"]
- last(): 最后一个, 如
//div[last()]
轴
- parent: 父标签
- child:子标签
- following: 后面的,如:
//*[text()="用户名"]/following::input[1] # 紧邻文本为用户名的输入框
- preceding:前面的
CSS selector
css选择器, 比xpath快
find_element(By.CSS_SELECTOR, “#firstdiv”)
基本
- id #firstdiv
- class .stuname
- 标签名 div
*
可以标识任意标签
位置 不支持向上
- 下级 #firstdiv>form>div >或空格
- 同级元素
- 索引: :first-child() :nth-child(n) #firstdiv>form>div:nth-child(3)
属性 不支持判断文本
- 属性 [type=”password”] [name=””]
- 属性 ^= 以 开头 $= 以 结尾 *= 包含
- input 常见属性: checked enabled
1 | from selenium import webdriver |
切换窗口
- switch_to.alert(): 切到弹出框
- switch_to.frame(): 切入框架
- switch_to.window(): 切换窗口
- window_handles: 所有窗口句柄 列表
- current_window_handle: 当前窗口句柄
只有两个窗口
1 | all = driver.window_handles # 所有窗口句柄 |
多个窗口
1 | all = driver.window_handles |
动作链 模拟鼠标操作
- click(): 单击
- double_click(): 双击
- context_click(): 右击
- move_to_element(a): 移动到元素a
- drag_and_drop(a,b): 拖放,将a拖动到b元素位置
- click_and_hold(): 单击并按住鼠标左键 release(): 松开鼠标
1 | from selenium.webdriver.common.action_chains import ActionChains |
Keys模拟键盘
!!! 键盘模拟需要额外导入库 from selenium.webdriver.common.keys import Keys
Keys模拟键盘
1
2# 在输入框中输入文本,并按下回车 , 输入问本是可选参数
element.send_keys("Hello", Keys.ENTER)Keys.TAB
: 模拟 Tab 键1
2# 向输入框中追加文本并按下 Tab 键,输入问本是可选参数
element.send_keys("World", Keys.TAB)Keys.SPACE
: 模拟空格键,常用于触发按钮点击或选择复选框等操作1
2
3
4
5
6# 模拟 Ctrl+A (全选)
element.send_keys(Keys.CONTROL, 'a')
# 模拟 Ctrl+C (复制)
element.send_keys(Keys.CONTROL, 'c')
# 模拟 Ctrl+V (粘贴)
element.send_keys(Keys.CONTROL, 'v')Keys.BACK_SPACE
: 删除一个字符(退格键)1
2# 删除输入框中的最后一个字符(相当于按一次退格键)
element.send_keys(Keys.BACK_SPACE)Keys.DELETE
: 删除一个字符(删除键)1
2# 删除输入框中的第一个字符(相当于按一次删除键)
element.send_keys(Keys.DELETE)Keys.ARROW_DOWN
,Keys.ARROW_UP
,keys.ARROW_LEFT
,keys.ARROW_RIGHT
: 分别模拟向下、向上、向左、向右箭头方向键1
2
3
4
5
6from selenium.webdriver.common.keys import Keys
# 在下拉列表中选择第一项,使用箭头向下定位到目标选项然后再按 Enter 键确认选择。
input_element = driver.find_element(By.ID,'dropdown')
input_element.click()
input_element.send_keys(Keys.ENTER)
级联选择
级联选择器,是点击一层才会显示下一层信息。所以需要一层一层点击
1 | from selenium import webdriver |
下拉框定位选择
使用Select 方法
- 定位到下拉列表的元素。
- 使用
Select
类来操作下拉列表。 - 使用
select_by_*
方法选择相应的选项。
1 | from selenium import webdriver |
执行 JavaScript代码
在某些情况下,可能需要执行页面上的 JavaScript 来获取信息或修改页面状态
1 | driver.execute_script("window.scrollTo(0, document.body.scrollHeight);") |
弹出框处理
allert警告框
1 | alert = driver.switch_to.alert() |
confirm确认框
1 | confirm = driver.switch_to.alert() |
propmt提示框
1 | propmt = driver.switch_to.alert() |
BasicAuth授权框
1 | dr.get("http://用户名:密码@www.example.com") |
框架页面处理 - iframe
- iframe: 嵌入在网页body中的单独框架(框架拥有一套独立的html代码)
- frameset: 框架组,包含多个frame
- frame:每个frame引用一个独立的网页
因为frame/iframe框架是一套独立的网页,因此frame/iframe中的元素不能直接定位到
- driver.switch_to.frame(name/id/index/Element)
- name: frame/iframe的name属性
- id: frame/iframe的id属性
- index: 如:0,第一个frame
- Element: 定位到的frame/iframe,再切换到指定frame
- driver.switch_to.parent_frame(): 切换到父级frame, 子frame之间不能相互切换
- driver.switch_to.default_content(): 跳出所有frame
1 | from selenium import webdriver |
截图
可以用来记录、验证页面内容、布局或状态。
1 | # 截取整个页面的截图 |
使用期望条件(except_conditions)
- presence_of_element_located((By.XPATH,””)): 直到元素出现并能定位到
1 | view = wait.until(EC.presence_of_element_located((By.XPATH,'//td[text()="王五"]/../td[4]/a'))) |
修改js,修改网页
隐藏元素
selenium默认定位不到隐藏元素1
2
3
4# 移除相应属性
driver.execute_script(
document.querySelector("#hd1").removeAttribute("hidden");
)不可输入/点击(移除readonly/disable属性)
拖动滚动条(流加载页面) document.documentElement.scrollTop=10000;
富文本框
更改元素样式
1
2
3
4
5
6
7# 通过js修改
js2 = '''
var q = document.getElementById("buttonID");
q.value="hello";
q.style.backgroundColor="red";
'''
driver.execute_script(js2)为一些不好定位的元素添加id
1
2q = document.querySelector("#firstdiv>form>table");
q.id = "table1";
日期控件
- 直接输入(只支持能输入的,点击选择时间控件的不生效)
- 逐个点击
- 用js修改为可输入,输入时间
上传下载
文件上传和文件下载
文件上传
- send_keys()
1
2
3
4# 定位上传文件的input元素
upload_input = dr.find_element_by_xpath("//input[@type='file']")
# 输入文件路径
upload_input.send_keys(r"C:\path\to\file.txt")- 如果网页禁止send_keys(),可以使用js修改元素属性方式进行上传
1
2
3
4# 打开需要上传文件的网页
dr.get("http://www.example.com/upload")
# 直接输入文件路径(使用JavaScript)
dr.execute_script("document.querySelector('input[type=file]').value = 'C:\\path\\to\\file.txt';")通过结合pywinauto的自动化结合操作
当selenuim点击浏览器上传页面,唤醒窗口时,用pywinauto定位窗口进行文件选择。
文件下载
- 通过点击按钮或者链接
- Selenium可能无法直接实现文件上传或下载,可以考虑借助第三方工具来实现。例如,使用AutoIt、Sikuli等工具来模拟键盘操作或屏幕操作来处理文件上传和下载
等待
强制等待
强制等待(Hard Wait)是一种简单粗暴的等待方式,它指定了一个固定的时间来暂停程序的执行,而不管等待的条件是否满足。在 Selenium 中,虽然它不推荐使用,但有时仍然可以根据特定需求使用。
1 | import time |
使用场景
强制等待通常在以下情况下使用:
- 调试和测试:用于调试时暂停执行,观察页面或程序的状态。
- 非常规等待需求:某些特定的场景下,可能需要等待一个固定时间,例如等待页面完全加载或执行 JavaScript 后。
注意事项
- 不推荐使用:Selenium 提供了更智能和灵活的等待机制(显式等待和隐式等待),因此强制等待并不被推荐,特别是在自动化测试中。
- 不灵活:强制等待是固定时间,无法根据实际页面加载速度或条件变化来自动调整等待时间。
- 性能影响:长时间的强制等待可能会增加测试执行的总体时间,并且可能导致测试执行不稳定。
隐式等待
隐式等待是设置一个全局等待时间,在查找元素时,如果 Selenium 没有立即找到元素,则会在指定的等待时间内轮询查找元素,直到找到为止或超时。
- 实现方式:通过
implicitly_wait
方法设置全局等待时间。一旦设置,对后续的所有find_element
或find_elements
方法都生效,直到 WebDriver 被关闭。 - 使用场景:适合应对整个页面加载时间不确定的情况,可以减少代码中显式等待的重复使用。
- 等待时间:设置的时间是最长等待时间,如果在这个时间内找到了元素,则会立即执行后续操作,否则抛出
NoSuchElementException
异常。 - 全局设置:一旦设置,对所有后续的查找元素操作都生效,因此适合用于整个测试过程中一致的等待需求。
1 | driver.implicitly_wait(10) # 设置隐式等待时间为 10 秒 |
条件等待 - 显示等待
条件等待(Conditional Wait)是 Selenium 中一种高级的等待方式,它允许测试脚本根据特定的条件等待页面元素状态的变化或某些预期的结果。与显式等待和隐式等待不同,条件等待提供了更灵活和精确的控制,适用于一些复杂的场景。针对某个特定的条件等待,例如元素的可见性、可点击、出现等。
实现方式:通过
WebDriverWait
类结合expected_conditions
模块中的条件来实现。常见的条件有presence_of_element_located
(元素出现)、element_to_be_clickable
(元素可点击)等。使用场景:适合用于等待特定元素加载完成、页面状态变化等情况。通常在需要等待的具体操作之前使用。
等待时间:需要显式指定等待的最长时间(如例子中的 10 秒),超过这个时间仍未满足条件则抛出
TimeoutException
异常。灵活性:因为可以精确地控制等待条件和时间,因此更为灵活,能够精确地应对不同的等待情况。
实现方式
条件等待依赖于 Selenium 提供的
WebDriverWait
类和expected_conditions
模块,它允许测试脚本根据特定的条件来等待,例如元素的可见性、元素的文本内容变化、元素的点击状态等。1
2
3
4
5
6
7
8
9
10from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
'''
使用 WebDriverWait 类来设置等待的最长时间,并指定等待的条件(例如 visibility_of_element_located、text_to_be_present_in_element 等)
'''
element = WebDriverWait(driver, 10).until(
EC.visibility_of_element_located((By.ID, "element_id"))
)常用的等待条件
presence_of_element_located(locator)
:等待页面中至少一个元素出现。visibility_of_element_located(locator)
:等待元素在页面中可见。element_to_be_clickable(locator)
:等待元素可点击。text_to_be_present_in_element(locator, text)
:等待特定元素中出现特定的文本。title_contains(title)
:等待页面标题包含特定文本。
使用场景
- 复杂的用户交互流程:等待某个按钮变为可点击状态,然后执行点击操作。
- 动态加载的内容:等待某个异步加载的区块出现,然后验证其内容。
- 表单提交和响应:等待页面提交后的特定结果或反馈信息。
使用 JavaScript 等待页面状态
JavaScript 的异步请求或更新页面的状态。可以通过执行 JavaScript 来判断页面是否完成了特定的异步操作,从而等待页面状态的变化。
1 | from selenium.webdriver.common.by import By |
区别总结
1. 精确度比较:
- 强制等待
- 精确度:最低。因为强制等待是固定时间内暂停程序执行,不考虑页面元素状态或条件是否已满足。
- 显示等待
- 精确度:高。显示等待允许程序等待直到特定条件成立,例如元素可见、文本出现等,因此能够精确控制等待的时间点。
- 隐式等待
- 精确度:较低。隐式等待是��局性设置,无法针对特定的元素或操作设定不同的等待时间,只能等待页面加载完成的最长时间。
- 条件等待
- 精确度:非常高。条件等待允许根据具体的页面状态或预期结果等待,可以精确等待某个特定的条件或操作的完成。
2. 使用场景比较:
- 强制等待
- 适用场景:调试时、简单脚本中可能会用到,不建议在自动化测试中大量使用,因为它会导致测试脚本的执行速度变慢。
- 显示等待
- 适用场景:复杂的页面交互、动态内容加载、需要精确等待某个特定条件的场景下使用,如等待元素可见、文本出现等。
- 隐式等待
- 适用场景:简单的页面加载等待,适合整体页面加载较为稳定且操作不需要太多精确等待的情况。
- 条件等待
- 适用场景:需要根据页面实际状态或特定操作的完成来决定下一步操作的复杂场景,例如等待元素状态变化、异步操作完成等。
3. 异常处理比较:
- 强制等待
- 异常处理:不适用异常处理,因为它是通过固定时间暂停程序执行,不会抛出异常。
- 显示等待
- 异常处理:适用
TimeoutException
异常处理,当等待超时时会抛出该异常,可以在异常处理中进行重试或其他操作。
- 异常处理:适用
- 隐式等待
- 异常处理:不直接抛出等待超时的异常,但操作超过设置的等待时间后可能会导致元素定位失败或其他异常,需要进行适当的错误处理。
- 条件等待
- 异常处理:与显示等待类似,适用
TimeoutException
异常处理,当等待条件不满足时会抛出异常,可以通过异常处理来捕获并处理。
- 异常处理:与显示等待类似,适用
浏览器选项
在创建浏览器驱动实例时,
self.dr = webdriver.Chrome(options=option)
。可以通过指定不同的选项Options
来配置浏览器的行为。
Chrome
1 | from selenium import webdriver |
Edge
1 | from selenium import webdriver |
Firefox
1 | from selenium import webdriver |
常见错误怎么处理
- 看报什么错 1. 元素定位不到 2. 元素不可见
- 看有没有frame/iframe
- 操作前 sleep()
- 看看元素或者上级/上上级元素是否不可及
框架
rameset: 框架组,用来布局框架
frame: 具体的一个框架,一般放在frameset中
iframe: 内联框架, 可以嵌入到其他网页的body中
切入 层层切入
switch_to.frame() # 只用切 iframe/frame 不用切frameset
- id
- name
- index
- 定位到的frame元素 find_element_by_id(“parent”)
切出
- switch_to.parent_frame() # 跳到上级 多层框架推荐使用
- switch_to.defaut_content() # 跳出所有 一层框架推荐使用
- 也可以用switch_to.parent_frame()跳出框架
1 | from selenium import webdriver |
结合浏览器插件使用
使用浏览器插件可以极大地增强 Selenium 自动化测试的功能和灵活性。
Chrome 插件
Chrome DevTools Protocol (CDP)
Chrome DevTools Protocol 允许开发者与 Chrome 浏览器进行通信和交互,通过 CDP 可以实现诸如网络监控、性能分析、页面审查等高级功能。在 Selenium 中,可以通过 PyChromeDevTools 或
selenium-requests
等库结合 CDP 来实现更复杂的操作。案例:使用 PyChromeDevTools 来启用网络截获和模拟网络条件
1
2
3
4
5
6
7
8
9
10from pychromedevtools import Chrome
chrome = Chrome()
chrome.Network.enable()
chrome.Network.emulateNetworkConditions(
offline=False,
latency=200,
downloadThroughput=1024 * 1024,
uploadThroughput=512 * 1024
)Chrome 浏览器扩展
可以通过安装 Chrome 浏览器扩展来模拟用户行为或改变页面内容,例如广告拦截、自动填充表单等。这些扩展可以通过 Selenium 控制浏览器来加载和使用。
案例:使用 Chrome 浏览器扩展可以拦截广告或修改页面内容
1
2
3
4
5
6
7
8
9
10
11from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options
chrome_options = Options()
chrome_options.add_extension('/path/to/extension.crx') # 添加扩展的路径
driver_path = '/path/to/chromedriver'
service = Service(driver_path)
driver = webdriver.Chrome(service=service, options=chrome_options)
driver.get('https://example.com')
Firefox 插件
使用 Firefox 扩展
Firefox 扩展可以通过
moz:firefoxOptions
配置项来添加到 FirefoxProfile 中,然后传递给 Selenium WebDriver。案例:使用 Firefox 扩展来模拟用户操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15from selenium import webdriver
from selenium.webdriver.firefox.service import Service
from selenium.webdriver.firefox.options import Options
from selenium.webdriver.firefox.firefox_profile import FirefoxProfile
firefox_profile = FirefoxProfile()
firefox_profile.add_extension('/path/to/extension.xpi') # 添加扩展的路径
firefox_options = Options()
firefox_options.profile = firefox_profile
driver_path = '/path/to/geckodriver'
service = Service(driver_path)
driver = webdriver.Firefox(service=service, options=firefox_options)
driver.get('https://example.com')
自动浏览器驱动下载 - (webdriver-manager)
webdriver-manager
是一个方便用于自动下载和管理浏览器驱动程序,例如 ChromeDriver、GeckoDriver 等,这些驱动程序用于 Selenium WebDriver 控制相应的浏览器。
安装
1 | $ pip install webdrivermanager |
使用 webdriver-manager - ChromeDriver (Chrome)
1 | from selenium import webdriver |
其他浏览器使用
GeckoDriver(Firefox)
1
2
3
4
5
6
7from selenium import webdriver
from webdriver_manager.firefox import GeckoDriverManager
# 使用 GeckoDriverManager 自动下载和配置 GeckoDriver
# 将Firefox浏览器驱动的下载源改为国内镜像
GeckoDriverManager(url='https://npm.taobao.org/mirrors/geckodriver/').install()
driver = webdriver.Firefox()EdgeDriver(Microsoft Edge)
1
2
3
4
5
6from selenium import webdriver
from webdriver_manager.microsoft import EdgeChromiumDriverManager
# 将Edge浏览器驱动的下载源改为国内镜像
EdgeChromiumDriverManager(url='https://npm.taobao.org/mirrors/edgedriver/').install()
driver = webdriver.Edge()
匹配本机浏览器当前版本
要匹配本机浏览器的当前版本并自动下载相应版本的浏览器驱动,可以使用
webdriver-auto-update
库。这个库可以自动更新匹配本地浏览器版本的驱动程序,省去了手动查找和下载的步骤。
安装 webdriver-auto-update
1 | $ pip install webdriver-auto-update |
示例代码
1 | from selenium import webdriver |