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

This commit is contained in:
baiqi 2024-05-17 11:52:54 +08:00 committed by 刘瑞斌
parent 952596f7d7
commit 3c3188e7eb
10 changed files with 108 additions and 28 deletions

View File

@ -386,6 +386,11 @@ export interface ScenarioStepFileParams {
deleteFileIds?: string[];
unLinkFileIds?: string[];
}
// 场景文件参数
export interface ScenarioFileParams {
uploadFileIds: string[];
linkFileIds: string[];
}
// 场景步骤详情
export type ScenarioStepDetails = Partial<RequestParam | CaseRequestParam | ExecuteConditionProcessor>;
// 场景
@ -405,6 +410,7 @@ export interface Scenario {
steps: ScenarioStepItem[];
stepDetails: Record<string, ScenarioStepDetails>; // case、api、脚本操作抽屉的详情结构
stepFileParam: Record<string, ScenarioStepFileParams>;
fileParam: ScenarioFileParams;
follow?: boolean;
uploadFileIds: string[];
linkFileIds: string[];
@ -455,6 +461,7 @@ export interface ApiScenarioDebugRequest {
steps: ScenarioStepItem[];
projectId: string;
stepFileParam: Record<string, ScenarioStepFileParams>;
fileParam: ScenarioFileParams;
frontendDebug?: boolean;
}

View File

@ -916,7 +916,7 @@
(arr) => {
if (arr.length > 0) {
let hasNoIdItem = false;
paramsData.value = arr.map((item, i) => {
paramsData.value = arr.map((item) => {
if (!item) {
// undefined
hasNoIdItem = true;
@ -935,7 +935,12 @@
}
return item;
});
if (hasNoIdItem && !filterKeyValParams(arr, props.defaultParamItem).lastDataIsDefault && !props.isTreeTable) {
if (
(!props.disabledExceptParam || !props.disabledParamValue) &&
hasNoIdItem &&
!filterKeyValParams(arr, props.defaultParamItem).lastDataIsDefault &&
!props.isTreeTable
) {
addTableLine(arr.length - 1, false, true);
}
} else {

View File

@ -124,6 +124,7 @@
import { CsvVariable } from '@/models/apiTest/scenario';
import { defaultCsvParamItem } from '@/views/api-test/components/config';
import { filterKeyValParams } from '@/views/api-test/components/utils';
const props = defineProps<{
scenarioId?: string | number;
@ -310,6 +311,18 @@
}
return true;
}
onBeforeMount(() => {
if (
csvVariables.value.length > 0 &&
!filterKeyValParams(csvVariables.value, defaultCsvParamItem).lastDataIsDefault
) {
csvVariables.value.push({
...cloneDeep(defaultCsvParamItem),
id: getGenerateId(),
});
}
});
</script>
<style lang="less">

View File

@ -409,6 +409,7 @@
label: string;
name: string;
stepId: string | number; // id
uniqueId: string | number; // id
stepName: string; //
resourceId: string | number; // id
isNew: boolean;
@ -471,6 +472,7 @@
name: '',
type: 'api',
stepId: '',
uniqueId: '',
stepName: '',
resourceId: '',
customizeRequest: true,
@ -560,7 +562,7 @@
);
const currentLoop = ref(1);
const currentResponse = computed(() => {
if (requestVModel.value.stepId === props.step?.uniqueId && props.step?.uniqueId) {
if (requestVModel.value.uniqueId === props.step?.uniqueId && props.step?.uniqueId) {
// id id
return props.stepResponses?.[props.step?.uniqueId]?.[currentLoop.value - 1];
}
@ -574,7 +576,7 @@
watch(
() => props.stepResponses,
(val) => {
if (val && val[requestVModel.value.stepId]) {
if (val && val[requestVModel.value.uniqueId]) {
requestVModel.value.executeLoading = false;
}
},
@ -766,7 +768,7 @@
const handlePluginFormChange = debounce(() => {
if (isEditableApi.value) {
//
temporaryPluginFormMap[requestVModel.value.stepId] = fApi.value?.formData();
temporaryPluginFormMap[requestVModel.value.uniqueId] = fApi.value?.formData();
}
handleActiveDebugChange();
}, 300);
@ -797,7 +799,7 @@
* 设置插件表单数据
*/
function setPluginFormData() {
const tempForm = temporaryPluginFormMap[requestVModel.value.stepId];
const tempForm = temporaryPluginFormMap[requestVModel.value.uniqueId];
if (tempForm || !requestVModel.value.isNew) {
//
const formData = isEditableApi.value ? tempForm || requestVModel.value : requestVModel.value;
@ -981,6 +983,7 @@
...requestParams,
resourceId: requestVModel.value.resourceId,
stepId: requestVModel.value.stepId,
uniqueId: requestVModel.value.uniqueId,
activeTab: requestVModel.value.protocol === 'HTTP' ? RequestComposition.HEADER : RequestComposition.PLUGIN,
responseActiveTab: ResponseComposition.BODY,
protocol: requestVModel.value.protocol,
@ -1153,7 +1156,7 @@
activeTab: contentTabList.value[0].value,
unSaved: false,
isNew: false,
label: res.name,
label: props.step?.name || res.name,
stepName: props.step?.name || res.name,
...res.request,
...res,
@ -1162,6 +1165,7 @@
name: res.name, // requestnamenull
resourceId: res.id,
stepId: props.step?.id || '',
uniqueId: props.step?.uniqueId || '',
responseActiveTab: ResponseComposition.BODY,
...parseRequestBodyResult,
};
@ -1214,11 +1218,14 @@
* @param newStep 替换的新步骤
*/
function handleReplace(newStep: ScenarioStepItem) {
emit('replace', newStep);
emit('replace', {
...newStep,
name: props.step?.name || newStep.name,
});
}
watch(
() => props.request?.stepId,
() => props.request?.uniqueId,
() => {
isSwitchingContent.value = true;
},
@ -1245,6 +1252,7 @@
activeTab: contentTabList.value[0].value,
responseActiveTab: ResponseComposition.BODY,
stepId: props.step?.uniqueId || '',
uniqueId: props.step?.uniqueId || '',
isNew: false,
});
if (_stepType.value.isQuoteApi) {
@ -1258,9 +1266,11 @@
handleActiveDebugProtocolChange(requestVModel.value.protocol);
} else {
//
const id = getGenerateId();
requestVModel.value = cloneDeep({
...defaultApiParams,
stepId: getGenerateId(),
stepId: id,
uniqueId: id,
});
}
requestVModel.value.activeTab = contentTabList.value[0].value;

View File

@ -340,6 +340,7 @@
stepName: '',
type: 'api',
stepId: '',
uniqueId: '',
resourceId: '',
customizeRequestEnvEnable: false,
protocol: 'HTTP',
@ -432,7 +433,7 @@
watch(
() => props.stepResponses,
(val) => {
if (val && val[requestVModel.value.stepId]) {
if (val && val[requestVModel.value.uniqueId]) {
requestVModel.value.executeLoading = false;
}
},
@ -628,7 +629,7 @@
const handlePluginFormChange = debounce(() => {
if (isEditableApi.value) {
//
temporaryPluginFormMap[requestVModel.value.stepId] = fApi.value?.formData();
temporaryPluginFormMap[requestVModel.value.uniqueId] = fApi.value?.formData();
}
handleActiveDebugChange();
}, 300);
@ -659,7 +660,7 @@
* 设置插件表单数据
*/
function setPluginFormData() {
const tempForm = temporaryPluginFormMap[requestVModel.value.stepId];
const tempForm = temporaryPluginFormMap[requestVModel.value.uniqueId];
if (tempForm || !requestVModel.value.isNew) {
//
const formData = isEditableApi.value ? tempForm || requestVModel.value : requestVModel.value;
@ -820,6 +821,7 @@
unSaved: requestVModel.value.unSaved,
resourceId: requestVModel.value.resourceId,
stepId: requestVModel.value.stepId,
uniqueId: requestVModel.value.uniqueId,
activeTab: requestVModel.value.protocol === 'HTTP' ? RequestComposition.HEADER : RequestComposition.PLUGIN,
responseActiveTab: ResponseComposition.BODY,
protocol: requestVModel.value.protocol,
@ -977,7 +979,7 @@
activeTab: res.protocol === 'HTTP' ? RequestComposition.HEADER : RequestComposition.PLUGIN,
unSaved: false,
isNew: false,
label: res.name,
label: activeStep.value?.name || res.name,
...res.request,
...res,
response: cloneDeep(defaultResponse),
@ -986,6 +988,7 @@
name: res.name, // requestnamenull
resourceId: res.id,
stepId: activeStep.value?.id || '',
uniqueId: activeStep.value?.uniqueId || '',
...parseRequestBodyResult,
};
nextTick(() => {
@ -1005,11 +1008,14 @@
* @param newStep 替换的新步骤
*/
function handleReplace(newStep: ScenarioStepItem) {
emit('replace', newStep);
emit('replace', {
...newStep,
name: activeStep.value?.name || newStep.name,
});
}
watch(
() => props.request?.stepId,
() => props.request?.uniqueId,
() => {
isSwitchingContent.value = true;
},
@ -1031,6 +1037,7 @@
...props.request,
isNew: false,
stepId: props.request?.stepId || '',
uniqueId: activeStep.value?.uniqueId || '',
stepName: activeStep.value?.name || props.request?.name || '',
});
if (isQuote.value || isCopyNeedInit.value) {

View File

@ -116,6 +116,10 @@ export const defaultScenario: Scenario = {
steps: [],
stepDetails: {},
stepFileParam: {},
fileParam: {
linkFileIds: [],
uploadFileIds: [],
},
executeTime: 0,
executeSuccessCount: 0,
executeFailCount: 0,

View File

@ -12,7 +12,7 @@ import type { ApiScenarioDebugRequest, Scenario, ScenarioStepItem } from '@/mode
import { ScenarioExecuteStatus, ScenarioStepRefType, ScenarioStepType } from '@/enums/apiEnum';
import type { RequestParam } from '../common/customApiDrawer.vue';
import updateStepStatus from '../utils';
import updateStepStatus, { getScenarioFileParams } from '../utils';
/**
*
@ -85,6 +85,9 @@ export default function useStepExecute({
projectId: appStore.currentProjectId,
scenarioConfig: scenario.value.scenarioConfig,
frontendDebug: scenario.value.executeType === 'localExec',
fileParam: {
...getScenarioFileParams(scenario.value),
},
...executeParams,
steps: mapTree(executeParams.steps, (node) => {
return {
@ -153,7 +156,7 @@ export default function useStepExecute({
* @param executeType
*/
function handleApiExecute(request: RequestParam, executeType?: 'localExec' | 'serverExec') {
const realStep = findNodeByKey<ScenarioStepItem>(steps.value, request.stepId, 'uniqueId');
const realStep = findNodeByKey<ScenarioStepItem>(steps.value, request.uniqueId || request.stepId, 'uniqueId');
if (realStep) {
delete scenario.value.stepResponses[realStep.uniqueId]; // 先移除上一次的执行结果
realStep.reportId = getGenerateId();
@ -175,7 +178,7 @@ export default function useStepExecute({
});
} else {
// 步骤列表找不到该步骤,说明是新建的自定义请求还未保存,则临时创建一个步骤进行调试(不保存步骤信息)
delete scenario.value.stepResponses[request.stepId]; // 先移除上一次的执行结果
delete scenario.value.stepResponses[request.uniqueId || request.stepId]; // 先移除上一次的执行结果
const reportId = getGenerateId();
request.executeLoading = true;
activeStep.value = {
@ -190,7 +193,7 @@ export default function useStepExecute({
projectId: appStore.currentProjectId,
isExecuting: false,
reportId,
uniqueId: request.stepId,
uniqueId: request.uniqueId,
};
realExecute({
steps: [activeStep.value],

View File

@ -96,32 +96,37 @@ export default function useStepOperation({
// 复制 api、引用 api、自定义 api打开抽屉
activeStep.value = step;
if (
step.isQuoteScenarioStep ||
(stepDetails.value[step.id] === undefined && step.copyFromStepId) ||
(stepDetails.value[step.id] === undefined && !step.isNew)
) {
// 查看场景详情时,详情映射中没有对应数据,初始化步骤详情(复制的步骤没有加载详情前就被复制,打开复制后的步骤就初始化被复制步骤的详情)
// 引用的场景步骤资源每次加载最新数据
// 查看步骤详情时,详情映射中没有对应数据,初始化步骤详情(复制的步骤没有加载详情前就被复制,打开复制后的步骤就初始化被复制步骤的详情)
await getStepDetail(step);
}
customApiDrawerVisible.value = true;
} else if (step.stepType === ScenarioStepType.API_CASE) {
activeStep.value = step;
if (
_stepType.isCopyCase &&
((stepDetails.value[step.id] === undefined && step.copyFromStepId) ||
(stepDetails.value[step.id] === undefined && !step.isNew))
step.isQuoteScenarioStep ||
(_stepType.isCopyCase && stepDetails.value[step.id] === undefined && step.copyFromStepId) ||
(stepDetails.value[step.id] === undefined && !step.isNew)
) {
// 引用的场景步骤资源每次加载最新数据
// 只有复制的 case 需要查看步骤详情,引用的无法更改所以不需要在此初始化详情
// 查看场景详情时,详情映射中没有对应数据,初始化步骤详情(复制的步骤没有加载详情前就被复制,打开复制后的步骤就初始化被复制步骤的详情)
// 查看步骤详情时,详情映射中没有对应数据,初始化步骤详情(复制的步骤没有加载详情前就被复制,打开复制后的步骤就初始化被复制步骤的详情)
await getStepDetail(step);
}
customCaseDrawerVisible.value = true;
} else if (step.stepType === ScenarioStepType.SCRIPT) {
activeStep.value = step;
if (
step.isQuoteScenarioStep ||
(stepDetails.value[step.id] === undefined && step.copyFromStepId) ||
(stepDetails.value[step.id] === undefined && !step.isNew)
) {
// 查看场景详情时,详情映射中没有对应数据,初始化步骤详情(复制的步骤没有加载详情前就被复制,打开复制后的步骤就初始化被复制步骤的详情)
// 引用的场景步骤资源每次加载最新数据
// 查看步骤详情时,详情映射中没有对应数据,初始化步骤详情(复制的步骤没有加载详情前就被复制,打开复制后的步骤就初始化被复制步骤的详情)
await getStepDetail(step);
}
scriptOperationDrawerVisible.value = true;

View File

@ -1,5 +1,5 @@
import { RequestResult } from '@/models/apiTest/common';
import { ScenarioStepItem } from '@/models/apiTest/scenario';
import { type Scenario, ScenarioStepItem } from '@/models/apiTest/scenario';
import { ScenarioExecuteStatus, ScenarioStepType } from '@/enums/apiEnum';
/**
@ -94,3 +94,23 @@ export default function updateStepStatus(
}
}
}
/**
*
* @param scenario
*/
export function getScenarioFileParams(scenario: Scenario) {
const linkFileIds = new Set<string>();
const uploadFileIds = new Set<string>();
scenario.scenarioConfig.variable.csvVariables.forEach((item) => {
if (item.file.local) {
uploadFileIds.add(item.file.fileId);
} else if (item.file.fileId) {
linkFileIds.add(item.file.fileId);
}
});
return {
linkFileIds: Array.from(linkFileIds),
uploadFileIds: Array.from(uploadFileIds),
};
}

View File

@ -148,7 +148,7 @@
import { defaultCsvParamItem, defaultNormalParamItem } from '../components/config';
import { defaultScenario } from './components/config';
import updateStepStatus from './components/utils';
import updateStepStatus, { getScenarioFileParams } from './components/utils';
import {
filterAssertions,
filterConditionsSqlValidParams,
@ -263,6 +263,9 @@
scenarioConfig: activeScenarioTab.value.scenarioConfig,
...executeParams,
stepFileParam: activeScenarioTab.value.stepFileParam,
fileParam: {
...getScenarioFileParams(activeScenarioTab.value),
},
steps: mapTree(executeParams.steps, (node) => {
return {
...node,
@ -278,6 +281,9 @@
projectId: appStore.currentProjectId,
scenarioConfig: activeScenarioTab.value.scenarioConfig,
stepFileParam: activeScenarioTab.value.stepFileParam,
fileParam: {
...getScenarioFileParams(activeScenarioTab.value),
},
frontendDebug: executeType === 'localExec',
...executeParams,
steps: mapTree(executeParams.steps, (node) => {