☰
HAR一键生成禅道测试用例:摆脱版本限制的接口Bug提报新姿势
2026/10/20 7:34:18 网站建设 项目流程

1. 项目概述:HAR 一键生成 Skill 到底做了什么

做测试这行久了,最烦的不是用例写得多、回归跑得勤,而是“问题说不清”。尤其是接口层的 bug,开发一句“我这边没问题”能把你噎死。以前我都是截图、录屏、写复现步骤,一条条列请求头和请求参数,费劲不说,还经常漏。

后来想通了:与其手动描述“我点了什么按钮、发了什么请求”,不如直接抓一份 HAR 出来,把浏览器和服务器之间的完整对话原样呈现。再进一步,把这套动作做成一个 Skill——HAR 文件导入后,自动解析所有 HTTP 请求、生成对应测试用例,甚至在禅道里直接创建 bug 单。标题里那句“不受版本限制”,说的就是 HAR 记录的是当前运行版本的完整网络交互,没有历史文档也能快速复现问题,老版本、新版本、临时分支,只要页面能跑起来,HAR 就能告诉你后端到底返回了什么。

这个 Skill 适合谁用?但凡你每天要跟禅道打交道,提 bug 之前要整理半天复现步骤,或者版本迭代快、接口文档常年滞后,都可以直接照着下面的思路搭一套。我在几个项目里试下来,单个 bug 的提报时间从原来的十五分钟左右,压缩到三分钟以内,而且附带的 HAR 数据让开发排查问题的效率明显提升。这篇文章就把完整的搭建思路、实现代码和踩坑过程都摊开来讲,按步骤抄作业就行。

2. 核心思路拆解:为什么选 HAR + 禅道 API

2.1 传统提 bug 方式被卡在哪几个环节

先聊聊为什么非要换个姿势。传统的提 bug 流程大概是:复现问题 → 截图或者录屏 → 手写操作步骤 → 手抄请求参数 → 填禅道单子。这个流程有三个明显的毛病。

第一个毛病是信息失真。你写给开发看的“我发了什么请求”,跟浏览器真正发出去的请求经常对不上。比如一个登录接口,实际还带了 token、时间戳、设备指纹、埋点参数,你手抄的时候根本抄不全。开发拿着你写的半截参数去调试,怎么都复现不了,来回拉锯一天。

第二个毛病是步骤繁琐。接口类 bug 的复现步骤往往很长,又涉及多个前置条件。你写“先登录,再进订单页,点开详情,修改数量,再点结算”,中间漏掉任何一个操作,开发就复现不出来。

第三个毛病是版本依赖。很多测试团队的用例和 bug 描述都是跟着某个具体版本写的,版本一升级,接口路径、参数名一变,旧记录全部失效。你不可能为每次小版本迭代都重新维护一套完整的接口文档。

2.2 HAR 为什么能天然规避这些坑

HAR(HTTP Archive)是浏览器导出的一种标准格式文件,记录了一个页面加载或一段操作过程中所有的 HTTP 请求和响应,包括请求头、请求体、响应头、响应体、状态码、耗时、时间戳,一个不落。

这不就是测试工程师梦寐以求的“完整现场”吗?你不用再手动摘录参数,直接把整个对话记录丢给开发就行。而且因为 HAR 是直接从当前页面的实际操作中抓取的,它跟版本没有关系——不管系统升级到哪个版本,只要页面能运行,抓取的就是那个版本的真实行为。这就是标题里“不再受版本限制”的核心逻辑。

2.3 禅道 API 集成,把最后一步自动化

抓到了 HAR,其实还只是拿到了原始数据,接下来还得有人去整理、去录入。这时接入禅道 API 就顺理成章了。禅道支持通过接口创建 bug、创建用例,我们只需要把 HAR 里解析出来的请求信息,按照模板组装成禅道要求的字段,调用接口提交即可。

我选禅道 API 而不是 UI 自动化的原因很简单:API 方式稳定、速度快、不受页面元素变动影响。禅道的 UI 偶尔会改版,元素定位一旦失效,脚本就白写了,而 API 接口相对稳定得多。整体方案就是“HAR 抓取 → 脚本解析 → 模板生成 → 禅道提交”,一条链路把测试工程师从重复劳动里解放出来。

