Pytest + Playwright 自动化测试工程设计要点 & 目录结构
适用:Web UI自动化,支持多环境、多浏览器、PageObject、数据驱动、报告、CI集成,工业后台/管理系统、Web应用都可复用。
一、整体需要考虑的核心方面
1. 环境与浏览器管理
- 浏览器选择:chromium / firefox / webkit;支持无头/有头模式;本地调试有头,CI流水线无头。
- playwright 安装依赖 :
playwright install安装浏览器二进制;CI环境需要处理系统依赖。 - pytest fixture 封装浏览器上下文、页面,不要每个用例重复写打开关闭页面。
- 支持多浏览器并发执行,pytest‑xdist + playwright。
- 窗口大小、代理、慢动作、超时配置,区分开发调试和生产执行。
2. PageObject 模式(PO模式)
- 把页面元素定位、页面操作封装到页面对象,测试用例只写业务逻辑,不写xpath/css选择器。
- 页面对象只负责元素与动作,不做断言;断言放在test用例中。
- 元素定位统一管理,页面改版只改PO层,不用大面积修改用例。
3. 配置管理(多环境)
- 区分环境:dev / test / pre / prod;域名、账号、接口地址不同。
- 配置来源:yaml配置文件 + pytest命令行参数 + 环境变量。
- 敏感信息(账号密码)不要硬编码,使用环境变量,不要提交到git。
4. 测试数据管理
- 业务测试数据:yaml/json存放,数据驱动
@pytest.mark.parametrize。 - 测试账号:独立测试账号,避免多用例并发互相污染;必要时做数据准备/数据清理fixture。
- 避免用例之间数据强依赖,尽量做到用例独立可重复执行。
5. 用例设计与标记
- pytest mark:
@pytest.mark.smoke、@pytest.mark.regression,支持按标签执行用例。 - 用例原则:一条用例测一个业务点;用例之间尽量隔离,每个用例尽量独立。
- 前置后置:使用fixture,少用setup/teardown。
- 失败重试:
pytest‑rerunfailures,处理偶现flaky用例。
6. 等待与元素定位
Playwright自带自动等待,尽量不要写
time.sleep()
- 使用page.wait_for_selector、expect断言等待元素状态。
- 定位器优先使用
get_by_role、get_by_text,其次data‑testid,尽量少用脆弱的xpath。 - 统一封装超时时间,全局配置。
7. 报告、截图、录屏
- 失败自动截图、录屏:playwright内置,pytest‑playwright可以配置。
- 测试报告:pytest‑html / allure‑pytest;推荐Allure,可读性更好。
- 产物输出目录:截图、视频、日志统一输出,CI中可以作为制品保存。
8. 日志与调试
- 封装logging,记录操作步骤、请求信息。
- 调试模式:slow_mo、headless=False、trace录制,
page.tracing,失败保存trace.zip,可在playwright trace viewer回放。
9. 并发执行
- pytest‑xdist 多进程运行;注意:playwright每个worker独立浏览器上下文,注意账号数据冲突。
- 并发下注意:不要多个worker共用同一个登录账号。
10. CI/CD集成
- GitHub Actions / GitLab CI / Jenkins。
- 流水线步骤:安装python依赖 → playwright install‑deps 系统依赖 → 执行pytest → 收集allure报告、截图视频。
- 无头模式运行。
11. 接口混合(可选)
很多UI自动化会需要接口做准备数据:可以集成requests,fixture封装http client,测试前调用接口造数据,UI只做页面校验。
12. 异常与稳定性
- flaky用例分析,trace回放定位问题。
- 弹窗、toast、loading等待处理。
- 页面跳转、网络慢场景兼容。
二、推荐工程目录结构
pytest‑playwright‑demo/
├── .gitignore # 忽略venv、allure报告、截图视频、*.pyc、环境配置
├── requirements.txt # 依赖清单
│ # pytest, playwright, pytest‑playwright, allure‑pytest,
│ # pyyaml, pytest‑rerunfailures, pytest‑xdist
├── pytest.ini # pytest全局配置:mark、默认参数、addopts
├── conftest.py # 全局fixture:浏览器、登录、读取配置、钩子
├── config/ # 配置文件
│ ├── settings.py # 配置类,读取yaml/环境变量
│ └── env/
│ ├── dev.yaml
│ ├── test.yaml
│ └── pre.yaml
├── data/ # 测试数据
│ ├── test_data.yaml # 业务测试数据
│ └── accounts.yaml # 账号信息(不要提交真实密码,用环境变量占位)
├── pages/ # PageObject页面对象层
│ ├── __init__.py
│ ├── base_page.py # BasePage,封装公共操作:点击、输入、等待、断言封装
│ ├── login_page.py # 登录页PO
│ └── dashboard_page.py # 业务页面PO
├── tests/ # 测试用例目录
│ ├── __init__.py
│ ├── conftest.py # 用例层局部fixture
│ ├── test_smoke/ # 冒烟用例
│ │ ├── test_login.py
│ │ └── test_dashboard.py
│ └── test_regression/ # 回归用例
│ └── test_xxx.py
├── utils/ # 工具类
│ ├── logger.py # 日志封装
│ ├── yaml_reader.py # yaml读取工具
│ └── http_client.py # 接口工具(造测试数据)
├── outputs/ # 输出产物(gitignore)
│ ├── screenshots/ # 失败截图
│ ├── videos/ # 录屏
│ ├── traces/ # playwright trace文件
│ └── allure‑results/ # allure原始结果
└── docs/ # 文档:用例说明、环境部署
关键文件简单说明
-
存放全局fixture:初始化playwright page、读取环境配置、登录fixture(返回已登录page)、失败截图钩子。
conftest不需要import,pytest自动识别。
-
pages/base_page.py
所有PO继承BasePage,封装公共方法:输入、点击、获取文本、等待元素,减少重复代码。
pythonclass BasePage: def __init__(self, page): self.page = page def input_text(self, locator, text): self.page.locator(locator).fill(text) -
pytest.ini
配置pytest命令默认参数、自定义mark标签。
ini[pytest] addopts = -v --alluredir=outputs/allure-results markers = smoke: 冒烟测试 regression: 回归测试 -
config/env/ 多环境yaml
yaml
# test.yaml
base_url: "https://test‑xxx.com"
username: "${TEST_USER}"
password: "${TEST_PWD}"
代码中读取环境变量替换占位符,避免明文密码。
三、示例运行命令参考
bash
# 执行冒烟用例
pytest tests/test_smoke -m smoke
# 多进程并发
pytest -n auto
# 生成allure报告
pytest --alluredir=outputs/allure-results
allure generate outputs/allure-results -o outputs/allure-report --clean
四、避坑要点总结
- 不要大量使用
time.sleep(),用playwright内置等待。 - 用例尽量独立,不要依赖上一条用例执行结果;需要前置数据优先接口fixture造数。
- 账号密码不要硬编码,走环境变量。
- CI务必执行
playwright install‑deps安装系统依赖,否则浏览器启动失败。 - trace录制对定位偶现问题非常有用,失败自动保存trace.zip。
- xdist并发时,每个worker独立浏览器上下文,注意账号隔离。
附:Pytest 里的 fixture(固件/夹具)
在
pytest + PlaywrightUI自动化里,fixture 就是可复用的「前置+后置代码块」 ,用来准备测试环境、资源,测试结束后做清理,替代老旧的setup()/teardown()。
简单大白话:
用例执行之前帮你准备好东西,用例跑完帮你收拾干净,可以被多个测试用例直接拿来用,不用重复写代码。
对比老写法 setup/teardown(不推荐)
python
# 老式,每个测试类都要写,复用差
class TestLogin:
def setup_method(self):
# 每个用例执行前:启动浏览器、打开页面
...
def teardown_method(self):
# 每个用例执行后:关闭浏览器
...
def test_login(self):
...
fixture 把这套「准备‑清理」抽出来,多处复用,灵活控制生命周期。
fixture 基础样子
用 @pytest.fixture 装饰函数,函数返回你要给用例使用的对象(比如 playwright 的 page)。
python
# conftest.py
import pytest
from playwright.sync_api import Page
@pytest.fixture
def page(page: Page):
"""pytest-playwright内置fixture,已经帮你建好浏览器、page对象"""
page.goto("https://test.xxx.com")
return page
测试用例直接把 page 当做参数写进去,pytest自动调用这个fixture:
python
def test_login(page): # 自动注入fixture返回的page对象
page.get_by_label("账号").fill("admin")
你不用手动调用
page(),pytest 自动识别参数名字,自动执行fixture函数。
fixture 核心概念,结合Playwright场景讲
1. 生命周期 scope(作用域)
控制 fixture 什么时候创建、什么时候销毁,非常关键,UI自动化经常用:
| scope | 执行时机 | 使用场景 |
|---|---|---|
function(默认) |
每个测试用例前后 | 每个用例全新页面,用例之间隔离,最常用 |
class |
每个测试类前后 | 一个类里面所有用例共用一个page(慎用,用例容易互相污染) |
module |
一个py文件前后 | 整个脚本共用浏览器上下文 |
session |
整个pytest执行全程只跑1次 | 全局:读取配置、启动一次浏览器,所有用例共享 |
示例:session级读取环境配置,整个测试只读取一次yaml
python
@pytest.fixture(scope="session")
def app_config():
return read_yaml("config/env/test.yaml")
2. yield:实现前置 + 后置(核心!)
yield 之前 = 测试前执行(准备)
yield 之后 = 测试跑完后执行(清理)
python
@pytest.fixture(scope="function")
def logged_page(page: Page):
# --------前置:用例运行之前,做登录
page.goto("/login")
page.get_by_label("用户名").fill("test")
page.get_by_label("密码").fill("123456")
page.get_by_role("button", name="登录").click()
yield page # 把page交给测试用例使用
# --------后置:用例跑完之后执行,清理工作
page.get_by_role("button", name="退出").click()
用例直接拿到已经登录好的页面:
python
def test_dashboard(logged_page):
# 这里不用再写登录逻辑,fixture已经登录完成
assert logged_page.get_by_text("工作台").is_visible()
没有yield,直接return:只有前置,没有后置清理。
3. fixture可以依赖fixture
fixture可以调用另外一个fixture,层层组装。
例如:logged_page 依赖pytest‑playwright内置的 page fixture;page 又依赖 browser fixture。
browser(session) → context → page(function) → logged_page(登录后的page)
4. conftest.py 的作用
放在conftest.py里的fixture,不需要import,全部用例直接通过参数名使用 。
这就是我们工程目录里放 conftest.py 的原因:存放全局公用fixture(浏览器、登录、读取配置)。
在Playwright自动化里常见fixture清单
page:pytest‑playwright插件自带fixture,每个用例打开新页面,用完关闭。browser:浏览器实例,session级别,整个测试只启动一次浏览器。context:浏览器上下文,可以配置cookie、存储登录状态。- 自定义
app_config:读取环境配置yaml。 - 自定义
logged_page:封装登录,返回已登录页面。 - 自定义
api_client:http接口对象,用来测试前造测试数据。
容易踩坑点
- scope不要乱用:不要随便用
session给page,多个用例共用同一个页面会互相干扰,大部分UI用例建议function级别,每个用例干净页面。 - 并发执行
pytest‑xdist,每个worker独立跑fixture,不要跨worker共享登录状态。 - yield后面的代码就算用例失败也会执行,保证资源一定释放关闭。
一句话总结fixture
fixture = pytest的资源管理器,把「准备资源、传给用例、用完清理」封装起来,解耦用例和环境准备代码,是整个pytest‑playwright工程的骨架。