feat(接口自动化 ): 多模式执行功能

This commit is contained in:
fit2-zhao 2021-04-09 11:07:41 +08:00
parent f6384b935c
commit f19f05db17
31 changed files with 684 additions and 212 deletions

View File

@ -66,6 +66,7 @@ public class ApiAutomationController {
public void update(@RequestPart("request") SaveApiScenarioRequest request, @RequestPart(value = "files") List<MultipartFile> bodyFiles) {
apiAutomationService.update(request, bodyFiles);
}
@GetMapping("/delete/{id}")
public void delete(@PathVariable String id) {
apiAutomationService.delete(id);
@ -139,10 +140,13 @@ public class ApiAutomationController {
}
@PostMapping(value = "/run/batch")
public String runBatcah(@RequestBody RunScenarioRequest request) {
public String runBatch(@RequestBody RunScenarioRequest request) {
request.setExecuteType(ExecuteType.Saved.name());
request.setTriggerMode(ApiRunMode.SCENARIO.name());
request.setRunMode(ApiRunMode.SCENARIO.name());
if (request.getConfig() != null && request.getConfig().getMode().equals("serial")) {
return apiAutomationService.runSerial(request);
}
return apiAutomationService.run(request);
}
@ -174,7 +178,7 @@ public class ApiAutomationController {
}
@PostMapping("/relevance/review")
public void testCaseReviewRelevance(@RequestBody ApiCaseRelevanceRequest request){
public void testCaseReviewRelevance(@RequestBody ApiCaseRelevanceRequest request) {
apiAutomationService.relevanceReview(request);
}

View File

@ -0,0 +1,11 @@
package io.metersphere.api.dto.automation;
import lombok.Data;
@Data
public class RunModeConfig {
private String mode;
private String reportType;
private String reportName;
private boolean onSampleError;
}

View File

@ -21,7 +21,7 @@ public class RunScenarioRequest extends ApiScenarioWithBLOBs {
private String runMode;
//测试情景和测试计划的关联ID
/**测试情景和测试计划的关联ID*/
private String planScenarioId;
private List<String> planCaseIds;
@ -30,8 +30,9 @@ public class RunScenarioRequest extends ApiScenarioWithBLOBs {
private String reportUserID;
private Map<String,String> scenarioTestPlanIdMap;
private Map<String, String> scenarioTestPlanIdMap;
private ApiScenarioRequest condition;
private RunModeConfig config;
}

View File

@ -18,6 +18,7 @@ import java.util.List;
@JSONType(typeName = "TestPlan")
public class MsTestPlan extends MsTestElement {
private String type = "TestPlan";
private boolean serializeThreadgroups = false;
@Override
public void toHashTree(HashTree tree, List<MsTestElement> hashTree, ParameterConfig config) {
@ -35,7 +36,7 @@ public class MsTestPlan extends MsTestElement {
testPlan.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("TestPlanGui"));
testPlan.setEnabled(true);
testPlan.setFunctionalMode(false);
testPlan.setSerialized(true);
testPlan.setSerialized(serializeThreadgroups);
testPlan.setTearDownOnShutdown(true);
testPlan.setUserDefinedVariables(new Arguments());
return testPlan;

View File

@ -19,6 +19,7 @@ import java.util.List;
public class MsThreadGroup extends MsTestElement {
private String type = "ThreadGroup";
private boolean enableCookieShare;
private boolean onSampleError;
@Override
public void toHashTree(HashTree tree, List<MsTestElement> hashTree, ParameterConfig config) {
@ -59,6 +60,9 @@ public class MsThreadGroup extends MsTestElement {
threadGroup.setDuration(0);
threadGroup.setProperty(ThreadGroup.ON_SAMPLE_ERROR, ThreadGroup.ON_SAMPLE_ERROR_CONTINUE);
threadGroup.setScheduler(false);
if (onSampleError) {
threadGroup.setProperty("ThreadGroup.on_sample_error", "stoptest");
}
threadGroup.setSamplerController(loopController);
return threadGroup;
}

View File

@ -0,0 +1,4 @@
package io.metersphere.api.dto.definition.request.sampler;
public class SamplerEnv {
}

View File

@ -10,5 +10,6 @@ public class HttpConfig {
private String domain;
private String protocol = "https";
private int port;
private List<HttpConfigCondition> conditions;
private List<KeyValue> headers;
}

View File

@ -0,0 +1,14 @@
package io.metersphere.api.dto.scenario;
import lombok.Data;
import java.util.List;
@Data
public class HttpConfigCondition {
private String type;
private List<KeyValue> details;
private String protocol;
private String socket;
private String domain;
}

View File

@ -36,6 +36,8 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl
public final static String TEST_ID = "ms.test.id";
public final static String TEST_REPORT_NAME = "ms.test.report.name";
private final static String THREAD_SPLIT = " ";
private final static String ID_SPLIT = "-";
@ -62,6 +64,8 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl
private String testId;
private String debugReportId;
// 只有合并报告是这个有值
private String reportName;
//获得控制台内容
private PrintStream oldPrintStream = System.out;
@ -125,7 +129,7 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl
TestResult testResult = new TestResult();
testResult.setTestId(testId);
testResult.setTotal(queue.size());
testResult.setReportName(this.reportName);
// 一个脚本里可能包含多个场景(ThreadGroup)所以要区分开key: 场景Id
final Map<String, ScenarioResult> scenarios = new LinkedHashMap<>();
queue.forEach(result -> {
@ -209,7 +213,7 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl
} else {
apiDefinitionExecResultService.saveApiResult(testResult, ApiRunMode.API_PLAN.name());
}
} else if (StringUtils.equalsAny(this.runMode, ApiRunMode.SCENARIO.name(), ApiRunMode.SCENARIO_PLAN.name(), ApiRunMode.SCHEDULE_SCENARIO_PLAN.name(),ApiRunMode.SCHEDULE_SCENARIO.name())) {
} else if (StringUtils.equalsAny(this.runMode, ApiRunMode.SCENARIO.name(), ApiRunMode.SCENARIO_PLAN.name(), ApiRunMode.SCHEDULE_SCENARIO_PLAN.name(), ApiRunMode.SCHEDULE_SCENARIO.name())) {
// 执行报告不需要存储由用户确认后在存储
testResult.setTestId(testId);
ApiScenarioReport scenarioReport = apiScenarioReportService.complete(testResult, this.runMode);
@ -247,7 +251,7 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl
}
}
if (report != null && StringUtils.equals(ReportTriggerMode.API.name(), report.getTriggerMode())||StringUtils.equals(ReportTriggerMode.SCHEDULE.name(), report.getTriggerMode())) {
if (report != null && StringUtils.equals(ReportTriggerMode.API.name(), report.getTriggerMode()) || StringUtils.equals(ReportTriggerMode.SCHEDULE.name(), report.getTriggerMode())) {
sendTask(report, reportUrl, testResult);
}
@ -394,6 +398,7 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl
private void setParam(BackendListenerContext context) {
this.testId = context.getParameter(TEST_ID);
this.reportName = context.getParameter(TEST_REPORT_NAME);
this.runMode = context.getParameter("runMode");
this.debugReportId = context.getParameter("debugReportId");
if (StringUtils.isBlank(this.runMode)) {

View File

@ -1,5 +1,7 @@
package io.metersphere.api.jmeter;
import io.fabric8.kubernetes.client.extended.run.RunConfig;
import io.metersphere.api.dto.automation.RunModeConfig;
import io.metersphere.commons.constants.ApiRunMode;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.LogUtil;
@ -88,6 +90,25 @@ public class JMeterService {
testPlan.add(testPlan.getArray()[0], backendListener);
}
private void addBackendListener(String testId, String debugReportId, String runMode, HashTree testPlan, RunModeConfig config) {
BackendListener backendListener = new BackendListener();
backendListener.setName(testId);
Arguments arguments = new Arguments();
if (config != null && config.getMode().equals("serial") && config.getReportType().equals("setReport")) {
arguments.addArgument(APIBackendListenerClient.TEST_REPORT_NAME, config.getReportName());
}
arguments.addArgument(APIBackendListenerClient.TEST_ID, testId);
if (StringUtils.isNotBlank(runMode)) {
arguments.addArgument("runMode", runMode);
}
if (StringUtils.isNotBlank(debugReportId)) {
arguments.addArgument("debugReportId", debugReportId);
}
backendListener.setArguments(arguments);
backendListener.setClassname(APIBackendListenerClient.class.getCanonicalName());
testPlan.add(testPlan.getArray()[0], backendListener);
}
public void runDefinition(String testId, HashTree testPlan, String debugReportId, String runMode) {
try {
init();
@ -99,4 +120,16 @@ public class JMeterService {
MSException.throwException(Translator.get("api_load_script_error"));
}
}
public void runSerial(String testId, HashTree testPlan, String debugReportId, String runMode, RunModeConfig config) {
try {
init();
addBackendListener(testId, debugReportId, runMode, testPlan, config);
LocalRunner runner = new LocalRunner(testPlan);
runner.run();
} catch (Exception e) {
LogUtil.error(e.getMessage(), e);
MSException.throwException(Translator.get("api_load_script_error"));
}
}
}

View File

@ -12,6 +12,8 @@ public class TestResult {
private String testId;
private String reportName;
private int success = 0;
private int error = 0;

View File

@ -448,7 +448,7 @@ public class ApiAutomationService {
String referenced = tr.getReferenced();
if (StringUtils.equals(MsTestElementConstants.REF.name(), referenced)) {
if (StringUtils.equals(tr.getType(), "HTTPSamplerProxy")) {
MsHTTPSamplerProxy http = (MsHTTPSamplerProxy)tr;
MsHTTPSamplerProxy http = (MsHTTPSamplerProxy) tr;
String refType = tr.getRefType();
if (StringUtils.equals(refType, "CASE")) {
http.setUrl(null);
@ -668,7 +668,7 @@ public class ApiAutomationService {
}
/**
* 场景测试执行
* 场景测试并行执行
*
* @param request
* @return
@ -737,6 +737,123 @@ public class ApiAutomationService {
return request.getId();
}
/**
* 生成串行HashTree
*
* @param apiScenarios 场景
* @param request 请求参数
* @param reportIds 报告ID
* @return hashTree
*/
private HashTree generateHashTree(List<ApiScenarioWithBLOBs> apiScenarios, RunScenarioRequest request, List<String> reportIds) {
HashTree jmeterHashTree = new ListedHashTree();
MsTestPlan testPlan = new MsTestPlan();
testPlan.setSerializeThreadgroups(true);
testPlan.setHashTree(new LinkedList<>());
try {
boolean isFirst = true;
for (ApiScenarioWithBLOBs item : apiScenarios) {
if (item.getStepTotal() == null || item.getStepTotal() == 0) {
// 只有一个场景且没有测试步骤则提示
if (apiScenarios.size() == 1) {
MSException.throwException((item.getName() + "" + Translator.get("automation_exec_info")));
}
LogUtil.warn(item.getName() + "" + Translator.get("automation_exec_info"));
continue;
}
MsThreadGroup group = new MsThreadGroup();
group.setLabel(item.getName());
group.setName(UUID.randomUUID().toString());
group.setOnSampleError(request.getConfig().isOnSampleError());
// 批量执行的结果直接存储为报告
if (isFirst) {
group.setName(request.getId());
isFirst = false;
}
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
JSONObject element = JSON.parseObject(item.getScenarioDefinition());
MsScenario scenario = JSONObject.parseObject(item.getScenarioDefinition(), MsScenario.class);
// 多态JSON普通转换会丢失内容需要通过 ObjectMapper 获取
if (element != null && StringUtils.isNotEmpty(element.getString("hashTree"))) {
LinkedList<MsTestElement> elements = mapper.readValue(element.getString("hashTree"),
new TypeReference<LinkedList<MsTestElement>>() {
});
scenario.setHashTree(elements);
}
if (StringUtils.isNotEmpty(element.getString("variables"))) {
LinkedList<ScenarioVariable> variables = mapper.readValue(element.getString("variables"),
new TypeReference<LinkedList<ScenarioVariable>>() {
});
scenario.setVariables(variables);
}
group.setEnableCookieShare(scenario.isEnableCookieShare());
LinkedList<MsTestElement> scenarios = new LinkedList<>();
scenarios.add(scenario);
// 创建场景报告
if (reportIds != null) {
//如果是测试计划页面触发的执行方式生成报告时createScenarioReport第二个参数需要特殊处理
if (StringUtils.equals(request.getRunMode(), ApiRunMode.SCENARIO_PLAN.name())) {
String testPlanScenarioId = item.getId();
if (request.getScenarioTestPlanIdMap() != null && request.getScenarioTestPlanIdMap().containsKey(item.getId())) {
testPlanScenarioId = request.getScenarioTestPlanIdMap().get(item.getId());
// 获取场景用例单独的执行环境
TestPlanApiScenario planApiScenario = testPlanApiScenarioMapper.selectByPrimaryKey(testPlanScenarioId);
String environment = planApiScenario.getEnvironment();
if (StringUtils.isNotBlank(environment)) {
scenario.setEnvironmentMap(JSON.parseObject(environment, Map.class));
}
}
APIScenarioReportResult reportResult = createScenarioReport(group.getName(), testPlanScenarioId, item.getName(), request.getTriggerMode() == null ? ReportTriggerMode.MANUAL.name() : request.getTriggerMode(),
request.getExecuteType(), item.getProjectId(), request.getReportUserID());
apiScenarioReportMapper.insert(reportResult);
} else {
APIScenarioReportResult reportResult = createScenarioReport(group.getName(), item.getId(), item.getName(), request.getTriggerMode() == null ? ReportTriggerMode.MANUAL.name() : request.getTriggerMode(),
request.getExecuteType(), item.getProjectId(), request.getReportUserID());
apiScenarioReportMapper.insert(reportResult);
}
reportIds.add(group.getName());
}
group.setHashTree(scenarios);
testPlan.getHashTree().add(group);
}
} catch (Exception ex) {
MSException.throwException(ex.getMessage());
}
testPlan.toHashTree(jmeterHashTree, testPlan.getHashTree(), new ParameterConfig());
return jmeterHashTree;
}
/**
* 场景串行
*
* @param request
* @return
*/
public String runSerial(RunScenarioRequest request) {
ServiceUtils.getSelectAllIds(request, request.getCondition(),
(query) -> extApiScenarioMapper.selectIdsByQuery((ApiScenarioRequest) query));
List<String> ids = request.getIds();
//检查是否有正在执行中的情景
this.checkScenarioIsRunning(ids);
List<ApiScenarioWithBLOBs> apiScenarios = extApiScenarioMapper.selectIds(ids);
String runMode = ApiRunMode.SCENARIO.name();
if (StringUtils.isNotBlank(request.getRunMode()) && StringUtils.equals(request.getRunMode(), ApiRunMode.SCENARIO_PLAN.name())) {
runMode = ApiRunMode.SCENARIO_PLAN.name();
}
if (StringUtils.isNotBlank(request.getRunMode()) && StringUtils.equals(request.getRunMode(), ApiRunMode.DEFINITION.name())) {
runMode = ApiRunMode.DEFINITION.name();
}
// 调用执行方法
List<String> reportIds = new LinkedList<>();
HashTree hashTree = generateHashTree(apiScenarios, request, reportIds);
jMeterService.runSerial(JSON.toJSONString(reportIds), hashTree, request.getReportId(), runMode, request.getConfig());
return request.getId();
}
public void checkScenarioIsRunning(List<String> ids) {
List<ApiScenarioReport> lastReportStatusByIds = apiReportService.selectLastReportByIds(ids);
for (ApiScenarioReport report : lastReportStatusByIds) {

View File

@ -530,7 +530,6 @@ public class ApiDefinitionService {
}
HashTree hashTree = request.getTestElement().generateHashTree(config);
String runMode = ApiRunMode.DEFINITION.name();
if (StringUtils.isNotBlank(request.getType()) && StringUtils.equals(request.getType(), ApiRunMode.API_PLAN.name())) {
runMode = ApiRunMode.API_PLAN.name();

View File

@ -16,6 +16,7 @@ import io.metersphere.base.mapper.ApiScenarioReportMapper;
import io.metersphere.base.mapper.TestPlanApiScenarioMapper;
import io.metersphere.base.mapper.ext.ExtApiScenarioReportMapper;
import io.metersphere.base.mapper.ext.ExtTestPlanScenarioCaseMapper;
import io.metersphere.commons.constants.APITestStatus;
import io.metersphere.commons.constants.ApiRunMode;
import io.metersphere.commons.constants.ReportTriggerMode;
import io.metersphere.commons.exception.MSException;
@ -33,10 +34,7 @@ import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.stream.Collectors;
@Service
@ -59,12 +57,12 @@ public class ApiScenarioReportService {
// 更新场景
if (result != null) {
if (StringUtils.equals(runMode, ApiRunMode.SCENARIO_PLAN.name())) {
return updatePlanCase(result);
return updatePlanCase(result,runMode);
} else if (StringUtils.equals(runMode, ApiRunMode.SCHEDULE_SCENARIO_PLAN.name())) {
return updateSchedulePlanCase(result);
return updateSchedulePlanCase(result,runMode);
} else {
updateScenarioStatus(result.getTestId());
return updateScenario(result);
return updateScenario(result, runMode);
}
}
return null;
@ -92,6 +90,30 @@ public class ApiScenarioReportService {
}
}
public APIScenarioReportResult createScenarioReport(String scenarioIds, String reportName, String status, String scenarioNames, String triggerMode, String projectId, String userID) {
APIScenarioReportResult report = new APIScenarioReportResult();
if (triggerMode.equals(ApiRunMode.SCENARIO.name()) || triggerMode.equals(ApiRunMode.DEFINITION.name())) {
triggerMode = ReportTriggerMode.MANUAL.name();
}
report.setId(UUID.randomUUID().toString());
report.setName(reportName);
report.setCreateTime(System.currentTimeMillis());
report.setUpdateTime(System.currentTimeMillis());
report.setStatus(status);
if (StringUtils.isNotEmpty(userID)) {
report.setUserId(userID);
} else {
report.setUserId(SessionUtils.getUserId());
}
report.setTriggerMode(triggerMode);
report.setExecuteType(ExecuteType.Saved.name());
report.setProjectId(projectId);
report.setScenarioName(scenarioNames);
report.setScenarioId(scenarioIds);
apiScenarioReportMapper.insert(report);
return report;
}
public ApiScenarioReport editReport(ScenarioResult test) {
ApiScenarioReport report = apiScenarioReportMapper.selectByPrimaryKey(test.getName());
report.setId(report.getId());
@ -122,6 +144,17 @@ public class ApiScenarioReportService {
return report;
}
private TestResult createTestResult(TestResult result) {
TestResult testResult = new TestResult();
testResult.setTestId(result.getTestId());
testResult.setTotal(result.getTotal());
testResult.setError(result.getError());
testResult.setPassAssertions(result.getPassAssertions());
testResult.setSuccess(result.getSuccess());
testResult.setTotalAssertions(result.getTotalAssertions());
return testResult;
}
private TestResult createTestResult(String testId, ScenarioResult scenarioResult) {
TestResult testResult = new TestResult();
testResult.setTestId(testId);
@ -133,10 +166,15 @@ public class ApiScenarioReportService {
return testResult;
}
public ApiScenarioReport updatePlanCase(TestResult result) {
public ApiScenarioReport updatePlanCase(TestResult result,String runMode) {
// TestPlanApiScenario testPlanApiScenario = testPlanApiScenarioMapper.selectByPrimaryKey(result.getTestId());
List<ScenarioResult> scenarioResultList = result.getScenarios();
ApiScenarioReport returnReport = null;
StringBuilder scenarioIds = new StringBuilder();
StringBuilder scenarioNames = new StringBuilder();
String projectId = null;
String userId = null;
TestResult fullResult = createTestResult(result);
for (ScenarioResult scenarioResult :
scenarioResultList) {
ApiScenarioReport report = editReport(scenarioResult);
@ -151,6 +189,12 @@ public class ApiScenarioReportService {
detail.setProjectId(report.getProjectId());
apiScenarioReportDetailMapper.insert(detail);
fullResult.addScenario(scenarioResult);
projectId = report.getProjectId();
userId = report.getUserId();
scenarioIds.append(scenarioResult.getName()).append(",");
scenarioNames.append(report.getName()).append(",");
TestPlanApiScenario testPlanApiScenario = testPlanApiScenarioMapper.selectByPrimaryKey(report.getScenarioId());
if (testPlanApiScenario != null) {
report.setScenarioId(testPlanApiScenario.getApiScenarioId());
@ -168,15 +212,20 @@ public class ApiScenarioReportService {
}
returnReport = report;
}
margeReport(result, scenarioIds, scenarioNames, runMode, projectId, userId);
return returnReport;
}
public ApiScenarioReport updateSchedulePlanCase(TestResult result) {
public ApiScenarioReport updateSchedulePlanCase(TestResult result,String runMode) {
ApiScenarioReport lastReport = null;
List<ScenarioResult> scenarioResultList = result.getScenarios();
List<String> testPlanReportIdList = new ArrayList<>();
StringBuilder scenarioIds = new StringBuilder();
StringBuilder scenarioNames = new StringBuilder();
String projectId = null;
String userId = null;
TestResult fullResult = createTestResult(result);
for (ScenarioResult scenarioResult : scenarioResultList) {
// 存储场景报告
ApiScenarioReport report = editReport(scenarioResult);
@ -224,8 +273,16 @@ public class ApiScenarioReportService {
testPlanApiScenario.setUpdateTime(System.currentTimeMillis());
testPlanApiScenarioMapper.updateByPrimaryKeySelective(testPlanApiScenario);
fullResult.addScenario(scenarioResult);
projectId = report.getProjectId();
userId = report.getUserId();
scenarioIds.append(scenarioResult.getName()).append(",");
scenarioNames.append(report.getName()).append(",");
lastReport = report;
}
// 合并报告
margeReport(result, scenarioIds, scenarioNames, runMode, projectId, userId);
TestPlanReportService testPlanReportService = CommonBeanFactory.getBean(TestPlanReportService.class);
testPlanReportService.updateReport(testPlanReportIdList, ApiRunMode.SCHEDULE_SCENARIO_PLAN.name(), ReportTriggerMode.SCHEDULE.name());
@ -266,16 +323,38 @@ public class ApiScenarioReportService {
}
}
public ApiScenarioReport updateScenario(TestResult result) {
private void margeReport(TestResult result, StringBuilder scenarioIds, StringBuilder scenarioNames, String runMode, String projectId, String userId) {
// 合并生成一份报告
if (StringUtils.isNotEmpty(result.getReportName())) {
ApiScenarioReport report = createScenarioReport(scenarioIds.toString(), result.getReportName(), result.getError() > 0 ? "Error" : "Success", scenarioNames.toString().substring(0, scenarioNames.toString().length() - 1), runMode, projectId, userId);
ApiScenarioReportDetail detail = new ApiScenarioReportDetail();
detail.setContent(JSON.toJSONString(result).getBytes(StandardCharsets.UTF_8));
detail.setReportId(report.getId());
detail.setProjectId(report.getProjectId());
apiScenarioReportDetailMapper.insert(detail);
}
}
public ApiScenarioReport updateScenario(TestResult result, String runMode) {
ApiScenarioReport lastReport = null;
StringBuilder scenarioIds = new StringBuilder();
StringBuilder scenarioNames = new StringBuilder();
String projectId = null;
String userId = null;
TestResult fullResult = createTestResult(result);
for (ScenarioResult item : result.getScenarios()) {
// 更新报告状态
ApiScenarioReport report = editReport(item);
// 报告详情内容
ApiScenarioReportDetail detail = new ApiScenarioReportDetail();
TestResult newResult = createTestResult(result.getTestId(), item);
item.setName(report.getScenarioName());
newResult.addScenario(item);
fullResult.addScenario(item);
projectId = report.getProjectId();
userId = report.getUserId();
scenarioIds.append(item.getName()).append(",");
scenarioNames.append(report.getName()).append(",");
// 报告详情内容
ApiScenarioReportDetail detail = new ApiScenarioReportDetail();
detail.setContent(JSON.toJSONString(newResult).getBytes(StandardCharsets.UTF_8));
detail.setReportId(report.getId());
detail.setProjectId(report.getProjectId());
@ -295,6 +374,8 @@ public class ApiScenarioReportService {
}
lastReport = report;
}
// 合并生成一份报告
margeReport(result, scenarioIds, scenarioNames, runMode, projectId, userId);
return lastReport;
}

View File

@ -100,7 +100,7 @@
<select id="list" resultMap="BaseResultMap">
SELECT r.name AS test_name,
r.name, r.description, r.id, r.project_id, r.create_time, r.update_time, r.status, r.trigger_mode,s.name as scenario_name,
r.name, r.description, r.id, r.project_id, r.create_time, r.update_time, r.status, r.trigger_mode,IfNULL(s.name,r.scenario_name) as scenario_name,
project.name AS project_name, user.name AS user_name
FROM api_scenario_report r
LEFT JOIN api_scenario s on r.scenario_id = s.id

View File

@ -0,0 +1,5 @@
package io.metersphere.commons.constants;
public enum ConditionType {
NO, PATH, MODULE
}

View File

@ -92,6 +92,9 @@ public class TestCaseReviewScenarioCaseService {
request.setIds(scenarioIds);
request.setScenarioTestPlanIdMap(scenarioIdApiScarionMap);
request.setRunMode(ApiRunMode.SCENARIO_PLAN.name());
if (request.getConfig() != null && request.getConfig().getMode().equals("serial")) {
return apiAutomationService.runSerial(request);
}
return apiAutomationService.run(request);
}

View File

@ -106,6 +106,9 @@ public class TestPlanScenarioCaseService {
request.setIds(scenarioIds);
request.setScenarioTestPlanIdMap(scenarioIdApiScarionMap);
request.setRunMode(ApiRunMode.SCENARIO_PLAN.name());
if (request.getConfig() != null && request.getConfig().getMode().equals("serial")) {
return apiAutomationService.runSerial(request);
}
return apiAutomationService.run(request);
}

View File

@ -0,0 +1,5 @@
-- api_scenario_report modify column length
ALTER TABLE api_scenario_report MODIFY COLUMN name VARCHAR(3000);
-- api_scenario_report modify column length
ALTER TABLE api_scenario_report MODIFY COLUMN scenario_id VARCHAR(3000);

View File

@ -100,14 +100,12 @@
formatResult(res) {
let resMap = new Map;
let array = [];
let i = 0;
if (res && res.scenarios) {
res.scenarios.forEach(item => {
if (item && item.requestResults) {
item.requestResults.forEach(req => {
resMap.set(req.id, req);
req.index = i;
i++;
req.name = item.name + "^@~@^" + req.name;
array.push(req);
})
}

View File

@ -157,7 +157,7 @@
</batch-edit>
<batch-move @refresh="search" @moveSave="moveSave" ref="testBatchMove"/>
<ms-run-mode @handleRunBatch="handleRunBatch" ref="runMode"/>
</div>
</template>
@ -180,6 +180,8 @@ import BatchEdit from "../../../track/case/components/BatchEdit";
import {API_SCENARIO_LIST, PROJECT_NAME, WORKSPACE_ID} from "../../../../../common/js/constants";
import EnvironmentSelect from "../../definition/components/environment/EnvironmentSelect";
import BatchMove from "../../../track/case/components/BatchMove";
import MsRunMode from "./common/RunMode";
import {
_filter,
_handleSelect,
@ -213,7 +215,8 @@ export default {
MsApiReportDetail,
MsScenarioExtendButtons,
MsTestPlanList,
MsTableOperatorButton
MsTableOperatorButton,
MsRunMode
},
props: {
referenced: {
@ -606,9 +609,13 @@ export default {
param.condition = this.condition;
},
handleBatchExecute() {
this.$refs.runMode.open();
},
handleRunBatch(config){
this.infoDb = false;
let url = "/api/automation/run/batch";
let run = {};
let run = {config: config};
run.id = getUUID();
this.buildBatchParam(run);
this.$post(url, run, response => {

View File

@ -0,0 +1,77 @@
<template>
<el-dialog
destroy-on-close
:title="$t('load_test.runtime_config')"
width="350px"
:visible.sync="runModeVisible"
>
<div>
<span class="ms-mode-span">{{ $t("run_mode.title") }}</span>
<el-radio-group v-model="runConfig.mode">
<el-radio label="serial">{{ $t("run_mode.serial") }}</el-radio>
<el-radio label="parallel">{{ $t("run_mode.parallel") }}</el-radio>
</el-radio-group>
</div>
<div class="ms-mode-div" v-if="runConfig.mode === 'serial'">
<span class="ms-mode-span">{{ $t("run_mode.other_config") }}</span>
<el-radio-group v-model="runConfig.reportType">
<el-radio label="iddReport">{{ $t("run_mode.idd_report") }}</el-radio>
<el-radio label="setReport">{{ $t("run_mode.set_report") }}</el-radio>
</el-radio-group>
</div>
<div class="ms-mode-div" v-if="runConfig.reportType === 'setReport'">
<span class="ms-mode-span">{{ $t("run_mode.report_name") }}</span>
<el-input
v-model="runConfig.reportName"
:placeholder="$t('commons.input_content')"
size="small"
style="width: 200px"
/>
</div>
<template v-slot:footer>
<ms-dialog-footer @cancel="close" @confirm="handleRunBatch"/>
</template>
</el-dialog>
</template>
<script>
import MsDialogFooter from "@/business/components/common/components/MsDialogFooter";
export default {
name: "RunMode",
components: {MsDialogFooter},
data() {
return {
runModeVisible: false,
runConfig: {mode: "serial", reportType: "iddReport", reportName: ""},
};
},
methods: {
open() {
this.runModeVisible = true;
},
close() {
this.runConfig = {mode: "serial", reportType: "iddReport", reportName: ""};
this.runModeVisible = false;
},
handleRunBatch() {
if (this.runConfig.mode === 'serial' && this.runConfig.reportType === 'setReport' && this.runConfig.reportName.trim() === "") {
this.$warning(this.$t('commons.input_name'));
return;
}
this.$emit("handleRunBatch", this.runConfig);
this.close();
},
},
};
</script>
<style scoped>
.ms-mode-span {
margin-right: 10px;
}
.ms-mode-div {
margin-top: 20px;
}
</style>

View File

@ -1,7 +1,7 @@
<template>
<el-dialog :close-on-click-modal="false" :title="$t('api_test.environment.environment_config')"
:visible.sync="visible" class="environment-dialog" width="60%"
@close="close" append-to-body ref="environmentConfig">
@close="close" append-to-body destroy-on-close ref="environmentConfig">
<el-container v-loading="result.loading">
<ms-aside-item :enable-aside-hidden="false" :title="$t('api_test.environment.environment_list')"
:data="environments" :item-operators="environmentOperators" :add-fuc="addEnvironment"

View File

@ -2,9 +2,9 @@
<el-form :model="condition" :rules="rules" ref="httpConfig">
<el-form-item prop="socket">
<span class="ms-env-span">{{$t('api_test.environment.socket')}}</span>
<el-input v-model="httpConfig.socket" style="width: 80%" :placeholder="$t('api_test.request.url_description')" clearable size="small">
<el-input v-model="condition.socket" style="width: 80%" :placeholder="$t('api_test.request.url_description')" clearable size="small">
<template v-slot:prepend>
<el-select v-model="httpConfig.protocol" class="request-protocol-select" size="small">
<el-select v-model="condition.protocol" class="request-protocol-select" size="small">
<el-option label="http://" value="http"/>
<el-option label="https://" value="https"/>
</el-select>
@ -14,15 +14,17 @@
<el-form-item prop="enable">
<span class="ms-env-span">{{$t('api_test.environment.condition_enable')}}</span>
<el-radio-group v-model="condition.type" @change="typeChange">
<el-radio label="no">{{ $t('api_test.definition.document.data_set.none') }}</el-radio>
<el-radio label="module">{{$t('test_track.module.module')}}</el-radio>
<el-radio label="path">{{$t('api_test.definition.api_path')}}</el-radio>
<el-radio label="NO">{{ $t('api_test.definition.document.data_set.none') }}</el-radio>
<el-radio label="MODULE">{{$t('test_track.module.module')}}</el-radio>
<el-radio label="PATH">{{$t('api_test.definition.api_path')}}</el-radio>
</el-radio-group>
<el-button type="primary" style="float: right" size="mini" @click="add">{{$t('commons.add')}}</el-button>
<div v-if="condition.type === 'module'">
<ms-select-tree size="small" :data="moduleOptions" :default-key="condition.value" @getValue="setModule" :obj="moduleObj" clearable checkStrictly multiple/>
<el-button type="primary" v-if="!condition.id" style="float: right" size="mini" @click="add">{{$t('commons.add')}}</el-button>
<el-button type="primary" v-else style="float: right" size="mini" @click="update">{{$t('commons.update')}}</el-button>
<div v-if="condition.type === 'MODULE'">
<ms-select-tree size="small" :data="moduleOptions" :default-key="condition.ids" @getValue="setModule" :obj="moduleObj" clearable checkStrictly multiple/>
</div>
<div v-if="condition.type === 'path'">
<div v-if="condition.type === 'PATH'">
<el-input v-model="pathDetails.name" :placeholder="$t('api_test.value')" clearable size="small">
<template v-slot:prepend>
<el-select v-model="pathDetails.value" class="request-protocol-select" size="small">
@ -33,38 +35,27 @@
</el-input>
</div>
</el-form-item>
<div class="el-form-item">
<el-table :data="httpConfig.conditions" style="width: 100%">
<el-table-column prop="domain" :label="$t('load_test.domain')" width="180">
<div class="ms-border">
<el-table :data="httpConfig.conditions" highlight-current-row @current-change="selectRow">
<el-table-column prop="socket" :label="$t('load_test.domain')" width="180">
<template v-slot:default="{row}">
{{getUrl(row)}}
</template>
</el-table-column>
<el-table-column prop="type"
:label="$t('api_test.environment.condition_enable')"
show-overflow-tooltip
min-width="120px">
<el-table-column prop="type" :label="$t('api_test.environment.condition_enable')" show-overflow-tooltip min-width="120px">
<template v-slot:default="{row}">
{{getName(row)}}
</template>
</el-table-column>
<el-table-column prop="details"
show-overflow-tooltip
min-width="120px"
:label="$t('api_test.value')">
<el-table-column prop="details" show-overflow-tooltip min-width="120px" :label="$t('api_test.value')">
<template v-slot:default="{row}">
{{getDetails(row)}}
</template>
</el-table-column>
<el-table-column :label="$t('commons.operating')" width="100px">
<template v-slot:default="{row}">
<ms-table-operator-button
:tip="$t('api_test.automation.copy')"
icon="el-icon-document-copy"
@exec="copy(row)"/>
<ms-table-operator-button
:tip="$t('api_test.automation.remove')"
icon="el-icon-delete"
@exec="remove(row)"
type="danger"
v-tester/>
<ms-table-operator-button :tip="$t('api_test.automation.copy')" icon="el-icon-document-copy" @exec="copy(row)"/>
<ms-table-operator-button :tip="$t('api_test.automation.remove')" icon="el-icon-delete" @exec="remove(row)" type="danger" v-tester/>
</template>
</el-table-column>
</el-table>
@ -82,6 +73,7 @@
import MsTableOperatorButton from "@/business/components/common/components/MsTableOperatorButton";
import {getUUID} from "@/common/js/utils";
import {KeyValue} from "../../../definition/model/ApiTestModel";
import Vue from "vue";
export default {
name: "MsEnvironmentHttpConfig",
@ -96,79 +88,100 @@
data() {
let socketValidator = (rule, value, callback) => {
if (!this.validateSocket(value)) {
callback(new Error(this.$t('commons.formatErr')));
callback(new Error(this.$t("commons.formatErr")));
return false;
} else {
callback();
return true;
}
}
};
return {
headerSuggestions: REQUEST_HEADERS,
rules: {
socket: [{required: false, validator: socketValidator, trigger: 'blur'}],
socket: [{required: false, validator: socketValidator, trigger: "blur"}],
},
moduleOptions: [],
moduleObj: {
id: 'id',
label: 'name',
id: "id",
label: "name",
},
pathDetails: new KeyValue({name: "", value: "contains"}),
condition: {type: 'no', details: [new KeyValue({name: "", value: "contains"})], domain: ""},
}
condition: {type: "NO", details: [new KeyValue({name: "", value: "contains"})], protocol: "", socket: "", domain: ""},
};
},
watch: {
projectId() {
this.list();
}
},
httpConfig: function (o) {
this.condition.protocol = this.httpConfig.protocol;
this.condition.socket = this.httpConfig.socket;
this.condition.domain = this.httpConfig.domain;
},
},
methods: {
getUrl(row) {
return row.protocol + "://" + row.socket;
},
getName(row) {
switch (row.type) {
case 'no':
return this.$t('api_test.definition.document.data_set.none');
case 'module':
return this.$t('test_track.module.module');
case 'path':
return this.$t('api_test.definition.api_path');
case "NO":
return this.$t("api_test.definition.document.data_set.none");
case "MODULE":
return this.$t("test_track.module.module");
case "PATH":
return this.$t("api_test.definition.api_path");
}
},
getDetails(row) {
if (row && row.type === 'module') {
if (row && row.type === "MODULE") {
if (row.details && row.details instanceof Array) {
let value = "";
row.details.forEach(item => {
row.details.forEach((item) => {
value += item.name + ",";
})
});
if (value.endsWith(",")) {
value = value.substr(0, value.length - 1);
}
return value;
}
} else if (row && row.type === 'path' && row.details.length > 0 && row.details[0].name) {
return row.details[0].value === 'equals' ? this.$t('commons.adv_search.operators.equals')
: this.$t('api_test.request.assertions.contains') + "/" + row.details[0].name;
}
else {
} else if (row && row.type === "PATH" && row.details.length > 0 && row.details[0].name) {
return row.details[0].value === "equals" ? this.$t("commons.adv_search.operators.equals") : this.$t("api_test.request.assertions.contains") + row.details[0].name;
} else {
return "";
}
},
selectRow(row) {
if (row) {
this.httpConfig.socket = row.socket;
this.httpConfig.protocol = row.protocol;
this.condition = row;
if (row.type === "PATH" && row.details.length > 0) {
this.pathDetails = row.details[0];
} else if (row.type === "MODULE" && row.details.length > 0) {
this.condition.ids = [];
row.details.forEach((item) => {
this.condition.ids.push(item.value);
});
}
}
},
typeChange() {
switch (this.condition.type) {
case 'no':
case "NO":
this.condition.details = [];
break;
case 'module':
case "MODULE":
this.condition.details = [];
break;
case 'path':
case "PATH":
this.pathDetails = new KeyValue({name: "", value: "contains"});
break;
}
},
list() {
let url = "/api/automation/module/list/" + this.projectId;
this.result = this.$get(url, response => {
this.result = this.$get(url, (response) => {
if (response.data !== undefined && response.data !== null) {
this.moduleOptions = response.data;
}
@ -177,14 +190,22 @@
setModule(id, data) {
if (data && data.length > 0) {
this.condition.details = [];
data.forEach(item => {
data.forEach((item) => {
this.condition.details.push(new KeyValue({name: item.name, value: item.id}));
})
});
}
},
update() {
const index = this.httpConfig.conditions.findIndex((d) => d.id === this.condition.id);
let obj = {id: this.condition.id, type: this.condition.type, domain: this.httpConfig.domain, socket: this.httpConfig.socket, protocol: this.httpConfig.protocol, details: this.condition.details};
if (index !== -1) {
Vue.set(this.httpConfig.conditions[index], obj, 1);
this.condition = {type: "NO", details: [new KeyValue({name: "", value: "contains"})], protocol: "", socket: "", domain: ""};
}
},
add() {
let obj = {id: getUUID(), type: this.condition.type, domain: this.httpConfig.socket};
if (this.condition.type === 'path') {
let obj = {id: getUUID(), type: this.condition.type, socket: this.httpConfig.socket, protocol: this.httpConfig.protocol, domain: this.httpConfig.domain,};
if (this.condition.type === "PATH") {
obj.details = [JSON.parse(JSON.stringify(this.pathDetails))];
} else {
obj.details = this.condition.details ? JSON.parse(JSON.stringify(this.condition.details)) : this.condition.details;
@ -192,12 +213,12 @@
this.httpConfig.conditions.push(obj);
},
remove(row) {
const index = this.httpConfig.conditions.findIndex(d => d.id === row.id);
const index = this.httpConfig.conditions.findIndex((d) => d.id === row.id);
this.httpConfig.conditions.splice(index, 1);
},
copy(row) {
const index = this.httpConfig.conditions.findIndex(d => d.id === row.id);
let obj = {id: getUUID(), type: row.type, domain: row.domain, details: row.details};
const index = this.httpConfig.conditions.findIndex((d) => d.id === row.id);
let obj = {id: getUUID(), type: row.type, socket: row.socket, details: row.details, protocol: row.protocol, domain: row.domain,};
if (index != -1) {
this.httpConfig.conditions.splice(index + 1, 0, obj);
} else {
@ -206,7 +227,7 @@
},
validateSocket(socket) {
if (!socket) return true;
let urlStr = this.httpConfig.protocol + '://' + socket;
let urlStr = this.httpConfig.protocol + "://" + socket;
let url = {};
try {
url = new URL(urlStr);
@ -216,9 +237,9 @@
this.httpConfig.domain = decodeURIComponent(url.hostname);
this.httpConfig.port = url.port;
let path = url.pathname === '/' ? '' : url.pathname;
let path = url.pathname === "/" ? "" : url.pathname;
if (url.port) {
this.httpConfig.socket = this.httpConfig.domain + ':' + url.port + path;
this.httpConfig.socket = this.httpConfig.domain + ":" + url.port + path;
} else {
this.httpConfig.socket = this.httpConfig.domain + path;
}
@ -226,17 +247,16 @@
},
validate() {
let isValidate = false;
this.$refs['httpConfig'].validate((valid) => {
this.$refs["httpConfig"].validate((valid) => {
isValidate = valid;
});
return isValidate;
}
}
}
},
},
};
</script>
<style scoped>
.request-protocol-select {
width: 90px;
}

View File

@ -10,7 +10,6 @@ export class Environment extends BaseConfig {
this.name = undefined;
this.id = undefined;
this.config = undefined;
this.set(options);
this.sets({}, options);
}
@ -63,7 +62,6 @@ export class CommonConfig extends BaseConfig {
export class HttpConfig extends BaseConfig {
constructor(options = {}) {
super();
this.socket = undefined;
this.domain = undefined;
this.headers = [];
@ -72,6 +70,7 @@ export class HttpConfig extends BaseConfig {
this.conditions = [];
this.set(options);
this.sets({headers: KeyValue}, options);
this.sets({conditions: KeyValue}, options);
}
initOptions(options = {}) {

View File

@ -0,0 +1,61 @@
<template>
<el-dialog
destroy-on-close
:title="$t('load_test.runtime_config')"
width="350px"
:visible.sync="runModeVisible"
>
<div>
<span class="ms-mode-span">{{ $t("run_mode.title") }}</span>
<el-radio-group v-model="runConfig.mode">
<el-radio label="serial">{{ $t("run_mode.serial") }}</el-radio>
<el-radio label="parallel">{{ $t("run_mode.parallel") }}</el-radio>
</el-radio-group>
</div>
<div class="ms-mode-div" v-if="runConfig.mode === 'serial'">
<span class="ms-mode-span">{{ $t("run_mode.other_config") }}</span>
<el-checkbox v-model="runConfig.onSampleError">失败停止</el-checkbox>
</div>
<template v-slot:footer>
<ms-dialog-footer @cancel="close" @confirm="handleRunBatch"/>
</template>
</el-dialog>
</template>
<script>
import MsDialogFooter from "@/business/components/common/components/MsDialogFooter";
export default {
name: "MsPlanRunMode",
components: {MsDialogFooter},
data() {
return {
runModeVisible: false,
runConfig: {mode: "serial", reportType: "iddReport", onSampleError: false},
};
},
methods: {
open() {
this.runModeVisible = true;
},
close() {
this.runConfig = {mode: "serial", reportType: "iddReport", onSampleError: false};
this.runModeVisible = false;
},
handleRunBatch() {
this.$emit("handleRunBatch", this.runConfig);
this.close();
},
},
};
</script>
<style scoped>
.ms-mode-span {
margin-right: 10px;
}
.ms-mode-div {
margin-top: 20px;
}
</style>

View File

@ -142,6 +142,7 @@
<batch-edit :dialog-title="$t('test_track.case.batch_edit_case')" :type-arr="typeArr" :value-arr="valueArr"
:select-row="selectRows" ref="batchEdit" @batchEdit="batchEdit"/>
<ms-plan-run-mode @handleRunBatch="handleRunBatch" ref="runMode"/>
</el-card>
</div>
@ -187,6 +188,7 @@ import HeaderCustom from "@/business/components/common/head/HeaderCustom";
import {Test_Plan_Api_Case} from "@/business/components/common/model/JsonData";
import HeaderLabelOperate from "@/business/components/common/head/HeaderLabelOperate";
import MsTableHeaderSelectPopover from "@/business/components/common/components/table/MsTableHeaderSelectPopover";
import MsPlanRunMode from "../../../common/PlanRunMode";
export default {
@ -209,7 +211,8 @@ export default {
MsContainer,
MsBottomContainer,
ShowMoreBtn,
MsTableHeaderSelectPopover
MsTableHeaderSelectPopover,
MsPlanRunMode
},
data() {
return {
@ -461,22 +464,22 @@ export default {
});
}
}
}
});
},
getResult(data) {
if (RESULT_MAP.get(data)) {
return RESULT_MAP.get(data);
} else {
return RESULT_MAP.get("default");
}
});
},
getResult(data) {
if (RESULT_MAP.get(data)) {
return RESULT_MAP.get(data);
} else {
return RESULT_MAP.get("default");
}
},
runRefresh(data) {
this.rowLoading = "";
this.$success(this.$t('schedule.event_success'));
this.initTable();
},
},
runRefresh(data) {
this.rowLoading = "";
this.$success(this.$t('schedule.event_success'));
this.initTable();
},
singleRun(row) {
this.runData = [];
@ -498,6 +501,26 @@ export default {
this.$refs.batchEdit.open(this.selectRows.size);
this.$refs.batchEdit.setSelectRows(this.selectRows);
},
getData() {
return new Promise((resolve) => {
let index = 1;
this.runData = [];
this.selectRows.forEach(row => {
this.$get('/api/testcase/get/' + row.caseId, (response) => {
let apiCase = response.data;
let request = JSON.parse(apiCase.request);
request.name = row.id;
request.id = row.id;
request.useEnvironment = row.environmentId;
this.runData.push(request);
if (this.selectRows.size === index) {
resolve();
}
index++;
});
});
});
},
batchEdit(form) {
let param = {};
//
@ -537,58 +560,44 @@ export default {
}
},
handleBatchExecute() {
if(this.condition != null && this.condition.selectAll){
this.$alert(this.$t('commons.option_cannot_spread_pages'), '', {
confirmButtonText: this.$t('commons.confirm'),
callback: (action) => {
if (action === 'confirm') {
this.selectRows.forEach(row => {
this.$get('/api/testcase/get/' + row.caseId, (response) => {
let apiCase = response.data;
let request = JSON.parse(apiCase.request);
request.name = row.id;
request.id = row.id;
request.useEnvironment = row.environmentId;
let runData = [];
runData.push(request);
this.batchRun(runData, getUUID().substring(0, 8));
});
});
this.$message('任务执行中,请稍后刷新查看结果');
this.search();
}
}
});
}else {
this.selectRows.forEach(row => {
this.$get('/api/testcase/get/' + row.caseId, (response) => {
let apiCase = response.data;
let request = JSON.parse(apiCase.request);
request.name = row.id;
request.id = row.id;
request.useEnvironment = row.environmentId;
let runData = [];
runData.push(request);
this.batchRun(runData, getUUID().substring(0, 8));
});
});
this.$message('任务执行中,请稍后刷新查看结果');
this.search();
}
this.getData().then(() => {
if (this.runData && this.runData.length > 0) {
this.$refs.runMode.open();
}
});
},
batchRun(runData, reportId) {
handleRunBatch(config) {
let testPlan = new TestPlan();
let projectId = this.$store.state.projectId;
let threadGroup = new ThreadGroup();
threadGroup.hashTree = [];
testPlan.hashTree = [threadGroup];
runData.forEach(item => {
threadGroup.hashTree.push(item);
});
let reqObj = {id: reportId, testElement: testPlan, type: 'API_PLAN', reportId: "run", projectId: projectId};
let bodyFiles = getBodyUploadFiles(reqObj, runData);
this.$fileUpload("/api/definition/run", null, bodyFiles, reqObj, response => {
});
if (config.mode === 'serial') {
testPlan.serializeThreadgroups = true;
testPlan.hashTree = [];
this.runData.forEach(item => {
let threadGroup = new ThreadGroup();
threadGroup.onSampleError = config.onSampleError;
threadGroup.hashTree = [];
threadGroup.hashTree.push(item);
testPlan.hashTree.push(threadGroup);
});
let reqObj = {id: getUUID().substring(0, 8), testElement: testPlan, type: 'API_PLAN', reportId: "run", projectId: projectId};
let bodyFiles = getBodyUploadFiles(reqObj, this.runData);
this.$fileUpload("/api/definition/run", null, bodyFiles, reqObj, response => {
});
} else {
testPlan.serializeThreadgroups = false;
let threadGroup = new ThreadGroup();
threadGroup.hashTree = [];
testPlan.hashTree = [threadGroup];
this.runData.forEach(item => {
threadGroup.hashTree.push(item);
});
let reqObj = {id: getUUID().substring(0, 8), testElement: testPlan, type: 'API_PLAN', reportId: "run", projectId: projectId};
let bodyFiles = getBodyUploadFiles(reqObj, this.runData);
this.$fileUpload("/api/definition/run", null, bodyFiles, reqObj, response => {
});
}
this.search();
this.$message('任务执行中,请稍后刷新查看结果');
},
autoCheckStatus() { //
if (!this.planId) {

View File

@ -101,7 +101,7 @@
<!-- 批量编辑 -->
<batch-edit :dialog-title="$t('test_track.case.batch_edit_case')" :type-arr="typeArr" :value-arr="valueArr"
:select-row="selectRows" ref="batchEdit" @batchEdit="batchEdit"/>
<ms-plan-run-mode @handleRunBatch="handleRunBatch" ref="runMode"/>
</div>
</template>
@ -135,6 +135,7 @@ import {TEST_CASE_LIST, TEST_PLAN_SCENARIO_CASE} from "@/common/js/constants";
import {Test_Plan_Scenario_Case, Track_Test_Case} from "@/business/components/common/model/JsonData";
import HeaderLabelOperate from "@/business/components/common/head/HeaderLabelOperate";
import BatchEdit from "@/business/components/track/case/components/BatchEdit";
import MsPlanRunMode from "../../../common/PlanRunMode";
import MsTableHeaderSelectPopover from "@/business/components/common/components/table/MsTableHeaderSelectPopover";
export default {
@ -153,6 +154,7 @@ export default {
MsScenarioExtendButtons,
MsTestPlanList,
BatchEdit,
MsPlanRunMode,
MsTableHeaderSelectPopover
},
props: {
@ -292,49 +294,32 @@ export default {
})
},
handleBatchExecute() {
// if (this.reviewId) {} By.Song Tianyang
// if (this.reviewId) {
// this.selectRows.forEach(row => {
// let param = this.buildExecuteParam(row);
// this.$post("/test/case/review/scenario/case/run", param, response => {
// });
// });
// }
if(this.condition != null && this.condition.selectAll){
this.$alert(this.$t('commons.option_cannot_spread_pages'), '', {
confirmButtonText: this.$t('commons.confirm'),
callback: (action) => {
if (action === 'confirm') {
if (this.planId) {
this.selectRows.forEach(row => {
let param = this.buildExecuteParam(row);
this.$post("/test/plan/scenario/case/run", param, response => {
});
});
}
this.$message('任务执行中,请稍后刷新查看结果');
this.search();
}
}
this.$refs.runMode.open();
},
handleRunBatch(config){
if (this.reviewId) {
let param = {config : config,planCaseIds:[]};
this.selectRows.forEach(row => {
this.buildExecuteParam(param,row);
});
}else {
if (this.planId) {
this.selectRows.forEach(row => {
let param = this.buildExecuteParam(row);
this.$post("/test/plan/scenario/case/run", param, response => {
});
});
}
this.$message('任务执行中,请稍后刷新查看结果');
this.search();
this.$post("/test/case/review/scenario/case/run", param, response => {});
}
if (this.planId) {
let param = {config : config,planCaseIds:[]};
this.selectRows.forEach(row => {
this.buildExecuteParam(param,row);
});
console.log(param)
this.$post("/test/plan/scenario/case/run", param, response => {});
}
this.$message('任务执行中,请稍后刷新查看结果');
this.search();
},
execute(row) {
this.infoDb = false;
let param = this.buildExecuteParam(row);
console.log(param)
let param ={planCaseIds: []};
this.buildExecuteParam(param,row);
if (this.planId) {
this.$post("/test/plan/scenario/case/run", param, response => {
this.runVisible = true;
@ -348,14 +333,11 @@ export default {
});
}
},
buildExecuteParam(row) {
let param = {};
buildExecuteParam(param,row) {
// param.id = row.id;
param.id = getUUID();
param.planScenarioId = row.id;
console.log(row.id)
param.projectId = row.projectId;
param.planCaseIds = [];
param.planCaseIds.push(row.id);
return param;
},

View File

@ -1677,5 +1677,14 @@ export default {
header_display_field: 'Header display field',
fields_to_be_selected: 'Fields to be selected',
selected_fields: 'Selected fields'
},
run_mode: {
title: "Mode",
serial: "Serial",
parallel: "Parallel",
other_config: "Other config",
idd_report: "Report",
set_report: "Set report",
report_name: "Report name",
}
};

View File

@ -1680,5 +1680,14 @@ export default {
header_display_field: '表头显示字段',
fields_to_be_selected: '待选字段',
selected_fields: '已选字段'
},
run_mode: {
title: "模式",
serial: "串行",
parallel: "并行",
other_config: "其他配置",
idd_report: "独立报告",
set_report: "集合报告",
report_name: "报告名称",
}
};

View File

@ -1678,6 +1678,14 @@ export default {
header_display_field: '表頭顯示欄位',
fields_to_be_selected: '待選欄位',
selected_fields: '已選欄位'
},
run_mode: {
title: "模式",
serial: "串行",
parallel: "並行",
other_config: "其他配置",
idd_report: "獨立報告",
set_report: "集合報告",
report_name: "報告名稱",
}
};