selenium简介

selenium是一款用于web浏览器的自动化工具,支持各大主流浏览器和编程语言。通过自动化代码可以模拟人的操作行为,如点击,输入等操作

selenium下载

pip install selenium

可以直接打开终端下载,因为新版本的selenium4.0会自动检索你电脑上的浏览器和版本,然后下载对应的驱动,不过也可以自己手动去浏览器下载对应版本的浏览器驱动(例如谷歌浏览器驱动,和edge浏览器驱动)

接下来,举一个例子作为开篇讲解

# 1. 导入Selenium的浏览器驱动工具
from selenium import webdriver
# 2. 导入元素定位工具(用来找网页上的输入框、按钮)
from selenium.webdriver.common.by import By
# 3. 导入时间等待工具(让程序暂停,给网页加载时间)
from time import sleep

# 4. 创建Edge浏览器的“控制器”
driver = webdriver.Edge()
# 5. 让浏览器打开百度网址
driver.get("https://www.baidu.com")
# 6. 程序暂停1秒(等待网页加载)
sleep(1)
# 7. 找到输入框 → 输入文字 selenium
driver.find_element(By.ID,"chat-textarea").send_keys("selenium")
# 8.点击搜索
driver.find_element(By.ID,"chat-submit-button").click()
# 9. 再暂停1秒(能看到输入效果)
sleep(1)

selenium元素定位

1.通过ID定位

通过selenium自动化工具打开的百度,可以看到它的搜索的标签是<textarea>,id=chat-textarea所以最终的效果就是打开edge浏览器->打开百度网页->在百度输入框输入selenium

2.通过NAME和CLASS_NAME

# 1. 导入Selenium浏览器驱动,用来控制Edge/Chrome浏览器
from selenium import webdriver
# 2. 导入元素定位工具,并重命名为by(用来找网页上的输入框、按钮)
from selenium.webdriver.common.by import By as by
# 3. 导入等待工具,让程序暂停,给网页加载时间
from time import sleep

# 4. 启动Edge浏览器,创建控制器driver(自动化机器人)
driver=webdriver.Edge()
# 5. 让机器人打开百度首页
driver.get("https://www.baidu.com")
# 6. 程序暂停1秒,等待页面初步加载
sleep(1)
# 7. 浏览器窗口最大化(全屏显示,防止元素被遮挡)
driver.maximize_window()
# 8. 程序暂停3秒,等待页面完全加载
sleep(3)
# 9. 通过类名找到百度聊天输入框,并自动输入文字python
driver.find_element(by.CLASS_NAME,"chat-input-scroll-style").send_keys("python")
# 10. 程序暂停3秒,方便查看输入效果
sleep(3)

3.通过TAG_NAME定位

写法如上,只不过通过标签名来定位

4.通过LINK_TEXT和PARTIAL_LINK_TEXT定位

这是通过<a></a>标签来定位的,也就是通过超链接来定位

例子(百度首页)

链接文字是:新闻你写 **“新”、“闻”、“新闻”** 都能找到:

# 必须写完整的“新闻”,才能找到
driver.find_element(By.LINK_TEXT, "新闻").click()
# 包含“新”字就能匹配到新闻链接
driver.find_element(By.PARTIAL_LINK_TEXT, "新").click()

但是link_text是精确匹配,partial_link_text是模糊匹配的,但是如果模糊查询不准确的话存在多个的话,程序会报错

5.通过CSS_SELECTOR定位

这是通过css选择器定位

by.CSS_SELECTOR

  • 意思:使用 CSS 选择器 定位网页元素
  • CSS 选择器是 Selenium 里最灵活、最强大的定位方式
  • 可以按:标签、class、id、元素属性、模糊匹配 来找元素
选择器类型语法前端 HTML 示例CSS 选择器写法说明
ID 选择器#id值<input id="kw">#kw最快、唯一,首选!
类选择器.class值<button class="s_btn">.s_btn匹配 class 属性,可重复
标签选择器标签名<input>input匹配标签,范围大
属性选择器[属性="值"]<input name="wd">[name="wd"]匹配自定义属性
组合选择器标签 + 属性 / ID<input id="kw">input#kw最精准、最常用

例如

from selenium import webdriver
from selenium.webdriver.common.by import By as by
from time import sleep
driver=webdriver.Edge()
driver.get("http://www.baidu.com")
sleep(1)
driver.maximize_window()
sleep(3)
driver.find_element(by.CSS_SELECTOR,"#chat-textarea").send_keys("什么是软件测试")
sleep(3)


css属性匹配三剑客

[herf^="http"]        ->以http开头

[herf$="com"]        ->以com结尾

[herf*='unf']        ->包含unf字符

子代选择器和后代选择器

# 1. 导入浏览器驱动工具
from selenium import webdriver
# 2. 导入元素定位工具,重命名为by
from selenium.webdriver.common.by import By as by
# 3. 导入等待工具
from time import sleep

# 4. 启动Edge浏览器
driver=webdriver.Edge()
# 5. 打开百度首页
driver.get("http://www.baidu.com")
# 6. 等待1秒,页面初步加载
sleep(1)
# 7. 浏览器窗口最大化
driver.maximize_window()
# 8. 等待3秒,页面完全加载
sleep(3)

# 9. 核心:批量定位百度左上角所有导航链接(新闻、地图、视频等)
elements=driver.find_elements(by.CSS_SELECTOR,"div#s-top-left>a")
# 10. 循环遍历每一个找到的链接
for element in elements:
    # 11. 打印链接上显示的文字
    print(element.text)

# 12. 等待3秒
sleep(3)

elements=driver.find_elements(by.CSS_SELECTOR,"div#s-top-left>a")讲解

div#s-top-left组合选择器,选择id为s-top-left的div标签

div#s-top-left>a选择id为s-top-left的div标签的直接子代选择器中的a标签

例如百度的导航栏

<div id="s-top-left">
  <a>新闻</a>   <!-- 亲儿子 -->
  <a>地图</a>   <!-- 亲儿子 -->
</div>

注意区分div#s-top-left a(空格)

这是子代选择器(亲儿子,孙子,重孙子),匹配范围更大一些

from selenium import webdriver
from selenium.webdriver.common.by import By as by
from time import sleep
driver=webdriver.Edge()
driver.get("http://www.baidu.com")
sleep(1)
driver.maximize_window()
sleep(3)
# 定位地图链接紧跟着的后面的a标签
elements=driver.find_element(by.CSS_SELECTOR,"a[href='http://map.baidu.com']+a")
# 打印上述标签的属性值
print(elements.get_attribute("href"))
sleep(3)


# 打印标签的属性值
get_attribute()
# 导入浏览器控制工具
from selenium import webdriver
# 导入元素定位工具,重命名为by
from selenium.webdriver.common.by import By as by
# 导入等待工具
from time import sleep

# 启动Edge浏览器
driver=webdriver.Edge()
# 打开百度首页
driver.get("http://www.baidu.com")
# 等待1秒,页面初步加载
sleep(1)
# 浏览器窗口最大化
driver.maximize_window()
# 等待3秒,页面完全加载
sleep(3)

