Skip to main content

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:一行一个,# 注释。

行为:

  1. 清单里配置了且代码里有 → 正常执行
  2. 清单里没配置的用例 → 直接 deselect,不执行
  3. 清单里配置了但代码里没有 → 生成空成功用例,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)

Source distribution for pytest-failurefix 0.1.0
File Size Uploaded Provenance
pytest_failurefix-0.1.0.tar.gz 37.4 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for pytest-failurefix 0.1.0
File Interpreter ABI Platform
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.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page