Allure报告

10 minute read

Allure 测试报告 完整学习大纲


一、Allure 概述

1.1 什么是 Allure Report?

  • 定义:Allure Report 是一个轻量级、支持多语言的开源测试报告框架,由 Yandex 团队开源,能把原始测试结果渲染成一份交互式的 HTML 报告。
  • 核心思路:测试框架(pytest/TestNG/JUnit…)只负责产出结构化结果文件(JSON/XML),Allure 命令行工具负责渲染成报告——两者解耦,所以能跨语言通用。
  • 现状:目前主流版本是 Allure 2(即 Allure Report 2.x),通常说的 “Allure” 指的就是它。

1.2 为什么需要它?

原生 pytest 输出是这样的:

test_login.py::test_login_success PASSED                 [ 33%]
test_login.py::test_login_wrong_pwd FAILED               [ 66%]
test_login.py::test_login_empty PASSED                   [100%]

问题:只有通过/失败,看不出业务含义、看不到失败截图、不知道哪一步失败、无法给非技术同事看。

Allure 解决的是:让测试报告从”给测试自己看”变成”给整个团队看”。

1.3 报告工具对比

工具 输出 优点 缺点
Allure 交互式 HTML ★ 美观、步骤/附件/历史趋势/分类齐全 需装命令行工具(依赖 Java)
pytest-html 单页 HTML 零配置,一个插件搞定 信息量少,无趋势,附件弱
HTMLTestRunner HTML Python 老牌 样式老旧,维护停滞
JUnit XML XML CI 通用、机器可读 不是给人看的
自研报告 自定义 灵活 成本高

💡 选型建议:项目要出正式测试报告、需要给产品或领导看 → Allure;只是快速看结果 → pytest-html 足够。

1.4 核心特性

  • 分层组织:Epic / Feature / Story 按业务维度组织用例
  • 步骤(Step):展示用例执行的每一步,失败定位到具体步骤
  • 附件(Attachment):截图、日志、请求响应、视频
  • 严重等级:BLOCKER / CRITICAL / NORMAL / MINOR / TRIVIAL
  • 缺陷分类(Categories):把失败原因归类(如”产品缺陷”/”测试脚本问题”)
  • 历史趋势:多次执行对比,看通过率变化
  • 时间轴(Timeline):并发执行时看时序
  • 环境信息:记录执行环境(版本、浏览器、URL)

1.5 生态支持

Python:   pytest (allure-pytest)      ★ 本文重点
Java:     TestNG / JUnit4 / JUnit5 / Cucumber JVM
JS:       Jest / Mocha / Cypress / Playwright
PHP:      PHPUnit
.NET:     NUnit / xUnit
其他:      Robot Framework / Behave / RSpec

二、环境搭建

2.1 安装(两步:Python 插件 + 命令行工具)

# 第一步:安装 pytest 插件(负责产出结果文件)
pip install allure-pytest

# 第二步:安装 Allure 命令行工具(负责渲染报告)

# Windows(需先装 Scoop,或用 winget)
scoop install allure
# 或
winget install Qameta.Allure

# macOS
brew install allure

# Linux / 手动(通用)
# 从 https://github.com/allure-framework/allure2/releases 下载 zip
# 解压后把 bin 目录加入 PATH

⚠️ 关键点:Allure 命令行工具是 Java 写的,运行必须依赖 JDK 8+。很多”命令找不到”“报告生成失败”的问题根源是没装 Java 或版本不对。

# 验证
allure --version
java -version        # 必须是 8 或以上

2.2 常见问题

现象 原因 解决
allure: command not found 未装命令行工具或未配 PATH 重装并把 bin 加入 PATH
'allure' 不是内部或外部命令 同上(Windows) 检查环境变量,重开终端
提示 Java 相关错误 未装 JDK 或版本过低 装 JDK 8+
allure serve 打不开浏览器 端口被占用 allure serve --port 8081 <dir>
报告是空的 路径写错,或没跑 pytest 确认 allure-results 下有 .json 文件

