feat(测试计划): 增加测试类型的失败停止和测试集的失败停止设置冲突时的失败停止支持&场景导出的页面优化
--bug=1047945 --user=宋天阳 【测试计划】-场景串行+失败停止,测试点A串行+失败不停止,测试点B并行,执行后,测试点2的场景全部执行了 https://www.tapd.cn/55049933/s/1597277
This commit is contained in:
parent
a53c2b75bf
commit
f2312e8da8
|
@ -31,6 +31,8 @@ public class ApiScenarioBatchExportRequest extends ApiScenarioBatchRequest imple
|
|||
@Schema(description = "排序字段(model中的字段 : asc/desc)")
|
||||
private Map<@Valid @Pattern(regexp = "^[A-Za-z]+$") String, @Valid @NotBlank String> sort;
|
||||
|
||||
private boolean exportAllRelatedData;
|
||||
|
||||
public String getSortString() {
|
||||
if (sort == null || sort.isEmpty()) {
|
||||
return null;
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package io.metersphere.api.service;
|
||||
|
||||
import io.metersphere.api.constants.ApiDefinitionStatus;
|
||||
import io.metersphere.api.constants.ApiScenarioExportType;
|
||||
import io.metersphere.api.constants.ApiScenarioStepType;
|
||||
import io.metersphere.api.domain.*;
|
||||
import io.metersphere.api.dto.ApiFile;
|
||||
|
@ -76,6 +75,7 @@ import java.io.File;
|
|||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
@ -1023,11 +1023,11 @@ public class ApiScenarioDataTransferService {
|
|||
Map<String, String> moduleMap = this.apiScenarioModuleService.getTree(request.getProjectId()).stream().collect(Collectors.toMap(BaseTreeNode::getId, BaseTreeNode::getPath));
|
||||
|
||||
String fileFolder = tmpDir.getPath() + File.separatorChar + request.getFileId();
|
||||
int fileIndex = 1;
|
||||
AtomicInteger fileIndex = new AtomicInteger(1);
|
||||
SubListUtils.dealForSubList(ids, 500, subList -> {
|
||||
request.setSelectIds(subList);
|
||||
ApiScenarioExportResponse exportResponse = this.genMetersphereExportResponse(request, moduleMap, exportType, userId);
|
||||
TempFileUtils.writeExportFile(fileFolder + File.separatorChar + "scenario_export_" + fileIndex + ".ms", exportResponse);
|
||||
ApiScenarioExportResponse exportResponse = this.genMetersphereExportResponse(request, moduleMap);
|
||||
TempFileUtils.writeExportFile(fileFolder + File.separatorChar + "scenario_" + fileIndex.getAndIncrement() + ".ms", exportResponse);
|
||||
});
|
||||
File zipFile = MsFileUtils.zipFile(tmpDir.getPath(), request.getFileId());
|
||||
if (zipFile == null) {
|
||||
|
@ -1058,13 +1058,13 @@ public class ApiScenarioDataTransferService {
|
|||
}
|
||||
}
|
||||
|
||||
private ApiScenarioExportResponse genMetersphereExportResponse(ApiScenarioBatchExportRequest request, Map<String, String> moduleMap, String exportType, String userId) {
|
||||
private ApiScenarioExportResponse genMetersphereExportResponse(ApiScenarioBatchExportRequest request, Map<String, String> moduleMap) {
|
||||
Project project = projectMapper.selectByPrimaryKey(request.getProjectId());
|
||||
MetersphereApiScenarioExportResponse response = apiScenarioService.selectAndSortScenarioDetailWithIds(request.getSelectIds(), moduleMap);
|
||||
response.setProjectId(project.getId());
|
||||
response.setOrganizationId(project.getOrganizationId());
|
||||
|
||||
if (StringUtils.equalsIgnoreCase(ApiScenarioExportType.METERSPHERE_ALL_DATA.name(), exportType)) {
|
||||
if (request.isExportAllRelatedData()) {
|
||||
// 全量导出,导出引用的api、apiCase
|
||||
List<String> apiDefinitionIdList = new ArrayList<>();
|
||||
List<String> apiCaseIdList = new ArrayList<>();
|
||||
|
|
|
@ -39,6 +39,7 @@ import java.util.ArrayList;
|
|||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
||||
|
@ -133,11 +134,12 @@ public class ApiDefinitionExportService {
|
|||
Map<String, String> moduleMap = this.buildModuleIdPathMap(request.getProjectId());
|
||||
|
||||
String fileFolder = tmpDir.getPath() + File.separatorChar + request.getFileId();
|
||||
int fileIndex = 1;
|
||||
|
||||
AtomicInteger fileIndex = new AtomicInteger(1);
|
||||
SubListUtils.dealForSubList(ids, 1000, subList -> {
|
||||
request.setSelectIds(subList);
|
||||
ApiDefinitionExportResponse exportResponse = this.genApiExportResponse(request, moduleMap, exportType, userId);
|
||||
TempFileUtils.writeExportFile(fileFolder + File.separatorChar + fileIndex + ".json", exportResponse);
|
||||
TempFileUtils.writeExportFile(fileFolder + File.separatorChar + "API_" + fileIndex.getAndIncrement() + ".json", exportResponse);
|
||||
});
|
||||
File zipFile = MsFileUtils.zipFile(tmpDir.getPath(), request.getFileId());
|
||||
if (zipFile == null) {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package io.metersphere.plan.mapper;
|
||||
|
||||
import io.metersphere.plan.domain.TestPlanCollection;
|
||||
import io.metersphere.plan.dto.TestPlanCollectionConfigDTO;
|
||||
import io.metersphere.plan.dto.TestPlanCollectionEnvDTO;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
@ -17,4 +18,6 @@ public interface ExtTestPlanCollectionMapper {
|
|||
String selectDefaultCollectionId(@Param("testPlanId")String newTestPlanId,@Param("type") String key);
|
||||
|
||||
boolean getParentStopOnFailure(String collectionId);
|
||||
|
||||
List<TestPlanCollection> selectByItemParentId(String collectionId);
|
||||
}
|
||||
|
|
|
@ -64,4 +64,10 @@
|
|||
from test_plan_collection
|
||||
where id IN (select parent_id from test_plan_collection where id = #{0})
|
||||
</select>
|
||||
<select id="selectByItemParentId" resultType="io.metersphere.plan.domain.TestPlanCollection">
|
||||
select *
|
||||
from test_plan_collection
|
||||
where parent_id = (select parent_id from test_plan_collection where id = #{0})
|
||||
ORDER BY pos
|
||||
</select>
|
||||
</mapper>
|
|
@ -50,4 +50,6 @@ public interface ExtTestPlanReportApiCaseMapper {
|
|||
List<TestPlanReportDetailCollectionResponse> listCollection(@Param("request") TestPlanReportDetailPageRequest request);
|
||||
|
||||
List<String> getIdsByReportIdAndCollectionId(@Param("testPlanReportId") String testPlanReportId, @Param("collectionId") String collectionId);
|
||||
|
||||
List<String> selectExecResultByReportIdAndCollectionId(@Param("collectionId") String collectionId, @Param("reportId") String prepareReportId);
|
||||
}
|
||||
|
|
|
@ -78,6 +78,12 @@
|
|||
and atc.deleted = false
|
||||
order by tpac.pos desc
|
||||
</select>
|
||||
<select id="selectExecResultByReportIdAndCollectionId" resultType="java.lang.String">
|
||||
select distinct api_case_execute_result
|
||||
from test_plan_report_api_case
|
||||
where test_plan_collection_id = #{collectionId}
|
||||
AND test_plan_report_id = #{reportId};
|
||||
</select>
|
||||
|
||||
<sql id="filter">
|
||||
<if test="request.filter != null and request.filter.size() > 0">
|
||||
|
|
|
@ -49,4 +49,6 @@ public interface ExtTestPlanReportApiScenarioMapper {
|
|||
* @return 关联的测试集集合
|
||||
*/
|
||||
List<TestPlanReportDetailCollectionResponse> listCollection(@Param("request") TestPlanReportDetailPageRequest request);
|
||||
|
||||
List<String> selectExecResultByReportIdAndCollectionId(@Param("collectionId") String collectionId, @Param("reportId") String prepareReportId);
|
||||
}
|
||||
|
|
|
@ -71,6 +71,12 @@
|
|||
and aso.deleted = false
|
||||
order by tpas.pos desc
|
||||
</select>
|
||||
<select id="selectExecResultByReportIdAndCollectionId" resultType="java.lang.String">
|
||||
select distinct api_scenario_execute_result
|
||||
from test_plan_report_api_scenario
|
||||
where test_plan_collection_id = #{collectionId}
|
||||
AND test_plan_report_id = #{reportId};
|
||||
</select>
|
||||
|
||||
<sql id="filter">
|
||||
<if test="request.filter != null and request.filter.size() > 0">
|
||||
|
|
|
@ -5,6 +5,7 @@ import io.metersphere.api.domain.ApiReportRelateTask;
|
|||
import io.metersphere.api.mapper.ApiReportRelateTaskMapper;
|
||||
import io.metersphere.api.service.ApiBatchRunBaseService;
|
||||
import io.metersphere.api.service.ApiCommonService;
|
||||
import io.metersphere.functional.constants.AssociateCaseType;
|
||||
import io.metersphere.plan.domain.*;
|
||||
import io.metersphere.plan.dto.request.TestPlanBatchExecuteRequest;
|
||||
import io.metersphere.plan.dto.request.TestPlanExecuteRequest;
|
||||
|
@ -30,6 +31,7 @@ import org.springframework.transaction.annotation.Transactional;
|
|||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import static io.metersphere.plan.service.TestPlanExecuteSupportService.*;
|
||||
|
||||
|
@ -56,6 +58,10 @@ public class TestPlanExecuteService {
|
|||
@Resource
|
||||
private TestPlanApiBatchRunBaseService testPlanApiBatchRunBaseService;
|
||||
|
||||
@Resource
|
||||
private ExtTestPlanReportApiCaseMapper extTestPlanReportApiCaseMapper;
|
||||
@Resource
|
||||
private ExtTestPlanReportApiScenarioMapper extTestPlanReportApiScenarioMapper;
|
||||
@Resource
|
||||
private TestPlanReportMapper testPlanReportMapper;
|
||||
@Resource
|
||||
|
@ -642,7 +648,7 @@ public class TestPlanExecuteService {
|
|||
this.executeByTestPlanCollection(queue);
|
||||
} else if (StringUtils.equalsIgnoreCase(queue.getQueueType(), QUEUE_PREFIX_TEST_PLAN_COLLECTION)) {
|
||||
// 判断是否是失败停止。 如果是失败停止,要检测父类是否也同样配置了失败停止。是的话,不再执行。
|
||||
if (this.isCaseTypeExecuteStop(queue.getSourceID(), isStopOnFailure)) {
|
||||
if (this.isCaseTypeExecuteStop(queue.getSourceID(), queue.getPrepareReportId(), isStopOnFailure)) {
|
||||
this.collectionExecuteQueueFinish(queue.getQueueId(), isStopOnFailure);
|
||||
} else {
|
||||
this.executeCase(queue);
|
||||
|
@ -650,10 +656,31 @@ public class TestPlanExecuteService {
|
|||
}
|
||||
}
|
||||
|
||||
private boolean isCaseTypeExecuteStop(String collectionId, boolean isStopOnFailure) {
|
||||
private boolean isCaseTypeExecuteStop(String collectionId, String prepareReportId, boolean isStopOnFailure) {
|
||||
boolean caseTypeStopOnFailure = extTestPlanCollectionMapper.getParentStopOnFailure(collectionId);
|
||||
// 如果测试集是失败停止触发的,通过父类的配置决定是否执行结束。
|
||||
if (isStopOnFailure) {
|
||||
return caseTypeStopOnFailure;
|
||||
} else if (caseTypeStopOnFailure) {
|
||||
// 如果是正常执行结束的,并且配置了失败停止,则根据该测试集的执行结果是否全部成功,来决定是否继续执行
|
||||
|
||||
//首先拿到执行结束的测试集
|
||||
List<TestPlanCollection> testPlanCollectionList = extTestPlanCollectionMapper.selectByItemParentId(collectionId);
|
||||
TestPlanCollection lastCollection = null;
|
||||
for (TestPlanCollection item : testPlanCollectionList) {
|
||||
if (StringUtils.equalsIgnoreCase(item.getId(), collectionId)) {
|
||||
break;
|
||||
}
|
||||
lastCollection = item;
|
||||
}
|
||||
|
||||
List<String> execResult = null;
|
||||
if (AssociateCaseType.API.equals(lastCollection.getType())) {
|
||||
execResult = extTestPlanReportApiCaseMapper.selectExecResultByReportIdAndCollectionId(lastCollection.getId(), prepareReportId);
|
||||
} else if (AssociateCaseType.SCENARIO.equals(lastCollection.getType())) {
|
||||
execResult = extTestPlanReportApiScenarioMapper.selectExecResultByReportIdAndCollectionId(lastCollection.getId(), prepareReportId);
|
||||
}
|
||||
return CollectionUtils.size(execResult) != 1 || !StringUtils.equalsIgnoreCase(Objects.requireNonNull(execResult).getFirst(), ResultStatus.SUCCESS.name());
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -257,11 +257,6 @@ export enum ScenarioExecuteStatus {
|
|||
FAKE_ERROR = 'FAKE_ERROR',
|
||||
}
|
||||
|
||||
// 场景导出配置
|
||||
export enum ScenarioExportType {
|
||||
SIMPLE = 'METERSPHERE_SIMPLE',
|
||||
ALL = 'METERSPHERE_ALL_DATA',
|
||||
}
|
||||
// 场景步骤类型
|
||||
export enum ScenarioStepType {
|
||||
API_CASE = 'API_CASE', // 接口用例
|
||||
|
|
|
@ -548,5 +548,6 @@ export interface ImportScenarioParams {
|
|||
// 导出场景参数
|
||||
export interface ExportScenarioParams extends BatchActionQueryParams {
|
||||
apiScenarioId: string;
|
||||
exportAllRelatedData: boolean;
|
||||
fileId: string;
|
||||
}
|
||||
|
|
|
@ -6,18 +6,20 @@
|
|||
class="ms-modal-upload ms-modal-medium"
|
||||
:width="400"
|
||||
>
|
||||
<a-radio-group v-model:model-value="exportTypeRadio">
|
||||
<a-radio :value="ScenarioExportType.SIMPLE"
|
||||
>{{ t('apiScenario.export.type.simple') }}
|
||||
<a-tooltip :content="t('apiScenario.export.simple.tooltip')" position="tl">
|
||||
<icon-question-circle
|
||||
class="ml-[4px] text-[var(--color-text-4)] hover:text-[rgb(var(--primary-5))]"
|
||||
size="16"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</a-radio>
|
||||
<a-radio :value="ScenarioExportType.ALL">{{ t('apiScenario.export.type.all') }}</a-radio>
|
||||
</a-radio-group>
|
||||
<div class="mb-[16px] flex items-center gap-[8px]">
|
||||
<a-switch v-model:model-value="exportTypeRadio" type="line" size="small"></a-switch>
|
||||
{{ t('apiScenario.export.type.all') }}
|
||||
<a-tooltip :content="t('apiScenario.export.simple.tooltip')" position="tl">
|
||||
<icon-question-circle
|
||||
class="ml-[4px] text-[var(--color-text-4)] hover:text-[rgb(var(--primary-5))]"
|
||||
size="16"
|
||||
/>
|
||||
<template #content>
|
||||
<div>{{ t('apiScenario.export.simple.tooltip1') }}</div>
|
||||
<div>{{ t('apiScenario.export.simple.tooltip2') }}</div>
|
||||
</template>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<div class="flex justify-end">
|
||||
|
@ -45,8 +47,6 @@
|
|||
import useAppStore from '@/store/modules/app';
|
||||
import { downloadByteFile, getGenerateId } from '@/utils';
|
||||
|
||||
import { ScenarioExportType } from '@/enums/apiEnum';
|
||||
|
||||
const appStore = useAppStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
|
@ -60,7 +60,7 @@
|
|||
const visible = defineModel<boolean>('visible', { required: true });
|
||||
|
||||
const exportLoading = ref(false);
|
||||
const exportTypeRadio = ref(ScenarioExportType.SIMPLE);
|
||||
const exportTypeRadio = ref(false);
|
||||
|
||||
function cancelExport() {
|
||||
visible.value = false;
|
||||
|
@ -184,6 +184,7 @@
|
|||
const { selectedIds, selectAll, excludeIds } = props.batchParams;
|
||||
const res = await exportScenario(
|
||||
{
|
||||
exportAllRelatedData: exportTypeRadio.value,
|
||||
selectIds: selectedIds || [],
|
||||
selectAll: !!selectAll,
|
||||
excludeIds: excludeIds || [],
|
||||
|
@ -191,7 +192,7 @@
|
|||
sort: props.sorter || {},
|
||||
fileId: reportId.value,
|
||||
},
|
||||
exportTypeRadio.value
|
||||
'METERSPHERE'
|
||||
);
|
||||
showExportingMessage(res);
|
||||
visible.value = false;
|
||||
|
|
|
@ -288,5 +288,6 @@ export default {
|
|||
'apiScenario.csvFileNotNull': 'CSV file cannot be empty',
|
||||
'apiScenario.export.type.simple': 'Simple',
|
||||
'apiScenario.export.type.all': 'All data',
|
||||
'apiScenario.export.simple.tooltip': 'Process referenced or copied request steps as custom requests',
|
||||
'apiScenario.export.simple.tooltip1': 'Close: Export referenced and copied steps as custom requests',
|
||||
'apiScenario.export.simple.tooltip2': 'Open: Export referenced and copied steps as native data',
|
||||
};
|
||||
|
|
|
@ -282,5 +282,6 @@ export default {
|
|||
'apiScenario.csvFileNotNull': 'CSV 文件不能为空',
|
||||
'apiScenario.export.type.simple': '普通导出',
|
||||
'apiScenario.export.type.all': '保留引用关系',
|
||||
'apiScenario.export.simple.tooltip': '将引用或复制的请求步骤处理为自定义请求',
|
||||
'apiScenario.export.simple.tooltip1': '关闭:导出引用和复制的步骤处理为自定义请求',
|
||||
'apiScenario.export.simple.tooltip2': '开启:导出引用和复制的步骤,保留接口、用例、场景的引用关系',
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue