Pytest + Playwright 自动化测试工程设计要点 & 目录结构

Pytest + Playwright 自动化测试工程设计要点 & 目录结构

适用:Web UI自动化,支持多环境、多浏览器、PageObject、数据驱动、报告、CI集成,工业后台/管理系统、Web应用都可复用。

一、整体需要考虑的核心方面

1. 环境与浏览器管理

  1. 浏览器选择:chromium / firefox / webkit;支持无头/有头模式;本地调试有头,CI流水线无头。
  2. playwright 安装依赖playwright install 安装浏览器二进制;CI环境需要处理系统依赖。
  3. pytest fixture 封装浏览器上下文、页面,不要每个用例重复写打开关闭页面。
  4. 支持多浏览器并发执行,pytest‑xdist + playwright。
  5. 窗口大小、代理、慢动作、超时配置,区分开发调试和生产执行。

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_roleget_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/                       # 文档:用例说明、环境部署

关键文件简单说明

  1. conftest.py

    存放全局fixture:初始化playwright page、读取环境配置、登录fixture(返回已登录page)、失败截图钩子。

    conftest不需要import,pytest自动识别。

  2. pages/base_page.py

    所有PO继承BasePage,封装公共方法:输入、点击、获取文本、等待元素,减少重复代码。

    python 复制代码
    class BasePage:
        def __init__(self, page):
            self.page = page
    
        def input_text(self, locator, text):
            self.page.locator(locator).fill(text)
  3. pytest.ini

    配置pytest命令默认参数、自定义mark标签。

    ini 复制代码
    [pytest]
    addopts = -v --alluredir=outputs/allure-results
    markers =
        smoke: 冒烟测试
        regression: 回归测试
  4. 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

四、避坑要点总结

  1. 不要大量使用time.sleep(),用playwright内置等待。
  2. 用例尽量独立,不要依赖上一条用例执行结果;需要前置数据优先接口fixture造数。
  3. 账号密码不要硬编码,走环境变量。
  4. CI务必执行 playwright install‑deps 安装系统依赖,否则浏览器启动失败。
  5. trace录制对定位偶现问题非常有用,失败自动保存trace.zip。
  6. xdist并发时,每个worker独立浏览器上下文,注意账号隔离。

附:Pytest 里的 fixture(固件/夹具)

pytest + Playwright UI自动化里,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清单

  1. page:pytest‑playwright插件自带fixture,每个用例打开新页面,用完关闭。
  2. browser:浏览器实例,session级别,整个测试只启动一次浏览器。
  3. context:浏览器上下文,可以配置cookie、存储登录状态。
  4. 自定义app_config:读取环境配置yaml。
  5. 自定义logged_page:封装登录,返回已登录页面。
  6. 自定义api_client:http接口对象,用来测试前造测试数据。

容易踩坑点

  1. scope不要乱用:不要随便用session给page,多个用例共用同一个页面会互相干扰,大部分UI用例建议function级别,每个用例干净页面。
  2. 并发执行pytest‑xdist,每个worker独立跑fixture,不要跨worker共享登录状态。
  3. yield后面的代码就算用例失败也会执行,保证资源一定释放关闭。

一句话总结fixture

fixture = pytest的资源管理器,把「准备资源、传给用例、用完清理」封装起来,解耦用例和环境准备代码,是整个pytest‑playwright工程的骨架。

相关推荐
努力搬砖的咸鱼1 天前
意图理解:让Agent从需求描述自动生成Pytest测试策略
人工智能·python·ai·单元测试·pytest·agent
测试19982 天前
Python接口测试之requests库安装和导入
自动化测试·软件测试·python·测试工具·职场和发展·测试用例·接口测试
祉猷并茂,雯华若锦4 天前
Selenium与Playwright元素定位终极对决
selenium·测试工具
小白学大数据4 天前
长周期爬虫的数据一致性:断点续爬 + 事务回滚保障采集质量
开发语言·爬虫·测试工具
St_rive4 天前
Pytest skip&&skipif跳过⽤例
运维·服务器·pytest
小绫网络安全4 天前
Wireshark抓包完全入门教程:从零开始掌握网络分析
arm开发·测试工具·wireshark
梦想不只是梦与想5 天前
鸿蒙 测试工具:DevEco Testing(一)
测试工具·harmonyos·testing