三、快速上手(三步出报告)

3.1 写用例

# test_login.py
import allure


@allure.feature("登录模块")
@allure.story("用户登录")
@allure.title("使用正确的账号密码登录成功")
def test_login_success():
    with allure.step("输入用户名"):
        assert True
    with allure.step("输入密码"):
        assert True
    with allure.step("点击登录按钮"):
        assert True
    with allure.step("断言跳转到首页"):
        assert True


@allure.feature("登录模块")
@allure.story("用户登录")
@allure.title("密码错误时登录失败")
def test_login_wrong_pwd():
    assert False, "预期登录失败,实际却成功了"

3.2 执行并生成结果

# 第一步:跑测试,把结果写到 allure-results 目录
pytest test_login.py --alluredir=./allure-results --clean-alluredir
参数说明:
  --alluredir=./allure-results    指定结果输出目录
  --clean-alluredir               每次执行前清空旧结果(强烈建议加,否则历史数据会混进来)

执行后目录内容:

allure-results/
├── 4a7c1f2e-xxxx-result.json     每条用例一个结果文件
├── 8b9d3c4e-xxxx-result.json
├── environment.properties        环境信息(可选,需自己生成)
├── categories.json               缺陷分类(可选)
└── history/                      历史趋势数据(需自己复制)

3.3 查看报告(三种方式)

# 方式一:临时起服务并自动打开浏览器(本地查看最方便)
allure serve ./allure-results

# 方式二:生成静态 HTML 报告(CI 归档用)
allure generate ./allure-results -o ./allure-report --clean

# 方式三:打开已生成的静态报告
allure open ./allure-report
命令 用途 场景
allure serve 起临时服务,自动开浏览器 本地调试
allure generate 生成静态文件到目录 CI 归档、发布
allure open 打开已生成的报告 查看历史归档

⚠️ allure generate 的 -o 目录建议加 --clean,否则旧文件会残留。另外生成的报告用双击 HTML 直接打开会因跨域限制看不到内容,必须走 allure open 或 HTTP 服务。

四、报告页面结构详解

4.1 七大页面

页面 作用 常用场景
Overview 总览 通过率、趋势、环境、套件统计 给领导看的第一页
Categories 分类 按缺陷类型归类失败用例 区分”产品 Bug”还是”脚本问题”
Suites 套件 按测试套件/类/方法组织 开发定位用
Graphs 图表 状态分布、严重等级、耗时、重试 分析质量分布
Timeline 时间轴 按时间线展示执行过程 并发执行时看时序
Behaviors 行为 按 Epic/Feature/Story 组织 按业务维度看覆盖率
Packages 包 按代码包结构组织 开发定位用

4.2 Overview 页要点

┌────────────────────────────────────────────┐
│  执行信息:开始时间、持续时间、总用例数      │
│  环形图:通过 / 失败 / 跳过 / 损坏 占比      │
│  Trend:历史趋势折线图(需配置 history)     │
│  环境信息:Environment 卡片(需自己生成)    │
│  套件统计:各套件通过率                      │
│  分类统计:Categories 各类型数量             │
└────────────────────────────────────────────┘

4.3 Behaviors 页(业务视角,最推荐)

Epic: 电商系统                          ← 一级:系统/大模块
  └─ Feature: 订单模块                   ← 二级:功能模块
      └─ Story: 用户下单                 ← 三级:用户故事
          ├─ 用例:使用优惠券下单成功
          ├─ 用例:库存不足时下单失败
          └─ 用例:未登录时下单跳转登录页

💡 测试价值:Behaviors 页让产品经理也能看懂测试覆盖了哪些业务场景,是跨角色沟通的桥梁。建议所有用例都打上 Epic/Feature/Story。

4.4 用例详情页

点开一条用例,能看到:

  • Status:通过/失败及失败原因
  • Description:用例描述
  • Steps:执行步骤(失败会标红并展开)
  • Attachments:截图、日志等附件
  • Links:关联的缺陷单、需求单
  • Labels:标签、严重等级、所属套件
  • Duration:耗时
  • History/Retries:历史趋势、重试记录

