Merge branch 'master' of https://github.com/metersphere/metersphere
This commit is contained in:
commit
5770f91e92
412
README.md
412
README.md
|
@ -80,199 +80,225 @@ v1.1.0 是 v1.0.0 之后的功能版本。
|
|||
|
||||
## 功能列表
|
||||
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td rowspan="17">测试跟踪</td>
|
||||
<td>项目管理</td>
|
||||
<td>多项目支持,测试用例、测试计划与项目关联</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">测试用例管理</td>
|
||||
<td>在线编辑用例</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>以树状形式展示项目的模块及其用例</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>自定义用例属性</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>快速导入用例到系统</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">测试用例评审</td>
|
||||
<td>基于已有用例发起评审</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>在线更新评审结果</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>支持多人在线添加评审评论</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>灵活的评审人分配形式</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="8">测试计划跟踪</td>
|
||||
<td>基于已有用例发起测试计划</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>在线更新用例执行结果</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>灵活的用例分配方式</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>在线生成测试报告,支持自定义测试报告模板</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>与平台中的接口测试、性能测试功能结合,自动更新关联用例的结果</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>记录测试用例关联的缺陷</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>缺陷记录支持关联到 Jira/TAPD 平台</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>测试报告支持分享、导出</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="19">接口测试</td>
|
||||
<td rowspan="12">测试脚本</td>
|
||||
<td>在线编辑接口测试内容</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>支持参数化测试</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>灵活多样的断言支持</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>支持多接口的场景化测试</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>测试场景复用</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>测试场景支持引用已有环境信息</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>测试环境信息管理</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>通过浏览器插件快速录制测试脚本</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>支持前后置 BeanShell/Python 脚本</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>上传并引用自定义 Jar 包</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>多协议支持,支持 HTTP、Dubbo、SQL、TCP 类型请求</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>支持等待时间、条件判断等逻辑控制功能</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">测试执行</td>
|
||||
<td>内置定时任务支持</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>通过 Jenkins 插件触发测试执行</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>多个接口测试一键合并执行</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>一键创建性能测试</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="3">测试报告</td>
|
||||
<td>测试执行后自动生成动态实时测试报告</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>测试报告导出</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>通过邮件、IM 工具等通知执行结果</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="12">性能测试</td>
|
||||
<td rowspan="6">测试脚本</td>
|
||||
<td>完全兼容 JMeter 脚本</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>在线调整压力参数</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>分布式压力测试</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>支持参数化测试</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>通过浏览器插件快速录制测试脚本</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>多协议支持</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="2">测试执行</td>
|
||||
<td>内置定时任务支持</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>通过 Jenkins 插件触发测试执行</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">测试报告</td>
|
||||
<td>测试执行后自动生成测试报告</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>丰富的测试报告展现形式</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>测试报告导出</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>查看测试日志详情</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="9">系统管理</td>
|
||||
<td rowspan="3">用户租户管理</td>
|
||||
<td>支持多级租户体系</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>支持多种租户角色</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>LDAP 认证对接</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>测试资源管理</td>
|
||||
<td>性能测试资源池管理</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="2">消息通知配置</td>
|
||||
<td>IM 工具通知(如企业微信、钉钉)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>邮件通知配置</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="3">集成与扩展</td>
|
||||
<td>完善的 API 列表</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>支持对接 Jenkins 等持续集成工具</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>支持对接 Jira/TAPD 等缺陷管理工具</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</head>
|
||||
<body link="blue" vlink="purple" class="xl65">
|
||||
<table width="505.15" border="0" cellpadding="0" cellspacing="0" style='width:505.15pt;border-collapse:collapse;table-layout:fixed;'>
|
||||
<col width="73" class="xl65" style='mso-width-source:userset;mso-width-alt:3114;'/>
|
||||
<col width="95.55" class="xl65" style='mso-width-source:userset;mso-width-alt:4076;'/>
|
||||
<col width="336.60" class="xl65" style='mso-width-source:userset;mso-width-alt:14361;'/>
|
||||
<col width="91.55" span="253" class="xl65" style='mso-width-source:userset;mso-width-alt:3906;'/>
|
||||
<tr height="22.55" style='height:22.55pt;mso-height-source:userset;mso-height-alt:451;'>
|
||||
<td class="xl68" height="424.85" width="73" rowspan="19" style='height:424.85pt;width:73.00pt;border-right:.5pt solid #3F3F3F;border-bottom:.5pt solid #A5A5A5;' x:str>测试跟踪</td>
|
||||
<td class="xl76" width="95.55" rowspan="8" style='width:95.55pt;border-right:.5pt solid #A5A5A5;border-bottom:.5pt solid #A5A5A5;' x:str>测试用例管理</td>
|
||||
<td class="xl70" width="336.60" style='width:336.60pt;' x:str>在线编辑用例</td>
|
||||
</tr>
|
||||
<tr height="22.35" style='height:22.35pt;mso-height-source:userset;mso-height-alt:447;'>
|
||||
<td class="xl77" x:str>编辑窗口支持上传附件</td>
|
||||
</tr>
|
||||
<tr height="22.35" style='height:22.35pt;mso-height-source:userset;mso-height-alt:447;'>
|
||||
<td class="xl73" x:str>查看与编辑窗口显示评审评论</td>
|
||||
</tr>
|
||||
<tr height="22.35" style='height:22.35pt;mso-height-source:userset;mso-height-alt:447;'>
|
||||
<td class="xl73" x:str>以树状形式展示项目的模块及其用例</td>
|
||||
</tr>
|
||||
<tr height="22.35" style='height:22.35pt;mso-height-source:userset;mso-height-alt:447;'>
|
||||
<td class="xl73" x:str>支持测试用例模块树拖拽排序</td>
|
||||
</tr>
|
||||
<tr height="22.35" style='height:22.35pt;mso-height-source:userset;mso-height-alt:447;'>
|
||||
<td class="xl73" x:str>自定义用例等级/用例类型/测试方式</td>
|
||||
</tr>
|
||||
<tr height="22.35" style='height:22.35pt;mso-height-source:userset;mso-height-alt:447;'>
|
||||
<td class="xl73" x:str>支持Excel/Xmind格式快速导入用例到系统</td>
|
||||
</tr>
|
||||
<tr height="22.35" style='height:22.35pt;mso-height-source:userset;mso-height-alt:447;'>
|
||||
<td class="xl73" x:str>支持Excel格式快速导出用例到本地</td>
|
||||
</tr>
|
||||
<tr height="22.35" style='height:22.35pt;mso-height-source:userset;mso-height-alt:447;'>
|
||||
<td class="xl72" rowspan="4" style='border-right:.5pt solid #A5A5A5;border-bottom:.5pt solid #A5A5A5;' x:str>测试用例评审</td>
|
||||
<td class="xl73" x:str>基于已有用例发起评审</td>
|
||||
</tr>
|
||||
<tr height="22.35" style='height:22.35pt;mso-height-source:userset;mso-height-alt:447;'>
|
||||
<td class="xl73" x:str>支持添加多个评审人</td>
|
||||
</tr>
|
||||
<tr height="22.35" style='height:22.35pt;mso-height-source:userset;mso-height-alt:447;'>
|
||||
<td class="xl73" x:str>在线更新评审结果</td>
|
||||
</tr>
|
||||
<tr height="22.35" style='height:22.35pt;mso-height-source:userset;mso-height-alt:447;'>
|
||||
<td class="xl73" x:str>支持多人在线添加评审评论</td>
|
||||
</tr>
|
||||
<tr height="22.35" style='height:22.35pt;mso-height-source:userset;mso-height-alt:447;'>
|
||||
<td class="xl72" rowspan="7" style='border-right:.5pt solid #A5A5A5;border-bottom:.5pt solid #A5A5A5;' x:str>测试计划跟踪</td>
|
||||
<td class="xl73" x:str>基于已有用例发起测试计划</td>
|
||||
</tr>
|
||||
<tr height="22.35" style='height:22.35pt;mso-height-source:userset;mso-height-alt:447;'>
|
||||
<td class="xl73" x:str>支持在线更新用例执行结果</td>
|
||||
</tr>
|
||||
<tr height="22.35" style='height:22.35pt;mso-height-source:userset;mso-height-alt:447;'>
|
||||
<td class="xl73" x:str>在线生成测试报告,支持自定义测试报告模板</td>
|
||||
</tr>
|
||||
<tr height="22.35" style='height:22.35pt;mso-height-source:userset;mso-height-alt:447;'>
|
||||
<td class="xl73" x:str>与平台中的接口测试、性能测试功能联动,自动更新关联用例的结果</td>
|
||||
</tr>
|
||||
<tr height="22.35" style='height:22.35pt;mso-height-source:userset;mso-height-alt:447;'>
|
||||
<td class="xl73" x:str>记录测试用例关联的缺陷</td>
|
||||
</tr>
|
||||
<tr height="22.35" style='height:22.35pt;mso-height-source:userset;mso-height-alt:447;'>
|
||||
<td class="xl73" x:str>缺陷记录支持关联到 Jira/TAPD</td>
|
||||
</tr>
|
||||
<tr height="22.35" style='height:22.35pt;mso-height-source:userset;mso-height-alt:447;'>
|
||||
<td class="xl73" x:str>支持PDF格式测试报告导出</td>
|
||||
</tr>
|
||||
<tr height="22.35" style='height:22.35pt;mso-height-source:userset;mso-height-alt:447;'>
|
||||
<td class="xl75" height="603.45" rowspan="27" style='height:603.45pt;border-right:.5pt solid #3F3F3F;border-bottom:.5pt solid #A5A5A5;' x:str>接口测试</td>
|
||||
<td class="xl72" rowspan="13" style='border-right:.5pt solid #A5A5A5;border-bottom:.5pt solid #A5A5A5;' x:str>接口定义</td>
|
||||
<td class="xl73" x:str>在线编辑接口测试内容</td>
|
||||
</tr>
|
||||
<tr height="22.35" style='height:22.35pt;mso-height-source:userset;mso-height-alt:447;'>
|
||||
<td class="xl73" x:str>支持 HTTP/Dubbo/SQL/TCP 类型接口请求</td>
|
||||
</tr>
|
||||
<tr height="22.35" style='height:22.35pt;mso-height-source:userset;mso-height-alt:447;'>
|
||||
<td class="xl73" x:str>支持接口快捷调制</td>
|
||||
</tr>
|
||||
<tr height="22.35" style='height:22.35pt;mso-height-source:userset;mso-height-alt:447;'>
|
||||
<td class="xl73" x:str>支持接口列表和用例列表切换显示</td>
|
||||
</tr>
|
||||
<tr height="22.35" style='height:22.35pt;mso-height-source:userset;mso-height-alt:447;'>
|
||||
<td class="xl73" x:str>支持用例编辑窗口正则/jsonpath/Xpath等多种类型的断言规则</td>
|
||||
</tr>
|
||||
<tr height="22.35" style='height:22.35pt;mso-height-source:userset;mso-height-alt:447;'>
|
||||
<td class="xl73" x:str>支持用例编辑窗口正则/jsonpath/Xpath类型的参数提取</td>
|
||||
</tr>
|
||||
<tr height="22.35" style='height:22.35pt;mso-height-source:userset;mso-height-alt:447;'>
|
||||
<td class="xl73" x:str>支持用例编辑窗口前后置 BeanShell/Python 脚本</td>
|
||||
</tr>
|
||||
<tr height="22.35" style='height:22.35pt;mso-height-source:userset;mso-height-alt:447;'>
|
||||
<td class="xl73" x:str>测试环境信息管理</td>
|
||||
</tr>
|
||||
<tr height="22.35" style='height:22.35pt;mso-height-source:userset;mso-height-alt:447;'>
|
||||
<td class="xl73" x:str>支持单接口测试引用环境信息</td>
|
||||
</tr>
|
||||
<tr height="22.35" style='height:22.35pt;mso-height-source:userset;mso-height-alt:447;'>
|
||||
<td class="xl73" x:str>支持通过浏览器插件快速录制测试脚本</td>
|
||||
</tr>
|
||||
<tr height="22.35" style='height:22.35pt;mso-height-source:userset;mso-height-alt:447;'>
|
||||
<td class="xl73" x:str>支持Metersphere json/Postman/Swagger格式快速导入用例到系统</td>
|
||||
</tr>
|
||||
<tr height="22.35" style='height:22.35pt;mso-height-source:userset;mso-height-alt:447;'>
|
||||
<td class="xl73" x:str>支持Metersphere json格式快速导出用例到本地</td>
|
||||
</tr>
|
||||
<tr height="22.35" style='height:22.35pt;mso-height-source:userset;mso-height-alt:447;'>
|
||||
<td class="xl73" x:str>支持上传并引用自定义 Jar 包</td>
|
||||
</tr>
|
||||
<tr height="22.35" style='height:22.35pt;mso-height-source:userset;mso-height-alt:447;'>
|
||||
<td class="xl72" rowspan="12" style='border-right:.5pt solid #A5A5A5;border-bottom:.5pt solid #A5A5A5;' x:str>接口自动化</td>
|
||||
<td class="xl73" x:str>创建多接口的场景化测试</td>
|
||||
</tr>
|
||||
<tr height="22.35" style='height:22.35pt;mso-height-source:userset;mso-height-alt:447;'>
|
||||
<td class="xl73" x:str>支持自定义场景标签</td>
|
||||
</tr>
|
||||
<tr height="22.35" style='height:22.35pt;mso-height-source:userset;mso-height-alt:447;'>
|
||||
<td class="xl73" x:str>支持多层级场景嵌套结构</td>
|
||||
</tr>
|
||||
<tr height="22.35" style='height:22.35pt;mso-height-source:userset;mso-height-alt:447;'>
|
||||
<td class="xl73" x:str>支持接口列表快速导入测试场景</td>
|
||||
</tr>
|
||||
<tr height="22.35" style='height:22.35pt;mso-height-source:userset;mso-height-alt:447;'>
|
||||
<td class="xl73" x:str>支持测试场景复用</td>
|
||||
</tr>
|
||||
<tr height="22.35" style='height:22.35pt;mso-height-source:userset;mso-height-alt:447;'>
|
||||
<td class="xl73" x:str>支持添加自定义请求/自定义脚本</td>
|
||||
</tr>
|
||||
<tr height="22.35" style='height:22.35pt;mso-height-source:userset;mso-height-alt:447;'>
|
||||
<td class="xl73" x:str>支持添加等待时间/条件判断等多类型逻辑控制器</td>
|
||||
</tr>
|
||||
<tr height="22.35" style='height:22.35pt;mso-height-source:userset;mso-height-alt:447;'>
|
||||
<td class="xl73" x:str>场景调试支持引用已有环境信息</td>
|
||||
</tr>
|
||||
<tr height="22.35" style='height:22.35pt;mso-height-source:userset;mso-height-alt:447;'>
|
||||
<td class="xl73" x:str>支持定时任务</td>
|
||||
</tr>
|
||||
<tr height="22.35" style='height:22.35pt;mso-height-source:userset;mso-height-alt:447;'>
|
||||
<td class="xl73" x:str>支持通过 Jenkins 插件触发测试执行</td>
|
||||
</tr>
|
||||
<tr height="22.35" style='height:22.35pt;mso-height-source:userset;mso-height-alt:447;'>
|
||||
<td class="xl73" x:str>Jenkins 插件支持 Pipeline 方式调用</td>
|
||||
</tr>
|
||||
<tr height="22.35" style='height:22.35pt;mso-height-source:userset;mso-height-alt:447;'>
|
||||
<td class="xl73" x:str>支持一键创建性能测试</td>
|
||||
</tr>
|
||||
<tr height="22.35" style='height:22.35pt;mso-height-source:userset;mso-height-alt:447;'>
|
||||
<td class="xl72" rowspan="2" style='border-right:.5pt solid #A5A5A5;border-bottom:.5pt solid #A5A5A5;' x:str>测试报告</td>
|
||||
<td class="xl73" x:str>测试执行后自动生成测试报告</td>
|
||||
</tr>
|
||||
<tr height="22.35" style='height:22.35pt;mso-height-source:userset;mso-height-alt:447;'>
|
||||
<td class="xl73" x:str>支持PDF格式测试报告导出</td>
|
||||
</tr>
|
||||
<tr height="22.35" style='height:22.35pt;mso-height-source:userset;mso-height-alt:447;'>
|
||||
<td class="xl75" height="178.80" rowspan="8" style='height:178.80pt;border-right:.5pt solid #3F3F3F;border-bottom:.5pt solid #A5A5A5;' x:str>性能测试</td>
|
||||
<td class="xl72" rowspan="4" style='border-right:.5pt solid #A5A5A5;border-bottom:.5pt solid #A5A5A5;' x:str>性能测试脚本</td>
|
||||
<td class="xl73" x:str>支持上传JMX/CSV/JAR格式文件创建性能测试</td>
|
||||
</tr>
|
||||
<tr height="22.35" style='height:22.35pt;mso-height-source:userset;mso-height-alt:447;'>
|
||||
<td class="xl73" x:str>支持分线程组配置压力参数</td>
|
||||
</tr>
|
||||
<tr height="22.35" style='height:22.35pt;mso-height-source:userset;mso-height-alt:447;'>
|
||||
<td class="xl73" x:str>支持下载 JTL 文件</td>
|
||||
</tr>
|
||||
<tr height="22.35" style='height:22.35pt;mso-height-source:userset;mso-height-alt:447;'>
|
||||
<td class="xl73" x:str>支持通过浏览器插件快速录制测试脚本</td>
|
||||
</tr>
|
||||
<tr height="22.35" style='height:22.35pt;mso-height-source:userset;mso-height-alt:447;'>
|
||||
<td class="xl72" rowspan="2" style='border-right:.5pt solid #A5A5A5;border-bottom:.5pt solid #A5A5A5;' x:str>测试执行</td>
|
||||
<td class="xl73" x:str>内置定时任务支持</td>
|
||||
</tr>
|
||||
<tr height="22.35" style='height:22.35pt;mso-height-source:userset;mso-height-alt:447;'>
|
||||
<td class="xl73" x:str>支持通过 Jenkins 插件触发测试执行</td>
|
||||
</tr>
|
||||
<tr height="22.35" style='height:22.35pt;mso-height-source:userset;mso-height-alt:447;'>
|
||||
<td class="xl72" rowspan="2" style='border-right:.5pt solid #A5A5A5;border-bottom:.5pt solid #A5A5A5;' x:str>测试报告</td>
|
||||
<td class="xl73" x:str>测试执行后自动生成动态实时测试报告</td>
|
||||
</tr>
|
||||
<tr height="22.35" style='height:22.35pt;mso-height-source:userset;mso-height-alt:447;'>
|
||||
<td class="xl73" x:str>支持PDF格式测试报告导出</td>
|
||||
</tr>
|
||||
<tr height="22.35" style='height:22.35pt;mso-height-source:userset;mso-height-alt:447;'>
|
||||
<td class="xl75" height="223.50" rowspan="10" style='height:223.50pt;border-right:.5pt solid #3F3F3F;border-bottom:.5pt solid #A5A5A5;' x:str>系统管理</td>
|
||||
<td class="xl72" rowspan="3" style='border-right:.5pt solid #A5A5A5;border-bottom:.5pt solid #A5A5A5;' x:str>用户租户管理</td>
|
||||
<td class="xl73" x:str>支持多级租户体系</td>
|
||||
</tr>
|
||||
<tr height="22.35" style='height:22.35pt;mso-height-source:userset;mso-height-alt:447;'>
|
||||
<td class="xl73" x:str>支持多种租户角色</td>
|
||||
</tr>
|
||||
<tr height="22.35" style='height:22.35pt;mso-height-source:userset;mso-height-alt:447;'>
|
||||
<td class="xl73" x:str>支持LDAP 认证对接</td>
|
||||
</tr>
|
||||
<tr height="22.35" style='height:22.35pt;mso-height-source:userset;mso-height-alt:447;'>
|
||||
<td class="xl72" x:str>测试资源管理</td>
|
||||
<td class="xl73" x:str>性能测试资源池管理</td>
|
||||
</tr>
|
||||
<tr height="22.35" style='height:22.35pt;mso-height-source:userset;mso-height-alt:447;'>
|
||||
<td class="xl72" rowspan="2" style='border-right:.5pt solid #A5A5A5;border-bottom:.5pt solid #A5A5A5;' x:str>消息通知配置</td>
|
||||
<td class="xl73" x:str>支持企业微信/钉钉等多种IM 工具通知配置</td>
|
||||
</tr>
|
||||
<tr height="22.35" style='height:22.35pt;mso-height-source:userset;mso-height-alt:447;'>
|
||||
<td class="xl73" x:str>支持邮件通知配置</td>
|
||||
</tr>
|
||||
<tr height="22.35" style='height:22.35pt;mso-height-source:userset;mso-height-alt:447;'>
|
||||
<td class="xl72" rowspan="3" style='border-right:.5pt solid #A5A5A5;border-bottom:.5pt solid #A5A5A5;' x:str>集成与扩展</td>
|
||||
<td class="xl73" x:str>配置API 列表</td>
|
||||
</tr>
|
||||
<tr height="22.35" style='height:22.35pt;mso-height-source:userset;mso-height-alt:447;'>
|
||||
<td class="xl73" x:str>支持对接 Jenkins 等持续集成工具</td>
|
||||
</tr>
|
||||
<tr height="22.35" style='height:22.35pt;mso-height-source:userset;mso-height-alt:447;'>
|
||||
<td class="xl73" x:str>支持对接 Jira/TAPD 等缺陷管理工具</td>
|
||||
</tr>
|
||||
<tr height="22.35" style='height:22.35pt;mso-height-source:userset;mso-height-alt:447;'>
|
||||
<td class="xl72" x:str>项目管理</td>
|
||||
<td class="xl73" x:str>多项目支持,测试用例、测试计划与项目关联</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
|
||||
详细的版本规划请参考 [版本路线图](https://github.com/metersphere/metersphere/blob/master/ROADMAP.md)
|
||||
|
||||
|
|
|
@ -14,7 +14,6 @@ import io.metersphere.api.service.*;
|
|||
import io.metersphere.base.domain.ApiTest;
|
||||
import io.metersphere.base.domain.Schedule;
|
||||
import io.metersphere.commons.constants.RoleConstants;
|
||||
import io.metersphere.commons.constants.ScheduleGroup;
|
||||
import io.metersphere.commons.utils.CronUtils;
|
||||
import io.metersphere.commons.utils.PageUtils;
|
||||
import io.metersphere.commons.utils.Pager;
|
||||
|
@ -59,7 +58,7 @@ public class APITestController {
|
|||
@Resource
|
||||
private ScheduleService scheduleService;
|
||||
@Resource
|
||||
private APIReportService apiReportService;
|
||||
private HistoricalDataUpgradeService historicalDataUpgradeService;
|
||||
|
||||
@GetMapping("recent/{count}")
|
||||
public List<APITestResult> recentTest(@PathVariable int count) {
|
||||
|
@ -359,4 +358,8 @@ public class APITestController {
|
|||
schedule.setEnable(request.isEnable());
|
||||
apiAutomationService.updateSchedule(schedule);
|
||||
}
|
||||
@PostMapping(value = "/historicalDataUpgrade")
|
||||
public String historicalDataUpgrade(@RequestBody SaveHistoricalDataUpgrade request) {
|
||||
return historicalDataUpgradeService.upgrade(request);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -121,5 +121,6 @@ public class ApiAutomationController {
|
|||
public void createSchedule(@RequestBody Schedule request) {
|
||||
apiAutomationService.createSchedule(request);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
package io.metersphere.api.dto;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Setter
|
||||
@Getter
|
||||
public class SaveHistoricalDataUpgrade {
|
||||
private List<String> testIds;
|
||||
|
||||
private String projectId;
|
||||
|
||||
private String modulePath;
|
||||
|
||||
private String moduleId;
|
||||
}
|
|
@ -31,7 +31,7 @@ public class MsDubboSampler extends MsTestElement {
|
|||
private String type = "DubboSampler";
|
||||
|
||||
@JSONField(ordinal = 52)
|
||||
private String protocol;
|
||||
private String protocol = "DUBBO";
|
||||
@JsonProperty(value = "interface")
|
||||
@JSONField(ordinal = 53, name = "interface")
|
||||
private String _interface;
|
||||
|
|
|
@ -172,7 +172,7 @@ public class MsHTTPSamplerProxy extends MsTestElement {
|
|||
LogUtil.error(e);
|
||||
}
|
||||
// REST参数
|
||||
if (CollectionUtils.isNotEmpty(this.getArguments())) {
|
||||
if (CollectionUtils.isNotEmpty(this.getRest())) {
|
||||
sampler.setArguments(httpArguments(this.getRest()));
|
||||
}
|
||||
// 请求参数
|
||||
|
@ -186,7 +186,9 @@ public class MsHTTPSamplerProxy extends MsTestElement {
|
|||
if (StringUtils.isNotEmpty(this.body.getType()) && this.body.getType().equals("Form Data")) {
|
||||
sampler.setDoMultipart(true);
|
||||
}
|
||||
sampler.setArguments(httpArguments(bodyParams));
|
||||
if (CollectionUtils.isNotEmpty(bodyParams)) {
|
||||
sampler.setArguments(httpArguments(bodyParams));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -42,7 +42,11 @@ public class Body {
|
|||
return true;
|
||||
} else return false;
|
||||
}
|
||||
|
||||
public boolean isOldKV() {
|
||||
if (StringUtils.equals(type, KV)) {
|
||||
return true;
|
||||
} else return false;
|
||||
}
|
||||
public List<KeyValue> getBodyParams(HTTPSamplerProxy sampler, String requestId) {
|
||||
List<KeyValue> body = new ArrayList<>();
|
||||
if (this.isKV() || this.isBinary()) {
|
||||
|
|
|
@ -0,0 +1,335 @@
|
|||
package io.metersphere.api.service;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import io.metersphere.api.dto.SaveHistoricalDataUpgrade;
|
||||
import io.metersphere.api.dto.automation.ScenarioStatus;
|
||||
import io.metersphere.api.dto.definition.request.MsScenario;
|
||||
import io.metersphere.api.dto.definition.request.MsTestElement;
|
||||
import io.metersphere.api.dto.definition.request.assertions.MsAssertions;
|
||||
import io.metersphere.api.dto.definition.request.controller.MsIfController;
|
||||
import io.metersphere.api.dto.definition.request.extract.MsExtract;
|
||||
import io.metersphere.api.dto.definition.request.processors.post.MsJSR223PostProcessor;
|
||||
import io.metersphere.api.dto.definition.request.processors.pre.MsJSR223PreProcessor;
|
||||
import io.metersphere.api.dto.definition.request.sampler.MsDubboSampler;
|
||||
import io.metersphere.api.dto.definition.request.sampler.MsHTTPSamplerProxy;
|
||||
import io.metersphere.api.dto.definition.request.sampler.MsJDBCSampler;
|
||||
import io.metersphere.api.dto.definition.request.sampler.MsTCPSampler;
|
||||
import io.metersphere.api.dto.definition.request.timer.MsConstantTimer;
|
||||
import io.metersphere.api.dto.scenario.Body;
|
||||
import io.metersphere.api.dto.scenario.Scenario;
|
||||
import io.metersphere.api.dto.scenario.request.*;
|
||||
import io.metersphere.base.domain.*;
|
||||
import io.metersphere.base.mapper.ApiScenarioMapper;
|
||||
import io.metersphere.base.mapper.ApiTestMapper;
|
||||
import io.metersphere.base.mapper.ext.ExtApiScenarioMapper;
|
||||
import io.metersphere.commons.utils.BeanUtils;
|
||||
import io.metersphere.commons.utils.DateUtils;
|
||||
import io.metersphere.commons.utils.LogUtil;
|
||||
import io.metersphere.commons.utils.SessionUtils;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.ibatis.session.ExecutorType;
|
||||
import org.apache.ibatis.session.SqlSession;
|
||||
import org.apache.ibatis.session.SqlSessionFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.io.*;
|
||||
import java.net.URL;
|
||||
import java.util.*;
|
||||
|
||||
@Service
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public class HistoricalDataUpgradeService {
|
||||
@Resource
|
||||
private ApiTestMapper apiTestMapper;
|
||||
@Resource
|
||||
private ExtApiScenarioMapper extApiScenarioMapper;
|
||||
@Resource
|
||||
SqlSessionFactory sqlSessionFactory;
|
||||
|
||||
private int getNextNum(String projectId) {
|
||||
ApiScenario apiScenario = extApiScenarioMapper.getNextNum(projectId);
|
||||
if (apiScenario == null) {
|
||||
return 100001;
|
||||
} else {
|
||||
return Optional.of(apiScenario.getNum() + 1).orElse(100001);
|
||||
}
|
||||
}
|
||||
|
||||
private MsScenario createScenario(Scenario oldScenario) {
|
||||
MsScenario scenario = new MsScenario();
|
||||
scenario.setVariables(oldScenario.getVariables());
|
||||
scenario.setName(oldScenario.getName());
|
||||
scenario.setEnableCookieShare(oldScenario.isEnableCookieShare());
|
||||
scenario.setEnvironmentId(oldScenario.getEnvironmentId());
|
||||
scenario.setReferenced("Upgrade");
|
||||
scenario.setId(oldScenario.getId());
|
||||
scenario.setResourceId(UUID.randomUUID().toString());
|
||||
LinkedList<MsTestElement> testElements = new LinkedList<>();
|
||||
int index = 1;
|
||||
for (Request request : oldScenario.getRequests()) {
|
||||
// 条件控制器
|
||||
MsIfController ifController = null;
|
||||
if (request.getController() != null && StringUtils.isNotEmpty(request.getController().getValue())
|
||||
&& StringUtils.isNotEmpty(request.getController().getVariable())) {
|
||||
ifController = new MsIfController();
|
||||
BeanUtils.copyBean(ifController, request.getController());
|
||||
ifController.setType("IfController");
|
||||
ifController.setName("IfController");
|
||||
ifController.setIndex(index + "");
|
||||
ifController.setResourceId(UUID.randomUUID().toString());
|
||||
}
|
||||
// 等待控制器
|
||||
if (request.getTimer() != null && StringUtils.isNotEmpty(request.getTimer().getDelay())) {
|
||||
MsConstantTimer constantTimer = new MsConstantTimer();
|
||||
BeanUtils.copyBean(constantTimer, request.getTimer());
|
||||
constantTimer.setType("ConstantTimer");
|
||||
constantTimer.setIndex(index + "");
|
||||
constantTimer.setResourceId(UUID.randomUUID().toString());
|
||||
testElements.add(constantTimer);
|
||||
}
|
||||
|
||||
MsTestElement element = null;
|
||||
if (request instanceof HttpRequest) {
|
||||
element = new MsHTTPSamplerProxy();
|
||||
HttpRequest request1 = (HttpRequest) request;
|
||||
if (StringUtils.isEmpty(request1.getPath()) && StringUtils.isNotEmpty(request1.getUrl())) {
|
||||
try {
|
||||
URL urlObject = new URL(request1.getUrl());
|
||||
String envPath = StringUtils.equals(urlObject.getPath(), "/") ? "" : urlObject.getPath();
|
||||
request1.setPath(envPath);
|
||||
} catch (Exception ex) {
|
||||
LogUtil.error(ex.getMessage());
|
||||
}
|
||||
}
|
||||
if (request1.getBody() != null && request1.getBody().isOldKV()) {
|
||||
request1.getBody().setType(Body.FORM_DATA);
|
||||
}
|
||||
BeanUtils.copyBean(element, request1);
|
||||
((MsHTTPSamplerProxy) element).setProtocol(RequestType.HTTP);
|
||||
((MsHTTPSamplerProxy) element).setArguments(request1.getParameters());
|
||||
element.setType("HTTPSamplerProxy");
|
||||
}
|
||||
if (request instanceof DubboRequest) {
|
||||
String requestJson = JSON.toJSONString(request);
|
||||
element = JSON.parseObject(requestJson, MsDubboSampler.class);
|
||||
element.setType("DubboSampler");
|
||||
}
|
||||
if (request instanceof SqlRequest) {
|
||||
element = new MsJDBCSampler();
|
||||
SqlRequest request1 = (SqlRequest) request;
|
||||
BeanUtils.copyBean(element, request1);
|
||||
element.setType("JDBCSampler");
|
||||
}
|
||||
if (request instanceof TCPRequest) {
|
||||
element = new MsTCPSampler();
|
||||
TCPRequest request1 = (TCPRequest) request;
|
||||
BeanUtils.copyBean(element, request1);
|
||||
element.setType("TCPSampler");
|
||||
}
|
||||
element.setIndex(index + "");
|
||||
element.setResourceId(UUID.randomUUID().toString());
|
||||
LinkedList<MsTestElement> msTestElements = new LinkedList<>();
|
||||
// 断言规则
|
||||
if (request.getAssertions() != null && ((request.getAssertions().getDuration() != null && request.getAssertions().getDuration().getValue() > 0) ||
|
||||
CollectionUtils.isNotEmpty(request.getAssertions().getJsonPath()) || CollectionUtils.isNotEmpty(request.getAssertions().getJsr223()) ||
|
||||
CollectionUtils.isNotEmpty(request.getAssertions().getRegex()) || CollectionUtils.isNotEmpty(request.getAssertions().getXpath2()))) {
|
||||
String assertions = JSON.toJSONString(request.getAssertions());
|
||||
MsAssertions msAssertions = JSON.parseObject(assertions, MsAssertions.class);
|
||||
msAssertions.setType("Assertions");
|
||||
msAssertions.setIndex(index + "");
|
||||
msAssertions.setResourceId(UUID.randomUUID().toString());
|
||||
msTestElements.add(msAssertions);
|
||||
}
|
||||
// 提取规则
|
||||
if (request.getExtract() != null && (CollectionUtils.isNotEmpty(request.getExtract().getJson()) ||
|
||||
CollectionUtils.isNotEmpty(request.getExtract().getRegex()) || CollectionUtils.isNotEmpty(request.getExtract().getXpath()))) {
|
||||
String extractJson = JSON.toJSONString(request.getExtract());
|
||||
MsExtract extract = JSON.parseObject(extractJson, MsExtract.class);
|
||||
extract.setType("Extract");
|
||||
extract.setIndex(index + "");
|
||||
extract.setResourceId(UUID.randomUUID().toString());
|
||||
msTestElements.add(extract);
|
||||
}
|
||||
// 前置脚本
|
||||
if (request.getJsr223PreProcessor() != null && StringUtils.isNotEmpty(request.getJsr223PreProcessor().getScript())) {
|
||||
String preJson = JSON.toJSONString(request.getJsr223PreProcessor());
|
||||
MsJSR223PreProcessor preProcessor = JSON.parseObject(preJson, MsJSR223PreProcessor.class);
|
||||
preProcessor.setType("JSR223PreProcessor");
|
||||
preProcessor.setIndex(index + "");
|
||||
preProcessor.setResourceId(UUID.randomUUID().toString());
|
||||
msTestElements.add(preProcessor);
|
||||
}
|
||||
// 后置脚本
|
||||
if (request.getJsr223PostProcessor() != null && StringUtils.isNotEmpty(request.getJsr223PostProcessor().getScript())) {
|
||||
String preJson = JSON.toJSONString(request.getJsr223PostProcessor());
|
||||
MsJSR223PostProcessor preProcessor = JSON.parseObject(preJson, MsJSR223PostProcessor.class);
|
||||
preProcessor.setType("JSR223PostProcessor");
|
||||
preProcessor.setIndex(index + "");
|
||||
preProcessor.setResourceId(UUID.randomUUID().toString());
|
||||
msTestElements.add(preProcessor);
|
||||
}
|
||||
if (CollectionUtils.isNotEmpty(msTestElements)) {
|
||||
element.setHashTree(msTestElements);
|
||||
}
|
||||
if (ifController != null) {
|
||||
LinkedList<MsTestElement> elements = new LinkedList<>();
|
||||
elements.add(element);
|
||||
ifController.setHashTree(elements);
|
||||
testElements.add(ifController);
|
||||
} else {
|
||||
testElements.add(element);
|
||||
}
|
||||
index++;
|
||||
}
|
||||
scenario.setHashTree(testElements);
|
||||
return scenario;
|
||||
}
|
||||
|
||||
private ApiScenarioWithBLOBs checkNameExist(Scenario oldScenario, String projectId, ApiScenarioMapper mapper) {
|
||||
ApiScenarioExample example = new ApiScenarioExample();
|
||||
example.createCriteria().andIdEqualTo(oldScenario.getId());
|
||||
List<ApiScenarioWithBLOBs> list = mapper.selectByExampleWithBLOBs(example);
|
||||
if (list.size() > 0) {
|
||||
return list.get(0);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static final String BODY_FILE_DIR = "/opt/metersphere/data/body";
|
||||
|
||||
//文件的拷贝
|
||||
private static void copyFile(String sourcePath, String newPath) {
|
||||
File readfile = new File(sourcePath);
|
||||
File newFile = new File(newPath);
|
||||
BufferedWriter bufferedWriter = null;
|
||||
Writer writer = null;
|
||||
FileOutputStream fileOutputStream = null;
|
||||
BufferedReader bufferedReader = null;
|
||||
try {
|
||||
fileOutputStream = new FileOutputStream(newFile, true);
|
||||
writer = new OutputStreamWriter(fileOutputStream, "UTF-8");
|
||||
bufferedWriter = new BufferedWriter(writer);
|
||||
|
||||
bufferedReader = new BufferedReader(new FileReader(readfile));
|
||||
|
||||
String line = null;
|
||||
while ((line = bufferedReader.readLine()) != null) {
|
||||
bufferedWriter.write(line);
|
||||
bufferedWriter.newLine();
|
||||
bufferedWriter.flush();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
try {
|
||||
if (bufferedWriter != null) {
|
||||
bufferedWriter.close();
|
||||
}
|
||||
if (bufferedReader != null) {
|
||||
bufferedReader.close();
|
||||
}
|
||||
if (writer != null) {
|
||||
writer.close();
|
||||
}
|
||||
if (fileOutputStream != null) {
|
||||
fileOutputStream.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void copyDir(String sourcePathDir, String newPathDir) {
|
||||
File start = new File(sourcePathDir);
|
||||
File end = new File(newPathDir);
|
||||
String[] filePath = start.list();
|
||||
if (!end.exists()) {
|
||||
end.mkdir();
|
||||
}
|
||||
for (String temp : filePath) {
|
||||
//添加满足情况的条件
|
||||
if (new File(sourcePathDir + File.separator + temp).isFile()) {
|
||||
//为文件则进行拷贝
|
||||
copyFile(sourcePathDir + File.separator + temp, newPathDir + File.separator + temp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void createBodyFiles(String testId) {
|
||||
String dir = BODY_FILE_DIR + "/" + testId;
|
||||
File testDir = new File(dir);
|
||||
if (testDir.exists()) {
|
||||
testDir.mkdirs();
|
||||
}
|
||||
copyDir(dir, BODY_FILE_DIR);
|
||||
}
|
||||
|
||||
private void createApiScenarioWithBLOBs(SaveHistoricalDataUpgrade saveHistoricalDataUpgrade, Scenario oldScenario, String scenarioDefinition, ApiScenarioMapper mapper) {
|
||||
if (StringUtils.isEmpty(oldScenario.getName())) {
|
||||
oldScenario.setName("默认名称-" + DateUtils.getTimeStr(System.currentTimeMillis()));
|
||||
}
|
||||
ApiScenarioWithBLOBs scenario = checkNameExist(oldScenario, saveHistoricalDataUpgrade.getProjectId(), mapper);
|
||||
if (scenario != null) {
|
||||
scenario.setName(oldScenario.getName());
|
||||
scenario.setProjectId(saveHistoricalDataUpgrade.getProjectId());
|
||||
scenario.setTags(scenario.getTags());
|
||||
scenario.setLevel("P0");
|
||||
scenario.setModulePath(saveHistoricalDataUpgrade.getModulePath());
|
||||
scenario.setApiScenarioModuleId(saveHistoricalDataUpgrade.getModuleId());
|
||||
scenario.setPrincipal(Objects.requireNonNull(SessionUtils.getUser()).getId());
|
||||
scenario.setStepTotal(oldScenario.getRequests().size());
|
||||
scenario.setScenarioDefinition(scenarioDefinition);
|
||||
scenario.setUpdateTime(System.currentTimeMillis());
|
||||
scenario.setStatus(ScenarioStatus.Underway.name());
|
||||
scenario.setUserId(SessionUtils.getUserId());
|
||||
scenario.setNum(getNextNum(saveHistoricalDataUpgrade.getProjectId()));
|
||||
mapper.updateByPrimaryKeySelective(scenario);
|
||||
} else {
|
||||
scenario = new ApiScenarioWithBLOBs();
|
||||
scenario.setId(oldScenario.getId());
|
||||
scenario.setName(oldScenario.getName());
|
||||
scenario.setProjectId(saveHistoricalDataUpgrade.getProjectId());
|
||||
scenario.setTags(scenario.getTags());
|
||||
scenario.setLevel("P0");
|
||||
scenario.setModulePath(saveHistoricalDataUpgrade.getModulePath());
|
||||
scenario.setApiScenarioModuleId(saveHistoricalDataUpgrade.getModuleId());
|
||||
scenario.setPrincipal(Objects.requireNonNull(SessionUtils.getUser()).getId());
|
||||
scenario.setStepTotal(oldScenario.getRequests().size());
|
||||
scenario.setScenarioDefinition(scenarioDefinition);
|
||||
scenario.setCreateTime(System.currentTimeMillis());
|
||||
scenario.setUpdateTime(System.currentTimeMillis());
|
||||
scenario.setStatus(ScenarioStatus.Underway.name());
|
||||
scenario.setUserId(SessionUtils.getUserId());
|
||||
scenario.setNum(getNextNum(saveHistoricalDataUpgrade.getProjectId()));
|
||||
mapper.insert(scenario);
|
||||
}
|
||||
}
|
||||
|
||||
public String upgrade(SaveHistoricalDataUpgrade saveHistoricalDataUpgrade) {
|
||||
ApiTestExample example = new ApiTestExample();
|
||||
example.createCriteria().andIdIn(saveHistoricalDataUpgrade.getTestIds());
|
||||
List<ApiTest> blobs = apiTestMapper.selectByExampleWithBLOBs(example);
|
||||
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
|
||||
ApiScenarioMapper mapper = sqlSession.getMapper(ApiScenarioMapper.class);
|
||||
for (ApiTest test : blobs) {
|
||||
// 附件迁移
|
||||
createBodyFiles(test.getId());
|
||||
|
||||
List<Scenario> scenarios = JSON.parseArray(test.getScenarioDefinition(), Scenario.class);
|
||||
if (CollectionUtils.isNotEmpty(scenarios)) {
|
||||
// 批量处理
|
||||
for (Scenario scenario : scenarios) {
|
||||
MsScenario scenario1 = createScenario(scenario);
|
||||
String scenarioDefinition = JSON.toJSONString(scenario1);
|
||||
createApiScenarioWithBLOBs(saveHistoricalDataUpgrade, scenario, scenarioDefinition, mapper);
|
||||
}
|
||||
}
|
||||
}
|
||||
sqlSession.flushStatements();
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -96,15 +96,27 @@ public class JmeterDocumentParser implements DocumentParser {
|
|||
processCheckoutArguments(ele);
|
||||
processCheckoutResponseAssertion(ele);
|
||||
} else if (nodeNameEquals(ele, CONCURRENCY_THREAD_GROUP)) {
|
||||
processConcurrencyThreadGroup(ele);
|
||||
processThreadGroupName(ele);
|
||||
processCheckoutTimer(ele);
|
||||
processCheckoutBackendListener(ele);
|
||||
} else if (nodeNameEquals(ele, VARIABLE_THROUGHPUT_TIMER)) {
|
||||
processVariableThroughputTimer(ele);
|
||||
} else if (nodeNameEquals(ele, THREAD_GROUP)) {
|
||||
processThreadGroup(ele);
|
||||
//
|
||||
processConcurrencyThreadGroup(ele);
|
||||
Object threadType = context.getProperty("threadType");
|
||||
if (threadType instanceof List) {
|
||||
Object o = ((List<?>) threadType).get(0);
|
||||
((List<?>) threadType).remove(0);
|
||||
if ("DURATION".equals(o)) {
|
||||
processThreadGroup(ele);
|
||||
}
|
||||
if ("ITERATION".equals(o)) {
|
||||
processIterationThreadGroup(ele);
|
||||
}
|
||||
} else {
|
||||
processThreadGroup(ele);
|
||||
}
|
||||
|
||||
processThreadGroupName(ele);
|
||||
processCheckoutTimer(ele);
|
||||
processCheckoutBackendListener(ele);
|
||||
} else if (nodeNameEquals(ele, BACKEND_LISTENER)) {
|
||||
|
@ -772,22 +784,120 @@ public class JmeterDocumentParser implements DocumentParser {
|
|||
*/
|
||||
removeChildren(threadGroup);
|
||||
// elementProp
|
||||
Object targetLevels = context.getProperty("TargetLevel");
|
||||
String threads = "10";
|
||||
if (targetLevels instanceof List) {
|
||||
Object o = ((List<?>) targetLevels).get(0);
|
||||
((List<?>) targetLevels).remove(0);
|
||||
threads = o.toString();
|
||||
}
|
||||
Object rampUps = context.getProperty("RampUp");
|
||||
String rampUp = "1";
|
||||
if (rampUps instanceof List) {
|
||||
Object o = ((List<?>) rampUps).get(0);
|
||||
((List<?>) rampUps).remove(0);
|
||||
rampUp = o.toString();
|
||||
}
|
||||
Object steps = context.getProperty("Steps");
|
||||
String step = "2";
|
||||
if (steps instanceof List) {
|
||||
Object o = ((List<?>) steps).get(0);
|
||||
((List<?>) steps).remove(0);
|
||||
step = o.toString();
|
||||
}
|
||||
Object holds = context.getProperty("Hold");
|
||||
String hold = "2";
|
||||
if (holds instanceof List) {
|
||||
Object o = ((List<?>) holds).get(0);
|
||||
((List<?>) holds).remove(0);
|
||||
hold = o.toString();
|
||||
}
|
||||
Element elementProp = document.createElement("elementProp");
|
||||
elementProp.setAttribute("name", "ThreadGroup.main_controller");
|
||||
elementProp.setAttribute("elementType", "com.blazemeter.jmeter.control.VirtualUserController");
|
||||
threadGroup.appendChild(elementProp);
|
||||
|
||||
threadGroup.appendChild(createStringProp(document, "ThreadGroup.on_sample_error", "continue"));
|
||||
threadGroup.appendChild(createStringProp(document, "TargetLevel", "2"));
|
||||
threadGroup.appendChild(createStringProp(document, "RampUp", "12"));
|
||||
threadGroup.appendChild(createStringProp(document, "Steps", "2"));
|
||||
threadGroup.appendChild(createStringProp(document, "Hold", "1"));
|
||||
threadGroup.appendChild(createStringProp(document, "TargetLevel", threads));
|
||||
threadGroup.appendChild(createStringProp(document, "RampUp", rampUp));
|
||||
threadGroup.appendChild(createStringProp(document, "Steps", step));
|
||||
threadGroup.appendChild(createStringProp(document, "Hold", hold));
|
||||
threadGroup.appendChild(createStringProp(document, "LogFilename", ""));
|
||||
// bzm - Concurrency Thread Group "Thread Iterations Limit:" 设置为空
|
||||
// threadGroup.appendChild(createStringProp(document, "Iterations", "1"));
|
||||
threadGroup.appendChild(createStringProp(document, "Unit", "S"));
|
||||
}
|
||||
|
||||
private void processIterationThreadGroup(Element threadGroup) {
|
||||
// 检查 threadgroup 后面的hashtree是否为空
|
||||
Node hashTree = threadGroup.getNextSibling();
|
||||
while (!(hashTree instanceof Element)) {
|
||||
hashTree = hashTree.getNextSibling();
|
||||
}
|
||||
if (!hashTree.hasChildNodes()) {
|
||||
MSException.throwException(Translator.get("jmx_content_valid"));
|
||||
}
|
||||
// 重命名 tagName
|
||||
Document document = threadGroup.getOwnerDocument();
|
||||
removeChildren(threadGroup);
|
||||
|
||||
// 选择按照迭代次数处理线程组
|
||||
/*
|
||||
<stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
|
||||
<elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller" enabled="true">
|
||||
<boolProp name="LoopController.continue_forever">false</boolProp>
|
||||
<stringProp name="LoopController.loops">1</stringProp>
|
||||
</elementProp>
|
||||
<stringProp name="ThreadGroup.num_threads">100</stringProp>
|
||||
<stringProp name="ThreadGroup.ramp_time">5</stringProp>
|
||||
<boolProp name="ThreadGroup.scheduler">true</boolProp>
|
||||
<stringProp name="ThreadGroup.duration">10</stringProp>
|
||||
<stringProp name="ThreadGroup.delay"></stringProp>
|
||||
<boolProp name="ThreadGroup.same_user_on_next_iteration">true</boolProp>
|
||||
*/
|
||||
// elementProp
|
||||
Object targetLevels = context.getProperty("TargetLevel");
|
||||
String threads = "10";
|
||||
if (targetLevels instanceof List) {
|
||||
Object o = ((List<?>) targetLevels).get(0);
|
||||
((List<?>) targetLevels).remove(0);
|
||||
threads = o.toString();
|
||||
}
|
||||
Object iterateNum = context.getProperty("iterateNum");
|
||||
String loops = "1";
|
||||
if (iterateNum instanceof List) {
|
||||
Object o = ((List<?>) iterateNum).get(0);
|
||||
((List<?>) iterateNum).remove(0);
|
||||
loops = o.toString();
|
||||
}
|
||||
Object rampUps = context.getProperty("iterateRampUpTime");
|
||||
String rampUp = "10";
|
||||
if (rampUps instanceof List) {
|
||||
Object o = ((List<?>) rampUps).get(0);
|
||||
((List<?>) rampUps).remove(0);
|
||||
rampUp = o.toString();
|
||||
}
|
||||
Element elementProp = document.createElement("elementProp");
|
||||
elementProp.setAttribute("name", "ThreadGroup.main_controller");
|
||||
elementProp.setAttribute("elementType", "LoopController");
|
||||
elementProp.setAttribute("guiclass", "LoopControlPanel");
|
||||
elementProp.setAttribute("testclass", "LoopController");
|
||||
elementProp.setAttribute("testname", "Loop Controller");
|
||||
elementProp.setAttribute("enabled", "true");
|
||||
elementProp.appendChild(createBoolProp(document, "LoopController.continue_forever", false));
|
||||
elementProp.appendChild(createStringProp(document, "LoopController.loops", loops));
|
||||
threadGroup.appendChild(elementProp);
|
||||
|
||||
threadGroup.appendChild(createStringProp(document, "ThreadGroup.on_sample_error", "continue"));
|
||||
threadGroup.appendChild(createStringProp(document, "ThreadGroup.num_threads", threads));
|
||||
threadGroup.appendChild(createStringProp(document, "ThreadGroup.ramp_time", rampUp));
|
||||
threadGroup.appendChild(createBoolProp(document, "ThreadGroup.scheduler", false)); // 不指定执行时间
|
||||
threadGroup.appendChild(createStringProp(document, "Hold", "1"));
|
||||
threadGroup.appendChild(createStringProp(document, "ThreadGroup.duration", "10"));
|
||||
threadGroup.appendChild(createStringProp(document, "ThreadGroup.delay", ""));
|
||||
threadGroup.appendChild(createBoolProp(document, "ThreadGroup.same_user_on_next_iteration", true));
|
||||
}
|
||||
|
||||
|
||||
private void processCheckoutTimer(Element element) {
|
||||
/*
|
||||
<kg.apc.jmeter.timers.VariableThroughputTimer guiclass="kg.apc.jmeter.timers.VariableThroughputTimerGui" testclass="kg.apc.jmeter.timers.VariableThroughputTimer" testname="jp@gc - Throughput Shaping Timer" enabled="true">
|
||||
|
@ -856,37 +966,14 @@ public class JmeterDocumentParser implements DocumentParser {
|
|||
return unit;
|
||||
}
|
||||
|
||||
private void processConcurrencyThreadGroup(Element concurrencyThreadGroup) {
|
||||
String testname = concurrencyThreadGroup.getAttribute("testname");
|
||||
concurrencyThreadGroup.setAttribute("testname", testname + "-" + context.getResourceIndex());
|
||||
if (concurrencyThreadGroup.getChildNodes().getLength() > 0) {
|
||||
final NodeList childNodes = concurrencyThreadGroup.getChildNodes();
|
||||
for (int i = 0; i < childNodes.getLength(); i++) {
|
||||
Node node = childNodes.item(i);
|
||||
if (node instanceof Element) {
|
||||
Element ele = (Element) node;
|
||||
if (invalid(ele)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (nodeNameEquals(ele, STRING_PROP)) {
|
||||
parseStringProp(ele);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
private void processThreadGroupName(Element threadGroup) {
|
||||
String testname = threadGroup.getAttribute("testname");
|
||||
threadGroup.setAttribute("testname", testname + "-" + context.getResourceIndex());
|
||||
}
|
||||
|
||||
private void processVariableThroughputTimer(Element variableThroughputTimer) {
|
||||
Object durations = context.getProperty("duration");
|
||||
Integer duration;
|
||||
if (durations instanceof List) {
|
||||
Object o = ((List<?>) durations).get(0);
|
||||
duration = (Integer) o;
|
||||
((List<?>) durations).remove(0);
|
||||
} else {
|
||||
duration = (Integer) durations;
|
||||
}
|
||||
// 设置rps时长
|
||||
Integer duration = Integer.MAX_VALUE;
|
||||
Object rpsLimits = context.getProperty("rpsLimit");
|
||||
String rpsLimit;
|
||||
if (rpsLimits instanceof List) {
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 9f4a9bbf46fc1333dbcccea21f83e27e3ec10b1f
|
||||
Subproject commit 068127ce59ea8b016434ed52a9de4a7a4b13bdb4
|
|
@ -58,8 +58,8 @@
|
|||
<p class="tip">{{$t('api_test.definition.request.req_param')}} </p>
|
||||
<ms-api-request-form :referenced="true" :headers="request.headers " :request="request" v-if="request.protocol==='HTTP' || request.type==='HTTPSamplerProxy'"/>
|
||||
<ms-tcp-basis-parameters :request="request" v-if="request.protocol==='TCP'|| request.type==='TCPSampler'"/>
|
||||
<ms-sql-basis-parameters :request="request" v-if="request.protocol==='SQL'|| request.type==='JDBCSampler'"/>
|
||||
<ms-dubbo-basis-parameters :request="request" v-if="request.protocol==='DUBBO' || request.protocol==='dubbo://'|| request.type==='DubboSampler'"/>
|
||||
<ms-sql-basis-parameters :request="request" v-if="request.protocol==='SQL'|| request.type==='JDBCSampler'" :showScript="false"/>
|
||||
<ms-dubbo-basis-parameters :request="request" v-if="request.protocol==='DUBBO' || request.protocol==='dubbo://'|| request.type==='DubboSampler'" :showScript="false"/>
|
||||
|
||||
<p class="tip">{{$t('api_test.definition.request.res_param')}} </p>
|
||||
<ms-request-result-tail :currentProtocol="request.protocol" :response="request.requestResult" ref="runResult"/>
|
||||
|
|
|
@ -55,7 +55,7 @@
|
|||
import MsApiAssertionRegex from "./ApiAssertionRegex";
|
||||
import MsApiAssertionDuration from "./ApiAssertionDuration";
|
||||
import MsApiAssertionJsonPath from "./ApiAssertionJsonPath";
|
||||
import MsApiAssertionJsr223 from "@/business/components/api/test/components/assertion/ApiAssertionJsr223";
|
||||
import MsApiAssertionJsr223 from "./ApiAssertionJsr223";
|
||||
import MsApiAssertionXPath2 from "./ApiAssertionXPath2";
|
||||
|
||||
export default {
|
||||
|
|
|
@ -63,18 +63,19 @@
|
|||
</el-tabs>
|
||||
</el-form>
|
||||
</div>
|
||||
<div v-for="row in request.hashTree" :key="row.id" v-loading="isReloadData" style="margin-left: 20px;width: 100%">
|
||||
<!-- 前置脚本 -->
|
||||
<ms-jsr233-processor v-if="row.label ==='JSR223 PreProcessor'" @copyRow="copyRow" @remove="remove" :is-read-only="false" :title="$t('api_test.definition.request.pre_script')" style-type="color: #B8741A;background-color: #F9F1EA"
|
||||
:jsr223-processor="row"/>
|
||||
<!--后置脚本-->
|
||||
<ms-jsr233-processor v-if="row.label ==='JSR223 PostProcessor'" @copyRow="copyRow" @remove="remove" :is-read-only="false" :title="$t('api_test.definition.request.post_script')" style-type="color: #783887;background-color: #F2ECF3"
|
||||
:jsr223-processor="row"/>
|
||||
<!--断言规则-->
|
||||
<ms-api-assertions v-if="row.type==='Assertions'" @copyRow="copyRow" @remove="remove" :is-read-only="isReadOnly" :assertions="row"/>
|
||||
<!--提取规则-->
|
||||
<ms-api-extract :is-read-only="isReadOnly" @copyRow="copyRow" @remove="remove" v-if="row.type==='Extract'" :extract="row"/>
|
||||
|
||||
<div v-if="showScript">
|
||||
<div v-for="row in request.hashTree" :key="row.id" v-loading="isReloadData" style="margin-left: 20px;width: 100%">
|
||||
<!-- 前置脚本 -->
|
||||
<ms-jsr233-processor v-if="row.label ==='JSR223 PreProcessor'" @copyRow="copyRow" @remove="remove" :is-read-only="false" :title="$t('api_test.definition.request.pre_script')" style-type="color: #B8741A;background-color: #F9F1EA"
|
||||
:jsr223-processor="row"/>
|
||||
<!--后置脚本-->
|
||||
<ms-jsr233-processor v-if="row.label ==='JSR223 PostProcessor'" @copyRow="copyRow" @remove="remove" :is-read-only="false" :title="$t('api_test.definition.request.post_script')" style-type="color: #783887;background-color: #F2ECF3"
|
||||
:jsr223-processor="row"/>
|
||||
<!--断言规则-->
|
||||
<ms-api-assertions v-if="row.type==='Assertions'" @copyRow="copyRow" @remove="remove" :is-read-only="isReadOnly" :assertions="row"/>
|
||||
<!--提取规则-->
|
||||
<ms-api-extract :is-read-only="isReadOnly" @copyRow="copyRow" @remove="remove" v-if="row.type==='Extract'" :extract="row"/>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="3" class="ms-left-cell" v-if="showScript">
|
||||
|
|
|
@ -41,19 +41,19 @@
|
|||
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<div v-for="row in request.hashTree" :key="row.id" v-loading="isReloadData" style="margin-left: 20px;width: 100%">
|
||||
<!-- 前置脚本 -->
|
||||
<ms-jsr233-processor v-if="row.label ==='JSR223 PreProcessor'" @copyRow="copyRow" @remove="remove" :is-read-only="false" :title="$t('api_test.definition.request.pre_script')" style-type="color: #B8741A;background-color: #F9F1EA"
|
||||
:jsr223-processor="row"/>
|
||||
<!--后置脚本-->
|
||||
<ms-jsr233-processor v-if="row.label ==='JSR223 PostProcessor'" @copyRow="copyRow" @remove="remove" :is-read-only="false" :title="$t('api_test.definition.request.post_script')" style-type="color: #783887;background-color: #F2ECF3"
|
||||
:jsr223-processor="row"/>
|
||||
<!--断言规则-->
|
||||
<ms-api-assertions v-if="row.type==='Assertions'" @copyRow="copyRow" @remove="remove" :is-read-only="isReadOnly" :assertions="row"/>
|
||||
<!--提取规则-->
|
||||
<ms-api-extract :is-read-only="isReadOnly" @copyRow="copyRow" @remove="remove" v-if="row.type==='Extract'" :extract="row"/>
|
||||
|
||||
<div v-if="showScript">
|
||||
<div v-for="row in request.hashTree" :key="row.id" v-loading="isReloadData" style="margin-left: 20px;width: 100%">
|
||||
<!-- 前置脚本 -->
|
||||
<ms-jsr233-processor v-if="row.label ==='JSR223 PreProcessor'" @copyRow="copyRow" @remove="remove" :is-read-only="false" :title="$t('api_test.definition.request.pre_script')" style-type="color: #B8741A;background-color: #F9F1EA"
|
||||
:jsr223-processor="row"/>
|
||||
<!--后置脚本-->
|
||||
<ms-jsr233-processor v-if="row.label ==='JSR223 PostProcessor'" @copyRow="copyRow" @remove="remove" :is-read-only="false" :title="$t('api_test.definition.request.post_script')" style-type="color: #783887;background-color: #F2ECF3"
|
||||
:jsr223-processor="row"/>
|
||||
<!--断言规则-->
|
||||
<ms-api-assertions v-if="row.type==='Assertions'" @copyRow="copyRow" @remove="remove" :is-read-only="isReadOnly" :assertions="row"/>
|
||||
<!--提取规则-->
|
||||
<ms-api-extract :is-read-only="isReadOnly" @copyRow="copyRow" @remove="remove" v-if="row.type==='Extract'" :extract="row"/>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
:title="$t('commons.test')"
|
||||
@create="create" :createTip="$t('load_test.create')" :runTip="$t('load_test.run')"
|
||||
:show-run="true"
|
||||
@runTest="runTest"/>
|
||||
@runTest="runTest" @historicalDataUpgrade="historicalDataUpgrade"/>
|
||||
|
||||
</template>
|
||||
|
||||
|
@ -54,7 +54,8 @@
|
|||
</el-card>
|
||||
|
||||
<api-copy-dialog ref="apiCopy" @refresh="search"/>
|
||||
|
||||
<ms-upgrade ref="upgrade" :select-ids="selectIds"
|
||||
:select-project-names="selectProjectNames" :select-project-id="selectProjectId"/>
|
||||
</ms-main-container>
|
||||
</ms-container>
|
||||
</template>
|
||||
|
@ -72,13 +73,14 @@
|
|||
import {TEST_CONFIGS} from "../../common/components/search/search-components";
|
||||
import {ApiEvent, LIST_CHANGE} from "@/business/components/common/head/ListEvent";
|
||||
import ApiCopyDialog from "./components/ApiCopyDialog";
|
||||
import MsUpgrade from "./Upgrade";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ApiCopyDialog,
|
||||
OneClickOperation,
|
||||
MsTableOperators,
|
||||
MsApiTestStatus, MsMainContainer, MsContainer, MsTableHeader, MsTablePagination, MsTableOperator
|
||||
MsApiTestStatus, MsMainContainer, MsContainer, MsTableHeader, MsTablePagination, MsTableOperator, MsUpgrade
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -204,6 +206,13 @@
|
|||
_filter(filters, this.condition);
|
||||
this.init();
|
||||
},
|
||||
historicalDataUpgrade() {
|
||||
if (this.selectIds.size < 1) {
|
||||
this.$warning(this.$t('test_track.plan_view.select_manipulate'));
|
||||
} else {
|
||||
this.$refs.upgrade.openOneClickOperation();
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.init();
|
||||
|
|
|
@ -0,0 +1,109 @@
|
|||
<template>
|
||||
<el-dialog
|
||||
title="选则模块"
|
||||
:visible.sync="oneClickOperationVisible"
|
||||
width="600px"
|
||||
left
|
||||
:destroy-on-close="true"
|
||||
show-close
|
||||
@closed="handleClose" v-loading="loading">
|
||||
<el-form :model="ruleForm" label-position="right" label-width="80px" size="small" :rules="rule">
|
||||
<el-form-item :label="$t('test_track.module.module')" prop="apiScenarioModuleId">
|
||||
<el-select size="small" style="width: 80%" v-model="apiScenarioModuleId">
|
||||
<el-option v-for="item in moduleOptions" :key="item.id" :label="item.path" :value="item.id"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<ms-dialog-footer
|
||||
@cancel="oneClickOperationVisible = false"
|
||||
@confirm="confirm"/>
|
||||
</span>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MsDialogFooter from '../../common/components/MsDialogFooter'
|
||||
import MsApiScenarioConfig from "./components/ApiScenarioConfig";
|
||||
import MsApiReportStatus from "../report/ApiReportStatus";
|
||||
import MsApiReportDialog from "./ApiReportDialog";
|
||||
import {getUUID, getCurrentProjectID} from "@/common/js/utils";
|
||||
import {buildNodePath} from "../definition/model/NodeTree";
|
||||
|
||||
|
||||
export default {
|
||||
name: "MsUpgrade",
|
||||
components: {
|
||||
MsApiReportDialog, MsApiReportStatus, MsApiScenarioConfig, MsDialogFooter
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
oneClickOperationVisible: false,
|
||||
apiScenarioModuleId: "",
|
||||
moduleOptions: [],
|
||||
ruleForm: {},
|
||||
loading: false,
|
||||
rule: {
|
||||
apiScenarioModuleId: [
|
||||
{required: true, message: this.$t('test_track.module.module'), trigger: 'blur'},
|
||||
],
|
||||
}
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.initModule();
|
||||
},
|
||||
props: {
|
||||
selectIds: {
|
||||
type: Set
|
||||
},
|
||||
selectProjectNames: {
|
||||
type: Set
|
||||
},
|
||||
selectProjectId: {
|
||||
type: Set
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
openOneClickOperation() {
|
||||
this.oneClickOperationVisible = true;
|
||||
},
|
||||
getPath(id) {
|
||||
let path = this.moduleOptions.filter(function (item) {
|
||||
return item.id === id ? item.path : "";
|
||||
});
|
||||
return path[0].path;
|
||||
},
|
||||
confirm() {
|
||||
this.loading = true;
|
||||
let arr = Array.from(this.selectIds);
|
||||
let obj = {testIds: arr, projectId: getCurrentProjectID(), modulePath: this.getPath(this.apiScenarioModuleId), moduleId: this.apiScenarioModuleId};
|
||||
this.$post("/api/historicalDataUpgrade", obj, response => {
|
||||
this.loading = false;
|
||||
this.$success(this.$t('organization.integration.successful_operation'));
|
||||
this.oneClickOperationVisible = false;
|
||||
})
|
||||
},
|
||||
initModule() {
|
||||
let url = "/api/automation/module/list/" + getCurrentProjectID();
|
||||
this.$get(url, response => {
|
||||
if (response.data != undefined && response.data != null) {
|
||||
this.data = response.data;
|
||||
let modules = [];
|
||||
this.data.forEach(node => {
|
||||
buildNodePath(node, {path: ''}, modules);
|
||||
});
|
||||
this.moduleOptions = modules;
|
||||
}
|
||||
});
|
||||
},
|
||||
handleClose() {
|
||||
this.ruleForm = {}
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -13,6 +13,8 @@
|
|||
<ms-table-button :is-tester-permission="isTesterPermission" v-if="showRun" icon="el-icon-video-play"
|
||||
type="primary"
|
||||
:content="runTip" @click="runTest"/>
|
||||
<ms-table-button :is-tester-permission="isTesterPermission" v-if="showRun" icon="el-icon-circle-plus-outline"
|
||||
content="转场景测试" @click="historicalDataUpgrade"/>
|
||||
|
||||
<slot name="button"></slot>
|
||||
</span>
|
||||
|
@ -84,6 +86,9 @@
|
|||
},
|
||||
runTest() {
|
||||
this.$emit('runTest')
|
||||
},
|
||||
historicalDataUpgrade() {
|
||||
this.$emit('historicalDataUpgrade');
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
|
|
@ -21,45 +21,85 @@
|
|||
size="mini"/>
|
||||
</el-form-item>
|
||||
<br>
|
||||
<el-form-item :label="$t('load_test.duration')">
|
||||
<el-input-number
|
||||
:disabled="true"
|
||||
:placeholder="$t('load_test.duration')"
|
||||
v-model="threadGroup.duration"
|
||||
:min="1"
|
||||
size="mini"/>
|
||||
<el-form-item>
|
||||
<el-radio-group v-model="threadGroup.threadType">
|
||||
<el-radio label="DURATION">{{ $t('load_test.by_duration') }}</el-radio>
|
||||
<el-radio label="ITERATION">{{ $t('load_test.by_iteration') }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<br>
|
||||
<el-form-item :label="$t('load_test.rps_limit')">
|
||||
<el-switch v-model="rpsLimitEnable"/>
|
||||
|
||||
<el-input-number
|
||||
:disabled="true"
|
||||
:placeholder="$t('load_test.input_rps_limit')"
|
||||
v-model="threadGroup.rpsLimit"
|
||||
:min="1"
|
||||
size="mini"/>
|
||||
</el-form-item>
|
||||
<br>
|
||||
<el-form-item :label="$t('load_test.ramp_up_time_within')">
|
||||
<el-input-number
|
||||
:disabled="true"
|
||||
placeholder=""
|
||||
:min="1"
|
||||
:max="threadGroup.duration"
|
||||
v-model="threadGroup.rampUpTime"
|
||||
size="mini"/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('load_test.ramp_up_time_minutes')">
|
||||
<el-input-number
|
||||
:disabled="true"
|
||||
placeholder=""
|
||||
:min="1"
|
||||
:max="Math.min(threadGroup.threadNumber, threadGroup.rampUpTime)"
|
||||
v-model="threadGroup.step"
|
||||
size="mini"/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('load_test.ramp_up_time_times')"/>
|
||||
<div v-if="threadGroup.threadType === 'DURATION'">
|
||||
<el-form-item :label="$t('load_test.duration')">
|
||||
<el-input-number
|
||||
:disabled="true"
|
||||
v-model="threadGroup.duration"
|
||||
:min="1"
|
||||
@change="calculateChart(threadGroup)"
|
||||
size="mini"/>
|
||||
</el-form-item>
|
||||
<br>
|
||||
<el-form-item :label="$t('load_test.rps_limit')">
|
||||
<el-switch v-model="threadGroup.rpsLimitEnable" @change="calculateTotalChart()"/>
|
||||
|
||||
<el-input-number
|
||||
:disabled="true "
|
||||
v-model="threadGroup.rpsLimit"
|
||||
@change="calculateChart(threadGroup)"
|
||||
:min="1"
|
||||
size="mini"/>
|
||||
</el-form-item>
|
||||
<br>
|
||||
<el-form-item :label="$t('load_test.ramp_up_time_within')">
|
||||
<el-input-number
|
||||
:disabled="true"
|
||||
:min="1"
|
||||
:max="threadGroup.duration"
|
||||
v-model="threadGroup.rampUpTime"
|
||||
@change="calculateChart(threadGroup)"
|
||||
size="mini"/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('load_test.ramp_up_time_minutes')">
|
||||
<el-input-number
|
||||
:disabled="true"
|
||||
:min="1"
|
||||
:max="Math.min(threadGroup.threadNumber, threadGroup.rampUpTime)"
|
||||
v-model="threadGroup.step"
|
||||
@change="calculateChart(threadGroup)"
|
||||
size="mini"/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('load_test.ramp_up_time_times')"/>
|
||||
</div>
|
||||
<div v-if="threadGroup.threadType === 'ITERATION'">
|
||||
<el-form-item :label="$t('load_test.iterate_num')">
|
||||
<el-input-number
|
||||
:disabled="true"
|
||||
v-model="threadGroup.iterateNum"
|
||||
:min="1"
|
||||
@change="calculateChart(threadGroup)"
|
||||
size="mini"/>
|
||||
</el-form-item>
|
||||
<br>
|
||||
<el-form-item :label="$t('load_test.rps_limit')">
|
||||
<el-switch v-model="threadGroup.rpsLimitEnable" @change="calculateTotalChart()"/>
|
||||
|
||||
<el-input-number
|
||||
:disabled="true || !threadGroup.rpsLimitEnable"
|
||||
v-model="threadGroup.rpsLimit"
|
||||
@change="calculateChart(threadGroup)"
|
||||
:min="1"
|
||||
size="mini"/>
|
||||
</el-form-item>
|
||||
<br>
|
||||
<el-form-item :label="$t('load_test.ramp_up_time_within')">
|
||||
<el-input-number
|
||||
:disabled="true"
|
||||
:min="1"
|
||||
v-model="threadGroup.iterateRampUp"
|
||||
@change="calculateChart(threadGroup)"
|
||||
size="mini"/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('load_test.ramp_up_time_seconds')"/>
|
||||
</div>
|
||||
</el-form>
|
||||
</el-col>
|
||||
<el-col :span="14">
|
||||
|
@ -83,6 +123,10 @@ const STEPS = "Steps";
|
|||
const DURATION = "duration";
|
||||
const RPS_LIMIT = "rpsLimit";
|
||||
const RPS_LIMIT_ENABLE = "rpsLimitEnable";
|
||||
const THREAD_TYPE = "threadType";
|
||||
const ITERATE_NUM = "iterateNum";
|
||||
const ITERATE_RAMP_UP = "iterateRampUpTime";
|
||||
|
||||
const hexToRgba = function (hex, opacity) {
|
||||
return 'rgba(' + parseInt('0x' + hex.slice(1, 3)) + ',' + parseInt('0x' + hex.slice(3, 5)) + ','
|
||||
+ parseInt('0x' + hex.slice(5, 7)) + ',' + opacity + ')';
|
||||
|
@ -128,6 +172,9 @@ export default {
|
|||
case RAMP_UP:
|
||||
this.threadGroups[i].rampUpTime = item.value;
|
||||
break;
|
||||
case ITERATE_RAMP_UP:
|
||||
this.threadGroups[i].iterateRampUp = item.value;
|
||||
break;
|
||||
case DURATION:
|
||||
if (item.unit) {
|
||||
this.threadGroups[i].duration = item.value;
|
||||
|
@ -144,6 +191,12 @@ export default {
|
|||
case RPS_LIMIT_ENABLE:
|
||||
this.threadGroups[i].rpsLimitEnable = item.value;
|
||||
break;
|
||||
case THREAD_TYPE:
|
||||
this.threadGroups[i].threadType = item.value;
|
||||
break;
|
||||
case ITERATE_NUM:
|
||||
this.threadGroups[i].iterateNum = item.value;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -157,6 +210,9 @@ export default {
|
|||
case RAMP_UP:
|
||||
this.threadGroups[0].rampUpTime = d.value;
|
||||
break;
|
||||
case ITERATE_RAMP_UP:
|
||||
this.threadGroups[0].iterateRampUp = d.value;
|
||||
break;
|
||||
case DURATION:
|
||||
if (d.unit) {
|
||||
this.threadGroups[0].duration = d.value;
|
||||
|
@ -173,6 +229,12 @@ export default {
|
|||
case RPS_LIMIT_ENABLE:
|
||||
this.threadGroups[0].rpsLimitEnable = d.value;
|
||||
break;
|
||||
case THREAD_TYPE:
|
||||
this.threadGroups[0].threadType = d.value;
|
||||
break;
|
||||
case ITERATE_NUM:
|
||||
this.threadGroups[0].iterateNum = d.value;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -298,6 +298,9 @@ export default {
|
|||
tg.rampUpTime = tg.rampUpTime || 5;
|
||||
tg.step = tg.step || 5;
|
||||
tg.rpsLimit = tg.rpsLimit || 10;
|
||||
tg.threadType = tg.threadType || 'DURATION';
|
||||
tg.iterateNum = tg.iterateNum || 1;
|
||||
tg.iterateRampUp = tg.iterateRampUp || 10;
|
||||
handler.calculateChart(tg);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -33,45 +33,83 @@
|
|||
size="mini"/>
|
||||
</el-form-item>
|
||||
<br>
|
||||
<el-form-item :label="$t('load_test.duration')">
|
||||
<el-input-number
|
||||
:disabled="isReadOnly"
|
||||
v-model="threadGroup.duration"
|
||||
:min="1"
|
||||
@change="calculateChart(threadGroup)"
|
||||
size="mini"/>
|
||||
<el-form-item>
|
||||
<el-radio-group v-model="threadGroup.threadType">
|
||||
<el-radio label="DURATION">{{ $t('load_test.by_duration') }}</el-radio>
|
||||
<el-radio label="ITERATION">{{ $t('load_test.by_iteration') }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<br>
|
||||
<el-form-item :label="$t('load_test.rps_limit')">
|
||||
<el-switch v-model="threadGroup.rpsLimitEnable" @change="calculateTotalChart()"/>
|
||||
|
||||
<el-input-number
|
||||
:disabled="isReadOnly || !threadGroup.rpsLimitEnable"
|
||||
v-model="threadGroup.rpsLimit"
|
||||
@change="calculateChart(threadGroup)"
|
||||
:min="1"
|
||||
size="mini"/>
|
||||
</el-form-item>
|
||||
<br>
|
||||
<el-form-item :label="$t('load_test.ramp_up_time_within')">
|
||||
<el-input-number
|
||||
:disabled="isReadOnly"
|
||||
:min="1"
|
||||
:max="threadGroup.duration"
|
||||
v-model="threadGroup.rampUpTime"
|
||||
@change="calculateChart(threadGroup)"
|
||||
size="mini"/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('load_test.ramp_up_time_minutes')">
|
||||
<el-input-number
|
||||
:disabled="isReadOnly"
|
||||
:min="1"
|
||||
:max="Math.min(threadGroup.threadNumber, threadGroup.rampUpTime)"
|
||||
v-model="threadGroup.step"
|
||||
@change="calculateChart(threadGroup)"
|
||||
size="mini"/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('load_test.ramp_up_time_times')"/>
|
||||
<div v-if="threadGroup.threadType === 'DURATION'">
|
||||
<el-form-item :label="$t('load_test.duration')">
|
||||
<el-input-number
|
||||
:disabled="isReadOnly"
|
||||
v-model="threadGroup.duration"
|
||||
:min="1"
|
||||
@change="calculateChart(threadGroup)"
|
||||
size="mini"/>
|
||||
</el-form-item>
|
||||
<br>
|
||||
<el-form-item :label="$t('load_test.rps_limit')">
|
||||
<el-switch v-model="threadGroup.rpsLimitEnable" @change="calculateTotalChart()"/>
|
||||
|
||||
<el-input-number
|
||||
:disabled="isReadOnly || !threadGroup.rpsLimitEnable"
|
||||
v-model="threadGroup.rpsLimit"
|
||||
@change="calculateChart(threadGroup)"
|
||||
:min="1"
|
||||
size="mini"/>
|
||||
</el-form-item>
|
||||
<br>
|
||||
<el-form-item :label="$t('load_test.ramp_up_time_within')">
|
||||
<el-input-number
|
||||
:disabled="isReadOnly"
|
||||
:min="1"
|
||||
:max="threadGroup.duration"
|
||||
v-model="threadGroup.rampUpTime"
|
||||
@change="calculateChart(threadGroup)"
|
||||
size="mini"/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('load_test.ramp_up_time_minutes')">
|
||||
<el-input-number
|
||||
:disabled="isReadOnly"
|
||||
:min="1"
|
||||
:max="Math.min(threadGroup.threadNumber, threadGroup.rampUpTime)"
|
||||
v-model="threadGroup.step"
|
||||
@change="calculateChart(threadGroup)"
|
||||
size="mini"/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('load_test.ramp_up_time_times')"/>
|
||||
</div>
|
||||
<div v-if="threadGroup.threadType === 'ITERATION'">
|
||||
<el-form-item :label="$t('load_test.iterate_num')">
|
||||
<el-input-number
|
||||
:disabled="isReadOnly"
|
||||
v-model="threadGroup.iterateNum"
|
||||
:min="1"
|
||||
@change="calculateChart(threadGroup)"
|
||||
size="mini"/>
|
||||
</el-form-item>
|
||||
<br>
|
||||
<el-form-item :label="$t('load_test.rps_limit')">
|
||||
<el-switch v-model="threadGroup.rpsLimitEnable" @change="calculateTotalChart()"/>
|
||||
|
||||
<el-input-number
|
||||
:disabled="isReadOnly || !threadGroup.rpsLimitEnable"
|
||||
v-model="threadGroup.rpsLimit"
|
||||
:min="1"
|
||||
size="mini"/>
|
||||
</el-form-item>
|
||||
<br>
|
||||
<el-form-item :label="$t('load_test.ramp_up_time_within')">
|
||||
<el-input-number
|
||||
:disabled="isReadOnly"
|
||||
:min="1"
|
||||
v-model="threadGroup.iterateRampUp"
|
||||
size="mini"/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('load_test.ramp_up_time_seconds')"/>
|
||||
</div>
|
||||
</el-form>
|
||||
</el-col>
|
||||
<el-col :span="14">
|
||||
|
@ -91,11 +129,14 @@ import {findTestPlan, findThreadGroup} from "@/business/components/performance/t
|
|||
|
||||
const TARGET_LEVEL = "TargetLevel";
|
||||
const RAMP_UP = "RampUp";
|
||||
const ITERATE_RAMP_UP = "iterateRampUpTime";
|
||||
const STEPS = "Steps";
|
||||
const DURATION = "duration";
|
||||
const RPS_LIMIT = "rpsLimit";
|
||||
const RPS_LIMIT_ENABLE = "rpsLimitEnable";
|
||||
const HOLD = "Hold";
|
||||
const THREAD_TYPE = "threadType";
|
||||
const ITERATE_NUM = "iterateNum";
|
||||
|
||||
const hexToRgba = function (hex, opacity) {
|
||||
return 'rgba(' + parseInt('0x' + hex.slice(1, 3)) + ',' + parseInt('0x' + hex.slice(3, 5)) + ','
|
||||
|
@ -186,6 +227,9 @@ export default {
|
|||
case RAMP_UP:
|
||||
this.threadGroups[i].rampUpTime = item.value;
|
||||
break;
|
||||
case ITERATE_RAMP_UP:
|
||||
this.threadGroups[i].iterateRampUp = item.value;
|
||||
break;
|
||||
case DURATION:
|
||||
if (item.unit) {
|
||||
this.threadGroups[i].duration = item.value;
|
||||
|
@ -202,9 +246,19 @@ export default {
|
|||
case RPS_LIMIT_ENABLE:
|
||||
this.threadGroups[i].rpsLimitEnable = item.value;
|
||||
break;
|
||||
case THREAD_TYPE:
|
||||
this.threadGroups[i].threadType = item.value;
|
||||
break;
|
||||
case ITERATE_NUM:
|
||||
this.threadGroups[i].iterateNum = item.value;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
//
|
||||
this.$set(this.threadGroups[i], "threadType", this.threadGroups[i].threadType || 'DURATION');
|
||||
this.$set(this.threadGroups[i], "iterateNum", this.threadGroups[i].iterateNum || 1);
|
||||
this.$set(this.threadGroups[i], "iterateRampUp", this.threadGroups[i].iterateRampUp || 10);
|
||||
})
|
||||
this.calculateChart(this.threadGroups[i]);
|
||||
} else {
|
||||
|
@ -215,6 +269,9 @@ export default {
|
|||
case RAMP_UP:
|
||||
this.threadGroups[0].rampUpTime = d.value;
|
||||
break;
|
||||
case ITERATE_RAMP_UP:
|
||||
this.threadGroups[0].iterateRampUp = d.value;
|
||||
break;
|
||||
case DURATION:
|
||||
if (d.unit) {
|
||||
this.threadGroups[0].duration = d.value;
|
||||
|
@ -231,9 +288,18 @@ export default {
|
|||
case RPS_LIMIT_ENABLE:
|
||||
this.threadGroups[0].rpsLimitEnable = d.value;
|
||||
break;
|
||||
case THREAD_TYPE:
|
||||
this.threadGroups[0].threadType = d.value;
|
||||
break;
|
||||
case ITERATE_NUM:
|
||||
this.threadGroups[0].iterateNum = d.value;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
this.$set(this.threadGroups[0], "threadType", this.threadGroups[0].threadType || 'DURATION');
|
||||
this.$set(this.threadGroups[0], "iterateNum", this.threadGroups[0].iterateNum || 1);
|
||||
this.$set(this.threadGroups[0], "iterateRampUp", this.threadGroups[0].iterateRampUp || 10);
|
||||
this.calculateChart(this.threadGroups[0]);
|
||||
}
|
||||
}
|
||||
|
@ -461,7 +527,7 @@ export default {
|
|||
|
||||
for (let i = 0; i < this.threadGroups.length; i++) {
|
||||
if (!this.threadGroups[i].threadNumber || !this.threadGroups[i].duration
|
||||
|| !this.threadGroups[i].rampUpTime || !this.threadGroups[i].step) {
|
||||
|| !this.threadGroups[i].rampUpTime || !this.threadGroups[i].step || !this.threadGroups[i].iterateNum) {
|
||||
this.$warning(this.$t('load_test.pressure_config_params_is_empty'));
|
||||
this.$emit('changeActive', '1');
|
||||
return false;
|
||||
|
@ -488,6 +554,9 @@ export default {
|
|||
{key: RPS_LIMIT, value: this.threadGroups[i].rpsLimit},
|
||||
{key: RPS_LIMIT_ENABLE, value: this.threadGroups[i].rpsLimitEnable},
|
||||
{key: HOLD, value: this.threadGroups[i].duration - this.threadGroups[i].rampUpTime},
|
||||
{key: THREAD_TYPE, value: this.threadGroups[i].threadType},
|
||||
{key: ITERATE_NUM, value: this.threadGroups[i].iterateNum},
|
||||
{key: ITERATE_RAMP_UP, value: this.threadGroups[i].iterateRampUp},
|
||||
]);
|
||||
}
|
||||
return result;
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 010ad7a5f072a5e9d368c756a2473bbd20781433
|
||||
Subproject commit 7d43154a7c19732407a8e9ace8a7d1ea13c91f36
|
|
@ -438,6 +438,10 @@ export default {
|
|||
input_rps_limit: 'Please enter a limit',
|
||||
ramp_up_time_within: 'In',
|
||||
ramp_up_time_minutes: 'seconds, separate',
|
||||
ramp_up_time_seconds: 'seconds add concurrent users',
|
||||
iterate_num: 'Iterations: ',
|
||||
by_iteration: 'By iterations',
|
||||
by_duration: 'By duration',
|
||||
ramp_up_time_times: 'add concurrent users',
|
||||
advanced_config_error: 'Advanced configuration verification failed',
|
||||
domain_bind: 'Domain bind',
|
||||
|
|
|
@ -434,6 +434,10 @@ export default {
|
|||
input_rps_limit: '请输入限制',
|
||||
ramp_up_time_within: '在',
|
||||
ramp_up_time_minutes: '秒内,分',
|
||||
ramp_up_time_seconds: '秒内增加并发用户',
|
||||
iterate_num: '迭代次数 (次): ',
|
||||
by_iteration: '按迭代次数',
|
||||
by_duration: '按持续时间',
|
||||
ramp_up_time_times: '次增加并发用户',
|
||||
advanced_config_error: '高级配置校验失败',
|
||||
domain_bind: '域名绑定',
|
||||
|
|
|
@ -434,6 +434,10 @@ export default {
|
|||
input_rps_limit: '請輸入限制',
|
||||
ramp_up_time_within: '在',
|
||||
ramp_up_time_minutes: '秒內,分',
|
||||
ramp_up_time_seconds: '秒內增加並發用戶',
|
||||
iterate_num: '迭代次數 (次): ',
|
||||
by_iteration: '按迭代次數',
|
||||
by_duration: '按壓測時長',
|
||||
ramp_up_time_times: '次增加並發用戶',
|
||||
advanced_config_error: '高級配置校驗失敗',
|
||||
domain_bind: '域名綁定',
|
||||
|
|
Loading…
Reference in New Issue