3. 环境准备与 HAR 抓包全流程

3.1 工具链选型与安装

先列一下这套 Skill 用到的工具和依赖,我以 Windows 10 环境为例,macOS/Linux 也基本通用:

工具/组件用途说明
Chrome/Edge 浏览器抓取 HAR直接使用开发者工具导出,无需额外抓包软件
Python 3.8+运行解析脚本后续所有代码基于 Python
requests 库调用禅道 API用 pip 安装
禅道系统接收用例和 bug版本 12+ 即可,需要开启 API 接口
(可选)Puppeteer自动触发操作并抓包用于无人值守的回归抓包场景

安装命令很简单:

pip install requests

如果是团队使用,我建议把整个 Skill 做成一个工厂函数包,统一管理解析逻辑和禅道配置,这样不同成员只需要传入 HAR 文件路径就能生成用例。项目后期我把它做成命令行工具,用法如下:

python har_skill.py --har ./bug.har --project 12 --type bug --title "订单金额计算错误"

3.2 浏览器抓取 HAR 的两种标准姿势

第一种是手动抓包,适合日常复现单个 bug。打开 Chrome 开发者工具,切到 Network 面板,勾选 Preserve log(保留日志),然后开始复现操作。操作完成后,右键点击请求列表,选择 Save all as HAR with content。这里要特别提醒,一定选带 with content 的选项,这样响应体才会包含在 HAR 文件里,不带 content 的版本只保留请求元数据,排障价值大打折扣。

第二种是自动化抓包,适合回归测试或持续集成场景。如果你需要定期对某个核心流程抓包并自动生成回归用例,可以用 Puppeteer 或 Selenium 驱动浏览器,在执行完指定操作后,通过 CDP(Chrome DevTools Protocol)导出 HAR。

