测试报告这件事我做自动化这些年踩过的坑比写过的用例还多。前几年接手一个老项目前任留下的测试报告是一个四百多行的 txtPASS和FAIL交替刷屏失败原因清一色写着AssertionError既没有截图也没有请求日志。为了定位三个失败用例我花了一整个下午手动复跑最后发现两个是环境数据被别的同学改掉了。从那次开始我就下决心一定要找一套能把测试结果讲成人话的方案兜兜转转最后落在 Allure 上。Allure 是一个开源的测试报告框架它本身不跑用例只负责接收各种测试框架吐出来的结果数据再渲染成一个可以按模块、按严重级别、按执行步骤层层钻取的静态站点。pytest、JUnit、TestNG、JMeter 这些都能往里喂数据Python 圈子里的allure-pytest、Java 圈子里的allure-junit5都是官方维护的适配器。这篇内容我打算把 Allure 从安装、接入、定制到排错的完整链路讲一遍重点放在怎么让报告真的帮上忙这件事上不管你是刚接触自动化测试的新人还是已经跑了几年回归的老手应该都能捞到几条能直接用的东西。1. 一份能看懂的测试报告为什么最后落在 Allure 上1.1 从 txt 日志到可视化报告中间隔着什么问题很多团队的第一份自动化报告就是控制台输出重定向到文件。跑二十条用例还行跑两千条就完全没法看了。真正让人头疼的不是看不到结果而是看到了也没法快速判断。举个很具体的场景回归跑完500 条用例挂了 37 条你需要的答案其实是四个——挂在哪个业务模块、是不是同一个根因、是产品缺陷还是脚本问题、和昨天比是变好了还是变差了。txt 日志这四个问题一个都答不上来因为你得靠人肉 grep 加脑补。Allure 的价值就在这儿。它在测试数据和最终展示之间加了一层结构化的加工每个用例运行时框架会把它的名称、所属模块、严重级别、执行步骤、耗时、附件、失败堆栈都写成一份 JSON 结果文件Allure 命令行工具再把这些零散文件聚合成一个纯静态的 HTML 站点。所以你在页面上能做的不只是看结果还能按 feature 分组看覆盖情况、按 severity 筛出关键用例、点开某条失败用例看它挂在哪一步、那一步的截图长什么样。这套东西对排查效率的提升是数量级的我自己的体感是失败定位时间从平均十几分钟压到了两三分钟。1.2 Allure 解决的是信息组织不是用例执行这里有个特别容易混淆的点值得单独说清楚Allure 跟 pytest、JUnit 不是一个层面的东西它不能替代它们中的任何一个。pytest 负责发现用例、组织 fixture、执行断言Allure 只做两件事——提供一套在代码里打标签、埋步骤、挂附件的 API以及一个把结果文件渲染成网页的命令行工具。理解了这个分层后面很多问题就顺了。比如你常见的困惑为什么我的 Allure 报告是空的本质原因基本都是 pytest 那条命令忘了加--alluredir压根没生成结果文件那么allure serve打开的自然是一片空白。再比如为什么报告里没有截图那是因为你没在失败钩子里调用allure.attach跟 Allure 本身没关系。把职责边界认清楚排查问题的时候就不会往错误的方向使劲。1.3 什么阶段、什么团队值得引入我的建议是分情况。如果你手上的自动化用例还不到 50 条、跑一次两分钟出结果、失败了自己看一眼控制台就知道问题在哪那引入 Allure 属于给自己找事收益为零。但只要有下面任意一条成立就值得上用例数超过 100 条或者跑一轮超过 10 分钟或者报告要给产品、测试经理这些不写代码的人看或者你开始做每日定时回归需要看趋势。这四种情况下人肉读日志的成本会快速膨胀Allure 的投入产出比就非常高了。引入成本其实比想象中低。Python 侧一条pip install allure-pytest加上命令行工具半小时能跑通第一个 demo。真正花时间的是怎么设计标签体系和怎么定附件规范这是团队约定问题而不是技术问题后面第 4 节我会专门展开讲我的做法。2. 拆开 Allure 的箱子三层结构与核心概念2.1 结果目录、渲染命令、静态站点Allure 的工作流可以粗暴地拆成三层理解这三层能解决 80% 的报告不对问题。第一层是结果目录通常叫allure-results。测试框架执行时每跑完一条用例就往这个目录里写若干文件包括xxxx-result.json用例主体信息、xxxx-container.jsonfixture 或 setup/teardown 的容器信息、xxxx-attachment.png之类的附件文件。这个目录是原始数据长得很难看但它是 Allure 的输入。第二层是渲染命令。allure generate读取结果目录输出一个完整的静态站点到指定目录比如allure-report。这个站点是纯 HTML JS CSS没有任何后端依赖直接扔到静态服务器上或者打包发给别人都能打开。开发阶段更常用的是allure serve它会启动一个本地临时服务并自动开浏览器改动结果文件后重新执行就能看到更新胜在方便。第三层是自定义数据文件。除了框架自动生成的 JSONAllure 还认几个约定名字的文件environment.properties或environment.xml写环境信息categories.json定义失败分类规则executor.json描述这次执行是谁触发的history目录承载历史趋势数据。这几个文件是让报告从能看变好用的关键很多人不知道它们存在报告自然就干巴巴的。命令上其实就两条# 开发调试起临时服务改完直接刷新 allure serve ./allure-results # CI 或归档生成静态站点 allure generate ./allure-results -o ./allure-report --clean--clean这个参数我强烈建议每次都带上。它的作用是生成前先清空输出目录。不加的话如果上一次生成了 300 条用例、这次只有 280 条那残留的 20 个旧文件可能造成页面上的数字对不上而且这种问题非常隐蔽你很难第一眼看出报告里的用例数是错的。2.2 层级模型epic、feature、story 与 suite 体系Allure 有两套并行的层级模型这是新手最容易绕晕的地方。一套是行为驱动模型对应epic史诗→feature功能→story场景三级。这套更像产品视角的分层适合把用例按业务价值组织起来。比如电商系统可以这么分epic 是交易中台feature 是下单支付退款story 是余额支付下单优惠券叠加下单。另一套是套件模型对应parentSuite→suite→subSuite三级更偏工程视角通常由测试框架自动填充比如 pytest 的模块名、类名。两套模型在报告的Behaviors和Suites两个标签页里分别展示互不干扰。我的实践建议是行为模型手工维护套件模型随它去。原因很实际——套件模型是根据代码目录结构自动生成的你把test_order.py挪个目录它就变了拿来做汇报口径非常不稳。而行为模型是你自己在装饰器里写死的稳定可控适合对产品和管理层展示。所以团队约定里只要规定好 epic / feature / story 的命名规则就行比如 feature 必须和需求文档里的模块名一一对应这样报告页面天然就是一张需求覆盖地图。严重级别severity是另一条独立维度取值有五个blocker、critical、normal、minor、trivial。默认是normal。这个字段的实用价值在于筛选——回归挂了一片的时候你先把 blocker 和 critical 的两眼扫完心里就有底了。但要注意别滥用我见过有团队把所有用例都标成 critical那这个字段就彻底废了。2.3 结果文件里到底存了什么有时候报告显示异常最有效的排查手段就是直接打开结果 JSON 看一眼。知道里面长什么样这一步就不会虚。一份典型的xxx-result.json大致是这样的结构我删掉了冗余字段{ uuid: 8f3c1e2a-..., name: 余额充足时下单成功金额与商品页一致, status: failed, statusDetails: { message: assert 199.00 189.00, trace: tests/test_order.py:23: in test_create_order\n assert page.total_amount() price\n }, stage: finished, start: 1712300000000, stop: 1712300004500, labels: [ {name: epic, value: 交易中台}, {name: feature, value: 下单}, {name: story, value: 余额支付}, {name: severity, value: critical}, {name: suite, value: tests.test_order}, {name: host, value: ci-runner-07} ], steps: [ { name: 使用 SKU-002 创建订单, status: passed, start: 1712300000100, stop: 1712300003900, steps: [ {name: 查找元素 (By.CSS_SELECTOR, button.submit-order), status: passed} ] } ], attachments: [ {name: 失败截图, source: a1b2c3-screenshot.png, type: image/png} ] }几个字段值得记一下status的取值是passed/failed/broken/skipped/unknown其中failed表示断言失败业务问题broken表示脚本抛了非断言异常脚本或环境问题这个区分在做失败归因时非常有用categories.json就是靠它做自动分类的。labels数组里的epic/feature/story/severity就是前面说的那些装饰器写进去的。steps是嵌套结构所以你能在页面上一层层点开看每一步的耗时。还有个container.json容易被忽略。它记录的是 fixture 的执行信息比如setup阶段跑了多久、是不是失败了。pytest 里如果某个 fixture 挂了对应的用例会被标记成 broken原因就在容器里用例本身的 result.json 是看不到的。3. 环境搭建与接入pytest、PO、JMeter 三条路3.1 Java 与 Allure CLI 的安装细节Allure 命令行工具是用 Java 写的所以机器上必须有 JRE 或 JDK这是最容易漏掉的一步。我遇到过好几次命令找不到或者命令执行了没反应最后发现都是 Java 环境的问题。版本上给个参考Allure 2.24 及之后的版本建议配 Java 11 或 17。太老的 Java 8 在部分新版 Allure 上会直接报UnsupportedClassVersionError。反过来如果你的 CI 节点只有 Java 8那就选一个稍早的 Allure 版本别硬上最新的。安装方式按平台来# macOS装了 Homebrew 的话最省事 brew install allure # Linux下载后解压配置 PATH wget https://repo.maven.apache.org/maven2/io/qameta/allure/allure-commandline/2.27.0/allure-commandline-2.27.0.zip unzip allure-commandline-2.27.0.zip -d /opt/ export PATH$PATH:/opt/allure-2.27.0/bin # Windows用 scoop 比较干净 scoop install allure装完做一次自检能打出类似2.27.0的版本号就说明环境通了allure --version java -version提示allure serve会占用一个本地端口并阻塞终端CI 里千万不要用它会一直挂住不退出。CI 场景统一用allure generate。3.2 pytest 接入从零到第一份报告Python 侧的接入是最顺的两条命令搞定依赖pip install allure-pytest # 注意allure-pytest 只是适配器命令行工具要单独装见 3.1然后跑用例的时候加上--alluredirpytest tests/ -v --alluredir./allure-results --clean-alluredir allure serve ./allure-results--clean-alluredir会在执行前清空结果目录避免上一次的残留数据混进来。这个参数我建议写进pytest.ini或者固定脚本里靠人记迟早会忘。关于版本兼容有个坑值得提前说pytest 大版本升级经常和allure-pytest打架。pytest 8 刚出来那阵子不少团队升级后遇到插件 hook 报错或者结果文件字段缺失最后是靠升级allure-pytest到较新版本才解决。所以我的习惯是锁定版本在requirements.txt里写死pytest7.4.4和allure-pytest2.13.5这一对不跟着自动升级跑。等确认新组合没问题了再整体抬版本别让测试基础设施的变化干扰你对业务结果的判断。标签怎么写最常用的几个装饰器长这样import allure import pytest allure.epic(交易中台) allure.feature(下单) allure.story(余额支付) allure.title(余额充足时下单成功金额与商品页一致) allure.severity(allure.severity_level.CRITICAL) allure.link(https://example.internal/req/1024, name需求文档) allure.issue(BUG-2048) allure.testcase(CASE-4096) class TestOrder: pytest.mark.parametrize(sku,price, [(SKU-001, 99.00), (SKU-002, 199.00)]) def test_create_order(self, driver, logged_user, sku, price): page OrderPage(driver).open().add_sku(sku) with allure.step(f提交 {sku} 的订单): page.submit() with allure.step(校验订单金额): assert page.total_amount() priceallure.title支持参数占位配合parametrize时可以写成下单成功 - {sku}报告里每条参数化用例的名字就会带上具体参数值一眼能看出是哪组数据挂了。这个小技巧在参数化用例多的时候特别管用。3.3 PO 模式下的步骤埋点如果你的自动化用的是 Page Object 模式Allure 的步骤体系可以嵌得很自然——直接在 page 方法上挂allure.step用例层写起来就很干净报告里的步骤树也符合页面动作的粒度。# pages/base_page.py import allure from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC class BasePage: def __init__(self, driver): self.driver driver self.wait WebDriverWait(driver, 10) allure.step(等待元素出现 {locator}) def find(self, locator): return self.wait.until(EC.presence_of_element_located(locator))# pages/order_page.py import allure from selenium.webdriver.common.by import By from pages.base_page import BasePage class OrderPage(BasePage): SUBMIT_BTN (By.CSS_SELECTOR, button.submit-order) TOTAL_TEXT (By.CSS_SELECTOR, .order-total) allure.step(提交订单) def submit(self): self.find(self.SUBMIT_BTN).click() return self allure.step(读取订单总额) def total_amount(self): return self.find(self.TOTAL_TEXT).textallure.step里的花括号是参数占位语法{locator}会被替换成实际的元组内容。报告页面上就能看到类似等待元素出现 (By.CSS_SELECTOR, button.submit-order)这样的步骤名。有人嫌它太长那就在 page 里给定位器起个可读的名字或者干脆在步骤名里写业务语言。这里有个取舍需要说清楚步骤粒度不要太细。我见过有人把每一次find都变成步骤结果一个用例在报告里展开三十层看着挺唬人实际上排查时你根本找不到关键那一步。我的标准是步骤名要能回答这一步在干什么业务动作纯粹的等待和查找这类机械操作包在业务步骤里就够了没必要单独暴露。失败自动截图是另一个必做项用 pytest 的钩子实现# conftest.py import pytest import allure pytest.hookimpl(hookwrapperTrue, tryfirstTrue) def pytest_runtest_makereport(item, call): outcome yield report outcome.get_result() if report.when call and report.failed: driver item.funcargs.get(driver) if driver is not None: allure.attach( driver.get_screenshot_as_png(), name失败截图, attachment_typeallure.attachment_type.PNG, )注意item.funcargs只在用例已经拿到 fixture 之后才有值。如果失败发生在 fixture 的setup阶段这里是拿不到 driver 的会直接抛AttributeError把钩子本身搞挂。稳妥写法是先做try/except包一层取不到就跳过截图别让截图逻辑反过来影响测试执行。3.4 JMeter 5.6.3 产出 Allure 结果性能测试这块JMeter 5.6.3 自带的报告是那种标准的 HTML 仪表盘指标齐全但组织方式偏统计跟功能测试的报告割裂。想把两边合并成一份统一的报告视图就得让它也吐出 Allure 格式的结果目录。JMeter 自己不带这个能力社区方案是在lib/ext里放一个 Allure 的 Backend Listener 插件然后在测试计划里添加对应的监听器。大致流程是这样# 1. 把 Allure listener 插件 jar 放到 JMeter 的 lib/ext 下具体 jar 名以插件文档为准 cp allure-jmeter-*.jar $JMETER_HOME/lib/ext/ # 2. 非 GUI 模式跑输出结果目录 jmeter -n -t order_api.jmx -l result.jtl \ -Jallure.results.directory./allure-results # 3. 和功能测试的结果合并后统一渲染 allure generate ./allure-results -o ./allure-report --clean这里要提醒一句不同插件版本接受的属性名可能不一样有的用-Jallure.results.directory有的得在 GUI 里配监听器参数跑之前务必先拿一个只有一条请求的最小 jmx 验证一遍。如果插件那条路走不通比如你的 JMeter 版本和插件不兼容还有条更可控的退路先用 JMeter 原生的-l result.jtl拿到 CSV 结果再用一个小脚本把它转成 Allure 结果 JSON。JTL 里其实有足够的信息——请求名、开始时间、耗时、成功与否、失败信息够拼出一份能看的报告了。# jtl_to_allure.py 核心逻辑示意 import csv, json, uuid, os, hashlib RESULT_DIR ./allure-results os.makedirs(RESULT_DIR, exist_okTrue) def make_id(*parts): return hashlib.md5(|.join(parts).encode()).hexdigest() with open(result.jtl, newline, encodingutf-8) as f: for row in csv.DictReader(f): label row[label] success row[success].strip().lower() true start int(float(row[timeStamp])) stop start int(float(row[elapsed])) result { uuid: str(uuid.uuid4()), historyId: make_id(jmeter, label), name: label, status: passed if success else failed, stage: finished, start: start, stop: stop, labels: [ {name: suite, value: JMeter 压测}, {name: host, value: row.get(hostname, )}, ], } if not success: result[statusDetails] {message: row.get(failureMessage, 请求失败)} with open(os.path.join(RESULT_DIR, f{result[uuid]}-result.json), w, encodingutf-8) as out: json.dump(result, out, ensure_asciiFalse, indent2)historyId这个字段很多人不知道但它是历史趋势功能的命门。Allure 靠它来判断这一次的这条用例和上一次的哪条是同一条。如果不填或者每次随机生成那历史趋势图里的曲线会全部断掉每次执行都被当成全新用例。所以不管用插件还是自写转换都要保证同一个业务用例的historyId稳定——用接口名或用例 ID 做哈希是最常见的做法。像pytest po allure这种组合场景下pytest 侧是自动生成稳定 historyId 的你只需要管好自己的转换脚本就行。3.5 报告定制environment、categories、executor框架自动产出的报告只是骨架加上这三个文件才算有血有肉。它们都放在结果目录里跟 JSON 一起被解析。environment.properties记录本次执行的运行环境报告首页会单独列一块出来。这个看着简单但在排查昨天还过今天就挂的问题时价值极大——你能一眼看出两次执行的环境是不是同一个。BrowserChrome 121 EnvStaging BaseUrlhttps://staging.example.internal Python3.11.7 Pytest7.4.4 Allure2.27.0 DBMySQL 8.0.35categories.json定义失败分类规则报告页面会自动按你的规则把失败用例分到不同的桶里还能在趋势图上按分类看曲线。规则匹配的依据是statuspassed/failed/broken/skipped加正则匹配的错误信息。[ { name: 断言失败疑似产品缺陷, matchedStatuses: [failed], messageRegex: .* }, { name: 元素定位超时, matchedStatuses: [broken], messageRegex: .*TimeoutException.* }, { name: 服务不可用 / 环境问题, matchedStatuses: [broken], messageRegex: .*(Connection refused|502|503|Service Unavailable).* }, { name: 数据准备异常, matchedStatuses: [broken], messageRegex: .*(ForeignKeyConstraintError|Duplicate entry).* } ]这里有个技巧规则顺序是有意义的从上往下匹配命中即停。所以一定要把最具体的规则放前面像上面那条.*兜底的断言规则必须放最后否则它会把所有失败都吃掉。我第一次写的时候就把兜底规则放第一条了结果页面上一片断言失败等于没分类。executor.json描述这次执行是谁触发的CI 上跑的时候尤其有用报告头部会显示构建编号和链接方便从报告直接跳回流水线。{ name: Jenkins, type: jenkins, url: https://ci.example.internal, buildName: daily-regression #1287, buildOrder: 1287, reportName: 每日回归报告 }4. 让报告从能看变好用的进阶细节4.1 附件不是随手一塞截图、日志、响应的规范附件功能是 Allure 最实用的部分也是最容易被用坏的部分。它的 API 很简单# 字节流适合截图 allure.attach(driver.get_screenshot_as_png(), name失败截图, attachment_typeallure.attachment_type.PNG) # 文本类适合日志、SQL、响应体 allure.attach(response.text, name接口响应, attachment_typeallure.attachment_type.TEXT) # JSON 类型页面里会有格式化展示 allure.attach(json.dumps(resp.json(), ensure_asciiFalse, indent2), name响应体, attachment_typeallure.attachment_type.JSON) # 从磁盘文件挂载 allure.attach.file(./logs/app.log, name服务端日志, attachment_typeallure.attachment_type.TEXT)问题就出在简单上。我见过一个项目每次跑完生成的报告有 1.2 GB打开一页要等十几秒。查下来就是无差别挂附件每条用例都挂完整截图、完整响应、完整日志。看着很专业实际上没人会去翻开第 300 条用例的响应体。我的规范是三条失败必挂成功按需体积设限。失败的用例自动挂截图和堆栈这个必须有成功的用例只在接口测试里挂一份关键响应用来做前后对比UI 测试一律不挂单张截图超过 2MB 的压缩到宽度 1280 再挂。另外日志类附件要截取尾部通常最后 2000 个字符就够了前面那几千行启动日志对排查毫无帮助。还有一个细节附件文件名在报告里是可以重复的但同名附件在同一用例里会按顺序显示不会互相覆盖这点不用担心你在循环里挂十张截图是没问题的。4.2 失败归因用好 broken 和 failed 的区别前面提过failed和broken的区别这里展开讲讲为什么它值得单独设计。failed是断言没通过说明程序跑到了校验那一步结果是不符合预期——大概率是产品缺陷或者需求变更。broken是用例本身抛了异常比如元素找不到、接口连不上、fixture 报错——大概率是脚本问题或环境问题。这两类问题的处理路径完全不同前者要提 bug 给开发后者要自己修脚本或者找运维。所以我在conftest.py里做了一个小处理把常见异常都归到 broken 上而不是让它们变成 failed。默认情况下 pytest 里的异常本来就会让用例变成 broken但有个例外是assert语句抛出的AssertionError会被标记成 failed这没问题。真正需要留意的是那些被try/except吞掉又用assert False抛出来的写法——如果你在 except 里写assert False, str(e)那异常就变成断言失败了报告里会被归到 failed 桶里误导后续的归因分析。正确做法是让异常自然冒泡或者用pytest.fail/pytest.skip这类语义明确的接口。配合categories.json里的正则分类一份报告的失败分布就能一目了然断言失败 12 条、定位超时 8 条、环境异常 17 条。这时候第一件事就是去看那 17 条环境异常因为那多半是同一个根因修一个能消一片。4.3 历史趋势与 CI 集成历史趋势是 Allure 最有价值的功能之一但它不会自动生成需要你在每次生成报告前把上一次的history目录搬到本次的 results 目录里。# CI 脚本片段 # 1. 如果上一份报告存在把历史数据搬到本次结果目录 if [ -d ./allure-report/history ]; then cp -r ./allure-report/history ./allure-results/history fi # 2. 执行用例产出新结果 pytest tests/ --alluredir./allure-results # 3. 生成报告注意 --clean 会清空输出目录所以第 1 步必须在前面 allure generate ./allure-results -o ./allure-report --clean这里的顺序很关键也是我自己踩过的坑allure generate --clean会把输出目录整个清掉包括里面的history。所以你必须先把上一次的历史数据复制到 results 目录再执行 generate。如果顺序反了Trends 页面永远是空的或者只有一次数据。我当时排查这个问题花了不少时间因为报告生成本身不报错只是趋势图上孤零零一个点很容易被忽略过去。CI 集成主要是把这段脚本串进流水线跑测试、生成报告、把报告目录作为构建产物归档。归档的时候建议把结果目录allure-results也一起存因为排查为什么这条用例偶尔失败的时候原始 JSON 比渲染后的页面更好用直接 grep 一下速度很快。提示CI 上跑allure serve会导致任务永远不结束。如果非要在流水线里预览用allure open配上后台进程和超时控制或者干脆生成静态站点后由运维统一托管。还有个并行执行的问题值得一提。用pytest-xdist多进程跑用例时多个进程同时往同一个allure-results目录写文件是安全的因为每条结果的文件名都带 uuid。但environment.properties、categories.json这类公共文件如果每个进程都写就可能互相覆盖。解决办法是让主进程负责写这些公共文件worker 进程只管产出用例结果。4.4 报告体积与性能Allure 报告是一个纯静态的单页应用加载所有用例数据的方式是读一个汇总的 JS 文件。用例数上千之后打开首屏会有明显卡顿。实测下来1000 条用例、附件控制在 200MB 以内的报告在普通办公电脑上打开大概 3 到 5 秒可以接受。超过 3000 条用例或者附件总量超过 1GB体验就明显下降了点击任意一条用例都要等一两秒。应对办法有几个。一是按模块拆分报告把不同测试套件生成到不同的结果目录分别渲染成多份小报告通过一份索引页串起来。二是定期清理附件只保留最近 30 天的报告归档老报告压缩后转存。三是如果只是要发给同事看一眼可以用较新版本 Allure 提供的--single-file参数生成单 HTML 文件allure generate ./allure-results -o ./allure-report --clean --single-file这个参数会把所有资源内联到一个 HTML 里分发起来极其方便代价是文件本身会更大而且少了那些需要多文件支持的交互细节。另外要注意这个参数对版本有要求2.21 之后才支持老版本会直接报参数不识别。5. 踩过的坑常见问题速查5.1 报告打开一片空白或者图表不出来这是最高频的问题按下面顺序排查基本能覆盖第一确认结果目录里到底有没有文件。ls ./allure-results看一眼如果只有environment.properties没有任何*-result.json那就是 pytest 命令漏了--alluredir参数或者路径写错了。第二确认命令执行时用的是哪个路径。相对路径在不同 shell 工作目录下结果不一样CI 里尤其容易踩建议统一用绝对路径或者以项目根目录为基准。第三如果是图表区域显示不出来而数据是有的通常是浏览器缓存导致的强制刷新一下或者换个无痕窗口试试。第四确认 Allure CLI 版本和生成报告时的版本一致用高版本生成、低版本查看有时候会出现 JS 报错。注意企业内网环境有些浏览器会拦截报告里的内联脚本表现就是页面结构出来了但图表空白。这种情况在自己机器上用 Chrome 打开验证一下就能区分是环境问题还是数据问题。5.2 中文乱码与时间显示异常中文乱码一般出在两个地方。一是结果 JSON 本身没按 UTF-8 写导致中文变成\u4e2d\u6587或者问号。Python 侧json.dump记得加ensure_asciiFalse并且文件打开时显式指定encodingutf-8。Windows 上尤其要注意默认编码可能是 GBK不加参数就会出问题。二是environment.properties这类 properties 文件。它在 Java 生态里的规范编码是 ISO-8859-1写中文进去很容易乱码虽然新版 Allure 对 UTF-8 支持好一些了但为了稳妥我在这个文件里只写 ASCII 字符中文环境名一律用拼音或者英文页面上的可读性问题通过别的方式解决。时间显示异常是另一类问题。Allure 存的时间戳是毫秒级的 Unix 时间报告页面会按浏览器所在时区渲染。如果你的用例是从别的地方导数据进来时间戳单位搞错比如误用了秒级那报告上的耗时和开始时间就会离谱到 1970 年或者几万小时后。转换的时候盯一下单位time.time()是秒要乘 1000。5.3 用例被覆盖、历史断线、附件找不到这三个问题我都会把它们归到结果数据不规范这一类。用例被覆盖同一个historyId在一个结果目录里出现两次后写的会覆盖前面的。这在参数化用例里容易发生——如果你手工构造结果 JSON 时用了用例名做 historyId而参数化用例的名字是相同的就会互相覆盖。解决办法是把参数拼进 historyId用用例名 str(参数)做哈希。历史断线前面讲过要么是historyId不稳定要么是history目录没正确搬运。有个判断技巧打开报告的 Trends 页面如果是完全空白是没搬 history如果只有今天一个点是搬了但 historyId 对不上。附件找不到报告页面显示附件名但点开报 404说明result.json里引用的source文件名在结果目录里不存在。常见于手工构造结果的时候写了附件记录却没把文件复制过去。用allure.attach这种官方 API 就不会有这个问题所以能不用手工拼 JSON 就别拼。5.4 问题排查速查表把上面这些整理成一张表出问题的时候直接对号入座现象大概率原因处理方式报告完全空白结果目录没有 result.json检查 pytest 是否带--alluredir用例数比实际少生成时没加--clean或 historyId 冲突覆盖加--clean检查 historyId 生成逻辑页面图表空白结构正常浏览器拦截脚本或缓存问题换浏览器、无痕模式验证中文显示为问号写文件时未指定 UTF-8JSON 加ensure_asciiFalseopen加encodingutf-8Trends 页面空白history 目录没搬运generate 前把旧 report/history 复制到 results时间显示为 1970 年时间戳单位用成了秒统一换算成毫秒附件点开 404结果目录缺附件文件用allure.attachAPI别手工拼失败用例没有截图钩子未生效或 fixture 取不到 driver加 try/except确认钩子注册在 conftest命令找不到Java 未安装或 PATH 未配置检查java -version和allure --version报告体积巨大无差别挂载完整附件失败必挂、成功按需截图压缩这套东西我用到现在最大的感受是 Allure 本身没什么玄学它的问题九成出在输入数据不规范上。所以比起研究它的高级用法不如先把标签规范、附件规范、historyId 规则这三件事定下来剩下的事情它自己会处理好。最后分享一个我最近才用顺的小技巧categories.json里的正则不要一次写完先跑一轮拿到真实的错误信息把高频的那几条字符串抠出来再写规则准确率比拍脑袋写高得多。我一开始按经验写了一大堆正则结果测下来匹配率不到一半反倒是从真实堆栈里摘出来的那几条简单正则命中率接近百分之百。
阅读完成 · 觉得有帮助?