五、核心装饰器 API(重点)

5.1 业务分层:epic / feature / story

import allure


@allure.epic("电商系统")
@allure.feature("订单模块")
@allure.story("用户下单")
def test_create_order():
    ...
装饰器 层级 建议粒度
@allure.epic 一级 整个系统/大业务线
@allure.feature 二级 功能模块(订单、支付、用户)
@allure.story 三级 用户故事(下单、取消订单、退款)

也可以用 @allure.parent_suite / @allure.suite / @allure.sub_suite 在 Suites 页做类似分层。

5.2 标题:title

@allure.title("使用正确的账号密码登录成功")
def test_login():
    ...

# 参数化时动态生成标题(很实用)
@pytest.mark.parametrize("username,password,expect", [
    ("admin", "123456", True),
    ("admin", "wrong", False),
    ("", "123456", False),
])
@allure.title("登录测试:账号={username},密码={password}")
def test_login_params(username, password, expect):
    ...

💡 不加 @allure.title 时,报告里显示的是函数名(test_login_params[admin-123456-True]),可读性差。参数化用例强烈建议加动态标题。

5.3 描述:description

@allure.description("""
验证用户在未登录状态下访问订单页面,应跳转到登录页。
前置条件:清除浏览器 Cookie
优先级:P0
""")
def test_order_need_login():
    ...

# 也支持 HTML 富文本
@allure.description_html("""
<h2>测试目的</h2>
<p>验证<b>订单</b>模块的边界值</p>
<table border="1"><tr><th>场景</th><th>预期</th></tr>
<tr><td>数量为0</td><td>提示错误</td></tr></table>
""")
def test_order_boundary():
    ...

5.4 严重等级:severity

import allure

@allure.severity(allure.severity_level.BLOCKER)
def test_payment():
    ...
等级 含义 对应优先级
BLOCKER 阻塞,系统不可用 P0
CRITICAL 严重,核心功能失效 P0
NORMAL 一般,功能缺陷 P1(默认)
MINOR 次要,界面/文案问题 P2
TRIVIAL 轻微,建议类 P3
# 按严重等级筛选执行
pytest --allure-severities=blocker,critical

5.5 步骤:step

# 方式一:with 上下文(推荐,能包裹多条语句)
def test_login():
    with allure.step("打开登录页"):
        print("open /login")
    with allure.step("输入账号密码"):
        print("input")
    with allure.step("点击登录"):
        print("click")

# 方式二:装饰器(适合封装好的函数)
@allure.step("登录用户:{username}")
def login(username, password):
    print(f"login {username}")

def test_use_step():
    login("admin", "123456")      # 该调用会在报告里显示为一个步骤
# 嵌套步骤
with allure.step("下单流程"):
    with allure.step("选择商品"):
        ...
    with allure.step("提交订单"):
        ...

💡 核心价值:失败时报告会精确标红失败的那一步,不用再翻日志猜是哪一步挂的。这是 Allure 最实用的功能之一。

5.6 附件:attach(重点)

import allure

# 文本
allure.attach("这是一段日志", name="执行日志",
              attachment_type=allure.attachment_type.TEXT)

# HTML
allure.attach("<h1>标题</h1><p>内容</p>", name="HTML片段",
              attachment_type=allure.attachment_type.HTML)

# JSON
allure.attach(json.dumps(data, ensure_ascii=False, indent=2), name="响应数据",
              attachment_type=allure.attachment_type.JSON)

# 图片(传 bytes)
allure.attach(png_bytes, name="截图",
              attachment_type=allure.attachment_type.PNG)

# 从文件读取
allure.attach.file("./logs/app.log", name="应用日志",
                   attachment_type=allure.attachment_type.TEXT)

常用 attachment_type:

