Python自动化测试框架全解析:从unittest到Playwright的实战指南
1. 项目概述:为什么我们需要自动化测试框架?
做开发或者测试的朋友,尤其是刚入行的新人,可能都经历过这样的场景:项目上线前,你手动点了几百个页面,眼睛都花了,结果还是漏掉了一个关键流程的bug,导致线上事故。或者,每次代码有改动,你都得把核心功能再手动跑一遍,耗时耗力,还容易出错。这种重复、机械、易遗漏的工作,正是自动化测试要解决的核心痛点。
Python,凭借其语法简洁、生态丰富、社区活跃的特点,成为了自动化测试领域的首选语言之一。但“用Python写自动化测试”和“用好Python自动化测试”是两码事。前者可能只是写一些零散的脚本,后者则需要一个清晰、可维护、可扩展的体系——这就是测试框架的价值。一个好的测试框架,能帮你组织测试用例、管理测试数据、生成测试报告、集成持续集成(CI)流程,让你从“脚本小子”升级为“测试工程师”。
今天,我们不谈那些庞大复杂的商业套件,就聚焦在四个基于Python、能让你从零开始上手,并足以支撑起一个严肃项目测试需求的优秀框架。无论你是想测试Web应用、API接口,还是桌面程序,这里都有对应的解决方案。我会结合我这些年踩过的坑和积累的经验,带你从“是什么”、“怎么选”到“怎么用”,把这四个框架讲透。
2. 框架全景图:四大金刚的定位与选型
在深入细节之前,我们必须先建立一个宏观的认知。这四个框架并非互相替代,而是各有侧重,共同构成了Python自动化测试的武器库。选对工具,事半功倍。
2.1 框架核心定位解析
为了让你一目了然,我把这四个框架的核心特性和适用场景做成了下面的表格:
| 框架名称 | 核心测试类型 | 主要应用场景 | 学习曲线 | 生态与社区 |
|---|---|---|---|---|
| unittest | 单元测试、集成测试 | Python标准库自带,所有Python项目的测试基础,尤其适合测试函数、类等代码单元。 | 平缓 | 极其成熟,是Python生态的基石。 |
| pytest | 全类型测试(单元、集成、功能) | 当前Python社区事实上的单元测试标准,功能强大、插件丰富,适合任何规模和类型的测试。 | 中等 | 异常活跃,插件生态庞大。 |
| Selenium | Web UI 自动化测试 | 模拟用户在浏览器中的操作(点击、输入、跳转等),用于测试Web应用的前端功能和交互。 | 较陡 | 历史悠久,跨语言支持,社区庞大。 |
| Playwright | Web UI 自动化测试、API测试 | 新一代浏览器自动化工具,支持多浏览器(Chromium, Firefox, WebKit),功能强大,速度更快。 | 中等偏上 | 由微软维护,发展迅猛,现代Web测试首选。 |
选型心法:
- 如果你是初学者,或者项目要求快速验证一个Python模块的功能 :从
unittest开始。它是Python的一部分,无需额外安装,语法相对简单,能帮你建立测试的基本概念(如断言、测试用例、测试套件)。 - 如果你的项目已经有一定规模,或者你希望拥有最灵活、最强大的测试工具 :直接上
pytest。它几乎可以完成unittest能做的所有事情,并且更简洁、更强大。很多新项目甚至直接用它替代unittest。 - 如果你的测试对象是Web页面,需要验证页面元素、交互流程 :在
Selenium和Playwright之间选择。-
Selenium更传统,资料极多,兼容性广(包括一些老版本浏览器),如果你需要支持IE等老旧浏览器,它可能是唯一选择。 -
Playwright更现代,性能更好,内置了自动等待、网络拦截等高级功能,对现代单页应用(SPA)支持更佳。对于新项目,我强烈推荐从Playwright开始。
-
注意:在实际项目中,它们常常组合使用。例如,用
pytest作为测试运行器和组织框架,用unittest或pytest写单元测试,再用Playwright写端到端(E2E)的UI测试。
2.2 环境准备:万变不离其宗的起点
无论你选择哪个框架,一个干净、独立的Python环境是第一步。这里我强烈推荐使用 venv (Python 3.3+ 内置)或 conda 来创建虚拟环境。这能避免项目间的包版本冲突。
以 venv 为例,在项目根目录下执行:
# Windows
python -m venv venv
# 激活环境
venv\Scripts\activate
# macOS/Linux
python3 -m venv venv
# 激活环境
source venv/bin/activate
激活后,你的命令行提示符前会出现 (venv) 字样,表示你正在虚拟环境中工作。
接下来,就是通过 pip 安装所需的框架了。我们将逐一进行。
3. 基石之选:unittest —— 内置的严谨派
unittest 是Python标准库中的测试框架,深受Java的JUnit影响。它提供了测试用例(TestCase)、测试套件(TestSuite)、测试夹具(Fixture)和测试运行器(TestRunner)等完整概念。
3.1 快速上手与核心概念
假设我们有一个简单的计算器模块 calculator.py :
def add(a, b):
return a + b
def subtract(a, b):
return a - b
为它编写 unittest 测试用例 test_calculator.py :
import unittest
from calculator import add, subtract
class TestCalculator(unittest.TestCase):
"""测试计算器功能的测试类,必须继承 unittest.TestCase"""
# 测试方法必须以 test_ 开头
def test_add_integers(self):
"""测试整数加法"""
self.assertEqual(add(1, 2), 3) # 断言:期望 add(1,2) 的结果等于 3
self.assertEqual(add(-1, 1), 0)
def test_add_floats(self):
"""测试浮点数加法,注意浮点数精度"""
self.assertAlmostEqual(add(0.1, 0.2), 0.3, places=7)
def test_subtract(self):
"""测试减法"""
self.assertEqual(subtract(5, 3), 2)
self.assertEqual(subtract(0, 5), -5)
# setUp 和 tearDown 是测试夹具
def setUp(self):
"""每个测试方法执行前都会运行,用于准备测试环境"""
print(f"\n开始执行测试: {self._testMethodName}")
def tearDown(self):
"""每个测试方法执行后都会运行,用于清理环境"""
print(f"测试执行完毕: {self._testMethodName}")
if __name__ == '__main__':
unittest.main() # 运行所有测试
在命令行执行 python test_calculator.py ,你会看到详细的测试通过或失败信息。
核心要点解析:
- 继承
unittest.TestCase:这是必须的,它赋予了类运行测试的能力。 - 方法命名 :所有测试方法必须以
test_开头,运行器会自动发现它们。 - 断言方法 :
self.assertEqual(a, b)是最常用的,还有self.assertTrue(x),self.assertIn(a, b),self.assertRaises(Error)等,用于验证测试结果。 - 测试夹具 (setUp/tearDown) :
setUp: 在每个测试方法 前 执行,常用于初始化资源(如数据库连接、打开浏览器)。tearDown: 在每个测试方法 后 执行,常用于清理资源(如关闭连接、退出浏览器)。- 还有
setUpClass/tearDownClass(在整个测试类前后执行一次)和setUpModule/tearDownModule(在整个模块前后执行一次)。
3.2 实战技巧与常见陷阱
技巧1:组织大型测试项目 对于大型项目,测试文件会很多。建议建立 tests 目录,并使用 TestLoader 和 TestSuite 来组织。
project/
├── src/
│ └── your_module.py
└── tests/
├── __init__.py
├── test_module_a.py
└── test_module_b.py
你可以使用 python -m unittest discover -s tests -p "test_*.py" 命令自动发现并运行 tests 目录下所有以 test_ 开头的测试文件。
陷阱1:测试隔离失败 unittest 默认会为每个测试方法创建一个新的测试类实例。但如果你在 setUpClass 中初始化了类属性(如一个全局的数据库连接),并在测试方法中修改了它,那么这个状态会影响到其他测试方法。 务必确保每个测试都是独立的 , tearDown 阶段要彻底清理。
陷阱2:过于复杂的断言 有时一个测试方法里塞满了十几个 assertEqual ,一旦中间某个失败,后面的断言就不会执行,你无法看到全貌。这时可以考虑:
- 拆分成多个更细粒度的测试方法。
- 使用
subTest上下文管理器(Python 3.4+),它允许在一个测试方法内运行多个子测试,即使一个失败,其他也会继续执行。def test_multiples_cases(self): test_cases = [(1,2,3), (0,0,0), (-1,1,0)] for a, b, expected in test_cases: with self.subTest(a=a, b=b, expected=expected): self.assertEqual(add(a, b), expected)
unittest 是坚实的基础,但当你需要更灵活的夹具、更简洁的语法、更强大的插件时,就该 pytest 登场了。
4. 社区王者:pytest —— 灵活强大的瑞士军刀
pytest 不是一个标准库,但它凭借其简洁的语法和强大的功能,几乎成为了Python单元测试的新标准。它的哲学是“约定优于配置”和“尽可能少的样板代码”。
4.1 安装与极简入门
首先安装: pip install pytest
沿用上面的 calculator.py ,用 pytest 重写测试,文件命名为 test_calc_pytest.py ( pytest 默认也会查找 test_*.py 文件):
# test_calc_pytest.py
from calculator import add, subtract
# 测试函数名以 test_ 开头即可,不需要继承任何类
def test_add_integers():
assert add(1, 2) == 3
assert add(-1, 1) == 0
def test_add_floats():
# pytest 使用标准的 assert 语句,更符合Python习惯
assert abs(add(0.1, 0.2) - 0.3) < 1e-7 # 自己处理浮点精度
def test_subtract():
assert subtract(5, 3) == 2
在命令行直接运行 pytest test_calc_pytest.py 。看,不需要 unittest.main() ,不需要特殊的断言方法,直接用 assert ,失败了 pytest 会给出非常清晰的错误信息,包括表达式的值。
4.2 核心特性深度解析
1. 夹具(Fixtures):超越 setUp/tearDown pytest 的夹具是其灵魂。它通过 @pytest.fixture 装饰器定义,比 unittest 的 setUp 更灵活、更可复用。
import pytest
@pytest.fixture
def db_connection():
"""模拟一个数据库连接夹具"""
print("\n=== 建立数据库连接 ===")
connection = {"connected": True} # 模拟连接对象
yield connection # yield 之前是 setup,之后是 teardown
print("=== 关闭数据库连接 ===")
connection["connected"] = False
def test_query_user(db_connection): # 将夹具作为参数传入测试函数
"""测试函数依赖 db_connection 夹具"""
assert db_connection["connected"] is True
# 执行查询逻辑...
print("执行用户查询测试")
def test_update_product(db_connection): # 多个测试可以复用同一个夹具
assert db_connection["connected"] is True
# 执行更新逻辑...
print("执行产品更新测试")
运行测试,你会看到每个测试函数执行时,连接建立和关闭的日志。 yield 模式让资源管理变得异常清晰。夹具还可以设置作用域( scope="session" / "module" / "class" / "function" ),实现不同级别的共享。
2. 参数化测试:一行代码覆盖多组数据 这是 pytest 的一大杀器,能极大减少重复代码。
import pytest
from calculator import add
@pytest.mark.parametrize("a, b, expected", [
(1, 2, 3),
(0, 0, 0),
(-1, 1, 0),
(100, -50, 50),
])
def test_add_parametrized(a, b, expected):
"""用一组参数测试 add 函数"""
assert add(a, b) == expected
运行后, pytest 会将其展开为4个独立的测试用例,并分别报告结果。
3. 丰富的插件生态
-
pytest-html: 生成漂亮的HTML测试报告。pip install pytest-html,然后运行pytest --html=report.html。 -
pytest-cov: 生成测试覆盖率报告。pip install pytest-cov,运行pytest --cov=your_module。 -
pytest-xdist: 并行运行测试,加速测试套件。pip install pytest-xdist,运行pytest -n auto(根据CPU核心数自动分配)。 -
pytest-mock: 集成unittest.mock,方便进行模拟(Mock)和打桩(Stub)。
4.3 高级用法与排错指南
标记(Markers)与选择性运行 你可以给测试打上标签,然后只运行特定的测试。
import pytest
@pytest.mark.slow # 自定义一个‘慢速测试’标记
def test_complex_calculation():
import time
time.sleep(2) # 模拟耗时操作
assert 1 == 1
@pytest.mark.quick # ‘快速测试’标记
def test_simple_check():
assert True
# 在命令行中运行:pytest -m quick # 只运行标记为 quick 的测试
# 运行:pytest -m "not slow" # 运行所有非 slow 标记的测试
需要在项目根目录创建一个 pytest.ini 文件来注册自定义标记,避免警告:
[pytest]
markers =
slow: marks tests as slow (deselect with '-m \"not slow\"')
quick: marks tests as quick to run
常见问题排查
- 问题:夹具找不到(FixtureNotFoundError)
- 原因 :测试函数请求的夹具没有定义,或者定义它的文件没有被
pytest正确发现。 - 解决 :确保夹具定义在测试文件内,或者在一个名为
conftest.py的文件中。pytest会自动发现项目目录树中所有conftest.py文件并加载其中的夹具,供所有测试文件使用。这是共享夹具的最佳实践。
- 原因 :测试函数请求的夹具没有定义,或者定义它的文件没有被
- 问题:测试通过了但代码改动后没反应
- 原因 :可能是模块缓存。或者测试文件/被测代码文件不在
pytest的搜索路径中。 - 解决 :使用
pytest --tb=short -v查看更详细的输出。确保运行pytest时所在的目录正确,或者使用python -m pytest命令来运行。对于缓存问题,可以尝试删除__pycache__目录和.pytest_cache目录。
- 原因 :可能是模块缓存。或者测试文件/被测代码文件不在
- 问题:并行测试(pytest-xdist)时资源冲突
- 原因 :多个测试进程同时访问同一个文件、端口或数据库。
- 解决 :使用夹具为每个测试进程创建独立的资源,例如为每个进程生成唯一的临时目录或数据库名。可以使用
pytest的worker_id夹具来区分不同的工作进程。
pytest 的强大远不止于此,但它足以让你构建一个非常健壮的后端测试体系。接下来,我们把目光投向浏览器,看看如何自动化Web操作。
5. 浏览器操控者:Selenium —— 经典而稳健
Selenium 的核心是 WebDriver,它是一个跨语言的协议,允许你用代码像真实用户一样操作浏览器。Python 通过 selenium 包提供了绑定。
5.1 环境搭建与第一个脚本
- 安装Selenium库 :
pip install selenium - 下载浏览器驱动 :这是关键一步。你需要下载与你电脑上浏览器版本匹配的驱动。
- Chrome : 下载 ChromeDriver
- Firefox : 下载 geckodriver
- Edge : 下载 Microsoft Edge WebDriver 将下载的驱动文件(如
chromedriver.exe)放在一个目录下,并将该目录添加到系统的PATH环境变量中,或者直接在代码里指定驱动路径。
一个最简单的示例,打开百度并搜索:
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
import time
# 1. 创建浏览器驱动实例,这里以Chrome为例
# 如果驱动已在PATH中,可以直接 webdriver.Chrome()
driver = webdriver.Chrome() # 也可以指定路径:webdriver.Chrome(executable_path=r'你的路径\chromedriver')
try:
# 2. 打开网页
driver.get("https://www.baidu.com")
time.sleep(2) # 等待页面加载,实际项目中应用显式等待替代
# 3. 定位元素并操作
# 找到搜索框,输入文本
search_box = driver.find_element(By.ID, "kw") # 百度搜索框的id是'kw'
search_box.send_keys("Selenium自动化测试")
search_box.send_keys(Keys.RETURN) # 模拟回车键
time.sleep(3) # 等待搜索结果加载
# 4. 断言验证
# 检查页面标题或某个结果元素
assert "Selenium自动化测试" in driver.title
print("测试通过!页面标题包含搜索关键词。")
# 例如,获取第一个结果的标题
# first_result = driver.find_element(By.CSS_SELECTOR, '#content_left h3 a')
# print(f"第一个结果是: {first_result.text}")
finally:
# 5. 关闭浏览器
driver.quit() # 关闭浏览器并释放驱动资源
5.2 元素定位与等待策略:稳定性的关键
元素定位八大法 find_element(By.XXX, “value”) 是核心。 By.XXX 包括:
By.ID: 最优先选择,通常唯一且稳定。By.NAME: 次选。By.CLASS_NAME: 注意类名可能有空格(多个类),需完整匹配或使用CSS选择器部分匹配。By.TAG_NAME: 如"input","a"。By.LINK_TEXT: 精确匹配链接文本。By.PARTIAL_LINK_TEXT: 部分匹配链接文本。-
By.CSS_SELECTOR: 非常强大且常用 ,语法与前端CSS一致。如#kw(ID),.s_ipt(class),input[name='wd']。 -
By.XPATH: 同样强大 ,可以在DOM树中导航。如//input[@id='kw']。功能最强但写起来复杂,性能可能稍差。
经验之谈:优先使用
ID>NAME>CSS_SELECTOR。CSS_SELECTOR通常比XPATH性能更好,也更易读。尽量避免使用包含索引的绝对路径(如/html/body/div[3]/div[2]/form/span[1]/input),因为页面结构一变就失效。
等待:告别 time.sleep 的噩梦 time.sleep(固定时间) 是极不推荐的,它要么浪费等待时间,要么在网速慢时导致元素找不到而失败。Selenium 提供了两种智能等待:
-
隐式等待 (Implicit Wait) :为整个
driver会话设置一个全局的最大等待时间,在查找元素时,如果元素没有立即出现,会轮询查找直到超时。driver.implicitly_wait(10) # 单位:秒 element = driver.find_element(By.ID, “someId”) # 会等待最多10秒缺点 :它只对
find_element系列方法有效,并且无法等待更复杂的条件(如元素可点击、元素包含特定文本)。 -
显式等待 (Explicit Wait) : 这是最佳实践 。针对某个特定条件进行等待,更精确、更灵活。
from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC # 等待最多10秒,直到ID为‘kw’的元素出现 element = WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.ID, "kw")) ) # 等待元素可点击 button = WebDriverWait(driver, 10).until( EC.element_to_be_clickable((By.CSS_SELECTOR, ".submit-btn")) ) button.click() # 等待页面标题包含特定文字 WebDriverWait(driver, 10).until( EC.title_contains("搜索结果") )expected_conditions模块提供了大量预定义条件,如visibility_of_element_located(元素可见)、text_to_be_present_in_element(元素包含文本)等。
5.3 与pytest结合的最佳实践
单纯的Selenium脚本不易维护和扩展。将其与 pytest 结合是标准做法。
1. 使用夹具管理浏览器生命周期 在 conftest.py 中创建浏览器夹具:
# conftest.py
import pytest
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
@pytest.fixture(scope="function") # 每个测试函数一个浏览器实例
def browser():
"""提供一个配置好的Chrome浏览器实例"""
options = Options()
# 常用配置
options.add_argument('--headless') # 无头模式,不显示浏览器窗口,适合CI环境
options.add_argument('--disable-gpu')
options.add_argument('--no-sandbox')
options.add_argument('--window-size=1920,1080')
driver = webdriver.Chrome(options=options)
driver.implicitly_wait(5) # 设置全局隐式等待
yield driver
# 测试结束后,无论成功失败,都关闭浏览器
driver.quit()
2. 编写基于pytest的页面测试
# test_baidu_search.py
def test_baidu_search_title(browser):
"""测试百度搜索后标题变化"""
browser.get("https://www.baidu.com")
assert "百度" in browser.title
def test_baidu_search_functionality(browser):
"""测试百度搜索功能"""
browser.get("https://www.baidu.com")
search_input = browser.find_element(By.ID, "kw")
search_input.send_keys("pytest")
search_input.submit() # 提交表单
# 使用显式等待等待结果出现
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
WebDriverWait(browser, 10).until(
EC.title_contains("pytest")
)
assert "pytest" in browser.title
3. 使用Page Object Model (POM) 设计模式 这是UI自动化测试的 黄金法则 。将页面封装成类,页面的元素定位和基本操作作为类的方法。测试脚本只调用这些方法,不与具体的定位符耦合。
# pages/search_page.py
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
class BaiduSearchPage:
def __init__(self, driver):
self.driver = driver
self.search_input = (By.ID, "kw")
self.search_button = (By.ID, "su")
def load(self):
self.driver.get("https://www.baidu.com")
WebDriverWait(self.driver, 10).until(
EC.presence_of_element_located(self.search_input)
)
def search(self, keyword):
"""输入关键词并搜索"""
input_elem = self.driver.find_element(*self.search_input)
input_elem.clear()
input_elem.send_keys(keyword)
self.driver.find_element(*self.search_button).click()
# 等待结果页加载
WebDriverWait(self.driver, 10).until(
EC.title_contains(keyword)
)
然后在测试中调用:
# test_with_pom.py
def test_search_with_pom(browser):
page = BaiduSearchPage(browser)
page.load()
page.search("Playwright")
assert "Playwright" in browser.title
POM极大地提高了代码的可读性、可维护性和复用性。当页面元素发生变化时,你只需要修改对应的Page类,而不需要修改所有测试脚本。
Selenium很强大,但它在处理现代Web应用(如大量AJAX、Shadow DOM)时,有时会显得力不从心,需要编写复杂的等待和JS注入。这时,它的挑战者Playwright带来了新的解决方案。
6. 现代新贵:Playwright —— 更快、更强、更智能
Playwright由微软开发,支持Chromium、Firefox和WebKit(Safari引擎)三大浏览器内核。它从设计之初就考虑了现代Web的复杂性。
6.1 安装与初体验:感受其便捷
安装Playwright Python包并安装浏览器: pip install playwright 然后 playwright install 这条命令会下载Chromium、Firefox和WebKit的可用版本。
一个等效的百度搜索示例:
from playwright.sync_api import sync_playwright # 同步API
def run(playwright):
# 启动Chromium浏览器,headless=False表示显示窗口
browser = playwright.chromium.launch(headless=False)
# 创建上下文和页面
context = browser.new_context()
page = context.new_page()
# 导航
page.goto("https://www.baidu.com")
# 定位和操作:Playwright支持多种选择器,这里用CSS
page.fill('#kw', 'Playwright自动化测试') # fill 方法会先清空再输入
page.click('#su') # 点击搜索按钮
# 等待导航完成
page.wait_for_load_state('networkidle') # 等待网络基本空闲
# 或者等待特定元素出现
# page.wait_for_selector('text=Playwright')
# 断言
assert 'Playwright' in page.title()
print(f"页面标题是: {page.title()}")
# 截图(非常方便)
page.screenshot(path='search_result.png')
# 关闭
context.close()
browser.close()
with sync_playwright() as playwright:
run(playwright)
代码看起来更简洁了。 page.fill() 、 page.click() 都是非常直观的高阶API。
6.2 颠覆性特性详解
1. 自动等待(Auto-waiting) 这是Playwright相比Selenium最大的优势之一。 绝大多数操作(如 click , fill , check )本身就已经内置了智能等待 。它会等待元素满足可操作条件(如可见、可点击、未禁用)后才执行操作,你基本不需要写显式等待。上面的 page.click(‘#su’) 就会自动等待该按钮可点击。
2. 强大的选择器引擎 Playwright的选择器语法非常丰富且强大:
- 文本选择器 :
page.click(‘text=登录’)点击包含“登录”文本的元素。 - CSS选择器 :
page.fill(‘#username’, ‘name’)。 - XPath :
page.click(‘//button’)。 - React/Vue组件选择器 (需额外配置):可以直接定位到前端框架的组件,这对于测试组件库非常有用。
3. 网络拦截与模拟(Network Interception) 你可以轻松地拦截和修改网络请求,这对于测试边缘情况、模拟慢速网络或API失败场景至关重要。
# 拦截所有请求,并阻止对某些图片的请求以加速测试
page.route("**/*.{png,jpg,jpeg}", lambda route: route.abort())
# 拦截特定API请求,并返回模拟数据
def handle_route(route):
if "/api/user" in route.request.url:
route.fulfill(
status=200,
content_type="application/json",
body='{"name": "Mock User", "id": 123}'
)
else:
route.continue_() # 其他请求继续
page.route("**/api/*", handle_route)
4. 设备模拟与移动端测试 一行代码即可模拟特定设备(如iPhone 13)的浏览器环境,包括视口大小、User-Agent、触摸事件等。
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
# 模拟iPhone 13
iphone_13 = p.devices['iPhone 13']
browser = p.chromium.launch(headless=False)
# 创建上下文时传入设备参数
context = browser.new_context(**iphone_13)
page = context.new_page()
page.goto('https://m.example.com')
# ... 进行移动端测试
5. 异步API支持 对于高性能或需要并发执行多个浏览器操作的场景,Playwright提供了完整的异步API( async/await )。
import asyncio
from playwright.async_api import async_playwright
async def main():
async with async_playwright() as p:
browser = await p.chromium.launch()
page = await browser.new_page()
await page.goto('http://example.com')
print(await page.title())
await browser.close()
asyncio.run(main())
6.3 与pytest集成及高级场景
Playwright官方提供了 pytest-playwright 插件,让集成变得异常简单。
-
安装插件 :
pip install pytest-playwright -
使用内置夹具 :插件提供了
page,context,browser等夹具,开箱即用。# test_with_playwright_pytest.py def test_playwright_search(page): # 直接使用 page 夹具 page.goto("https://www.baidu.com") page.fill('#kw', 'Playwright pytest') page.click('#su') # 等待选择器出现,并获取其文本 first_result = page.text_content('#content_left h3 a >> nth=0') assert 'Playwright' in first_result运行测试时,无需自己管理浏览器的启动和关闭,插件会自动处理。
-
录制与代码生成 :Playwright有一个强大的录制工具
playwright codegen。- 在命令行运行:
playwright codegen https://www.baidu.com - 这会打开一个浏览器和一个录制窗口。你在浏览器中的所有操作都会被实时转换成Python(或其它语言)代码。这是快速生成测试脚本原型的利器。
- 在命令行运行:
-
处理复杂场景 :
- 文件上传 :
page.set_input_files(‘input[type=”file”]’, ‘path/to/file.pdf’),极其简单。 - 下载文件 :监听
download事件。 - iframe :
frame = page.frame(name=’frame-name’)然后frame.click(‘button’)。 - 弹窗/对话框 :
page.on(‘dialog’, lambda dialog: dialog.accept())自动处理弹窗。
- 文件上传 :
Selenium vs Playwright 最终抉择建议:
- 选择 Selenium 如果 :你的项目需要支持非常老旧的浏览器(如IE),或者团队对Selenium有深厚积累,或者依赖某些仅支持Selenium的第三方云测试平台。
- 选择 Playwright 如果 :你主要测试现代浏览器(Chrome, Firefox, Safari),追求更快的执行速度、更稳定的测试、更简洁的API,并且需要处理复杂的现代Web交互(如SPA、网络拦截)。对于 新项目,我几乎毫无保留地推荐Playwright 。
7. 融会贯通:构建你的自动化测试体系
掌握了这四个框架,你已经有能力搭建一个完整的自动化测试金字塔了。
7.1 测试金字塔实践
理想的自动化测试结构应该是金字塔形:
- 底层(大量) : 单元测试 。使用
pytest(或unittest)测试单个函数、类。运行速度极快,是信心的基石。 - 中层(适量) : 集成测试/API测试 。使用
pytest测试模块间的接口、数据库操作、API调用。可以结合requests库进行HTTP API测试。 - 顶层(少量) : 端到端(E2E)UI测试 。使用
Playwright(或Selenium)模拟用户完整操作流程。运行慢、脆弱,但能验证核心用户旅程。
一个项目目录结构可能如下:
my_project/
├── src/ # 源代码
├── tests/
│ ├── unit/ # 单元测试 (pytest)
│ │ ├── test_models.py
│ │ └── test_services.py
│ ├── integration/ # 集成测试 (pytest + requests)
│ │ └── test_api.py
│ ├── e2e/ # UI端到端测试 (pytest + playwright)
│ │ ├── conftest.py # 共享的playwright夹具
│ │ ├── pages/ # Page Object 类
│ │ │ └── login_page.py
│ │ └── test_login_flow.py
│ └── conftest.py # 全局共享的pytest夹具(如数据库连接)
├── requirements-test.txt # 测试依赖
└── pytest.ini # pytest配置文件
7.2 持续集成(CI)集成
自动化测试只有在持续集成中自动运行才有最大价值。以GitHub Actions为例,一个简单的 .github/workflows/test.yml 配置如下:
name: Python Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.9", "3.10"]
steps:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install -r requirements-test.txt
playwright install --with-deps chromium # 安装Playwright及浏览器
- name: Run unit & integration tests
run: |
pytest tests/unit tests/integration -v
- name: Run E2E tests
run: |
pytest tests/e2e --headless -v
这样,每次代码推送或发起拉取请求时,都会自动运行全套测试,确保新代码不会破坏现有功能。
7.3 最后的忠告:避免常见误区
- 不要为了自动化而自动化 :自动化测试的投入是有成本的。优先自动化那些 稳定、核心、高频 的业务流程。变化过于频繁的页面不适合做UI自动化。
- 测试数据管理 :测试数据是另一个大坑。尽量让测试自己创建所需数据(setup),并在测试后清理(teardown)。使用工厂模式或夹具来生成测试数据。避免使用生产数据库或共享的测试数据库,以免测试间相互干扰。
- 测试的独立性 :每个测试用例必须能够独立运行,且不依赖其他测试用例产生的状态或数据。这是保证测试稳定可靠的基本原则。
- 失败分析 :当UI测试失败时,不要只看断言错误。第一时间查看自动截屏(Playwright和Selenium都支持)、页面源代码、以及浏览器控制台日志。很多前端错误会在控制台体现。
- 保持耐心 :UI自动化测试,尤其是初期,可能会比较“脆弱”。需要花时间优化选择器、完善等待策略、重构Page Object。这是一个迭代的过程,随着框架和经验的成熟,稳定性会越来越高。
从 unittest 的严谨,到 pytest 的强大,再到 Selenium 的经典和 Playwright 的现代,这四个框架构成了Python自动化测试的坚实拼图。没有哪个是“最好”的,只有“最适合”你当前场景的。我的建议是,从 pytest 开始构建你的单元测试基础,然后用 Playwright 攻克UI测试的难关。记住,工具是为人服务的,清晰的测试思路、良好的代码结构和持续的重构,比单纯追求某个框架的新特性更重要。
更多推荐


所有评论(0)