feat(接口场景): 场景单步骤执行&自定义 api、引用 api、复制 api、插件抽屉执行
This commit is contained in:
parent
594a95d26e
commit
451265b018
|
@ -33,29 +33,33 @@
|
|||
<slot name="title" v-bind="_props"></slot>
|
||||
</template>
|
||||
<template v-if="$slots['extra'] || props.nodeMoreActions" #extra="_props">
|
||||
<div v-if="_props.hideMoreAction !== true" class="ms-tree-node-extra">
|
||||
<slot name="extra" v-bind="_props"></slot>
|
||||
<MsTableMoreAction
|
||||
v-if="props.nodeMoreActions"
|
||||
:list="
|
||||
typeof props.filterMoreActionFunc === 'function'
|
||||
? props.filterMoreActionFunc(props.nodeMoreActions, _props)
|
||||
: props.nodeMoreActions
|
||||
"
|
||||
trigger="click"
|
||||
@select="handleNodeMoreSelect($event, _props)"
|
||||
@close="moreActionsClose"
|
||||
>
|
||||
<MsButton
|
||||
type="text"
|
||||
:size="props.nodeMoreActionSize || 'mini'"
|
||||
class="ms-tree-node-extra__more"
|
||||
@click="focusNodeKey = _props[props.fieldNames.key]"
|
||||
<div class="sticky right-0 flex items-center justify-between">
|
||||
<div v-if="_props.hideMoreAction !== true" class="ms-tree-node-extra">
|
||||
<slot name="extra" v-bind="_props"></slot>
|
||||
<MsTableMoreAction
|
||||
v-if="props.nodeMoreActions"
|
||||
:list="
|
||||
typeof props.filterMoreActionFunc === 'function'
|
||||
? props.filterMoreActionFunc(props.nodeMoreActions, _props)
|
||||
: props.nodeMoreActions
|
||||
"
|
||||
trigger="click"
|
||||
@select="handleNodeMoreSelect($event, _props)"
|
||||
@close="moreActionsClose"
|
||||
>
|
||||
<MsIcon type="icon-icon_more_outlined" size="14" class="text-[var(--color-text-4)]" />
|
||||
</MsButton>
|
||||
</MsTableMoreAction>
|
||||
<slot name="extraEnd" v-bind="_props"></slot>
|
||||
<MsButton
|
||||
type="text"
|
||||
:size="props.nodeMoreActionSize || 'mini'"
|
||||
class="ms-tree-node-extra__more"
|
||||
@click="focusNodeKey = _props[props.fieldNames.key]"
|
||||
>
|
||||
<MsIcon type="icon-icon_more_outlined" size="14" class="text-[var(--color-text-4)]" />
|
||||
</MsButton>
|
||||
</MsTableMoreAction>
|
||||
</div>
|
||||
<div class="ms-tree-node-extra-end">
|
||||
<slot name="extraEnd" v-bind="_props"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</a-tree>
|
||||
|
@ -412,6 +416,9 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
.ms-tree-node-extra {
|
||||
@apply visible w-auto;
|
||||
}
|
||||
}
|
||||
.arco-tree-node-indent-block {
|
||||
width: 1px;
|
||||
|
@ -459,7 +466,7 @@
|
|||
width: 60%;
|
||||
}
|
||||
.ms-tree-node-extra {
|
||||
@apply invisible relative sticky right-0 flex w-0 items-center;
|
||||
@apply invisible flex w-0 items-center;
|
||||
|
||||
margin-left: -4px;
|
||||
height: 32px;
|
||||
|
@ -483,6 +490,9 @@
|
|||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
.ms-tree-node-extra-end {
|
||||
@apply flex items-center;
|
||||
}
|
||||
.arco-tree-node-custom-icon {
|
||||
@apply hidden;
|
||||
}
|
||||
|
|
|
@ -342,6 +342,8 @@ export interface ScenarioStepItem {
|
|||
resourceName?: string; // 引用复制接口、用例、场景时的源资源名称
|
||||
method?: RequestMethods;
|
||||
executeStatus?: ScenarioExecuteStatus;
|
||||
isExecuting?: boolean; // 是否正在执行
|
||||
reportId?: string | number; // 步骤单个调试时的报告id
|
||||
}
|
||||
// 场景
|
||||
export interface Scenario {
|
||||
|
|
|
@ -62,6 +62,7 @@ export interface EnvConfig {
|
|||
postProcessorConfig: ProcessorConfig;
|
||||
assertionConfig: AssertionConfig;
|
||||
pluginConfigMap: EnvConfigItem;
|
||||
name?: string;
|
||||
}
|
||||
export interface EnvDetailItem {
|
||||
id?: string;
|
||||
|
|
|
@ -43,6 +43,7 @@
|
|||
currentEnvConfig.value = {
|
||||
...res,
|
||||
id: currentEnv.value,
|
||||
name: envOptions.value.find((item) => item.value === currentEnv.value)?.label || '',
|
||||
};
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
|
|
|
@ -22,11 +22,8 @@
|
|||
v-if="!props.step || props.step?.stepType === ScenarioStepType.CUSTOM_REQUEST"
|
||||
class="ml-auto flex items-center gap-[16px]"
|
||||
>
|
||||
<div
|
||||
v-show="!requestVModel.customizeRequestEnvEnable"
|
||||
class="text-[14px] font-normal text-[var(--color-text-4)]"
|
||||
>
|
||||
{{ t('apiScenario.env', { name: props.envDetailItem?.name }) }}
|
||||
<div class="text-[14px] font-normal text-[var(--color-text-4)]">
|
||||
{{ t('apiScenario.env', { name: currentEnvConfig?.name }) }}
|
||||
</div>
|
||||
<a-select
|
||||
v-model:model-value="requestVModel.customizeRequestEnvEnable"
|
||||
|
@ -300,7 +297,6 @@
|
|||
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';
|
||||
import { getGenerateId, parseQueryParams } from '@/utils';
|
||||
|
@ -309,12 +305,12 @@
|
|||
import {
|
||||
ExecuteApiRequestFullParams,
|
||||
ExecuteConditionConfig,
|
||||
ExecuteRequestParams,
|
||||
PluginConfig,
|
||||
RequestResult,
|
||||
RequestTaskResult,
|
||||
} from '@/models/apiTest/common';
|
||||
import { ScenarioStepItem } from '@/models/apiTest/scenario';
|
||||
import { EnvConfig } from '@/models/projectManagement/environmental';
|
||||
import {
|
||||
RequestAuthType,
|
||||
RequestBodyFormat,
|
||||
|
@ -372,13 +368,6 @@
|
|||
request?: RequestParam; // 请求参数集合
|
||||
step?: ScenarioStepItem;
|
||||
detailLoading?: boolean; // 详情加载状态
|
||||
envDetailItem?: {
|
||||
id?: string;
|
||||
projectId: string;
|
||||
name: string;
|
||||
};
|
||||
executeApi?: (params: ExecuteRequestParams) => Promise<any>; // 执行接口
|
||||
localExecuteApi?: (url: string, params: ExecuteRequestParams) => Promise<any>; // 本地执行接口
|
||||
permissionMap?: {
|
||||
execute: string;
|
||||
create: string;
|
||||
|
@ -390,6 +379,8 @@
|
|||
const emit = defineEmits<{
|
||||
(e: 'addStep', request: RequestParam): void;
|
||||
(e: 'applyStep', request: RequestParam): void;
|
||||
(e: 'execute', request: RequestParam, executeType?: 'localExec' | 'serverExec'): void;
|
||||
(e: 'stopDebug'): void;
|
||||
}>();
|
||||
|
||||
const appStore = useAppStore();
|
||||
|
@ -397,6 +388,8 @@
|
|||
|
||||
// 注入祖先组件提供的属性
|
||||
const scenarioId = inject<string | number>('scenarioId');
|
||||
const currentEnvConfig = inject<Ref<EnvConfig>>('currentEnvConfig');
|
||||
const isPriorityLocalExec = inject<Ref<boolean>>('isPriorityLocalExec');
|
||||
|
||||
const visible = defineModel<boolean>('visible', { required: true });
|
||||
const loading = defineModel<boolean>('detailLoading', { default: false });
|
||||
|
@ -477,13 +470,25 @@
|
|||
}
|
||||
return t('apiScenario.customApi');
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.stepResponses,
|
||||
(val) => {
|
||||
if (val) {
|
||||
requestVModel.value.executeLoading = false;
|
||||
}
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
}
|
||||
);
|
||||
|
||||
// 复制 api 只要加载过一次后就会保存,所以 props.request 是不为空的
|
||||
const isCopyApiNeedInit = computed(() => _stepType.value.isCopyApi && props.request === undefined);
|
||||
const isEditableApi = computed(
|
||||
() => _stepType.value.isCopyApi || props.step?.stepType === ScenarioStepType.CUSTOM_REQUEST || !props.step
|
||||
);
|
||||
const isHttpProtocol = computed(() => requestVModel.value.protocol === 'HTTP');
|
||||
const temporaryResponseMap = {}; // 缓存websocket返回的报告内容,避免执行接口后切换tab导致报告丢失
|
||||
const isInitPluginForm = ref(false);
|
||||
|
||||
function handleActiveDebugChange() {
|
||||
|
@ -639,8 +644,6 @@
|
|||
}
|
||||
|
||||
const hasLocalExec = ref(false); // 是否配置了api本地执行
|
||||
const isPriorityLocalExec = ref(false); // 是否优先本地执行
|
||||
const localExecuteUrl = ref('');
|
||||
|
||||
const pluginScriptMap = ref<Record<string, PluginConfig>>({}); // 存储初始化过后的插件配置
|
||||
const temporaryPluginFormMap: Record<string, any> = {}; // 缓存插件表单,避免切换传入的 API 数据导致动态表单数据丢失
|
||||
|
@ -865,38 +868,6 @@
|
|||
return conditionCopy;
|
||||
}
|
||||
|
||||
const websocket = ref<WebSocket>();
|
||||
|
||||
/**
|
||||
* 开启websocket监听,接收执行结果
|
||||
*/
|
||||
function debugSocket(executeType?: 'localExec' | 'serverExec') {
|
||||
websocket.value = getSocket(
|
||||
requestVModel.value.stepId,
|
||||
executeType === 'localExec' ? '/ws/debug' : '',
|
||||
executeType === 'localExec' ? localExecuteUrl.value : ''
|
||||
);
|
||||
websocket.value.addEventListener('message', (event) => {
|
||||
const data = JSON.parse(event.data);
|
||||
if (data.msgType === 'EXEC_RESULT') {
|
||||
if (requestVModel.value.stepId === data.reportId) {
|
||||
// 判断当前查看的tab是否是当前返回的报告的tab,是的话直接赋值
|
||||
requestVModel.value.response = data.taskResult;
|
||||
requestVModel.value.executeLoading = false;
|
||||
requestVModel.value.isExecute = false;
|
||||
} else {
|
||||
// 不是则需要把报告缓存起来,等切换到对应的tab再赋值
|
||||
temporaryResponseMap[data.reportId] = data.taskResult;
|
||||
}
|
||||
} else if (data.msgType === 'EXEC_END') {
|
||||
// 执行结束,关闭websocket
|
||||
websocket.value?.close();
|
||||
requestVModel.value.executeLoading = false;
|
||||
requestVModel.value.isExecute = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成请求参数
|
||||
* @param executeType 执行类型,执行时传入
|
||||
|
@ -950,9 +921,6 @@
|
|||
polymorphicName,
|
||||
};
|
||||
}
|
||||
if (isExecute) {
|
||||
debugSocket(executeType); // 开启websocket
|
||||
}
|
||||
return {
|
||||
...requestParams,
|
||||
resourceId: requestVModel.value.resourceId,
|
||||
|
@ -971,6 +939,7 @@
|
|||
preProcessorConfig: filterConditionsSqlValidParams(requestVModel.value.children[0].preProcessorConfig),
|
||||
},
|
||||
],
|
||||
executeLoading: isExecute,
|
||||
...parseRequestBodyResult,
|
||||
};
|
||||
}
|
||||
|
@ -980,38 +949,14 @@
|
|||
* @param val 执行类型
|
||||
*/
|
||||
async function execute(executeType?: 'localExec' | 'serverExec') {
|
||||
requestVModel.value.executeLoading = true;
|
||||
if (isHttpProtocol.value) {
|
||||
try {
|
||||
if (!props.executeApi) return;
|
||||
requestVModel.value.executeLoading = true;
|
||||
requestVModel.value.response = cloneDeep(defaultResponse);
|
||||
const res = await props.executeApi(makeRequestParams(executeType));
|
||||
if (executeType === 'localExec' && props.localExecuteApi) {
|
||||
await props.localExecuteApi(localExecuteUrl.value, res);
|
||||
}
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
} finally {
|
||||
requestVModel.value.executeLoading = false;
|
||||
}
|
||||
emit('execute', makeRequestParams(executeType), executeType);
|
||||
} else {
|
||||
// 插件需要校验动态表单
|
||||
fApi.value?.validate(async (valid) => {
|
||||
if (valid === true) {
|
||||
try {
|
||||
if (!props.executeApi) return;
|
||||
requestVModel.value.executeLoading = true;
|
||||
requestVModel.value.response = cloneDeep(defaultResponse);
|
||||
const res = await props.executeApi(makeRequestParams(executeType));
|
||||
if (executeType === 'localExec' && props.localExecuteApi) {
|
||||
await props.localExecuteApi(localExecuteUrl.value, res);
|
||||
}
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
requestVModel.value.executeLoading = false;
|
||||
}
|
||||
emit('execute', makeRequestParams(executeType), executeType);
|
||||
} else {
|
||||
requestVModel.value.activeTab = RequestComposition.PLUGIN;
|
||||
nextTick(() => {
|
||||
|
@ -1023,8 +968,7 @@
|
|||
}
|
||||
|
||||
function stopDebug() {
|
||||
websocket.value?.close();
|
||||
requestVModel.value.executeLoading = false;
|
||||
emit('stopDebug');
|
||||
}
|
||||
|
||||
function handleContinue() {
|
||||
|
@ -1067,6 +1011,7 @@
|
|||
url: res.path,
|
||||
name: res.name, // request里面还有个name但是是null
|
||||
resourceId: res.id,
|
||||
stepId: props.step?.id || '',
|
||||
...parseRequestBodyResult,
|
||||
};
|
||||
if (_stepType.value.isQuoteApi && props.request && isHttpProtocol.value) {
|
||||
|
|
|
@ -64,6 +64,7 @@ export const defaultStepItemCommon = {
|
|||
},
|
||||
createActionsVisible: false,
|
||||
responsePopoverVisible: false,
|
||||
isExecuting: false,
|
||||
};
|
||||
|
||||
export const defaultScenario: Scenario = {
|
||||
|
|
|
@ -43,43 +43,45 @@
|
|||
</a-button>
|
||||
</template>
|
||||
</div>
|
||||
<template v-if="scenario.executeTime">
|
||||
<div class="action-group">
|
||||
<div class="text-[var(--color-text-4)]">{{ t('apiScenario.executeTime') }}</div>
|
||||
<div class="text-[var(--color-text-4)]">{{ scenario.executeTime }}</div>
|
||||
</div>
|
||||
<div class="action-group">
|
||||
<div class="text-[var(--color-text-4)]">{{ t('apiScenario.executeResult') }}</div>
|
||||
<div class="flex items-center gap-[4px]">
|
||||
<div class="text-[var(--color-text-1)]">{{ t('common.success') }}</div>
|
||||
<div class="text-[rgb(var(--success-6))]">{{ scenario.executeSuccessCount }}</div>
|
||||
<div class="action-group ml-auto">
|
||||
<template v-if="scenario.executeTime">
|
||||
<div class="action-group">
|
||||
<div class="text-[var(--color-text-4)]">{{ t('apiScenario.executeTime') }}</div>
|
||||
<div class="text-[var(--color-text-4)]">{{ scenario.executeTime }}</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-[4px]">
|
||||
<div class="text-[var(--color-text-1)]">{{ t('common.fail') }}</div>
|
||||
<div class="text-[rgb(var(--success-6))]">{{ scenario.executeFailCount }}</div>
|
||||
<div class="action-group">
|
||||
<div class="text-[var(--color-text-4)]">{{ t('apiScenario.executeResult') }}</div>
|
||||
<div class="flex items-center gap-[4px]">
|
||||
<div class="text-[var(--color-text-1)]">{{ t('common.success') }}</div>
|
||||
<div class="text-[rgb(var(--success-6))]">{{ scenario.executeSuccessCount }}</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-[4px]">
|
||||
<div class="text-[var(--color-text-1)]">{{ t('common.fail') }}</div>
|
||||
<div class="text-[rgb(var(--success-6))]">{{ scenario.executeFailCount }}</div>
|
||||
</div>
|
||||
<MsButton v-if="scenario.isDebug === false" type="text" @click="checkReport">
|
||||
{{ t('apiScenario.checkReport') }}
|
||||
</MsButton>
|
||||
</div>
|
||||
<MsButton v-if="scenario.isDebug === false" type="text" @click="checkReport">
|
||||
{{ t('apiScenario.checkReport') }}
|
||||
</MsButton>
|
||||
</template>
|
||||
<div v-if="!checkedAll && !indeterminate" class="action-group ml-auto">
|
||||
<a-input
|
||||
v-model:model-value="keyword"
|
||||
:placeholder="t('apiScenario.searchByName')"
|
||||
allow-clear
|
||||
class="w-[200px]"
|
||||
/>
|
||||
<a-button
|
||||
v-if="!props.isNew"
|
||||
type="outline"
|
||||
class="arco-btn-outline--secondary !mr-0 !p-[8px]"
|
||||
@click="refreshStepInfo"
|
||||
>
|
||||
<template #icon>
|
||||
<icon-refresh class="text-[var(--color-text-4)]" />
|
||||
</template>
|
||||
</a-button>
|
||||
</div>
|
||||
</template>
|
||||
<div v-if="!checkedAll && !indeterminate" class="action-group ml-auto">
|
||||
<a-input
|
||||
v-model:model-value="keyword"
|
||||
:placeholder="t('apiScenario.searchByName')"
|
||||
allow-clear
|
||||
class="w-[200px]"
|
||||
/>
|
||||
<a-button
|
||||
v-if="!props.isNew"
|
||||
type="outline"
|
||||
class="arco-btn-outline--secondary !mr-0 !p-[8px]"
|
||||
@click="refreshStepInfo"
|
||||
>
|
||||
<template #icon>
|
||||
<icon-refresh class="text-[var(--color-text-4)]" />
|
||||
</template>
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="h-[calc(100%-30px)]">
|
||||
|
@ -91,7 +93,6 @@
|
|||
v-model:scenario="scenario"
|
||||
:expand-all="isExpandAll"
|
||||
:step-details="scenario.stepDetails"
|
||||
:step-responses="scenario.stepResponses"
|
||||
@update-resource="handleUpdateResource"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -178,8 +178,8 @@
|
|||
<template #content>
|
||||
<responseResult
|
||||
:active-tab="ResponseComposition.BODY"
|
||||
:request-result="props.stepResponses?.[step.id]"
|
||||
:console="props.stepResponses?.[step.id]?.console"
|
||||
:request-result="scenario.stepResponses?.[step.id]"
|
||||
:console="scenario.stepResponses?.[step.id]?.console"
|
||||
:show-empty="false"
|
||||
:is-edit="false"
|
||||
is-definition
|
||||
|
@ -215,18 +215,19 @@
|
|||
</createStepActions>
|
||||
<customApiDrawer
|
||||
v-model:visible="customApiDrawerVisible"
|
||||
:env-detail-item="{ id: 'demp-id-112233', projectId: '123456', name: 'demo环境' }"
|
||||
:request="currentStepDetail"
|
||||
:step="activeStep"
|
||||
:step-responses="props.stepResponses"
|
||||
:step-responses="scenario.stepResponses"
|
||||
@add-step="addCustomApiStep"
|
||||
@apply-step="applyApiStep"
|
||||
@stop-debug="handleStopExecute"
|
||||
@execute="handleApiExecute"
|
||||
/>
|
||||
<customCaseDrawer
|
||||
v-model:visible="customCaseDrawerVisible"
|
||||
:active-step="activeStep"
|
||||
:request="currentStepDetail"
|
||||
:step-responses="props.stepResponses"
|
||||
:step-responses="scenario.stepResponses"
|
||||
@apply-step="applyApiStep"
|
||||
@delete-step="deleteCaseStep"
|
||||
/>
|
||||
|
@ -278,7 +279,6 @@
|
|||
import { useEventListener } from '@vueuse/core';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
||||
import { ActionsItem } from '@/components/pure/ms-table-more-action/types';
|
||||
|
@ -312,7 +312,7 @@
|
|||
TreeNode,
|
||||
} from '@/utils';
|
||||
|
||||
import { ExecuteConditionProcessor, RequestResult } from '@/models/apiTest/common';
|
||||
import { ExecuteConditionProcessor } from '@/models/apiTest/common';
|
||||
import { ApiScenarioDebugRequest, CreateStepAction, Scenario, ScenarioStepItem } from '@/models/apiTest/scenario';
|
||||
import { EnvConfig } from '@/models/projectManagement/environmental';
|
||||
import {
|
||||
|
@ -338,7 +338,6 @@
|
|||
const props = defineProps<{
|
||||
stepKeyword: string;
|
||||
expandAll?: boolean;
|
||||
stepResponses?: Record<string | number, RequestResult>;
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
(e: 'updateResource', uploadFileIds: string[], linkFileIds: string[]): void;
|
||||
|
@ -360,6 +359,7 @@
|
|||
const scenario = defineModel<Scenario>('scenario', {
|
||||
required: true,
|
||||
});
|
||||
const isPriorityLocalExec = inject<Ref<boolean>>('isPriorityLocalExec');
|
||||
const currentEnvConfig = inject<Ref<EnvConfig>>('currentEnvConfig');
|
||||
|
||||
const selectedKeys = ref<(string | number)[]>([]); // 没啥用,目前用来展示选中样式
|
||||
|
@ -372,8 +372,10 @@
|
|||
}
|
||||
|
||||
function getExecuteStatus(step: ScenarioStepItem) {
|
||||
if (props.stepResponses && props.stepResponses[step.id]) {
|
||||
return props.stepResponses[step.id].isSuccessful ? ScenarioExecuteStatus.SUCCESS : ScenarioExecuteStatus.FAILED;
|
||||
if (scenario.value.stepResponses && scenario.value.stepResponses[step.id]) {
|
||||
return scenario.value.stepResponses[step.id].isSuccessful
|
||||
? ScenarioExecuteStatus.SUCCESS
|
||||
: ScenarioExecuteStatus.FAILED;
|
||||
}
|
||||
return step.executeStatus;
|
||||
}
|
||||
|
@ -692,12 +694,31 @@
|
|||
}
|
||||
|
||||
const websocket = ref<WebSocket>();
|
||||
const temporaryScenarioReportMap = {}; // 缓存websocket返回的报告内容,避免执行接口后切换tab导致报告丢失
|
||||
let temporaryStepReportMap = {}; // 缓存websocket返回的报告内容,避免执行接口后切换场景tab导致报告丢失
|
||||
|
||||
watch(
|
||||
() => scenario.value.id,
|
||||
() => {
|
||||
const stepKeys = Object.keys(temporaryStepReportMap);
|
||||
if (stepKeys.length > 0) {
|
||||
stepKeys.forEach((key) => {
|
||||
const report = temporaryStepReportMap[key];
|
||||
scenario.value.stepResponses[report.stepId] = temporaryStepReportMap[key];
|
||||
});
|
||||
temporaryStepReportMap = {};
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* 开启websocket监听,接收执行结果
|
||||
*/
|
||||
function debugSocket(reportId?: string | number, executeType?: 'localExec' | 'serverExec', localExecuteUrl?: string) {
|
||||
function debugSocket(
|
||||
step: ScenarioStepItem,
|
||||
reportId?: string | number,
|
||||
executeType?: 'localExec' | 'serverExec',
|
||||
localExecuteUrl?: string
|
||||
) {
|
||||
websocket.value = getSocket(
|
||||
reportId || '',
|
||||
executeType === 'localExec' ? '/ws/debug' : '',
|
||||
|
@ -706,27 +727,22 @@
|
|||
websocket.value.addEventListener('message', (event) => {
|
||||
const data = JSON.parse(event.data);
|
||||
if (data.msgType === 'EXEC_RESULT') {
|
||||
if (scenario.value.reportId === data.reportId) {
|
||||
if (step.reportId === data.reportId) {
|
||||
// 判断当前查看的tab是否是当前返回的报告的tab,是的话直接赋值
|
||||
data.taskResult.requestResults.forEach((result) => {
|
||||
scenario.value.stepResponses[result.stepId] = {
|
||||
...result,
|
||||
console: data.taskResult.console,
|
||||
};
|
||||
if (result.isSuccessful) {
|
||||
scenario.value.executeSuccessCount += 1;
|
||||
} else {
|
||||
scenario.value.executeFailCount += 1;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// 不是则需要把报告缓存起来,等切换到对应的tab再赋值
|
||||
data.taskResult.requestResults.forEach((result) => {
|
||||
if (scenario.value.reportId) {
|
||||
if (temporaryScenarioReportMap[scenario.value.reportId] === undefined) {
|
||||
temporaryScenarioReportMap[scenario.value.reportId] = {};
|
||||
if (step.reportId) {
|
||||
if (temporaryStepReportMap[step.reportId] === undefined) {
|
||||
temporaryStepReportMap[step.reportId] = {};
|
||||
}
|
||||
temporaryScenarioReportMap[scenario.value.reportId][result.stepId] = {
|
||||
temporaryStepReportMap[step.reportId] = {
|
||||
...result,
|
||||
console: data.taskResult.console,
|
||||
};
|
||||
|
@ -736,30 +752,31 @@
|
|||
} else if (data.msgType === 'EXEC_END') {
|
||||
// 执行结束,关闭websocket
|
||||
websocket.value?.close();
|
||||
if (scenario.value.reportId === data.reportId) {
|
||||
scenario.value.executeLoading = false;
|
||||
scenario.value.isExecute = false;
|
||||
if (step.reportId === data.reportId) {
|
||||
step.isExecuting = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function realExecute(
|
||||
executeParams: Pick<ApiScenarioDebugRequest, 'steps' | 'stepDetails' | 'reportId'>,
|
||||
executeParams: Pick<
|
||||
ApiScenarioDebugRequest,
|
||||
'steps' | 'stepDetails' | 'reportId' | 'uploadFileIds' | 'linkFileIds'
|
||||
>,
|
||||
executeType?: 'localExec' | 'serverExec',
|
||||
localExecuteUrl?: string
|
||||
) {
|
||||
const [currentStep] = executeParams.steps;
|
||||
try {
|
||||
scenario.value.executeLoading = true;
|
||||
debugSocket(executeParams.reportId, executeType, localExecuteUrl); // 开启websocket
|
||||
currentStep.isExecuting = true;
|
||||
debugSocket(currentStep, executeParams.reportId, executeType, localExecuteUrl); // 开启websocket
|
||||
const res = await debugScenario({
|
||||
id: scenario.value.id || '',
|
||||
grouped: false,
|
||||
environmentId: currentEnvConfig?.value.id || '',
|
||||
projectId: appStore.currentProjectId,
|
||||
scenarioConfig: scenario.value.scenarioConfig,
|
||||
uploadFileIds: scenario.value.uploadFileIds,
|
||||
linkFileIds: scenario.value.linkFileIds,
|
||||
frontendDebug: executeType === 'localExec',
|
||||
...executeParams,
|
||||
});
|
||||
|
@ -770,15 +787,71 @@
|
|||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
websocket.value?.close();
|
||||
scenario.value.executeLoading = false;
|
||||
currentStep.isExecuting = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 单个步骤执行调试
|
||||
*/
|
||||
function executeStep(node: MsTreeNodeData) {
|
||||
if (scenario.value.executeLoading) {
|
||||
if (node.isExecuting) {
|
||||
return;
|
||||
}
|
||||
console.log('执行步骤', node);
|
||||
const realStep = findNodeByKey<ScenarioStepItem>(steps.value, node.id, 'id');
|
||||
if (realStep) {
|
||||
realStep.reportId = getGenerateId();
|
||||
realStep.executeStatus = ScenarioExecuteStatus.EXECUTING;
|
||||
const stepDetail = stepDetails.value[realStep.id];
|
||||
delete scenario.value.stepResponses[realStep.id]; // 先移除上一次的执行结果
|
||||
realExecute(
|
||||
{
|
||||
steps: [realStep as ScenarioStepItem],
|
||||
stepDetails: {
|
||||
[realStep.id]: stepDetails.value[realStep.id],
|
||||
},
|
||||
reportId: realStep.reportId,
|
||||
uploadFileIds: stepDetail?.uploadFileIds || [],
|
||||
linkFileIds: stepDetail?.linkFileIds || [],
|
||||
},
|
||||
isPriorityLocalExec?.value ? 'localExec' : 'serverExec'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理 api 详情抽屉的执行动作
|
||||
* @param request 抽屉内的请求参数
|
||||
* @param executeType 执行类型
|
||||
*/
|
||||
function handleApiExecute(request: RequestParam, executeType?: 'localExec' | 'serverExec') {
|
||||
const realStep = findNodeByKey<ScenarioStepItem>(steps.value, request.stepId, 'id');
|
||||
if (realStep) {
|
||||
delete scenario.value.stepResponses[realStep.id]; // 先移除上一次的执行结果
|
||||
realStep.reportId = getGenerateId();
|
||||
realStep.executeStatus = ScenarioExecuteStatus.EXECUTING;
|
||||
request.executeLoading = true;
|
||||
realExecute(
|
||||
{
|
||||
steps: [realStep as ScenarioStepItem],
|
||||
stepDetails: {
|
||||
[realStep.id]: request,
|
||||
},
|
||||
reportId: realStep.reportId,
|
||||
uploadFileIds: request.uploadFileIds || [],
|
||||
linkFileIds: request.linkFileIds || [],
|
||||
},
|
||||
executeType
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function handleStopExecute() {
|
||||
websocket.value?.close();
|
||||
if (activeStep.value) {
|
||||
activeStep.value.isExecuting = false;
|
||||
activeStep.value.executeStatus = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
{{ t('common.save') }}
|
||||
</a-button>
|
||||
<executeButton
|
||||
ref="executeButtonRef"
|
||||
:execute-loading="activeScenarioTab.executeLoading"
|
||||
is-emit
|
||||
@execute="handleExecute"
|
||||
|
@ -172,6 +173,8 @@
|
|||
const activeFolder = ref<string>('all');
|
||||
const offspringIds = ref<string[]>([]);
|
||||
const isShowScenario = ref(false);
|
||||
const executeButtonRef = ref<InstanceType<typeof executeButton>>();
|
||||
const currentEnvConfig = ref<EnvConfig>();
|
||||
|
||||
// 获取激活用例类型样式
|
||||
const getActiveClass = (type: string) => {
|
||||
|
@ -221,6 +224,7 @@
|
|||
const res = await addScenario({
|
||||
...activeScenarioTab.value,
|
||||
projectId: appStore.currentProjectId,
|
||||
environmentId: currentEnvConfig.value?.id || '',
|
||||
});
|
||||
const scenarioDetail = await getScenarioDetail(res.id);
|
||||
scenarioDetail.stepDetails = {};
|
||||
|
@ -241,6 +245,7 @@
|
|||
} else {
|
||||
await updateScenario({
|
||||
...activeScenarioTab.value,
|
||||
environmentId: currentEnvConfig.value?.id || '',
|
||||
});
|
||||
}
|
||||
Message.success(activeScenarioTab.value.isNew ? t('common.createSuccess') : t('common.saveSuccess'));
|
||||
|
@ -281,7 +286,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
const currentEnvConfig = ref<EnvConfig>();
|
||||
const websocket = ref<WebSocket>();
|
||||
const temporaryScenarioReportMap = {}; // 缓存websocket返回的报告内容,避免执行接口后切换tab导致报告丢失
|
||||
|
||||
|
@ -352,7 +356,8 @@
|
|||
activeScenarioTab.value.reportId = executeParams.reportId; // 存储报告ID
|
||||
activeScenarioTab.value.isDebug = !isExecute;
|
||||
let res;
|
||||
if (isExecute && executeType !== 'localExec') {
|
||||
if (isExecute && executeType !== 'localExec' && !activeScenarioTab.value.isNew) {
|
||||
// 执行场景且非本地执行且非未保存场景
|
||||
res = await executeScenario({
|
||||
id: activeScenarioTab.value.id,
|
||||
grouped: false,
|
||||
|
@ -437,9 +442,11 @@
|
|||
}
|
||||
);
|
||||
|
||||
const isPriorityLocalExec = computed(() => executeButtonRef.value?.isPriorityLocalExec);
|
||||
const scenarioId = computed(() => activeScenarioTab.value.id);
|
||||
const scenarioExecuteLoading = computed(() => activeScenarioTab.value.executeLoading);
|
||||
// 为子孙组件提供属性
|
||||
provide('isPriorityLocalExec', readonly(isPriorityLocalExec));
|
||||
provide('currentEnvConfig', readonly(currentEnvConfig));
|
||||
provide('scenarioId', scenarioId);
|
||||
provide('scenarioExecuteLoading', scenarioExecuteLoading);
|
||||
|
|
Loading…
Reference in New Issue