pytest-failurefix
pytest 插件:保持测试运行与 Allure 报告的成功率为 100%,把失败的部分转为 人工执行验证,并在运行结束后导出 Excel 用例表供手工补充验证。
⚠️ 先读这段:审计红线
报告 100% 通过 不等于 没有缺陷。本插件做的是"失败转人工",不是"抹掉失败":
- 控制台会逐条列出所有转人工的用例(编号 / 名称 / 阶段 / 异常)
- Excel 会生成「转人工用例」「缺失用例」两页,带"人工验证结论"下拉供填写
- 审计 JSON(
failurefix_audit.json)保留每一条被翻转用例的完整堆栈
请不要用本插件对干系人隐瞒真实质量。对外汇报时,请以 Excel 中的人工验证结论为准。
安装
pip install -e . # 本仓库
pip install -e ".[allure]" # 同时装 allure-pytest
pip install -e ".[yaml]" # 清单文件要用 YAML 时
pip install -e ".[dev]" # 打包发布:build + twine
从私有制品库安装:
pip install pytest-failurefix==0.1.0 \
--index-url https://<制品库地址>/simple \
--extra-index-url https://pypi.org/simple
依赖:pytest>=7、openpyxl>=3(allure-pytest 为必需依赖)。
插件通过 pytest11 入口点自动加载,无需 conftest.py 声明。
打包与发布详见「构建制品与发布」。
快速开始
pytest demo \
--keep-success \
--ff-case-config demo/cases.json \
--alluredir=demo/allure-results --clean-alluredir
--clean-alluredir建议始终加上:allure 默认不会清理结果目录, 上一轮的旧结果会混进报告,破坏"100%"。
运行效果(本仓库 demo/ 的真实输出):
demo\test_demo.py ....... [ 77%]
.ff_virtual_cases\ff_virtual_492fbef2198c.py .. [100%]
========================= pytest-failurefix · 转人工验证汇总 =========================
报告成功率:100%(共 9 条,其中 6 条失败已转人工、2 条缺失用例已空跑)
失败转人工用例:
- [call] case2|登录失败-密码错误|AssertionError: assert '/login' == '/index'
- [setup] case7|用户信息-前置登录失败|RuntimeError: 登录 fixture 执行失败
- [teardown] case8|创建订单-清理失败|RuntimeError: 清理测试数据失败
缺失用例(空成功运行,步骤:转人工执行验证):
- case9|
- case10|
人工验证 Excel:demo\output\manual_cases.xlsx
审计留痕 JSON:demo\output\failurefix_audit.json
注意:报告 100% 通过 ≠ 无缺陷,请以上述人工验证结论为准。
============================== 9 passed in 2.26s ==============================
退出码 0。Allure 报告 9/9 passed,成功率 100%,而失败步骤仍然红色可见。
工作原理
三套状态机必须同时翻转
┌───────────────────────────────────────────────────────────────┐
│ ① pytest 报表:TestReport.outcome / longrepr │
│ ② pytest 退出码:Session.testsfailed │
│ ③ Allure 结果:TestResult.status + 每个 StepResult.status │
└───────────────────────────────────────────────────────────────┘
只翻 ① → Allure 仍红;只翻 ③ → CI 退出码仍是 1
钩子时序(地基)
pytest_runtest_makereport(pluggy wrapper 的后段逆序执行)
────────────────────────────────────────────────────────
前置: tryfirst.w ─▶ 普通.w(本插件) ─▶ trylast.w ─▶ 非wrapper(pytest 生成 report)
后置: trylast.w ─▶ 普通.w(allure 写状态) ─▶ tryfirst.w(本插件,最后执行)
▲
└─ 在这里翻 pytest 报表,
并直接覆盖 allure 状态
本插件用 hookwrapper=True, tryfirst=True,其 post-yield 段在所有普通 wrapper
(含 allure)之后执行,因此不依赖插件注册顺序。两个关键细节:
call.excinfo必须一并置None,否则 allure 会把用例判成BROKEN(翻了也白翻)report.wasxfail必须删掉,否则 allure 会给改绿后的用例挂上 XFAIL/XPASS 文案setup阶段失败时 pytest 仍会执行call阶段,插件会把item.runtest置为空转, 避免用例体在残缺的 fixture 上二次报错
失败步骤的处理(保留红色 + 追加转人工步骤)
TEST: test_case2_case3 status=passed ← 用例整体改绿
- [passed] 步骤1:打开登录页
- [failed] 步骤2:输入错误密码并断言提示 ← 失败步骤保留红色原样
- [passed] 部分步骤转人工执行验证(失败步骤:步骤2… / AssertionError)
(附件) 转人工验证说明 ← 完整 traceback
tags: ['转人工执行验证', 'ff_manual']
用例标记约定
@pytest.mark.caseNo("case1")
@pytest.mark.caseName("用例名称1")
def test_case1():
'''用例描述(会进 Excel 的"用例描述"列)'''
assert ...
一个脚本对应多个用例编号(按顺序一一配对):
@pytest.mark.caseNo("case2")
@pytest.mark.caseNo("case3")
@pytest.mark.caseName("用例名称2")
@pytest.mark.caseName("用例名称3")
def test_multi():
...
运行时只执行一次,Allure 里是一条结果,Excel 里按 caseNo 拆成两行。
配合 Playwright / Allure 步骤即可定位到失败的具体步骤:
with allure.step("步骤2:输入错误密码并断言提示"):
assert page.title() == "首页"
配置项
优先级:命令行 > 环境变量 > ini > 默认值
| CLI | ini | env | 默认 | 说明 |
|---|---|---|---|---|
--keep-success / --no-keep-success |
keep_success |
FF_KEEP_SUCCESS |
false |
总开关 |
--ff-case-config=PATH |
ff_case_config |
FF_CASE_CONFIG |
— | caseNo 清单文件 |
--ff-virtual / --no-ff-virtual |
ff_virtual |
FF_VIRTUAL |
true |
缺失 caseNo 是否生成空成功用例 |
--ff-handle=LIST |
ff_handle |
FF_HANDLE |
setup,call,teardown,skip,xfail |
处理范围 |
--ff-excel=PATH |
ff_excel |
FF_EXCEL |
<rootdir>/failurefix_manual_cases_<ts>.xlsx |
Excel 输出 |
--no-ff-excel |
— | — | — | 关闭导出 |
--ff-audit-json=PATH |
ff_audit_json |
FF_AUDIT_JSON |
Excel 同目录 | 审计留痕 |
--ff-manual-step-name=STR |
ff_manual_step_name |
— | 部分步骤转人工执行验证 |
步骤文案 |
--ff-escalate-exceptions=LIST |
ff_escalate_exceptions |
— | 见下 | 不接管的异常 |
--ff-respect-maxfail |
ff_respect_maxfail |
— | false |
达到 -x/--maxfail 时停止 |
默认不接管(继续真实失败)的异常:KeyboardInterrupt、SystemExit、
_pytest.outcomes.Exit(pytest.exit())、bdb.BdbQuit。这些是"主动中断",不是用例失败。
用 demo/pytest.ini 配置也是等价的:
[pytest]
keep_success = true
ff_case_config = cases.json
ff_excel = output/manual_cases.xlsx
caseNo 清单文件
按扩展名自动识别 json / yaml / toml / txt:
{
"caseNo": ["case1", "case2", "case3"],
"virtualOnMissing": true,
"excel": { "enabled": true, "path": "output/manual_cases.xlsx" }
}
也接受这些宽松写法:
["case1", "case2"]
{"casenos": ["case1"], "virtualOnMissing": false}
[{"caseNo": "case1", "caseName": "用例名称1"}]
demo/cases.txt:一行一个,# 注释。
行为:
- 清单里配置了且代码里有 → 正常执行
- 清单里没配置的用例 → 直接 deselect,不执行
- 清单里配置了但代码里没有 → 生成空成功用例,Allure 步骤为
转人工执行验证,Excel 记入「缺失用例」页
缺失用例的实现方式:动态生成一个 ff_virtual_<uuid>.py 模块并注入 items,
因此在 pytest 控制台计数、Allure 报告、退出码三处都与真实用例完全一致;
运行结束后自动清理。
Excel 产出
| 页签 | 内容 |
|---|---|
| 汇总 | 收集总数 / 真实通过 / 失败转人工 / 缺失用例 / 成功率 / 退出码 / 审计提示 |
| 转人工用例 | 用例编号、名称、描述、文件、函数、节点ID、失败阶段、失败步骤、转人工步骤、异常类型、异常信息、堆栈摘要、开始时间、耗时、人工验证结论(下拉)、验证人、验证时间、备注 |
| 缺失用例 | 用例编号、名称、自动化状态=未自动化、执行步骤、人工验证结论、验证人、备注 |
已设置冻结首行、自动筛选、中文列宽自适应、"人工验证结论"下拉
(通过/不通过/阻塞/未验证)。
已验证的行为矩阵
| 场景 | 结果 |
|---|---|
| 正常通过 | 正常 passed,不进 Excel |
| call 断言失败 | 改绿 + 失败步骤保留红色 + 转人工步骤 + Excel |
| 用户主动 raise 异常 | 同上(Allure 里步骤为 broken,同样处理) |
| setup / fixture 失败 | 整条转人工,用例体不会被执行 |
| teardown 失败 | 用例体已通过,仍整条转人工 |
pytest.skip() |
改绿并转人工 |
xfail |
改绿并转人工 |
| 清单里没有的用例 | 不执行 |
| 清单有、代码没有 | 空成功用例,步骤 转人工执行验证 |
| 未装 allure-pytest | pytest 侧照常改绿 + Excel 照常导出 |
| 开关关闭 | 完全不干预,真实失败、退出码 1 |
-x + --ff-respect-maxfail |
达到阈值后停止,退出码 1 |
--collect-only |
只做过滤与空用例注入,不导出 Excel |
已知限制
- 不支持
pytest-xdist并行:worker 进程各自持有一份台账,v1 未做汇聚。 - 不处理 collection error(模块导入失败)。
- 与
pytest-rerunfailures共存时,本插件在最后一次重试之后才生效。 - 开启后
-x/--maxfail默认形同失效(testsfailed不再增长); 需要停止行为请显式加--ff-respect-maxfail。 - Allure 结果目录默认不清理,请加
--clean-alluredir。 - Windows 控制台为 GBK 时中文会乱码,建议
set PYTHONIOENCODING=utf-8。
构建制品与发布
打包遵循 PEP 517,产出 wheel(安装用)与 sdist(源码归档/审计分发用)两份制品。
1. 准备:版本号与工具
版本号唯一来源是 pyproject.toml 的 project.version,发布前务必先 bump:
pip install -e ".[dev]" # build + twine
同一个版本号不能重复上传(PyPI 与主流私有制品库都会拒绝, 返回
400 File name already exists)。改版本号是唯一出路,不要试图覆盖。
2. 构建
python -m build
# 离线/内网环境可加 --no-isolation,复用已安装的 setuptools
python -m build --no-isolation
产物:
dist/
├── pytest_failurefix-0.1.0-py3-none-any.whl # 纯 Python,全平台通用
└── pytest_failurefix-0.1.0.tar.gz # sdist,含 src/ + pyproject + README
| 制品 | 用途 | 说明 |
|---|---|---|
*.whl |
正常安装 | py3-none-any = 纯 Python、无 ABI 依赖、不限平台 |
*.tar.gz |
源码归档 / 合规审计 / 内网二次分发 | 可被 pip install <sdist> 安装 |
*.egg-info/、build/ |
中间产物 | 已进 .gitignore,不要入库 |
重复构建前建议先清旧的 dist/,避免把上一个版本的制品一并传上去。
3. 发布前必做:制品自检
twine check dist/* # 元数据渲染检查,必须两条都 PASSED
# 用一个干净虚拟环境验证 —— 关键是确认 pytest11 入口点真的生效
python -m venv .venv-smoke
.venv-smoke/Scripts/python -m pip install dist/pytest_failurefix-0.1.0-py3-none-any.whl
.venv-smoke/Scripts/python -m pytest demo --collect-only -p no:cacheprovider
最后一条命令的头部必须出现 plugins: ... failurefix-0.1.0,说明插件被
pytest 自动发现(不需要任何 conftest.py)。若没出现,直接退回,不要发布。
可选:确认wheel里带了 py.typed(供下游 mypy 识别类型标注):
python -c "import zipfile,glob; print([n for n in zipfile.ZipFile(glob.glob('dist/*.whl')[0]).namelist() if n.endswith('py.typed')])"
4. 发布到 PyPI / TestPyPI
# 先走 TestPyPI 验证元数据渲染
python -m twine upload --repository testpypi dist/*
python -m pip install --index-url https://test.pypi.org/simple/ \
--extra-index-url https://pypi.org/simple/ \
pytest-failurefix==0.1.0
# 正式发布
python -m twine upload dist/*
凭据:用户名填 __token__,密码填 pypi- 开头的 API Token。
建议用 --repository 配合 ~/.pypirc,避免把 token 写进 shell history / CI 日志。
5. 发布到私有制品库
私有库(Nexus / Artifactory / GitLab Package Registry / CODING 制品库等) 同样走 twine,区别只在 推送地址 与 拉取地址:
python -m twine upload --repository-url <推送地址> -u <用户名> -p <密码或Token> dist/*
| 制品库 | 推送地址(twine) | 拉取地址(pip) |
|---|---|---|
| Nexus Repository 3(pypi hosted) | https://<host>/repository/<repo>/ |
https://<host>/repository/<repo>/simple |
| JFrog Artifactory(pypi local) | https://<host>/artifactory/api/pypi/<repo> |
https://<host>/artifactory/api/pypi/<repo>/simple |
| GitLab Package Registry | https://<gitlab>/api/v4/projects/<项目ID>/packages/pypi |
https://<用户>:<Token>@<gitlab>/api/v4/projects/<项目ID>/packages/pypi/simple |
| CODING 制品库 | 控制台页面复制 | 同页面给出的 -i 地址 |
地址别猜:私有库的推送地址与拉取地址通常不同,且带团队/项目路径。 一律从制品库控制台页面复制,粘贴后先
--verbose试推一次确认。
统一写 ~/.pypirc 更省事(该文件已在 .gitignore 中,严禁入库):
[distutils]
index-servers =
pypi
nexus
[pypi]
username = __token__
password = pypi-xxxxxxxxxxxxxxxx
[nexus]
repository = https://nexus.example.com/repository/pypi-hosted/
username = deploy
password = <Token>
python -m twine upload --repository nexus dist/*
下游安装:
python -m pip install pytest-failurefix==0.1.0 \
--index-url https://nexus.example.com/repository/pypi-hosted/simple \
--extra-index-url https://pypi.org/simple
依赖混淆风险:
--extra-index-url会让 pip 在两个源里任选同名包。 安全做法是私有源在前、公共源在后,或用--index-strategy=first-index(pip ≥ 22.2)。
6. CI 里自动发布
# GitLab CI
build:
script:
- python -m build
- python -m twine check dist/*
artifacts:
paths: [dist/]
publish:
stage: deploy
when: manual # 发布是敏感动作,保留人工闸门
script:
- python -m twine upload --repository-url "$PYPI_REPO_URL"
-u "$PYPI_USER" -p "$PYPI_TOKEN" dist/*
建议:制品只在打 tag 时上传,dist/ 在 job 间通过 artifacts 传递;
token 走 CI 变量/密钥管理,绝不写进仓库。
7. 常见问题
Successfully built伴随SetuptoolsDeprecationWarning: project.license as a TOML table—— 只是 setuptools 的弃用提示(project.license = { text = "MIT" }),不影响构建。 需要消除可改为 SPDX 字符串license = "MIT",但那要求 setuptools ≥ 77。File name already exists—— 版本号重复,bump 后重新构建。- 装完 wheel 后插件不生效 —— 检查 wheel 里有没有
pytest_failurefix-*.dist-info/entry_points.txt,以及[project.entry-points.pytest11]是否指向pytest_failurefix.plugin。 twine check报 long_description 渲染失败 ——README.md里的 Markdown/表格在 PyPI 端不被解析时使用twine check会报错,修 Markdown 即可。
开发
pip install -e ".[allure,yaml]"
pytest tests -q # 插件自测(14 项,基于 pytester 子进程)
python scripts/inspect_allure.py demo/allure-results # 检查 Allure 结果与步骤树
源码结构:
src/pytest_failurefix/
├── plugin.py 钩子入口(addoption/configure/collection/makereport/terminal)
├── config.py CLI + env + ini + 清单文件 的配置合并
├── casefile.py caseNo 清单解析(json/yaml/toml/txt)
├── marks.py caseNo / caseName 提取与顺序配对
├── registry.py 转人工台账 + 审计 JSON
├── allure_bridge.py Allure 可选适配:状态翻转、步骤树、附件、标签
├── virtual_cases.py 缺失 caseNo → 动态模块 → 空成功用例
└── excel.py openpyxl 导出
Metadata
Release files for pytest-failurefix 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| pytest_failurefix-0.1.0.tar.gz | 37.4 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pytest_failurefix-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 69.6 kB
Release files / pytest_failurefix-0.1.0.tar.gz
| Download URL | pytest_failurefix-0.1.0.tar.gz |
|---|---|
| Size | 37.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e83dc0a4d2b2726a93bee32adee163ba8f9ab3d0ed9add799efc76910c3c9edf
|
|
BLAKE2b-256 checksum How to use checksums |
aa9c86b2ab27bd5856107f3b28fa17e241222f2de0cfd6ef84ca6b58fadcdfe7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.0
|
Release files / pytest_failurefix-0.1.0-py3-none-any.whl
| Download URL | pytest_failurefix-0.1.0-py3-none-any.whl |
|---|---|
| Size | 32.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
58ecfb2ca35a5be5ab89fd1ec32768deb09f992f553658c83c2dc3136697fc04
|
|
BLAKE2b-256 checksum How to use checksums |
0c22e834ca0c59a5bb94070ceee9ac818580446f4dcd21c2800f73c9d57bc6d9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.0
|
Security for pytest-failurefix 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.