类型 用途 类型 用途
TEXT 纯文本/日志 PNG 截图(PNG)
HTML HTML 片段 JPG 截图(JPG)
JSON JSON 数据 CSV 表格数据
XML XML 数据 PDF PDF 文档
YAML YAML 配置 ZIP 压缩包
@allure.link("https://wiki.example.com/order", name="需求文档")
@allure.issue("https://jira.example.com/browse/BUG-1234", name="BUG-1234")
@allure.testcase("https://tapd.example.com/case/5678", name="TC-5678")
def test_order():
    ...
装饰器 显示位置 用途
link Links 区(普通图标) 需求文档、Wiki
issue Links 区(Bug 图标) 关联缺陷单
testcase Links 区(用例图标) 关联用例管理平台

💡 测试价值:把失败用例和 Jira 单号关联起来,开发点开报告就能直接跳到缺陷详情,沟通成本大幅降低。

5.8 标签:tag / label

@allure.tag("冒烟", "P0", "回归")
@allure.label("owner", "张三")
@allure.label("env", "test")
def test_smoke():
    ...
# 按标签筛选执行
pytest --allure-tags=冒烟
pytest --allure-tags=冒烟,P0          # 多标签(或关系)

5.9 运行时动态设置

装饰器只能写死,参数化/循环场景下需要用 dynamic:

import allure

@pytest.mark.parametrize("case", cases)
def test_dynamic(case):
    allure.dynamic.title(case["name"])              # 动态标题
    allure.dynamic.description(case["desc"])        # 动态描述
    allure.dynamic.severity(case["severity"])       # 动态等级
    allure.dynamic.feature(case["module"])          # 动态模块
    allure.dynamic.tag(case["tag"])
    allure.dynamic.link(case["doc_url"], name="文档")
    ...

六、实战:失败自动截图(重点)

这是 Allure 在 UI 自动化中最有价值的用法——用例失败时自动把截图贴进报告。

6.1 完整实现(conftest.py)

# conftest.py
import allure
import pytest
from selenium import webdriver


# ---------- 1. 浏览器 fixture ----------
@pytest.fixture(scope="function")
def driver():
    d = webdriver.Chrome()
    d.maximize_window()
    yield d
    d.quit()


# ---------- 2. 失败时自动截图 ----------
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item, call):
    """把每个阶段(setup/call/teardown)的执行结果挂到 item 上"""
    outcome = yield
    report = outcome.get_result()
    setattr(item, "rep_" + report.when, report)     # rep_setup / rep_call / rep_teardown


@pytest.fixture(autouse=True)
def screenshot_on_failure(driver, request):
    """用例执行后自动触发,失败则截图"""
    yield
    # 只在用例主体(call)失败时才截图
    if hasattr(request.node, "rep_call") and request.node.rep_call.failed:
        try:
            png = driver.get_screenshot_as_png()     # 也可用 save_screenshot(path) 再读文件
            allure.attach(png, name="失败截图",
                          attachment_type=allure.attachment_type.PNG)
        except Exception as e:
            # 浏览器已崩溃时截图会失败,别让它影响报告生成
            allure.attach(f"截图失败: {e}", name="截图异常",
                          attachment_type=allure.attachment_type.TEXT)

原理说明:

1. pytest_runtest_makereport 是 pytest 的钩子,在每个阶段执行完后被调用
2. hookwrapper=True 让我们能拿到执行结果(report)
3. 把结果存到 item.rep_call(call 阶段 = 用例主体)
4. screenshot_on_failure 是 autouse fixture,在用例后执行
5. 检查 rep_call.failed → 失败则截图 → allure.attach 贴进报告

6.2 用例中使用

# test_baidu.py
import allure
from selenium.webdriver.common.by import By


@allure.epic("搜索功能")
@allure.feature("百度搜索")
@allure.story("搜索关键字")
@allure.title("搜索'pytest'应显示结果")
def test_search(driver):
    with allure.step("打开百度首页"):
        driver.get("https://www.baidu.com")
    with allure.step("输入关键字 pytest"):
        driver.find_element(By.ID, "kw").send_keys("pytest")
    with allure.step("点击搜索按钮"):
        driver.find_element(By.ID, "su").click()
    with allure.step("断言结果页包含pytest"):
        assert "pytest" in driver.title      # 失败时报告里会自动附截图
