HAR 自动化测试脚本(动态参数+依赖关联) 📅 发布时间:2026/8/19 10:12:36 👁 浏览次数: HAR 自动化测试脚本动态参数依赖关联这份脚本核心实现了上下文变量池 占位符替换 JSONPath 动态提取的完整链路完美解决登录 Token、订单 ID、业务流水号等上下游参数传递问题可直接用于复杂业务流程的接口重放测试。一、依赖安装pipinstallrequests jsonpath-ngrequests发送 HTTP 请求jsonpath-ng从 JSON 响应中精准提取动态参数比正则更稳定二、完整脚本实现importjsonimportreimportrequestsfromjsonpath_ngimportparsefromtypingimportList,Dict,AnyclassHarDependentReplayer: 支持动态参数提取 上下游依赖关联的 HAR 重放测试器 核心机制上下文变量池 {{变量名}} 占位符替换 JSONPath 提取 def__init__(self,har_path:str,base_url:strNone,initial_context:Dict[str,Any]None,extract_rules:List[Dict[str,str]]None,assert_rules:List[Dict[str,Any]]None,filter_config:Dict[str,Any]None): :param har_path: HAR 文件路径 :param base_url: 替换目标域名为空则使用录制时的域名 :param initial_context: 初始上下文变量池预置账号、固定参数等 :param extract_rules: 参数提取规则格式 [{var: 变量名, from: body/headers, expr: JSONPath/正则}] :param assert_rules: 全局断言规则格式 [{type: status_code, value: 200}, {type: body_field, expr: $.code, value: 0}] :param filter_config: 请求过滤配置 self.har_pathhar_path self.base_urlbase_url self.contextinitial_contextor{}self.extract_rulesextract_rulesor[]self.assert_rulesassert_rulesor[]self.filter_configfilter_configorself._default_filter_config()self.entries[]self.result{success:0,fail:0,error:0,details:[]}# 重放时自动排除的请求头避免与 requests 自动生成的头冲突self.exclude_headers{host,content-length,connection,accept-encoding,user-agent}staticmethoddef_default_filter_config()-Dict[str,Any]:默认过滤规则排除静态资源、预检请求、第三方域名return{exclude_suffix:[.js,.css,.png,.jpg,.jpeg,.ico,.svg,.woff,.woff2,.map],exclude_methods:[OPTIONS],include_domains:[]# 空列表表示不过滤域名}def_replace_placeholders(self,content:str)-str:替换字符串中的 {{变量名}} 占位符从上下文池取值defreplacer(match):var_namematch.group(1)returnstr(self.context.get(var_name,match.group(0)))returnre.sub(r\{\{(\w)\}\},replacer,str(content))def_extract_value(self,response:requests.Response,rule:Dict[str,str])-Any:根据提取规则从响应中提取值sourcerule[from]exprrule[expr]try:ifsourcebody:json_dataresponse.json()jsonpath_exprparse(expr)matchesjsonpath_expr.find(json_data)returnmatches[0].valueifmatcheselseNoneelifsourceheaders:returnresponse.headers.get(expr)elifsourcecookie:returnresponse.cookies.get(expr)exceptExceptionase:print(f ⚠️ 参数提取失败{rule[var]}:{str(e)})returnNonedef_assert_response(self,response:requests.Response,entry:Dict)-List[str]:执行断言返回失败原因列表failures[]# 1. 状态码断言默认使用录制时的状态码也可全局覆盖expect_statusentry[response][status]ifresponse.status_code!expect_status:failures.append(f状态码不符预期{expect_status}实际{response.status_code})# 2. 自定义断言规则forruleinself.assert_rules:ifrule[type]body_field:try:json_dataresponse.json()jsonpath_exprparse(rule[expr])matchesjsonpath_expr.find(json_data)actualmatches[0].valueifmatcheselseNoneifactual!rule[value]:failures.append(f字段{rule[expr]}不符预期{rule[value]}实际{actual})exceptExceptionase:failures.append(f断言执行失败{str(e)})returnfailuresdef_is_valid_request(self,entry:Dict)-bool:过滤无效请求urlentry[request][url]methodentry[request][method]# 方法过滤ifmethod.upper()inself.filter_config[exclude_methods]:returnFalse# 后缀过滤forsuffixinself.filter_config[exclude_suffix]:ifurl.lower().endswith(suffix):returnFalse# 域名过滤ifself.filter_config[include_domains]:ifnotany(domaininurlfordomaininself.filter_config[include_domains]):returnFalsereturnTruedefload_and_filter(self):加载 HAR 文件并过滤有效请求withopen(self.har_path,r,encodingutf-8)asf:har_datajson.load(f)all_entrieshar_data[log][entries]self.entries[eforeinall_entriesifself._is_valid_request(e)]print(f✅ 加载完成原始请求{len(all_entries)}条过滤后有效请求{len(self.entries)}条)def_build_request(self,entry:Dict)-Dict[str,Any]:从 HAR 条目构建请求参数并替换所有占位符reqentry[request]methodreq[method].upper()urlreq[url]# 替换域名ifself.base_url:# 提取原 URL 的路径部分fromurllib.parseimporturlparse parsedurlparse(url)urlself.base_url.rstrip(/)parsed.pathifparsed.query:url?parsed.query# 替换 URL 中的占位符urlself._replace_placeholders(url)# 处理请求头headers{}forhinreq[headers]:nameh[name].lower()ifnamenotinself.exclude_headers:headers[h[name]]self._replace_placeholders(h[value])# 处理请求体bodyNoneifpostDatainreq:post_datareq[postData]body_textpost_data.get(text,)bodyself._replace_placeholders(body_text)# 确保 Content-Type 正确ifmimeTypeinpost_dataandContent-Typenotinheaders:headers[Content-Type]post_data[mimeType]return{method:method,url:url,headers:headers,data:body,timeout:30}defrun(self):执行完整重放流程self.load_and_filter()print(\n 开始执行重放测试...\n)foridx,entryinenumerate(self.entries,1):req_infoself._build_request(entry)url_shortreq_info[url].split(?)[0]print(f[{idx}/{len(self.entries)}]{req_info[method]}{url_short})try:# 发送请求resprequests.request(**req_info,verifyFalse)# 执行断言failuresself._assert_response(resp,entry)ifnotfailures:print(f ✅ 通过)self.result[success]1else:print(f ❌ 失败:{; .join(failures)})self.result[fail]1# 提取动态参数并存入上下文池forruleinself.extract_rules:valueself._extract_value(resp,rule)ifvalueisnotNone:self.context[rule[var]]valueprint(f 提取变量:{rule[var]}{str(value)[:50]}...)# 记录详情self.result[details].append({index:idx,url:req_info[url],status:PASSifnotfailureselseFAIL,failures:failures})exceptExceptionase:print(f 请求异常:{str(e)})self.result[error]1self.result[details].append({index:idx,url:req_info[url],status:ERROR,error:str(e)})# 输出汇总结果print(\n*60)print(f 执行汇总成功{self.result[success]}| 失败{self.result[fail]}| 异常{self.result[error]})print(*60)returnself.result# 配置与运行示例 if__name____main__:# 1. 配置提取规则从响应中提取变量供后续请求使用extract_rules[# 从登录接口响应体提取 token{var:access_token,from:body,expr:$.data.accessToken},# 从创建订单响应提取订单 ID{var:order_id,from:body,expr:$.data.orderId},# 从响应头提取 requestId{var:request_id,from:headers,expr:X-Request-Id}]# 2. 配置全局断言规则assert_rules[{type:body_field,expr:$.code,value:0},# 业务返回码必须为 0{type:body_field,expr:$.msg,value:success}]# 3. 初始上下文预置固定参数initial_context{username:test_user_001,password:test_password_123,tenant_id:1001}# 4. 过滤配置filter_config{exclude_suffix:[.js,.css,.png,.jpg,.ico,.svg,.woff],exclude_methods:[OPTIONS],include_domains:[api.your-business.com]# 只保留业务域名请求}# 5. 实例化并运行replayerHarDependentReplayer(har_pathbusiness_flow.har,base_urlshturl.cc/14Vq51,# 可替换为测试环境域名initial_contextinitial_context,extract_rulesextract_rules,assert_rulesassert_rules,filter_configfilter_config)resultreplayer.run()# 6. 可将结果写入报告文件withopen(har_test_report.json,w,encodingutf-8)asf:json.dump(result,f,ensure_asciiFalse,indent2)三、核心能力说明1. 上下游依赖关联机制采用占位符 上下文变量池的通用方案和 Postman 的变量机制完全一致学习成本极低在 HAR 文件对应的请求中将动态参数位置修改为{{变量名}}URL 路径/api/order/{{order_id}}/detail请求头Authorization: Bearer {{access_token}}请求体{userId: {{user_id}}, amount: 100}脚本执行时自动从上下文池中取值替换上游提取的参数自动供下游使用2. 动态参数提取能力支持三种提取来源覆盖绝大多数业务场景响应体 JSON用 JSONPath 表达式精准提取嵌套字段推荐响应头直接按 Header 名称提取Cookie按 Cookie 键名提取3. 多层断言体系基础层自动使用录制时的 HTTP 状态码做基准断言业务层自定义 JSONPath 字段断言校验业务返回码、核心数据可扩展新增响应时间断言、Schema 校验等4. 灵活的过滤与适配自动过滤静态资源、预检请求减少无效执行支持一键切换测试环境修改base_url即可自动排除冲突请求头避免重放时的协议级错误四、使用步骤录制 HAR在浏览器或 Playwright 中完成完整业务流程如登录→创建订单→查询订单导出带 content 的 HAR 文件标记占位符打开 HAR 文件将需要动态传递的参数值替换为{{变量名}}配置规则在脚本中配置对应的提取规则、断言规则、环境信息执行脚本运行脚本查看控制台输出与生成的 JSON 报告五、进阶扩展建议接入 Pytest将每个 HAR 场景封装为一个测试用例接入单元测试框架支持批量执行与测试报告参数脱敏增加敏感字段加密/替换逻辑避免账号密码、Token 明文存储智能关联识别增加自动识别上下游相同参数的逻辑减少人工标记占位符的工作量响应 Diff对比录制响应与重放响应的字段差异输出详细的变更报告幂等处理对写操作接口增加前置数据清理逻辑避免重复执行报错