feat(接口测试): 场景脚本断言&响应内容&样式调整

This commit is contained in:
baiqi 2024-08-06 14:27:22 +08:00 committed by 刘瑞斌
parent de429a90fb
commit 5c9162f898
10 changed files with 162 additions and 90 deletions

View File

@ -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) }}

View File

@ -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

View File

@ -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] }}

View File

@ -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]" />

View File

@ -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);

View File

@ -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')
// );

View File

@ -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);

View File

@ -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>

View File

@ -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>

View File

@ -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',