feat(测试计划): 测试计划不再强制关联用例时选择环境
--story=1012486 --user=宋天阳 【货车之家】接口场景包含跨项目步骤时取消跨项目环境必选&测试计划关联接口、UI测试时取消运行环境必选 https://www.tapd.cn/55049933/s/1401625
This commit is contained in:
parent
eed243d40c
commit
dd92feeba2
|
@ -287,8 +287,16 @@ public class ApiExecuteService {
|
|||
TestPlanApiCaseExample example = new TestPlanApiCaseExample();
|
||||
example.createCriteria().andTestPlanIdEqualTo(request.getTestPlanId()).andApiCaseIdEqualTo(request.getCaseId());
|
||||
List<TestPlanApiCase> list = testPlanApiCaseMapper.selectByExample(example);
|
||||
if (CollectionUtils.isNotEmpty(list)) {
|
||||
request.setEnvironmentId(list.get(0).getEnvironmentId());
|
||||
element.setName(list.get(0).getId());
|
||||
} else {
|
||||
TestPlanApiCase apiCase = testPlanApiCaseMapper.selectByPrimaryKey(request.getCaseId());
|
||||
if (apiCase != null) {
|
||||
request.setEnvironmentId(apiCase.getEnvironmentId());
|
||||
element.setName(request.getCaseId());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
element.setName(request.getCaseId());
|
||||
}
|
||||
|
|
|
@ -11,10 +11,7 @@ import io.metersphere.base.mapper.ext.BaseApiExecutionQueueMapper;
|
|||
import io.metersphere.base.mapper.ext.ExtApiDefinitionExecResultMapper;
|
||||
import io.metersphere.base.mapper.ext.ExtApiScenarioReportMapper;
|
||||
import io.metersphere.base.mapper.plan.ext.ExtTestPlanApiCaseMapper;
|
||||
import io.metersphere.commons.constants.ApiRunMode;
|
||||
import io.metersphere.commons.constants.CommonConstants;
|
||||
import io.metersphere.commons.constants.KafkaTopicConstants;
|
||||
import io.metersphere.commons.constants.TestPlanReportStatus;
|
||||
import io.metersphere.commons.constants.*;
|
||||
import io.metersphere.commons.enums.ApiReportStatus;
|
||||
import io.metersphere.commons.utils.BeanUtils;
|
||||
import io.metersphere.commons.utils.JSON;
|
||||
|
@ -89,9 +86,11 @@ public class ApiExecutionQueueService {
|
|||
Map<String, String> detailMap = new HashMap<>();
|
||||
List<ApiExecutionQueueDetail> queueDetails = new LinkedList<>();
|
||||
// 初始化API/用例队列
|
||||
String redisLockType = TestPlanExecuteCaseType.SCENARIO.name();
|
||||
if (StringUtils.equalsAnyIgnoreCase(type, ApiRunMode.DEFINITION.name(), ApiRunMode.API_PLAN.name())) {
|
||||
Map<String, ApiDefinitionExecResult> runMap = (Map<String, ApiDefinitionExecResult>) runObj;
|
||||
initApi(runMap, resQueue, config, detailMap, queueDetails);
|
||||
redisLockType = TestPlanExecuteCaseType.API_CASE.name();
|
||||
}
|
||||
// 初始化场景
|
||||
else {
|
||||
|
@ -101,11 +100,16 @@ public class ApiExecutionQueueService {
|
|||
if (CollectionUtils.isNotEmpty(queueDetails)) {
|
||||
extApiExecutionQueueMapper.sqlInsert(queueDetails);
|
||||
}
|
||||
//redis移除key (执行测试计划时会添加key)
|
||||
redisTemplateService.unlock(reportId, redisLockType, reportId);
|
||||
resQueue.setDetailMap(detailMap);
|
||||
LoggerUtil.info("报告【" + type + "】生成执行链结束", reportId);
|
||||
return resQueue;
|
||||
}
|
||||
|
||||
@Resource
|
||||
private RedisTemplateService redisTemplateService;
|
||||
|
||||
private void initScenario(Map<String, RunModeDataDTO> runMap, DBTestQueue resQueue, RunModeConfigDTO config, Map<String, String> detailMap, List<ApiExecutionQueueDetail> queueDetails) {
|
||||
final int[] sort = {0};
|
||||
runMap.forEach((k, v) -> {
|
||||
|
|
|
@ -846,9 +846,8 @@ public class MockConfigService {
|
|||
if (project != null) {
|
||||
RequestMockParams requestMockParams = MockApiUtils.genRequestMockParamsFromHttpRequest(request, true);
|
||||
String urlSuffix = this.getUrlSuffix(project.getSystemId(), request);
|
||||
LogUtil.info("Mock urlSuffix:{}", urlSuffix);
|
||||
LogUtil.info("Mock requestHeaderMap:{}", requestHeaderMap);
|
||||
LogUtil.info("Mock requestMockParams:{}", JSON.toJSONString(requestMockParams));
|
||||
LogUtil.info("Mock [" + url + "] Header:{}", requestHeaderMap);
|
||||
LogUtil.info("Mock [" + url + "] request:{}", JSON.toJSONString(requestMockParams));
|
||||
List<ApiDefinitionWithBLOBs> qualifiedApiList = apiDefinitionService.preparedUrl(project.getId(), method, urlSuffix, requestHeaderMap.get(MockApiHeaders.MOCK_API_RESOURCE_ID));
|
||||
for (ApiDefinitionWithBLOBs api : qualifiedApiList) {
|
||||
if (StringUtils.isEmpty(returnStr)) {
|
||||
|
@ -872,6 +871,7 @@ public class MockConfigService {
|
|||
response.setStatus(404);
|
||||
returnStr = Translator.get("mock_warning");
|
||||
}
|
||||
LogUtil.info("Mock [" + url + "] response:{}", returnStr);
|
||||
return returnStr;
|
||||
}
|
||||
|
||||
|
@ -883,9 +883,8 @@ public class MockConfigService {
|
|||
RequestMockParams requestMockParams = MockApiUtils.genRequestMockParamsFromHttpRequest(request, false);
|
||||
|
||||
String urlSuffix = this.getUrlSuffix(project.getSystemId(), request);
|
||||
LogUtil.info("Mock urlSuffix:{}", urlSuffix);
|
||||
LogUtil.info("Mock requestHeaderMap:{}", requestHeaderMap);
|
||||
LogUtil.info("Mock requestMockParams:{}", JSON.toJSONString(requestMockParams));
|
||||
LogUtil.info("Mock [" + url + "] Header:{}", requestHeaderMap);
|
||||
LogUtil.info("Mock [" + url + "] request:{}", JSON.toJSONString(requestMockParams));
|
||||
List<ApiDefinitionWithBLOBs> qualifiedApiList = apiDefinitionService.preparedUrl(project.getId(), method, urlSuffix, requestHeaderMap.get(MockApiHeaders.MOCK_API_RESOURCE_ID));
|
||||
/*
|
||||
GET/DELETE 这种通过url穿参数的接口,在接口路径相同的情况下可能会出现这样的情况:
|
||||
|
@ -919,6 +918,7 @@ public class MockConfigService {
|
|||
response.setStatus(404);
|
||||
returnStr = Translator.get("mock_warning");
|
||||
}
|
||||
LogUtil.info("Mock [" + url + "] response:{}", returnStr);
|
||||
return returnStr;
|
||||
}
|
||||
|
||||
|
|
|
@ -99,4 +99,10 @@ public class RedisTemplateService {
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void unlock(String testPlanReportId, String key, String value) {
|
||||
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
|
||||
RedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
|
||||
this.redisTemplate.execute(redisScript, Collections.singletonList(StringUtils.join(testPlanReportId, key)), new Object[]{value});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -737,6 +737,7 @@ const message = {
|
|||
batch_add_to_ws: "Add to environment group in bulk",
|
||||
choice_conflict: "one project chooses a corresponding environment!",
|
||||
env_list: "Environment List",
|
||||
case_env: "Case environment",
|
||||
confirm: "Confirm",
|
||||
please_select_env_for_current_scenario:
|
||||
"please select env for current scenario",
|
||||
|
|
|
@ -725,6 +725,7 @@ const message = {
|
|||
batch_add_to_ws: "批量添加到环境组",
|
||||
choice_conflict: "环境选择冲突,一个项目选择一个对应环境!",
|
||||
env_list: "环境列表",
|
||||
case_env: "用例环境",
|
||||
confirm: "确 定",
|
||||
please_select_env_for_current_scenario: "请为当前场景选择一个运行环境!",
|
||||
please_select_env_for_current_plan: "请为当前测试计划选择一个运行环境!",
|
||||
|
|
|
@ -724,6 +724,7 @@ const message = {
|
|||
batch_add_to_ws: "批量添加到環境組",
|
||||
choice_conflict: "環境選擇沖突,一個項目選擇一個對應環境!",
|
||||
env_list: "環境列表",
|
||||
case_env: "用例環境",
|
||||
confirm: "確 定",
|
||||
please_select_env_for_current_scenario: "請為當前場景選擇一個運行環境!",
|
||||
please_select_env_for_current_plan: "請為當前测试计划選擇一個運行環境!",
|
||||
|
|
|
@ -155,7 +155,11 @@ export const JMETER_FUNC = [
|
|||
{type: "Information", name: "${__machineIP}", description: "get the local machine IP address"},
|
||||
{type: "Information", name: "${__machineName}", description: "get the local machine name"},
|
||||
{type: "Information", name: "${__time}", description: "return current time in various formats"},
|
||||
{type: "Information", name: "${__timeShift}", description: "return a date in various formats with the specified amount of seconds/minutes/hours/days added"},
|
||||
{
|
||||
type: "Information",
|
||||
name: "${__timeShift}",
|
||||
description: "return a date in various formats with the specified amount of seconds/minutes/hours/days added"
|
||||
},
|
||||
{type: "Information", name: "${__log}", description: "log (or display) a message (and return the value)"},
|
||||
{type: "Information", name: "${__logn}", description: "log (or display) a message (empty return value)"},
|
||||
{type: "Input", name: "${__StringFromFile}", description: "read a line from a file"},
|
||||
|
@ -164,13 +168,21 @@ export const JMETER_FUNC = [
|
|||
{type: "Input", name: "${__XPath}", description: "Use an XPath expression to read from a file"},
|
||||
{type: "Input", name: "${__StringToFile}", description: "write a string to a file"},
|
||||
{type: "Calculation", name: "${__counter}", description: "generate an incrementing number"},
|
||||
{type: "Formatting", name: "${__dateTimeConvert}", description: "Convert a date or time from source to target format"},
|
||||
{
|
||||
type: "Formatting",
|
||||
name: "${__dateTimeConvert}",
|
||||
description: "Convert a date or time from source to target format"
|
||||
},
|
||||
{type: "Calculation", name: "${__digest}", description: "Generate a digest (SHA-1, SHA-256, MD5...)"},
|
||||
{type: "Calculation", name: "${__intSum}", description: "add int numbers"},
|
||||
{type: "Calculation", name: "${__longSum}", description: "add long numbers"},
|
||||
{type: "Calculation", name: "${__Random}", description: "generate a random number"},
|
||||
{type: "Calculation", name: "${__RandomDate}", description: "generate random date within a specific date range"},
|
||||
{type: "Calculation", name: "${__RandomFromMultipleVars}", description: "extracts an element from the values of a set of variables separated by |"},
|
||||
{
|
||||
type: "Calculation",
|
||||
name: "${__RandomFromMultipleVars}",
|
||||
description: "extracts an element from the values of a set of variables separated by |"
|
||||
},
|
||||
{type: "Calculation", name: "${__RandomString}", description: "generate a random string"},
|
||||
{type: "Calculation", name: "${__UUID}", description: "generate a random type 4 UUID"},
|
||||
{type: "Scripting", name: "${__groovy}", description: "run an Apache Groovy script"},
|
||||
|
@ -196,7 +208,11 @@ export const JMETER_FUNC = [
|
|||
{type: "String", name: "${__unescape}", description: "Process strings containing Java escapes (e.g. \n & \t)"},
|
||||
{type: "String", name: "${__unescapeHtml}", description: "Decode HTML-encoded strings"},
|
||||
{type: "String", name: "${__urldecode}", description: "Decode a application/x-www-form-urlencoded string"},
|
||||
{type: "String", name: "${__urlencode}", description: "Encode a string to a application/x-www-form-urlencoded string"},
|
||||
{
|
||||
type: "String",
|
||||
name: "${__urlencode}",
|
||||
description: "Encode a string to a application/x-www-form-urlencoded string"
|
||||
},
|
||||
{type: "String", name: "${__TestPlanName}", description: "Return name of current test plan"},
|
||||
]
|
||||
|
||||
|
@ -219,6 +235,7 @@ export const WORKSTATION={
|
|||
}
|
||||
|
||||
export const ENV_TYPE = {
|
||||
DEFAULT: "DEFAULT",
|
||||
JSON: "JSON",
|
||||
GROUP: "GROUP"
|
||||
}
|
||||
|
@ -297,13 +314,69 @@ export const TASK_DATA = [
|
|||
percentage: 14,
|
||||
permission: ['PROJECT_MANAGER:READ', 'WORKSPACE_PROJECT_MANAGER:READ', 'PROJECT_TRACK_CASE:READ+CREATE', 'PROJECT_TRACK_REVIEW:READ+CREATE', 'PROJECT_TRACK_REVIEW:READ+COMMENT', 'PROJECT_TRACK_PLAN:READ+CREATE', 'PROJECT_TRACK_PLAN:READ+RELEVANCE_OR_CANCEL', 'PROJECT_TRACK_ISSUE:READ+CREATE', 'PROJECT_TRACK_CASE:READ+BATCH_ADD_PUBLIC'],
|
||||
taskData: [
|
||||
{ id: 1, name: "side_task.test_tracking.task_1", status: 1, permission: ['PROJECT_MANAGER:READ', 'WORKSPACE_PROJECT_MANAGER:READ'], api: [''], path: '/setting/project/:type', url: "" },
|
||||
{ id: 2, name: "side_task.test_tracking.task_2", status: 0, permission: ['PROJECT_TRACK_CASE:READ+CREATE'], api: ["/test/case/add"], path: '/track/case/all', url: "/assets/guide/track/task-2.gif" },
|
||||
{ id: 3, name: "side_task.test_tracking.task_3", status: 0, permission: ['PROJECT_TRACK_REVIEW:READ+CREATE'], api: ["/test/case/review/save"], path: '/track/review/all', url: "/assets/guide/track/task-3.gif" },
|
||||
{ id: 4, name: "side_task.test_tracking.task_4", status: 0, permission: ['PROJECT_TRACK_REVIEW:READ+COMMENT'], api: ["/test/case/comment/save"], path: '/track/review/all', url: "/assets/guide/track/task-4.gif" },
|
||||
{ id: 5, name: "side_task.test_tracking.task_5", status: 0, permission: ['PROJECT_TRACK_PLAN:READ+CREATE'], api: ["/test/plan/add"], path: '/track/plan/all', url: "/assets/guide/track/task-5.gif" },
|
||||
{ id: 6, name: "side_task.test_tracking.task_6", status: 0, permission: ['PROJECT_TRACK_PLAN:READ+RELEVANCE_OR_CANCEL'], api: ["/test/plan/relevance"], path: '/track/plan/all', url: "/assets/guide/track/task-6.gif" },
|
||||
{ id: 7, name: "side_task.test_tracking.task_7", status: 0, permission: ['PROJECT_TRACK_ISSUE:READ+CREATE','PROJECT_TRACK_CASE:READ+BATCH_ADD_PUBLIC'], api: ["issues/add","test/case/issues/relate"], path: '/track/issue', url: "/assets/guide/track/task-7.gif" },
|
||||
{
|
||||
id: 1,
|
||||
name: "side_task.test_tracking.task_1",
|
||||
status: 1,
|
||||
permission: ['PROJECT_MANAGER:READ', 'WORKSPACE_PROJECT_MANAGER:READ'],
|
||||
api: [''],
|
||||
path: '/setting/project/:type',
|
||||
url: ""
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "side_task.test_tracking.task_2",
|
||||
status: 0,
|
||||
permission: ['PROJECT_TRACK_CASE:READ+CREATE'],
|
||||
api: ["/test/case/add"],
|
||||
path: '/track/case/all',
|
||||
url: "/assets/guide/track/task-2.gif"
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: "side_task.test_tracking.task_3",
|
||||
status: 0,
|
||||
permission: ['PROJECT_TRACK_REVIEW:READ+CREATE'],
|
||||
api: ["/test/case/review/save"],
|
||||
path: '/track/review/all',
|
||||
url: "/assets/guide/track/task-3.gif"
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: "side_task.test_tracking.task_4",
|
||||
status: 0,
|
||||
permission: ['PROJECT_TRACK_REVIEW:READ+COMMENT'],
|
||||
api: ["/test/case/comment/save"],
|
||||
path: '/track/review/all',
|
||||
url: "/assets/guide/track/task-4.gif"
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: "side_task.test_tracking.task_5",
|
||||
status: 0,
|
||||
permission: ['PROJECT_TRACK_PLAN:READ+CREATE'],
|
||||
api: ["/test/plan/add"],
|
||||
path: '/track/plan/all',
|
||||
url: "/assets/guide/track/task-5.gif"
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
name: "side_task.test_tracking.task_6",
|
||||
status: 0,
|
||||
permission: ['PROJECT_TRACK_PLAN:READ+RELEVANCE_OR_CANCEL'],
|
||||
api: ["/test/plan/relevance"],
|
||||
path: '/track/plan/all',
|
||||
url: "/assets/guide/track/task-6.gif"
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
name: "side_task.test_tracking.task_7",
|
||||
status: 0,
|
||||
permission: ['PROJECT_TRACK_ISSUE:READ+CREATE', 'PROJECT_TRACK_CASE:READ+BATCH_ADD_PUBLIC'],
|
||||
api: ["issues/add", "test/case/issues/relate"],
|
||||
path: '/track/issue',
|
||||
url: "/assets/guide/track/task-7.gif"
|
||||
},
|
||||
],
|
||||
rate: 1,
|
||||
status: 0
|
||||
|
@ -315,13 +388,69 @@ export const TASK_DATA = [
|
|||
percentage: 0,
|
||||
permission: ['PROJECT_API_DEFINITION:READ+CREATE_API', 'PROJECT_API_DEFINITION:READ+IMPORT_API', 'PROJECT_API_DEFINITION:READ+DEBUG', 'PROJECT_API_DEFINITION:READ+CREATE_CASE', 'PROJECT_API_DEFINITION:READ', 'PROJECT_API_SCENARIO:READ+CREATE', 'PROJECT_API_SCENARIO:READ+SCHEDULE'],
|
||||
taskData: [
|
||||
{id: 1, name: "side_task.api_test.task_1", status: 0, path: '/api/definition', permission: ['PROJECT_API_DEFINITION:READ+CREATE_API'], api: ["/api/definition/create"], url: "/assets/guide/api/task-1.gif" },
|
||||
{id: 2, name: "side_task.api_test.task_2", status: 0, path: '/api/definition', permission: ['PROJECT_API_DEFINITION:READ+IMPORT_API'], api: ["/api/definition/import"], url: "/assets/guide/api/task-2.gif" },
|
||||
{id: 3, name: "side_task.api_test.task_3", status: 0, path: '/api/definition', permission: ['PROJECT_API_DEFINITION:READ+DEBUG'], api: ["/api/definition/run/debug"], url: "/assets/guide/api/task-3.gif" },
|
||||
{id: 4, name: "side_task.api_test.task_4", status: 0, path: '/api/definition', permission: ['PROJECT_API_DEFINITION:READ+CREATE_CASE'], api: ["/api/testcase/create"], url: "/assets/guide/api/task-4.gif" },
|
||||
{id: 5, name: "side_task.api_test.task_5", status: 0, path: '/api/definition', permission: ['PROJECT_API_DEFINITION:READ'], api: ["/share/generate/api/document"], url: "/assets/guide/api/task-5.gif" },
|
||||
{id: 6, name: "side_task.api_test.task_6", status: 0, path: '/api/automation', permission: ['PROJECT_API_SCENARIO:READ+CREATE'], api: ["/api/automation/create"], url: "/assets/guide/api/task-6.gif" },
|
||||
{id: 7, name: "side_task.api_test.task_7", status: 0, path: '/api/automation', permission: ['PROJECT_API_SCENARIO:READ+SCHEDULE'], api: ["/api/automation/schedule/create"], url: "/assets/guide/api/task-7.gif" },
|
||||
{
|
||||
id: 1,
|
||||
name: "side_task.api_test.task_1",
|
||||
status: 0,
|
||||
path: '/api/definition',
|
||||
permission: ['PROJECT_API_DEFINITION:READ+CREATE_API'],
|
||||
api: ["/api/definition/create"],
|
||||
url: "/assets/guide/api/task-1.gif"
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "side_task.api_test.task_2",
|
||||
status: 0,
|
||||
path: '/api/definition',
|
||||
permission: ['PROJECT_API_DEFINITION:READ+IMPORT_API'],
|
||||
api: ["/api/definition/import"],
|
||||
url: "/assets/guide/api/task-2.gif"
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: "side_task.api_test.task_3",
|
||||
status: 0,
|
||||
path: '/api/definition',
|
||||
permission: ['PROJECT_API_DEFINITION:READ+DEBUG'],
|
||||
api: ["/api/definition/run/debug"],
|
||||
url: "/assets/guide/api/task-3.gif"
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: "side_task.api_test.task_4",
|
||||
status: 0,
|
||||
path: '/api/definition',
|
||||
permission: ['PROJECT_API_DEFINITION:READ+CREATE_CASE'],
|
||||
api: ["/api/testcase/create"],
|
||||
url: "/assets/guide/api/task-4.gif"
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: "side_task.api_test.task_5",
|
||||
status: 0,
|
||||
path: '/api/definition',
|
||||
permission: ['PROJECT_API_DEFINITION:READ'],
|
||||
api: ["/share/generate/api/document"],
|
||||
url: "/assets/guide/api/task-5.gif"
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
name: "side_task.api_test.task_6",
|
||||
status: 0,
|
||||
path: '/api/automation',
|
||||
permission: ['PROJECT_API_SCENARIO:READ+CREATE'],
|
||||
api: ["/api/automation/create"],
|
||||
url: "/assets/guide/api/task-6.gif"
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
name: "side_task.api_test.task_7",
|
||||
status: 0,
|
||||
path: '/api/automation',
|
||||
permission: ['PROJECT_API_SCENARIO:READ+SCHEDULE'],
|
||||
api: ["/api/automation/schedule/create"],
|
||||
url: "/assets/guide/api/task-7.gif"
|
||||
},
|
||||
],
|
||||
rate: 0,
|
||||
status: 0
|
||||
|
@ -333,8 +462,24 @@ export const TASK_DATA = [
|
|||
percentage: 0,
|
||||
permission: ['PROJECT_API_SCENARIO:READ+CREATE_PERFORMANCE', "PROJECT_API_SCENARIO:READ+CREATE_PERFORMANCE_BATCH", 'PROJECT_PERFORMANCE_REPORT:READ'],
|
||||
taskData: [
|
||||
{id: 1, name: 'side_task.performance_test.task_1', status: 0, path: '/performance/test/all', permission: ['PROJECT_API_SCENARIO:READ+CREATE_PERFORMANCE',"PROJECT_API_SCENARIO:READ+CREATE_PERFORMANCE_BATCH"], api: ["/performance/save"], url: "/assets/guide/performance/task-1.gif" },
|
||||
{id: 2, name: 'side_task.performance_test.task_2', status: 0, path: '/performance/report/all', permission: ['PROJECT_PERFORMANCE_REPORT:READ'], api: ["/share/generate/expired"], url: "/assets/guide/performance/task-2.gif" },
|
||||
{
|
||||
id: 1,
|
||||
name: 'side_task.performance_test.task_1',
|
||||
status: 0,
|
||||
path: '/performance/test/all',
|
||||
permission: ['PROJECT_API_SCENARIO:READ+CREATE_PERFORMANCE', "PROJECT_API_SCENARIO:READ+CREATE_PERFORMANCE_BATCH"],
|
||||
api: ["/performance/save"],
|
||||
url: "/assets/guide/performance/task-1.gif"
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'side_task.performance_test.task_2',
|
||||
status: 0,
|
||||
path: '/performance/report/all',
|
||||
permission: ['PROJECT_PERFORMANCE_REPORT:READ'],
|
||||
api: ["/share/generate/expired"],
|
||||
url: "/assets/guide/performance/task-2.gif"
|
||||
},
|
||||
],
|
||||
rate: 0,
|
||||
status: 0
|
||||
|
@ -346,9 +491,33 @@ export const TASK_DATA = [
|
|||
percentage: 0,
|
||||
permission: ['WORKSPACE_PROJECT_MANAGER:READ+CREATE', 'PROJECT_USER:READ+CREATE', 'PROJECT_ENVIRONMENT:READ+CREATE'],
|
||||
taskData: [
|
||||
{id: 1, name: 'side_task.project_setting.task_1', status: 0, permission: ['WORKSPACE_PROJECT_MANAGER:READ+CREATE'], api: ["/project/add"], path: '/setting/project/:type', url: "/assets/guide/project/task-1.gif" },
|
||||
{id: 2, name: 'side_task.project_setting.task_2', status: 0, permission: ['PROJECT_USER:READ+CREATE'], api: ["/project/member/add","/setting/user/project/member/add"], path: '/project/member', url: "/assets/guide/project/task-2.gif" },
|
||||
{id: 3, name: 'side_task.project_setting.task_3', status: 0, permission: ['PROJECT_ENVIRONMENT:READ+CREATE'], api: ["/environment/add"], path: '/project/env', url: "/assets/guide/project/task-3.gif" },
|
||||
{
|
||||
id: 1,
|
||||
name: 'side_task.project_setting.task_1',
|
||||
status: 0,
|
||||
permission: ['WORKSPACE_PROJECT_MANAGER:READ+CREATE'],
|
||||
api: ["/project/add"],
|
||||
path: '/setting/project/:type',
|
||||
url: "/assets/guide/project/task-1.gif"
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'side_task.project_setting.task_2',
|
||||
status: 0,
|
||||
permission: ['PROJECT_USER:READ+CREATE'],
|
||||
api: ["/project/member/add", "/setting/user/project/member/add"],
|
||||
path: '/project/member',
|
||||
url: "/assets/guide/project/task-2.gif"
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'side_task.project_setting.task_3',
|
||||
status: 0,
|
||||
permission: ['PROJECT_ENVIRONMENT:READ+CREATE'],
|
||||
api: ["/environment/add"],
|
||||
path: '/project/env',
|
||||
url: "/assets/guide/project/task-3.gif"
|
||||
},
|
||||
],
|
||||
rate: 0,
|
||||
status: 0
|
||||
|
@ -360,9 +529,33 @@ export const TASK_DATA = [
|
|||
percentage: 0,
|
||||
permission: ['PROJECT_UI_ELEMENT:READ+CREATE', 'PROJECT_UI_SCENARIO:READ+CREATE', 'PROJECT_UI_SCENARIO:READ+RUN', 'PROJECT_UI_SCENARIO:READ+DEBUG'],
|
||||
taskData: [
|
||||
{id: 1, name: 'side_task.ui_test.task_1', status: 0, permission: ['PROJECT_UI_ELEMENT:READ+CREATE'], api: ["/ui/element/add"], path: '/ui/element', url: "/assets/guide/ui/task-1.gif" },
|
||||
{id: 2, name: 'side_task.ui_test.task_2', status: 0, permission: ['PROJECT_UI_SCENARIO:READ+CREATE'], api: ["/ui/automation/create"], path: '/ui/automation', url: "/assets/guide/ui/task-2.gif" },
|
||||
{id: 2, name: 'side_task.ui_test.task_3', status: 0, permission: ['PROJECT_UI_SCENARIO:READ+RUN','PROJECT_UI_SCENARIO:READ+DEBUG'], api: ["/ui/automation/run/debug"], path: '/ui/automation', url: "/assets/guide/ui/task-3.gif" },
|
||||
{
|
||||
id: 1,
|
||||
name: 'side_task.ui_test.task_1',
|
||||
status: 0,
|
||||
permission: ['PROJECT_UI_ELEMENT:READ+CREATE'],
|
||||
api: ["/ui/element/add"],
|
||||
path: '/ui/element',
|
||||
url: "/assets/guide/ui/task-1.gif"
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'side_task.ui_test.task_2',
|
||||
status: 0,
|
||||
permission: ['PROJECT_UI_SCENARIO:READ+CREATE'],
|
||||
api: ["/ui/automation/create"],
|
||||
path: '/ui/automation',
|
||||
url: "/assets/guide/ui/task-2.gif"
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'side_task.ui_test.task_3',
|
||||
status: 0,
|
||||
permission: ['PROJECT_UI_SCENARIO:READ+RUN', 'PROJECT_UI_SCENARIO:READ+DEBUG'],
|
||||
api: ["/ui/automation/run/debug"],
|
||||
path: '/ui/automation',
|
||||
url: "/assets/guide/ui/task-3.gif"
|
||||
},
|
||||
],
|
||||
rate: 0,
|
||||
status: 0
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
package io.metersphere.commons.constants;
|
||||
|
||||
public enum TestPlanExecuteCaseType {
|
||||
API_CASE, SCENARIO, UI_SCENARIO, LOAD_CASE
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
package io.metersphere.service;
|
||||
|
||||
import io.metersphere.commons.utils.LogUtil;
|
||||
import io.metersphere.utils.LoggerUtil;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.core.script.DefaultRedisScript;
|
||||
import org.springframework.data.redis.core.script.RedisScript;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Service
|
||||
public class RedisTemplateService {
|
||||
public static final long TIME_OUT = 480;
|
||||
@Resource
|
||||
private RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
public boolean setIfAbsent(String key, String value) {
|
||||
try {
|
||||
return redisTemplate.opsForValue().setIfAbsent(key, value);
|
||||
} catch (Exception e) {
|
||||
LoggerUtil.error(key, e);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public Object get(String key) {
|
||||
try {
|
||||
return redisTemplate.opsForValue().get(key);
|
||||
} catch (Exception e) {
|
||||
LoggerUtil.error(key, e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean delete(String key) {
|
||||
try {
|
||||
return redisTemplate.delete(key);
|
||||
} catch (Exception e) {
|
||||
LoggerUtil.error(key, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加锁
|
||||
*/
|
||||
public boolean lock(String testPlanReportId, String key, String value) {
|
||||
Boolean hasReport = redisTemplate.opsForValue().setIfAbsent(
|
||||
StringUtils.join(testPlanReportId, key),
|
||||
value,
|
||||
TIME_OUT,
|
||||
TimeUnit.MINUTES);
|
||||
if (Boolean.FALSE.equals(hasReport)) {
|
||||
redisTemplate.opsForValue().setIfPresent(
|
||||
StringUtils.join(testPlanReportId, key),
|
||||
value,
|
||||
TIME_OUT,
|
||||
TimeUnit.MINUTES);
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean has(String testPlanReportId, String key, String reportId) {
|
||||
try {
|
||||
Object value = redisTemplate.opsForValue().get(StringUtils.join(testPlanReportId, key));
|
||||
return ObjectUtils.isNotEmpty(value) && StringUtils.equals(reportId, String.valueOf(value));
|
||||
} catch (Exception e) {
|
||||
LogUtil.error(e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解锁
|
||||
*/
|
||||
public boolean unlock(String testPlanReportId, String key, String value) {
|
||||
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
|
||||
RedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
|
||||
Long result = redisTemplate.execute(redisScript, Collections.singletonList(StringUtils.join(testPlanReportId, key)), value);
|
||||
if (Objects.equals(1L, result)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -6,6 +6,7 @@ import io.metersphere.base.mapper.ApiExecutionQueueMapper;
|
|||
import io.metersphere.base.mapper.TestPlanLoadCaseMapper;
|
||||
import io.metersphere.base.mapper.ext.BaseApiExecutionQueueMapper;
|
||||
import io.metersphere.commons.constants.KafkaTopicConstants;
|
||||
import io.metersphere.commons.constants.TestPlanExecuteCaseType;
|
||||
import io.metersphere.commons.constants.TestPlanLoadCaseStatus;
|
||||
import io.metersphere.commons.constants.TriggerMode;
|
||||
import io.metersphere.commons.utils.BeanUtils;
|
||||
|
@ -14,6 +15,7 @@ import io.metersphere.constants.RunModeConstants;
|
|||
import io.metersphere.dto.RunModeConfigDTO;
|
||||
import io.metersphere.plan.exec.queue.DBTestQueue;
|
||||
import io.metersphere.request.RunTestPlanRequest;
|
||||
import io.metersphere.service.RedisTemplateService;
|
||||
import io.metersphere.utils.LoggerUtil;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
|
@ -175,13 +177,16 @@ public class PerfQueueService {
|
|||
return queue;
|
||||
}
|
||||
|
||||
@Resource
|
||||
private RedisTemplateService redisTemplateService;
|
||||
|
||||
@Transactional(propagation = Propagation.REQUIRES_NEW)
|
||||
public DBTestQueue add(Object runObj, String poolId, String reportId, String reportType, String runMode, RunModeConfigDTO config) {
|
||||
LoggerUtil.info("报告【" + reportId + "】开始生成执行链");
|
||||
public DBTestQueue add(Object runObj, String poolId, String testPlanReportId, String reportType, String runMode, RunModeConfigDTO config) {
|
||||
LoggerUtil.info("报告【" + testPlanReportId + "】开始生成执行链");
|
||||
if (config.getEnvMap() == null) {
|
||||
config.setEnvMap(new LinkedHashMap<>());
|
||||
}
|
||||
ApiExecutionQueue executionQueue = getApiExecutionQueue(poolId, reportId, reportType, runMode, config);
|
||||
ApiExecutionQueue executionQueue = getApiExecutionQueue(poolId, testPlanReportId, reportType, runMode, config);
|
||||
queueMapper.insert(executionQueue);
|
||||
DBTestQueue resQueue = new DBTestQueue();
|
||||
BeanUtils.copyBean(resQueue, executionQueue);
|
||||
|
@ -196,7 +201,9 @@ public class PerfQueueService {
|
|||
extApiExecutionQueueMapper.sqlInsert(queueDetails);
|
||||
}
|
||||
resQueue.setDetailMap(detailMap);
|
||||
LoggerUtil.info("报告【" + reportId + "】生成执行链结束");
|
||||
LoggerUtil.info("报告【" + testPlanReportId + "】生成执行链结束");
|
||||
//移除Redis中的标志
|
||||
redisTemplateService.unlock(testPlanReportId, TestPlanExecuteCaseType.LOAD_CASE.name(), testPlanReportId);
|
||||
return resQueue;
|
||||
}
|
||||
|
||||
|
@ -207,7 +214,7 @@ public class PerfQueueService {
|
|||
executionQueue.setPoolId(poolId);
|
||||
executionQueue.setFailure(config.isOnSampleError());
|
||||
executionQueue.setReportId(reportId);
|
||||
executionQueue.setReportType(StringUtils.isNotEmpty(reportType) ? reportType : RunModeConstants.INDEPENDENCE.toString());
|
||||
executionQueue.setReportType(TestPlanExecuteCaseType.LOAD_CASE.name());
|
||||
executionQueue.setRunMode(runMode);
|
||||
return executionQueue;
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import io.metersphere.base.mapper.ApiExecutionQueueMapper;
|
|||
import io.metersphere.commons.constants.KafkaTopicConstants;
|
||||
import io.metersphere.plan.service.AutomationCaseExecOverService;
|
||||
import io.metersphere.plan.service.TestPlanReportService;
|
||||
import io.metersphere.service.RedisTemplateService;
|
||||
import io.metersphere.utils.LoggerUtil;
|
||||
import io.metersphere.utils.NamedThreadFactory;
|
||||
import jakarta.annotation.Resource;
|
||||
|
@ -28,6 +29,8 @@ public class ExecReportListener {
|
|||
@Resource
|
||||
private TestPlanReportService testPlanReportService;
|
||||
@Resource
|
||||
private RedisTemplateService redisTemplateService;
|
||||
@Resource
|
||||
private AutomationCaseExecOverService automationCaseExecOverService;
|
||||
|
||||
// 线程池维护线程的最少数量
|
||||
|
@ -57,6 +60,7 @@ public class ExecReportListener {
|
|||
task.setApiExecutionQueueDetailMapper(executionQueueDetailMapper);
|
||||
task.setAutomationCaseExecOverService(automationCaseExecOverService);
|
||||
task.setTestPlanReportService(testPlanReportService);
|
||||
task.setRedisTemplateService(redisTemplateService);
|
||||
task.setRecord(item);
|
||||
threadPool.execute(task);
|
||||
});
|
||||
|
|
|
@ -5,14 +5,16 @@ import io.metersphere.base.domain.ApiExecutionQueueDetailExample;
|
|||
import io.metersphere.base.domain.ApiExecutionQueueExample;
|
||||
import io.metersphere.base.mapper.ApiExecutionQueueDetailMapper;
|
||||
import io.metersphere.base.mapper.ApiExecutionQueueMapper;
|
||||
import io.metersphere.commons.constants.TestPlanExecuteCaseType;
|
||||
import io.metersphere.commons.constants.TestPlanReportStatus;
|
||||
import io.metersphere.plan.service.AutomationCaseExecOverService;
|
||||
import io.metersphere.plan.service.TestPlanReportService;
|
||||
import io.metersphere.service.RedisTemplateService;
|
||||
import io.metersphere.utils.LoggerUtil;
|
||||
import lombok.Data;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.apache.kafka.clients.consumer.ConsumerRecord;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
@ -25,7 +27,7 @@ public class ExecReportListenerTask implements Runnable {
|
|||
private ApiExecutionQueueDetailMapper apiExecutionQueueDetailMapper;
|
||||
private TestPlanReportService testPlanReportService;
|
||||
private AutomationCaseExecOverService automationCaseExecOverService;
|
||||
|
||||
private RedisTemplateService redisTemplateService;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
@ -50,15 +52,15 @@ public class ExecReportListenerTask implements Runnable {
|
|||
ApiExecutionQueueExample executionQueueExample = new ApiExecutionQueueExample();
|
||||
executionQueueExample.createCriteria().andReportIdEqualTo(testPlanReportId);
|
||||
List<ApiExecutionQueue> queues = apiExecutionQueueMapper.selectByExample(executionQueueExample);
|
||||
if (CollectionUtils.isEmpty(queues)) {
|
||||
if (CollectionUtils.isEmpty(queues) && this.isTestPlanIsEmptyInRedis(testPlanReportId)) {
|
||||
LoggerUtil.info("Normal execution completes, update test plan report status:" + testPlanReportId);
|
||||
testPlanReportService.testPlanExecuteOver(testPlanReportId, TestPlanReportStatus.COMPLETED.name());
|
||||
} else {
|
||||
} else if (CollectionUtils.isNotEmpty(queues)) {
|
||||
List<String> ids = queues.stream().map(ApiExecutionQueue::getId).collect(Collectors.toList());
|
||||
ApiExecutionQueueDetailExample detailExample = new ApiExecutionQueueDetailExample();
|
||||
detailExample.createCriteria().andQueueIdIn(ids);
|
||||
long count = apiExecutionQueueDetailMapper.countByExample(detailExample);
|
||||
if (count == 0) {
|
||||
if (count == 0 && this.isTestPlanIsEmptyInRedis(testPlanReportId)) {
|
||||
LoggerUtil.info("Normal execution completes, update test plan report status:" + testPlanReportId);
|
||||
testPlanReportService.testPlanExecuteOver(testPlanReportId, TestPlanReportStatus.COMPLETED.name());
|
||||
LoggerUtil.info("Clear Queue:" + ids);
|
||||
|
@ -68,4 +70,15 @@ public class ExecReportListenerTask implements Runnable {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试计划执行时会将运行标志放入redis中。当测试计划执行队列入库后,会将redis中的标志清除。
|
||||
*/
|
||||
private boolean isTestPlanIsEmptyInRedis(String testPlanReportId) {
|
||||
Object scenarioObj = redisTemplateService.get(testPlanReportId + TestPlanExecuteCaseType.SCENARIO);
|
||||
Object apiCaseObj = redisTemplateService.get(testPlanReportId + TestPlanExecuteCaseType.API_CASE);
|
||||
Object uiObj = redisTemplateService.get(testPlanReportId + TestPlanExecuteCaseType.UI_SCENARIO);
|
||||
Object loadObj = redisTemplateService.get(testPlanReportId + TestPlanExecuteCaseType.LOAD_CASE);
|
||||
return ObjectUtils.isEmpty(scenarioObj) && ObjectUtils.isEmpty(apiCaseObj) && ObjectUtils.isEmpty(uiObj) && ObjectUtils.isEmpty(loadObj);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,221 +0,0 @@
|
|||
package io.metersphere.plan.service;
|
||||
|
||||
import io.metersphere.base.domain.TestPlanWithBLOBs;
|
||||
import io.metersphere.base.mapper.TestPlanMapper;
|
||||
import io.metersphere.commons.exception.MSException;
|
||||
import io.metersphere.commons.utils.JSON;
|
||||
import io.metersphere.commons.utils.LogUtil;
|
||||
import io.metersphere.constants.RunModeConstants;
|
||||
import io.metersphere.dto.*;
|
||||
import io.metersphere.i18n.Translator;
|
||||
import io.metersphere.plan.dto.ExecutionWay;
|
||||
import io.metersphere.plan.request.api.TestPlanRunRequest;
|
||||
import io.metersphere.plan.service.remote.api.PlanTestPlanApiCaseService;
|
||||
import io.metersphere.plan.service.remote.api.PlanTestPlanScenarioCaseService;
|
||||
import io.metersphere.plan.service.remote.performance.PerfExecService;
|
||||
import io.metersphere.plan.service.remote.ui.PlanTestPlanUiScenarioCaseService;
|
||||
import io.metersphere.plan.utils.TestPlanRequestUtil;
|
||||
import io.metersphere.utils.LoggerUtil;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.commons.collections.MapUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Propagation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@Service
|
||||
@Transactional
|
||||
public class TestPlanExecuteService {
|
||||
@Resource
|
||||
@Lazy
|
||||
private TestPlanService testPlanService;
|
||||
@Resource
|
||||
private TestPlanReportService testPlanReportService;
|
||||
@Resource
|
||||
private PlanTestPlanApiCaseService planTestPlanApiCaseService;
|
||||
@Resource
|
||||
private PlanTestPlanScenarioCaseService planTestPlanScenarioCaseService;
|
||||
@Resource
|
||||
private PerfExecService perfExecService;
|
||||
@Resource
|
||||
private PlanTestPlanUiScenarioCaseService planTestPlanUiScenarioCaseService;
|
||||
|
||||
@Resource
|
||||
private TestPlanMapper testPlanMapper;
|
||||
|
||||
/**
|
||||
* 执行测试计划流程是会调用其它服务的执行方法,并通过kafka传递信息给test-track服务来判断测试计划是否执行结束
|
||||
* 执行方法采用单独的事务控制,执行完了就提交,让测试报告以及包括执行内容的数据及时入库
|
||||
*/
|
||||
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
|
||||
public String runTestPlan(String testPlanId, String projectId, String userId, String triggerMode, String planReportId, String executionWay, String apiRunConfig) {
|
||||
// 校验测试计划是否在执行中
|
||||
if (testPlanService.checkTestPlanIsRunning(testPlanId)) {
|
||||
LogUtil.info("当前测试计划正在执行中,请稍后再试", testPlanId);
|
||||
MSException.throwException(Translator.get("test_plan_run_message"));
|
||||
}
|
||||
RunModeConfigDTO runModeConfig = null;
|
||||
try {
|
||||
runModeConfig = JSON.parseObject(apiRunConfig, RunModeConfigDTO.class);
|
||||
} catch (Exception e) {
|
||||
LogUtil.error(e);
|
||||
}
|
||||
if (runModeConfig == null) {
|
||||
runModeConfig = this.buildRunModeConfigDTO();
|
||||
}
|
||||
|
||||
//环境参数为空时,依据测试计划保存的环境执行
|
||||
if (((StringUtils.equals("GROUP", runModeConfig.getEnvironmentType()) && StringUtils.isBlank(runModeConfig.getEnvironmentGroupId()))
|
||||
|| (!StringUtils.equals("GROUP", runModeConfig.getEnvironmentType()) && MapUtils.isEmpty(runModeConfig.getEnvMap()) && MapUtils.isEmpty(runModeConfig.getTestPlanDefaultEnvMap())))
|
||||
&& !StringUtils.equals(executionWay, ExecutionWay.RUN.name())) {
|
||||
TestPlanWithBLOBs testPlanWithBLOBs = testPlanMapper.selectByPrimaryKey(testPlanId);
|
||||
if (StringUtils.isNotEmpty(testPlanWithBLOBs.getRunModeConfig())) {
|
||||
try {
|
||||
Map json = JSON.parseMap(testPlanWithBLOBs.getRunModeConfig());
|
||||
TestPlanRequestUtil.changeStringToBoolean(json);
|
||||
TestPlanRunRequest testPlanRunRequest = JSON.parseObject(JSON.toJSONString(json), TestPlanRunRequest.class);
|
||||
if (testPlanRunRequest != null) {
|
||||
String envType = testPlanRunRequest.getEnvironmentType();
|
||||
Map<String, String> envMap = testPlanRunRequest.getEnvMap();
|
||||
String environmentGroupId = testPlanRunRequest.getEnvironmentGroupId();
|
||||
runModeConfig = testPlanService.getRunModeConfigDTO(testPlanRunRequest, envType, envMap, environmentGroupId, testPlanId);
|
||||
runModeConfig.setTestPlanDefaultEnvMap(testPlanRunRequest.getTestPlanDefaultEnvMap());
|
||||
if (!testPlanRunRequest.isRunWithinResourcePool()) {
|
||||
runModeConfig.setResourcePoolId(null);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LogUtil.error("获取测试计划保存的环境信息出错!", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (planReportId == null) {
|
||||
planReportId = UUID.randomUUID().toString();
|
||||
}
|
||||
if (testPlanService.haveExecCase(testPlanId, true)) {
|
||||
testPlanService.verifyPool(projectId, runModeConfig);
|
||||
}
|
||||
//创建测试报告,然后返回的ID重新赋值为resourceID,作为后续的参数
|
||||
TestPlanScheduleReportInfoDTO reportInfoDTO = testPlanService.genTestPlanReport(planReportId, testPlanId, userId, triggerMode, runModeConfig);
|
||||
|
||||
LoggerUtil.info("预生成测试计划报告【" + reportInfoDTO.getTestPlanReport() != null ? reportInfoDTO.getTestPlanReport().getName() : StringUtils.EMPTY + "】计划报告ID[" + planReportId + "]");
|
||||
|
||||
List<TestPlanApiDTO> apiTestCases = null;
|
||||
List<TestPlanScenarioDTO> scenarioCases = null;
|
||||
List<TestPlanUiScenarioDTO> uiScenarios = null;
|
||||
Map<String, String> loadCaseReportMap = null;
|
||||
if (MapUtils.isNotEmpty(reportInfoDTO.getApiTestCaseDataMap())) {
|
||||
try {
|
||||
apiTestCases = planTestPlanApiCaseService.getFailureListByIds(reportInfoDTO.getApiTestCaseDataMap().keySet());
|
||||
} catch (Exception e) {
|
||||
LogUtil.error("测试计划执行查询接口用例失败!", e);
|
||||
}
|
||||
}
|
||||
if (MapUtils.isNotEmpty(reportInfoDTO.getPlanScenarioIdMap())) {
|
||||
try {
|
||||
scenarioCases = planTestPlanScenarioCaseService.getFailureListByIds(reportInfoDTO.getPlanScenarioIdMap().keySet());
|
||||
} catch (Exception e) {
|
||||
LogUtil.error("测试计划执行查询场景用例失败!", e);
|
||||
}
|
||||
}
|
||||
if (MapUtils.isNotEmpty(reportInfoDTO.getUiScenarioIdMap())) {
|
||||
try {
|
||||
uiScenarios = planTestPlanUiScenarioCaseService.getFailureListByIds(reportInfoDTO.getUiScenarioIdMap().keySet());
|
||||
} catch (Exception e) {
|
||||
LogUtil.error("测试计划执行查询UI用例失败!", e);
|
||||
}
|
||||
}
|
||||
boolean haveApiCaseExec = false, haveScenarioCaseExec = false, haveLoadCaseExec = false, haveUICaseExec = false;
|
||||
if (CollectionUtils.isNotEmpty(apiTestCases)) {
|
||||
//执行接口案例任务
|
||||
LoggerUtil.info("开始执行测试计划接口用例 " + planReportId);
|
||||
try {
|
||||
Map<String, String> apiCaseReportMap = testPlanService.executeApiTestCase(triggerMode, planReportId, userId, testPlanId, runModeConfig);
|
||||
if (MapUtils.isNotEmpty(apiCaseReportMap)) {
|
||||
haveApiCaseExec = true;
|
||||
for (TestPlanApiDTO dto : apiTestCases) {
|
||||
dto.setReportId(apiCaseReportMap.get(dto.getId()));
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
apiTestCases = null;
|
||||
LoggerUtil.info("测试报告" + planReportId + "本次执行测试计划接口用例失败! ", e);
|
||||
}
|
||||
}
|
||||
if (CollectionUtils.isNotEmpty(scenarioCases)) {
|
||||
//执行场景执行任务
|
||||
LoggerUtil.info("开始执行测试计划场景用例 " + planReportId);
|
||||
try {
|
||||
Map<String, String> scenarioReportMap = testPlanService.executeScenarioCase(planReportId, testPlanId, projectId, runModeConfig, triggerMode, userId, reportInfoDTO.getPlanScenarioIdMap());
|
||||
if (MapUtils.isNotEmpty(scenarioReportMap)) {
|
||||
haveScenarioCaseExec = true;
|
||||
List<TestPlanScenarioDTO> removeDTO = new ArrayList<>();
|
||||
for (TestPlanScenarioDTO dto : scenarioCases) {
|
||||
if (scenarioReportMap.containsKey(dto.getId())) {
|
||||
dto.setReportId(scenarioReportMap.get(dto.getId()));
|
||||
} else {
|
||||
removeDTO.add(dto);
|
||||
}
|
||||
}
|
||||
if (CollectionUtils.isNotEmpty(removeDTO)) {
|
||||
scenarioCases.removeAll(removeDTO);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
scenarioCases = null;
|
||||
LoggerUtil.info("测试报告" + planReportId + "本次执行测试计划场景用例失败! ", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (MapUtils.isNotEmpty(reportInfoDTO.getPerformanceIdMap())) {
|
||||
//执行性能测试任务
|
||||
LoggerUtil.info("开始执行测试计划性能用例 " + planReportId);
|
||||
try {
|
||||
loadCaseReportMap = perfExecService.executeLoadCase(planReportId, runModeConfig, testPlanService.transformationPerfTriggerMode(triggerMode), reportInfoDTO.getPerformanceIdMap());
|
||||
if (MapUtils.isNotEmpty(loadCaseReportMap)) {
|
||||
haveLoadCaseExec = true;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LoggerUtil.info("测试报告" + planReportId + "本次执行测试计划性能用例失败! ", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (CollectionUtils.isNotEmpty(uiScenarios)) {
|
||||
//执行UI场景执行任务
|
||||
LoggerUtil.info("开始执行测试计划 UI 场景用例 " + planReportId);
|
||||
try {
|
||||
Map<String, String> uiScenarioReportMap = testPlanService.executeUiScenarioCase(planReportId, testPlanId, projectId, runModeConfig, triggerMode, userId, reportInfoDTO.getUiScenarioIdMap());
|
||||
if (MapUtils.isNotEmpty(uiScenarioReportMap)) {
|
||||
haveUICaseExec = true;
|
||||
for (TestPlanUiScenarioDTO dto : uiScenarios) {
|
||||
dto.setReportId(uiScenarioReportMap.get(dto.getId()));
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
uiScenarios = null;
|
||||
LoggerUtil.info("测试报告" + planReportId + "本次执行测试计划 UI 用例失败! ", e);
|
||||
}
|
||||
}
|
||||
|
||||
LoggerUtil.info("开始生成测试计划报告内容 " + planReportId);
|
||||
testPlanReportService.createTestPlanReportContentReportIds(planReportId, apiTestCases, scenarioCases, uiScenarios, loadCaseReportMap);
|
||||
if (!haveApiCaseExec && !haveScenarioCaseExec && !haveLoadCaseExec && !haveUICaseExec) {
|
||||
//如果没有执行的自动化用例,调用结束测试计划的方法。 因为方法中包含着测试计划执行队列的处理逻辑。
|
||||
testPlanReportService.testPlanUnExecute(reportInfoDTO.getTestPlanReport());
|
||||
}
|
||||
return planReportId;
|
||||
}
|
||||
|
||||
private RunModeConfigDTO buildRunModeConfigDTO() {
|
||||
RunModeConfigDTO runModeConfig = new RunModeConfigDTO();
|
||||
runModeConfig.setMode(RunModeConstants.SERIAL.name());
|
||||
runModeConfig.setReportType("iddReport");
|
||||
runModeConfig.setEnvMap(new HashMap<>());
|
||||
runModeConfig.setOnSampleError(false);
|
||||
return runModeConfig;
|
||||
}
|
||||
}
|
|
@ -49,6 +49,7 @@ import org.apache.ibatis.session.SqlSession;
|
|||
import org.apache.ibatis.session.SqlSessionFactory;
|
||||
import org.mybatis.spring.SqlSessionUtils;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
|
@ -622,6 +623,7 @@ public class TestPlanReportService {
|
|||
}
|
||||
}
|
||||
|
||||
@Async
|
||||
public void testPlanUnExecute(TestPlanReport testPlanReport) {
|
||||
if (testPlanReport != null && !StringUtils.equalsIgnoreCase(testPlanReport.getStatus(), TestPlanReportStatus.COMPLETED.name())) {
|
||||
testPlanReport.setIsApiCaseExecuting(false);
|
||||
|
|
|
@ -40,6 +40,7 @@ import io.metersphere.plan.request.performance.LoadPlanReportDTO;
|
|||
import io.metersphere.plan.request.ui.RunUiScenarioRequest;
|
||||
import io.metersphere.plan.request.ui.TestPlanUiExecuteReportDTO;
|
||||
import io.metersphere.plan.request.ui.UiPlanReportRequest;
|
||||
import io.metersphere.plan.service.execute.TestPlanExecuteService;
|
||||
import io.metersphere.plan.service.remote.api.PlanApiAutomationService;
|
||||
import io.metersphere.plan.service.remote.api.PlanTestPlanApiCaseService;
|
||||
import io.metersphere.plan.service.remote.api.PlanTestPlanScenarioCaseService;
|
||||
|
@ -495,6 +496,22 @@ public class TestPlanService {
|
|||
request.setProjectId(request.getProjectId());
|
||||
}
|
||||
List<TestPlanDTOWithMetric> testPlanList = extTestPlanMapper.list(request);
|
||||
|
||||
//统计测试计划的测试用例数
|
||||
List<String> testPlanIdList = testPlanList.stream().map(TestPlanDTOWithMetric::getId).collect(Collectors.toList());
|
||||
Map<String, ParamsDTO> planTestCaseCountMap = extTestPlanMapper.testPlanTestCaseCount(testPlanIdList);
|
||||
Map<String, ParamsDTO> planApiCaseMap = extTestPlanMapper.testPlanApiCaseCount(testPlanIdList);
|
||||
Map<String, ParamsDTO> planApiScenarioMap = extTestPlanMapper.testPlanApiScenarioCount(testPlanIdList);
|
||||
Map<String, ParamsDTO> planUiScenarioMap = extTestPlanMapper.testPlanUiScenarioCount(testPlanIdList);
|
||||
Map<String, ParamsDTO> planLoadCaseMap = extTestPlanMapper.testPlanLoadCaseCount(testPlanIdList);
|
||||
for (TestPlanDTOWithMetric testPlanMetric : testPlanList) {
|
||||
testPlanMetric.setTestPlanTestCaseCount(planTestCaseCountMap.get(testPlanMetric.getId()) == null ? 0 : Integer.parseInt(planTestCaseCountMap.get(testPlanMetric.getId()).getValue() == null ? "0" : planTestCaseCountMap.get(testPlanMetric.getId()).getValue()));
|
||||
testPlanMetric.setTestPlanApiCaseCount(planApiCaseMap.get(testPlanMetric.getId()) == null ? 0 : Integer.parseInt(planApiCaseMap.get(testPlanMetric.getId()).getValue() == null ? "0" : planApiCaseMap.get(testPlanMetric.getId()).getValue()));
|
||||
testPlanMetric.setTestPlanApiScenarioCount(planApiScenarioMap.get(testPlanMetric.getId()) == null ? 0 : Integer.parseInt(planApiScenarioMap.get(testPlanMetric.getId()).getValue() == null ? "0" : planApiScenarioMap.get(testPlanMetric.getId()).getValue()));
|
||||
testPlanMetric.setTestPlanUiScenarioCount(planUiScenarioMap.get(testPlanMetric.getId()) == null ? 0 : Integer.parseInt(planUiScenarioMap.get(testPlanMetric.getId()).getValue() == null ? "0" : planUiScenarioMap.get(testPlanMetric.getId()).getValue()));
|
||||
testPlanMetric.setTestPlanLoadCaseCount(planLoadCaseMap.get(testPlanMetric.getId()) == null ? 0 : Integer.parseInt(planLoadCaseMap.get(testPlanMetric.getId()).getValue() == null ? "0" : planLoadCaseMap.get(testPlanMetric.getId()).getValue()));
|
||||
}
|
||||
|
||||
if (CollectionUtils.isNotEmpty(testPlanList)) {
|
||||
List<String> changeToFinishedIds = new ArrayList<>();
|
||||
//检查定时任务的设置
|
||||
|
@ -556,17 +573,7 @@ public class TestPlanService {
|
|||
|
||||
public List<TestPlanDTOWithMetric> selectTestPlanMetricById(List<String> idList) {
|
||||
List<TestPlanDTOWithMetric> testPlanMetricList = this.calcTestPlanRateByIdList(idList);
|
||||
Map<String, ParamsDTO> planTestCaseCountMap = extTestPlanMapper.testPlanTestCaseCount(idList);
|
||||
Map<String, ParamsDTO> planApiCaseMap = extTestPlanMapper.testPlanApiCaseCount(idList);
|
||||
Map<String, ParamsDTO> planApiScenarioMap = extTestPlanMapper.testPlanApiScenarioCount(idList);
|
||||
Map<String, ParamsDTO> planUiScenarioMap = extTestPlanMapper.testPlanUiScenarioCount(idList);
|
||||
Map<String, ParamsDTO> planLoadCaseMap = extTestPlanMapper.testPlanLoadCaseCount(idList);
|
||||
for (TestPlanDTOWithMetric testPlanMetric : testPlanMetricList) {
|
||||
testPlanMetric.setTestPlanTestCaseCount(planTestCaseCountMap.get(testPlanMetric.getId()) == null ? 0 : Integer.parseInt(planTestCaseCountMap.get(testPlanMetric.getId()).getValue() == null ? "0" : planTestCaseCountMap.get(testPlanMetric.getId()).getValue()));
|
||||
testPlanMetric.setTestPlanApiCaseCount(planApiCaseMap.get(testPlanMetric.getId()) == null ? 0 : Integer.parseInt(planApiCaseMap.get(testPlanMetric.getId()).getValue() == null ? "0" : planApiCaseMap.get(testPlanMetric.getId()).getValue()));
|
||||
testPlanMetric.setTestPlanApiScenarioCount(planApiScenarioMap.get(testPlanMetric.getId()) == null ? 0 : Integer.parseInt(planApiScenarioMap.get(testPlanMetric.getId()).getValue() == null ? "0" : planApiScenarioMap.get(testPlanMetric.getId()).getValue()));
|
||||
testPlanMetric.setTestPlanUiScenarioCount(planUiScenarioMap.get(testPlanMetric.getId()) == null ? 0 : Integer.parseInt(planUiScenarioMap.get(testPlanMetric.getId()).getValue() == null ? "0" : planUiScenarioMap.get(testPlanMetric.getId()).getValue()));
|
||||
testPlanMetric.setTestPlanLoadCaseCount(planLoadCaseMap.get(testPlanMetric.getId()) == null ? 0 : Integer.parseInt(planLoadCaseMap.get(testPlanMetric.getId()).getValue() == null ? "0" : planLoadCaseMap.get(testPlanMetric.getId()).getValue()));
|
||||
List<User> followUsers = this.getPlanFollow(testPlanMetric.getId());
|
||||
testPlanMetric.setFollowUsers(followUsers);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,352 @@
|
|||
package io.metersphere.plan.service.execute;
|
||||
|
||||
import com.esotericsoftware.minlog.Log;
|
||||
import io.metersphere.base.domain.TestPlanReport;
|
||||
import io.metersphere.base.domain.TestPlanWithBLOBs;
|
||||
import io.metersphere.base.mapper.TestPlanMapper;
|
||||
import io.metersphere.commons.constants.TestPlanExecuteCaseType;
|
||||
import io.metersphere.commons.exception.MSException;
|
||||
import io.metersphere.commons.utils.JSON;
|
||||
import io.metersphere.commons.utils.LogUtil;
|
||||
import io.metersphere.constants.RunModeConstants;
|
||||
import io.metersphere.dto.*;
|
||||
import io.metersphere.i18n.Translator;
|
||||
import io.metersphere.plan.dto.ExecutionWay;
|
||||
import io.metersphere.plan.request.api.TestPlanRunRequest;
|
||||
import io.metersphere.plan.service.TestPlanReportService;
|
||||
import io.metersphere.plan.service.TestPlanService;
|
||||
import io.metersphere.plan.service.remote.api.PlanTestPlanApiCaseService;
|
||||
import io.metersphere.plan.service.remote.api.PlanTestPlanScenarioCaseService;
|
||||
import io.metersphere.plan.service.remote.performance.PerfExecService;
|
||||
import io.metersphere.plan.service.remote.ui.PlanTestPlanUiScenarioCaseService;
|
||||
import io.metersphere.plan.utils.TestPlanRequestUtil;
|
||||
import io.metersphere.service.RedisTemplateService;
|
||||
import io.metersphere.utils.LoggerUtil;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.Data;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.commons.collections.MapUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Propagation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
@Service
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public class TestPlanExecuteService {
|
||||
@Resource
|
||||
@Lazy
|
||||
private TestPlanService testPlanService;
|
||||
@Resource
|
||||
private TestPlanReportService testPlanReportService;
|
||||
@Resource
|
||||
private PlanTestPlanApiCaseService planTestPlanApiCaseService;
|
||||
@Resource
|
||||
private PlanTestPlanScenarioCaseService planTestPlanScenarioCaseService;
|
||||
@Resource
|
||||
private PerfExecService perfExecService;
|
||||
@Resource
|
||||
private PlanTestPlanUiScenarioCaseService planTestPlanUiScenarioCaseService;
|
||||
|
||||
@Resource
|
||||
private TestPlanMapper testPlanMapper;
|
||||
|
||||
@Resource
|
||||
private RedisTemplateService redisTemplateService;
|
||||
|
||||
/**
|
||||
* 执行测试计划流程是会调用其它服务的执行方法,并通过kafka传递信息给test-track服务来判断测试计划是否执行结束
|
||||
* 执行方法采用单独的事务控制,执行完了就提交,让测试报告以及包括执行内容的数据及时入库
|
||||
*/
|
||||
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
|
||||
public String runTestPlan(String testPlanId, String projectId, String userId, String triggerMode, String planReportId, String executionWay, String apiRunConfig) {
|
||||
//获取运行模式
|
||||
RunModeConfigDTO runModeConfig = this.getRunModeConfig(apiRunConfig, executionWay, testPlanId);
|
||||
if (StringUtils.isEmpty(planReportId)) {
|
||||
planReportId = UUID.randomUUID().toString();
|
||||
}
|
||||
TestPlanReport testPlanReport = null;
|
||||
try {
|
||||
this.checkTestPlanCanRunning(testPlanId, projectId, runModeConfig);
|
||||
//创建测试报告,然后返回的ID重新赋值为resourceID,作为后续的参数
|
||||
TestPlanScheduleReportInfoDTO reportInfoDTO = testPlanService.genTestPlanReport(planReportId, testPlanId, userId, triggerMode, runModeConfig);
|
||||
testPlanReport = reportInfoDTO.getTestPlanReport();
|
||||
LoggerUtil.info("预生成测试计划报告【" + (reportInfoDTO.getTestPlanReport() != null ? reportInfoDTO.getTestPlanReport().getName() : StringUtils.EMPTY) + "】计划报告ID[" + planReportId + "]");
|
||||
this.execute(reportInfoDTO, runModeConfig, triggerMode, projectId, userId);
|
||||
} catch (Exception e) {
|
||||
//如果执行失败,要保证执行队列是否不被影响
|
||||
if (testPlanReport == null) {
|
||||
testPlanReport = new TestPlanReport();
|
||||
testPlanReport.setId(planReportId);
|
||||
}
|
||||
testPlanReportService.testPlanUnExecute(testPlanReport);
|
||||
Log.error("执行测试计划失败!", e);
|
||||
}
|
||||
return planReportId;
|
||||
}
|
||||
|
||||
private void checkTestPlanCanRunning(String testPlanId, String projectId, RunModeConfigDTO runModeConfig) throws Exception {
|
||||
|
||||
// 校验测试计划是否在执行中
|
||||
if (testPlanService.checkTestPlanIsRunning(testPlanId)) {
|
||||
|
||||
LogUtil.info("当前测试计划正在执行中,请稍后再试", testPlanId);
|
||||
MSException.throwException(Translator.get("test_plan_run_message"));
|
||||
}
|
||||
//检查执行资源池
|
||||
if (testPlanService.haveExecCase(testPlanId, true)) {
|
||||
testPlanService.verifyPool(projectId, runModeConfig);
|
||||
}
|
||||
}
|
||||
|
||||
private void execute(TestPlanScheduleReportInfoDTO reportInfoDTO, RunModeConfigDTO runModeConfig, String triggerMode, String projectId, String executeUser) throws Exception {
|
||||
CaseExecuteResult caseExecuteResult = new CaseExecuteResult();
|
||||
|
||||
CountDownLatch countDownLatch = this.countDownExecute(reportInfoDTO, caseExecuteResult, runModeConfig, triggerMode, projectId, executeUser);
|
||||
countDownLatch.await();
|
||||
LoggerUtil.info("开始生成测试计划报告内容 " + reportInfoDTO.getTestPlanReport().getId());
|
||||
testPlanReportService.createTestPlanReportContentReportIds(reportInfoDTO.getTestPlanReport().getId(),
|
||||
caseExecuteResult.getApiCaseDTO(), caseExecuteResult.getScenarioCases(), caseExecuteResult.getUiScenarios(), caseExecuteResult.getLoadCaseReportMap());
|
||||
if (!caseExecuteResult.isExecuting()) {
|
||||
MSException.throwException("测试计划执行失败,不存在可执行的用例!报告ID:[" + reportInfoDTO.getTestPlanReport().getTestPlanId() + "]");
|
||||
}
|
||||
}
|
||||
|
||||
private CountDownLatch countDownExecute(TestPlanScheduleReportInfoDTO reportInfoDTO, CaseExecuteResult caseExecuteResult, RunModeConfigDTO runModeConfig, String triggerMode, String projectId, String executeUser) {
|
||||
CountDownLatch countDownLatch = new CountDownLatch(4);
|
||||
try {
|
||||
this.executeApiCase(caseExecuteResult, reportInfoDTO.getApiTestCaseDataMap(), triggerMode,
|
||||
reportInfoDTO.getTestPlanReport().getId(), reportInfoDTO.getTestPlanReport().getTestPlanId(), executeUser, runModeConfig);
|
||||
} catch (Exception e) {
|
||||
LogUtil.error(e);
|
||||
} finally {
|
||||
countDownLatch.countDown();
|
||||
}
|
||||
|
||||
try {
|
||||
this.executeScenarioCase(caseExecuteResult, reportInfoDTO.getPlanScenarioIdMap(), triggerMode,
|
||||
reportInfoDTO.getTestPlanReport().getId(), reportInfoDTO.getTestPlanReport().getTestPlanId(), projectId, executeUser, runModeConfig);
|
||||
} catch (Exception e) {
|
||||
LogUtil.error(e);
|
||||
} finally {
|
||||
countDownLatch.countDown();
|
||||
}
|
||||
try {
|
||||
this.executeUiCase(caseExecuteResult, reportInfoDTO.getUiScenarioIdMap(), triggerMode,
|
||||
reportInfoDTO.getTestPlanReport().getId(), reportInfoDTO.getTestPlanReport().getTestPlanId(), projectId, executeUser, runModeConfig);
|
||||
} catch (Exception e) {
|
||||
LogUtil.error(e);
|
||||
} finally {
|
||||
countDownLatch.countDown();
|
||||
}
|
||||
try {
|
||||
this.executeLoadCase(caseExecuteResult, reportInfoDTO.getPerformanceIdMap(), triggerMode,
|
||||
reportInfoDTO.getTestPlanReport().getId(), runModeConfig);
|
||||
} catch (Exception e) {
|
||||
LogUtil.error(e);
|
||||
} finally {
|
||||
countDownLatch.countDown();
|
||||
}
|
||||
return countDownLatch;
|
||||
}
|
||||
|
||||
private void executeApiCase(CaseExecuteResult executeResult, Map<String, String> executeCase, String triggerMode, String testPlanReportId, String testPlanId, String executeUser, RunModeConfigDTO runModeConfig) {
|
||||
boolean executing = false;
|
||||
List<TestPlanApiDTO> apiTestCases = null;
|
||||
if (MapUtils.isNotEmpty(executeCase)) {
|
||||
try {
|
||||
apiTestCases = planTestPlanApiCaseService.getFailureListByIds(executeCase.keySet());
|
||||
} catch (Exception e) {
|
||||
LogUtil.error("测试计划执行查询接口用例失败!", e);
|
||||
}
|
||||
}
|
||||
if (CollectionUtils.isNotEmpty(apiTestCases)) {
|
||||
//执行接口案例任务
|
||||
LoggerUtil.info("开始执行测试计划接口用例 " + testPlanReportId);
|
||||
try {
|
||||
redisTemplateService.lock(testPlanReportId, TestPlanExecuteCaseType.API_CASE.name(), testPlanReportId);
|
||||
Map<String, String> apiCaseReportMap = testPlanService.executeApiTestCase(triggerMode, testPlanReportId, executeUser, testPlanId, runModeConfig);
|
||||
if (MapUtils.isNotEmpty(apiCaseReportMap)) {
|
||||
executing = true;
|
||||
for (TestPlanApiDTO dto : apiTestCases) {
|
||||
dto.setReportId(apiCaseReportMap.get(dto.getId()));
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
redisTemplateService.unlock(testPlanReportId, TestPlanExecuteCaseType.API_CASE.name(), testPlanReportId);
|
||||
apiTestCases = null;
|
||||
LoggerUtil.info("测试报告" + testPlanReportId + "本次执行测试计划接口用例失败! ", e);
|
||||
}
|
||||
}
|
||||
executeResult.setApiCaseExecuting(executing);
|
||||
executeResult.setApiCaseDTO(apiTestCases);
|
||||
}
|
||||
|
||||
private void executeScenarioCase(CaseExecuteResult executeResult, Map<String, String> executeCase, String triggerMode, String testPlanReportId, String testPlanId, String projectId, String executeUser, RunModeConfigDTO runModeConfig) {
|
||||
boolean executing = false;
|
||||
List<TestPlanScenarioDTO> scenarioCases = null;
|
||||
if (MapUtils.isNotEmpty(executeCase)) {
|
||||
try {
|
||||
scenarioCases = planTestPlanScenarioCaseService.getFailureListByIds(executeCase.keySet());
|
||||
} catch (Exception e) {
|
||||
LogUtil.error("测试计划执行查询场景用例失败!", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (CollectionUtils.isNotEmpty(scenarioCases)) {
|
||||
//执行场景执行任务
|
||||
LoggerUtil.info("开始执行测试计划场景用例 " + testPlanReportId);
|
||||
try {
|
||||
|
||||
redisTemplateService.lock(testPlanReportId, TestPlanExecuteCaseType.SCENARIO.name(), testPlanReportId);
|
||||
Map<String, String> scenarioReportMap = testPlanService.executeScenarioCase(testPlanReportId, testPlanId, projectId, runModeConfig, triggerMode, executeUser, executeCase);
|
||||
if (MapUtils.isNotEmpty(scenarioReportMap)) {
|
||||
executing = true;
|
||||
List<TestPlanScenarioDTO> removeDTO = new ArrayList<>();
|
||||
for (TestPlanScenarioDTO dto : scenarioCases) {
|
||||
if (scenarioReportMap.containsKey(dto.getId())) {
|
||||
dto.setReportId(scenarioReportMap.get(dto.getId()));
|
||||
} else {
|
||||
removeDTO.add(dto);
|
||||
}
|
||||
}
|
||||
if (CollectionUtils.isNotEmpty(removeDTO)) {
|
||||
scenarioCases.removeAll(removeDTO);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
redisTemplateService.unlock(testPlanReportId, TestPlanExecuteCaseType.SCENARIO.name(), testPlanReportId);
|
||||
scenarioCases = null;
|
||||
LoggerUtil.info("测试报告" + testPlanReportId + "本次执行测试计划场景用例失败! ", e);
|
||||
}
|
||||
}
|
||||
executeResult.setScenarioCases(scenarioCases);
|
||||
executeResult.setScenarioExecuting(executing);
|
||||
}
|
||||
|
||||
private void executeUiCase(CaseExecuteResult executeResult, Map<String, String> executeCase, String triggerMode, String testPlanReportId, String testPlanId, String projectId, String executeUser, RunModeConfigDTO runModeConfig) {
|
||||
boolean executing = false;
|
||||
List<TestPlanUiScenarioDTO> uiScenarios = null;
|
||||
if (MapUtils.isNotEmpty(executeCase)) {
|
||||
try {
|
||||
uiScenarios = planTestPlanUiScenarioCaseService.getFailureListByIds(executeCase.keySet());
|
||||
} catch (Exception e) {
|
||||
LogUtil.error("测试计划执行查询UI用例失败!", e);
|
||||
}
|
||||
}
|
||||
if (CollectionUtils.isNotEmpty(uiScenarios)) {
|
||||
//执行UI场景执行任务
|
||||
LoggerUtil.info("开始执行测试计划 UI 场景用例 " + testPlanReportId);
|
||||
try {
|
||||
redisTemplateService.lock(testPlanReportId, TestPlanExecuteCaseType.UI_SCENARIO.name(), testPlanReportId);
|
||||
Map<String, String> uiScenarioReportMap = testPlanService.executeUiScenarioCase(testPlanReportId, testPlanId, projectId, runModeConfig, triggerMode, executeUser, executeCase);
|
||||
if (MapUtils.isNotEmpty(uiScenarioReportMap)) {
|
||||
executing = true;
|
||||
for (TestPlanUiScenarioDTO dto : uiScenarios) {
|
||||
dto.setReportId(uiScenarioReportMap.get(dto.getId()));
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
redisTemplateService.unlock(testPlanReportId, TestPlanExecuteCaseType.UI_SCENARIO.name(), testPlanReportId);
|
||||
uiScenarios = null;
|
||||
LoggerUtil.info("测试报告" + testPlanReportId + "本次执行测试计划 UI 用例失败! ", e);
|
||||
}
|
||||
}
|
||||
executeResult.setUiScenarios(uiScenarios);
|
||||
executeResult.setUiScenarioExecuting(executing);
|
||||
}
|
||||
|
||||
private void executeLoadCase(CaseExecuteResult executeResult, Map<String, String> executeCase, String triggerMode, String testPlanReportId, RunModeConfigDTO runModeConfig) {
|
||||
boolean executing = false;
|
||||
|
||||
Map<String, String> loadCaseReportMap = null;
|
||||
|
||||
if (MapUtils.isNotEmpty(executeCase)) {
|
||||
//执行性能测试任务
|
||||
LoggerUtil.info("开始执行测试计划性能用例 " + testPlanReportId);
|
||||
try {
|
||||
redisTemplateService.lock(testPlanReportId, TestPlanExecuteCaseType.LOAD_CASE.name(), testPlanReportId);
|
||||
loadCaseReportMap = perfExecService.executeLoadCase(testPlanReportId, runModeConfig, testPlanService.transformationPerfTriggerMode(triggerMode), executeCase);
|
||||
if (MapUtils.isNotEmpty(loadCaseReportMap)) {
|
||||
executing = true;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
redisTemplateService.unlock(testPlanReportId, TestPlanExecuteCaseType.LOAD_CASE.name(), testPlanReportId);
|
||||
LoggerUtil.info("测试报告" + testPlanReportId + "本次执行测试计划性能用例失败! ", e);
|
||||
}
|
||||
}
|
||||
|
||||
executeResult.setLoadCaseReportMap(loadCaseReportMap);
|
||||
executeResult.setLoadCaseExecuting(executing);
|
||||
}
|
||||
|
||||
private RunModeConfigDTO getRunModeConfig(String apiRunConfig, String executionWay, String testPlanId) {
|
||||
RunModeConfigDTO runModeConfig = null;
|
||||
try {
|
||||
runModeConfig = JSON.parseObject(apiRunConfig, RunModeConfigDTO.class);
|
||||
} catch (Exception e) {
|
||||
LogUtil.error(e);
|
||||
}
|
||||
if (runModeConfig == null) {
|
||||
runModeConfig = this.buildRunModeConfigDTO();
|
||||
}
|
||||
|
||||
//环境参数为空时,依据测试计划保存的环境执行
|
||||
if (((StringUtils.equals("GROUP", runModeConfig.getEnvironmentType()) && StringUtils.isBlank(runModeConfig.getEnvironmentGroupId()))
|
||||
|| (!StringUtils.equals("GROUP", runModeConfig.getEnvironmentType()) && MapUtils.isEmpty(runModeConfig.getEnvMap()) && MapUtils.isEmpty(runModeConfig.getTestPlanDefaultEnvMap())))
|
||||
&& !StringUtils.equals(executionWay, ExecutionWay.RUN.name())) {
|
||||
TestPlanWithBLOBs testPlanWithBLOBs = testPlanMapper.selectByPrimaryKey(testPlanId);
|
||||
if (StringUtils.isNotEmpty(testPlanWithBLOBs.getRunModeConfig())) {
|
||||
try {
|
||||
Map json = JSON.parseMap(testPlanWithBLOBs.getRunModeConfig());
|
||||
TestPlanRequestUtil.changeStringToBoolean(json);
|
||||
TestPlanRunRequest testPlanRunRequest = JSON.parseObject(JSON.toJSONString(json), TestPlanRunRequest.class);
|
||||
if (testPlanRunRequest != null) {
|
||||
String envType = testPlanRunRequest.getEnvironmentType();
|
||||
Map<String, String> envMap = testPlanRunRequest.getEnvMap();
|
||||
String environmentGroupId = testPlanRunRequest.getEnvironmentGroupId();
|
||||
runModeConfig = testPlanService.getRunModeConfigDTO(testPlanRunRequest, envType, envMap, environmentGroupId, testPlanId);
|
||||
runModeConfig.setTestPlanDefaultEnvMap(testPlanRunRequest.getTestPlanDefaultEnvMap());
|
||||
if (!testPlanRunRequest.isRunWithinResourcePool()) {
|
||||
runModeConfig.setResourcePoolId(null);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LogUtil.error("获取测试计划保存的环境信息出错!", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return runModeConfig;
|
||||
}
|
||||
|
||||
private RunModeConfigDTO buildRunModeConfigDTO() {
|
||||
RunModeConfigDTO runModeConfig = new RunModeConfigDTO();
|
||||
runModeConfig.setMode(RunModeConstants.SERIAL.name());
|
||||
runModeConfig.setReportType("iddReport");
|
||||
runModeConfig.setEnvMap(new HashMap<>());
|
||||
runModeConfig.setOnSampleError(false);
|
||||
return runModeConfig;
|
||||
}
|
||||
}
|
||||
|
||||
@Data
|
||||
class CaseExecuteResult {
|
||||
private boolean apiCaseExecuting;
|
||||
private boolean scenarioExecuting;
|
||||
private boolean uiScenarioExecuting;
|
||||
private boolean loadCaseExecuting;
|
||||
|
||||
|
||||
private List<TestPlanApiDTO> apiCaseDTO;
|
||||
private List<TestPlanScenarioDTO> scenarioCases;
|
||||
private List<TestPlanUiScenarioDTO> uiScenarios;
|
||||
private Map<String, String> loadCaseReportMap;
|
||||
|
||||
public boolean isExecuting() {
|
||||
return apiCaseExecuting || scenarioExecuting || uiScenarioExecuting || loadCaseExecuting;
|
||||
}
|
||||
}
|
|
@ -7,6 +7,7 @@
|
|||
@close="close"
|
||||
:visible.sync="runModeVisible"
|
||||
>
|
||||
|
||||
<div class="env-container">
|
||||
<div>
|
||||
<div>{{ $t("commons.environment") }}:</div>
|
||||
|
@ -16,6 +17,7 @@
|
|||
:project-env-map="projectEnvListMap"
|
||||
:environment-type.sync="runConfig.environmentType"
|
||||
:has-option-group="true"
|
||||
:is-env-saved="isEnvSaved"
|
||||
:group-id="runConfig.environmentGroupId"
|
||||
@setProjectEnvMap="setProjectEnvMap"
|
||||
@setDefaultEnv="setDefaultEnv"
|
||||
|
@ -59,13 +61,12 @@
|
|||
<div>
|
||||
<div class="mode-row">{{ $t("run_mode.other_config") }}:</div>
|
||||
<div>
|
||||
<!-- 串行 -->
|
||||
<!-- 资源池 -->
|
||||
<div
|
||||
class="mode-row"
|
||||
v-if="
|
||||
runConfig.mode === 'serial' &&
|
||||
testType === 'API' &&
|
||||
haveOtherExecCase
|
||||
(haveOtherExecCase && !haveUICase)
|
||||
"
|
||||
>
|
||||
<span>{{ $t("run_mode.run_with_resource_pool") }}: </span>
|
||||
|
@ -83,31 +84,6 @@
|
|||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
<!-- 并行 -->
|
||||
<div
|
||||
class="mode-row"
|
||||
v-if="
|
||||
runConfig.mode === 'parallel' &&
|
||||
testType === 'API' &&
|
||||
haveOtherExecCase
|
||||
"
|
||||
>
|
||||
<span>{{ $t("run_mode.run_with_resource_pool") }}: </span>
|
||||
<el-select
|
||||
v-model="runConfig.resourcePoolId"
|
||||
size="mini"
|
||||
style="width: 100%; margin-top: 8px"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in resourcePools"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:disabled="!item.api"
|
||||
:value="item.id"
|
||||
>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
|
||||
<!-- 失败重试 -->
|
||||
<div class="mode-row">
|
||||
|
@ -164,7 +140,8 @@
|
|||
<el-button @click="close">{{ $t("commons.cancel") }}</el-button>
|
||||
<el-dropdown @command="handleCommand" style="margin-left: 5px">
|
||||
<el-button type="primary">
|
||||
{{ $t("api_test.run")
|
||||
{{
|
||||
$t("api_test.run")
|
||||
}}<i class="el-icon-arrow-down el-icon--right"></i>
|
||||
</el-button>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
|
@ -233,7 +210,7 @@ export default {
|
|||
resourcePoolId: null,
|
||||
envMap: new Map(),
|
||||
environmentGroupId: "",
|
||||
environmentType: ENV_TYPE.JSON,
|
||||
environmentType: ENV_TYPE.DEFAULT,
|
||||
retryEnable: false,
|
||||
retryNum: 1,
|
||||
browser: "CHROME",
|
||||
|
@ -241,6 +218,8 @@ export default {
|
|||
},
|
||||
projectList: [],
|
||||
projectIds: new Set(),
|
||||
//环境是否保存过。未保存过的话展示的是”用例环境“
|
||||
isEnvSaved: true,
|
||||
options: [
|
||||
{
|
||||
value: "confirmAndRun",
|
||||
|
@ -279,6 +258,7 @@ export default {
|
|||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
//是否有其他用例(性能测试除外)
|
||||
haveOtherExecCase: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
|
@ -289,11 +269,22 @@ export default {
|
|||
this.defaultEnvMap = {};
|
||||
if (runModeConfig) {
|
||||
this.runConfig = JSON.parse(runModeConfig);
|
||||
if (!this.runConfig.envMap || JSON.stringify(this.runConfig.envMap) === "{}") {
|
||||
this.isEnvSaved = false;
|
||||
this.runConfig.environmentType = ENV_TYPE.DEFAULT;
|
||||
} else {
|
||||
this.isEnvSaved = true;
|
||||
this.runConfig.environmentType = ENV_TYPE.JSON;
|
||||
}
|
||||
this.runConfig.envMap = new Map();
|
||||
this.runConfig.testPlanDefaultEnvMap = {};
|
||||
this.runConfig.onSampleError =
|
||||
this.runConfig.onSampleError === "true" ||
|
||||
this.runConfig.onSampleError === true;
|
||||
} else {
|
||||
this.isEnvSaved = false;
|
||||
//没保存过运行配置的测试计划,运行环境类型为default
|
||||
this.runConfig.environmentType = ENV_TYPE.DEFAULT;
|
||||
}
|
||||
this.runModeVisible = true;
|
||||
this.testType = testType;
|
||||
|
@ -464,6 +455,7 @@ export default {
|
|||
overflow-y: auto;
|
||||
padding-bottom: 1px;
|
||||
}
|
||||
|
||||
.env-container .title {
|
||||
width: 100px;
|
||||
min-width: 100px;
|
||||
|
@ -486,6 +478,7 @@ export default {
|
|||
.radio-change:deep(.el-radio__input.is-checked + .el-radio__label) {
|
||||
color: #606266 !important;
|
||||
}
|
||||
|
||||
.radio-change:deep(.el-checkbox__input.is-checked + .el-checkbox__label) {
|
||||
color: #606266 !important;
|
||||
}
|
||||
|
|
|
@ -78,20 +78,21 @@
|
|||
<crontab-result :ex="form.cronValue" ref="crontabResult"/>
|
||||
</el-form>
|
||||
|
||||
<div v-if="haveUICase || haveOtherExecCase">
|
||||
<div class="el-step__icon is-text" style="margin-right: 10px">
|
||||
<div class="el-step__icon-inner">2</div>
|
||||
</div>
|
||||
<span>{{ $t("load_test.runtime_config") }}</span>
|
||||
<div class="ms-mode-div">
|
||||
<span class="ms-mode-span">{{ $t("run_mode.title") }}:</span>
|
||||
<el-radio-group v-model="runConfig.mode" @change="changeMode">
|
||||
<el-radio-group v-if="haveUICase || haveOtherExecCase" v-model="runConfig.mode" @change="changeMode">
|
||||
<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 style="margin-top: 10px">
|
||||
<div v-if="haveUICase" style="margin-top: 10px">
|
||||
<span class="ms-mode-span">{{ $t("浏览器") }}:</span>
|
||||
<el-select
|
||||
size="mini"
|
||||
|
@ -106,7 +107,7 @@
|
|||
></el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="ms-mode-div" v-if="runConfig.mode === 'serial'">
|
||||
<div class="ms-mode-div" v-if="(haveUICase || haveOtherExecCase) && runConfig.mode === 'serial'">
|
||||
<el-row>
|
||||
<el-col :span="3">
|
||||
<span class="ms-mode-span"
|
||||
|
@ -114,7 +115,7 @@
|
|||
>
|
||||
</el-col>
|
||||
<el-col :span="18">
|
||||
<div v-if="testType === 'API'">
|
||||
<div v-if="haveOtherExecCase && testType === 'API'">
|
||||
<sapn>{{ $t("run_mode.run_with_resource_pool") }}:</sapn>
|
||||
<el-select
|
||||
v-model="runConfig.resourcePoolId"
|
||||
|
@ -132,7 +133,7 @@
|
|||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
<div class="ms-mode-div" v-if="runConfig.mode === 'parallel'">
|
||||
<div class="ms-mode-div" v-if="(haveUICase || haveOtherExecCase) && runConfig.mode === 'parallel'">
|
||||
<el-row>
|
||||
<el-col :span="3">
|
||||
<span class="ms-mode-span"
|
||||
|
@ -140,7 +141,7 @@
|
|||
>
|
||||
</el-col>
|
||||
<el-col :span="18">
|
||||
<div v-if="testType === 'API'">
|
||||
<div v-if="haveOtherExecCase && testType === 'API'">
|
||||
<span>
|
||||
{{ $t("run_mode.run_with_resource_pool") }} :
|
||||
</span>
|
||||
|
@ -209,7 +210,7 @@
|
|||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
<div>
|
||||
<div v-if="haveUICase">
|
||||
<el-row>
|
||||
<el-col :span="3"> </el-col>
|
||||
<el-col :span="18">
|
||||
|
@ -221,7 +222,7 @@
|
|||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<el-dialog
|
||||
width="60%"
|
||||
:title="$t('schedule.generate_expression')"
|
||||
|
@ -250,15 +251,8 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
getCurrentProjectID,
|
||||
getCurrentUser,
|
||||
getCurrentWorkspaceId,
|
||||
} from "metersphere-frontend/src/utils/token";
|
||||
import {
|
||||
listenGoBack,
|
||||
removeGoBackListener,
|
||||
} from "metersphere-frontend/src/utils";
|
||||
import {getCurrentProjectID, getCurrentUser, getCurrentWorkspaceId,} from "metersphere-frontend/src/utils/token";
|
||||
import {listenGoBack, removeGoBackListener,} from "metersphere-frontend/src/utils";
|
||||
import Crontab from "metersphere-frontend/src/components/cron/Crontab";
|
||||
import CrontabResult from "metersphere-frontend/src/components/cron/CrontabResult";
|
||||
import {cronValidate} from "metersphere-frontend/src/utils/cron";
|
||||
|
@ -276,7 +270,6 @@ import { saveNotice } from "@/api/notice";
|
|||
import {getProjectMember} from "@/api/user";
|
||||
import {getQuotaValidResourcePools} from "@/api/remote/resource-pool";
|
||||
import {getProjectConfig} from "@/api/project";
|
||||
import { getSystemBaseSetting } from "metersphere-frontend/src/api/system";
|
||||
|
||||
function defaultCustomValidate() {
|
||||
return {pass: true};
|
||||
|
@ -308,6 +301,11 @@ export default {
|
|||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
//是否有其他用例(性能测试除外)
|
||||
haveOtherExecCase: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
|
|
|
@ -122,7 +122,8 @@
|
|||
<span v-if="scope.row.scheduleStatus === 'OPEN'">
|
||||
<el-tooltip placement="bottom-start" effect="light">
|
||||
<div slot="content">
|
||||
{{ $t("home.table.run_rule") }}: {{ scope.row.scheduleCorn
|
||||
{{ $t("home.table.run_rule") }}: {{
|
||||
scope.row.scheduleCorn
|
||||
}}<br/>
|
||||
{{ $t("test_track.plan.next_run_time") }}:<span>{{
|
||||
scope.row.scheduleExecuteTime | datetimeFormat
|
||||
|
@ -424,6 +425,7 @@
|
|||
:plan-case-ids="[]"
|
||||
:type="'plan'"
|
||||
:have-u-i-case="haveUICase"
|
||||
:have-other-exec-case="haveOtherExecCase"
|
||||
/>
|
||||
<ms-test-plan-schedule-batch-switch
|
||||
ref="scheduleBatchSwitch"
|
||||
|
@ -487,7 +489,7 @@ import HeaderLabelOperate from "metersphere-frontend/src/components/head/HeaderL
|
|||
import MsTag from "metersphere-frontend/src/components/MsTag";
|
||||
import MsTestPlanScheduleMaintain from "@/business/plan/components/ScheduleMaintain";
|
||||
import {getCurrentProjectID, getCurrentUser, getCurrentUserId,} from "metersphere-frontend/src/utils/token";
|
||||
import {hasLicense, hasPermission,} from "metersphere-frontend/src/utils/permission";
|
||||
import {hasPermission,} from "metersphere-frontend/src/utils/permission";
|
||||
import {operationConfirm} from "metersphere-frontend/src/utils";
|
||||
import MsTestPlanRunModeWithEnv from "@/business/plan/common/TestPlanRunModeWithEnv";
|
||||
import MsTaskCenter from "metersphere-frontend/src/components/task/TaskCenter";
|
||||
|
@ -500,8 +502,6 @@ import {
|
|||
testPlanEditFollows,
|
||||
testPlanEditRunConfig,
|
||||
testPlanGetEnableScheduleCount,
|
||||
testPlanHaveExecCase,
|
||||
testPlanHaveUiCase,
|
||||
testPlanList,
|
||||
testPlanMetric,
|
||||
testPlanRun,
|
||||
|
@ -634,7 +634,7 @@ export default {
|
|||
batchExecuteType: "serial",
|
||||
//是否有UI执行用例
|
||||
haveUICase: false,
|
||||
//是否有API/性能执行用例
|
||||
//是否有API执行用例
|
||||
haveOtherExecCase: false,
|
||||
};
|
||||
},
|
||||
|
@ -763,31 +763,6 @@ export default {
|
|||
this.$set(item, "passed", metricData.passed);
|
||||
this.$set(item, "tested", metricData.tested);
|
||||
this.$set(item, "total", metricData.total);
|
||||
this.$set(
|
||||
item,
|
||||
"testPlanTestCaseCount",
|
||||
metricData.testPlanTestCaseCount
|
||||
);
|
||||
this.$set(
|
||||
item,
|
||||
"testPlanApiCaseCount",
|
||||
metricData.testPlanApiCaseCount
|
||||
);
|
||||
this.$set(
|
||||
item,
|
||||
"testPlanApiScenarioCount",
|
||||
metricData.testPlanApiScenarioCount
|
||||
);
|
||||
this.$set(
|
||||
item,
|
||||
"testPlanUiScenarioCount",
|
||||
metricData.testPlanUiScenarioCount
|
||||
);
|
||||
this.$set(
|
||||
item,
|
||||
"testPlanLoadCaseCount",
|
||||
metricData.testPlanLoadCaseCount
|
||||
);
|
||||
if (metricData.followUsers) {
|
||||
let data = metricData.followUsers;
|
||||
let follow = "";
|
||||
|
@ -820,7 +795,7 @@ export default {
|
|||
});
|
||||
},
|
||||
resetTestPlanRow(item) {
|
||||
if (!isMetricLoadOver) {
|
||||
if (!item.isMetricLoadOver) {
|
||||
return;
|
||||
}
|
||||
this.$set(item, "isMetricLoadOver", true);
|
||||
|
@ -834,11 +809,6 @@ export default {
|
|||
this.$set(item, "passed", 0);
|
||||
this.$set(item, "tested", 0);
|
||||
this.$set(item, "total", 0);
|
||||
this.$set(item, "testPlanTestCaseCount", 0);
|
||||
this.$set(item, "testPlanApiCaseCount", 0);
|
||||
this.$set(item, "testPlanApiScenarioCount", 0);
|
||||
this.$set(item, "testPlanUiScenarioCount", 0);
|
||||
this.$set(item, "testPlanLoadCaseCount", 0);
|
||||
},
|
||||
copyData(status) {
|
||||
return JSON.parse(JSON.stringify(this.dataMap.get(status)));
|
||||
|
@ -1049,8 +1019,19 @@ export default {
|
|||
this.$refs.testCaseReportView.open(plan);
|
||||
},
|
||||
async scheduleTask(row) {
|
||||
this.haveUICase = false;
|
||||
this.haveOtherExecCase = false;
|
||||
|
||||
row.redirectFrom = "testPlan";
|
||||
this.currentPlanId = row.id;
|
||||
|
||||
this.haveUICase = row.testPlanUiScenarioCount > 0;
|
||||
let haveApiCase = row.testPlanApiCaseCount > 0;
|
||||
let haveScenarioCase = row.testPlanApiScenarioCount > 0;
|
||||
if (haveApiCase || haveScenarioCase) {
|
||||
this.haveOtherExecCase = true;
|
||||
}
|
||||
|
||||
this.$refs.scheduleMaintain.open(row);
|
||||
},
|
||||
saveSortField(key, orders) {
|
||||
|
@ -1074,18 +1055,27 @@ export default {
|
|||
});
|
||||
},
|
||||
handleRun(row) {
|
||||
this.haveUICase = false;
|
||||
this.haveOtherExecCase = false;
|
||||
|
||||
this.currentPlanId = row.id;
|
||||
this.haveUIScenario().then(() => {
|
||||
testPlanHaveExecCase(row.id).then(async (res) => {
|
||||
this.haveOtherExecCase = res.data;
|
||||
let haveApiCase = row.testPlanApiCaseCount > 0;
|
||||
let haveScenarioCase = row.testPlanApiScenarioCount > 0;
|
||||
let haveLoadCase = row.testPlanLoadCaseCount > 0;
|
||||
this.haveUICase = row.testPlanUiScenarioCount > 0;
|
||||
|
||||
if (!this.haveUICase && !haveApiCase && !haveScenarioCase && haveLoadCase) {
|
||||
//只有性能测试,则直接执行
|
||||
this.$refs.runMode.handleCommand("run");
|
||||
} else if (haveApiCase || haveScenarioCase || this.haveUICase) {
|
||||
this.haveOtherExecCase = true;
|
||||
//因为ui没有资源池,这里必须分离两个变量
|
||||
if (this.haveOtherExecCase || this.haveUICase) {
|
||||
this.$refs.runMode.open("API", row.runModeConfig);
|
||||
} else {
|
||||
//没有可执行的资源,则直接跳转到计划里
|
||||
this.$router.push("/track/plan/view/" + row.id);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
},
|
||||
_handleRun(config) {
|
||||
let defaultPlanEnvMap = config.testPlanDefaultEnvMap;
|
||||
|
@ -1183,18 +1173,18 @@ export default {
|
|||
return;
|
||||
}
|
||||
},
|
||||
haveUIScenario() {
|
||||
if (hasLicense()) {
|
||||
return new Promise((resolve) => {
|
||||
testPlanHaveUiCase(this.currentPlanId).then((r) => {
|
||||
this.haveUICase = r.data;
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
} else {
|
||||
return new Promise((resolve) => resolve());
|
||||
}
|
||||
},
|
||||
// haveUIScenario() {
|
||||
// if (hasLicense()) {
|
||||
// return new Promise((resolve) => {
|
||||
// testPlanHaveUiCase(this.currentPlanId).then((r) => {
|
||||
// this.haveUICase = r.data;
|
||||
// resolve();
|
||||
// });
|
||||
// });
|
||||
// } else {
|
||||
// return new Promise((resolve) => resolve());
|
||||
// }
|
||||
// },
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -1,16 +1,23 @@
|
|||
<template>
|
||||
<div>
|
||||
<!-- {{ JSON.stringify(eventData) }}-->
|
||||
<el-radio-group
|
||||
v-model="radio"
|
||||
style="width: 100%"
|
||||
@change="radioChange"
|
||||
class="radio-change"
|
||||
>
|
||||
<el-radio v-show="!isEnvSaved" :label="ENV_TYPE.DEFAULT">{{
|
||||
$t("workspace.env_group.case_env")
|
||||
}}
|
||||
</el-radio>
|
||||
<el-radio :label="ENV_TYPE.JSON">{{
|
||||
$t("workspace.env_group.env_list")
|
||||
}}</el-radio>
|
||||
}}
|
||||
</el-radio>
|
||||
<el-radio :label="ENV_TYPE.GROUP" v-if="showEnvGroup"
|
||||
>{{ $t("workspace.env_group.name")
|
||||
>{{
|
||||
$t("workspace.env_group.name")
|
||||
}}<i class="el-icon-tickets mode-span" @click="viewGroup"></i
|
||||
></el-radio>
|
||||
</el-radio-group>
|
||||
|
@ -47,10 +54,12 @@
|
|||
>
|
||||
<el-radio label="DEFAULT_ENV" style="margin-top: 7px">{{
|
||||
$t("api_test.environment.default_environment")
|
||||
}}</el-radio>
|
||||
}}
|
||||
</el-radio>
|
||||
<el-radio label="CUSTOMIZE_ENV" style="margin-top: 7px">{{
|
||||
$t("api_test.environment.choose_new_environment")
|
||||
}}</el-radio>
|
||||
}}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
<el-tag
|
||||
v-show="!pe.showEnvSelect"
|
||||
|
@ -58,7 +67,8 @@
|
|||
:key="index"
|
||||
size="mini"
|
||||
style="margin-left: 0; margin-right: 2px; margin-top: 8px"
|
||||
>{{ itemName }}</el-tag
|
||||
>{{ itemName }}
|
||||
</el-tag
|
||||
>
|
||||
<el-select
|
||||
v-show="pe.showEnvSelect"
|
||||
|
@ -142,10 +152,7 @@
|
|||
|
||||
<script>
|
||||
import {ENV_TYPE} from "metersphere-frontend/src/utils/constants";
|
||||
import {
|
||||
environmentGetALL,
|
||||
getEnvironmentOptions,
|
||||
} from "metersphere-frontend/src/api/environment";
|
||||
import {environmentGetALL, getEnvironmentOptions,} from "metersphere-frontend/src/api/environment";
|
||||
import MsTag from "metersphere-frontend/src/components/MsTag";
|
||||
import EnvironmentGroup from "@/business/plan/env/EnvironmentGroupList";
|
||||
import {getEnvironmentByProjectId} from "@/api/remote/api/api-environment";
|
||||
|
@ -189,6 +196,11 @@ export default {
|
|||
},
|
||||
projectIds: Set,
|
||||
projectList: Array,
|
||||
//环境是否保存过
|
||||
isEnvSaved: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
projectEnvMap: Object,
|
||||
envMap: Map,
|
||||
environmentType: String,
|
||||
|
|
|
@ -75,14 +75,8 @@
|
|||
<script>
|
||||
import TestCaseRelevanceBase from "../base/TestCaseRelevanceBase";
|
||||
import MxVersionSelect from "metersphere-frontend/src/components/version/MxVersionSelect";
|
||||
import {
|
||||
apiDefinitionListBatch,
|
||||
apiDefinitionRelevance,
|
||||
} from "@/api/remote/api/api-definition";
|
||||
import {
|
||||
apiTestCaseListBlobs,
|
||||
apiTestCaseRelevance,
|
||||
} from "@/api/remote/api/api-case";
|
||||
import {apiDefinitionListBatch, apiDefinitionRelevance,} from "@/api/remote/api/api-definition";
|
||||
import {apiTestCaseListBlobs, apiTestCaseRelevance,} from "@/api/remote/api/api-case";
|
||||
import RelevanceApiList from "@/business/plan/view/comonents/api/RelevanceApiList";
|
||||
import RelevanceCaseList from "@/business/plan/view/comonents/api/RelevanceCaseList";
|
||||
import MsApiModule from "@/business/plan/view/comonents/api/module/ApiModule";
|
||||
|
@ -244,13 +238,6 @@ export default {
|
|||
|
||||
postRelevance(relevanceList, environmentId, selectIds, protocol) {
|
||||
let param = {};
|
||||
if (protocol !== "DUBBO") {
|
||||
if (!environmentId) {
|
||||
this.isSaving = false;
|
||||
this.$warning(this.$t("api_test.environment.select_environment"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (selectIds.length < 1) {
|
||||
this.isSaving = false;
|
||||
this.$warning(this.$t("test_track.plan_view.please_choose_test_case"));
|
||||
|
|
|
@ -36,12 +36,7 @@
|
|||
import TestCaseRelevanceBase from "../base/TestCaseRelevanceBase";
|
||||
import RelevanceScenarioList from "./RelevanceScenarioList";
|
||||
import {ENV_TYPE} from "metersphere-frontend/src/utils/constants";
|
||||
import {
|
||||
getCurrentProjectID,
|
||||
hasLicense,
|
||||
strMapToObj,
|
||||
} from "@/business/utils/sdk-utils";
|
||||
import { getVersionFilters } from "@/business/utils/sdk-utils";
|
||||
import {getCurrentProjectID, getVersionFilters, hasLicense, strMapToObj,} from "@/business/utils/sdk-utils";
|
||||
import {testPlanAutoCheck} from "@/api/remote/plan/test-plan";
|
||||
import {scenarioRelevance} from "@/api/remote/plan/test-plan-scenario";
|
||||
import MsApiScenarioModule from "@/business/plan/view/comonents/api/module/ApiScenarioModule";
|
||||
|
@ -137,11 +132,6 @@ export default {
|
|||
},
|
||||
async saveCaseRelevance() {
|
||||
this.isSaving = true;
|
||||
const sign = await this.$refs.apiScenarioList.checkEnv();
|
||||
if (!sign) {
|
||||
this.isSaving = false;
|
||||
return false;
|
||||
}
|
||||
let selectIds = [];
|
||||
let selectRows = this.$refs.apiScenarioList.selectRows;
|
||||
const envMap = this.$refs.apiScenarioList.projectEnvMap;
|
||||
|
@ -157,15 +147,7 @@ export default {
|
|||
selectRows.forEach((row) => {
|
||||
selectIds.push(row.id);
|
||||
});
|
||||
if (envType === ENV_TYPE.JSON && (!envMap || envMap.size < 1)) {
|
||||
this.isSaving = false;
|
||||
this.$warning(this.$t("api_test.environment.select_environment"));
|
||||
return false;
|
||||
} else if (envType === ENV_TYPE.GROUP && !envGroupId) {
|
||||
this.isSaving = false;
|
||||
this.$warning(this.$t("api_test.environment.select_environment"));
|
||||
return false;
|
||||
}
|
||||
|
||||
let param = {};
|
||||
param.planId = this.planId;
|
||||
param.mapping = strMapToObj(map);
|
||||
|
|
|
@ -124,6 +124,9 @@ export default {
|
|||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.initProtocol();
|
||||
},
|
||||
watch: {
|
||||
'condition.filterText'() {
|
||||
this.filter();
|
||||
|
|
|
@ -163,12 +163,6 @@ export default {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!envMap || envMap.size == 0) {
|
||||
this.isSaving = false;
|
||||
this.$warning(this.$t("api_test.environment.select_environment"));
|
||||
return;
|
||||
}
|
||||
|
||||
let param = {};
|
||||
param.planId = this.planId;
|
||||
param.mapping = strMapToObj(map);
|
||||
|
|
Loading…
Reference in New Issue