feat(接口测试): 增加场景级别断言配置

This commit is contained in:
Captain.B 2020-11-11 12:08:22 +08:00
parent d772fac5e3
commit 6b443008cd
5 changed files with 149 additions and 124 deletions

View File

@ -1,5 +1,6 @@
package io.metersphere.api.dto.scenario; package io.metersphere.api.dto.scenario;
import io.metersphere.api.dto.scenario.assertions.Assertions;
import io.metersphere.api.dto.scenario.request.Request; import io.metersphere.api.dto.scenario.request.Request;
import lombok.Data; import lombok.Data;
@ -15,6 +16,7 @@ public class Scenario {
private List<KeyValue> variables; private List<KeyValue> variables;
private List<KeyValue> headers; private List<KeyValue> headers;
private List<Request> requests; private List<Request> requests;
private Assertions assertions;
private DubboConfig dubboConfig; private DubboConfig dubboConfig;
private TCPConfig tcpConfig; private TCPConfig tcpConfig;
private List<DatabaseConfig> databaseConfigs; private List<DatabaseConfig> databaseConfigs;

View File

@ -76,28 +76,28 @@
</template> </template>
<script> <script>
import MsApiScenarioConfig from "./components/ApiScenarioConfig"; import MsApiScenarioConfig from "./components/ApiScenarioConfig";
import {Scenario, Test} from "./model/ScenarioModel" import {Scenario, Test} from "./model/ScenarioModel"
import MsApiReportStatus from "../report/ApiReportStatus"; import MsApiReportStatus from "../report/ApiReportStatus";
import MsApiReportDialog from "./ApiReportDialog"; import MsApiReportDialog from "./ApiReportDialog";
import {checkoutTestManagerOrTestUser, downloadFile, getUUID} from "@/common/js/utils"; import {checkoutTestManagerOrTestUser, downloadFile, getUUID} from "@/common/js/utils";
import MsScheduleConfig from "../../common/components/MsScheduleConfig"; import MsScheduleConfig from "../../common/components/MsScheduleConfig";
import ApiImport from "./components/import/ApiImport"; import ApiImport from "./components/import/ApiImport";
import {ApiEvent, LIST_CHANGE} from "@/business/components/common/head/ListEvent"; import {ApiEvent, LIST_CHANGE} from "@/business/components/common/head/ListEvent";
import MsContainer from "@/business/components/common/components/MsContainer"; import MsContainer from "@/business/components/common/components/MsContainer";
import MsMainContainer from "@/business/components/common/components/MsMainContainer"; import MsMainContainer from "@/business/components/common/components/MsMainContainer";
import MsJarConfig from "./components/jar/JarConfig"; import MsJarConfig from "./components/jar/JarConfig";
export default { export default {
name: "MsApiTestConfig", name: "MsApiTestConfig",
components: { components: {
MsJarConfig, MsJarConfig,
MsMainContainer, MsMainContainer,
MsContainer, ApiImport, MsScheduleConfig, MsApiReportDialog, MsApiReportStatus, MsApiScenarioConfig MsContainer, ApiImport, MsScheduleConfig, MsApiReportDialog, MsApiReportStatus, MsApiScenarioConfig
}, },
props: ["id"], props: ["id"],
data() { data() {
return { return {
@ -305,6 +305,7 @@
}, },
cancel() { cancel() {
this.$router.push('/api/test/list/all'); this.$router.push('/api/test/list/all');
// console.log(this.test.toJMX().xml);
}, },
handleCommand(command) { handleCommand(command) {
switch (command) { switch (command) {

View File

@ -52,6 +52,9 @@
<el-tab-pane :label="$t('api_test.environment.tcp_config')" name="tcp"> <el-tab-pane :label="$t('api_test.environment.tcp_config')" name="tcp">
<ms-tcp-config :config="scenario.tcpConfig" :is-read-only="isReadOnly"/> <ms-tcp-config :config="scenario.tcpConfig" :is-read-only="isReadOnly"/>
</el-tab-pane> </el-tab-pane>
<el-tab-pane :label="$t('api_test.request.assertions.label')" name="assertions">
<ms-api-assertions :scenario="scenario" :is-read-only="isReadOnly" :assertions="scenario.assertions"/>
</el-tab-pane>
</el-tabs> </el-tabs>
<api-environment-config ref="environmentConfig" @close="environmentConfigClose"/> <api-environment-config ref="environmentConfig" @close="environmentConfigClose"/>
@ -72,6 +75,7 @@ import MsDubboConsumerService from "@/business/components/api/test/components/re
import MsDatabaseConfig from "./request/database/DatabaseConfig"; import MsDatabaseConfig from "./request/database/DatabaseConfig";
import {parseEnvironment} from "../model/EnvironmentModel"; import {parseEnvironment} from "../model/EnvironmentModel";
import MsTcpConfig from "@/business/components/api/test/components/request/tcp/TcpConfig"; import MsTcpConfig from "@/business/components/api/test/components/request/tcp/TcpConfig";
import MsApiAssertions from "@/business/components/api/test/components/assertion/ApiAssertions";
export default { export default {
name: "MsApiScenarioForm", name: "MsApiScenarioForm",
@ -79,7 +83,8 @@ export default {
MsTcpConfig, MsTcpConfig,
MsDatabaseConfig, MsDatabaseConfig,
MsDubboConsumerService, MsDubboConsumerService,
MsDubboConfigCenter, MsDubboRegistryCenter, ApiEnvironmentConfig, MsApiScenarioVariables, MsApiKeyValue MsDubboConfigCenter, MsDubboRegistryCenter, ApiEnvironmentConfig, MsApiScenarioVariables, MsApiKeyValue,
MsApiAssertions
}, },
props: { props: {
scenario: Scenario, scenario: Scenario,

View File

@ -3,7 +3,8 @@
<div class="assertion-add"> <div class="assertion-add">
<el-row :gutter="10"> <el-row :gutter="10">
<el-col :span="4"> <el-col :span="4">
<el-select :disabled="isReadOnly" class="assertion-item" v-model="type" :placeholder="$t('api_test.request.assertions.select_type')" <el-select :disabled="isReadOnly" class="assertion-item" v-model="type"
:placeholder="$t('api_test.request.assertions.select_type')"
size="small"> size="small">
<el-option :label="$t('api_test.request.assertions.text')" :value="options.TEXT"/> <el-option :label="$t('api_test.request.assertions.text')" :value="options.TEXT"/>
<el-option :label="$t('api_test.request.assertions.regex')" :value="options.REGEX"/> <el-option :label="$t('api_test.request.assertions.regex')" :value="options.REGEX"/>
@ -14,13 +15,18 @@
</el-select> </el-select>
</el-col> </el-col>
<el-col :span="20"> <el-col :span="20">
<ms-api-assertion-text :is-read-only="isReadOnly" :list="assertions.regex" v-if="type === options.TEXT" :callback="after"/> <ms-api-assertion-text :is-read-only="isReadOnly" :list="assertions.regex" v-if="type === options.TEXT"
<ms-api-assertion-regex :is-read-only="isReadOnly" :list="assertions.regex" v-if="type === options.REGEX" :callback="after"/> :callback="after"/>
<ms-api-assertion-json-path :is-read-only="isReadOnly" :list="assertions.jsonPath" v-if="type === options.JSON_PATH" :callback="after"/> <ms-api-assertion-regex :is-read-only="isReadOnly" :list="assertions.regex" v-if="type === options.REGEX"
<ms-api-assertion-x-path2 :is-read-only="isReadOnly" :list="assertions.xpath2" v-if="type === options.XPATH2" :callback="after"/> :callback="after"/>
<ms-api-assertion-json-path :is-read-only="isReadOnly" :list="assertions.jsonPath"
v-if="type === options.JSON_PATH" :callback="after"/>
<ms-api-assertion-x-path2 :is-read-only="isReadOnly" :list="assertions.xpath2" v-if="type === options.XPATH2"
:callback="after"/>
<ms-api-assertion-duration :is-read-only="isReadOnly" v-model="time" :duration="assertions.duration" <ms-api-assertion-duration :is-read-only="isReadOnly" v-model="time" :duration="assertions.duration"
v-if="type === options.DURATION" :callback="after"/> v-if="type === options.DURATION" :callback="after"/>
<ms-api-assertion-jsr223 :is-read-only="isReadOnly" :list="assertions.jsr223" v-if="type === options.JSR223" :callback="after"/> <ms-api-assertion-jsr223 :is-read-only="isReadOnly" :list="assertions.jsr223" v-if="type === options.JSR223"
:callback="after"/>
<el-button v-if="!type" :disabled="true" type="primary" size="small"> <el-button v-if="!type" :disabled="true" type="primary" size="small">
{{ $t('api_test.request.assertions.add') }} {{ $t('api_test.request.assertions.add') }}
</el-button> </el-button>
@ -28,125 +34,128 @@
</el-row> </el-row>
</div> </div>
<div> <div v-if="!scenario">
<el-row :gutter="10" class="json-path-suggest-button"> <el-row :gutter="10" class="json-path-suggest-button">
<el-button size="small" type="primary" @click="suggestJsonOpen"> <el-button size="small" type="primary" @click="suggestJsonOpen">
{{$t('api_test.request.assertions.json_path_suggest')}} {{ $t('api_test.request.assertions.json_path_suggest') }}
</el-button> </el-button>
<el-button size="small" type="danger" @click="clearJson"> <el-button size="small" type="danger" @click="clearJson">
{{$t('api_test.request.assertions.json_path_clear')}} {{ $t('api_test.request.assertions.json_path_clear') }}
</el-button> </el-button>
</el-row> </el-row>
</div> </div>
<ms-api-jsonpath-suggest-list @addJsonpathSuggest="addJsonpathSuggest" :request="request" ref="jsonpathSuggestList"/> <ms-api-jsonpath-suggest-list @addJsonpathSuggest="addJsonpathSuggest" :request="request"
ref="jsonpathSuggestList"/>
<ms-api-assertions-edit :is-read-only="isReadOnly" :assertions="assertions"/> <ms-api-assertions-edit :is-read-only="isReadOnly" :assertions="assertions"/>
</div> </div>
</template> </template>
<script> <script>
import MsApiAssertionText from "./ApiAssertionText"; import MsApiAssertionText from "./ApiAssertionText";
import MsApiAssertionRegex from "./ApiAssertionRegex"; import MsApiAssertionRegex from "./ApiAssertionRegex";
import MsApiAssertionDuration from "./ApiAssertionDuration"; import MsApiAssertionDuration from "./ApiAssertionDuration";
import {ASSERTION_TYPE, Assertions, HttpRequest, JSONPath} from "../../model/ScenarioModel"; import {ASSERTION_TYPE, Assertions, HttpRequest, JSONPath, Scenario} from "../../model/ScenarioModel";
import MsApiAssertionsEdit from "./ApiAssertionsEdit"; import MsApiAssertionsEdit from "./ApiAssertionsEdit";
import MsApiAssertionJsonPath from "./ApiAssertionJsonPath"; import MsApiAssertionJsonPath from "./ApiAssertionJsonPath";
import MsApiAssertionJsr223 from "@/business/components/api/test/components/assertion/ApiAssertionJsr223"; import MsApiAssertionJsr223 from "@/business/components/api/test/components/assertion/ApiAssertionJsr223";
import MsApiJsonpathSuggestList from "./ApiJsonpathSuggestList"; import MsApiJsonpathSuggestList from "./ApiJsonpathSuggestList";
import MsApiAssertionXPath2 from "./ApiAssertionXPath2"; import MsApiAssertionXPath2 from "./ApiAssertionXPath2";
export default { export default {
name: "MsApiAssertions", name: "MsApiAssertions",
components: { components: {
MsApiAssertionXPath2, MsApiAssertionXPath2,
MsApiAssertionJsr223, MsApiAssertionJsr223,
MsApiJsonpathSuggestList, MsApiJsonpathSuggestList,
MsApiAssertionJsonPath, MsApiAssertionJsonPath,
MsApiAssertionsEdit, MsApiAssertionDuration, MsApiAssertionRegex, MsApiAssertionText}, MsApiAssertionsEdit, MsApiAssertionDuration, MsApiAssertionRegex, MsApiAssertionText
},
props: { props: {
assertions: Assertions, assertions: Assertions,
request: HttpRequest, request: HttpRequest,
isReadOnly: { scenario: Scenario,
type: Boolean, isReadOnly: {
default: false type: Boolean,
} default: false
},
data() {
return {
options: ASSERTION_TYPE,
time: "",
type: "",
}
},
methods: {
after() {
this.type = "";
},
suggestJsonOpen() {
if (!this.request.debugRequestResult) {
this.$message(this.$t('api_test.request.assertions.debug_first'));
return;
}
this.$refs.jsonpathSuggestList.open();
},
addJsonpathSuggest(jsonPathList) {
jsonPathList.forEach(jsonPath => {
let jsonItem = new JSONPath();
jsonItem.expression = jsonPath.json_path;
jsonItem.expect = jsonPath.json_value;
jsonItem.setJSONPathDescription();
this.assertions.jsonPath.push(jsonItem);
});
},
clearJson() {
this.assertions.jsonPath = [];
}
} }
},
data() {
return {
options: ASSERTION_TYPE,
time: "",
type: "",
}
},
methods: {
after() {
this.type = "";
},
suggestJsonOpen() {
if (!this.request.debugRequestResult) {
this.$message(this.$t('api_test.request.assertions.debug_first'));
return;
}
this.$refs.jsonpathSuggestList.open();
},
addJsonpathSuggest(jsonPathList) {
jsonPathList.forEach(jsonPath => {
let jsonItem = new JSONPath();
jsonItem.expression = jsonPath.json_path;
jsonItem.expect = jsonPath.json_value;
jsonItem.setJSONPathDescription();
this.assertions.jsonPath.push(jsonItem);
});
},
clearJson() {
this.assertions.jsonPath = [];
}
} }
}
</script> </script>
<style scoped> <style scoped>
.assertion-item { .assertion-item {
width: 100%; width: 100%;
} }
.assertion-add { .assertion-add {
padding: 10px; padding: 10px;
border: #DCDFE6 solid 1px; border: #DCDFE6 solid 1px;
margin: 5px 0; margin: 5px 0;
border-radius: 5px; border-radius: 5px;
} }
.bg-purple-dark { .bg-purple-dark {
background: #99a9bf; background: #99a9bf;
} }
.bg-purple { .bg-purple {
background: #d3dce6; background: #d3dce6;
} }
.bg-purple-light { .bg-purple-light {
background: #e5e9f2; background: #e5e9f2;
} }
.grid-content { .grid-content {
border-radius: 4px; border-radius: 4px;
min-height: 36px; min-height: 36px;
} }
.row-bg { .row-bg {
padding: 10px 0; padding: 10px 0;
background-color: #f9fafc; background-color: #f9fafc;
} }
.json-path-suggest-button { .json-path-suggest-button {
text-align: right; text-align: right;
} }
</style> </style>

View File

@ -1,5 +1,6 @@
import { import {
Arguments, Arguments,
ConstantTimer as JMXConstantTimer,
CookieManager, CookieManager,
DNSCacheManager, DNSCacheManager,
DubboSample, DubboSample,
@ -10,22 +11,24 @@ import {
HTTPSamplerArguments, HTTPSamplerArguments,
HTTPsamplerFiles, HTTPsamplerFiles,
HTTPSamplerProxy, HTTPSamplerProxy,
IfController as JMXIfController,
JDBCDataSource, JDBCDataSource,
JDBCSampler, JDBCSampler,
JSONPathAssertion, JSONPathAssertion,
JSONPostProcessor, JSONPostProcessor,
JSR223Assertion,
JSR223PostProcessor, JSR223PostProcessor,
JSR223PreProcessor, JSR223PreProcessor,
RegexExtractor, RegexExtractor,
ResponseCodeAssertion, ResponseCodeAssertion,
ResponseDataAssertion, ResponseDataAssertion,
ResponseHeadersAssertion, ResponseHeadersAssertion,
TCPSampler,
TestElement, TestElement,
TestPlan, TestPlan,
ThreadGroup, ThreadGroup,
XPath2Assertion,
XPath2Extractor, XPath2Extractor,
IfController as JMXIfController,
ConstantTimer as JMXConstantTimer, TCPSampler, JSR223Assertion, XPath2Assertion,
} from "./JMX"; } from "./JMX";
import Mock from "mockjs"; import Mock from "mockjs";
import {funcFilters} from "@/common/js/func-filter"; import {funcFilters} from "@/common/js/func-filter";
@ -226,6 +229,7 @@ export class Scenario extends BaseConfig {
this.enable = true; this.enable = true;
this.databaseConfigs = []; this.databaseConfigs = [];
this.tcpConfig = undefined; this.tcpConfig = undefined;
this.assertions = undefined;
this.set(options); this.set(options);
this.sets({ this.sets({
@ -242,6 +246,7 @@ export class Scenario extends BaseConfig {
options.databaseConfigs = options.databaseConfigs || []; options.databaseConfigs = options.databaseConfigs || [];
options.dubboConfig = new DubboConfig(options.dubboConfig); options.dubboConfig = new DubboConfig(options.dubboConfig);
options.tcpConfig = new TCPConfig(options.tcpConfig); options.tcpConfig = new TCPConfig(options.tcpConfig);
options.assertions = new Assertions(options.assertions);
return options; return options;
} }
@ -1151,6 +1156,9 @@ class JMXGenerator {
this.addScenarioCookieManager(threadGroup, scenario); this.addScenarioCookieManager(threadGroup, scenario);
this.addJDBCDataSources(threadGroup, scenario); this.addJDBCDataSources(threadGroup, scenario);
this.addAssertion(threadGroup, scenario);
scenario.requests.forEach(request => { scenario.requests.forEach(request => {
if (request.enable) { if (request.enable) {
if (!request.isValid()) return; if (!request.isValid()) return;
@ -1175,7 +1183,7 @@ class JMXGenerator {
this.addRequestExtractor(sampler, request); this.addRequestExtractor(sampler, request);
this.addRequestAssertion(sampler, request); this.addAssertion(sampler, request);
this.addJSR223PreProcessor(sampler, request); this.addJSR223PreProcessor(sampler, request);
@ -1467,7 +1475,7 @@ class JMXGenerator {
httpSamplerProxy.add(new HTTPsamplerFiles(files)); httpSamplerProxy.add(new HTTPsamplerFiles(files));
} }
addRequestAssertion(httpSamplerProxy, request) { addAssertion(httpSamplerProxy, request) {
let assertions = request.assertions; let assertions = request.assertions;
if (assertions.regex.length > 0) { if (assertions.regex.length > 0) {
assertions.regex.filter(this.filter).forEach(regex => { assertions.regex.filter(this.filter).forEach(regex => {