feat(接口场景): 场景执行初步&插件问题修复
This commit is contained in:
parent
a9396da1b6
commit
88756c8685
|
@ -9,6 +9,7 @@ import {
|
|||
BatchRecoverScenarioUrl,
|
||||
BatchRecycleScenarioUrl,
|
||||
BatchRunScenarioUrl,
|
||||
DebugScenarioUrl,
|
||||
DeleteModuleUrl,
|
||||
DeleteScenarioUrl,
|
||||
ExecuteHistoryUrl,
|
||||
|
@ -23,7 +24,10 @@ import {
|
|||
RecycleScenarioUrl,
|
||||
ScenarioHistoryUrl,
|
||||
ScenarioPageUrl,
|
||||
ScenarioTransferFileUrl,
|
||||
ScenarioTransferModuleOptionsUrl,
|
||||
ScenarioTrashPageUrl,
|
||||
ScenarioUploadTempFileUrl,
|
||||
UpdateModuleUrl,
|
||||
UpdateScenarioUrl,
|
||||
} from '@/api/requrls/api-test/scenario';
|
||||
|
@ -32,6 +36,7 @@ import {
|
|||
ApiScenarioBatchDeleteParams,
|
||||
ApiScenarioBatchEditParams,
|
||||
ApiScenarioBatchRunParams,
|
||||
ApiScenarioDebugRequest,
|
||||
ApiScenarioGetModuleParams,
|
||||
ApiScenarioModuleUpdateParams,
|
||||
ApiScenarioPageParams,
|
||||
|
@ -44,7 +49,7 @@ import {
|
|||
ScenarioHistoryItem,
|
||||
ScenarioHistoryPageParams,
|
||||
} from '@/models/apiTest/scenario';
|
||||
import { AddModuleParams, CommonList, ModuleTreeNode, MoveModules } from '@/models/common';
|
||||
import { AddModuleParams, CommonList, ModuleTreeNode, MoveModules, TransferFileParams } from '@/models/common';
|
||||
|
||||
// 更新模块
|
||||
export function updateModule(data: ApiScenarioModuleUpdateParams) {
|
||||
|
@ -200,3 +205,23 @@ export function getScenarioDetail(id: string) {
|
|||
export function getScenarioStep(stepId: string | number) {
|
||||
return MSR.get({ url: GetScenarioStepUrl, params: stepId });
|
||||
}
|
||||
|
||||
// 文件转存
|
||||
export function transferFile(data: TransferFileParams) {
|
||||
return MSR.post({ url: ScenarioTransferFileUrl, data });
|
||||
}
|
||||
|
||||
// 文件转存目录
|
||||
export function getTransferOptions(projectId: string) {
|
||||
return MSR.get<ModuleTreeNode[]>({ url: ScenarioTransferModuleOptionsUrl, params: projectId });
|
||||
}
|
||||
|
||||
// 上传文件
|
||||
export function uploadTempFile(file: File) {
|
||||
return MSR.uploadFile({ url: ScenarioUploadTempFileUrl }, { fileList: [file] }, 'file');
|
||||
}
|
||||
|
||||
// 场景调试
|
||||
export function debugScenario(data: ApiScenarioDebugRequest) {
|
||||
return MSR.post({ url: DebugScenarioUrl, data });
|
||||
}
|
||||
|
|
|
@ -10,6 +10,10 @@ export const GetScenarioUrl = '/api/scenario/get'; // 获取接口场景详情
|
|||
export const GetScenarioStepUrl = '/api/scenario/step/get'; // 获取接口场景步骤详情
|
||||
export const UpdateScenarioUrl = '/api/scenario/update'; // 更新接口场景
|
||||
export const RecycleScenarioUrl = '/api/scenario/delete-to-gc'; // 删除接口场景
|
||||
export const ScenarioUploadTempFileUrl = '/api/scenario/upload/temp/file'; // 接口场景上传临时文件
|
||||
export const ScenarioTransferFileUrl = '/api/scenario/transfer'; // 接口场景临时文件转存
|
||||
export const ScenarioTransferModuleOptionsUrl = '/api/scenario/transfer/options'; // 接口场景临时文件转存目录
|
||||
export const DebugScenarioUrl = '/api/scenario/debug'; // 接口场景调试
|
||||
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'; // 批量复制接口场景
|
||||
|
|
|
@ -368,6 +368,7 @@ export interface Scenario {
|
|||
executeTime?: string | number; // 执行时间
|
||||
executeSuccessCount?: number; // 执行成功数量
|
||||
executeFailCount?: number; // 执行失败数量
|
||||
reportId?: string;
|
||||
}
|
||||
export interface ScenarioDetail extends Scenario {
|
||||
stepTotal: number;
|
||||
|
@ -384,3 +385,17 @@ export interface ScenarioDetail extends Scenario {
|
|||
updateTime: number;
|
||||
updateUser: string;
|
||||
}
|
||||
|
||||
export interface ApiScenarioDebugRequest {
|
||||
id: string | number; // 场景 id
|
||||
grouped: boolean;
|
||||
environmentId: string;
|
||||
scenarioConfig: ScenarioConfig;
|
||||
stepDetails: Record<string, ScenarioStepDetail>;
|
||||
reportId?: string | number;
|
||||
steps: ScenarioStepItem[];
|
||||
projectId: string;
|
||||
uploadFileIds: string[];
|
||||
linkFileIds: string[];
|
||||
frontendDebug?: boolean;
|
||||
}
|
||||
|
|
|
@ -31,14 +31,19 @@
|
|||
const { openNewPage } = useOpenNewPage();
|
||||
|
||||
const currentEnv = defineModel<string>('currentEnv', { default: '' });
|
||||
const currentEnvConfig = ref<EnvConfig>();
|
||||
const currentEnvConfig = defineModel<EnvConfig>('currentEnvConfig', {
|
||||
default: {},
|
||||
});
|
||||
const envLoading = ref(false);
|
||||
const envOptions = ref<SelectOptionData[]>([]);
|
||||
|
||||
async function initEnvironment() {
|
||||
try {
|
||||
currentEnvConfig.value = await getEnvironment(currentEnv.value);
|
||||
currentEnvConfig.value.id = currentEnv.value;
|
||||
const res = await getEnvironment(currentEnv.value);
|
||||
currentEnvConfig.value = {
|
||||
...res,
|
||||
id: currentEnv.value,
|
||||
};
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
|
|
|
@ -45,7 +45,7 @@
|
|||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'execute', executeType?: 'localExec' | 'serverExec'): void;
|
||||
(e: 'execute', executeType?: 'localExec' | 'serverExec', localExecuteUrl?: string): void;
|
||||
(e: 'stopDebug'): void;
|
||||
}>();
|
||||
|
||||
|
@ -94,7 +94,7 @@
|
|||
|
||||
async function execute(executeType?: 'localExec' | 'serverExec') {
|
||||
if (!caseDetail.value || props.isEmit) {
|
||||
emit('execute', executeType);
|
||||
emit('execute', executeType, localExecuteUrl.value);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
|
|
|
@ -188,10 +188,10 @@
|
|||
:layout="activeLayout"
|
||||
:disabled-except-param="!isEditableApi"
|
||||
:second-box-height="secondBoxHeight"
|
||||
:upload-temp-file-api="props.uploadTempFileApi"
|
||||
:file-save-as-source-id="props.fileSaveAsSourceId"
|
||||
:file-save-as-api="props.fileSaveAsApi"
|
||||
:file-module-options-api="props.fileModuleOptionsApi"
|
||||
:upload-temp-file-api="uploadTempFile"
|
||||
:file-save-as-source-id="scenarioId"
|
||||
:file-save-as-api="transferFile"
|
||||
:file-module-options-api="getTransferOptions"
|
||||
@change="handleActiveDebugChange"
|
||||
/>
|
||||
<httpQuery
|
||||
|
@ -213,7 +213,7 @@
|
|||
<precondition
|
||||
v-else-if="requestVModel.activeTab === RequestComposition.PRECONDITION"
|
||||
v-model:config="requestVModel.children[0].preProcessorConfig"
|
||||
:is-definition="false"
|
||||
is-definition
|
||||
:disabled="!isEditableApi"
|
||||
@change="handleActiveDebugChange"
|
||||
/>
|
||||
|
@ -224,13 +224,13 @@
|
|||
:layout="activeLayout"
|
||||
:disabled="!isEditableApi"
|
||||
:second-box-height="secondBoxHeight"
|
||||
:is-definition="false"
|
||||
is-definition
|
||||
@change="handleActiveDebugChange"
|
||||
/>
|
||||
<assertion
|
||||
v-else-if="requestVModel.activeTab === RequestComposition.ASSERTION"
|
||||
v-model:params="requestVModel.children[0].assertionConfig.assertions"
|
||||
:is-definition="false"
|
||||
is-definition
|
||||
:disabled="!isEditableApi"
|
||||
:assertion-config="requestVModel.children[0].assertionConfig"
|
||||
/>
|
||||
|
@ -262,7 +262,7 @@
|
|||
:is-expanded="isVerticalExpanded"
|
||||
:request-task-result="requestVModel.response"
|
||||
:is-edit="false"
|
||||
:upload-temp-file-api="props.uploadTempFileApi"
|
||||
:upload-temp-file-api="uploadTempFile"
|
||||
:loading="requestVModel.executeLoading || loading"
|
||||
:is-definition="false"
|
||||
@change-expand="changeVerticalExpand"
|
||||
|
@ -299,6 +299,7 @@
|
|||
|
||||
import { getPluginScript, getProtocolList } from '@/api/modules/api-test/common';
|
||||
import { getDefinitionDetail } from '@/api/modules/api-test/management';
|
||||
import { getTransferOptions, transferFile, uploadTempFile } from '@/api/modules/api-test/scenario';
|
||||
import { getSocket } from '@/api/modules/project-management/commonScript';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import { useAppStore } from '@/store';
|
||||
|
@ -313,7 +314,6 @@
|
|||
RequestTaskResult,
|
||||
} from '@/models/apiTest/common';
|
||||
import { ScenarioStepItem } from '@/models/apiTest/scenario';
|
||||
import { ModuleTreeNode, TransferFileParams } from '@/models/common';
|
||||
import {
|
||||
RequestAuthType,
|
||||
RequestBodyFormat,
|
||||
|
@ -378,10 +378,6 @@
|
|||
};
|
||||
executeApi?: (params: ExecuteRequestParams) => Promise<any>; // 执行接口
|
||||
localExecuteApi?: (url: string, params: ExecuteRequestParams) => Promise<any>; // 本地执行接口
|
||||
uploadTempFileApi?: (...args) => Promise<any>; // 上传临时文件接口
|
||||
fileSaveAsSourceId?: string | number; // 文件转存关联的资源id
|
||||
fileSaveAsApi?: (params: TransferFileParams) => Promise<string>; // 文件转存接口
|
||||
fileModuleOptionsApi?: (projectId: string) => Promise<ModuleTreeNode[]>; // 文件转存目录下拉框接口
|
||||
permissionMap?: {
|
||||
execute: string;
|
||||
create: string;
|
||||
|
@ -397,6 +393,9 @@
|
|||
const appStore = useAppStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
// 注入祖先组件提供的属性
|
||||
const scenarioId = inject<string | number>('scenarioId');
|
||||
|
||||
const visible = defineModel<boolean>('visible', { required: true });
|
||||
const loading = defineModel<boolean>('detailLoading', { default: false });
|
||||
|
||||
|
@ -666,7 +665,6 @@
|
|||
*/
|
||||
function setPluginFormData() {
|
||||
const tempForm = temporaryPluginFormMap[requestVModel.value.stepId];
|
||||
console.log('setPluginFormData', temporaryPluginFormMap, requestVModel.value.stepId);
|
||||
if (tempForm || !requestVModel.value.isNew) {
|
||||
// 如果缓存的表单数据存在或者是编辑状态,则需要将之前的输入数据填充
|
||||
const formData = isEditableApi.value ? tempForm || requestVModel.value : requestVModel.value;
|
||||
|
@ -696,17 +694,9 @@
|
|||
}
|
||||
|
||||
// 切换是否使用环境变量
|
||||
async function handleUseEnvChange() {
|
||||
function handleUseEnvChange() {
|
||||
if (!isHttpProtocol.value) {
|
||||
const pluginId = protocolOptions.value.find((e) => e.value === requestVModel.value.protocol)?.pluginId;
|
||||
const res = await getPluginScript(pluginId);
|
||||
pluginScriptMap.value[requestVModel.value.protocol] = res;
|
||||
fApi.value?.nextTick(() => {
|
||||
controlPluginFormFields();
|
||||
});
|
||||
nextTick(() => {
|
||||
fApi.value?.resetFields();
|
||||
});
|
||||
controlPluginFormFields();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1055,7 +1045,6 @@
|
|||
resourceId: res.id,
|
||||
...parseRequestBodyResult,
|
||||
};
|
||||
console.log('initQuoteApiDetail', requestVModel.value);
|
||||
nextTick(() => {
|
||||
// 等待内容渲染出来再隐藏loading
|
||||
loading.value = false;
|
||||
|
|
|
@ -45,6 +45,11 @@ export const defaultConditionController = {
|
|||
condition: RequestAssertionCondition.EQUALS, // 条件操作符
|
||||
};
|
||||
|
||||
// 条件控制器
|
||||
export const defaultTimeController = {
|
||||
delay: 0, // 等待时间
|
||||
};
|
||||
|
||||
export const defaultStepItemCommon = {
|
||||
checked: false,
|
||||
expanded: false,
|
||||
|
@ -56,7 +61,6 @@ export const defaultStepItemCommon = {
|
|||
id: '',
|
||||
name: '',
|
||||
enable: true,
|
||||
waitTime: 0, // 等待时间
|
||||
},
|
||||
createActionsVisible: false,
|
||||
};
|
||||
|
|
|
@ -48,7 +48,7 @@
|
|||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
import { findNodeByKey, getGenerateId } from '@/utils';
|
||||
import { findNodeByKey } from '@/utils';
|
||||
|
||||
import { CreateStepAction, ScenarioStepItem } from '@/models/apiTest/scenario';
|
||||
import { ScenarioAddStepActionType, ScenarioStepRefType, ScenarioStepType } from '@/enums/apiEnum';
|
||||
|
|
|
@ -6,7 +6,12 @@ import { getGenerateId, insertNodes, TreeNode } from '@/utils';
|
|||
import { CreateStepAction, ScenarioStepItem } from '@/models/apiTest/scenario';
|
||||
import { ScenarioStepRefType, ScenarioStepType } from '@/enums/apiEnum';
|
||||
|
||||
import { defaultConditionController, defaultLoopController, defaultStepItemCommon } from '../../config';
|
||||
import {
|
||||
defaultConditionController,
|
||||
defaultLoopController,
|
||||
defaultStepItemCommon,
|
||||
defaultTimeController,
|
||||
} from '../../config';
|
||||
|
||||
export default function useCreateActions() {
|
||||
const { t } = useI18n();
|
||||
|
@ -102,6 +107,8 @@ export default function useCreateActions() {
|
|||
config = cloneDeep(defaultLoopController);
|
||||
} else if (stepType === ScenarioStepType.IF_CONTROLLER) {
|
||||
config = cloneDeep(defaultConditionController);
|
||||
} else if (stepType === ScenarioStepType.CONSTANT_TIMER) {
|
||||
config = cloneDeep(defaultTimeController);
|
||||
}
|
||||
if (item.id) {
|
||||
// 引用复制接口、用例、场景时的源资源信息
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="flex h-full flex-col gap-[16px]">
|
||||
<div class="flex h-full flex-col gap-[8px]">
|
||||
<div class="action-line">
|
||||
<div class="action-group">
|
||||
<a-checkbox
|
||||
|
@ -79,7 +79,7 @@
|
|||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="h-[calc(100%-48px)]">
|
||||
<div class="h-[calc(100%-30px)]">
|
||||
<stepTree
|
||||
ref="stepTreeRef"
|
||||
v-model:steps="scenario.steps"
|
||||
|
@ -224,6 +224,16 @@
|
|||
function refreshStepInfo() {
|
||||
console.log('刷新步骤信息');
|
||||
}
|
||||
|
||||
async function executeScenario() {
|
||||
try {
|
||||
scenario.value.executeLoading = true;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
scenario.value.executeLoading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<template>
|
||||
<div class="flex items-center gap-[4px]" draggable="false">
|
||||
<a-tooltip :content="innerData.waitTime.toString()" :disabled="!innerData.waitTime">
|
||||
<a-tooltip :content="innerData.delay.toString()" :disabled="!innerData.delay">
|
||||
<a-input-number
|
||||
v-model:model-value="innerData.waitTime"
|
||||
v-model:model-value="innerData.delay"
|
||||
class="max-w-[500px] px-[8px]"
|
||||
size="mini"
|
||||
:step="1"
|
||||
|
@ -25,7 +25,7 @@
|
|||
|
||||
export interface WaitTimeContentProps {
|
||||
id: string | number;
|
||||
waitTime: number;
|
||||
delay: number;
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="flex h-full flex-col gap-[16px]">
|
||||
<div class="flex h-full flex-col gap-[8px]">
|
||||
<a-spin class="max-h-[calc(100%-46px)] w-full" :loading="loading">
|
||||
<MsTree
|
||||
ref="treeRef"
|
||||
|
|
|
@ -2,29 +2,49 @@
|
|||
<MsSplitBox ref="splitBoxRef" :size="0.7" :max="0.9" :min="0.7" direction="horizontal" expand-direction="right">
|
||||
<template #first>
|
||||
<a-tabs v-model:active-key="activeKey" class="h-full" animation lazy-load>
|
||||
<a-tab-pane :key="ScenarioCreateComposition.STEP" :title="t('apiScenario.step')" class="p-[16px]">
|
||||
<a-tab-pane
|
||||
:key="ScenarioCreateComposition.STEP"
|
||||
:title="t('apiScenario.step')"
|
||||
class="scenario-create-tab-pane"
|
||||
>
|
||||
<step v-if="activeKey === ScenarioCreateComposition.STEP" v-model:scenario="scenario" is-new />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane :key="ScenarioCreateComposition.PARAMS" :title="t('apiScenario.params')" class="p-[16px]">
|
||||
<a-tab-pane
|
||||
:key="ScenarioCreateComposition.PARAMS"
|
||||
:title="t('apiScenario.params')"
|
||||
class="scenario-create-tab-pane"
|
||||
>
|
||||
<params
|
||||
v-if="activeKey === ScenarioCreateComposition.PARAMS"
|
||||
v-model:params="scenario.scenarioConfig.variable.commonVariables"
|
||||
/>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane :key="ScenarioCreateComposition.PRE_POST" :title="t('apiScenario.prePost')" class="p-[16px]">
|
||||
<a-tab-pane
|
||||
:key="ScenarioCreateComposition.PRE_POST"
|
||||
:title="t('apiScenario.prePost')"
|
||||
class="scenario-create-tab-pane"
|
||||
>
|
||||
<prePost
|
||||
v-if="activeKey === ScenarioCreateComposition.PRE_POST"
|
||||
v-model:post-processor-config="scenario.scenarioConfig.postProcessorConfig"
|
||||
v-model:pre-processor-config="scenario.scenarioConfig.preProcessorConfig"
|
||||
/>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane :key="ScenarioCreateComposition.ASSERTION" :title="t('apiScenario.assertion')" class="p-[16px]">
|
||||
<a-tab-pane
|
||||
:key="ScenarioCreateComposition.ASSERTION"
|
||||
:title="t('apiScenario.assertion')"
|
||||
class="scenario-create-tab-pane"
|
||||
>
|
||||
<assertion
|
||||
v-if="activeKey === ScenarioCreateComposition.ASSERTION"
|
||||
v-model:assertion-config="scenario.scenarioConfig.assertionConfig"
|
||||
/>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane :key="ScenarioCreateComposition.SETTING" :title="t('common.setting')" class="p-[16px]">
|
||||
<a-tab-pane
|
||||
:key="ScenarioCreateComposition.SETTING"
|
||||
:title="t('common.setting')"
|
||||
class="scenario-create-tab-pane"
|
||||
>
|
||||
<setting
|
||||
v-if="activeKey === ScenarioCreateComposition.SETTING"
|
||||
v-model:other-config="scenario.scenarioConfig.otherConfig"
|
||||
|
@ -207,5 +227,8 @@
|
|||
@apply h-full;
|
||||
}
|
||||
}
|
||||
.scenario-create-tab-pane {
|
||||
padding: 8px 16px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -34,22 +34,26 @@
|
|||
</template>
|
||||
</MsDetailCard>
|
||||
</div>
|
||||
<div class="h-[calc(100%-124px)]">
|
||||
<div class="h-[calc(100%-104px)]">
|
||||
<a-tabs v-model:active-key="activeKey" class="h-full" animation lazy-load>
|
||||
<a-tab-pane
|
||||
:key="ScenarioDetailComposition.BASE_INFO"
|
||||
:title="t('apiScenario.baseInfo')"
|
||||
class="px-[24px] py-[16px]"
|
||||
class="scenario-detail-tab-pane"
|
||||
>
|
||||
<baseInfo :scenario="scenario as ScenarioDetail" />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane :key="ScenarioDetailComposition.STEP" :title="t('apiScenario.step')" class="px-[24px] py-[16px]">
|
||||
<a-tab-pane
|
||||
:key="ScenarioDetailComposition.STEP"
|
||||
:title="t('apiScenario.step')"
|
||||
class="scenario-detail-tab-pane"
|
||||
>
|
||||
<step v-if="activeKey === ScenarioDetailComposition.STEP" v-model:scenario="scenario" />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane
|
||||
:key="ScenarioDetailComposition.PARAMS"
|
||||
:title="t('apiScenario.params')"
|
||||
class="px-[24px] py-[16px]"
|
||||
class="scenario-detail-tab-pane"
|
||||
>
|
||||
<params
|
||||
v-if="activeKey === ScenarioDetailComposition.PARAMS"
|
||||
|
@ -59,7 +63,7 @@
|
|||
<a-tab-pane
|
||||
:key="ScenarioDetailComposition.PRE_POST"
|
||||
:title="t('apiScenario.prePost')"
|
||||
class="px-[24px] py-[16px]"
|
||||
class="scenario-detail-tab-pane"
|
||||
>
|
||||
<prePost
|
||||
v-if="activeKey === ScenarioDetailComposition.PRE_POST"
|
||||
|
@ -70,7 +74,7 @@
|
|||
<a-tab-pane
|
||||
:key="ScenarioDetailComposition.ASSERTION"
|
||||
:title="t('apiScenario.assertion')"
|
||||
class="px-[24px] py-[16px]"
|
||||
class="scenario-detail-tab-pane"
|
||||
>
|
||||
<assertion
|
||||
v-if="activeKey === ScenarioDetailComposition.ASSERTION"
|
||||
|
@ -80,28 +84,32 @@
|
|||
<a-tab-pane
|
||||
:key="ScenarioDetailComposition.EXECUTE_HISTORY"
|
||||
:title="t('apiScenario.executeHistory')"
|
||||
class="px-[24px] py-[16px]"
|
||||
class="scenario-detail-tab-pane"
|
||||
>
|
||||
<executeHistory v-if="activeKey === ScenarioDetailComposition.EXECUTE_HISTORY" :scenario-id="scenario.id" />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane
|
||||
:key="ScenarioDetailComposition.CHANGE_HISTORY"
|
||||
:title="t('apiScenario.changeHistory')"
|
||||
class="px-[24px] py-[16px]"
|
||||
class="scenario-detail-tab-pane"
|
||||
>
|
||||
<changeHistory v-if="activeKey === ScenarioDetailComposition.CHANGE_HISTORY" :source-id="scenario.id" />
|
||||
</a-tab-pane>
|
||||
<!-- <a-tab-pane
|
||||
:key="ScenarioDetailComposition.DEPENDENCY"
|
||||
:title="t('apiScenario.dependency')"
|
||||
class="px-[24px] py-[16px]"
|
||||
class="scenario-detail-tab-pane"
|
||||
>
|
||||
<dependency v-if="activeKey === ScenarioDetailComposition.DEPENDENCY" />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane :key="ScenarioDetailComposition.QUOTE" :title="t('apiScenario.quote')" class="px-[24px] py-[16px]">
|
||||
<a-tab-pane :key="ScenarioDetailComposition.QUOTE" :title="t('apiScenario.quote')" class="scenario-detail-tab-pane">
|
||||
<quote v-if="activeKey === ScenarioDetailComposition.QUOTE" />
|
||||
</a-tab-pane> -->
|
||||
<a-tab-pane :key="ScenarioDetailComposition.SETTING" :title="t('common.setting')" class="px-[24px] py-[16px]">
|
||||
<a-tab-pane
|
||||
:key="ScenarioDetailComposition.SETTING"
|
||||
:title="t('common.setting')"
|
||||
class="scenario-detail-tab-pane"
|
||||
>
|
||||
<setting
|
||||
v-if="activeKey === ScenarioDetailComposition.SETTING"
|
||||
v-model:other-config="scenario.scenarioConfig.otherConfig"
|
||||
|
@ -197,5 +205,16 @@
|
|||
}
|
||||
:deep(.arco-tabs-content) {
|
||||
@apply pt-0;
|
||||
|
||||
height: calc(100% - 49px);
|
||||
.arco-tabs-content-list {
|
||||
@apply h-full;
|
||||
.arco-tabs-pane {
|
||||
@apply h-full;
|
||||
}
|
||||
}
|
||||
.scenario-detail-tab-pane {
|
||||
padding: 8px 16px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -16,11 +16,16 @@
|
|||
</template>
|
||||
</MsEditableTab>
|
||||
<div v-if="activeScenarioTab.id !== 'all'" class="flex items-center gap-[8px]">
|
||||
<environmentSelect />
|
||||
<environmentSelect v-model:current-env-config="currentEnvConfig" />
|
||||
<a-button type="primary" :loading="saveLoading" @click="saveScenario">
|
||||
{{ t('common.save') }}
|
||||
</a-button>
|
||||
<!-- <executeButton /> -->
|
||||
<executeButton
|
||||
:execute-loading="activeScenarioTab.executeLoading"
|
||||
is-emit
|
||||
@execute="handleExecute"
|
||||
@stop-debug="handleStopExecute"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<a-divider class="!my-0" />
|
||||
|
@ -84,17 +89,31 @@
|
|||
import MsSplitBox from '@/components/pure/ms-split-box/index.vue';
|
||||
import scenarioModuleTree from './components/scenarioModuleTree.vue';
|
||||
import environmentSelect from '@/views/api-test/components/environmentSelect.vue';
|
||||
// import executeButton from '@/views/api-test/components/executeButton.vue';
|
||||
import executeButton from '@/views/api-test/components/executeButton.vue';
|
||||
import ScenarioTable from '@/views/api-test/scenario/components/scenarioTable.vue';
|
||||
|
||||
import { addScenario, getScenarioDetail, getTrashModuleCount, updateScenario } from '@/api/modules/api-test/scenario';
|
||||
import { localExecuteApiDebug } from '@/api/modules/api-test/common';
|
||||
import {
|
||||
addScenario,
|
||||
debugScenario,
|
||||
getScenarioDetail,
|
||||
getTrashModuleCount,
|
||||
updateScenario,
|
||||
} from '@/api/modules/api-test/scenario';
|
||||
import { getSocket } from '@/api/modules/project-management/commonScript';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import router from '@/router';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
import { getGenerateId } from '@/utils';
|
||||
|
||||
import { ApiScenarioGetModuleParams, ApiScenarioTableItem, Scenario } from '@/models/apiTest/scenario';
|
||||
import {
|
||||
ApiScenarioDebugRequest,
|
||||
ApiScenarioGetModuleParams,
|
||||
ApiScenarioTableItem,
|
||||
Scenario,
|
||||
} from '@/models/apiTest/scenario';
|
||||
import { ModuleTreeNode } from '@/models/common';
|
||||
import { EnvConfig } from '@/models/projectManagement/environmental';
|
||||
import { ApiTestRouteEnum } from '@/enums/routeEnum';
|
||||
|
||||
import { defaultScenario } from './components/config';
|
||||
|
@ -251,6 +270,85 @@
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
const currentEnvConfig = ref<EnvConfig>();
|
||||
const reportId = ref('');
|
||||
const websocket = ref<WebSocket>();
|
||||
const temporaryResponseMap = {}; // 缓存websocket返回的报告内容,避免执行接口后切换tab导致报告丢失
|
||||
|
||||
/**
|
||||
* 开启websocket监听,接收执行结果
|
||||
*/
|
||||
function debugSocket(executeType?: 'localExec' | 'serverExec', localExecuteUrl?: string) {
|
||||
websocket.value = getSocket(
|
||||
reportId.value,
|
||||
executeType === 'localExec' ? '/ws/debug' : '',
|
||||
executeType === 'localExec' ? localExecuteUrl : ''
|
||||
);
|
||||
websocket.value.addEventListener('message', (event) => {
|
||||
const data = JSON.parse(event.data);
|
||||
if (data.msgType === 'EXEC_RESULT') {
|
||||
if (activeScenarioTab.value.reportId === data.reportId) {
|
||||
// 判断当前查看的tab是否是当前返回的报告的tab
|
||||
activeScenarioTab.value.executeLoading = false;
|
||||
} else {
|
||||
// 不是则需要把报告缓存起来,等切换到对应的tab再赋值
|
||||
temporaryResponseMap[activeScenarioTab.value.id][data.reportId] = data.taskResult;
|
||||
}
|
||||
} else if (data.msgType === 'EXEC_END') {
|
||||
// 执行结束,关闭websocket
|
||||
websocket.value?.close();
|
||||
activeScenarioTab.value.executeLoading = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function realExecute(
|
||||
executeParams: ApiScenarioDebugRequest,
|
||||
executeType?: 'localExec' | 'serverExec',
|
||||
localExecuteUrl?: string
|
||||
) {
|
||||
try {
|
||||
activeScenarioTab.value.executeLoading = true;
|
||||
reportId.value = getGenerateId();
|
||||
activeScenarioTab.value.reportId = reportId.value; // 存储报告ID
|
||||
debugSocket(executeType, localExecuteUrl); // 开启websocket
|
||||
executeParams.environmentId = currentEnvConfig.value?.id || '';
|
||||
const res = await debugScenario(executeParams);
|
||||
if (executeType === 'localExec' && localExecuteUrl) {
|
||||
await localExecuteApiDebug(localExecuteUrl, res);
|
||||
}
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
activeScenarioTab.value.executeLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
function handleExecute(executeType?: 'localExec' | 'serverExec', localExecuteUrl?: string) {
|
||||
const environmentId = currentEnvConfig.value?.id || '';
|
||||
realExecute(
|
||||
{
|
||||
grouped: false,
|
||||
environmentId,
|
||||
...activeScenarioTab.value,
|
||||
},
|
||||
executeType,
|
||||
localExecuteUrl
|
||||
);
|
||||
}
|
||||
|
||||
function handleStopExecute() {
|
||||
websocket.value?.close();
|
||||
activeScenarioTab.value.executeLoading = false;
|
||||
}
|
||||
|
||||
const scenarioId = computed(() => activeScenarioTab.value.id);
|
||||
const scenarioExecuteLoading = computed(() => activeScenarioTab.value.executeLoading);
|
||||
// 为子孙组件提供属性
|
||||
provide('scenarioId', scenarioId);
|
||||
provide('scenarioExecuteLoading', scenarioExecuteLoading);
|
||||
provide('temporaryResponseMap', temporaryResponseMap);
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
|
|
Loading…
Reference in New Issue