pytest test_baidu.py --alluredir=./allure-results --clean-alluredir
allure serve ./allure-results

更多 Selenium 用法见 Selenium 与 PO模式。

6.3 接口测试:附请求与响应

# conftest.py
import allure
import requests


@pytest.fixture
def api():
    session = requests.Session()
    yield session
    session.close()


@pytest.fixture(autouse=True)
def attach_api_log(request):
    """记录每次接口调用的请求与响应"""
    logs = []

    def record(method, url, **kwargs):
        resp = requests.request(method, url, **kwargs)
        logs.append(
            f"【请求】{method.upper()} {url}\n"
            f"  headers: {dict(kwargs.get('headers', {}))}\n"
            f"  body: {kwargs.get('json') or kwargs.get('data')}\n"
            f"【响应】{resp.status_code}\n"
            f"  body: {resp.text[:2000]}\n"
        )
        return resp

    globals()["_record_request"] = record
    yield
    if logs:
        allure.attach("\n".join(logs), name="接口调用日志",
                      attachment_type=allure.attachment_type.TEXT)
# 更简洁的做法:直接在用例里贴
def test_query_order(api):
    resp = api.get("https://api.example.com/orders/1")
    allure.attach(
        json.dumps(resp.json(), ensure_ascii=False, indent=2),
        name="响应数据", attachment_type=allure.attachment_type.JSON
    )
    assert resp.status_code == 200

6.4 其他常用附件场景

# 附页面源码(排查元素定位失败很有用)
allure.attach(driver.page_source, name="页面源码",
              attachment_type=allure.attachment_type.HTML)

# 附浏览器日志
allure.attach("\n".join(driver.get_log("browser")), name="Console日志",
              attachment_type=allure.attachment_type.TEXT)

# 附 App 自动化截图(UIAutomator2)
allure.attach(d.screenshot(format="raw"), name="App截图",
              attachment_type=allure.attachment_type.PNG)

# 附日志文件
allure.attach.file("./logs/test.log", name="完整日志",
                   attachment_type=allure.attachment_type.TEXT)

七、环境信息与缺陷分类

7.1 environment.properties(环境信息)

# 方式一:在 allure-results 目录手动创建
cat > ./allure-results/environment.properties <<'EOF'
system.version=2.3.0
test.env=TEST
base.url=https://test.example.com
browser=Chrome 120
python.version=3.11.5
executor=张三
EOF
# 方式二:用 conftest 自动生成(推荐,动态获取真实值)
# conftest.py
import platform
import pytest


def pytest_configure(config):
    """生成环境信息文件"""
    import os
    results_dir = config.getoption("--alluredir")
    if not results_dir:
        return
    os.makedirs(results_dir, exist_ok=True)
    env = {
        "system.version": "2.3.0",
        "test.env": "TEST",
        "python.version": platform.python_version(),
        "platform": platform.platform(),
        "executor": os.getenv("USER", "unknown"),
    }
    with open(os.path.join(results_dir, "environment.properties"),
              "w", encoding="utf-8") as f:
        for k, v in env.items():
            f.write(f"{k}={v}\n")

7.2 categories.json(缺陷分类)

默认只有”Product defects”和”Test defects”。自定义后可按失败原因归类,便于区分责任:

[
  {
    "name": "产品缺陷",
    "matchedStatuses": ["failed"],
    "messageRegex": ".*AssertionError.*"
  },
  {
    "name": "脚本问题(元素找不到)",
    "matchedStatuses": ["broken"],
    "messageRegex": ".*NoSuchElementException.*"
  },
  {
    "name": "环境问题",
    "matchedStatuses": ["broken", "failed"],
    "messageRegex": ".*(ConnectionError|Timeout|ConnectionRefused).*"
  },
  {
    "name": "接口异常",
    "matchedStatuses": ["failed"],
    "messageRegex": ".*状态码.*5\\d\\d.*"
  },
  {
    "name": "忽略的用例",
    "matchedStatuses": ["skipped"]
  }
]

