fix(接口测试): 修复场景步骤拖拽问题

--bug=1017459 --user=赵勇 【接口测试】github#18262,接口自动化:引用了如图场景,然后自定义一个脚本,放到引用的场景里面去,发现只执行了引用的这个场景,自己自定义的脚本没有执行 https://www.tapd.cn/55049933/s/1283209
This commit is contained in:
fit2-zhao 2022-10-31 11:40:55 +08:00 committed by 刘瑞斌
parent 540865db48
commit 88a3858c31
4 changed files with 133 additions and 41 deletions

View File

@ -1,21 +1,22 @@
package io.metersphere.service; package io.metersphere.service;
import io.metersphere.api.dto.automation.ApiScenarioDTO; import io.metersphere.api.dto.automation.ApiScenarioDTO;
import io.metersphere.api.dto.definition.ApiDefinitionResult;
import io.metersphere.api.dto.definition.ApiTestCaseInfo; import io.metersphere.api.dto.definition.ApiTestCaseInfo;
import io.metersphere.api.dto.definition.request.ElementUtil; import io.metersphere.api.dto.definition.request.ElementUtil;
import io.metersphere.base.domain.Project; import io.metersphere.base.domain.ApiDefinition;
import io.metersphere.base.mapper.ProjectMapper;
import io.metersphere.service.definition.ApiDefinitionService;
import io.metersphere.service.definition.ApiTestCaseService;
import io.metersphere.base.domain.ApiScenarioWithBLOBs; import io.metersphere.base.domain.ApiScenarioWithBLOBs;
import io.metersphere.base.domain.ApiTestCaseWithBLOBs; import io.metersphere.base.domain.ApiTestCaseWithBLOBs;
import io.metersphere.base.domain.Project;
import io.metersphere.base.mapper.ApiDefinitionMapper;
import io.metersphere.base.mapper.ApiScenarioMapper; import io.metersphere.base.mapper.ApiScenarioMapper;
import io.metersphere.base.mapper.ProjectMapper;
import io.metersphere.base.mapper.ext.ExtApiScenarioMapper; import io.metersphere.base.mapper.ext.ExtApiScenarioMapper;
import io.metersphere.commons.constants.ElementConstants; import io.metersphere.commons.constants.ElementConstants;
import io.metersphere.commons.constants.PropertyConstant; import io.metersphere.commons.constants.PropertyConstant;
import io.metersphere.commons.utils.JSON; import io.metersphere.commons.utils.JSON;
import io.metersphere.commons.utils.JSONUtil; import io.metersphere.commons.utils.JSONUtil;
import io.metersphere.service.definition.ApiDefinitionService;
import io.metersphere.service.definition.ApiTestCaseService;
import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.json.JSONArray; import org.json.JSONArray;
@ -36,6 +37,8 @@ public class MsHashTreeService {
@Resource @Resource
private ApiDefinitionService apiDefinitionService; private ApiDefinitionService apiDefinitionService;
@Resource @Resource
private ApiDefinitionMapper apiDefinitionMapper;
@Resource
private ExtApiScenarioMapper extApiScenarioMapper; private ExtApiScenarioMapper extApiScenarioMapper;
@Resource @Resource
@ -44,7 +47,7 @@ public class MsHashTreeService {
public static final String CASE = "CASE"; public static final String CASE = "CASE";
public static final String REFERENCED = "referenced"; public static final String REFERENCED = "referenced";
public static final String REF = "REF"; public static final String REF = "REF";
public static final String CREATED = "Created"; public static final String COPY = "Copy";
public static final String REF_TYPE = "refType"; public static final String REF_TYPE = "refType";
public static final String ID = "id"; public static final String ID = "id";
public static final String NAME = "name"; public static final String NAME = "name";
@ -165,19 +168,15 @@ public class MsHashTreeService {
isExist = true; isExist = true;
this.setElement(element, apiTestCase.getNum(), enable, apiTestCase.getVersionName(), apiTestCase.getVersionEnable()); this.setElement(element, apiTestCase.getNum(), enable, apiTestCase.getVersionName(), apiTestCase.getVersionEnable());
} }
} else { } else if (StringUtils.equalsIgnoreCase(element.optString(REFERENCED), COPY)) {
if (StringUtils.equalsIgnoreCase(element.optString(REFERENCED), "Copy")) { ApiDefinition definition = apiDefinitionMapper.selectByPrimaryKey(element.optString(ID));
ApiDefinitionResult definitionWithBLOBs = apiDefinitionService.getById(element.optString(ID)); if (definition != null) {
if (definitionWithBLOBs != null) { Project project = projectMapper.selectByPrimaryKey(definition.getProjectId());
Project project = projectMapper.selectByPrimaryKey(definitionWithBLOBs.getProjectId()); element.put(ID, definition.getId());
definitionWithBLOBs.setProjectName(project.getName()); this.setElement(element, definition.getNum(), enable, project.getName(), project.getVersionEnable());
definitionWithBLOBs.setVersionEnable(project.getVersionEnable());
element.put(ID, definitionWithBLOBs.getId());
this.setElement(element, definitionWithBLOBs.getNum(), enable, definitionWithBLOBs.getVersionName(), definitionWithBLOBs.getVersionEnable());
isExist = true; isExist = true;
} }
} }
}
if (!isExist) { if (!isExist) {
if (StringUtils.equalsIgnoreCase(element.optString(REFERENCED), REF)) { if (StringUtils.equalsIgnoreCase(element.optString(REFERENCED), REF)) {
element.put(ENABLE, false); element.put(ENABLE, false);

View File

@ -183,11 +183,13 @@
<el-tree node-key="resourceId" :props="props" :data="scenarioDefinition" class="ms-tree" <el-tree node-key="resourceId" :props="props" :data="scenarioDefinition" class="ms-tree"
:expand-on-click-node="false" :expand-on-click-node="false"
:allow-drop="allowDrop" :allow-drop="allowDrop"
:allow-drag="allowDrag"
:empty-text="$t('api_test.scenario.step_info')" :empty-text="$t('api_test.scenario.step_info')"
highlight-current highlight-current
:show-checkbox="isBatchProcess" :show-checkbox="isBatchProcess"
@check-change="chooseHeadsUp" @check-change="chooseHeadsUp"
@node-drag-end="allowDrag" @node-click="nodeClick" draggable ref="stepTree" :key="reloadTree"> @node-drag-end="nodeDragEnd" @node-click="nodeClick" draggable ref="stepTree"
:key="reloadTree">
<el-row class="custom-tree-node" :gutter="10" type="flex" align="middle" slot-scope="{node, data}" <el-row class="custom-tree-node" :gutter="10" type="flex" align="middle" slot-scope="{node, data}"
style="width: 100%"> style="width: 100%">
@ -1562,6 +1564,12 @@ export default {
environmentConfigClose() { environmentConfigClose() {
this.getEnvironments(); this.getEnvironments();
}, },
allowDrag(node) {
if (node.data && node.data.disabled && node.parent.data && node.parent.data.disabled) {
return false;
}
return true;
},
allowDrop(draggingNode, dropNode, dropType) { allowDrop(draggingNode, dropNode, dropType) {
if (draggingNode.data.type === 'Assertions' || dropNode.data.type === 'Assertions') { if (draggingNode.data.type === 'Assertions' || dropNode.data.type === 'Assertions') {
return false; return false;
@ -1576,12 +1584,13 @@ export default {
} }
return false; return false;
} else if (dropType === "inner" && dropNode.data.referenced !== 'REF' && dropNode.data.referenced !== 'Deleted' } else if (dropType === "inner" && dropNode.data.referenced !== 'REF' && dropNode.data.referenced !== 'Deleted'
&& !dropNode.data.disabled
&& (this.stepFilter.get(dropNode.data.type) && this.stepFilter.get(dropNode.data.type).indexOf(draggingNode.data.type) != -1)) { && (this.stepFilter.get(dropNode.data.type) && this.stepFilter.get(dropNode.data.type).indexOf(draggingNode.data.type) != -1)) {
return true; return true;
} }
return false; return false;
}, },
allowDrag(draggingNode, dropNode, dropType) { nodeDragEnd(draggingNode, dropNode, dropType) {
if (dropNode && draggingNode && dropType) { if (dropNode && draggingNode && dropType) {
this.sort(); this.sort();
this.forceRerender(); this.forceRerender();

View File

@ -136,6 +136,7 @@ export default {
this.isShowNum = this.scenario.num ? true : false; this.isShowNum = this.scenario.num ? true : false;
if (this.scenario.id && this.scenario.referenced === 'REF' && !this.scenario.loaded && this.scenario.hashTree) { if (this.scenario.id && this.scenario.referenced === 'REF' && !this.scenario.loaded && this.scenario.hashTree) {
this.scenario.root = this.node.parent.parent ? false : true; this.scenario.root = this.node.parent.parent ? false : true;
this.scenario.disabled = true;
this.recursive(this.scenario.hashTree, this.scenario.projectId, true); this.recursive(this.scenario.hashTree, this.scenario.projectId, true);
} }
}, },
@ -233,6 +234,7 @@ export default {
} }
} }
if (this.scenario && this.scenario.hashTree && this.node.expanded) { if (this.scenario && this.scenario.hashTree && this.node.expanded) {
this.scenario.disabled = true;
this.recursive(this.scenario.hashTree, this.scenario.projectId, (this.scenario.id && this.scenario.referenced === 'REF')); this.recursive(this.scenario.hashTree, this.scenario.projectId, (this.scenario.id && this.scenario.referenced === 'REF'));
} }
this.reload(); this.reload();

View File

@ -1,23 +1,58 @@
<template> <template>
<div> <div>
<ms-run :debug="true" :environment="envMap" :reportId="reportId" :saved="false" :runMode="'DEFINITION'" :run-data="debugData" @errorRefresh="errorRefresh" @runRefresh="runRefresh" ref="runTest"/> <ms-run
<api-base-component :if-from-variable-advance="ifFromVariableAdvance" @copy="copyRow" @active="active(controller)" @remove="remove" :data="controller" :draggable="draggable" :is-max="isMax" :show-btn="showBtn" :show-version="showVersion" color="#02A7F0" :debug="true"
background-color="#F4F4F5" :title="$t('api_test.automation.loop_controller')"> :environment="envMap"
:reportId="reportId"
:saved="false"
:runMode="'DEFINITION'"
:run-data="debugData"
@errorRefresh="errorRefresh"
@runRefresh="runRefresh"
ref="runTest"/>
<api-base-component
:if-from-variable-advance="ifFromVariableAdvance"
:data="controller"
:draggable="draggable"
:is-max="isMax"
:show-btn="showBtn"
:show-version="showVersion"
:title="$t('api_test.automation.loop_controller')"
@copy="copyRow"
@active="active(controller)"
@remove="remove"
color="#02A7F0"
background-color="#F4F4F5">
<template v-slot:headerLeft> <template v-slot:headerLeft>
<i class="icon el-icon-arrow-right" :class="{'is-active': controller.active}" style="margin-right: 10px" v-if="!isMax"/> <i class="icon el-icon-arrow-right" :class="{'is-active': controller.active}" style="margin-right: 10px"
<el-radio @change="changeRadio" class="ms-radio ms-radio-margin" v-model="controller.loopType" label="LOOP_COUNT">{{ $t('loop.loops_title') }}</el-radio> v-if="!isMax"/>
<el-radio @change="changeRadio" class="ms-radio ms-radio-margin" v-model="controller.loopType" label="FOREACH">{{ $t('loop.foreach') }}</el-radio> <el-radio :disabled="controller.disabled" @change="changeRadio" class="ms-radio ms-radio-margin"
<el-radio @change="changeRadio" class="ms-radio ms-radio-margin" v-model="controller.loopType" label="WHILE">{{ $t('loop.while') }}</el-radio> v-model="controller.loopType" label="LOOP_COUNT">
{{ $t('loop.loops_title') }}
</el-radio>
<el-radio :disabled="controller.disabled" @change="changeRadio" class="ms-radio ms-radio-margin"
v-model="controller.loopType" label="FOREACH">
{{ $t('loop.foreach') }}
</el-radio>
<el-radio :disabled="controller.disabled" @change="changeRadio" class="ms-radio ms-radio-margin"
v-model="controller.loopType" label="WHILE">
{{ $t('loop.while') }}
</el-radio>
</template> </template>
<template v-slot:message> <template v-slot:message>
<span v-if="requestResult && requestResult.scenarios && requestResult.scenarios.length > 0 " style="color: #8c939d;margin-right: 10px"> <span v-if="requestResult && requestResult.scenarios && requestResult.scenarios.length > 0 "
</span> style="color: #8c939d;margin-right: 10px"/>
</template> </template>
<template v-slot:button> <template v-slot:button>
<el-button @click="conn" :disabled="!controller.enable" :tip="$t('api_test.run')" icon="el-icon-video-play" style="background-color: #409EFF;color: white;padding: 5px" size="mini" circle/> <el-button
:disabled="!controller.enable"
:tip="$t('api_test.run')"
@click="conn"
icon="el-icon-video-play"
class="ms-conn"
size="mini" circle/>
</template> </template>
<div v-if="controller.loopType==='LOOP_COUNT'" draggable v-loading="loading"> <div v-if="controller.loopType==='LOOP_COUNT'" draggable v-loading="loading">
<el-row> <el-row>
@ -28,14 +63,19 @@
</el-col> </el-col>
<el-col :span="8"> <el-col :span="8">
<span class="ms-span ms-radio">{{ $t('loop.interval') }}</span> <span class="ms-span ms-radio">{{ $t('loop.interval') }}</span>
<el-input-number size="small" v-model="controller.countController.interval" :placeholder="$t('commons.millisecond')" :max="1000*10000000" :min="0" :step="1000"/> <el-input-number
v-model="controller.countController.interval"
:disabled="controller.disabled"
:placeholder="$t('commons.millisecond')"
:max="1000*10000000"
:min="0" :step="1000" size="small"/>
<span class="ms-span ms-radio">ms</span> <span class="ms-span ms-radio">ms</span>
</el-col> </el-col>
<el-col :span="8"> <el-col :span="8">
<span class="ms-span ms-radio">{{ $t('loop.proceed') }}</span> <span class="ms-span ms-radio">{{ $t('loop.proceed') }}</span>
<el-tooltip class="item" effect="dark" :content="$t('api_test.automation.loop_content')" placement="top">> <el-tooltip class="item" effect="dark" :content="$t('api_test.automation.loop_content')" placement="top">>
<el-switch v-model="controller.countController.proceed" @change="switchChange"/> <el-switch :disabled="controller.disabled" v-model="controller.countController.proceed"
@change="switchChange"/>
</el-tooltip> </el-tooltip>
</el-col> </el-col>
</el-row> </el-row>
@ -44,29 +84,64 @@
<div v-else-if="controller.loopType==='FOREACH'" draggable v-loading="loading"> <div v-else-if="controller.loopType==='FOREACH'" draggable v-loading="loading">
<el-row> <el-row>
<el-col :span="8"> <el-col :span="8">
<el-input :placeholder="$t('api_test.automation.loop_return_val')" v-model="controller.forEachController.returnVal" size="small"/> <el-input
:disabled="controller.disabled"
:placeholder="$t('api_test.automation.loop_return_val')"
v-model="controller.forEachController.returnVal" size="small"/>
</el-col> </el-col>
<el-col :span="1" style="margin-top: 6px"> <el-col :span="1" style="margin-top: 6px">
<span style="margin:10px 10px 10px">in</span> <span style="margin:10px 10px 10px">in</span>
</el-col> </el-col>
<el-col :span="8"> <el-col :span="8">
<el-input :placeholder="$t('api_test.automation.loop_input_val')" v-model="controller.forEachController.inputVal" size="small"/> <el-input
:disabled="controller.disabled"
:placeholder="$t('api_test.automation.loop_input_val')"
v-model="controller.forEachController.inputVal" size="small"/>
</el-col> </el-col>
<el-col :span="7"> <el-col :span="7">
<span class="ms-span ms-radio">{{ $t('loop.interval') }}</span> <span class="ms-span ms-radio">{{ $t('loop.interval') }}</span>
<el-input-number size="small" v-model="controller.forEachController.interval" :placeholder="$t('commons.millisecond')" :max="1000*10000000" :min="0" :step="1000"/> <el-input-number
v-model="controller.forEachController.interval"
:disabled="controller.disabled"
size="small"
:placeholder="$t('commons.millisecond')"
:max="1000*10000000"
:min="0"
:step="1000"/>
<span class="ms-span ms-radio">ms</span> <span class="ms-span ms-radio">ms</span>
</el-col> </el-col>
</el-row> </el-row>
</div> </div>
<div v-else draggable v-loading="loading"> <div v-else draggable v-loading="loading">
<el-input size="small" v-model="controller.whileController.variable" style="width: 20%" :placeholder="$t('api_test.request.condition_variable')"/> <el-input
<el-select v-model="controller.whileController.operator" :placeholder="$t('commons.please_select')" size="small" @change="change" style="width: 10%;margin-left: 10px"> v-model="controller.whileController.variable"
:disabled="controller.disabled"
size="small"
:placeholder="$t('api_test.request.condition_variable')"
style="width: 20%"
/>
<el-select
v-model="controller.whileController.operator"
:disabled="controller.disabled"
:placeholder="$t('commons.please_select')" size="small"
@change="change" style="width: 10%;margin-left: 10px">
<el-option v-for="o in operators" :key="o.value" :label="$t(o.label)" :value="o.value"/> <el-option v-for="o in operators" :key="o.value" :label="$t(o.label)" :value="o.value"/>
</el-select> </el-select>
<el-input size="small" v-model="controller.whileController.value" :placeholder="$t('api_test.value')" v-if="!hasEmptyOperator" style="width: 20%;margin-left: 20px"/> <el-input
v-model="controller.whileController.value"
:disabled="controller.disabled"
size="small"
:placeholder="$t('api_test.value')"
v-if="!hasEmptyOperator" style="width: 20%;margin-left: 20px"/>
<span class="ms-span ms-radio">{{ $t('loop.timeout') }}</span> <span class="ms-span ms-radio">{{ $t('loop.timeout') }}</span>
<el-input-number size="small" v-model="controller.whileController.timeout" :placeholder="$t('commons.millisecond')" :max="1000*10000000" :min="3000" :step="1000"/> <el-input-number
v-model="controller.whileController.timeout"
:disabled="controller.disabled"
size="small"
:placeholder="$t('commons.millisecond')"
:max="1000*10000000"
:min="3000"
:step="1000"/>
<span class="ms-span ms-radio">ms</span> <span class="ms-span ms-radio">ms</span>
</div> </div>
@ -75,7 +150,8 @@
<i class="el-icon-loading" style="font-size: 16px"/> <i class="el-icon-loading" style="font-size: 16px"/>
{{ $t('commons.testing') }} {{ $t('commons.testing') }}
</span> </span>
<span class="ms-step-debug-code" :class="node.data.code ==='ERROR'?'ms-req-error':'ms-req-success'" v-if="!loading && !node.data.testing && node.data.debug"> <span class="ms-step-debug-code" :class="node.data.code ==='ERROR'?'ms-req-error':'ms-req-success'"
v-if="!loading && !node.data.testing && node.data.debug">
{{ getCode() }} {{ getCode() }}
</span> </span>
</template> </template>
@ -412,4 +488,10 @@ export default {
white-space: nowrap; white-space: nowrap;
width: 60px; width: 60px;
} }
.ms-conn {
background-color: #409EFF;
color: white;
padding: 5px
}
</style> </style>