# 【核心新知识点】CSS通用兄弟选择器:找到地图链接 后面所有的同级链接
elements=driver.find_elements(by.CSS_SELECTOR,"a[href='http://map.baidu.com']~a")
# 循环遍历所有找到的链接
for element in elements:
    # 打印每个链接的文字
    print(element.text)
# 等待3秒
sleep(3)

XPATH定位

绝对路径(不推荐)

从顶层的html一层一层的往下写一层一层、从上到下写完整的层级路径,以 单斜杠 / 开头。

例如定位右上角的新闻标签,一步一步都不能出错:/html/body/div[1]/div[1]/div[1]/div[1]/a[3]

而且代码结构一改,就会报错

相对路径(推荐)

不从根节点开始,直接匹配页面中符合条件的元素,以 双斜杠 // 开头,灵活、稳定。

//标签名[@属性="属性值"]/子标签
from selenium import webdriver
from selenium.webdriver.common.by import By as by
from time import sleep
driver=webdriver.Edge()
driver.get("http://www.baidu.com")
sleep(1)
driver.maximize_window()
sleep(3)
driver.find_element(by.XPATH,"//div[@id='s-top-left']/a[1]").click()
sleep(3)

通过文字定位

from selenium import webdriver
from selenium.webdriver.common.by import By as by
from time import sleep
driver=webdriver.Edge()
driver.get("http://www.baidu.com")
sleep(1)
driver.maximize_window()
sleep(3)
driver.find_element(by.XPATH,"//*[text()='换一换']").click()
sleep(3)

这是最关键的部分,拆成 4 个知识点:

1.//

XPath 相对路径

2.*

匹配所有标签(不管是 a 标签、span 标签、button 标签,全都找)

不过还是建议少用通配符*,容易匹配看不见的垃圾元素

3.[]

XPath 筛选条件(相当于:满足 xxx 条件的元素)

4.text()='换一换'

按文字匹配:找到页面显示文字完全等于 换一换 的元素

✅ 整句翻译:在整个网页里,查找 任何标签中,文字正好是「换一换」的元素

click():对找到的元素执行 点击操作

这里需要区分的是,什么时候有@?

@是给标签属性用的,例如:

<a id="kw" class="s_btn" href="http://xxx">百度一下</a>

这里的id,class,herf都是标签属性,而

<span>换一换</span>

这里的换一换是属于标签文本,不用加@

文本模糊匹配

driver.find_element(by.XPATH,"//span[contains(text(),'奋力谱写')]").click()

selenium基础操作

# 1. 导入浏览器驱动,控制Edge
from selenium import webdriver
# 2. 导入元素定位工具,重命名为by
from selenium.webdriver.common.by import By as by
# 3. 导入键盘按键工具(模拟回车、空格、删除等)
from selenium.webdriver.common.keys import Keys
# 4. 导入等待工具
from time import sleep

# 5. 启动Edge浏览器
driver=webdriver.Edge()
# 6. 打开百度首页
driver.get("http://www.baidu.com")
# 7. 等待1秒,页面初步加载
sleep(1)
# 8. 窗口最大化
driver.maximize_window()
# 9. 等待3秒,页面完全加载
sleep(3)

# 10. 找到百度AI输入框,把这个元素存到变量textarea里(方便重复使用)
textarea=driver.find_element(by.ID,"chat-textarea")
# 11. 向输入框中输入文字:什么是python自动化?
textarea.send_keys("什么是python自动化?")
# 12. 等待3秒,查看输入效果
sleep(3)

# 13. 核心!模拟键盘【回车键】,提交问题(替代点击按钮)
textarea.send_keys(Keys.ENTER)
# 14. 等待3秒,查看搜索结果
sleep(3)

更高级的等待方式

我们之前用的sleep(3)是 强制等待:不管元素加载没加载好,死等 3 秒,浪费时间

但是我们现在用更高级的只能等待

# 1. 导入浏览器驱动(控制Edge)
from selenium import webdriver
# 2. 导入元素定位工具(By.ID/CSS_SELECTOR)
from selenium.webdriver.common.by import By
# 3. 导入【显式等待】核心类(智能等待工具)
from selenium.webdriver.support.ui import WebDriverWait
# 4. 导入等待条件(判断元素是否出现、可点击等)
from selenium.webdriver.support import expected_conditions as EC

# 5. 启动Edge浏览器
driver = webdriver.Edge()
# 6. 打开百度首页
driver.get("http://www.baidu.com")
# 7. 窗口最大化(避免元素遮挡)
driver.maximize_window()

# ===================== 核心新知识:显式等待 =====================
# 8. 创建等待对象:最多等10秒,每0.5秒检查一次条件
wait = WebDriverWait(driver, 10)

# 9. 等待【输入框元素出现】,出现后立即返回元素,不浪费时间
input_box = wait.until(EC.presence_of_element_located((By.ID, "chat-textarea")))
# 10. 向输入框输入文字
input_box.send_keys("什么是selenium自动化?")

# 11. 等待【提交按钮可点击】,满足条件后再点击(彻底解决报错!)
submit_btn = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, "button#chat-submit-button")))
# 12. 点击按钮
submit_btn.click()

# 后续操作...

WebDriverWait是一个等待工具,我们创建一个等待对象,最多等10秒,超过十秒则报错

EC则是一个判断条件,满足了立马执行,不会浪费时间,例如:

等待条件作用适用场景
presence_of_element_located元素出现在 DOM 中(不保证可见)仅检查元素是否存在,不适合交互
visibility_of_element_located元素可见(宽高 > 0,display 非 none)输入框、文本等需要可见的元素
element_to_be_clickable元素可点击(可见、启用、无遮挡)按钮、链接等需要点击的元素

selenium基本操作

截图+获取网页名

from time import sleep

from selenium import webdriver
from selenium.webdriver.common.by import By as by, By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
driver=webdriver.Edge()
wait=WebDriverWait(driver,10)
driver.get("http://www.baidu.com")
driver.maximize_window()

input_box=wait.until(EC.presence_of_element_located((By.ID,"chat-textarea")))
input_box.send_keys("什么是python自动化?")
input_box.send_keys(Keys.ENTER)
sleep(5)
url1=driver.current_url
print(url1)
print(driver.title) #打印网页名
driver.save_screenshot("搜索界面截图.png") # 截图当前页面并且命名

driver.quit()

窗口切换

元素找不到的原因之一:要定位的元素不在当前页面,需要切换窗口

Selenium 默认只停留在原来的百度首页,不会自动切换到新标签

点击百度首页的新闻后,浏览器会新开一个标签页

  • Selenium 默认停留在原百度首页,不会自动切换

  • 必须用 driver.window_handles 获取所有标签页 ID

  • 用 driver.switch_to.window(title[-1]) 切换到新标签页

# 1. 导入固定等待工具
from time import sleep
# 2. 导入浏览器驱动
from selenium import webdriver
# 3. 导入元素定位工具
from selenium.webdriver.common.by import By 
# 4. 导入键盘工具(本段未使用)
from selenium.webdriver.common.keys import Keys
# 5. 导入显式等待(智能等待)
from selenium.webdriver.support.ui import WebDriverWait
# 6. 导入等待条件
from selenium.webdriver.support import expected_conditions as EC

# 7. 启动Edge浏览器
driver=webdriver.Edge()
# 8. 创建智能等待器:最长等待10秒
wait=WebDriverWait(driver,10)
# 9. 打开百度首页
driver.get("http://www.baidu.com")
# 10. 窗口最大化
driver.maximize_window()

# 11. 【核心1】等待元素出现 → 模糊匹配文字含"新"的链接(新闻)→ 点击
# 点击后会**自动打开新标签页**
wait.until(EC.presence_of_element_located((By.PARTIAL_LINK_TEXT,"新"))).click()

# 12. 【核心2】获取浏览器**所有标签页/窗口的唯一标识**(窗口句柄)
handls=driver.window_handles
# 13. 打印所有窗口句柄(列表格式,看有几个标签页)
print(handls)

# 14. 【核心3】切换到**最后一个打开的新标签页**
driver.switch_to.window(handls[-1])

# 15. 在新的新闻标签页中,找到含"苦练内功"的链接并点击
wait.until(EC.presence_of_element_located((By.PARTIAL_LINK_TEXT,"苦练内功"))).click()

# 16. 等待3秒,查看效果
sleep(3)
# 17. 关闭浏览器,结束程序
driver.quit()

窗口滚动

绝对滚动

# 导入固定等待工具
from time import sleep
# 导入浏览器驱动
from selenium import webdriver
# 导入元素定位工具
from selenium.webdriver.common.by import By
# 导入键盘工具(本段代码没用到,可删)
from selenium.webdriver.common.keys import Keys
# 导入智能等待(显式等待)
from selenium.webdriver.support.ui import WebDriverWait
# 导入等待条件
from selenium.webdriver.support import expected_conditions as EC

# 1. 启动Edge浏览器
driver = webdriver.Edge()
# 2. 创建智能等待器:最长等待10秒,每隔0.5秒检查一次元素
wait=WebDriverWait(driver,10)
# 3. 打开百度首页(https加密版,更安全)
driver.get("https://www.baidu.com")
# 4. 浏览器窗口最大化,避免元素被遮挡
driver.maximize_window()

# 5. 智能等待:找到文字包含"新"的链接(新闻)→ 出现后立刻点击
# 点击后会【自动打开新的标签页:百度新闻】
wait.until(EC.presence_of_element_located((By.PARTIAL_LINK_TEXT,"新"))).click()

# 6. 获取浏览器【所有标签页的句柄(唯一ID)】,存入变量title
# 注意:变量名不规范,应该叫handles,不是title(title是网页标题)
title=driver.window_handles

# 7. 切换到【最新打开的标签页】(百度新闻页)
# title[-1] = 列表最后一个元素 = 刚打开的新闻标签页
driver.switch_to.window(title[-1])

# ===================== 新知识点:JS滚动页面 =====================
# 8. 编写JS代码:滚动到页面【最底部】
# window.scrollTo(横坐标0, 纵坐标100000) 数字越大滚得越下
js='window.scrollTo(0,100000)'
# 9. 驱动执行JS脚本,让页面滚动到底部
driver.execute_script(js)

# 10. 在新闻页面底部,智能等待找到包含"知道年轻人"的链接并点击
wait.until(EC.presence_of_element_located((By.PARTIAL_LINK_TEXT,"知道年轻人"))).click()

# 11. 等待3秒,查看点击效果
sleep(3)
# 12. 关闭整个浏览器,结束程序
driver.quit()

思考一个问题,为什么需要滚动?你去一个网页找一个链接,虽然说没在当前的可视的窗口,但是找不到吗?

要搞清楚这个问题,需要理解selenium的概念,模拟人的操作,如果是你,这个链接在这个页面的最底下(元素在屏幕之外),你当前看不到,你能点击吗?同理,你做不到的,selenium同样做不到

为什么写js代码?

js='window.scrollTo(0,100000)'

不是python代码,而是一段javascript代码,存成了一个字符串(浏览器指令)

driver.execute_script(js)

让selenium拿着这串JS指令,丢给浏览器去执行

python不会滚动页面,浏览器会JS,所以丢给浏览器自己去执行

scrollTo(x, y) 的意思

让页面滚动到:横向 x 像素、纵向 y 像素的位置

更标准写法

driver.execute_script("window.scrollTo(0, document.body.scrollHeight)")

document.body.scrollHeight = 网页实际总高度

相对滚动

写法名字意思(人话)
scrollTo(x,y)绝对位置滚动直接跳到网页的 x,y 坐标处(不管现在在哪)
scrollBy(x,y)相对位置滚动在现在的位置基础上,再滚 x,y 像素

selenium嵌套页面

# 1. 导入所有需要的工具包
from selenium import webdriver  # 控制浏览器
from selenium.webdriver.support.ui import WebDriverWait  # 智能等待
from selenium.webdriver.support import expected_conditions as ec  # 等待条件
from selenium.webdriver.common.by import By  # 元素定位方式
from time import sleep  # 强制等待

# 2. 初始化Chrome浏览器,窗口最大化,打开LOL官网下载页
driver = webdriver.Chrome()
driver.maximize_window()
driver.get("https://lol.qq.com/download.shtml")
# 创建智能等待器,最长等10秒
wait = WebDriverWait(driver, 10)

# ===================== 第一步:点击登录按钮 =====================
# 等待【登录】按钮可点击 → 点击,弹出QQ登录弹窗
wait.until(ec.element_to_be_clickable((By.PARTIAL_LINK_TEXT, "登录"))).click()
sleep(3)  # 等待弹窗加载完成

# ===================== 第二步:切换嵌套iframe(核心难点) =====================
# QQ登录弹窗**不是普通页面**,是嵌套在网页里的【iframe内嵌框架】
# 必须先切换进iframe,才能操作里面的元素

# 切换第一层iframe:根据class定位 loginframe
driver.switch_to.frame(wait.until(ec.presence_of_element_located((By.CLASS_NAME, "loginframe"))))
sleep(2)
# 切换第二层iframe:根据ID定位 ptlogin_iframe(双层嵌套)
driver.switch_to.frame(wait.until(ec.presence_of_element_located((By.ID, "ptlogin_iframe"))))

# ===================== 第三步:点击注册账号 =====================
# 现在已经进入iframe内部,可以找到并点击【注册账号】链接
wait.until(ec.element_to_be_clickable((By.LINK_TEXT, "注册账号"))).click()
sleep(3)  # 等待新窗口打开

# ===================== 🔥 核心修复:退出iframe(你之前报错的根源) =====================
# 规则:只要进入了iframe,操作完成后**必须退出**!
# 切换新窗口不会自动退出iframe,不退出就找不到新页面的元素
driver.switch_to.default_content()

# ===================== 第四步:切换到新打开的注册窗口 =====================
# 点击注册后,浏览器新建了一个标签页,切换到最新的窗口
driver.switch_to.window(driver.window_handles[-1])
# 等待页面标题包含「QQ注册」,确认页面加载完成
wait.until(ec.title_contains("QQ注册"))

# ===================== 第五步:填写QQ注册表单 =====================
# 1. 输入昵称:等待输入框可见 → 输入内容
wait.until(ec.visibility_of_element_located((By.CSS_SELECTOR, "input.content-input[placeholder='输入昵称']"))).send_keys("没钱的弹药库")
# 2. 输入QQ密码
wait.until(ec.visibility_of_element_located((By.CSS_SELECTOR, "input[placeholder='输入QQ密码']"))).send_keys("WYH20050916")
# 3. 输入手机号
wait.until(ec.visibility_of_element_located((By.CSS_SELECTOR, "input[placeholder='输入手机号码']"))).send_keys("15099729257")

# 勾选用户协议(等待可点击 → 点击)
wait.until(ec.element_to_be_clickable((By.CSS_SELECTOR, "div.q-checkbox__icon"))).click()
# 点击【创建账号】按钮
wait.until(ec.element_to_be_clickable((By.CSS_SELECTOR, "div[dt-eid='em_bas_create_account_btn']"))).click()

# 等待3秒查看效果,关闭浏览器
sleep(3)
driver.quit()

大家可以自己动手实操一下,实操的时候一定要去看网页的html的设计

什么叫内置框架iframe?

就类似于看视频中的画中画,一个网页中真真实实的嵌套一个小网页,这个小网页有着自己的html,就比如打开英雄联盟网页点击登录后弹出的 QQ 登录窗口 = iframe 内嵌网页,这个登录窗口不是官网自己写的,而是直接把腾讯的登录qq.com嵌入进来

易错点

Selenium 操作网页,有 两个完全独立、互不干扰的定位权限:

  1. 窗口定位:你在哪个标签页(百度首页?新闻页?注册页?)

  2. iframe 定位:你在当前标签页的哪个文档(主页面?内嵌 iframe?)

switch_to.window() 只改窗口定位,不改 iframe 定位!

  • 只要进过 iframe,切换窗口前,必须退出 iframe

  • switch_to.window() 只换标签页,不换 iframe 权限

  • switch_to.default_content() = 万能解锁,退出所有内嵌框架

下拉单选框

注意是select标签

<!DOCTYPE html>
<html>

<body>
        <h3>城市选择下拉框</h3>
        <!-- 原生select下拉框 -->
        <select id="city">
            <option value="bj">北京</option>
            <option value="sh">上海</option>
            <option value="gz">广州</option>
            <option value="sz">深圳</option>
        </select>

</body>

</html>

这是效果

from selenium import webdriver
from selenium.webdriver.common.by import By
# 导入Select类
from selenium.webdriver.support.ui import Select
from time import sleep

# 打开本地下拉框测试文件
driver = webdriver.Chrome()
driver.maximize_window()
driver.get("file:///E:/python_Demo/PyCharm_Demo1/select_test.html")
sleep(1)

# 1. 定位【原生下拉框<select>】
ele = driver.find_element(By.ID, "city")

# 2. 创建Select对象
s = Select(ele)

# ==================== 三个选择方法演示 ====================
# 方法1:按索引选(第1个=0,北京)
s.select_by_index(0)
sleep(1)

# 方法2:按value选(value="sh",上海)
s.select_by_value("sh")
sleep(1)

# 方法3:按可见文字选(页面显示"深圳")
s.select_by_visible_text("深圳")
sleep(2)

driver.quit()

系统弹窗管理

下载atolt工具,可以定位弹窗属性

from pywinauto.findwindows import find_element
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as ec
from selenium.webdriver.common.by import By
from time import sleep
from selenium.webdriver.edge.options import Options
from pywinauto import Desktop



edge_options = Options()

# 创建Edge浏览器对象
driver = webdriver.Edge(options=edge_options)
try:
    # 最大化浏览器窗口
    driver.maximize_window()
    # 打开登录页面
    driver.get("https://v5.crmeb.net/admin/login?redirect=%2Fadmin%2Findex")
    # 创建等待对象
    wait=WebDriverWait(driver, 10)
    driver.find_element(By.TAG_NAME,"button").click()
    wait.until(ec.visibility_of_element_located((By.XPATH,"//div[@class='open-image']/img"))).click()
    sleep(2)

    wait.until(ec.element_to_be_clickable((By.XPATH,"//ul/li[@title='商品']"))).click()
    sleep(3)
    wait.until(ec.element_to_be_clickable((By.XPATH,"//span[text()='商品分类']"))).click()
    wait.until(ec.element_to_be_clickable((By.XPATH, "//span[text()='添加分类']"))).click()
    wait.until(ec.element_to_be_clickable((By.XPATH, "//div/i[@class='el-icon-picture-outline']"))).click()
    ele=driver.find_element(By.TAG_NAME,"iframe")
    driver.switch_to.frame(ele)

    wait.until(ec.element_to_be_clickable((By.XPATH, "//span[text()='上传图片']"))).click()
    path = r"E:\python_Demo\PyCharm_Demo1\1.png"
    wait.until(ec.presence_of_element_located((By.XPATH, "//i[@class='el-icon-plus']"))).click()
    sleep(2)
    app=Desktop ()
    app['打开']['Edit'].type_keys(path)
    sleep(3)
    app['打开']['Button'].click()
    sleep(3)
    driver.switch_to.default_content()
finally:
    driver.quit()


selenium鼠标移动操作

在UI设计中,如果遇到一个标签,鼠标悬停的时候,属性改变了,这就是设置了:hover伪类.,例如:

/* 默认样式 */
.el-range__close-icon {
  color: #C0C4CC; /* 浅灰 */
  opacity: 0.6;
  cursor: pointer;
}

/* 悬停样式 :hover 伪类 */
.el-range__close-icon:hover {
  color: #606266; /* 深灰 */
  opacity: 1;
  transform: scale(1.1); /* 轻微放大 */
}

这个时候就需要代码模拟鼠标操作

# 1. 导入Selenium必备库:操控浏览器
from selenium import webdriver
# 显式等待:等待元素加载完成再操作
from selenium.webdriver.support.ui import WebDriverWait
# 等待条件:元素可见、可点击等
from selenium.webdriver.support import expected_conditions as ec
# 元素定位方式:ID、XPATH、CSS等
from selenium.webdriver.common.by import By
# 强制等待:固定暂停几秒
from time import sleep
# Edge浏览器配置选项
from selenium.webdriver.edge.options import Options
# 鼠标操作库:模拟鼠标悬停、点击、拖拽等
from selenium.webdriver.common.action_chains import ActionChains

# 2. 配置Edge浏览器(空配置,默认启动)
edge_options = Options()

# 3. 创建Edge浏览器对象,启动浏览器
driver = webdriver.Edge(options=edge_options)
# try语句:包裹核心代码,无论是否报错,最终都会执行finally关闭浏览器
try:
    # 浏览器窗口最大化,避免元素被遮挡
    driver.maximize_window()
    # 打开CRMEB商城后台登录页面
    driver.get("https://v5.crmeb.net/admin/login?redirect=%2Fadmin%2Findex")
    # 创建显式等待对象:最多等待10秒,直到元素出现
    wait = WebDriverWait(driver, 10)

    # ===================== 【问题代码】登录操作(错误) =====================
    # 直接点击页面第一个button按钮,未输入账号密码,登录会失败
    driver.find_element(By.TAG_NAME,"button").click()
    # 点击一个无关的图片元素,无实际作用
    wait.until(ec.visibility_of_element_located((By.XPATH,"//div[@class='open-image']/img"))).click()
    # 强制等待2秒,给页面加载时间
    sleep(2)

    # ===================== 点击左侧菜单 =====================
    # 【问题代码】XPath错误:页面无ul直接包裹li,可能定位失败
    # 等待【订单】菜单可点击,然后点击
    wait.until(ec.element_to_be_clickable((By.XPATH,"//ul/li[@title='订单']"))).click()
    sleep(3)
    # 等待【订单统计】可点击,点击进入页面
    wait.until(ec.element_to_be_clickable((By.XPATH,"//span[text()='订单统计']"))).click()
    sleep(3)

    # ===================== 核心:鼠标操作清空日期框 =====================
    # 实例化鼠标操作对象
    action_chains = ActionChains(driver)
    # 定位日期框的【清空图标】(就是你问的hover变化的i标签)
    ele = driver.find_element(By.XPATH,"//i[@class='el-input__icon el-range__close-icon']")
    # 鼠标操作:移动到图标上 → 暂停1秒 → 点击图标 → 执行操作
    action_chains.move_to_element(ele).pause(1).click(ele).perform()

    # ===================== 定位只读日期输入框 =====================
    # 等待【开始日期】输入框可见,并赋值给变量
    start_date_input = wait.until(ec.visibility_of_element_located((By.XPATH, "//input[@placeholder='开始日期']")))
    print("开始日期输入框已定位")
    # 等待【结束日期】输入框可见,并赋值给变量
    end_date_input = wait.until(ec.visibility_of_element_located((By.XPATH, "//input[@placeholder='结束日期']")))

    # ===================== 核心:JS破解readonly属性+赋值日期 =====================
    # 执行JS:移除开始日期框的readonly只读属性 → 赋值日期2026/03/17
    driver.execute_script("arguments[0].removeAttribute('readonly'); arguments[0].value = '2026/03/17';", start_date_input)
    print("开始日期已设置为2026/03/17")
    # 执行JS:移除结束日期框的readonly只读属性 → 赋值日期2026/03/24
    driver.execute_script("arguments[0].removeAttribute('readonly'); arguments[0].value = '2026/03/24';", end_date_input)
    print("结束日期已设置为2026/03/24")

    # 强制等待3秒,查看效果
    sleep(3)

# 无论代码是否报错,最终都会执行:关闭浏览器
finally:
    driver.quit()
  • ActionChains(driver):创建鼠标操作链
  • move_to_element(ele):鼠标悬停到清空图标上
  • pause(1):悬停后暂停 1 秒
  • click(ele):点击图标
  • perform():执行整个鼠标操作(必须加,否则不生效)
driver.execute_script("JS代码", 元素)

常见的鼠标操作

鼠标操作方法作用工作常用场景
move_to_element(元素)鼠标悬停在元素上下拉菜单、hover 显示按钮、你之前的清空小图标
click(元素)鼠标单击点击按钮、菜单、图标
double_click(元素)鼠标双击编辑文本、快捷打开、表格行双击
context_click(元素)鼠标右键单击弹出右键菜单(复制、删除等)
drag_and_drop(源, 目标)鼠标拖拽滑块验证、列表排序、拖动模块
click_and_hold(元素)鼠标长按不松开滑块拖动、长按加载
pause(秒数)鼠标动作暂停等待动作之间留反应时间

PO模式

什么是PO模式?

Page Object模式,也就是页面对象模型,是自动化测试里工作必用,最核心的设计规范。通俗来讲,就是把每一个网页,单独封装成一个python类:

这个类里面只存当前页面的元素定位,操作方法,测试脚本/业务代码,只调用方法,不写任何定位

PO模式的核心三层结构

1.BasePage基类(公共父类)

封装所有页面的通用的方法:点击,输入,等待,切换iframe,鼠标操作等

# 导入显式等待类
from selenium.webdriver.support.ui import WebDriverWait
# 导入等待条件(可点击、可见等)
from selenium.webdriver.support import expected_conditions as ec
# 导入鼠标操作类(悬停、点击等)
from selenium.webdriver.common.action_chains import ActionChains

# 定义基础页面对象类,所有页面都继承这个类
class BasePage:
    # 初始化方法,接收外部传入的浏览器驱动
    def __init__(self, driver):
        # 将传入的driver赋值给实例变量,供全类使用
        self.driver = driver
        # 创建显式等待对象,最大等待10秒
        self.wait = WebDriverWait(driver, 10)

    # 封装通用点击方法:等待元素可点击后点击
    # locator:元素定位器(定位方式, 路径)
    def click(self, locator):
        # 等待元素可点击
        element = self.wait.until(ec.element_to_be_clickable(locator))
        # 执行点击操作
        element.click()

    # 封装通用输入方法:等待元素可见后输入文本
    def input_text(self, locator, text):
        # 等待元素可见
        element = self.wait.until(ec.visibility_of_element_located(locator))
        # 输入指定文本
        element.send_keys(text)

    # 封装通用JS赋值方法:破解readonly属性并赋值(日期框专用)
    def js_set_value(self, locator, value):
        # 等待元素可见
        ele = self.wait.until(ec.visibility_of_element_located(locator))
        # 执行JS代码:移除readonly属性 + 赋值
        self.driver.execute_script(
            "arguments[0].removeAttribute('readonly'); arguments[0].value = arguments[1];",
            ele,  # arguments[0] 对应第一个JS参数:元素对象
            value # arguments[1] 对应第二个JS参数:要设置的值
        )

    # 封装通用鼠标悬停+点击方法(处理hover效果的图标)
    def hover_click(self, locator):
        # 等待元素可点击
        ele = self.wait.until(ec.element_to_be_clickable(locator))
        # 创建鼠标操作对象
        actions = ActionChains(self.driver)
        # 鼠标移动到元素上 + 暂停0.5秒 + 点击 + 执行操作
        actions.move_to_element(ele).pause(0.5).click(ele).perform()

2.页面类(LoginPage,OrderPage)

·每一个页面对应一个类

·类里定义元素定位(变量存XPath/CSS)

·类里写页面的操作方法(login(),search_order(),set_date())

# 导入元素定位方式
from selenium.webdriver.common.by import By
# 导入基础父类
from pages.base import BasePage