💡 测试价值:Categories 页能一眼看出”这次失败里,3 个是真 Bug、2 个是脚本问题、1 个是环境不稳定”——写测试报告时直接引用。

7.3 executor.json(CI 执行信息)

{
  "name": "Jenkins",
  "type": "jenkins",
  "url": "https://jenkins.example.com/job/api-test/123/",
  "buildName": "api-test #123",
  "buildUrl": "https://jenkins.example.com/job/api-test/123/",
  "reportUrl": "https://jenkins.example.com/job/api-test/123/allure/",
  "buildOrder": 123
}
# conftest.py 自动生成
import json
import os

def pytest_configure(config):
    results_dir = config.getoption("--alluredir")
    if not results_dir:
        return
    executor = {
        "name": "Jenkins",
        "type": "jenkins",
        "buildName": os.getenv("JOB_NAME", "local"),
        "buildUrl": os.getenv("BUILD_URL", ""),
        "buildOrder": os.getenv("BUILD_NUMBER", ""),
    }
    with open(os.path.join(results_dir, "executor.json"), "w", encoding="utf-8") as f:
        json.dump(executor, f, ensure_ascii=False, indent=2)

7.4 历史趋势(Trend)

Allure 的 Trend 折线图需要把上一次报告的 history 复制过来:

# 执行流程
# 1. 把上次的 history 复制到本次结果目录
cp -r ./allure-report/history ./allure-results/history

# 2. 跑测试
pytest --alluredir=./allure-results --clean-alluredir
# 注意:--clean-alluredir 会清空,所以上面的复制要在之后做,
#       或者用 --clean-alluredir 之后再从备份目录复制

# 3. 生成报告(会同时产生新的 history)
allure generate ./allure-results -o ./allure-report --clean
# 标准做法:results 每次清空 → 从 report 目录回填 history
# conftest.py
def pytest_configure(config):
    import shutil, os
    results_dir = config.getoption("--alluredir")
    history_src = "./allure-report/history"       # 上次报告里的
    history_dst = os.path.join(results_dir or "", "history")
    if os.path.exists(history_src) and results_dir:
        os.makedirs(results_dir, exist_ok=True)
        if os.path.exists(history_dst):
            shutil.rmtree(history_dst)
        shutil.copytree(history_src, history_dst)

⚠️ 顺序很重要:先 clean results → 再回填 history → 再跑测试 → 再 generate。

7.5 重试趋势(Retries)

配合 pytest-rerunfailures 自动重跑,报告里会显示重试记录:

pip install pytest-rerunfailures

pytest --reruns=2 --reruns-delay=1 --alluredir=./allure-results

报告里能看到”该用例重试了 N 次最终通过”——能识别出不稳定用例(flaky test),这是很有价值的质量信号。

八、与 pytest 的集成配置

8.1 命令行参数

pytest \
  --alluredir=./allure-results \        # 结果输出目录
  --clean-alluredir \                    # 清空旧结果
  --allure-severities=blocker,critical \ # 只跑指定等级
  --allure-tags=冒烟 \                   # 只跑指定标签
  --allure-features=订单模块 \            # 只跑指定 feature
  --allure-stories=用户下单 \             # 只跑指定 story
  --allure-link-pattern=issue:https://jira.example.com/browse/{}   # 链接模板

--allure-link-pattern 很好用:配了之后,@allure.issue("BUG-1234") 会自动拼成完整 URL。

8.2 pytest.ini 配置

[pytest]
addopts = --alluredir=./allure-results --clean-alluredir
testpaths = tests
python_files = test_*.py
python_classes = Test*
python_functions = test_*
markers =
    smoke: 冒烟测试
    regression: 回归测试

8.3 与 fixture 结合(典型结构)

# conftest.py
import allure
import pytest


@pytest.fixture(scope="session")
def env_config():
    return {"base_url": "https://test.example.com", "env": "TEST"}


