fix: 场景报告增加跳转功能、增加步骤统计纬度

场景报告增加跳转功能、增加步骤统计纬度
This commit is contained in:
song-tianyang 2021-06-16 11:30:45 +08:00 committed by 刘瑞斌
parent dfb9890af4
commit ad85f0814f
6 changed files with 122 additions and 23 deletions

View File

@ -131,11 +131,11 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl
LogUtil.error("testPlanApiCaseService is required"); LogUtil.error("testPlanApiCaseService is required");
} }
apiEnvironmentRunningParamService = CommonBeanFactory.getBean(ApiEnvironmentRunningParamService.class); apiEnvironmentRunningParamService = CommonBeanFactory.getBean(ApiEnvironmentRunningParamService.class);
if (apiEnvironmentRunningParamService == null) { if(apiEnvironmentRunningParamService == null){
LogUtil.error("apiEnvironmentRunningParamService is required"); LogUtil.error("apiEnvironmentRunningParamService is required");
} }
testPlanTestCaseService = CommonBeanFactory.getBean(TestPlanTestCaseService.class); testPlanTestCaseService = CommonBeanFactory.getBean(TestPlanTestCaseService.class);
if (testPlanTestCaseService == null) { if(testPlanTestCaseService == null){
LogUtil.error("testPlanTestCaseService is required"); LogUtil.error("testPlanTestCaseService is required");
} }
super.setupTest(context); super.setupTest(context);
@ -151,17 +151,16 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl
public void teardownTest(BackendListenerContext context) throws Exception { public void teardownTest(BackendListenerContext context) throws Exception {
TestResult testResult = new TestResult(); TestResult testResult = new TestResult();
testResult.setTestId(testId); testResult.setTestId(testId);
testResult.setTotal(queue.size());
testResult.setConsole(getConsole()); testResult.setConsole(getConsole());
testResult.setTotal(0);
// 一个脚本里可能包含多个场景(ThreadGroup)所以要区分开key: 场景Id // 一个脚本里可能包含多个场景(ThreadGroup)所以要区分开key: 场景Id
final Map<String, ScenarioResult> scenarios = new LinkedHashMap<>(); final Map<String, ScenarioResult> scenarios = new LinkedHashMap<>();
queue.forEach(result -> { queue.forEach(result -> {
// 线程名称: <场景名> <场景Index>-<请求Index>, 例如Scenario 2-1 // 线程名称: <场景名> <场景Index>-<请求Index>, 例如Scenario 2-1
if (StringUtils.equals(result.getSampleLabel(), RunningParamKeys.RUNNING_DEBUG_SAMPLER_NAME)) { if(StringUtils.equals(result.getSampleLabel(), RunningParamKeys.RUNNING_DEBUG_SAMPLER_NAME)){
String evnStr = result.getResponseDataAsString(); String evnStr = result.getResponseDataAsString();
apiEnvironmentRunningParamService.parseEvn(evnStr); apiEnvironmentRunningParamService.parseEvn(evnStr);
} else { }else {
String scenarioName = StringUtils.substringBeforeLast(result.getThreadName(), THREAD_SPLIT); String scenarioName = StringUtils.substringBeforeLast(result.getThreadName(), THREAD_SPLIT);
String index = StringUtils.substringAfterLast(result.getThreadName(), THREAD_SPLIT); String index = StringUtils.substringAfterLast(result.getThreadName(), THREAD_SPLIT);
String scenarioId = StringUtils.substringBefore(index, ID_SPLIT); String scenarioId = StringUtils.substringBefore(index, ID_SPLIT);
@ -193,11 +192,12 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl
testResult.addPassAssertions(requestResult.getPassAssertions()); testResult.addPassAssertions(requestResult.getPassAssertions());
testResult.addTotalAssertions(requestResult.getTotalAssertions()); testResult.addTotalAssertions(requestResult.getTotalAssertions());
testResult.setTotal(testResult.getTotal()+1);
scenarioResult.addPassAssertions(requestResult.getPassAssertions()); scenarioResult.addPassAssertions(requestResult.getPassAssertions());
scenarioResult.addTotalAssertions(requestResult.getTotalAssertions()); scenarioResult.addTotalAssertions(requestResult.getTotalAssertions());
} }
}); });
testResult.getScenarios().addAll(scenarios.values()); testResult.getScenarios().addAll(scenarios.values());
testResult.getScenarios().sort(Comparator.comparing(ScenarioResult::getId)); testResult.getScenarios().sort(Comparator.comparing(ScenarioResult::getId));
ApiTestReport report = null; ApiTestReport report = null;
@ -297,7 +297,7 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl
//环境 //环境
ApiScenarioWithBLOBs apiScenario = apiAutomationService.getDto(scenarioReport.getScenarioId()); ApiScenarioWithBLOBs apiScenario = apiAutomationService.getDto(scenarioReport.getScenarioId());
String name = ""; String name = "";
if (apiScenario != null) { if(apiScenario!= null ) {
String executionEnvironment = apiScenario.getScenarioDefinition(); String executionEnvironment = apiScenario.getScenarioDefinition();
JSONObject json = JSONObject.parseObject(executionEnvironment); JSONObject json = JSONObject.parseObject(executionEnvironment);
if (json != null && json.getString("environmentMap") != null && json.getString("environmentMap").length() > 2) { if (json != null && json.getString("environmentMap") != null && json.getString("environmentMap").length() > 2) {

View File

@ -1,6 +1,7 @@
package io.metersphere.api.jmeter; package io.metersphere.api.jmeter;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import io.metersphere.commons.constants.DelimiterConstants; import io.metersphere.commons.constants.DelimiterConstants;
import lombok.Data; import lombok.Data;
import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.CollectionUtils;
@ -40,12 +41,15 @@ public class TestResult {
private String console; private String console;
private String runningDebugSampler;
private List<ScenarioResult> scenarios = new ArrayList<>(); private List<ScenarioResult> scenarios = new ArrayList<>();
private Map<String, Boolean> margeScenariMap = new HashMap<>(); private Map<String, Boolean> margeScenariMap = new HashMap<>();
private Map<String, Boolean> scenarioStepMap = new HashMap<>();
private int scenarioStepSuccess = 0;
private int scenarioStepError = 0;
private int scenarioStepTotal = 0;
public void addError(int count) { public void addError(int count) {
this.error += count; this.error += count;
} }
@ -74,12 +78,33 @@ public class TestResult {
} }
} }
private void setStepStatus(String step_names, boolean status) {
if (!scenarioStepMap.containsKey(step_names) || status) {
scenarioStepMap.put(step_names, status);
}
}
public void addScenario(ScenarioResult result) { public void addScenario(ScenarioResult result) {
if (result != null && CollectionUtils.isNotEmpty(result.getRequestResults())) { if (result != null && CollectionUtils.isNotEmpty(result.getRequestResults())) {
result.getRequestResults().forEach(item -> { result.getRequestResults().forEach(item -> {
String itemScenarioName = "";
if (StringUtils.isNotEmpty(item.getScenario())) { if (StringUtils.isNotEmpty(item.getScenario())) {
List<String> id_names = JSON.parseObject(item.getScenario(), List.class); List<String> all_id_names = JSON.parseObject(item.getScenario(), List.class);
if(all_id_names.size()>1){
List<String> id_names = new ArrayList<>();
all_id_names.forEach(name -> {
if(!name.endsWith(result.getName())){
id_names.add(name);
}
});
this.setStatus(id_names, item.getError() > 0); this.setStatus(id_names, item.getError() > 0);
itemScenarioName = JSONArray.toJSONString(id_names);
}else{
this.setStatus(all_id_names, item.getError() > 0);
itemScenarioName = JSONArray.toJSONString(all_id_names);
}
} }
if (StringUtils.isNotEmpty(item.getName()) && item.getName().indexOf(SEPARATOR) != -1) { if (StringUtils.isNotEmpty(item.getName()) && item.getName().indexOf(SEPARATOR) != -1) {
String array[] = item.getName().split(SEPARATOR); String array[] = item.getName().split(SEPARATOR);
@ -102,17 +127,38 @@ public class TestResult {
} }
}); });
} }
this.setStepStatus(item.getName()+itemScenarioName,item.getError()>0);
}); });
scenarios.add(result); scenarios.add(result);
} }
/**
* 1.10.2 场景成功/失败统计不再按照请求为纬度按照场景为纬度
*/
for (String key : scenarioStepMap.keySet()) {
if (scenarioStepMap .get(key)) {
this.scenarioStepError++;
} else {
this.scenarioStepSuccess++;
}
}
boolean hasError = false;
for (String key : margeScenariMap.keySet()) { for (String key : margeScenariMap.keySet()) {
if (margeScenariMap.get(key)) { if (margeScenariMap.get(key)) {
this.scenarioError++; hasError = true;
} else { break;
}
}
if(!margeScenariMap.isEmpty()){
if(hasError){
this.scenarioError ++;
}else {
this.scenarioSuccess++; this.scenarioSuccess++;
} }
this.scenarioTotal++;
} }
this.setScenarioTotal(this.margeScenariMap.size());
this.setScenarioStepTotal(this.scenarioStepMap.size());
} }
} }

View File

@ -384,6 +384,9 @@ public class ApiScenarioReportService {
testResult.setScenarioSuccess(testResult.getScenarioSuccess() + scenarioResult.getScenarioSuccess()); testResult.setScenarioSuccess(testResult.getScenarioSuccess() + scenarioResult.getScenarioSuccess());
testResult.setScenarioError(testResult.getScenarioError() + scenarioResult.getScenarioError()); testResult.setScenarioError(testResult.getScenarioError() + scenarioResult.getScenarioError());
testResult.setConsole(scenarioResult.getConsole()); testResult.setConsole(scenarioResult.getConsole());
testResult.setScenarioStepError(scenarioResult.getScenarioStepError()+testResult.getScenarioStepError());
testResult.setScenarioStepSuccess(scenarioResult.getScenarioStepSuccess()+testResult.getScenarioStepSuccess());
testResult.setScenarioStepTotal(scenarioResult.getScenarioStepTotal()+testResult.getScenarioStepTotal());
} catch (Exception e) { } catch (Exception e) {
LogUtil.error(e.getMessage()); LogUtil.error(e.getMessage());
} }

View File

@ -2,7 +2,19 @@
<header class="report-header"> <header class="report-header">
<el-row> <el-row>
<el-col> <el-col>
<span v-if="!debug"><el-input size="mini" style="width: 200px" v-model="report.name"/> </span> <!-- <span v-if="!debug"><el-input size="mini" style="width: 200px" v-model="report.name"/> </span>-->
<span v-if="!debug">
<el-input v-if="nameIsEdit" size="mini" @blur="nameIsEdit = false" style="width: 200px" v-model="report.name"/>
<span v-else>
<router-link v-if="isSingleScenario" :to="{name: 'ApiAutomation', params: { dataSelectRange: 'edit:' + scenarioId }}">
{{ report.name }}
</router-link>
<span v-else>
{{ report.name }}
</span>
<i class="el-icon-edit" style="cursor:pointer" @click="nameIsEdit = true" @click.stop/>
</span>
</span>
<span class="time"> {{ report.createTime | timestampFormatDate }}</span> <span class="time"> {{ report.createTime | timestampFormatDate }}</span>
<el-button v-if="!debug" v-permission="['PROJECT_API_REPORT:READ+EXPORT']" :disabled="isReadOnly" class="export-button" plain type="primary" size="mini" @click="handleExport(report.name)" style="margin-right: 10px"> <el-button v-if="!debug" v-permission="['PROJECT_API_REPORT:READ+EXPORT']" :disabled="isReadOnly" class="export-button" plain type="primary" size="mini" @click="handleExport(report.name)" style="margin-right: 10px">
@ -30,10 +42,26 @@
path() { path() {
return "/api/test/edit?id=" + this.report.testId; return "/api/test/edit?id=" + this.report.testId;
}, },
scenarioId(){
if(typeof this.report.scenarioId === 'string'){
return this.report.scenarioId;
}else {
return "";
}
},
isSingleScenario(){
try {
JSON.parse(this.report.scenarioId);
return false;
} catch(e){
return true;
}
}
}, },
data() { data() {
return { return {
isReadOnly: false, isReadOnly: false,
nameIsEdit:false,
} }
}, },
created() { created() {

View File

@ -38,22 +38,43 @@
<div style="width: 50%"> <div style="width: 50%">
<el-row type="flex" justify="center" align="middle"> <el-row type="flex" justify="center" align="middle">
<el-row type="flex" justify="center" align="middle"> <el-row type="flex" justify="center" align="middle">
<div class="metric-box" style="margin-right: 50px"> <!-- <div class="metric-box" style="margin-right: 50px">-->
<div class="metric-box">
<div class="value">{{ content.scenarioTotal ? content.scenarioTotal : 0}}</div> <div class="value">{{ content.scenarioTotal ? content.scenarioTotal : 0}}</div>
<div class="name">{{ $t('api_test.scenario.scenario') }}</div> <div class="name">{{ $t('api_test.scenario.scenario') }}</div>
</div> </div>
<i class="circle success"/> <i class="circle success" style="margin-left: 20px;margin-right: 20px"/>
<div class="metric-box"> <div class="metric-box">
<div class="value">{{ content.scenarioSuccess ? content.scenarioSuccess: 0 }}</div> <div class="value">{{ content.scenarioSuccess ? content.scenarioSuccess: 0 }}</div>
<div class="name">{{ $t('api_report.success') }}</div> <div class="name">{{ $t('api_report.success') }}</div>
</div> </div>
<div style="width: 40px"></div> <!-- <div style="width: 40px"></div>-->
<i class="circle fail"/> <i class="circle fail" style="margin-left: 20px;margin-right: 20px"/>
<div class="metric-box"> <div class="metric-box">
<div class="value">{{ content.scenarioError ? content.scenarioError : 0 }}</div> <div class="value">{{ content.scenarioError ? content.scenarioError : 0 }}</div>
<div class="name">{{ $t('api_report.fail') }}</div> <div class="name">{{ $t('api_report.fail') }}</div>
</div> </div>
</el-row> </el-row>
<el-divider></el-divider>
<el-row type="flex" justify="center" align="middle">
<el-row type="flex" justify="center" align="middle">
<div class="metric-box">
<div class="value">{{ content.scenarioStepTotal ? content.scenarioStepTotal : 0}}</div>
<div class="name">{{ $t('test_track.plan_view.step') }}</div>
</div>
<i class="circle success" style="margin-left: 20px;margin-right: 20px"/>
<div class="metric-box">
<div class="value">{{ content.scenarioStepSuccess ? content.scenarioStepSuccess: 0 }}</div>
<div class="name">{{ $t('api_report.success') }}</div>
</div>
<!-- <div style="width: 40px"></div>-->
<i class="circle fail" style="margin-left: 20px;margin-right: 20px"/>
<div class="metric-box">
<div class="value">{{ content.scenarioStepError ? content.scenarioStepError : 0 }}</div>
<div class="name">{{ $t('api_report.fail') }}</div>
</div>
</el-row>
</el-row>
</el-row> </el-row>
</div> </div>
<div class="split"></div> <div class="split"></div>

View File

@ -318,6 +318,7 @@ export default {
data() { data() {
return { return {
onSampleError: false, onSampleError: false,
showConfigButtonWithOutPermission:false,
props: { props: {
label: "label", label: "label",
children: "hashTree" children: "hashTree"