const puppeteer = require('puppeteer'); const fs = require('fs'); (async () => { const browser = await puppeteer.launch({ headless: true }); const page = await browser.newPage(); const client = await page.target().createCDPSession(); await client.send('Network.enable'); await client.send('Page.enable'); // 等所有请求完成后获取性能日志 const result = await client.send('Performance.enable'); // 实际项目中会用 Network.getResponseBody 遍历请求并拼接 HAR await page.goto('https://your-target-page', { waitUntil: 'networkidle2' }); // ... 交互操作 ... // 通过 CDP 导出 HAR 的核心逻辑: // 这里省略中间拼接步骤,直接说明思路: // 1. 开启 Performance 记录 // 2. 执行完业务操作 // 3. 从缓冲中取出 requestWillBeSent 和 responseReceived 事件 // 4. 组装成 HAR 格式并写文件 fs.writeFileSync('auto.har', JSON.stringify(harData)); await browser.close(); })();

手动抓包适合临时定位,自动化抓包适合批量生成。我的建议是两条腿走路:日常 bug 用第一种,快速高效;版本回归和巡检用第二种,让机器替人操作。

3.3 HAR 文件结构速览

拿到 HAR 文件后,先用文本编辑器打开看一眼结构,避免后面写解析脚本时两眼一抹黑。HAR 是标准的 JSON 格式,最外层是log对象,里面包含version、creator、entries三块核心内容。entries是数组,每个元素对应一个完整的 HTTP 请求-响应周期,包含request、response、startedDateTime、time、serverIPAddress等字段。

{ "log": { "version": "1.2", "entries": [ { "startedDateTime": "2024-11-20T10:15:30.123Z", "time": 234, "request": { "method": "POST", "url": "https://api.example.com/order/calc", "headers": [], "postData": { "text": "{\"skuId\":\"1001\",\"count\":2}" } }, "response": { "status": 200, "headers": [], "content": { "text": "{\"code\":0,\"amount\":99.8}" } } } ] } }

理解了结构,解析脚本就是顺理成章的事。但要注意一点,HAR 里包含大量静态资源请求——图片、CSS、JS、字体文件等,对接口类 bug 和用例生成没有意义,解析时必须过滤掉。

4. 核心代码实现与参数详解

4.1 HAR 解析模块:从文件到结构化数据

解析模块是整个 Skill 的地基。我的设计思路是:读文件 → 遍历 entries → 过滤静态资源 → 提取关键字段 → 输出结构化列表。下面给出可以直接用的核心代码。

import json import re STATIC_EXT = {'.js', '.css', '.png', '.jpg', '.jpeg', '.gif', '.ico', '.svg', '.woff', '.woff2', '.ttf', '.eot'} def is_static_resource(url: str) -> bool: """判断是否为静态资源请求""" path = url.split('?')[0].lower() for ext in STATIC_EXT: if path.endswith(ext): return True # 常见静态资源路径特征 if re.search(r'/(static|assets|public|dist|images?|css|js)/', path): return True return False def parse_har(har_path: str, max_body_size: int = 512 * 1024): """解析 HAR 文件,返回接口请求列表,超大响应体截断""" with open(har_path, 'r', encoding='utf-8') as f: har = json.load(f) entries = har['log'].get('entries', []) api_calls = [] for idx, entry in enumerate(entries, start=1): req = entry.get('request', {}) resp = entry.get('response', {}) url = req.get('url', '') if is_static_resource(url): continue request_body = '' post_data = req.get('postData', {}) if post_data: request_body = post_data.get('text', '') response_body = '' content = resp.get('content', {}) if content: response_body = content.get('text', '') # 响应体过大时截断,避免禅道单子字段超限 if len(response_body) > max_body_size: response_body = response_body[:max_body_size] + '...[truncated]' api_calls.append({ 'index': idx, 'method': req.get('method', 'GET'), 'url': url, 'status': resp.get('status', 0), 'request_headers': req.get('headers', []), 'request_body': request_body, 'response_body': response_body, 'duration_ms': entry.get('time', 0) }) return api_calls

这段代码有四个关键点值得细说。第一,静态资源过滤我用的是扩展名 + 路径特征双保险,避免漏网。第二,响应体截断是必须的,因为禅道单子的字段有长度限制,几百 KB 的 JSON 直接塞进去会导致接口报错。第三,保留request_headers便于后续排查 cookie、token 问题。第四,给每条记录加上递增的index,方便在生成的用例中区分步骤顺序。

4.2 用例生成模块:结构化数据转测试用例

拿到结构化接口列表后,下一步是把每个接口转成禅道的用例或 bug 描述。这里有一个设计细节:我不建议把每个接口拆成独立用例,因为实际业务操作往往涉及多个接口联动,比如“提交订单”这个操作可能要依次调用校验库存、计算价格、创建订单三个接口,把它们放在同一个用例里更符合真实场景。

生成用例的模板函数如下:

def build_steps_text(api_calls): """根据接口列表组装复现步骤文本""" lines = [] for call in api_calls: lines.append(f"步骤{call['index']}:触发 {call['method']} {call['url']}") if call['request_body']: lines.append(f"请求参数:\n{call['request_body']}") lines.append(f"期望结果:响应状态码为 {call['status']}") if call['response_body']: lines.append(f"实际响应:\n{call['response_body'][:500]}") lines.append('---') return '\n'.join(lines) def build_case_data(api_calls, project_id, title): """组装禅道用例数据结构""" steps_text = build_steps_text(api_calls) return { 'projectId': project_id, 'title': title, 'steps': steps_text, 'expect': '接口响应状态码与实际执行结果一致,关键字段值符合页面展示', 'pri': 2, 'type': 'interface' }

这里要特别说明“优先级(pri)”的设计。我默认配置为 2,也就是“普通”,因为 HAR 解析只能证明“这个请求发生过、这个响应返回过”,不能自动判断它是正常还是异常。真正提 bug 时,需要测试人员看一眼响应码,如果接口 500 或者超时,才需要手动把优先级调高。千万不要让全自动脚本把所有东西都提成紧急 bug,你会被开发投诉的。

4.3 禅道 API 调用模块:登录鉴权与提交

禅道从 12 版本开始内置了 API v1 接口,使用 token 鉴权。先获取 token,再调用创建 bug / 用例接口。

import requests ZEN_URL = 'http://your-zentao-server' ZEN_USER = 'your-username' ZEN_PASS = 'your-password' def get_token(): """获取禅道 API 访问令牌""" resp = requests.post( f'{ZEN_URL}/api.php/v1/tokens', json={'account': ZEN_USER, 'password': ZEN_PASS}, timeout=10 ) resp.raise_for_status() return resp.json()['token'] def create_zentao_bug(token, data): """在禅道中创建 bug""" headers = { 'Content-Type': 'application/json', 'Authorization': f'Bearer {token}' } payload = { 'product': data['product_id'], 'module': data.get('module_id', 0), 'title': data['title'], 'steps': data['steps'], 'pri': data.get('pri', 2), 'severity': data.get('severity', 2), 'type': data.get('type', 'codeerror') } resp = requests.post( f'{ZEN_URL}/api.php/v1/bugs', json=payload, headers=headers, timeout=10 ) resp.raise_for_status() return resp.json()

注意几个容易踩的坑。第一点是 token 的时效性,禅道的 token 默认有效期是一天,脚本跨天执行时需要重新获取。第二点是产品与项目的区别,禅道里 bug 是挂在“产品”下面的,用例可能挂在“项目”或“产品”下面,接口字段名不一样,别搞混。第三点是steps字段的值需要是 HTML 格式,禅道支持换行转<br>,但如果直接把纯文本带换行符塞进去,页面显示会挤成一团,我通常会把换行符替换成<br>标签。

def htmlize_steps(text: str) -> str: import html safe = html.escape(text) return safe.replace('\n', '<br>')

4.4 一键串联的主流程

把上面三个模块串起来,就是完整的 Skill 主程序。

def har_to_zentao(har_path, project_id, type_='bug', title=None): """HAR 一键生成禅道用例/bug""" # 1. 解析 HAR api_calls = parse_har(har_path) if not api_calls: raise ValueError('HAR 文件中没有找到接口请求,请确认抓包内容') # 2. 生成内容数据 if title is None: title = f'接口异常复现 - {len(api_calls)} 个请求' content_data = { 'project_id': project_id, 'title': title, 'steps': htmlize_steps(build_steps_text(api_calls)) } # 3. 提交禅道 token = get_token() result = create_zentao_bug(token, content_data) return result

我在实际使用时,还会在入口处加一个argparse命令行解析,让测试同学用一条命令就能完成全部操作,不用打开编辑器改代码。为了方便团队使用,我也写了一个简单的交互式入口,输入 HAR 路径后自动识别文件里的产品 ID 和模块 ID,进一步降低使用门槛。

5. 禅道集成细节与数据展示优化

5.1 用例和 Bug 的字段映射策略

禅道创建 Bug 和创建用例的字段差异比较大,我在集成时单独维护了一个字段映射表。创建 Bug 时,核心字段是product、title、steps、pri、severity;创建用例时,核心字段是product、module、title、precondition、steps、expect。

我的习惯是给 HAR 解析结果自动附加一段前置条件说明,放在用例的precondition字段里——例如“需要登录系统并进入订单详情页,HAR 抓包时间为 2024-11-20 10:15”,这样即使用例本身不全,读者也能从上下文中理解场景。

5.2 附件上传:把 HAR 文件本身作为凭证

调用禅道 API 创建 bug,除了文本步骤,我还会想办法把原始 HAR 文件作为附件挂上去。为什么?因为文本步骤再全,也只是二次转述,开发有时候还是要看原始请求才能定位。HAR 文件里包含完整的请求头、cookie、响应头,很多问题一看便知。

禅道 API 支持附件上传,但不同版本接口路径有些差异。我的做法是调用禅道的文件上传接口拿到 fileID,再在创建 bug 时把这个 fileID 关联进去。如果不想折腾 API,也可以直接把 HAR 文件拷贝到禅道服务器指定目录,然后在 steps 文本中写一句“详见服务器 /data/har/xxxx.har”。

下面是一个简化版的上传附件参考:

def upload_attachment(token, object_type, object_id, file_path): """上传附件到禅道""" headers = { 'Authorization': f'Bearer {token}', 'Content-Type': 'multipart/form-data' } with open(file_path, 'rb') as f: files = {'files': (file_path.split('/')[-1], f)} data = {'objectType': object_type, 'objectID': object_id} resp = requests.post( f'{ZEN_URL}/api.php/v1/files', files=files, data=data, headers=headers, timeout=30 ) resp.raise_for_status() return resp.json()

用这个方法,每次提 bug 出来,单子里既有清晰的接口步骤,又有原始 HAR 文件,开发再也不用追着你问“请求头呢”“完整响应呢”。

5.3 请求去重与相似请求合并

抓 HAR 时经常遇到一种情况:某个接口在操作过程中被调用了多次,比如轮询接口、心跳接口,几秒钟刷一次。如果不做去重,生成的用例会有一大堆重复步骤,既冗余又难看。我在解析模块里加了一个基于 URL + 方法 + 请求体的去重逻辑,只保留同一接口第一次出现的记录,后续重复调用统一在步骤中标注“该接口在操作期间被调用 N 次,此处保留首次记录”。

def deduplicate_api_calls(api_calls): seen = set() result = [] for call in api_calls: key = (call['method'], call['url'], call['request_body'][:200]) if key in seen: continue seen.add(key) result.append(call) return result

去重后生成的用例可读性好了很多,特别是涉及实时刷新类页面的场景,从原本的几十个请求锐减到几个核心接口,步骤清爽多了。

6. 踩坑实录与排查速查表

6.1 HAR 抓取不到完整请求怎么办

这是第一个高频翻车点。很多人用 Chrome 开发者工具抓 HAR,发现页面里某些接口请求不在列表里,尤其是一些跨域请求、WebSocket 连接和 service worker 发起的请求。Chrome 的 Network 面板默认会显示这些,但导出 HAR 时 WebSocket 帧数据不会全量保留,只保留握手请求。

另一类缺失是 HTTP/2 连接下,Chrome 有时会合并或跳过某些数据,尤其当页面本身使用了缓存。排查时,建议在开发者工具里勾选 Disable cache,并在复现操作前强制刷新。对于更彻底的抓包,也可以用 Charles 或 Fiddler 做中间人代理,把手机端或桌面端的流量全部收进来,再导出 HAR。

6.2 禅道 API 返回 401 或权限不足

遇到 401 先检查两个东西。第一是 token 是否过期,重新调一次登录接口获取新 token。第二是账号权限,禅道的 API 访问要求该账号对目标产品有“提 Bug”权限,如果账号只是只读权限,创建接口会直接拒绝。有些企业版禅道还会开启 IP 白名单,测试脚本所在机器的 IP 不在白名单里,同样会 401。这个最隐蔽,我踩过一次,浪费了半天时间,最后才发现是网络策略问题。

6.3 请求体包含动态签名,生成的用例无法复现

很多系统有防篡改机制,接口请求体里带 sign、nonce、timestamp 等动态字段。HAR 解析出来的请求体是当时那次请求的完整快照,但如果你拿这个请求体去重放,签名早就过期了,服务器会返回验签失败。这种情况,生成用例时要特别注明“该请求包含动态签名,需根据当前时间重新生成”,不能盲目让开发重放。

我在build_steps_text中加了一段自动检测逻辑,如果请求体里发现sign=或signature或timestamp,自动追加提示文字“注意:本请求包含时效性参数,重放前需要同步更新”。

6.4 禅道 steps 字段渲染异常的排查

如果提交后禅道页面上的步骤展示成了一长串没有换行的文本,十有八九是steps字段没有转义换行符。注意禅道不同版本的渲染策略不一样,老版本认<br>,某些新版本是 HTML 富文本编辑器,需要完整地包裹<p>标签。我的做法是写一个自适应渲染函数,在提交前把纯文本按照\n切分成列表,再用<p>包起来,这样在大多数版本下都能正常显示。

def render_steps_html(text: str) -> str: safe = html.escape(text) lines = [ln.strip() for ln in safe.split('\n') if ln.strip()] return ''.join(f'<p>{ln}</p>' for ln in lines)

6.5 低频接口抓不到

某些 bug 只在特定操作路径下触发,比如“先打开 A 再迅速打开 B,再返回 A”这种竞态问题,手动复现时动作快,HAR 里可能漏掉几个关键请求。我的经验是,抓包前先把浏览器开发者工具里 Network 面板的 Log 级别调到 Verbose,同时确保Preserve log开启,操作时放慢节奏,分步骤停顿,这样每个请求都能被记录下来。

7. 扩展玩法:从“提 Bug 工具”到“测试资产沉淀”

7.1 自动生成回归用例集

HAR 一键生成的能力,除了提 Bug,还可以用到回归用例集的自动构建上。每次发版前,我习惯抓取几条核心业务链路的 HAR——比如登录、加购、下单、支付——然后通过 Skill 自动生成对应的接口回归用例,直接批量写入禅道。这样发版后跑回归,用例数量和质量都有兜底,接口层的问题基本不会漏。

原本手工编写这些用例,一个核心流程至少需要半天,现在从抓包到禅道里出现可用例,全程不超过十分钟。团队里其他同事也可以直接用命令行批量跑,不需要了解底层实现。

7.2 接口变更对比分析

HAR 记录的是任意版本的实时数据,所以天然适合做接口变更对比。我把两个版本抓到同一页面的 HAR 文件导入脚本,解析出接口列表后按 URL+方法做 diff,能清晰地看到哪些接口新增、删除了参数,哪些接口响应结构变了。

实现思路很简单:解析两份 HAR → 关键字段序列化 → 对比。我在人物分析时发现,这类对比对老系统交接特别有用——新接手的测试同学不用翻文档,直接对比新旧版本的 HAR 就能了解接口全貌。这也回应了标题里“不受版本限制”的另一种用法:不限制在旧方案里,随时可以通过抓包掌握最新接口状态。

7.3 和持续集成流水线结合

如果团队有一套 CI 流程,可以在自动化测试结束后自动导出 HAR,并触发 Skill 脚本,把失败用例和接口快照一起推送到禅道。我这里提供一个简化的思路:CI 脚本在测试失败时调用导出 HAR 的函数,然后交给 Python 脚本生成 bug 并上传附件。

# CI 流水线中的伪代码 pytest --tb=short || python har_skill.py --har ./failure.har --type bug --title "自动生成的失败用例"

需要注意的是,自动生成的 bug 单一定要在标题里加“[自动]”前缀,并且默认分配给指定测试负责人二次确认,避免机器人直接派单给开发造成困扰。我见过有团队直接把自动化 bug 全量推送给开发,结果几十张无效单子铺过来,开发直接把单子全团关了,效果适得其反。

7.4 知识沉淀:让 HAR 数据资产化

长期做测试的团队,会积累大量 HAR 文件。这些文件其实就是系统的“行为快照历史”。我建议在服务器上按版本号归档 HAR 文件,目录结构类似product/version/date/xxx.har。这样以后排查疑问——“这个接口从哪次版本开始变的”“那个字段以前返回过什么值”——直接翻历史 HAR 就能找到答案,不用去求开发查代码。

8. 我个人用下来的几点体会

这套 Skill 从最初只有几十行解析脚本,到现在包含抓包、解析、生成、提交、附件的完整工具链,最大的体会是:测试工程师的重复劳动,大多数时候都不是靠“更努力”解决的,而是靠“换一种记录方式”解决的。

HAR 本身就是浏览器和服务器之间最诚实的记录员,我们以前费尽力气去“转述”网络行为,其实是在给自己增加工作量。现在直接让原话记录进禅道,步骤少了,信息却更全了,开发来问的追问也少了。

最后分享一个小技巧:HAR 文件不要提交到公开的禅道单子上。里面可能携带登录态的 cookie、token、session 等信息,有一定泄露风险。我在文件上传逻辑里做了自动脱敏,把cookie、authorization、token等敏感头和敏感字段替换成***,既保留请求结构,又降低安全风险。这个细节虽然小,但团队天天用,安全底线还是得守住。后续如果要扩展,可以考虑给 Skill 加上前端小面板,让不懂命令行的测试同学也能拖拽 HAR 文件完成全部操作。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询