@pytest.fixture
def login_user(env_config):
    """登录并返回 token"""
    with allure.step("前置:用户登录"):
        token = "mock_token"
        allure.attach(token, name="登录token",
                      attachment_type=allure.attachment_type.TEXT)
    yield token
    with allure.step("后置:退出登录"):
        ...

8.4 并发执行

pip install pytest-xdist

pytest -n 4 --alluredir=./allure-results --clean-alluredir

并发时 Timeline 页特别有用,能看清各进程的执行时序和耗时分布。

九、CI/CD 集成

9.1 Jenkins

# 1. 安装 Allure Jenkins Plugin
#    系统管理 → 插件管理 → 搜索 "Allure"

# 2. 全局工具配置里添加 Allure Commandline
#    系统管理 → 全局工具配置 → Allure Commandline → 新增

# 3. 流水线中使用
pipeline {
    agent any
    stages {
        stage('Test') {
            steps {
                sh 'pytest --alluredir=./allure-results --clean-alluredir'
            }
            post {
                always {
                    // 生成报告并归档
                    allure includeProperties: false,
                           results: [[path: 'allure-results']]
                }
            }
        }
    }
}

详细 Jenkins 配置见 Jenkins。

9.2 GitLab CI

stages:
  - test

api-test:
  stage: test
  image: python:3.11
  script:
    - pip install -r requirements.txt
    - pytest --alluredir=./allure-results --clean-alluredir
  artifacts:
    when: always
    paths:
      - allure-results/
      - allure-report/
    expire_in: 30 days
  after_script:
    - allure generate ./allure-results -o ./allure-report --clean

9.3 GitHub Actions

name: API Test
on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.11'

      - name: Install dependencies
        run: pip install -r requirements.txt

      - name: Run tests
        run: pytest --alluredir=./allure-results --clean-alluredir
        continue-on-error: true

      - name: Get Allure history
        uses: actions/checkout@v4
        if: always()
        continue-on-error: true
        with:
          ref: gh-pages
          path: gh-pages

      - name: Build Allure report
        uses: simple-elf/allure-report-action@master
        if: always()
        with:
          allure_results: allure-results
          gh_pages: gh-pages
          allure_report: allure-report

      - name: Upload report
        uses: actions/upload-artifact@v4
        if: always()
        with:
          name: allure-report
          path: allure-report

9.4 报告归档与通知

实践建议:
  1. 每次构建归档 allure-results(体积小)
  2. 保留 allure-report/history 目录,实现跨构建趋势
  3. 构建结束后推送通知到企业微信/钉钉:
     消息里带上 通过率、失败数、报告链接
# notify.py —— 解析报告摘要并推送
import json
import glob
import os
import requests


def summary(results_dir="./allure-results") -> dict:
    """统计本次执行结果"""
    stat = {"passed": 0, "failed": 0, "broken": 0, "skipped": 0}
    for f in glob.glob(os.path.join(results_dir, "*-result.json")):
        with open(f, encoding="utf-8") as fp:
            stat[json.load(fp)["status"]] = stat.get(
                json.load(fp)["status"], 0) + 1
    total = sum(stat.values())
    stat["total"] = total
    stat["pass_rate"] = f"{stat['passed'] / total * 100:.1f}%" if total else "0%"
    return stat


def send_to_wechat(webhook: str, content: str):
    requests.post(webhook, json={
        "msgtype": "markdown",
        "markdown": {"content": content}
    })


if __name__ == "__main__":
    s = summary()
    msg = (
        f"### 自动化测试报告\n"
        f"> 用例总数:**{s['total']}**\n"
        f"> 通过:{s['passed']} 失败:{s['failed']} "
        f"损坏:{s['broken']} 跳过:{s['skipped']}\n"
        f"> 通过率:**{s['pass_rate']}**\n"
        f"> [查看完整报告](https://jenkins.example.com/job/api-test/allure/)"
    )
    send_to_wechat(os.getenv("WECHAT_WEBHOOK"), msg)

十、其他框架接入(简述)

