feat(接口测试): 场景脚本断言&响应内容&样式调整
This commit is contained in:
parent
de429a90fb
commit
5c9162f898
|
@ -2,6 +2,7 @@
|
|||
<div class="ms-detail-card">
|
||||
<div class="ms-detail-card-title">
|
||||
<div class="flex flex-1 items-center gap-[8px]">
|
||||
<slot name="titlePrefix"></slot>
|
||||
<a-tooltip :content="t(props.title)">
|
||||
<div class="one-line-text max-w-[300px] font-medium text-[var(--color-text-1)]">
|
||||
{{ t(props.title) }}
|
||||
|
|
|
@ -233,8 +233,6 @@ export interface ExecuteConditionProcessorCommon {
|
|||
beforeStepScript: boolean; // 是否是步骤内前置脚本前
|
||||
assertionType?: RequestConditionProcessor;
|
||||
}
|
||||
// 执行请求-前后置操作-脚本处理器
|
||||
export type ScriptProcessor = ScriptCommonConfig & ExecuteConditionProcessorCommon;
|
||||
// 执行请求-前后置操作-SQL脚本处理器
|
||||
export interface SQLProcessor extends ExecuteConditionProcessorCommon {
|
||||
name: string; // 描述
|
||||
|
@ -278,16 +276,6 @@ export interface XPathExtract extends ExpressionCommonConfig {
|
|||
export interface ExtractProcessor extends ExecuteConditionProcessorCommon {
|
||||
extractors: (RegexExtract | JSONPathExtract | XPathExtract)[];
|
||||
}
|
||||
// 执行请求-前后置操作配置
|
||||
export type ExecuteConditionProcessor = Partial<
|
||||
ScriptProcessor & SQLProcessor & TimeWaitingProcessor & ExtractProcessor
|
||||
> &
|
||||
ExecuteConditionProcessorCommon;
|
||||
export interface ExecuteConditionConfig {
|
||||
enableGlobal?: boolean; // 是否启用全局前/后置 默认为 true
|
||||
processors: ExecuteConditionProcessor[];
|
||||
activeItemId?: number;
|
||||
}
|
||||
// 执行请求-断言配置子项
|
||||
export type ExecuteAssertionItem = ResponseAssertionCommon &
|
||||
ResponseCodeAssertion &
|
||||
|
@ -301,6 +289,24 @@ export interface ExecuteAssertionConfig {
|
|||
enableGlobal?: boolean; // 是否启用全局断言,部分地方没有
|
||||
assertions: ExecuteAssertionItem[];
|
||||
}
|
||||
// 执行请求-前后置操作-脚本处理器
|
||||
export interface ScriptProcessorChild {
|
||||
polymorphicName: string; // 协议多态名称,写死MsCommonElement
|
||||
assertionConfig: ExecuteAssertionConfig;
|
||||
}
|
||||
export interface ScriptProcessor extends ScriptCommonConfig, ExecuteConditionProcessorCommon {
|
||||
children: ScriptProcessorChild[]; // 协议共有的子项配置
|
||||
}
|
||||
// 执行请求-前后置操作配置
|
||||
export type ExecuteConditionProcessor = Partial<
|
||||
ScriptProcessor & SQLProcessor & TimeWaitingProcessor & ExtractProcessor
|
||||
> &
|
||||
ExecuteConditionProcessorCommon;
|
||||
export interface ExecuteConditionConfig {
|
||||
enableGlobal?: boolean; // 是否启用全局前/后置 默认为 true
|
||||
processors: ExecuteConditionProcessor[];
|
||||
activeItemId?: number;
|
||||
}
|
||||
// 执行请求-共用配置子项
|
||||
export interface ExecuteCommonChild {
|
||||
polymorphicName: string; // 协议多态名称,写死MsCommonElement
|
||||
|
|
|
@ -94,7 +94,7 @@
|
|||
>
|
||||
<template #content>
|
||||
<div class="ms-params-popover-title">
|
||||
{{ t('apiTestDebug.paramName') }}
|
||||
{{ t(columnConfig.title as string) }}
|
||||
</div>
|
||||
<div class="ms-params-popover-value">
|
||||
{{ record[columnConfig.dataIndex as string] }}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div
|
||||
v-if="props.requestResult?.responseResult?.responseCode"
|
||||
class="flex items-center justify-between gap-[24px] text-[14px]"
|
||||
class="flex items-center justify-between gap-[16px] text-[14px]"
|
||||
>
|
||||
<a-tooltip :content="props.requestResult.fakeErrorCode" :disabled="!props.requestResult.fakeErrorCode">
|
||||
<executeStatus :status="finalStatus" size="small" class="ml-[4px]" />
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
<template>
|
||||
<div v-show="props.requestResult?.responseResult.responseCode" class="h-full">
|
||||
<a-tabs v-model:active-key="activeTab" class="no-content border-b border-[var(--color-text-n8)]">
|
||||
<a-tab-pane v-for="item of responseCompositionTabList" :key="item.value" :title="item.label" />
|
||||
</a-tabs>
|
||||
<div class="flex items-center" :class="$slots.tabRight ? 'border-b border-[var(--color-text-n8)]' : ''">
|
||||
<a-tabs v-model:active-key="activeTab" class="no-content flex-1">
|
||||
<a-tab-pane v-for="item of responseCompositionTabList" :key="item.value" :title="item.label" />
|
||||
</a-tabs>
|
||||
<slot name="tabRight"></slot>
|
||||
</div>
|
||||
<div class="response-container">
|
||||
<ResBody
|
||||
v-if="activeTab === ResponseComposition.BODY"
|
||||
|
@ -138,7 +141,7 @@
|
|||
<style lang="less" scoped>
|
||||
.response-container {
|
||||
margin-top: 8px;
|
||||
height: calc(100% - 48px);
|
||||
height: calc(100% - 58px);
|
||||
}
|
||||
:deep(.arco-table-th) {
|
||||
background-color: var(--color-text-n9);
|
||||
|
|
|
@ -360,7 +360,6 @@
|
|||
import { TabErrorMessage } from '@/views/api-test/components/requestComposition/index.vue';
|
||||
import postcondition from '@/views/api-test/components/requestComposition/postcondition.vue';
|
||||
import precondition from '@/views/api-test/components/requestComposition/precondition.vue';
|
||||
import response from '@/views/api-test/components/requestComposition/response/index.vue';
|
||||
import setting from '@/views/api-test/components/requestComposition/setting.vue';
|
||||
|
||||
import { getPluginScript, getProtocolList } from '@/api/modules/api-test/common';
|
||||
|
@ -416,6 +415,9 @@
|
|||
const httpBody = defineAsyncComponent(() => import('@/views/api-test/components/requestComposition/body.vue'));
|
||||
const httpQuery = defineAsyncComponent(() => import('@/views/api-test/components/requestComposition/query.vue'));
|
||||
const httpRest = defineAsyncComponent(() => import('@/views/api-test/components/requestComposition/rest.vue'));
|
||||
const response = defineAsyncComponent(
|
||||
() => import('@/views/api-test/components/requestComposition/response/index.vue')
|
||||
);
|
||||
// const addDependencyDrawer = defineAsyncComponent(
|
||||
// () => import('@/views/api-test/management/components/addDependencyDrawer.vue')
|
||||
// );
|
||||
|
|
|
@ -21,31 +21,17 @@
|
|||
<div class="flex h-full flex-col">
|
||||
<loopPagination v-model:current-loop="currentLoop" :loop-total="loopTotal" />
|
||||
<div class="flex-1 overflow-y-hidden">
|
||||
<div v-if="step.stepType === ScenarioStepType.SCRIPT" class="flex h-full flex-col p-[8px]">
|
||||
<div class="mb-[8px] flex gap-[8px] text-[14px] font-medium text-[var(--color-text-1)]">
|
||||
{{ t('apiScenario.executionResult') }}
|
||||
<div class="one-line-text text-[var(--color-text-4)]">({{ step.name }})</div>
|
||||
</div>
|
||||
<div class="flex-1 bg-[var(--color-text-n9)] p-[12px]">
|
||||
<pre class="response-header-pre">{{ currentResponse?.console }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="response-result">
|
||||
<div class="response-result">
|
||||
<responseResult
|
||||
:active-tab="ResponseComposition.BODY"
|
||||
:request-result="currentResponse"
|
||||
:console="currentResponse?.console"
|
||||
:show-empty="false"
|
||||
:is-edit="false"
|
||||
:is-priority-local-exec="false"
|
||||
is-definition
|
||||
>
|
||||
<template #titleLeft>
|
||||
<div class="flex items-center text-[14px]">
|
||||
<div class="font-medium text-[var(--color-text-1)]">{{ t('apiScenario.response') }}</div>
|
||||
<a-tooltip :content="props.step.name">
|
||||
<div class="one-line-text">({{ props.step.name }})</div>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<template #tabRight>
|
||||
<responseCodeTimeSize :request-result="currentResponse" />
|
||||
</template>
|
||||
</responseResult>
|
||||
</div>
|
||||
|
@ -66,8 +52,6 @@
|
|||
import executeStatus from './executeStatus.vue';
|
||||
import loopPagination from './loopPagination.vue';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
import { RequestResult } from '@/models/apiTest/common';
|
||||
import { ScenarioStepItem } from '@/models/apiTest/scenario';
|
||||
import {
|
||||
|
@ -78,7 +62,10 @@
|
|||
} from '@/enums/apiEnum';
|
||||
|
||||
const responseResult = defineAsyncComponent(
|
||||
() => import('@/views/api-test/components/requestComposition/response/index.vue')
|
||||
() => import('@/views/api-test/components/requestComposition/response/result.vue')
|
||||
);
|
||||
const responseCodeTimeSize = defineAsyncComponent(
|
||||
() => import('@/views/api-test/components/requestComposition/response/responseCodeTimeSize.vue')
|
||||
);
|
||||
|
||||
const props = defineProps<{
|
||||
|
@ -88,8 +75,6 @@
|
|||
}>();
|
||||
const emit = defineEmits(['visibleChange']);
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const currentLoop = ref(1);
|
||||
const currentResponse = computed(() => props.stepResponses?.[props.step.uniqueId]?.[currentLoop.value - 1]);
|
||||
const loopTotal = computed(() => props.stepResponses?.[props.step.uniqueId]?.length || 0);
|
||||
|
@ -119,10 +104,11 @@
|
|||
|
||||
<style lang="less">
|
||||
.scenario-step-response-popover {
|
||||
width: 540px;
|
||||
height: 500px;
|
||||
padding: 8px 16px;
|
||||
width: 640px;
|
||||
height: 440px;
|
||||
.arco-popover-content {
|
||||
@apply h-full;
|
||||
@apply mt-0 h-full;
|
||||
.response-header-pre {
|
||||
@apply h-full overflow-auto bg-white;
|
||||
.ms-scroll-bar();
|
||||
|
@ -133,6 +119,9 @@
|
|||
.response-result {
|
||||
@apply h-full overflow-auto bg-white;
|
||||
.ms-scroll-bar();
|
||||
.arco-tabs-tab:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
.response {
|
||||
.response-head {
|
||||
background-color: var(--color-text-n9);
|
||||
|
|
|
@ -10,36 +10,71 @@
|
|||
@cancel="handleDrawerCancel"
|
||||
>
|
||||
<div class="scenario-script-drawer-content">
|
||||
<div class="ml-[16px] mt-[10px]">
|
||||
<!-- <stepTypeVue v-if="props.step" :step="props.step" /> -->
|
||||
{{ t('apiScenario.scriptOperationName') }}
|
||||
</div>
|
||||
<div class="ml-[16px] mt-[3px] max-w-[70%]">
|
||||
<a-input
|
||||
v-model="scriptName"
|
||||
:placeholder="t('apiScenario.scriptOperationNamePlaceholder')"
|
||||
:max-length="255"
|
||||
:disabled="isReadonly"
|
||||
/>
|
||||
</div>
|
||||
<div class="mt-[10px] flex flex-1 gap-[8px]">
|
||||
<conditionContent
|
||||
v-if="visible"
|
||||
v-model:data="activeItem"
|
||||
condition-type="scenario"
|
||||
:disabled="isReadonly"
|
||||
:is-build-in="true"
|
||||
script-code-editor-height="100%"
|
||||
@change="unSaved = true"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="currentResponse?.console" class="p-[8px]">
|
||||
<div class="mb-[8px] font-medium text-[var(--color-text-1)]">{{ t('apiScenario.executionResult') }}</div>
|
||||
<loopPagination v-model:current-loop="currentLoop" :loop-total="loopTotal" />
|
||||
<div class="h-[300px] bg-[var(--color-text-n9)] p-[12px]">
|
||||
<pre class="response-header-pre">{{ currentResponse?.console }}</pre>
|
||||
<MsTab
|
||||
v-model:active-key="activeTab"
|
||||
:content-tab-list="contentTabList"
|
||||
:get-text-func="() => ''"
|
||||
class="sticky-content no-content relative top-0 border-b px-[16px]"
|
||||
/>
|
||||
<template v-if="activeTab === 'script'">
|
||||
<div class="ml-[16px] mt-[10px]">
|
||||
<!-- <stepTypeVue v-if="props.step" :step="props.step" /> -->
|
||||
{{ t('apiScenario.scriptOperationName') }}
|
||||
</div>
|
||||
<div class="ml-[16px] mt-[3px] max-w-[70%]">
|
||||
<a-input
|
||||
v-model="scriptName"
|
||||
:placeholder="t('apiScenario.scriptOperationNamePlaceholder')"
|
||||
:max-length="255"
|
||||
:disabled="isReadonly"
|
||||
/>
|
||||
</div>
|
||||
<div class="mt-[10px] flex flex-1 gap-[8px]">
|
||||
<conditionContent
|
||||
v-if="visible"
|
||||
v-model:data="scriptVModel"
|
||||
condition-type="scenario"
|
||||
:disabled="isReadonly"
|
||||
:is-build-in="true"
|
||||
script-code-editor-height="100%"
|
||||
@change="unSaved = true"
|
||||
/>
|
||||
</div>
|
||||
<!-- <div v-if="currentResponse?.console" class="p-[8px]">
|
||||
<div class="mb-[8px] font-medium text-[var(--color-text-1)]">{{ t('apiScenario.executionResult') }}</div>
|
||||
<loopPagination v-model:current-loop="currentLoop" :loop-total="loopTotal" />
|
||||
<div class="h-[300px] bg-[var(--color-text-n9)] p-[12px]">
|
||||
<pre class="response-header-pre">{{ currentResponse?.console }}</pre>
|
||||
</div>
|
||||
</div> -->
|
||||
</template>
|
||||
<div v-else-if="scriptVModel.children" class="p-[16px]">
|
||||
<assertion
|
||||
v-model:params="scriptVModel.children[0].assertionConfig.assertions"
|
||||
is-definition
|
||||
:disabled="isReadonly"
|
||||
:assertion-config="scriptVModel.children[0].assertionConfig"
|
||||
/>
|
||||
</div>
|
||||
<response
|
||||
v-if="visible && currentResponse && currentResponse.responseResult.responseCode"
|
||||
ref="responseRef"
|
||||
v-model:active-layout="activeLayout"
|
||||
v-model:active-tab="responseActiveTab"
|
||||
class="response"
|
||||
:is-http-protocol="false"
|
||||
:is-priority-local-exec="isPriorityLocalExec"
|
||||
request-url=""
|
||||
:is-expanded="isVerticalExpanded"
|
||||
:request-result="currentResponse"
|
||||
:console="currentResponse?.console"
|
||||
:is-edit="false"
|
||||
hide-layout-switch
|
||||
>
|
||||
<template #titleRight>
|
||||
<loopPagination v-model:current-loop="currentLoop" :loop-total="loopTotal" />
|
||||
</template>
|
||||
</response>
|
||||
</div>
|
||||
<template v-if="!props.detail" #footer>
|
||||
<a-button type="secondary" @click="handleDrawerCancel">
|
||||
|
@ -60,6 +95,8 @@
|
|||
|
||||
import { LanguageEnum } from '@/components/pure/ms-code-editor/types';
|
||||
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
|
||||
import MsTab from '@/components/pure/ms-tab/index.vue';
|
||||
import assertion from '@/components/business/ms-assertion/index.vue';
|
||||
import loopPagination from './loopPagination.vue';
|
||||
|
||||
// import stepTypeVue from './stepType/stepType.vue';
|
||||
|
@ -67,9 +104,12 @@
|
|||
|
||||
import { ExecuteConditionProcessor, RequestResult } from '@/models/apiTest/common';
|
||||
import { ScenarioStepItem } from '@/models/apiTest/scenario';
|
||||
import { RequestConditionProcessor } from '@/enums/apiEnum';
|
||||
import { RequestConditionProcessor, ResponseComposition } from '@/enums/apiEnum';
|
||||
|
||||
const conditionContent = defineAsyncComponent(() => import('@/views/api-test/components/condition/content.vue'));
|
||||
const response = defineAsyncComponent(
|
||||
() => import('@/views/api-test/components/requestComposition/response/index.vue')
|
||||
);
|
||||
|
||||
const props = defineProps<{
|
||||
detail?: ExecuteConditionProcessor;
|
||||
|
@ -89,9 +129,17 @@
|
|||
scriptLanguage: LanguageEnum.BEANSHELL_JSR233,
|
||||
commonScriptInfo: {},
|
||||
polymorphicName: 'MsScriptElement',
|
||||
children: [
|
||||
{
|
||||
polymorphicName: 'MsCommonElement',
|
||||
assertionConfig: {
|
||||
assertions: [],
|
||||
},
|
||||
},
|
||||
],
|
||||
} as unknown as ExecuteConditionProcessor;
|
||||
const scriptName = ref('');
|
||||
const activeItem = ref<ExecuteConditionProcessor>(cloneDeep(defaultScript));
|
||||
const scriptVModel = ref<ExecuteConditionProcessor>(cloneDeep(defaultScript));
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
|
@ -105,13 +153,17 @@
|
|||
}
|
||||
});
|
||||
const loopTotal = computed(() => (props.step?.uniqueId && props.stepResponses?.[props.step?.uniqueId]?.length) || 0);
|
||||
const activeLayout = ref<'horizontal' | 'vertical'>('vertical');
|
||||
const responseActiveTab = ref<ResponseComposition>(ResponseComposition.BODY);
|
||||
const isPriorityLocalExec = inject<Ref<boolean>>('isPriorityLocalExec');
|
||||
const isVerticalExpanded = computed(() => activeLayout.value === 'vertical');
|
||||
|
||||
watch(
|
||||
() => visible.value,
|
||||
(val) => {
|
||||
if (val) {
|
||||
scriptName.value = props.detail ? props.name || '' : '';
|
||||
activeItem.value = cloneDeep(
|
||||
scriptVModel.value = cloneDeep(
|
||||
props.detail
|
||||
? {
|
||||
...props.detail,
|
||||
|
@ -124,25 +176,38 @@
|
|||
}
|
||||
);
|
||||
|
||||
const activeTab = ref<'script' | 'assertion'>('script');
|
||||
const contentTabList = [
|
||||
{
|
||||
value: 'script',
|
||||
label: t('apiTestManagement.script'),
|
||||
},
|
||||
{
|
||||
value: 'assertion',
|
||||
label: t('apiTestDebug.assertion'),
|
||||
},
|
||||
];
|
||||
|
||||
function handleDrawerCancel() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
function saveAndContinue() {
|
||||
emit('add', scriptName.value, activeItem.value);
|
||||
emit('add', scriptName.value, scriptVModel.value);
|
||||
}
|
||||
|
||||
function save() {
|
||||
emit('add', scriptName.value, activeItem.value);
|
||||
emit('add', scriptName.value, scriptVModel.value);
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
function handleClose() {
|
||||
if (props.detail) {
|
||||
emit('save', scriptName.value, activeItem.value, unSaved.value);
|
||||
emit('save', scriptName.value, scriptVModel.value, unSaved.value);
|
||||
}
|
||||
scriptName.value = '';
|
||||
activeItem.value = defaultScript as unknown as ExecuteConditionProcessor;
|
||||
scriptVModel.value = defaultScript as unknown as ExecuteConditionProcessor;
|
||||
activeTab.value = 'script';
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -156,6 +221,9 @@
|
|||
</style>
|
||||
|
||||
<style lang="less" scoped>
|
||||
:deep(.arco-tabs-tab:first-child) {
|
||||
margin-left: 0;
|
||||
}
|
||||
.response-header-pre {
|
||||
@apply h-full overflow-auto bg-white;
|
||||
.ms-scroll-bar();
|
||||
|
@ -163,4 +231,9 @@
|
|||
padding: 8px 12px;
|
||||
border-radius: var(--border-radius-small);
|
||||
}
|
||||
.sticky-content {
|
||||
@apply sticky overflow-visible bg-white;
|
||||
|
||||
z-index: 101;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -48,8 +48,9 @@
|
|||
<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 class="text-[var(--color-text-1)]">{{ scenario.executeTime }}</div>
|
||||
</div>
|
||||
<a-divider direction="vertical" :margin="8"></a-divider>
|
||||
<div class="action-group">
|
||||
<div class="text-[var(--color-text-4)]">{{ t('apiScenario.executeResult') }}</div>
|
||||
<div class="flex items-center gap-[4px]">
|
||||
|
@ -67,8 +68,10 @@
|
|||
<MsButton
|
||||
v-if="scenario.isDebug === false && !scenario.executeLoading && !scenario.isNew"
|
||||
type="text"
|
||||
class="ml-[8px]"
|
||||
@click="checkReport"
|
||||
>
|
||||
<icon-eye class="mr-[4px] text-[rgb(var(--primary-5))]" />
|
||||
{{ t('apiScenario.checkReport') }}
|
||||
</MsButton>
|
||||
</div>
|
||||
|
|
|
@ -2,6 +2,10 @@
|
|||
<div class="h-full w-full overflow-hidden">
|
||||
<div class="px-[24px] pt-[16px]">
|
||||
<MsDetailCard :title="`【${scenario.num}】${scenario.name}`" :description="description" class="!py-[8px]">
|
||||
<template #titlePrefix>
|
||||
<apiStatus :status="scenario.status" size="small" />
|
||||
<caseLevel :case-level="scenario.priority as CaseLevel" />
|
||||
</template>
|
||||
<template #titleAppend>
|
||||
<a-tooltip :content="t(scenario.follow ? 'common.forked' : 'common.notForked')">
|
||||
<MsIcon
|
||||
|
@ -21,10 +25,6 @@
|
|||
@click="share"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<apiStatus :status="scenario.status" size="small" />
|
||||
</template>
|
||||
<template #priority="{ value }">
|
||||
<caseLevel :case-level="value as CaseLevel" />
|
||||
</template>
|
||||
</MsDetailCard>
|
||||
</div>
|
||||
|
@ -190,11 +190,6 @@
|
|||
});
|
||||
|
||||
const description = computed(() => [
|
||||
{
|
||||
key: 'priority',
|
||||
locale: 'apiScenario.scenarioLevel',
|
||||
value: scenario.value.priority,
|
||||
},
|
||||
{
|
||||
key: 'tag',
|
||||
locale: 'common.tag',
|
||||
|
|
Loading…
Reference in New Issue