# 登录页面类,继承BasePage
class LoginPage(BasePage):
    # ===================== 页面元素定位(常量) =====================
    # 用户名输入框:XPath定位
    USER_INPUT = (By.XPATH, "//input[@placeholder='请输入用户名']")
    # 密码输入框:XPath定位
    PWD_INPUT = (By.XPATH, "//input[@placeholder='请输入密码']")
    # 登录按钮:CSS定位
    LOGIN_BTN = (By.CSS_SELECTOR, "button.el-button--primary")

    # ===================== 页面操作方法 =====================
    # 登录方法:接收账号和密码
    def user_login(self, username, password):
        # 调用父类方法:输入用户名
        self.input_text(self.USER_INPUT, username)
        # 调用父类方法:输入密码
        self.input_text(self.PWD_INPUT, password)
        # 调用父类方法:点击登录按钮
        self.click(self.LOGIN_BTN)
# 导入元素定位方式
from selenium.webdriver.common.by import By
# 导入基础父类
from pages.base import BasePage

# 订单页面类,继承BasePage
class OrderPage(BasePage):
    # ===================== 页面元素定位(常量) =====================
    # 左侧订单菜单
    ORDER_MENU = (By.XPATH, "//li[@title='订单']")
    # 订单统计子菜单
    ORDER_STATISTICS = (By.XPATH, "//span[text()='订单统计']")
    # 日期清空图标
    CLEAR_ICON = (By.CSS_SELECTOR, "i.el-range__close-icon")
    # 开始日期输入框
    START_DATE = (By.XPATH, "//input[@placeholder='开始日期']")
    # 结束日期输入框
    END_DATE = (By.XPATH, "//input[@placeholder='结束日期']")

    # ===================== 页面操作方法 =====================
    # 进入订单统计页面
    def go_to_order_statistics(self):
        # 点击订单菜单
        self.click(self.ORDER_MENU)
        # 点击订单统计子菜单
        self.click(self.ORDER_STATISTICS)

    # 设置订单查询日期
    def set_order_date(self, start_date, end_date):
        # 鼠标悬停点击清空图标
        self.hover_click(self.CLEAR_ICON)
        # JS赋值:设置开始日期
        self.js_set_value(self.START_DATE, start_date)
        # JS赋值:设置结束日期
        self.js_set_value(self.END_DATE, end_date)

3.测试用例类

只做业务逻辑,完全不出元素定位,只调用页面类的方法

# 导入Edge浏览器驱动
from selenium import webdriver
# 导入登录页面类
from pages.login_page import LoginPage
# 导入订单页面类
from pages.order_page import OrderPage
# 导入强制等待(仅用于查看效果)
from time import sleep

# 程序主入口:只有直接运行此文件时,才执行内部代码
if __name__ == '__main__':
    # ===================== 1. 初始化浏览器 =====================
    # 创建Edge浏览器对象
    driver = webdriver.Edge()
    # 浏览器窗口最大化
    driver.maximize_window()
    # 打开CRMEB登录页面
    driver.get("https://v5.crmeb.net/admin/login")

    # ===================== 2. 执行业务流程:登录 =====================
    # 实例化登录页面对象,传入driver
    login_page = LoginPage(driver)
    # 调用登录方法,输入账号密码
    login_page.user_login("demo", "crmeb.com")
    # 等待2秒,页面加载
    sleep(2)

    # ===================== 3. 执行业务流程:订单统计 =====================
    # 实例化订单页面对象,传入driver
    order_page = OrderPage(driver)
    # 进入订单统计页面
    order_page.go_to_order_statistics()
    # 设置查询日期
    order_page.set_order_date("2026/03/17", "2026/03/24")

    # ===================== 4. 结束操作 =====================
    # 等待3秒查看效果
    sleep(3)
    # 关闭浏览器
    driver.quit()

使用PO模式的优点

易维护,可复用,代码清晰,解耦合,团队协作规范

pytest之测试夹具函数

什么是测试夹具(Fixture)?

测试夹具就是自动化的【前置准备+后置清理】工具函数

前置:测试开始前做的事(打开浏览器,初始化驱动,登录系统)

后置:测试结束后的事(关闭浏览器,清楚数据,退出登录)

写一次,所有的测试用例都能复用,不用重复写代码

核心语法:

装饰器:@pytest.fixture → 标记这是一个夹具函数

关键字 yield:

yield 之前 = 前置代码(测试前执行)

yield 之后 = 后置代码(测试后执行)

作用域:控制夹具执行次数

作用域

作用域写法含义你的场景
function(默认)@pytest.fixture(scope="function")每个测试函数执行 1 次每个用例都要重置状态
classscope="class"每个测试类执行 1 次类内用例共享前置
modulescope="module"每个.py文件执行 1 次文件内用例共享
sessionscope="session"整个测试全程只执行 1 次✅ Selenium 驱动全局唯一(必用)
# 1. 导入需要的工具
import pytest  # 测试框架:自动找用例、自动运行
from selenium import webdriver  # 操控Edge浏览器
from selenium.webdriver.common.keys import Keys  # 键盘按键(回车、空格等)
from selenium.webdriver.common.by import By  # 元素定位方式(ID、XPATH等)

# ===================== 核心:测试夹具(全自动前置+后置) =====================
# @pytest.fixture:标记这是 pytest 专用的夹具函数
@pytest.fixture(scpoe='范围',autouse=True)
# 夹具函数名:driver(pytest 靠这个名字自动匹配用例参数)
def driver():
    # ===================== 【前置代码】测试开始前自动执行 =====================
    # 创建浏览器对象:打开 Edge 浏览器(变量名也叫 driver,是真正的浏览器)
    driver = webdriver.Edge()
    # 浏览器窗口最大化
    driver.maximize_window()
    # 自动打开百度官网
    driver.get("https://www.baidu.com")

    # ===================== 核心关键字:yield =====================
    # 作用:暂停夹具,把「浏览器对象」返回给测试用例使用
    yield driver

    # ===================== 【后置代码】测试结束后自动执行 =====================
    # 自动关闭浏览器(无论用例成功/失败,都会执行!)
    driver.quit()

# ===================== 测试用例1:检查百度首页标题 =====================
# 函数以 test_ 开头 → pytest 自动识别为测试用例
# 参数 driver:自动接收 上面夹具 返回的「浏览器对象」
def test_baidu_title(driver):
    # 获取当前网页的标题(百度首页顶部的文字)
    title = driver.title
    # 断言:判断 实际标题 == 预期标题,不相等就直接报错
    assert title == "百度一下,你就知道"
    # 断言通过后,打印提示
    print("✅ 百度标题测试用例 执行通过")

# ===================== 测试用例2:测试百度搜索功能 =====================
def test_baidu_search(driver):
    # 1. 定位百度搜索框(你原代码ID错了!正确ID是 kw)
    search_input = driver.find_element(By.ID, "kw")
    # 2. 向搜索框输入文字:软件测试
    search_input.send_keys("软件测试")
    # 3. 模拟键盘按下回车键(执行搜索)
    search_input.send_keys(Keys.ENTER)
    # 4. 断言:检查搜索框里的值是否正确
    assert "软件测试" in search_input.get_attribute("value")
    # 断言通过后,打印提示
    print("✅ 百度搜索测试用例 执行通过")

scope干嘛用?

这是你设置的测试夹具的范围,如果scope='function'则每一个测试用例都执行一边,打开浏览器->关闭浏览器,如果你的scope='session',则所有的测试用例都共用一个浏览器,测试用例1执行完接着执行测试用例2,全部执行完毕之后再关闭浏览器

执行流程:

上述python代码,例如执行test_baidu_title这个函数。pytest框架看到这个以test_开头的函数,自动检测出这是pttest代码,自动调用,不用手动调用。在测试函数执行之前,看到这个用例的参数:driver,自动找到同名的夹具函数,然后先执行夹具函数driver(),跑yield之前的代码,然后返回浏览器对象driver,等这个函数所有代码都执行完毕之后再执行yield之后的代码。

什么是pytest框架?

它是一个专门写自动化测试的python工具

怎么才算pytest代码?

1.文件名/函数名以test_开头(pytest自动识别核心规则)

2.使用的pytest功能(夹具@pytest.fixture,断言,装饰器)(注意import pytest)

autouse=True什么意思?

夹具自动执行,不用用例传参激活

conftest.py文件管理测试夹具函数

什么是conftest.py?

1.项目目录全局夹具公共仓库

2.把所有的测试用例都需要的共同的夹具放这里

3.所有测试文件自动能用,不用复制代码,不用手动导入

4.文件名固定死,必须是conftest.py

conftest.py有什么用?

比如你的夹具函数:

# test_baidu.py
@pytest.fixture
def driver():
    ...  # 开浏览器

你每一个test_.py文件测试函数,都需要写一边夹具代码,代码冗余。

把所有的夹具只写一次写在cinftest.py文件里,所有的测试文件都可以自动公用,统一管理,改一次,全项目生效。

规则

文件名固定‘

放在根目录,所有测试用例自动识别

无需导入,不写任何import

夹具函数之作用域设置

什么是夹具函数作用域?

简单来说,就是控制夹具具体执行多少次

作用域参数全称执行规则你的浏览器场景
function函数级(默认)每个测试函数执行 1 次一个用例开一次浏览器
class类级每个测试类执行 1 次一个类里的用例共用浏览器
module文件级每个 .py 测试文件执行 1 次一个文件里的用例共用浏览器
session会话级整个测试过程只执行 1 次全项目所有用例只开 1 次浏览器

1.scope="function"(默认)

每一个 test_ 测试用例,都重新执行一次夹具(开浏览器 → 跑用例 → 关浏览器 → 开浏览器 → 跑用例 → 关浏览器)

适用场景:用例需要完全隔离,互不干扰

2.scope='class'

一个测试类只执行一次夹具,类里所有用例共用一个浏览器,但是如果要pytest判定为测试类的话,类名必须以Test开头

# 测试类:只需要按规则写
class TestBaidu:
    def test_title(self):
        print("测试标题")

# ✅ 你什么都不用写!pytest 自动找、自动运行

同理,module级别的文件名必须以test_开头

pytest之失败重跑设置

# test_baidu.py 测试文件
import pytest
from selenium import webdriver
from selenium.webdriver.common.by import By

# 你的夹具
@pytest.fixture
def driver():
    driver = webdriver.Edge()
    driver.get("https://www.baidu.com")
    yield driver
    driver.quit()

# 你的测试用例
def test_baidu_title(driver):
    assert driver.title == "百度一下,你就知道"

# ===================== 你问的这段代码 =====================
# 直接运行这个文件,就自动执行pytest测试
if __name__ == '__main__':
    pytest.main(['-vs','--reruns','1'])

讲解:

if __name__ == '__main__':

只有运行这个文件的时候,下面的代码才能执行,如果这个文件被其他别的文件导入的时候,下面的代码不执行

pytest.main([...]):

直接在代码里调用pytest运行测试,不用在命令行(终端)里敲命令。与直接在终端里输入:

pytest -vs --reruns 1

效果完全一样

参数拆分含义
-vs合并了 -v + -s-v:输出详细测试结果-s:打印用例里的 print 内容
--reruns失败重跑参数对应插件 pytest-rerunfailures
1重跑次数用例失败 → 自动重跑 1 次

或者直接在命令行里使用:

pytest test_baidu.py -v --reruns=2 --reruns-delay=1
参数作用
--reruns=n失败重跑 n 次(最常用:2~3 次)
--reruns-delay=n重跑前等待 n 秒(给页面加载时间)

pytest之多线程跑测试用例

# 1. 导入依赖:测试框架 + 浏览器驱动 + 元素定位 + 键盘操作
import pytest
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.by import By

# 2. 测试夹具:管理浏览器(核心!)
# scope='function':每个测试用例 → 独立打开/关闭1个浏览器
@pytest.fixture(scope='function')
def driver():
    # 【前置】创建Edge浏览器 → 最大化 → 打开百度
    driver = webdriver.Edge()
    driver.maximize_window()
    driver.get("https://www.baidu.com")

    yield driver  # 把浏览器对象交给测试用例

    # 【后置】用例执行完 → 自动关闭浏览器
    driver.quit()

# 3. 测试用例1:检查百度标题
def test_baidu_title(driver):
    title=driver.title
    assert title=="百度一下,你就知道"
    print("✅ 百度标题测试用例 执行通过")

# 4. 测试用例2:搜索功能(⚠️你这里定位错了!导致重跑)
def test_baidu_search(driver):
    # 错误定位:chat-textarea 是百度AI对话框,不是搜索框
    search_input=driver.find_element(By.ID, "chat-textarea")
    search_input.send_keys("软件测试")
    search_input.send_keys(Keys.ENTER)
    assert "软件测试" in search_input.get_attribute("value")
    print("✅ 百度搜索测试用例 执行通过")

# 5. 测试用例3:检查新闻链接
def test_baidu_news(driver):
    news_link=driver.find_element(By.LINK_TEXT, "新闻")
    assert driver.current_url.startswith("http")
    print("✅ 百度新闻测试用例 执行通过")

# 6. 测试用例4:检查地图链接
def test_map_link(driver):
    map_link=driver.find_element(By.LINK_TEXT, "地图")
    assert driver.current_url.startswith("http")
    print("✅ 百度地图测试用例 执行通过")

# 7. 测试用例5:检查图片链接
def test_picture(driver):
    picture_link=driver.find_element(By.LINK_TEXT, "图片")
    assert driver.current_url.startswith("http")
    print("✅ 百度图片测试用例 执行通过")

# 8. 主函数:代码启动测试
if __name__ == "__main__":
    pytest.main([
        '-vs',            # 详细日志 + 打印print
        '-n=5',           # 开启5个进程 = 同时开5个浏览器
        '--reruns=1',     # 用例失败自动重跑1次
        __file__          # 运行当前文件
    ])

pytest之断言

assert是pytest唯一的断言方式,作用是:

判断实际结果和预期结果是否一致

  • 一致 → 用例通过(绿色)
  • 不一致 → 用例失败(红色),自动抛出异常

1. 相等 / 不相等断言

# 用例:判断百度标题是否相等
def test_baidu_title(driver):
    # 实际结果
    title = driver.title
    # 预期结果
    expect_title = "百度一下,你就知道"
    
    # 断言相等
    assert title == expect_title
    
    # 断言不相等(例子)
    assert title != "谷歌"

2. 包含 / 不包含断言

def test_baidu_search(driver):
    driver.find_element(By.ID, "kw").send_keys("软件测试")
    driver.find_element(By.ID, "su").click()
    
    # 断言:搜索结果标题包含关键词
    assert "软件测试" in driver.title
    
    # 断言不包含
    assert "抖音" not in driver.title

3. 真假断言(True / False)

def test_link_enabled(driver):
    # 判断新闻链接是否可点击
    news_btn = driver.find_element(By.LINK_TEXT, "新闻")
    # 断言为真
    assert news_btn.is_enabled() is True
    # 简写(推荐)
    assert news_btn.is_enabled()

4. 为空 / 不为空断言

def test_search_input(driver):
    input_text = driver.find_element(By.ID, "kw").text
    # 断言输入框为空
    assert input_text == ""
    # 断言不为空
    assert input_text is not None

5. 异常断言

def test_no_element(driver):
    # 用 pytest.raises 断言:找不到元素会抛 NoSuchElementException
    with pytest.raises(Exception):
        # 定位一个不存在的元素,预期报错
        driver.find_element(By.ID, "不存在的ID")

pytest之日志设置

一、日志级别

级别英文用途(工作场景)
调试DEBUG最详细(打开浏览器、定位元素、执行步骤)
普通INFO关键流程(用例开始 / 结束、搜索成功)
警告WARNING不影响运行的提醒
错误ERROR元素找不到、用例失败
严重CRITICAL系统崩溃
# 1. 导入需要的工具
from loguru import logger  # 导入日志库loguru的核心对象logger
import time,os            # 导入time(时间)、os(系统路径)模块
# 格式化当前时间:年-月-日_时-分-秒
t=time.strftime("%Y-%m-%d_%H-%M-%S",time.localtime())
# 打印时间,方便你看
print(t)
# 1. 获取当前这个py文件的【绝对完整路径】
abs_path=os.path.abspath(__file__)
print(abs_path)  # 打印:E:\python_Demo\PyCharm_Demo1\xxx.py

# 2. 获取当前文件所在的【文件夹路径】
log_path=os.path.dirname(abs_path)
print(log_path)  # 打印:E:\python_Demo\PyCharm_Demo1

# 3. 获取上一级文件夹路径
demo_path=os.path.dirname(log_path)
print(demo_path)  # 打印:E:\python_Demo

# 4. 拼接最终日志存放的文件夹:E:\python_Demo\PyCharm_Demo1
now_path=os.path.join(demo_path,"PyCharm_Demo1")
# 拼接:文件夹路径 + 时间名字 + .log后缀
log_file_name=now_path+'\\'+t+'.log'
logger.info("系统正常运行")    # 普通信息日志
logger.debug("调试日志")      # 调试日志(最详细)
logger.error("错误级别")      # 错误日志
logger.warning("警告")        # 警告日志

pytest之allure生成测试报告

什么是allure?

ALLURE是一款轻量级,可视化的测试报告工具,专门配合pytest使用

生成 Allure 报告的2 个核心步骤

  1. 运行测试用例 → 生成临时数据(json 格式,不能直接看)
  2. 把临时数据 → 生成网页版 HTML 报告(可以打开看)
import pytest               # 测试核心框架,用来写用例、运行用例
import os                   # 系统操作库,执行cmd命令(生成allure报告)
from selenium import webdriver  # 操控浏览器(Edge/Chrome)
from selenium.webdriver.common.keys import Keys  # 键盘按键(回车、空格)
from selenium.webdriver.common.by import By      # 元素定位方式(ID/文本)
import allure               # 生成Allure测试报告的库
# Allure装饰器:给测试报告分组(大项目/功能模块)
@allure.epic("百度自动化测试")  # 最高级分类:项目名称
@allure.feature("基础功能测试") # 二级分类:功能模块
# pytest规则:测试类必须以 Test 开头
class TestBaidu:
@allure.title("测试百度标题")  # Allure:用例名称
def test_baidu_title(self, driver):  # pytest用例:test开头
    title = driver.title  # 获取网页标题
    assert title == "百度一下,你就知道"  # 断言:标题必须等于这个
    print("✅ 百度标题测试用例 执行通过")
@allure.title("测试百度搜索")
def test_baidu_search(self, driver):
    # ❌ 错误定位:chat-textarea 是百度AI聊天框,不是搜索框!
    search_input = driver.find_element(By.ID, "chat-textarea")  
    search_input.send_keys("软件测试")  # 输入文字
    search_input.send_keys(Keys.ENTER)  # 按回车搜索
    # 断言:输入框里的值包含 软件测试
    assert "软件测试" in search_input.get_attribute("value")
@allure.title("测试百度新闻链接")
def test_baidu_news(self, driver):
    driver.find_element(By.LINK_TEXT, "新闻")  # 根据链接文字定位元素
    assert driver.current_url.startswith("http")  # 断言网址以http开头

# 地图、图片用例逻辑完全一样
if __name__ == "__main__":
    # 步骤1:运行所有测试用例
    pytest.main([
        "-vs",               # 显示详细运行日志
        "--clean",            # 清理临时文件
        "--clean-logs",       # 清理旧日志
        '-n=5',               # 多线程:开5个进程,同时跑5个用例(提速)
        "--alluredir=./allure-results",  # 生成Allure临时数据(json文件)
        __file__              # 运行当前文件
    ])

    # 步骤2:执行cmd命令 → 把临时数据 生成 网页报告
    os.system("allure generate ./allure-results -o ./allure-report --clean")

命令解释

  1. --alluredir=./allure-results生成报告原始数据(必须有这一步)
  2. allure generate把原始数据 → 生成 HTML 网页报告
  3. --clean覆盖旧报告,不报错

代码运行后的结果

  1. 自动打开 5 个 Edge 浏览器,同时执行 5 个用例
  2. 生成文件夹 allure-results(报告原始数据)
  3. 生成文件夹 allure-report(网页报告,双击index.html打开)
  4. 控制台显示结果

pytest之allure错误报告截图

什么是钩子函数(Hook)?

pytest 运行过程中,在固定的时间点,会自动调用你写的函数,不用你手动去调。

pytest:用例执行完 / 用例失败 → 自动执行你写的代码

这个 “自动触发” 的动作,就是钩子

pytest 常见钩子

  • pytest_runtest_setup:用例执行前自动跑

  • pytest_runtest_teardown:用例执行后自动跑

  • pytest_runtest_makereport:用例执行完、生成报告时跑

  • pytest_session_start:整个测试开始时跑

  • pytest_session_finish:整个测试结束时跑

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