框架 依赖 关键用法
pytest allure-pytest 装饰器 + allure.attach
TestNG allure-testng @Epic/@Feature/@Step、@Attachment
JUnit5 allure-junit5 @Epic/@Step、Allure.addAttachment()
Cucumber JVM allure-cucumber-jvm 自动按 Feature/Scenario 分层
Pytest-BDD allure-pytest 与 BDD 无缝,Scenario 即 Story
Playwright allure-playwright 自带截图/视频/网络日志附件
Robot Framework allure-robotframework --listener allure_robotframework
// TestNG 示例(对照理解)
@Epic("电商系统")
@Feature("订单模块")
public class OrderTest {

    @Test(description = "创建订单")
    @Story("用户下单")
    @Severity(SeverityLevel.CRITICAL)
    @Description("验证正常下单流程")
    public void testCreateOrder() {
        step("打开下单页");
        step("提交订单");
    }

    @Step("{action}")
    public void step(String action) { }

    @Attachment(value = "失败截图", type = "image/png")
    public byte[] screenshot() {
        return ((TakesScreenshot) driver).getScreenshotAs(OutputType.BYTES);
    }
}

十一、最佳实践与常见问题

11.1 最佳实践清单

  • 每条用例都加 @allure.title(尤其参数化的,用动态标题)
  • 业务分层打全:epic / feature / story
  • 关键操作用 allure.step 包裹,失败能定位到步
  • UI 用例配置失败自动截图
  • 接口用例附上请求与响应(排查效率翻倍)
  • 标注 severity,便于按等级筛选执行
  • 关联 issue / testcase,与缺陷平台打通
  • 生成 environment.properties,报告自带环境信息
  • 自定义 categories.json,区分真 Bug 与脚本问题
  • CI 里保留 history,形成趋势图
  • 每次执行加 --clean-alluredir,避免脏数据

11.2 常见问题速查

问题 原因 解决
allure 命令找不到 未装命令行工具 / PATH 未配 重装并配 PATH
提示 Java 错误 未装 JDK 或版本低 装 JDK 8+
报告里没有步骤 没用 allure.step 用 with allure.step() 包裹
报告是空的 --alluredir 路径错,或没跑 pytest 检查目录下有没有 *-result.json
截图没显示 fixture 作用域/hook 没配对 检查 pytest_runtest_makereport 钩子
参数化标题是一串参数 没写动态 title @allure.title("...{param}")
趋势图不显示 没回填 history 复制上次 report/history 到 results
旧报告数据混进来了 没加 --clean-alluredir 加上该参数
直接双击 HTML 打不开 跨域限制 用 allure open 或起 HTTP 服务
报告生成很慢 用例太多、附件太大 压缩图片、清理无用附件
中文乱码 文件编码问题 properties/json 显式用 UTF-8 写
并发时报告重复 xdist 与旧数据冲突 确保 --clean-alluredir + 结果目录独立

11.3 一条推荐的执行脚本

#!/bin/bash
# run_tests.sh —— 标准的测试执行 + 报告生成流程

set -e

RESULTS=./allure-results
REPORT=./allure-report
HISTORY_BACKUP=./.history_backup

# 1. 备份上次的 history
[ -d "$REPORT/history" ] && cp -r "$REPORT/history" "$HISTORY_BACKUP"

# 2. 跑测试(清空旧结果)
pytest --alluredir=$RESULTS --clean-alluredir -v

# 3. 回填 history 以实现趋势
[ -d "$HISTORY_BACKUP" ] && cp -r "$HISTORY_BACKUP" "$RESULTS/history"

# 4. 生成报告
allure generate $RESULTS -o $REPORT --clean

# 5. 输出摘要
echo "报告已生成:$REPORT/index.html"
allure open $REPORT &

十二、学习资源

资源 说明
Allure 官方文档 权威,含各语言接入指南
allure-pytest 文档 Python 用法完整参考
allure-python GitHub 源码与 issue
Allure2 发布页 命令行工具下载
Jenkins Allure 插件 CI 集成
pytest 文档 本站 pytest 篇
Selenium 配合截图实战