feat(接口场景): 场景导入场景&部分问题修复
This commit is contained in:
parent
42d41df3f1
commit
dee6bc7b32
|
@ -18,6 +18,7 @@ import {
|
|||
GetModuleTreeUrl,
|
||||
GetScenarioStepUrl,
|
||||
GetScenarioUrl,
|
||||
GetSystemRequestUrl,
|
||||
GetTrashModuleCountUrl,
|
||||
GetTrashModuleTreeUrl,
|
||||
MoveModuleUrl,
|
||||
|
@ -45,6 +46,7 @@ import {
|
|||
ApiScenarioUpdateDTO,
|
||||
ExecuteHistoryItem,
|
||||
ExecutePageParams,
|
||||
GetSystemRequestParams,
|
||||
Scenario,
|
||||
ScenarioDetail,
|
||||
ScenarioHistoryItem,
|
||||
|
@ -231,3 +233,8 @@ export function debugScenario(data: ApiScenarioDebugRequest) {
|
|||
export function executeScenario(data: ApiScenarioDebugRequest) {
|
||||
return MSR.post({ url: ExecuteScenarioUrl, data });
|
||||
}
|
||||
|
||||
// 获取导入的系统请求数据
|
||||
export function getSystemRequest(data: GetSystemRequestParams) {
|
||||
return MSR.post<ApiScenarioTableItem[]>({ url: GetSystemRequestUrl, data });
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ export const ScenarioTransferFileUrl = '/api/scenario/transfer'; // 接口场景
|
|||
export const ScenarioTransferModuleOptionsUrl = '/api/scenario/transfer/options'; // 接口场景临时文件转存目录
|
||||
export const DebugScenarioUrl = '/api/scenario/debug'; // 接口场景调试(不保存报告)
|
||||
export const ExecuteScenarioUrl = '/api/scenario/run'; // 接口场景执行(保存报告)
|
||||
export const GetSystemRequestUrl = '/api/scenario/get/system-request'; // 获取导入的系统请求数据
|
||||
export const BatchRecycleScenarioUrl = '/api/scenario/batch-operation/delete-gc'; // 批量删除接口场景
|
||||
export const BatchMoveScenarioUrl = '/api/scenario/batch-operation/move'; // 批量移动接口场景
|
||||
export const BatchCopyScenarioUrl = '/api/scenario/batch-operation/copy'; // 批量复制接口场景
|
||||
|
|
|
@ -407,3 +407,19 @@ export interface ApiScenarioUpdateDTO extends Partial<Scenario> {
|
|||
deleteFileIds?: string[];
|
||||
unLinkFileIds?: string[];
|
||||
}
|
||||
|
||||
export interface GetSystemRequestTypeParams {
|
||||
moduleIds?: (string | number)[];
|
||||
selectedIds: (string | number)[];
|
||||
unselectedIds: (string | number)[];
|
||||
projectId: string;
|
||||
protocol?: string;
|
||||
versionId?: string;
|
||||
}
|
||||
|
||||
export interface GetSystemRequestParams {
|
||||
apiRequest?: GetSystemRequestTypeParams;
|
||||
caseRequest?: GetSystemRequestTypeParams;
|
||||
scenarioRequest?: GetSystemRequestTypeParams;
|
||||
refType: ScenarioStepRefType.COPY | ScenarioStepRefType.REF;
|
||||
}
|
||||
|
|
|
@ -235,7 +235,7 @@
|
|||
if (matchesIterator) {
|
||||
const matches = Array.from(matchesIterator);
|
||||
try {
|
||||
if (expressionForm.value.expressionMatchingRule === 'EXPRESSION') {
|
||||
if (expressionForm.value.expressionMatchingRule === RequestExtractExpressionRuleType.EXPRESSION) {
|
||||
// 匹配表达式,取第一个匹配结果,是完整匹配结果
|
||||
matchResult.value = matches.map((e) => e[0]) || [];
|
||||
} else {
|
||||
|
|
|
@ -380,6 +380,8 @@
|
|||
title: 'apiTestManagement.paramName',
|
||||
dataIndex: 'key',
|
||||
inputType: 'text',
|
||||
width: 250,
|
||||
showTooltip: true,
|
||||
},
|
||||
{
|
||||
title: 'apiTestManagement.paramVal',
|
||||
|
|
|
@ -362,7 +362,7 @@
|
|||
|
||||
export type RequestParam = ExecuteApiRequestFullParams & {
|
||||
response?: RequestTaskResult;
|
||||
customizeRequestEnvEnable: boolean;
|
||||
customizeRequestEnvEnable?: boolean;
|
||||
} & RequestCustomAttr;
|
||||
|
||||
const props = defineProps<{
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
<template>
|
||||
<MsDrawer
|
||||
v-model:visible="visible"
|
||||
unmount-on-close
|
||||
:mask="false"
|
||||
:width="900"
|
||||
:footer="false"
|
||||
show-full-screen
|
||||
|
@ -88,17 +86,13 @@
|
|||
import requestAndResponse from '@/views/api-test/components/requestAndResponse.vue';
|
||||
import { RequestParam } from '@/views/api-test/components/requestComposition/index.vue';
|
||||
|
||||
import { localExecuteApiDebug } from '@/api/modules/api-test/common';
|
||||
import {
|
||||
debugCase,
|
||||
getCaseDetail,
|
||||
getTransferOptionsCase,
|
||||
runCase,
|
||||
transferFileCase,
|
||||
uploadTempFileCase,
|
||||
} from '@/api/modules/api-test/management';
|
||||
import { getSocket } from '@/api/modules/project-management/commonScript';
|
||||
import { characterLimit, getGenerateId } from '@/utils';
|
||||
import { characterLimit } from '@/utils';
|
||||
|
||||
import { RequestResult } from '@/models/apiTest/common';
|
||||
import { ScenarioStepItem } from '@/models/apiTest/scenario';
|
||||
|
@ -121,6 +115,8 @@
|
|||
const emit = defineEmits<{
|
||||
(e: 'applyStep', request: RequestParam): void;
|
||||
(e: 'deleteStep'): void;
|
||||
(e: 'execute', request: RequestParam, executeType?: 'localExec' | 'serverExec'): void;
|
||||
(e: 'stopDebug'): void;
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
|
@ -208,11 +204,23 @@
|
|||
() =>
|
||||
activeStep.value?.stepType === ScenarioStepType.API_CASE && activeStep.value?.refType === ScenarioStepRefType.REF
|
||||
);
|
||||
const isHttpProtocol = computed(() => requestVModel.value.protocol === 'HTTP');
|
||||
|
||||
const stepName = ref(activeStep.value?.name);
|
||||
watchEffect(() => {
|
||||
stepName.value = activeStep.value?.name;
|
||||
});
|
||||
watch(
|
||||
() => props.stepResponses,
|
||||
(val) => {
|
||||
if (val && val[requestVModel.value.stepId]) {
|
||||
requestVModel.value.executeLoading = false;
|
||||
}
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
}
|
||||
);
|
||||
|
||||
const executeRef = ref<InstanceType<typeof executeButton>>();
|
||||
const requestAndResponseRef = ref<InstanceType<typeof requestAndResponse>>();
|
||||
|
@ -230,66 +238,31 @@
|
|||
isShowEditStepNameInput.value = false;
|
||||
}
|
||||
|
||||
const reportId = ref('');
|
||||
const websocket = ref<WebSocket>();
|
||||
const temporaryResponseMap = {}; // 缓存websocket返回的报告内容,避免执行接口后切换tab导致报告丢失
|
||||
// 开启websocket监听,接收执行结果
|
||||
function debugSocket(executeType?: 'localExec' | 'serverExec') {
|
||||
websocket.value = getSocket(
|
||||
reportId.value,
|
||||
executeType === 'localExec' ? '/ws/debug' : '',
|
||||
executeType === 'localExec' ? executeRef.value?.localExecuteUrl : ''
|
||||
);
|
||||
websocket.value.addEventListener('message', (event) => {
|
||||
const data = JSON.parse(event.data);
|
||||
if (data.msgType === 'EXEC_RESULT') {
|
||||
if (requestVModel.value.reportId === data.reportId) {
|
||||
// 判断当前查看的tab是否是当前返回的报告的tab,是的话直接赋值
|
||||
requestVModel.value.response = data.taskResult; // 渲染出用例详情和创建用例抽屉的响应数据
|
||||
requestVModel.value.executeLoading = false;
|
||||
} else {
|
||||
// 不是则需要把报告缓存起来,等切换到对应的tab再赋值
|
||||
temporaryResponseMap[data.reportId] = data.taskResult;
|
||||
}
|
||||
} else if (data.msgType === 'EXEC_END') {
|
||||
// 执行结束,关闭websocket
|
||||
websocket.value?.close();
|
||||
requestVModel.value.executeLoading = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 执行调试
|
||||
* @param val 执行类型
|
||||
*/
|
||||
async function handleExecute(executeType?: 'localExec' | 'serverExec') {
|
||||
try {
|
||||
requestVModel.value.executeLoading = true;
|
||||
requestVModel.value.response = cloneDeep(defaultResponse);
|
||||
const makeRequestParams = requestAndResponseRef.value?.makeRequestParams(executeType); // 写在reportId之前,防止覆盖reportId
|
||||
reportId.value = getGenerateId();
|
||||
requestVModel.value.reportId = reportId.value; // 存储报告ID
|
||||
debugSocket(executeType); // 开启websocket
|
||||
let res;
|
||||
const params = {
|
||||
apiDefinitionId: requestVModel.value.apiDefinitionId,
|
||||
...makeRequestParams,
|
||||
reportId: reportId.value,
|
||||
};
|
||||
if (!(requestVModel.value.resourceId as string).startsWith('c') && executeType === 'serverExec') {
|
||||
// 已创建的服务端
|
||||
res = await runCase(params);
|
||||
} else {
|
||||
res = await debugCase(params);
|
||||
}
|
||||
if (executeType === 'localExec') {
|
||||
await localExecuteApiDebug(executeRef.value?.localExecuteUrl ?? '', res);
|
||||
}
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
requestVModel.value.executeLoading = false;
|
||||
requestVModel.value.executeLoading = true;
|
||||
if (isHttpProtocol.value) {
|
||||
emit('execute', requestAndResponseRef.value?.makeRequestParams(executeType), executeType);
|
||||
} else {
|
||||
// 插件需要校验动态表单
|
||||
// fApi.value?.validate(async (valid) => {
|
||||
// if (valid === true) {
|
||||
// emit('execute', requestAndResponseRef.value?.makeRequestParams(executeType), executeType);
|
||||
// } else {
|
||||
// requestVModel.value.activeTab = RequestComposition.PLUGIN;
|
||||
// nextTick(() => {
|
||||
// scrollIntoView(document.querySelector('.arco-form-item-message'), { block: 'center' });
|
||||
// });
|
||||
// }
|
||||
// });
|
||||
}
|
||||
}
|
||||
|
||||
function stopDebug() {
|
||||
websocket.value?.close();
|
||||
requestVModel.value.executeLoading = false;
|
||||
emit('stopDebug');
|
||||
}
|
||||
|
||||
function handleClose() {
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
<div class="mb-[12px] flex items-center gap-[8px]">
|
||||
<MsProjectSelect v-model:project="currentProject" @change="resetModule" />
|
||||
<a-select
|
||||
v-if="activeKey !== 'scenario'"
|
||||
v-model:model-value="protocol"
|
||||
:options="protocolOptions"
|
||||
class="w-[90px]"
|
||||
|
@ -70,11 +71,11 @@
|
|||
</MsButton>
|
||||
</div>
|
||||
<div class="flex items-center gap-[12px]">
|
||||
<a-button type="secondary" @click="handleCancel">{{ t('common.cancel') }}</a-button>
|
||||
<a-button type="primary" :disabled="totalSelected === 0" @click="handleCopy">
|
||||
<a-button type="secondary" :disabled="loading" @click="handleCancel">{{ t('common.cancel') }}</a-button>
|
||||
<a-button type="primary" :loading="loading" :disabled="totalSelected === 0" @click="handleCopy">
|
||||
{{ t('common.copy') }}
|
||||
</a-button>
|
||||
<a-button type="primary" :disabled="totalSelected === 0" @click="handleQuote">
|
||||
<a-button type="primary" :loading="loading" :disabled="totalSelected === 0" @click="handleQuote">
|
||||
{{ t('common.quote') }}
|
||||
</a-button>
|
||||
</div>
|
||||
|
@ -96,11 +97,13 @@
|
|||
import apiTable from './table.vue';
|
||||
|
||||
import { getProtocolList } from '@/api/modules/api-test/common';
|
||||
import { getSystemRequest } from '@/api/modules/api-test/scenario';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
|
||||
import { ApiCaseDetail, ApiDefinitionDetail } from '@/models/apiTest/management';
|
||||
import { ApiScenarioTableItem } from '@/models/apiTest/scenario';
|
||||
import type { ApiCaseDetail, ApiDefinitionDetail } from '@/models/apiTest/management';
|
||||
import type { ApiScenarioTableItem } from '@/models/apiTest/scenario';
|
||||
import { ScenarioStepRefType } from '@/enums/apiEnum';
|
||||
|
||||
export interface ImportData {
|
||||
api: MsTableDataItem<ApiDefinitionDetail>[];
|
||||
|
@ -121,6 +124,7 @@
|
|||
});
|
||||
const activeKey = ref<'api' | 'case' | 'scenario'>('api');
|
||||
|
||||
const loading = ref(false);
|
||||
const selectedApis = ref<MsTableDataItem<ApiDefinitionDetail>[]>([]);
|
||||
const selectedCases = ref<MsTableDataItem<ApiCaseDetail>[]>([]);
|
||||
const selectedScenarios = ref<MsTableDataItem<ApiScenarioTableItem>[]>([]);
|
||||
|
@ -167,7 +171,9 @@
|
|||
const moduleIds = ref<(string | number)[]>([]);
|
||||
|
||||
function resetModule() {
|
||||
moduleTreeRef.value?.init(activeKey.value);
|
||||
nextTick(() => {
|
||||
moduleTreeRef.value?.init(activeKey.value);
|
||||
});
|
||||
}
|
||||
|
||||
function handleModuleSelect(ids: (string | number)[], node: MsTreeNodeData) {
|
||||
|
@ -187,28 +193,100 @@
|
|||
visible.value = false;
|
||||
}
|
||||
|
||||
function handleCopy() {
|
||||
emit(
|
||||
'copy',
|
||||
cloneDeep({
|
||||
api: selectedApis.value,
|
||||
case: selectedCases.value,
|
||||
scenario: selectedScenarios.value,
|
||||
})
|
||||
);
|
||||
handleCancel();
|
||||
async function getScenarioSteps(refType: ScenarioStepRefType.COPY | ScenarioStepRefType.REF) {
|
||||
const scenarioMap: Record<string, MsTableDataItem<ApiScenarioTableItem>[]> = {};
|
||||
selectedScenarios.value.forEach((e) => {
|
||||
if (!scenarioMap[e.projectId]) {
|
||||
scenarioMap[e.projectId] = [];
|
||||
}
|
||||
scenarioMap[e.projectId].push(e);
|
||||
});
|
||||
const scenarioRequestArr: any[] = [];
|
||||
Object.keys(scenarioMap).forEach((projectId) => {
|
||||
scenarioRequestArr.push(
|
||||
getSystemRequest({
|
||||
scenarioRequest: {
|
||||
projectId,
|
||||
unselectedIds: [],
|
||||
selectedIds: scenarioMap[projectId].map((e) => e.id),
|
||||
},
|
||||
refType,
|
||||
})
|
||||
);
|
||||
});
|
||||
try {
|
||||
loading.value = true;
|
||||
const allRes = await Promise.all(scenarioRequestArr);
|
||||
let fullScenarioArr: MsTableDataItem<ApiScenarioTableItem>[] = [];
|
||||
allRes.forEach((res) => {
|
||||
fullScenarioArr.push(...res);
|
||||
});
|
||||
if (refType === ScenarioStepRefType.COPY) {
|
||||
fullScenarioArr = fullScenarioArr.map((e) => {
|
||||
return {
|
||||
...e,
|
||||
name: `copy-${e.name}`,
|
||||
copyFromStepId: e.id,
|
||||
};
|
||||
});
|
||||
emit(
|
||||
'copy',
|
||||
cloneDeep({
|
||||
api: selectedApis.value,
|
||||
case: selectedCases.value,
|
||||
scenario: fullScenarioArr,
|
||||
})
|
||||
);
|
||||
handleCancel();
|
||||
} else {
|
||||
emit(
|
||||
'quote',
|
||||
cloneDeep({
|
||||
api: selectedApis.value,
|
||||
case: selectedCases.value,
|
||||
scenario: fullScenarioArr,
|
||||
})
|
||||
);
|
||||
handleCancel();
|
||||
}
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function handleQuote() {
|
||||
emit(
|
||||
'quote',
|
||||
cloneDeep({
|
||||
api: selectedApis.value,
|
||||
case: selectedCases.value,
|
||||
scenario: selectedScenarios.value,
|
||||
})
|
||||
);
|
||||
handleCancel();
|
||||
async function handleCopy() {
|
||||
if (selectedScenarios.value.length > 0) {
|
||||
await getScenarioSteps(ScenarioStepRefType.COPY);
|
||||
} else {
|
||||
emit(
|
||||
'copy',
|
||||
cloneDeep({
|
||||
api: selectedApis.value,
|
||||
case: selectedCases.value,
|
||||
scenario: selectedScenarios.value,
|
||||
})
|
||||
);
|
||||
handleCancel();
|
||||
}
|
||||
}
|
||||
|
||||
async function handleQuote() {
|
||||
if (selectedScenarios.value.length > 0) {
|
||||
await getScenarioSteps(ScenarioStepRefType.REF);
|
||||
} else {
|
||||
emit(
|
||||
'quote',
|
||||
cloneDeep({
|
||||
api: selectedApis.value,
|
||||
case: selectedCases.value,
|
||||
scenario: selectedScenarios.value,
|
||||
})
|
||||
);
|
||||
handleCancel();
|
||||
}
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
|
|
|
@ -191,7 +191,72 @@
|
|||
// 接口用例表格
|
||||
const useCaseTable = useTable(getCasePage, tableConfig);
|
||||
// 接口场景表格
|
||||
const useScenarioTable = useTable(getScenarioPage, tableConfig);
|
||||
const useScenarioTable = useTable(getScenarioPage, {
|
||||
...tableConfig,
|
||||
columns: [
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'num',
|
||||
slotName: 'num',
|
||||
sortIndex: 1,
|
||||
sortable: {
|
||||
sortDirections: ['ascend', 'descend'],
|
||||
sorter: true,
|
||||
},
|
||||
fixed: 'left',
|
||||
width: 100,
|
||||
showTooltip: true,
|
||||
columnSelectorDisabled: true,
|
||||
},
|
||||
{
|
||||
title: 'apiScenario.table.columns.name',
|
||||
dataIndex: 'name',
|
||||
sortable: {
|
||||
sortDirections: ['ascend', 'descend'],
|
||||
sorter: true,
|
||||
},
|
||||
width: 134,
|
||||
showTooltip: true,
|
||||
columnSelectorDisabled: true,
|
||||
},
|
||||
{
|
||||
title: 'apiScenario.table.columns.level',
|
||||
dataIndex: 'priority',
|
||||
slotName: 'priority',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: 'apiScenario.table.columns.status',
|
||||
dataIndex: 'status',
|
||||
slotName: 'status',
|
||||
titleSlotName: 'statusFilter',
|
||||
width: 140,
|
||||
},
|
||||
{
|
||||
title: 'apiScenario.table.columns.tags',
|
||||
dataIndex: 'tags',
|
||||
isTag: true,
|
||||
isStringTag: true,
|
||||
width: 240,
|
||||
},
|
||||
{
|
||||
title: 'apiScenario.table.columns.scenarioEnv',
|
||||
dataIndex: 'environmentName',
|
||||
width: 159,
|
||||
},
|
||||
{
|
||||
title: 'apiScenario.table.columns.steps',
|
||||
dataIndex: 'stepTotal',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: 'apiScenario.table.columns.module',
|
||||
dataIndex: 'modulePath',
|
||||
width: 120,
|
||||
showTooltip: true,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const methodFilterVisible = ref(false);
|
||||
const methodFilters = ref(Object.keys(RequestMethods));
|
||||
|
@ -324,6 +389,7 @@
|
|||
case 'scenario':
|
||||
default:
|
||||
routeName = ApiTestRouteEnum.API_TEST_SCENARIO;
|
||||
query.sId = id;
|
||||
break;
|
||||
}
|
||||
openNewPage(routeName, query);
|
||||
|
|
|
@ -133,8 +133,10 @@ export default function useCreateActions() {
|
|||
...defaultStepItemCommon.config,
|
||||
...config,
|
||||
},
|
||||
children: item.children || [],
|
||||
stepType,
|
||||
refType,
|
||||
copyFromStepId: item.copyFromStepId,
|
||||
...resourceField,
|
||||
name: name || item.name,
|
||||
sort: startOrder + index,
|
||||
|
|
|
@ -127,7 +127,7 @@
|
|||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useOpenNewPage from '@/hooks/useOpenNewPage';
|
||||
import { deleteNodes, filterTree, getGenerateId } from '@/utils';
|
||||
import { deleteNodes, filterTree, getGenerateId, mapTree } from '@/utils';
|
||||
import { countNodes } from '@/utils/tree';
|
||||
|
||||
import { ApiScenarioDebugRequest, Scenario } from '@/models/apiTest/scenario';
|
||||
|
@ -207,16 +207,22 @@
|
|||
|
||||
async function handleBeforeBatchToggle(done: (closed: boolean) => void) {
|
||||
try {
|
||||
let ids = checkedKeys.value;
|
||||
const ids = new Set(checkedKeys.value);
|
||||
if (batchToggleRange.value === 'top') {
|
||||
ids = scenario.value.steps.map((item) => item.id);
|
||||
scenario.value.steps = scenario.value.steps.map((item) => {
|
||||
if (ids.has(item.id)) {
|
||||
item.enable = isBatchEnable.value;
|
||||
}
|
||||
return item;
|
||||
});
|
||||
} else {
|
||||
scenario.value.steps = mapTree(scenario.value.steps, (node) => {
|
||||
if (ids.has(node.id)) {
|
||||
node.enable = isBatchEnable.value;
|
||||
}
|
||||
return node;
|
||||
});
|
||||
}
|
||||
console.log('ids', ids);
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve(true);
|
||||
}, 1000);
|
||||
});
|
||||
done(true);
|
||||
Message.success(isBatchEnable.value ? t('common.enableSuccess') : t('common.disableSuccess'));
|
||||
} catch (error) {
|
||||
|
@ -228,6 +234,10 @@
|
|||
function batchDelete() {
|
||||
deleteNodes(scenario.value.steps, checkedKeys.value, 'id');
|
||||
Message.success(t('common.deleteSuccess'));
|
||||
if (scenario.value.steps.length === 0) {
|
||||
checkedAll.value = false;
|
||||
indeterminate.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function checkReport() {
|
||||
|
|
|
@ -60,7 +60,7 @@
|
|||
<div class="mr-[8px] flex items-center gap-[8px]">
|
||||
<!-- 步骤启用/禁用 -->
|
||||
<a-switch
|
||||
:default-checked="step.enable"
|
||||
v-model:model-value="step.enable"
|
||||
size="small"
|
||||
@click.stop="handleStepToggleEnable(step)"
|
||||
></a-switch>
|
||||
|
@ -242,6 +242,8 @@
|
|||
:step-responses="scenario.stepResponses"
|
||||
@apply-step="applyApiStep"
|
||||
@delete-step="deleteCaseStep"
|
||||
@stop-debug="handleStopExecute(activeStep)"
|
||||
@execute="(request, executeType) => handleApiExecute((request as unknown as RequestParam), executeType)"
|
||||
/>
|
||||
<importApiDrawer
|
||||
v-if="importApiDrawerVisible"
|
||||
|
@ -519,6 +521,7 @@
|
|||
case 'copy':
|
||||
const id = getGenerateId();
|
||||
const stepDetail = stepDetails.value[node.id];
|
||||
const { isQuoteScenario } = getStepType(node as ScenarioStepItem);
|
||||
if (stepDetail) {
|
||||
// 如果复制的步骤还有详情数据,则也复制详情数据
|
||||
stepDetails.value[id] = cloneDeep(stepDetail);
|
||||
|
@ -531,13 +534,25 @@
|
|||
mapTree<ScenarioStepItem>(node, (childNode) => {
|
||||
const childId = getGenerateId();
|
||||
const childStepDetail = stepDetails.value[node.id];
|
||||
let childCopyFromStepId = childNode.id;
|
||||
if (childStepDetail) {
|
||||
// 如果复制的步骤下子步骤还有详情数据,则也复制详情数据
|
||||
stepDetails.value[childId] = cloneDeep(childStepDetail);
|
||||
}
|
||||
if (!isQuoteScenario) {
|
||||
// 非引用场景才处理复制来源 id
|
||||
if (childStepDetail || (childNode.isNew && childNode.stepRefType === ScenarioStepRefType.REF)) {
|
||||
// 如果子步骤查看过详情,则复制来源直接取它的 id
|
||||
// 如果子步骤没有查看过详情,且是新建的步骤,且子步骤是引用的步骤,则还是取它本身的 id
|
||||
childCopyFromStepId = childNode.id;
|
||||
} else if (childNode.isNew && childNode.stepRefType === ScenarioStepRefType.COPY) {
|
||||
// 如果子步骤没有查看过详情,且是新建的步骤,且子步骤是复制的步骤,则取它的来源 id
|
||||
childCopyFromStepId = childNode.copyFromStepId;
|
||||
}
|
||||
}
|
||||
return {
|
||||
...cloneDeep(childNode),
|
||||
copyFromStepId: childNode.id,
|
||||
copyFromStepId: childCopyFromStepId,
|
||||
id: childId,
|
||||
};
|
||||
})[0]
|
||||
|
@ -545,7 +560,7 @@
|
|||
name: `copy-${node.name}`,
|
||||
copyFromStepId: node.id,
|
||||
sort: node.sort + 1,
|
||||
isNew: false,
|
||||
isNew: true,
|
||||
id,
|
||||
},
|
||||
'after',
|
||||
|
@ -819,7 +834,12 @@
|
|||
const realStep = findNodeByKey<ScenarioStepItem>(steps.value, node.id, 'id');
|
||||
if (realStep) {
|
||||
realStep.reportId = getGenerateId();
|
||||
realStep.executeStatus = ScenarioExecuteStatus.EXECUTING;
|
||||
if (
|
||||
[ScenarioStepType.API, ScenarioStepType.API_CASE, ScenarioStepType.CUSTOM_REQUEST].includes(realStep.stepType)
|
||||
) {
|
||||
// 请求和场景类型才直接显示执行中,其他控制器需要等待执行完毕才结算执行结果
|
||||
realStep.executeStatus = ScenarioExecuteStatus.EXECUTING;
|
||||
}
|
||||
const stepDetail = stepDetails.value[realStep.id];
|
||||
delete scenario.value.stepResponses[realStep.id]; // 先移除上一次的执行结果
|
||||
realExecute(
|
||||
|
|
|
@ -84,6 +84,7 @@
|
|||
* @description 接口测试-接口场景主页
|
||||
*/
|
||||
|
||||
import { useRoute } from 'vue-router';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import dayjs from 'dayjs';
|
||||
|
@ -132,6 +133,7 @@
|
|||
|
||||
export type ScenarioParams = Scenario & TabItem;
|
||||
|
||||
const route = useRoute();
|
||||
const appStore = useAppStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
|
@ -211,8 +213,6 @@
|
|||
});
|
||||
}
|
||||
|
||||
onBeforeMount(selectRecycleCount);
|
||||
|
||||
const createRef = ref<InstanceType<typeof create>>();
|
||||
const saveLoading = ref(false);
|
||||
|
||||
|
@ -278,10 +278,10 @@
|
|||
}
|
||||
}
|
||||
|
||||
async function openScenarioTab(record: ApiScenarioTableItem, isCopy?: boolean) {
|
||||
async function openScenarioTab(record: ApiScenarioTableItem | string, isCopy?: boolean) {
|
||||
try {
|
||||
appStore.showLoading();
|
||||
const res = await getScenarioDetail(record.id);
|
||||
const res = await getScenarioDetail(typeof record === 'string' ? record : record.id);
|
||||
res.stepDetails = {};
|
||||
if (!res.steps) {
|
||||
res.steps = [];
|
||||
|
@ -297,6 +297,13 @@
|
|||
}
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
selectRecycleCount();
|
||||
if (route.query.sId) {
|
||||
openScenarioTab(route.query.sId as string);
|
||||
}
|
||||
});
|
||||
|
||||
const websocket = ref<WebSocket>();
|
||||
const temporaryScenarioReportMap = {}; // 缓存websocket返回的报告内容,避免执行接口后切换tab导致报告丢失
|
||||
|
||||
|
|
Loading…
Reference in New Issue