Merge branch 'v1.8' into pr@master@fix_合并1.8
# Conflicts: # frontend/src/business/components/api/automation/report/ApiReportDetail.vue # frontend/src/business/components/api/automation/report/components/RequestResult.vue # frontend/src/business/components/api/definition/components/case/ApiCaseList.vue # frontend/src/business/components/api/definition/components/list/ApiCaseSimpleList.vue # frontend/src/business/components/common/components/MsModuleMinder.vue # frontend/src/business/components/track/review/view/components/TestReviewTestCaseList.vue
This commit is contained in:
commit
58593b4337
|
@ -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,7 +140,7 @@ 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());
|
||||
|
@ -174,7 +175,7 @@ public class ApiAutomationController {
|
|||
}
|
||||
|
||||
@PostMapping("/relevance/review")
|
||||
public void testCaseReviewRelevance(@RequestBody ApiCaseRelevanceRequest request){
|
||||
public void testCaseReviewRelevance(@RequestBody ApiCaseRelevanceRequest request) {
|
||||
apiAutomationService.relevanceReview(request);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
package io.metersphere.api.dto;
|
||||
|
||||
import io.metersphere.api.dto.automation.RunModeConfig;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class RunRequest {
|
||||
private String testId;
|
||||
private String runMode;
|
||||
private String jmx;
|
||||
private RunModeConfig config;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -76,8 +76,12 @@ public class MsIfController extends MsTestElement {
|
|||
public String getCondition() {
|
||||
String variable = "\"" + this.variable + "\"";
|
||||
String operator = this.operator;
|
||||
String value = "\"" + this.value + "\"";
|
||||
|
||||
String value;
|
||||
if (StringUtils.equals(operator, "<") || StringUtils.equals(operator, ">")) {
|
||||
value = this.value;
|
||||
} else {
|
||||
value = "\"" + this.value + "\"";
|
||||
}
|
||||
if (StringUtils.contains(operator, "~")) {
|
||||
value = "\".*" + this.value + ".*\"";
|
||||
}
|
||||
|
|
|
@ -56,7 +56,7 @@ public class MsLoopController extends MsTestElement {
|
|||
}
|
||||
final HashTree groupTree = controller(tree);
|
||||
if (CollectionUtils.isNotEmpty(config.getVariables())) {
|
||||
this.addCsvDataSet(groupTree, config.getVariables(),config);
|
||||
this.addCsvDataSet(groupTree, config.getVariables(), config);
|
||||
this.addCounter(groupTree, config.getVariables());
|
||||
this.addRandom(groupTree, config.getVariables());
|
||||
}
|
||||
|
@ -111,7 +111,12 @@ public class MsLoopController extends MsTestElement {
|
|||
private String getCondition() {
|
||||
String variable = "\"" + this.whileController.getVariable() + "\"";
|
||||
String operator = this.whileController.getOperator();
|
||||
String value = "\"" + this.whileController.getValue() + "\"";
|
||||
String value;
|
||||
if (StringUtils.equals(operator, "<") || StringUtils.equals(operator, ">")) {
|
||||
value = this.whileController.getValue();
|
||||
} else {
|
||||
value = "\"" + this.whileController.getValue() + "\"";
|
||||
}
|
||||
|
||||
if (StringUtils.contains(operator, "~")) {
|
||||
value = "\".*" + this.whileController.getValue() + ".*\"";
|
||||
|
|
|
@ -7,8 +7,18 @@ import io.metersphere.api.dto.definition.request.ParameterConfig;
|
|||
import io.metersphere.api.dto.definition.request.auth.MsAuthManager;
|
||||
import io.metersphere.api.dto.definition.request.dns.MsDNSCacheManager;
|
||||
import io.metersphere.api.dto.scenario.Body;
|
||||
import io.metersphere.api.dto.scenario.HttpConfig;
|
||||
import io.metersphere.api.dto.scenario.HttpConfigCondition;
|
||||
import io.metersphere.api.dto.scenario.KeyValue;
|
||||
import io.metersphere.api.service.ApiDefinitionService;
|
||||
import io.metersphere.api.service.ApiModuleService;
|
||||
import io.metersphere.api.service.ApiTestEnvironmentService;
|
||||
import io.metersphere.base.domain.ApiDefinition;
|
||||
import io.metersphere.base.domain.ApiTestEnvironmentWithBLOBs;
|
||||
import io.metersphere.commons.constants.ConditionType;
|
||||
import io.metersphere.commons.constants.MsTestElementConstants;
|
||||
import io.metersphere.commons.exception.MSException;
|
||||
import io.metersphere.commons.utils.CommonBeanFactory;
|
||||
import io.metersphere.commons.utils.LogUtil;
|
||||
import io.metersphere.commons.utils.ScriptEngineUtils;
|
||||
import lombok.Data;
|
||||
|
@ -128,7 +138,7 @@ public class MsHTTPSamplerProxy extends MsTestElement {
|
|||
}
|
||||
|
||||
// 数据兼容处理
|
||||
if(config.getConfig() != null && StringUtils.isNotEmpty(this.getProjectId()) && config.getConfig().containsKey(this.getProjectId())){
|
||||
if (config.getConfig() != null && StringUtils.isNotEmpty(this.getProjectId()) && config.getConfig().containsKey(this.getProjectId())) {
|
||||
// 1.8 之后 当前正常数据
|
||||
} else if (config.getConfig() != null && config.getConfig().containsKey(getParentProjectId())) {
|
||||
// 1.8 前后 混合数据
|
||||
|
@ -155,12 +165,15 @@ public class MsHTTPSamplerProxy extends MsTestElement {
|
|||
}
|
||||
try {
|
||||
if (config.isEffective(this.getProjectId())) {
|
||||
String url = config.getConfig().get(this.getProjectId()).getHttpConfig().getProtocol() + "://" + config.getConfig().get(this.getProjectId()).getHttpConfig().getSocket();
|
||||
HttpConfig httpConfig = getHttpConfig(config.getConfig().get(this.getProjectId()).getHttpConfig());
|
||||
if (httpConfig == null) {
|
||||
MSException.throwException("未匹配到环境,请检查环境配置");
|
||||
}
|
||||
String url = httpConfig.getProtocol() + "://" + httpConfig.getSocket();
|
||||
// 补充如果是完整URL 则用自身URL
|
||||
boolean isUrl = false;
|
||||
if (StringUtils.isNotEmpty(this.getUrl()) && isURL(this.getUrl())) {
|
||||
boolean isUrl;
|
||||
if (isUrl = (StringUtils.isNotEmpty(this.getUrl()) && isURL(this.getUrl()))) {
|
||||
url = this.getUrl();
|
||||
isUrl = true;
|
||||
}
|
||||
if (isUrl) {
|
||||
if (StringUtils.isNotEmpty(this.getPort()) && this.getPort().startsWith("${")) {
|
||||
|
@ -177,15 +190,15 @@ public class MsHTTPSamplerProxy extends MsTestElement {
|
|||
sampler.setProtocol(urlObject.getProtocol());
|
||||
sampler.setPath(urlObject.getPath());
|
||||
} else {
|
||||
sampler.setDomain(config.getConfig().get(this.getProjectId()).getHttpConfig().getDomain());
|
||||
url = config.getConfig().get(this.getProjectId()).getHttpConfig().getProtocol() + "://" + config.getConfig().get(this.getProjectId()).getHttpConfig().getSocket();
|
||||
sampler.setDomain(httpConfig.getDomain());
|
||||
url = httpConfig.getProtocol() + "://" + httpConfig.getSocket();
|
||||
URL urlObject = new URL(url);
|
||||
String envPath = StringUtils.equals(urlObject.getPath(), "/") ? "" : urlObject.getPath();
|
||||
if (StringUtils.isNotBlank(this.getPath())) {
|
||||
envPath += this.getPath();
|
||||
}
|
||||
sampler.setPort(config.getConfig().get(this.getProjectId()).getHttpConfig().getPort());
|
||||
sampler.setProtocol(config.getConfig().get(this.getProjectId()).getHttpConfig().getProtocol());
|
||||
sampler.setPort(httpConfig.getPort());
|
||||
sampler.setProtocol(httpConfig.getProtocol());
|
||||
sampler.setPath(envPath);
|
||||
}
|
||||
String envPath = sampler.getPath();
|
||||
|
@ -233,14 +246,7 @@ public class MsHTTPSamplerProxy extends MsTestElement {
|
|||
}
|
||||
} catch (Exception e) {
|
||||
LogUtil.error(e);
|
||||
}
|
||||
// REST参数
|
||||
if (CollectionUtils.isNotEmpty(this.getRest())) {
|
||||
sampler.setArguments(httpArguments(this.getRest()));
|
||||
}
|
||||
// 请求参数
|
||||
if (CollectionUtils.isNotEmpty(this.getArguments())) {
|
||||
sampler.setArguments(httpArguments(this.getArguments()));
|
||||
MSException.throwException(e.getMessage());
|
||||
}
|
||||
// 请求体
|
||||
if (!StringUtils.equals(this.getMethod(), "GET")) {
|
||||
|
@ -298,7 +304,7 @@ public class MsHTTPSamplerProxy extends MsTestElement {
|
|||
|
||||
private String getParentProjectId() {
|
||||
MsTestElement parent = this.getParent();
|
||||
while(parent != null) {
|
||||
while (parent != null) {
|
||||
if (StringUtils.isNotBlank(parent.getProjectId())) {
|
||||
return parent.getProjectId();
|
||||
}
|
||||
|
@ -378,7 +384,7 @@ public class MsHTTPSamplerProxy extends MsTestElement {
|
|||
}
|
||||
}
|
||||
|
||||
public boolean isURL(String str) {
|
||||
private boolean isURL(String str) {
|
||||
try {
|
||||
new URL(str);
|
||||
return true;
|
||||
|
@ -391,6 +397,37 @@ public class MsHTTPSamplerProxy extends MsTestElement {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 按照环境规则匹配环境
|
||||
*
|
||||
* @param httpConfig
|
||||
* @return
|
||||
*/
|
||||
private HttpConfig getHttpConfig(HttpConfig httpConfig) {
|
||||
if (CollectionUtils.isNotEmpty(httpConfig.getConditions())) {
|
||||
for (HttpConfigCondition item : httpConfig.getConditions()) {
|
||||
if (item.getType().equals(ConditionType.NONE.name())) {
|
||||
return httpConfig.initHttpConfig(item);
|
||||
} else if (item.getType().equals(ConditionType.PATH.name())) {
|
||||
HttpConfig config = httpConfig.getPathCondition(this.getPath());
|
||||
if (config != null) {
|
||||
return config;
|
||||
}
|
||||
} else if (item.getType().equals(ConditionType.MODULE.name())) {
|
||||
ApiDefinitionService apiDefinitionService = CommonBeanFactory.getBean(ApiDefinitionService.class);
|
||||
ApiDefinition apiDefinition = apiDefinitionService.get(this.getId());
|
||||
if (apiDefinition != null) {
|
||||
HttpConfig config = httpConfig.getModuleCondition(apiDefinition.getModuleId());
|
||||
if (config != null) {
|
||||
return config;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return httpConfig;
|
||||
}
|
||||
|
||||
private boolean isRest() {
|
||||
return this.getRest().stream().filter(KeyValue::isEnable).filter(KeyValue::isValid).toArray().length > 0;
|
||||
}
|
||||
|
|
|
@ -1,14 +1,59 @@
|
|||
package io.metersphere.api.dto.scenario;
|
||||
|
||||
import io.metersphere.commons.constants.ConditionType;
|
||||
import io.metersphere.commons.utils.BeanUtils;
|
||||
import lombok.Data;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.beans.Beans;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Data
|
||||
public class HttpConfig {
|
||||
private String socket;
|
||||
private String domain;
|
||||
private String protocol = "https";
|
||||
private String defaultCondition;
|
||||
private int port;
|
||||
private List<HttpConfigCondition> conditions;
|
||||
private List<KeyValue> headers;
|
||||
|
||||
|
||||
public boolean isNode(String type) {
|
||||
return StringUtils.equals(defaultCondition, type);
|
||||
}
|
||||
|
||||
public HttpConfig initHttpConfig(HttpConfigCondition configCondition) {
|
||||
HttpConfig config = new HttpConfig();
|
||||
BeanUtils.copyBean(config, configCondition);
|
||||
return config;
|
||||
}
|
||||
|
||||
public HttpConfig getPathCondition(String path) {
|
||||
List<HttpConfigCondition> conditions = this.getConditions().stream().filter(condition -> ConditionType.PATH.name().equals(condition.getType()) && CollectionUtils.isNotEmpty(condition.getDetails())).collect(Collectors.toList());
|
||||
if (CollectionUtils.isNotEmpty(conditions)) {
|
||||
for (HttpConfigCondition item : conditions) {
|
||||
List<KeyValue> details = item.getDetails().stream().filter(detail -> (detail.getValue().equals("contains") && StringUtils.contains(path, detail.getName())) || (detail.getValue().equals("equals") && StringUtils.equals(path, detail.getName()))).collect(Collectors.toList());
|
||||
if (CollectionUtils.isNotEmpty(details)) {
|
||||
return initHttpConfig(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public HttpConfig getModuleCondition(String moduleId) {
|
||||
List<HttpConfigCondition> conditions = this.getConditions().stream().filter(condition -> ConditionType.MODULE.name().equals(condition.getType()) && CollectionUtils.isNotEmpty(condition.getDetails())).collect(Collectors.toList());
|
||||
if (CollectionUtils.isNotEmpty(conditions)) {
|
||||
for (HttpConfigCondition item : conditions) {
|
||||
List<KeyValue> details = item.getDetails().stream().filter(detail -> StringUtils.contains(detail.getValue(), moduleId)).collect(Collectors.toList());
|
||||
if (CollectionUtils.isNotEmpty(details)) {
|
||||
return initHttpConfig(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
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;
|
||||
private int port;
|
||||
}
|
|
@ -38,6 +38,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 = "-";
|
||||
|
@ -66,6 +68,8 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl
|
|||
private String testId;
|
||||
|
||||
private String debugReportId;
|
||||
// 只有合并报告是这个有值
|
||||
private String reportName;
|
||||
|
||||
//获得控制台内容
|
||||
private PrintStream oldPrintStream = System.out;
|
||||
|
@ -132,7 +136,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 -> {
|
||||
|
@ -216,7 +220,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);
|
||||
|
@ -426,6 +430,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)) {
|
||||
|
|
|
@ -1,30 +1,64 @@
|
|||
package io.metersphere.api.jmeter;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import io.metersphere.api.dto.RunRequest;
|
||||
import io.metersphere.api.dto.automation.RunModeConfig;
|
||||
import io.metersphere.api.dto.definition.request.MsTestPlan;
|
||||
import io.metersphere.api.dto.scenario.request.BodyFile;
|
||||
import io.metersphere.base.domain.JarConfig;
|
||||
import io.metersphere.base.domain.TestResource;
|
||||
import io.metersphere.commons.constants.ApiRunMode;
|
||||
import io.metersphere.commons.exception.MSException;
|
||||
import io.metersphere.commons.utils.CommonBeanFactory;
|
||||
import io.metersphere.commons.utils.LogUtil;
|
||||
import io.metersphere.commons.utils.UrlTestUtils;
|
||||
import io.metersphere.config.JmeterProperties;
|
||||
import io.metersphere.dto.BaseSystemConfigDTO;
|
||||
import io.metersphere.dto.NodeDTO;
|
||||
import io.metersphere.i18n.Translator;
|
||||
import io.metersphere.service.JarConfigService;
|
||||
import io.metersphere.service.SystemParameterService;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.jmeter.config.Arguments;
|
||||
import org.apache.jmeter.config.CSVDataSet;
|
||||
import org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy;
|
||||
import org.apache.jmeter.protocol.http.util.HTTPFileArg;
|
||||
import org.apache.jmeter.save.SaveService;
|
||||
import org.apache.jmeter.util.JMeterUtils;
|
||||
import org.apache.jmeter.visualizers.backend.BackendListener;
|
||||
import org.apache.jorphan.collections.HashTree;
|
||||
import org.springframework.context.i18n.LocaleContextHolder;
|
||||
import org.springframework.core.io.ByteArrayResource;
|
||||
import org.springframework.core.io.FileSystemResource;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.Resource;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class JMeterService {
|
||||
|
||||
private static final String BASE_URL = "http://%s:%d";
|
||||
@Resource
|
||||
private JmeterProperties jmeterProperties;
|
||||
@Resource
|
||||
ResourcePoolCalculation resourcePoolCalculation;
|
||||
@Resource
|
||||
private RestTemplate restTemplate;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
|
@ -88,6 +122,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 +152,172 @@ 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"));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前jmx 涉及到的文件
|
||||
*
|
||||
* @param tree
|
||||
*/
|
||||
public void getFiles(HashTree tree, List<BodyFile> files) {
|
||||
for (Object key : tree.keySet()) {
|
||||
HashTree node = tree.get(key);
|
||||
if (key instanceof HTTPSamplerProxy) {
|
||||
HTTPSamplerProxy source = (HTTPSamplerProxy) key;
|
||||
if (source != null && source.getHTTPFiles().length > 0) {
|
||||
for (HTTPFileArg arg : source.getHTTPFiles()) {
|
||||
BodyFile file = new BodyFile();
|
||||
file.setId(arg.getParamName());
|
||||
file.setName(arg.getPath());
|
||||
files.add(file);
|
||||
}
|
||||
}
|
||||
} else if (key instanceof CSVDataSet) {
|
||||
CSVDataSet source = (CSVDataSet) key;
|
||||
if (source != null && source.getFilename() != null) {
|
||||
BodyFile file = new BodyFile();
|
||||
file.setId(source.getFilename());
|
||||
file.setName(source.getFilename());
|
||||
files.add(file);
|
||||
}
|
||||
}
|
||||
if (node != null) {
|
||||
getFiles(node, files);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] fileToByte(File tradeFile) {
|
||||
byte[] buffer = null;
|
||||
try (FileInputStream fis = new FileInputStream(tradeFile);
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();) {
|
||||
byte[] b = new byte[1024];
|
||||
int n;
|
||||
while ((n = fis.read(b)) != -1) {
|
||||
bos.write(b, 0, n);
|
||||
}
|
||||
buffer = bos.toByteArray();
|
||||
} catch (Exception e) {
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
private List<Object> getJar() {
|
||||
List<Object> jarFiles = new LinkedList<>();
|
||||
// jar 包
|
||||
JarConfigService jarConfigService = CommonBeanFactory.getBean(JarConfigService.class);
|
||||
List<JarConfig> jars = jarConfigService.list();
|
||||
jars.forEach(jarConfig -> {
|
||||
try {
|
||||
String path = jarConfig.getPath();
|
||||
File file = new File(path);
|
||||
if (file.isDirectory() && !path.endsWith("/")) {
|
||||
file = new File(path + "/");
|
||||
}
|
||||
FileSystemResource resource = new FileSystemResource(file);
|
||||
ByteArrayResource byteArrayResource = new ByteArrayResource(this.fileToByte(file)) {
|
||||
@Override
|
||||
public String getFilename() throws IllegalStateException {
|
||||
return resource.getFilename();
|
||||
}
|
||||
};
|
||||
jarFiles.add(byteArrayResource);
|
||||
|
||||
} catch (Exception e) {
|
||||
LogUtil.error(e.getMessage(), e);
|
||||
}
|
||||
});
|
||||
return jarFiles;
|
||||
}
|
||||
|
||||
private List<Object> getMultipartFiles(HashTree hashTree) {
|
||||
List<Object> multipartFiles = new LinkedList<>();
|
||||
// 获取附件
|
||||
List<BodyFile> files = new LinkedList<>();
|
||||
getFiles(hashTree, files);
|
||||
if (CollectionUtils.isNotEmpty(files)) {
|
||||
for (BodyFile bodyFile : files) {
|
||||
File file = new File(bodyFile.getName());
|
||||
if (file != null && !file.exists()) {
|
||||
FileSystemResource resource = new FileSystemResource(file);
|
||||
ByteArrayResource byteArrayResource = new ByteArrayResource(this.fileToByte(file)) {
|
||||
@Override
|
||||
public String getFilename() throws IllegalStateException {
|
||||
return resource.getFilename();
|
||||
}
|
||||
};
|
||||
multipartFiles.add(byteArrayResource);
|
||||
}
|
||||
}
|
||||
}
|
||||
return multipartFiles;
|
||||
}
|
||||
|
||||
public void runTest(String testId, HashTree hashTree, String runMode, RunModeConfig config) {
|
||||
// 获取JMX使用到的附件
|
||||
List<Object> multipartFiles = getMultipartFiles(hashTree);
|
||||
// 获取JAR
|
||||
List<Object> jarFiles = getJar();
|
||||
|
||||
// 获取可以执行的资源池
|
||||
TestResource testResource = resourcePoolCalculation.getPool();
|
||||
|
||||
String configuration = testResource.getConfiguration();
|
||||
NodeDTO node = JSON.parseObject(configuration, NodeDTO.class);
|
||||
String nodeIp = node.getIp();
|
||||
Integer port = node.getPort();
|
||||
|
||||
BaseSystemConfigDTO baseInfo = CommonBeanFactory.getBean(SystemParameterService.class).getBaseInfo();
|
||||
// 占位符
|
||||
String metersphereUrl = "http://localhost:8081";
|
||||
if (baseInfo != null) {
|
||||
metersphereUrl = baseInfo.getUrl();
|
||||
}
|
||||
// 检查≈地址是否正确
|
||||
String jmeterPingUrl = metersphereUrl + "/jmeter/ping";
|
||||
// docker 不能从 localhost 中下载文件
|
||||
if (StringUtils.contains(metersphereUrl, "http://localhost")
|
||||
|| !UrlTestUtils.testUrlWithTimeOut(jmeterPingUrl, 1000)) {
|
||||
MSException.throwException(Translator.get("run_load_test_file_init_error"));
|
||||
}
|
||||
|
||||
String uri = String.format(BASE_URL + "/jmeter/api/run", nodeIp, port);
|
||||
try {
|
||||
RunRequest runRequest = new RunRequest();
|
||||
runRequest.setTestId(testId);
|
||||
runRequest.setRunMode(runMode);
|
||||
runRequest.setConfig(config);
|
||||
runRequest.setJmx(new MsTestPlan().getJmx(hashTree));
|
||||
MultiValueMap<String, Object> postParameters = new LinkedMultiValueMap<>();
|
||||
postParameters.put("files", multipartFiles);
|
||||
postParameters.put("jarFiles", jarFiles);
|
||||
postParameters.add("request", JSON.toJSONString(runRequest));
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
|
||||
headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN));
|
||||
HttpEntity<MultiValueMap<String, Object>> request = new HttpEntity<>(postParameters, headers);
|
||||
|
||||
String result = restTemplate.postForObject(uri, request, String.class);
|
||||
if (result == null) {
|
||||
MSException.throwException(Translator.get("start_engine_fail"));
|
||||
}
|
||||
} catch (MSException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
MSException.throwException("Please check node-controller status.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package io.metersphere.api.jmeter;
|
|||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import io.github.ningyu.jmeter.plugin.dubbo.sample.DubboSample;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.jmeter.extractor.JSR223PostProcessor;
|
||||
import org.apache.jmeter.extractor.RegexExtractor;
|
||||
import org.apache.jmeter.extractor.XPath2Extractor;
|
||||
|
@ -31,32 +32,36 @@ public class JMeterVars {
|
|||
* @param vars
|
||||
* @param extract
|
||||
*/
|
||||
public static void addVars(Integer testId, JMeterVariables vars, String extract) {
|
||||
JMeterVariables vs = new JMeterVariables();
|
||||
public static void addVars(Integer testId, JMeterVariables vars, String extract) {
|
||||
JMeterVariables vs = variables.get(testId);
|
||||
if (vs == null) {
|
||||
vs = new JMeterVariables();
|
||||
}
|
||||
if (!StringUtils.isEmpty(extract) && vars != null) {
|
||||
List<String> extracts = Arrays.asList(extract.split(";"));
|
||||
Optional.ofNullable(extracts).orElse(new ArrayList<>()).forEach(item -> {
|
||||
|
||||
String nrKey = item + "_matchNr";
|
||||
Object nr = vars.get(nrKey);
|
||||
if (nr != null) {
|
||||
int nrv = 0;
|
||||
try {
|
||||
nrv = Integer.valueOf(String.valueOf(nr));
|
||||
} catch (Exception e) {
|
||||
}
|
||||
if (nrv > 0) {
|
||||
List<Object> data = new ArrayList<>();
|
||||
for (int i = 1; i < nrv + 1; i++) {
|
||||
data.add(vars.get(item + "_" + i));
|
||||
if (CollectionUtils.isNotEmpty(extracts)) {
|
||||
for (String item : extracts) {
|
||||
String nrKey = item + "_matchNr";
|
||||
Object nr = vars.get(nrKey);
|
||||
if (nr != null) {
|
||||
int nrv = 0;
|
||||
try {
|
||||
nrv = Integer.valueOf(String.valueOf(nr));
|
||||
} catch (Exception e) {
|
||||
}
|
||||
if (nrv > 0) {
|
||||
List<Object> data = new ArrayList<>();
|
||||
for (int i = 1; i < nrv + 1; i++) {
|
||||
data.add(vars.get(item + "_" + i));
|
||||
}
|
||||
String array = JSON.toJSONString(data);
|
||||
vars.put(item, array);
|
||||
}
|
||||
String array = JSON.toJSONString(data);
|
||||
vars.put(item, array);
|
||||
}
|
||||
vs.put(item, vars.get(item) == null ? "" : vars.get(item));
|
||||
}
|
||||
vs.put(item, vars.get(item) == null ? "" : vars.get(item));
|
||||
});
|
||||
vs.remove("TESTSTART.MS"); // 标示变量移除
|
||||
vs.remove("TESTSTART.MS"); // 标示变量移除
|
||||
}
|
||||
}
|
||||
|
||||
variables.put(testId, vs);
|
||||
|
|
|
@ -0,0 +1,213 @@
|
|||
package io.metersphere.api.jmeter;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.metersphere.api.service.*;
|
||||
import io.metersphere.base.domain.ApiDefinitionExecResult;
|
||||
import io.metersphere.base.domain.ApiScenarioReport;
|
||||
import io.metersphere.base.domain.ApiTestReport;
|
||||
import io.metersphere.commons.constants.*;
|
||||
import io.metersphere.commons.utils.CommonBeanFactory;
|
||||
import io.metersphere.commons.utils.LogUtil;
|
||||
import io.metersphere.dto.BaseSystemConfigDTO;
|
||||
import io.metersphere.i18n.Translator;
|
||||
import io.metersphere.notice.sender.NoticeModel;
|
||||
import io.metersphere.notice.service.NoticeSendService;
|
||||
import io.metersphere.service.SystemParameterService;
|
||||
import io.metersphere.track.service.TestPlanReportService;
|
||||
import io.metersphere.track.service.TestPlanTestCaseService;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.kafka.clients.consumer.ConsumerRecord;
|
||||
import org.springframework.kafka.annotation.KafkaListener;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Service
|
||||
public class MsKafkaListener {
|
||||
public static final String TOPICS = "ms-api-exec-topic";
|
||||
|
||||
@KafkaListener(topics = TOPICS)
|
||||
public void consume(ConsumerRecord<?, String> record) {
|
||||
LogUtil.info("接收到执行结果开始存储");
|
||||
this.save(record.value());
|
||||
LogUtil.info("执行内容存储结束");
|
||||
}
|
||||
|
||||
@Resource
|
||||
private APITestService apiTestService;
|
||||
@Resource
|
||||
private APIReportService apiReportService;
|
||||
@Resource
|
||||
private ApiDefinitionService apiDefinitionService;
|
||||
@Resource
|
||||
private ApiDefinitionExecResultService apiDefinitionExecResultService;
|
||||
@Resource
|
||||
private TestPlanReportService testPlanReportService;
|
||||
@Resource
|
||||
private ApiScenarioReportService apiScenarioReportService;
|
||||
|
||||
private TestResult formatResult(String result) {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
try {
|
||||
// 多态JSON普通转换会丢失内容,需要通过 ObjectMapper 获取
|
||||
if (StringUtils.isNotEmpty(result)) {
|
||||
TestResult element = mapper.readValue(result, new TypeReference<TestResult>() {
|
||||
});
|
||||
return element;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
LogUtil.error(e.getMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void save(String execResult) {
|
||||
TestResult testResult = this.formatResult(execResult);
|
||||
ApiTestReport report = null;
|
||||
String reportUrl = null;
|
||||
// 这部分后续优化只留 DEFINITION 和 SCENARIO 两部分
|
||||
if (StringUtils.equals(testResult.getRunMode(), ApiRunMode.DEFINITION.name())) {
|
||||
// 调试操作,不需要存储结果
|
||||
apiDefinitionService.addResult(testResult);
|
||||
if (!testResult.isDebug()) {
|
||||
apiDefinitionExecResultService.saveApiResult(testResult, ApiRunMode.DEFINITION.name());
|
||||
}
|
||||
} else if (StringUtils.equals(testResult.getRunMode(), ApiRunMode.JENKINS.name())) {
|
||||
apiDefinitionService.addResult(testResult);
|
||||
apiDefinitionExecResultService.saveApiResult(testResult, ApiRunMode.DEFINITION.name());
|
||||
|
||||
} else if (StringUtils.equals(testResult.getRunMode(), ApiRunMode.JENKINS_API_PLAN.name())) {
|
||||
apiDefinitionService.addResult(testResult);
|
||||
apiDefinitionExecResultService.saveApiResult(testResult, ApiRunMode.API_PLAN.name());
|
||||
ApiDefinitionExecResult result = apiDefinitionService.getResultByJenkins(testResult.getTestId(), ApiRunMode.API_PLAN.name());
|
||||
if (result != null) {
|
||||
report = new ApiTestReport();
|
||||
report.setStatus(result.getStatus());
|
||||
report.setId(result.getId());
|
||||
report.setTriggerMode(ApiRunMode.API.name());
|
||||
report.setName(apiDefinitionService.getApiCaseInfo(testResult.getTestId()).getName());
|
||||
}
|
||||
} else if (StringUtils.equalsAny(testResult.getRunMode(), ApiRunMode.API_PLAN.name(), ApiRunMode.SCHEDULE_API_PLAN.name())) {
|
||||
apiDefinitionService.addResult(testResult);
|
||||
//测试计划定时任务-接口执行逻辑的话,需要同步测试计划的报告数据
|
||||
if (StringUtils.equalsAny(testResult.getRunMode(), ApiRunMode.SCHEDULE_API_PLAN.name(), ApiRunMode.JENKINS_API_PLAN.name())) {
|
||||
apiDefinitionExecResultService.saveApiResultByScheduleTask(testResult, ApiRunMode.SCHEDULE_API_PLAN.name());
|
||||
List<String> testPlanReportIdList = new ArrayList<>();
|
||||
testPlanReportIdList.add(testResult.getTestId());
|
||||
// 更新每个测试计划的状态
|
||||
for (String testPlanReportId : testPlanReportIdList) {
|
||||
testPlanReportService.checkTestPlanStatus(testPlanReportId);
|
||||
}
|
||||
testPlanReportService.updateReport(testPlanReportIdList, ApiRunMode.SCHEDULE_API_PLAN.name(), ReportTriggerMode.SCHEDULE.name());
|
||||
} else {
|
||||
apiDefinitionExecResultService.saveApiResult(testResult, ApiRunMode.API_PLAN.name());
|
||||
}
|
||||
} else if (StringUtils.equalsAny(testResult.getRunMode(), ApiRunMode.SCENARIO.name(), ApiRunMode.SCENARIO_PLAN.name(), ApiRunMode.SCHEDULE_SCENARIO_PLAN.name(), ApiRunMode.SCHEDULE_SCENARIO.name())) {
|
||||
// 执行报告不需要存储,由用户确认后在存储
|
||||
testResult.setTestId(testResult.getTestId());
|
||||
ApiScenarioReport scenarioReport = apiScenarioReportService.complete(testResult, testResult.getRunMode());
|
||||
|
||||
report = new ApiTestReport();
|
||||
report.setStatus(scenarioReport.getStatus());
|
||||
report.setId(scenarioReport.getId());
|
||||
report.setTriggerMode(scenarioReport.getTriggerMode());
|
||||
report.setName(scenarioReport.getName());
|
||||
|
||||
SystemParameterService systemParameterService = CommonBeanFactory.getBean(SystemParameterService.class);
|
||||
assert systemParameterService != null;
|
||||
BaseSystemConfigDTO baseSystemConfigDTO = systemParameterService.getBaseInfo();
|
||||
reportUrl = baseSystemConfigDTO.getUrl() + "/#/api/automation/report";
|
||||
|
||||
testResult.setTestId(scenarioReport.getScenarioId());
|
||||
} else {
|
||||
apiTestService.changeStatus(testResult.getTestId(), APITestStatus.Completed);
|
||||
report = apiReportService.getRunningReport(testResult.getTestId());
|
||||
apiReportService.complete(testResult, report);
|
||||
}
|
||||
|
||||
TestPlanTestCaseService testPlanTestCaseService = CommonBeanFactory.getBean(TestPlanTestCaseService.class);
|
||||
List<String> ids = testPlanTestCaseService.getTestPlanTestCaseIds(testResult.getTestId());
|
||||
if (ids.size() > 0) {
|
||||
try {
|
||||
if (StringUtils.equals(APITestStatus.Success.name(), report.getStatus())) {
|
||||
testPlanTestCaseService.updateTestCaseStates(ids, TestPlanTestCaseStatus.Pass.name());
|
||||
} else {
|
||||
testPlanTestCaseService.updateTestCaseStates(ids, TestPlanTestCaseStatus.Failure.name());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
|
||||
}
|
||||
}
|
||||
if (report != null && StringUtils.equals(ReportTriggerMode.API.name(), report.getTriggerMode()) || StringUtils.equals(ReportTriggerMode.SCHEDULE.name(), report.getTriggerMode())) {
|
||||
sendTask(report, reportUrl, testResult);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static void sendTask(ApiTestReport report, String reportUrl, TestResult testResult) {
|
||||
if (report == null) {
|
||||
return;
|
||||
}
|
||||
SystemParameterService systemParameterService = CommonBeanFactory.getBean(SystemParameterService.class);
|
||||
NoticeSendService noticeSendService = CommonBeanFactory.getBean(NoticeSendService.class);
|
||||
assert systemParameterService != null;
|
||||
assert noticeSendService != null;
|
||||
BaseSystemConfigDTO baseSystemConfigDTO = systemParameterService.getBaseInfo();
|
||||
String url = baseSystemConfigDTO.getUrl() + "/#/api/report/view/" + report.getId();
|
||||
String url2 = baseSystemConfigDTO.getUrl() + "/#/api/automation/report/view/" + report.getId();
|
||||
|
||||
String successContext = "";
|
||||
String failedContext = "";
|
||||
String subject = "";
|
||||
String event = "";
|
||||
if (StringUtils.equals(ReportTriggerMode.API.name(), report.getTriggerMode())) {
|
||||
successContext = "接口测试 API任务通知:'" + report.getName() + "'执行成功" + "\n" + "【接口定义暂无报告链接】" + "\n" + "请点击下面链接进入测试报告页面" + "\n" + "(旧版)接口测)路径" + url + "\n" + "(新版)接口测试路径" + url2;
|
||||
failedContext = "接口测试 API任务通知:'" + report.getName() + "'执行失败" + "\n" + "【接口定义暂无报告链接】" + "\n" + "请点击下面链接进入测试报告页面" + "\n" + "(旧版)接口测试路径" + url + "\n" + "(新版)接口测试路径" + url2;
|
||||
subject = Translator.get("task_notification_jenkins");
|
||||
}
|
||||
if (StringUtils.equals(ReportTriggerMode.SCHEDULE.name(), report.getTriggerMode())) {
|
||||
successContext = "接口测试定时任务通知:'" + report.getName() + "'执行成功" + "\n" + "【接口定义暂无报告链接】" + "\n" + "请点击下面链接进入测试报告页面" + "\n" + "(旧版)接口测试路径" + url + "\n" + "(新版)接口测试路径" + url2;
|
||||
failedContext = "接口测试定时任务通知:'" + report.getName() + "'执行失败" + "\n" + "【接口定义暂无报告链接】" + "\n" + "请点击下面链接进入测试报告页面" + "\n" + "(旧版)接口测试路径" + url + "\n" + "(新版)接口测试路径" + url2;
|
||||
subject = Translator.get("task_notification");
|
||||
}
|
||||
if (StringUtils.equals("Success", report.getStatus())) {
|
||||
event = NoticeConstants.Event.EXECUTE_SUCCESSFUL;
|
||||
}
|
||||
if (StringUtils.equals("success", report.getStatus())) {
|
||||
event = NoticeConstants.Event.EXECUTE_SUCCESSFUL;
|
||||
}
|
||||
if (StringUtils.equals("Error", report.getStatus())) {
|
||||
event = NoticeConstants.Event.EXECUTE_FAILED;
|
||||
}
|
||||
if (StringUtils.equals("error", report.getStatus())) {
|
||||
event = NoticeConstants.Event.EXECUTE_FAILED;
|
||||
}
|
||||
Map<String, Object> paramMap = new HashMap<>();
|
||||
paramMap.put("testName", report.getName());
|
||||
paramMap.put("id", report.getId());
|
||||
paramMap.put("type", "api");
|
||||
paramMap.put("url", baseSystemConfigDTO.getUrl());
|
||||
paramMap.put("status", report.getStatus());
|
||||
NoticeModel noticeModel = NoticeModel.builder()
|
||||
.successContext(successContext)
|
||||
.successMailTemplate("ApiSuccessfulNotification")
|
||||
.failedContext(failedContext)
|
||||
.failedMailTemplate("ApiFailedNotification")
|
||||
.testId(testResult.getTestId())
|
||||
.status(report.getStatus())
|
||||
.event(event)
|
||||
.subject(subject)
|
||||
.paramMap(paramMap)
|
||||
.build();
|
||||
noticeSendService.send(report.getTriggerMode(), noticeModel);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
package io.metersphere.api.jmeter;
|
||||
|
||||
import io.metersphere.base.domain.TestResource;
|
||||
import io.metersphere.base.domain.TestResourceExample;
|
||||
import io.metersphere.base.domain.TestResourcePool;
|
||||
import io.metersphere.base.domain.TestResourcePoolExample;
|
||||
import io.metersphere.base.mapper.TestResourceMapper;
|
||||
import io.metersphere.base.mapper.TestResourcePoolMapper;
|
||||
import io.metersphere.commons.exception.MSException;
|
||||
import io.metersphere.i18n.Translator;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
public class ResourcePoolCalculation {
|
||||
@Resource
|
||||
TestResourcePoolMapper testResourcePoolMapper;
|
||||
@Resource
|
||||
TestResourceMapper testResourceMapper;
|
||||
|
||||
public TestResource getPool() {
|
||||
// 获取可以执行的资源池
|
||||
TestResourcePoolExample example = new TestResourcePoolExample();
|
||||
example.createCriteria().andStatusEqualTo("VALID").andTypeEqualTo("NODE").andNameEqualTo("赵勇资源池");
|
||||
List<TestResourcePool> pools = testResourcePoolMapper.selectByExample(example);
|
||||
// 暂时随机获取一个正常状态NODE
|
||||
TestResource testResource = null;
|
||||
if (CollectionUtils.isNotEmpty(pools)) {
|
||||
List<String> poolIds = pools.stream().map(pool -> pool.getId()).collect(Collectors.toList());
|
||||
TestResourceExample resourceExample = new TestResourceExample();
|
||||
resourceExample.createCriteria().andTestResourcePoolIdIn(poolIds);
|
||||
List<TestResource> testResources = testResourceMapper.selectByExampleWithBLOBs(resourceExample);
|
||||
int index = (int) (Math.random() * testResources.size());
|
||||
testResource = testResources.get(index);
|
||||
}
|
||||
if (testResource == null) {
|
||||
MSException.throwException(Translator.get("run_load_test_file_init_error"));
|
||||
}
|
||||
return testResource;
|
||||
}
|
||||
}
|
|
@ -1,17 +1,23 @@
|
|||
package io.metersphere.api.jmeter;
|
||||
|
||||
import io.metersphere.commons.utils.BeanUtils;
|
||||
import lombok.Data;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class TestResult {
|
||||
|
||||
private String testId;
|
||||
|
||||
private String reportName;
|
||||
|
||||
private boolean isDebug;
|
||||
|
||||
private String runMode;
|
||||
|
||||
private int success = 0;
|
||||
|
||||
private int error = 0;
|
||||
|
|
|
@ -387,13 +387,14 @@ public class ApiAutomationService {
|
|||
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
try {
|
||||
JSONObject element = JSON.parseObject(definition);
|
||||
List<MsTestElement> hashTree = mapper.readValue(element.getString("hashTree"), new TypeReference<LinkedList<MsTestElement>>(){});
|
||||
List<MsTestElement> hashTree = mapper.readValue(element.getString("hashTree"), new TypeReference<LinkedList<MsTestElement>>() {
|
||||
});
|
||||
for (int i = 0; i < hashTree.size(); i++) {
|
||||
MsTestElement tr = hashTree.get(i);
|
||||
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);
|
||||
|
@ -421,14 +422,15 @@ public class ApiAutomationService {
|
|||
env.getProjectIds().add(apiScenario.getProjectId());
|
||||
String scenarioDefinition = apiScenario.getScenarioDefinition();
|
||||
JSONObject element1 = JSON.parseObject(scenarioDefinition);
|
||||
LinkedList<MsTestElement> hashTree1 = mapper.readValue(element1.getString("hashTree"), new TypeReference<LinkedList<MsTestElement>>(){});
|
||||
LinkedList<MsTestElement> hashTree1 = mapper.readValue(element1.getString("hashTree"), new TypeReference<LinkedList<MsTestElement>>() {
|
||||
});
|
||||
tr.setHashTree(hashTree1);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (StringUtils.equals(tr.getType(), "HTTPSamplerProxy")) {
|
||||
// 校验是否是全路径
|
||||
MsHTTPSamplerProxy httpSamplerProxy = (MsHTTPSamplerProxy)tr;
|
||||
MsHTTPSamplerProxy httpSamplerProxy = (MsHTTPSamplerProxy) tr;
|
||||
if (httpSamplerProxy.isEnable()) {
|
||||
if (StringUtils.isBlank(httpSamplerProxy.getUrl()) || !isURL(httpSamplerProxy.getUrl())) {
|
||||
env.getProjectIds().add(httpSamplerProxy.getProjectId());
|
||||
|
@ -464,7 +466,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);
|
||||
|
@ -486,20 +488,21 @@ public class ApiAutomationService {
|
|||
ApiDefinition apiDefinition = apiDefinitionService.get(tr.getId());
|
||||
env.getProjectIds().add(apiDefinition.getProjectId());
|
||||
}
|
||||
} else if (StringUtils.equals(tr.getType(), "scenario")) {
|
||||
} else if (StringUtils.equals(tr.getType(), "scenario")) {
|
||||
if (tr.isEnable()) {
|
||||
ApiScenarioWithBLOBs apiScenario = getApiScenario(tr.getId());
|
||||
env.getProjectIds().add(apiScenario.getProjectId());
|
||||
String scenarioDefinition = apiScenario.getScenarioDefinition();
|
||||
JSONObject element1 = JSON.parseObject(scenarioDefinition);
|
||||
LinkedList<MsTestElement> hashTree1 = mapper.readValue(element1.getString("hashTree"), new TypeReference<LinkedList<MsTestElement>>(){});
|
||||
LinkedList<MsTestElement> hashTree1 = mapper.readValue(element1.getString("hashTree"), new TypeReference<LinkedList<MsTestElement>>() {
|
||||
});
|
||||
tr.setHashTree(hashTree1);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (StringUtils.equals(tr.getType(), "HTTPSamplerProxy")) {
|
||||
// 校验是否是全路径
|
||||
MsHTTPSamplerProxy httpSamplerProxy = (MsHTTPSamplerProxy)tr;
|
||||
MsHTTPSamplerProxy httpSamplerProxy = (MsHTTPSamplerProxy) tr;
|
||||
if (httpSamplerProxy.isEnable()) {
|
||||
if (StringUtils.isBlank(httpSamplerProxy.getUrl()) || !isURL(httpSamplerProxy.getUrl())) {
|
||||
env.setFullUrl(false);
|
||||
|
@ -549,7 +552,7 @@ public class ApiAutomationService {
|
|||
}
|
||||
|
||||
public byte[] loadFileAsBytes(FileOperationRequest fileOperationRequest) {
|
||||
File file = new File(FileUtils.BODY_FILE_DIR + fileOperationRequest.getId() + "_" + fileOperationRequest.getName());
|
||||
File file = new File(FileUtils.BODY_FILE_DIR + "/" + fileOperationRequest.getId() + "_" + fileOperationRequest.getName());
|
||||
try (FileInputStream fis = new FileInputStream(file);
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream(1000);) {
|
||||
byte[] b = new byte[1000];
|
||||
|
@ -589,6 +592,7 @@ public class ApiAutomationService {
|
|||
report.setProjectId(projectId);
|
||||
report.setScenarioName(scenarioName);
|
||||
report.setScenarioId(scenarioId);
|
||||
apiScenarioReportMapper.insert(report);
|
||||
return report;
|
||||
}
|
||||
|
||||
|
@ -684,12 +688,13 @@ public class ApiAutomationService {
|
|||
}
|
||||
|
||||
/**
|
||||
* 场景测试执行
|
||||
* 场景测试并行执行
|
||||
* 这种方法性能有问题 2021/04/12
|
||||
*
|
||||
* @param request
|
||||
* @return
|
||||
*/
|
||||
public String run(RunScenarioRequest request) {
|
||||
public String abandonedRun(RunScenarioRequest request) {
|
||||
ServiceUtils.getSelectAllIds(request, request.getCondition(),
|
||||
(query) -> extApiScenarioMapper.selectIdsByQuery((ApiScenarioRequest) query));
|
||||
|
||||
|
@ -753,6 +758,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(request.getConfig() != null && request.getConfig().getMode().equals("serial"));
|
||||
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());
|
||||
if (request.getConfig() != null) {
|
||||
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));
|
||||
}
|
||||
}
|
||||
createScenarioReport(group.getName(), testPlanScenarioId, item.getName(), request.getTriggerMode() == null ? ReportTriggerMode.MANUAL.name() : request.getTriggerMode(),
|
||||
request.getExecuteType(), item.getProjectId(), request.getReportUserID());
|
||||
} else {
|
||||
createScenarioReport(group.getName(), item.getId(), item.getName(), request.getTriggerMode() == null ? ReportTriggerMode.MANUAL.name() : request.getTriggerMode(),
|
||||
request.getExecuteType(), item.getProjectId(), request.getReportUserID());
|
||||
}
|
||||
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 run(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) {
|
||||
|
@ -813,9 +935,10 @@ public class ApiAutomationService {
|
|||
MSException.throwException(e.getMessage());
|
||||
}
|
||||
// 调用执行方法
|
||||
APIScenarioReportResult reportResult = createScenarioReport(request.getId(), request.getScenarioId(), request.getScenarioName(), ReportTriggerMode.MANUAL.name(), request.getExecuteType(), request.getProjectId(),
|
||||
createScenarioReport(request.getId(), request.getScenarioId(), request.getScenarioName(), ReportTriggerMode.MANUAL.name(), request.getExecuteType(), request.getProjectId(),
|
||||
SessionUtils.getUserId());
|
||||
apiScenarioReportMapper.insert(reportResult);
|
||||
// 调用执行方法
|
||||
// jMeterService.runTest(request.getId(), hashTree, ApiRunMode.SCENARIO.name(), null);
|
||||
// 调用执行方法
|
||||
jMeterService.runDefinition(request.getId(), hashTree, request.getReportId(), ApiRunMode.SCENARIO.name());
|
||||
return request.getId();
|
||||
|
@ -994,9 +1117,9 @@ public class ApiAutomationService {
|
|||
if (StringUtils.equals(request.getGroup(), ScheduleGroup.TEST_PLAN_TEST.name())) {
|
||||
scheduleService.addOrUpdateCronJob(
|
||||
request, TestPlanTestJob.getJobKey(request.getResourceId()), TestPlanTestJob.getTriggerKey(request.getResourceId()), TestPlanTestJob.class);
|
||||
}else if(StringUtils.equals(request.getGroup(), ScheduleGroup.SWAGGER_IMPORT.name())){
|
||||
} else if (StringUtils.equals(request.getGroup(), ScheduleGroup.SWAGGER_IMPORT.name())) {
|
||||
scheduleService.addOrUpdateCronJob(request, SwaggerUrlImportJob.getJobKey(request.getResourceId()), SwaggerUrlImportJob.getTriggerKey(request.getResourceId()), SwaggerUrlImportJob.class);
|
||||
} else{
|
||||
} else {
|
||||
scheduleService.addOrUpdateCronJob(
|
||||
request, ApiScenarioTestJob.getJobKey(request.getResourceId()), ApiScenarioTestJob.getTriggerKey(request.getResourceId()), ApiScenarioTestJob.class);
|
||||
}
|
||||
|
@ -1248,7 +1371,7 @@ public class ApiAutomationService {
|
|||
*/
|
||||
Map<String, List<String>> urlMap = new HashMap<>();
|
||||
List<String> allApiIdList = new ArrayList<>();
|
||||
Map<String,List<String>> caseIdMap = new HashMap<>();
|
||||
Map<String, List<String>> caseIdMap = new HashMap<>();
|
||||
for (ApiDefinition model : allEffectiveApiList) {
|
||||
String url = model.getPath();
|
||||
String id = model.getId();
|
||||
|
@ -1261,7 +1384,7 @@ public class ApiAutomationService {
|
|||
urlMap.put(url, list);
|
||||
}
|
||||
}
|
||||
for (ApiTestCase model : allEffectiveApiCaseList){
|
||||
for (ApiTestCase model : allEffectiveApiCaseList) {
|
||||
String caseId = model.getId();
|
||||
String apiId = model.getApiDefinitionId();
|
||||
if (urlMap.containsKey(caseId)) {
|
||||
|
@ -1278,20 +1401,20 @@ public class ApiAutomationService {
|
|||
}
|
||||
|
||||
List<String> urlList = new ArrayList<>();
|
||||
List<String> idList= new ArrayList<>();
|
||||
List<String> idList = new ArrayList<>();
|
||||
|
||||
for (ApiScenarioWithBLOBs model : allScenarioInfoList) {
|
||||
String scenarioDefiniton = model.getScenarioDefinition();
|
||||
this.addUrlAndIdToList(scenarioDefiniton,urlList,idList);
|
||||
this.addUrlAndIdToList(scenarioDefiniton, urlList, idList);
|
||||
}
|
||||
|
||||
List<String> containsApiIdList = new ArrayList<>();
|
||||
|
||||
for (String url : urlList) {
|
||||
List<String> apiIdList = urlMap.get(url);
|
||||
if(apiIdList!=null ){
|
||||
if (apiIdList != null) {
|
||||
for (String api : apiIdList) {
|
||||
if(!containsApiIdList.contains(api)){
|
||||
if (!containsApiIdList.contains(api)) {
|
||||
containsApiIdList.add(api);
|
||||
}
|
||||
}
|
||||
|
@ -1300,16 +1423,16 @@ public class ApiAutomationService {
|
|||
|
||||
for (String id : idList) {
|
||||
List<String> apiIdList = caseIdMap.get(id);
|
||||
if(apiIdList!=null ){
|
||||
if (apiIdList != null) {
|
||||
for (String api : apiIdList) {
|
||||
if(!containsApiIdList.contains(api)){
|
||||
if (!containsApiIdList.contains(api)) {
|
||||
containsApiIdList.add(api);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(allApiIdList.contains(id)){
|
||||
if(!containsApiIdList.contains(id)){
|
||||
if (allApiIdList.contains(id)) {
|
||||
if (!containsApiIdList.contains(id)) {
|
||||
containsApiIdList.add(id);
|
||||
}
|
||||
}
|
||||
|
@ -1322,25 +1445,25 @@ public class ApiAutomationService {
|
|||
private void addUrlAndIdToList(String scenarioDefiniton, List<String> urlList, List<String> idList) {
|
||||
try {
|
||||
JSONObject scenarioObj = JSONObject.parseObject(scenarioDefiniton);
|
||||
if(scenarioObj.containsKey("hashTree")){
|
||||
if (scenarioObj.containsKey("hashTree")) {
|
||||
JSONArray hashArr = scenarioObj.getJSONArray("hashTree");
|
||||
for (int i = 0; i < hashArr.size(); i++) {
|
||||
JSONObject elementObj = hashArr.getJSONObject(i);
|
||||
if(elementObj.containsKey("id")){
|
||||
if (elementObj.containsKey("id")) {
|
||||
String id = elementObj.getString("id");
|
||||
idList.add(id);
|
||||
}
|
||||
if(elementObj.containsKey("url")){
|
||||
if (elementObj.containsKey("url")) {
|
||||
String url = elementObj.getString("url");
|
||||
urlList.add(url);
|
||||
}
|
||||
if(elementObj.containsKey("path")){
|
||||
if (elementObj.containsKey("path")) {
|
||||
String path = elementObj.getString("path");
|
||||
urlList.add(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}catch (Exception e){
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
package io.metersphere.commons.constants;
|
||||
|
||||
public enum ConditionType {
|
||||
NONE, PATH, MODULE
|
||||
}
|
|
@ -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);
|
|
@ -40,7 +40,7 @@
|
|||
import MsApiReportExport from "./ApiReportExport";
|
||||
import MsApiReportViewHeader from "./ApiReportViewHeader";
|
||||
import {RequestFactory} from "../../definition/model/ApiTestModel";
|
||||
import {windowPrint} from "@/common/js/utils";
|
||||
import {windowPrint,getUUID} from "@/common/js/utils";
|
||||
|
||||
export default {
|
||||
name: "MsApiReport",
|
||||
|
@ -102,14 +102,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+ "UUID="+getUUID();
|
||||
array.push(req);
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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 => {
|
||||
|
|
|
@ -126,7 +126,7 @@
|
|||
<el-col :span="4">
|
||||
<el-button :disabled="scenarioDefinition.length < 1" size="mini" type="primary" v-prevent-re-click @click="runDebug">{{$t('api_test.request.debug')}}</el-button>
|
||||
<el-tooltip class="item" effect="dark" :content="$t('commons.refresh')" placement="right-start">
|
||||
<el-button :disabled="scenarioDefinition.length < 1" size="mini" icon ="el-icon-refresh" v-prevent-re-click @click="getApiScenario"></el-button>
|
||||
<el-button :disabled="scenarioDefinition.length < 1" size="mini" icon="el-icon-refresh" v-prevent-re-click @click="getApiScenario"></el-button>
|
||||
</el-tooltip>
|
||||
<font-awesome-icon class="alt-ico" :icon="['fa', 'expand-alt']" size="lg" @click="fullScreen"/>
|
||||
</el-col>
|
||||
|
@ -134,13 +134,19 @@
|
|||
</div>
|
||||
<!-- 场景步骤内容 -->
|
||||
<div>
|
||||
<el-button class="el-icon-files ms-open-btn ms-open-btn-left" size="mini" v-prevent-re-click @click="openExpansion">
|
||||
{{$t('api_test.automation.open_expansion')}}
|
||||
</el-button>
|
||||
<el-button class=" el-icon-notebook-1 ms-open-btn" size="mini" @click="closeExpansion">
|
||||
{{$t('api_test.automation.close_expansion')}}
|
||||
</el-button>
|
||||
<el-tree node-key="resourceId" :props="props" :data="scenarioDefinition" class="ms-tree"
|
||||
:default-expanded-keys="expandedNode"
|
||||
:expand-on-click-node="false"
|
||||
highlight-current
|
||||
@node-expand="nodeExpand"
|
||||
@node-collapse="nodeCollapse"
|
||||
:allow-drop="allowDrop" @node-drag-end="allowDrag" @node-click="nodeClick" v-if="!loading" draggable>
|
||||
:allow-drop="allowDrop" @node-drag-end="allowDrag" @node-click="nodeClick" v-if="!loading" draggable ref="stepTree">
|
||||
<span class="custom-tree-node father" slot-scope="{ node, data}" style="width: 96%">
|
||||
<!-- 步骤组件-->
|
||||
<ms-component-config :type="data.type" :scenario="data" :response="response" :currentScenario="currentScenario"
|
||||
|
@ -206,7 +212,8 @@
|
|||
<ms-drawer :visible="drawer" :size="100" @close="close" direction="right" :show-full-screen="false" :is-show-close="false" style="overflow: hidden">
|
||||
<template v-slot:header>
|
||||
<scenario-header :currentScenario="currentScenario" :projectEnvMap="projectEnvMap" :projectIds.sync="projectIds" :projectList="projectList" :scenarioDefinition="scenarioDefinition" :enableCookieShare="enableCookieShare"
|
||||
:isFullUrl.sync="isFullUrl" @closePage="close" @unFullScreen="unFullScreen" @showAllBtn="showAllBtn" @runDebug="runDebug" @setProjectEnvMap="setProjectEnvMap" @showScenarioParameters="showScenarioParameters" @setCookieShare="setCookieShare" ref="maximizeHeader"/>
|
||||
:isFullUrl.sync="isFullUrl" @closePage="close" @unFullScreen="unFullScreen" @showAllBtn="showAllBtn" @runDebug="runDebug" @setProjectEnvMap="setProjectEnvMap" @showScenarioParameters="showScenarioParameters" @setCookieShare="setCookieShare"
|
||||
ref="maximizeHeader"/>
|
||||
</template>
|
||||
|
||||
<maximize-scenario :scenario-definition="scenarioDefinition" :envMap="projectEnvMap" :moduleOptions="moduleOptions"
|
||||
|
@ -325,7 +332,8 @@
|
|||
projectList: [],
|
||||
debugResult: new Map,
|
||||
drawer: false,
|
||||
isFullUrl: true
|
||||
isFullUrl: true,
|
||||
expandedStatus: false,
|
||||
}
|
||||
},
|
||||
created() {
|
||||
|
@ -597,6 +605,7 @@
|
|||
recursiveSorting(arr, scenarioProjectId) {
|
||||
for (let i in arr) {
|
||||
arr[i].index = Number(i) + 1;
|
||||
arr[i].active = this.expandedStatus;
|
||||
if (arr[i].type === ELEMENT_TYPE.LoopController && arr[i].loopType === "LOOP_COUNT" && arr[i].hashTree && arr[i].hashTree.length > 1) {
|
||||
arr[i].countController.proceed = true;
|
||||
}
|
||||
|
@ -623,6 +632,7 @@
|
|||
for (let i in this.scenarioDefinition) {
|
||||
// 排序
|
||||
this.scenarioDefinition[i].index = Number(i) + 1;
|
||||
this.scenarioDefinition[i].active = this.expandedStatus;
|
||||
// 设置循环控制
|
||||
if (this.scenarioDefinition[i].type === ELEMENT_TYPE.LoopController && this.scenarioDefinition[i].hashTree
|
||||
&& this.scenarioDefinition[i].hashTree.length > 1) {
|
||||
|
@ -1149,6 +1159,44 @@
|
|||
this.getEnv(JSON.stringify(definition)).then(() => {
|
||||
this.$refs.envPopover.openEnvSelect();
|
||||
})
|
||||
},
|
||||
shrinkTreeNode() {
|
||||
//改变每个节点的状态
|
||||
for (let i in this.scenarioDefinition) {
|
||||
if (this.scenarioDefinition[i]) {
|
||||
if (this.expandedStatus) {
|
||||
this.expandedNode.push(this.scenarioDefinition[i].resourceId);
|
||||
}
|
||||
this.scenarioDefinition[i].active = this.expandedStatus;
|
||||
if (this.scenarioDefinition[i].hashTree && this.scenarioDefinition[i].hashTree.length > 0) {
|
||||
this.changeNodeStatus(this.scenarioDefinition[i].hashTree);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
changeNodeStatus(nodes) {
|
||||
for (let i in nodes) {
|
||||
if (nodes[i]) {
|
||||
if (this.expandedStatus) {
|
||||
this.expandedNode.push(nodes[i].resourceId);
|
||||
}
|
||||
nodes[i].active = this.expandedStatus;
|
||||
if (nodes[i].hashTree != undefined && nodes[i].hashTree.length > 0) {
|
||||
this.changeNodeStatus(nodes[i].hashTree);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
openExpansion() {
|
||||
this.expandedNode = [];
|
||||
this.expandedStatus = true;
|
||||
this.shrinkTreeNode();
|
||||
},
|
||||
closeExpansion() {
|
||||
this.expandedStatus = false;
|
||||
this.expandedNode = [];
|
||||
this.shrinkTreeNode();
|
||||
this.reload();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1309,4 +1357,15 @@
|
|||
white-space: nowrap;
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.ms-open-btn {
|
||||
margin: 5px 5px 0px;
|
||||
font-size: 10px;
|
||||
background-color: #F2F9EE;
|
||||
color: #67C23A;
|
||||
}
|
||||
|
||||
.ms-open-btn-left {
|
||||
margin-left: 30px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -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>
|
|
@ -2,9 +2,15 @@
|
|||
<div>
|
||||
<!-- 场景步骤-->
|
||||
<ms-container>
|
||||
<ms-aside-container>
|
||||
<ms-aside-container style="padding-top: 0px">
|
||||
<!-- 场景步骤内容 -->
|
||||
<div v-loading="loading">
|
||||
<el-button class="el-icon-files ms-open-btn ms-open-btn-left" size="mini" @click="openExpansion">
|
||||
{{$t('api_test.automation.open_expansion')}}
|
||||
</el-button>
|
||||
<el-button class="el-icon-notebook-1 ms-open-btn" size="mini" @click="closeExpansion">
|
||||
{{$t('api_test.automation.close_expansion')}}
|
||||
</el-button>
|
||||
<el-tree node-key="resourceId"
|
||||
:props="props"
|
||||
:data="scenarioDefinition"
|
||||
|
@ -226,6 +232,7 @@
|
|||
projectEnvMap: new Map,
|
||||
projectList: [],
|
||||
debugResult: new Map,
|
||||
expandedStatus: false,
|
||||
}
|
||||
},
|
||||
created() {
|
||||
|
@ -940,6 +947,44 @@
|
|||
// 把执行结果分发给各个请求
|
||||
this.debugResult = result;
|
||||
this.sort()
|
||||
},
|
||||
shrinkTreeNode() {
|
||||
//改变每个节点的状态
|
||||
for (let i in this.scenarioDefinition) {
|
||||
if (this.scenarioDefinition[i]) {
|
||||
if (this.expandedStatus) {
|
||||
this.expandedNode.push(this.scenarioDefinition[i].resourceId);
|
||||
}
|
||||
this.scenarioDefinition[i].active = this.expandedStatus;
|
||||
if (this.scenarioDefinition[i].hashTree && this.scenarioDefinition[i].hashTree.length > 0) {
|
||||
this.changeNodeStatus(this.scenarioDefinition[i].hashTree);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
changeNodeStatus(nodes) {
|
||||
for (let i in nodes) {
|
||||
if (nodes[i]) {
|
||||
if (this.expandedStatus) {
|
||||
this.expandedNode.push(nodes[i].resourceId);
|
||||
}
|
||||
nodes[i].active = this.expandedStatus;
|
||||
if (nodes[i].hashTree != undefined && nodes[i].hashTree.length > 0) {
|
||||
this.changeNodeStatus(nodes[i].hashTree);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
openExpansion() {
|
||||
this.expandedNode = [];
|
||||
this.expandedStatus = true;
|
||||
this.shrinkTreeNode();
|
||||
},
|
||||
closeExpansion() {
|
||||
this.expandedStatus = false;
|
||||
this.expandedNode = [];
|
||||
this.shrinkTreeNode();
|
||||
this.reload();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1111,4 +1156,15 @@
|
|||
.father:hover .child {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.ms-open-btn {
|
||||
margin: 5px 5px 0px;
|
||||
font-size: 10px;
|
||||
background-color: #F2F9EE;
|
||||
color: #67C23A;
|
||||
}
|
||||
|
||||
.ms-open-btn-left {
|
||||
margin-left: 30px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -222,7 +222,7 @@
|
|||
},
|
||||
created() {
|
||||
let dataRange = this.$route.params.dataSelectRange;
|
||||
if (dataRange.length > 0) {
|
||||
if (dataRange && dataRange.length > 0) {
|
||||
this.activeDom = 'middle';
|
||||
}
|
||||
if (this.activeDom === 'left') {
|
||||
|
|
|
@ -248,7 +248,6 @@
|
|||
if (!apiCase.request.hashTree) {
|
||||
apiCase.request.hashTree = [];
|
||||
}
|
||||
|
||||
});
|
||||
this.apiCaseList = data;
|
||||
if (!this.useEnvironment && this.apiCaseList[0] && this.apiCaseList[0].request && this.apiCaseList[0].request.useEnvironment) {
|
||||
|
|
|
@ -42,7 +42,7 @@
|
|||
import {Environment} from "../../model/EnvironmentModel";
|
||||
import MsApiHostTable from "./ApiHostTable";
|
||||
import MsDatabaseConfig from "../request/database/DatabaseConfig";
|
||||
import MsEnvironmentHttpConfig from "./EnvironmentHttpConfig";
|
||||
import MsEnvironmentHttpConfig from "../../../test/components/environment/EnvironmentHttpConfig";
|
||||
import MsEnvironmentCommonConfig from "./EnvironmentCommonConfig";
|
||||
import EnvironmentTcpConfig from "./EnvironmentTcpConfig";
|
||||
|
||||
|
|
|
@ -3,15 +3,15 @@
|
|||
<el-row type="flex">
|
||||
<el-col>
|
||||
<div style="font-size: 14px;color: #AAAAAA;float: left">{{$t('api_report.response_code')}} :</div>
|
||||
<div style="font-size: 14px;color:#61C550;margin-top:2px;margin-left:10px;float: left">{{response.responseResult.responseCode ? response.responseResult.responseCode :'0'}}</div>
|
||||
<div style="font-size: 14px;color:#61C550;margin-top:2px;margin-left:10px;float: left">{{response.responseResult && response.responseResult.responseCode ? response.responseResult.responseCode :'0'}}</div>
|
||||
</el-col>
|
||||
<el-col>
|
||||
<div style="font-size: 14px;color: #AAAAAA;float: left">{{$t('api_report.response_time')}} :</div>
|
||||
<div style="font-size: 14px;color:#61C550;margin-top:2px;margin-left:10px;float: left">{{response.responseResult.responseTime?response.responseResult.responseTime:0}} ms</div>
|
||||
<div style="font-size: 14px;color:#61C550;margin-top:2px;margin-left:10px;float: left">{{response.responseResult && response.responseResult.responseTime?response.responseResult.responseTime:0}} ms</div>
|
||||
</el-col>
|
||||
<el-col>
|
||||
<div style="font-size: 14px;color: #AAAAAA;float: left">{{$t('api_report.response_size')}} :</div>
|
||||
<div style="font-size: 14px;color:#61C550; margin-top:2px;margin-left:10px;float: left">{{response.responseResult.responseSize?response.responseResult.responseSize:0}} bytes</div>
|
||||
<div style="font-size: 14px;color:#61C550; margin-top:2px;margin-left:10px;float: left">{{response.responseResult && response.responseResult.responseSize?response.responseResult.responseSize:0}} bytes</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
|
|
|
@ -117,8 +117,11 @@
|
|||
if (!this.response.body) {
|
||||
this.response.body = "";
|
||||
}
|
||||
if(!this.response.responseResult.vars){
|
||||
this.response.responseResult.vars="";
|
||||
if (!this.response.responseResult) {
|
||||
this.response.responseResult = {};
|
||||
}
|
||||
if (!this.response.responseResult.vars) {
|
||||
this.response.responseResult.vars = "";
|
||||
}
|
||||
this.reqMessages = this.$t('api_test.request.address') + ":\n" + this.response.url + "\n" +
|
||||
this.$t('api_test.scenario.headers') + ":\n" + this.response.headers + "\n" + "Cookies :\n" +
|
||||
|
|
|
@ -145,7 +145,10 @@
|
|||
})
|
||||
},
|
||||
runRefresh(data) {
|
||||
this.responseData = data;
|
||||
this.responseData = {type: 'HTTP', responseResult: {responseCode: ""}, subRequestResults: []};
|
||||
if (data) {
|
||||
this.responseData = data;
|
||||
}
|
||||
this.loading = false;
|
||||
},
|
||||
saveAs() {
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
<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"
|
||||
:delete-fuc="deleteEnvironment" @itemSelected="environmentSelected" ref="environmentItems"/>
|
||||
<environment-edit :environment="currentEnvironment" ref="environmentEdit" @close="close"/>
|
||||
<environment-edit :project-id="projectId" :environment="currentEnvironment" ref="environmentEdit" @close="close"/>
|
||||
</el-container>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane :label="$t('api_test.environment.http_config')" name="http">
|
||||
<ms-environment-http-config :http-config="environment.config.httpConfig" ref="httpConfig"/>
|
||||
<ms-environment-http-config :project-id="projectId" :http-config="environment.config.httpConfig" ref="httpConfig"/>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="$t('api_test.environment.database_config')" name="sql">
|
||||
<ms-database-config :configs="environment.config.databaseConfigs"/>
|
||||
|
@ -52,9 +52,11 @@
|
|||
MsTcpConfig,
|
||||
MsEnvironmentCommonConfig,
|
||||
MsEnvironmentHttpConfig,
|
||||
MsDatabaseConfig, MsApiHostTable, MsDialogFooter, MsApiKeyValue, MsApiScenarioVariables},
|
||||
MsDatabaseConfig, MsApiHostTable, MsDialogFooter, MsApiKeyValue, MsApiScenarioVariables
|
||||
},
|
||||
props: {
|
||||
environment: new Environment(),
|
||||
projectId: String,
|
||||
},
|
||||
data() {
|
||||
|
||||
|
|
|
@ -1,86 +1,282 @@
|
|||
<template>
|
||||
<el-form :model="httpConfig" :rules="rules" ref="httpConfig">
|
||||
<span>{{$t('api_test.environment.socket')}}</span>
|
||||
<el-form-item prop="socket">
|
||||
<el-input v-model="httpConfig.socket" :placeholder="$t('api_test.request.url_description')" clearable>
|
||||
<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="condition.socket" style="width: 80%" :placeholder="$t('api_test.request.url_description')" clearable size="small">
|
||||
<template v-slot:prepend>
|
||||
<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>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<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="NONE">{{ $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" 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'">
|
||||
<el-input v-model="pathDetails.name" :placeholder="$t('api_test.value')" clearable size="small">
|
||||
<template v-slot:prepend>
|
||||
<el-select v-model="httpConfig.protocol" class="request-protocol-select">
|
||||
<el-option label="http://" value="http"/>
|
||||
<el-option label="https://" value="https"/>
|
||||
<el-select v-model="pathDetails.value" class="request-protocol-select" size="small">
|
||||
<el-option :label="$t('api_test.request.assertions.contains')" value="contains"/>
|
||||
<el-option :label="$t('commons.adv_search.operators.equals')" value="equals"/>
|
||||
</el-select>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
|
||||
<span>{{$t('api_test.request.headers')}}</span>
|
||||
<ms-api-key-value :items="httpConfig.headers" :isShowEnable="true" :suggestions="headerSuggestions"/>
|
||||
</el-form>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<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">
|
||||
<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')">
|
||||
<template v-slot:default="{row}">
|
||||
{{getDetails(row)}}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" show-overflow-tooltip min-width="120px" :label="$t('commons.create_time')">
|
||||
<template v-slot:default="{row}">
|
||||
<span>{{ row.time | timestampFormatDate }}</span>
|
||||
</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/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
<span>{{$t('api_test.request.headers')}}</span>
|
||||
<ms-api-key-value :items="httpConfig.headers" :isShowEnable="true" :suggestions="headerSuggestions"/>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {HttpConfig} from "../../model/EnvironmentModel";
|
||||
import MsApiKeyValue from "../ApiKeyValue";
|
||||
import {REQUEST_HEADERS} from "../../../../../../common/js/constants";
|
||||
import {HttpConfig} from "../../model/EnvironmentModel";
|
||||
import MsApiKeyValue from "../ApiKeyValue";
|
||||
import {REQUEST_HEADERS} from "../../../../../../common/js/constants";
|
||||
import MsSelectTree from "../../../../common/select-tree/SelectTree";
|
||||
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",
|
||||
components: {MsApiKeyValue},
|
||||
props: {
|
||||
httpConfig: new HttpConfig(),
|
||||
},
|
||||
data() {
|
||||
let socketValidator = (rule, value, callback) => {
|
||||
if (!this.validateSocket(value)) {
|
||||
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'}],
|
||||
},
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
validateSocket(socket) {
|
||||
if (!socket) return true;
|
||||
let urlStr = this.httpConfig.protocol + '://' + socket;
|
||||
let url = {};
|
||||
try {
|
||||
url = new URL(urlStr);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
this.httpConfig.domain = decodeURIComponent(url.hostname);
|
||||
|
||||
this.httpConfig.port = url.port;
|
||||
let path = url.pathname === '/' ? '' : url.pathname;
|
||||
if (url.port) {
|
||||
this.httpConfig.socket = this.httpConfig.domain + ':' + url.port + path;
|
||||
} else {
|
||||
this.httpConfig.socket = this.httpConfig.domain + path;
|
||||
}
|
||||
export default {
|
||||
name: "MsEnvironmentHttpConfig",
|
||||
components: {MsApiKeyValue, MsSelectTree, MsTableOperatorButton},
|
||||
props: {
|
||||
httpConfig: new HttpConfig(),
|
||||
projectId: String,
|
||||
},
|
||||
created() {
|
||||
this.list();
|
||||
},
|
||||
data() {
|
||||
let socketValidator = (rule, value, callback) => {
|
||||
if (!this.validateSocket(value)) {
|
||||
callback(new Error(this.$t("commons.formatErr")));
|
||||
return false;
|
||||
} else {
|
||||
callback();
|
||||
return true;
|
||||
},
|
||||
validate() {
|
||||
let isValidate = false;
|
||||
this.$refs['httpConfig'].validate((valid) => {
|
||||
isValidate = valid;
|
||||
});
|
||||
return isValidate;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
return {
|
||||
headerSuggestions: REQUEST_HEADERS,
|
||||
rules: {
|
||||
socket: [{required: false, validator: socketValidator, trigger: "blur"}],
|
||||
},
|
||||
moduleOptions: [],
|
||||
moduleObj: {
|
||||
id: "id",
|
||||
label: "name",
|
||||
},
|
||||
pathDetails: new KeyValue({name: "", value: "contains"}),
|
||||
condition: {type: "NONE", details: [new KeyValue({name: "", value: "contains"})], protocol: "http", socket: "", domain: "", port: 0},
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
projectId() {
|
||||
this.list();
|
||||
},
|
||||
httpConfig: function (o) {
|
||||
this.condition = {type: "NONE", details: [new KeyValue({name: "", value: "contains"})], protocol: "http", socket: "", domain: "", port: 0};
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getUrl(row) {
|
||||
return row.protocol + "://" + row.socket;
|
||||
},
|
||||
getName(row) {
|
||||
switch (row.type) {
|
||||
case "NONE":
|
||||
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.details && row.details instanceof Array) {
|
||||
let value = "";
|
||||
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 {
|
||||
return "";
|
||||
}
|
||||
},
|
||||
selectRow(row) {
|
||||
if (row) {
|
||||
this.httpConfig.socket = row.socket;
|
||||
this.httpConfig.protocol = row.protocol;
|
||||
this.httpConfig.port = row.port;
|
||||
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 "NONE":
|
||||
this.condition.details = [];
|
||||
break;
|
||||
case "MODULE":
|
||||
this.condition.details = [];
|
||||
break;
|
||||
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) => {
|
||||
if (response.data !== undefined && response.data !== null) {
|
||||
this.moduleOptions = response.data;
|
||||
}
|
||||
});
|
||||
},
|
||||
setModule(id, data) {
|
||||
if (data && data.length > 0) {
|
||||
this.condition.details = [];
|
||||
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);
|
||||
this.validateSocket(this.condition.socket);
|
||||
let obj = {
|
||||
id: this.condition.id, type: this.condition.type, domain: this.condition.domain, socket: this.condition.socket,
|
||||
protocol: this.condition.protocol, details: this.condition.details, port: this.condition.port, time: this.condition.time
|
||||
};
|
||||
if (index !== -1) {
|
||||
Vue.set(this.httpConfig.conditions[index], obj, 1);
|
||||
this.condition = {type: "NONE", details: [new KeyValue({name: "", value: "contains"})], protocol: "", socket: "", domain: ""};
|
||||
}
|
||||
},
|
||||
add() {
|
||||
let obj = {
|
||||
id: getUUID(), type: this.condition.type, socket: this.condition.socket, protocol: this.condition.protocol,
|
||||
domain: this.condition.domain, port: this.condition.port, time: new Date().getTime()
|
||||
};
|
||||
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;
|
||||
}
|
||||
this.httpConfig.conditions.unshift(obj);
|
||||
},
|
||||
remove(row) {
|
||||
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, socket: row.socket, details: row.details, protocol: row.protocol, domain: row.domain,};
|
||||
if (index != -1) {
|
||||
this.httpConfig.conditions.splice(index + 1, 0, obj);
|
||||
} else {
|
||||
this.httpConfig.conditions.push(obj);
|
||||
}
|
||||
},
|
||||
validateSocket(socket) {
|
||||
if (!socket) return true;
|
||||
let urlStr = this.condition.protocol + "://" + socket;
|
||||
let url = {};
|
||||
try {
|
||||
url = new URL(urlStr);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
this.condition.domain = decodeURIComponent(url.hostname);
|
||||
|
||||
this.condition.port = url.port;
|
||||
let path = url.pathname === "/" ? "" : url.pathname;
|
||||
if (url.port) {
|
||||
this.condition.socket = this.condition.domain + ":" + url.port + path;
|
||||
} else {
|
||||
this.condition.socket = this.condition.domain + path;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
validate() {
|
||||
let isValidate = false;
|
||||
this.$refs["httpConfig"].validate((valid) => {
|
||||
isValidate = valid;
|
||||
});
|
||||
return isValidate;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.request-protocol-select {
|
||||
width: 90px;
|
||||
}
|
||||
|
||||
.ms-env-span {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
/deep/ .el-form-item {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -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,15 +62,16 @@ export class CommonConfig extends BaseConfig {
|
|||
export class HttpConfig extends BaseConfig {
|
||||
constructor(options = {}) {
|
||||
super();
|
||||
|
||||
this.socket = undefined;
|
||||
this.domain = undefined;
|
||||
this.headers = [];
|
||||
this.protocol = 'https';
|
||||
this.port = undefined;
|
||||
|
||||
this.conditions = [];
|
||||
this.defaultCondition = "NONE";
|
||||
this.set(options);
|
||||
this.sets({headers: KeyValue}, options);
|
||||
this.sets({conditions: KeyValue}, options);
|
||||
}
|
||||
|
||||
initOptions(options = {}) {
|
||||
|
|
|
@ -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>
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
},
|
||||
|
|
|
@ -16,6 +16,17 @@
|
|||
<status-edit ref="statusEdit" :plan-id="reviewId"
|
||||
:select-ids="new Set(Array.from(this.selectRows).map(row => row.id))" @refresh="initTableData"/>
|
||||
|
||||
<el-table
|
||||
v-loading="result.loading"
|
||||
class="adjust-table"
|
||||
border
|
||||
@select-all="handleSelectAll"
|
||||
@filter-change="filter"
|
||||
@sort-change="sort"
|
||||
@select="handleSelectionChange"
|
||||
row-key="id"
|
||||
@row-click="showDetail"
|
||||
:data="tableData">
|
||||
<el-table
|
||||
v-loading="result.loading"
|
||||
class="test-content adjust-table ms-select-all-fixed"
|
||||
|
|
|
@ -712,6 +712,8 @@ export default {
|
|||
}
|
||||
},
|
||||
automation: {
|
||||
open_expansion: "One-click expansion",
|
||||
close_expansion: "One-click storage",
|
||||
constant: "constant",
|
||||
counter: "counter",
|
||||
random: "random",
|
||||
|
@ -775,6 +777,7 @@ export default {
|
|||
environment: {
|
||||
name: "Environment Name",
|
||||
socket: "Socket",
|
||||
condition_enable: "Activation conditions",
|
||||
globalVariable: "Global Variable",
|
||||
environment_list: "Environment List",
|
||||
environment_config: "Environment Config",
|
||||
|
@ -1679,5 +1682,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",
|
||||
}
|
||||
};
|
||||
|
|
|
@ -713,6 +713,8 @@ export default {
|
|||
}
|
||||
},
|
||||
automation: {
|
||||
open_expansion: "一键展开",
|
||||
close_expansion: "一键收起",
|
||||
constant: "常量",
|
||||
counter: "计数器",
|
||||
random: "随机数",
|
||||
|
@ -776,6 +778,7 @@ export default {
|
|||
environment: {
|
||||
name: "环境名称",
|
||||
socket: "环境域名",
|
||||
condition_enable: "启用条件",
|
||||
globalVariable: "全局变量",
|
||||
environment_list: "环境列表",
|
||||
environment_config: "环境配置",
|
||||
|
@ -1682,5 +1685,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: "报告名称",
|
||||
}
|
||||
};
|
||||
|
|
|
@ -712,6 +712,8 @@ export default {
|
|||
}
|
||||
},
|
||||
automation: {
|
||||
open_expansion: "一鍵展開",
|
||||
close_expansion: "一鍵收起",
|
||||
constant: "常量",
|
||||
counter: "計數器",
|
||||
random: "随机器",
|
||||
|
@ -775,6 +777,7 @@ export default {
|
|||
environment: {
|
||||
name: "環境名稱",
|
||||
socket: "環境域名",
|
||||
condition_enable: "啟用條件",
|
||||
globalVariable: "全局變量",
|
||||
environment_list: "環境列表",
|
||||
environment_config: "環境配置",
|
||||
|
@ -1680,6 +1683,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: "報告名稱",
|
||||
}
|
||||
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue