feat(接口定义): 完成提取参数和断言规则

This commit is contained in:
fit2-zhao 2020-11-23 15:17:19 +08:00
parent 595c9572de
commit 7f2fe8acd1
23 changed files with 141 additions and 108 deletions

View File

@ -220,6 +220,7 @@ public class ApiDefinitionService {
createBodyFiles(bodyUploadIds, bodyFiles); createBodyFiles(bodyUploadIds, bodyFiles);
HashTree hashTree = request.getTestElement().generateHashTree(); HashTree hashTree = request.getTestElement().generateHashTree();
request.getTestElement().getJmx(hashTree);
// 调用执行方法 // 调用执行方法
jMeterService.runDefinition(request.getId(), hashTree, request.getReportId(), ApiRunMode.DELIMIT.name()); jMeterService.runDefinition(request.getId(), hashTree, request.getReportId(), ApiRunMode.DELIMIT.name());
return request.getId(); return request.getId();

@ -1 +1 @@
Subproject commit 24047fea950a74f7848a9fdaa857a22b884c4ce2 Subproject commit 419c75bca64b7c5bfbd1194d7f0fd9919f0caa04

View File

@ -167,7 +167,6 @@ check_owner_comment=The current user does not have permission to manipulate this
upload_content_is_null=Imported content is empty upload_content_is_null=Imported content is empty
test_plan_notification=Test plan notification test_plan_notification=Test plan notification
task_defect_notification=Task defect notification task_defect_notification=Task defect notification
task_notification=Jenkins Task notification
task_notification_=Timing task result notification task_notification_=Timing task result notification
api_definition_url_not_repeating=The interface request address already exists api_definition_url_not_repeating=The interface request address already exists
task_notification_jenkins=Jenkins Task notification task_notification_jenkins=Jenkins Task notification

View File

@ -168,7 +168,6 @@ check_owner_comment=当前用户没有操作此评论的权限
upload_content_is_null=导入内容为空 upload_content_is_null=导入内容为空
test_plan_notification=测试计划通知 test_plan_notification=测试计划通知
task_defect_notification=缺陷任务通知 task_defect_notification=缺陷任务通知
task_notification=jenkins任务通知
task_notification_=定时任务结果通知 task_notification_=定时任务结果通知
api_definition_url_not_repeating=接口请求地址已经存在 api_definition_url_not_repeating=接口请求地址已经存在
task_notification_jenkins=jenkins任务通知 task_notification_jenkins=jenkins任务通知

View File

@ -171,7 +171,5 @@ test_plan_notification=測試計畫通知
task_defect_notification=缺陷任務通知 task_defect_notification=缺陷任務通知
task_notification_jenkins=jenkins任務通知 task_notification_jenkins=jenkins任務通知
task_notification=任務通知 task_notification=任務通知
task_notification=jenkins任務通知
task_notification_=定時任務通知 task_notification_=定時任務通知
api_definition_url_not_repeating=接口請求地址已經存在 api_definition_url_not_repeating=接口請求地址已經存在

View File

@ -17,14 +17,12 @@
<script> <script>
import {Duration} from "../../model/ApiTestModel";
export default { export default {
name: "MsApiAssertionDuration", name: "MsApiAssertionDuration",
props: { props: {
duration: Duration,
value: [Number, String], value: [Number, String],
duration:{},
edit: Boolean, edit: Boolean,
callback: Function, callback: Function,
isReadOnly: { isReadOnly: {
@ -42,6 +40,7 @@
}, },
remove() { remove() {
this.duration.value = undefined; this.duration.value = undefined;
}, },
change(value) { change(value) {
if (this.validate()) { if (this.validate()) {

View File

@ -27,7 +27,6 @@
props: { props: {
jsonPath: { jsonPath: {
type: JSONPath,
default: () => { default: () => {
return new JSONPath(); return new JSONPath();
} }

View File

@ -64,7 +64,6 @@ export default {
components: {MsDialogFooter, MsJsr233Processor}, components: {MsDialogFooter, MsJsr233Processor},
props: { props: {
assertion: { assertion: {
type: AssertionJSR223,
default: () => { default: () => {
return new AssertionJSR223(); return new AssertionJSR223();
} }

View File

@ -37,7 +37,6 @@ export default {
props: { props: {
regex: { regex: {
type: Regex,
default: () => { default: () => {
return new Regex(); return new Regex();
} }

View File

@ -23,7 +23,6 @@
props: { props: {
xPath2: { xPath2: {
type: XPath2,
default: () => { default: () => {
return new XPath2(); return new XPath2();
} }

View File

@ -1,12 +1,18 @@
<template> <template>
<div style="border:1px #DCDFE6 solid; height: 100%;border-radius: 4px ;width: 100% ;margin-top: 20px"> <div style="border:1px #DCDFE6 solid; height: 100%;border-radius: 4px ;width: 100% ;margin-top: 20px" v-loading="loading">
<div>
<el-button class="ms-left-buttion" size="small" type="danger" plain>{{$t('api_test.definition.request.assertions_rule')}}</el-button>
<el-button size="small" style="float: right;margin-top: 0px" @click="remove">移除</el-button>
</div>
<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" <el-select :disabled="isReadOnly" class="assertion-item" v-model="type"
:placeholder="$t('api_test.request.assertions.select_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"/>
<el-option :label="'JSONPath'" :value="options.JSON_PATH"/> <el-option :label="'JSONPath'" :value="options.JSON_PATH"/>
<el-option :label="'XPath'" :value="options.XPATH2"/> <el-option :label="'XPath'" :value="options.XPATH2"/>
@ -15,8 +21,10 @@
</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"
:callback="after"/>
-->
<ms-api-assertion-regex :is-read-only="isReadOnly" :list="assertions.regex" v-if="type === options.REGEX" <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" <ms-api-assertion-json-path :is-read-only="isReadOnly" :list="assertions.jsonPath"
@ -77,7 +85,7 @@
}, },
props: { props: {
assertions: Assertions, assertions: {},
request: {}, request: {},
scenario: Scenario, scenario: Scenario,
isReadOnly: { isReadOnly: {
@ -91,12 +99,14 @@
options: ASSERTION_TYPE, options: ASSERTION_TYPE,
time: "", time: "",
type: "", type: "",
loading: false,
} }
}, },
methods: { methods: {
after() { after() {
this.type = ""; this.type = "";
this.reload();
}, },
suggestJsonOpen() { suggestJsonOpen() {
if (!this.request.debugRequestResult) { if (!this.request.debugRequestResult) {
@ -105,6 +115,15 @@
} }
this.$refs.jsonpathSuggestList.open(); this.$refs.jsonpathSuggestList.open();
}, },
reload() {
this.loading = true
this.$nextTick(() => {
this.loading = false
})
},
remove(){
this.$emit('remove', this.assertions);
},
addJsonpathSuggest(jsonPathList) { addJsonpathSuggest(jsonPathList) {
jsonPathList.forEach(jsonPath => { jsonPathList.forEach(jsonPath => {
let jsonItem = new JSONPath(); let jsonItem = new JSONPath();

View File

@ -54,7 +54,6 @@
<script> <script>
import MsApiAssertionRegex from "./ApiAssertionRegex"; import MsApiAssertionRegex from "./ApiAssertionRegex";
import MsApiAssertionDuration from "./ApiAssertionDuration"; import MsApiAssertionDuration from "./ApiAssertionDuration";
import {Assertions} from "../../model/ApiTestModel";
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 MsApiAssertionXPath2 from "./ApiAssertionXPath2"; import MsApiAssertionXPath2 from "./ApiAssertionXPath2";
@ -67,7 +66,7 @@ export default {
MsApiAssertionJsr223, MsApiAssertionJsonPath, MsApiAssertionDuration, MsApiAssertionRegex}, MsApiAssertionJsr223, MsApiAssertionJsonPath, MsApiAssertionDuration, MsApiAssertionRegex},
props: { props: {
assertions: Assertions, assertions: {},
isReadOnly: { isReadOnly: {
type: Boolean, type: Boolean,
default: false default: false

View File

@ -50,64 +50,65 @@
</template> </template>
<script> <script>
import MsDialogFooter from "../../../../common/components/MsDialogFooter"; import MsDialogFooter from "../../../../common/components/MsDialogFooter";
import {HttpRequest} from "../../model/ApiTestModel"; import {HttpRequest} from "../../model/ApiTestModel";
export default {
name: "MsApiJsonpathSuggestList", export default {
components: {MsDialogFooter}, name: "MsApiJsonpathSuggestList",
data() { components: {MsDialogFooter},
return { data() {
result: {}, return {
dialogFormVisible: false, result: {},
isCheckAll: false, dialogFormVisible: false,
selectItems: new Set(), isCheckAll: false,
jsonPathList: [], selectItems: new Set(),
}; jsonPathList: [],
};
},
props: {
request: HttpRequest,
},
methods: {
close() {
this.selectItems.clear();
}, },
props: { open() {
request: HttpRequest, this.getJsonPaths();
}, },
methods: { getJsonPaths() {
close() { if (this.request.debugRequestResult) {
this.selectItems.clear(); let param = {
}, jsonPath: this.request.debugRequestResult.responseResult.body
open() { };
this.getJsonPaths(); this.result = this.$post("/api/getJsonPaths", param).then(response => {
}, this.jsonPathList = response.data.data;
getJsonPaths() { this.dialogFormVisible = true;
if (this.request.debugRequestResult) { }).catch(() => {
let param = { this.$warning(this.$t('api_test.request.assertions.json_path_err'));
jsonPath: this.request.debugRequestResult.responseResult.body this.dialogFormVisible = false;
}; });
this.result = this.$post("/api/getJsonPaths", param).then(response => {
this.jsonPathList = response.data.data;
this.dialogFormVisible = true;
}).catch(() => {
this.$warning(this.$t('api_test.request.assertions.json_path_err'));
this.dialogFormVisible = false;
});
}
},
handleSelectAll(selection) {
if (selection.length > 0) {
this.selectItems = new Set(this.jsonPathList);
} else {
this.selectItems = new Set();
}
},
handleSelectionChange(selection, row) {
if (this.selectItems.has(row)) {
this.selectItems.delete(row);
} else {
this.selectItems.add(row);
}
},
commit() {
this.$emit("addJsonpathSuggest", this.selectItems);
this.dialogFormVisible = false;
} }
},
handleSelectAll(selection) {
if (selection.length > 0) {
this.selectItems = new Set(this.jsonPathList);
} else {
this.selectItems = new Set();
}
},
handleSelectionChange(selection, row) {
if (this.selectItems.has(row)) {
this.selectItems.delete(row);
} else {
this.selectItems.add(row);
}
},
commit() {
this.$emit("addJsonpathSuggest", this.selectItems);
this.dialogFormVisible = false;
} }
} }
}
</script> </script>
<style scoped> <style scoped>

View File

@ -1,6 +1,8 @@
<template> <template>
<div style="border:1px #DCDFE6 solid; height: 100%;border-radius: 4px ;width: 100% ;margin-top: 20px"> <div style="border:1px #DCDFE6 solid; height: 100%;border-radius: 4px ;width: 100% ;margin-top: 20px">
<el-button class="ms-left-buttion" size="small" type="info" plain>提取参数</el-button> <el-button class="ms-left-buttion" size="small" type="info" plain>{{$t('api_test.definition.request.extract_param')}}</el-button>
<el-button size="small" style="float: right;margin-top: 0px" @click="remove">移除</el-button>
<div style="margin: 20px"> <div style="margin: 20px">
<div class="extract-description"> <div class="extract-description">
{{$t('api_test.request.extract.description')}} {{$t('api_test.request.extract.description')}}
@ -59,7 +61,11 @@
methods: { methods: {
after() { after() {
this.type = ""; this.type = "";
} },
remove() {
this.$emit('remove', this.extract);
},
}, },
computed: { computed: {

View File

@ -43,7 +43,6 @@
} }
}, },
common: { common: {
type: ExtractCommon,
default: () => { default: () => {
return new ExtractCommon(); return new ExtractCommon();
} }

View File

@ -43,7 +43,7 @@
components: {MsApiExtractCommon}, components: {MsApiExtractCommon},
props: { props: {
extract: Extract, extract: {},
isReadOnly: { isReadOnly: {
type: Boolean, type: Boolean,
default: false default: false

View File

@ -19,7 +19,7 @@
<!--query 参数--> <!--query 参数-->
<el-tab-pane :label="$t('api_test.definition.request.query_param')" name="parameters" :disabled="request.rest.length>1"> <el-tab-pane :label="$t('api_test.definition.request.query_param')" name="parameters" :disabled="request.rest.length>1">
<el-tooltip class="item-tabs" effect="dark" content="地址栏中跟在?后面的参数,如updateapi?id=112" placement="top-start" slot="label"> <el-tooltip class="item-tabs" effect="dark" :content="$t('api_test.definition.request.query_info')" placement="top-start" slot="label">
<span>{{$t('api_test.definition.request.query_param')}} <span>{{$t('api_test.definition.request.query_param')}}
<div class="el-step__icon is-text ms-api-col ms-query" v-if="request.arguments.length>1"> <div class="el-step__icon is-text ms-api-col ms-query" v-if="request.arguments.length>1">
<div class="el-step__icon-inner">{{request.arguments.length-1}}</div> <div class="el-step__icon-inner">{{request.arguments.length-1}}</div>
@ -31,7 +31,7 @@
<!--REST 参数--> <!--REST 参数-->
<el-tab-pane :label="$t('api_test.definition.request.rest_param')" name="rest" :disabled="request.arguments.length>1"> <el-tab-pane :label="$t('api_test.definition.request.rest_param')" name="rest" :disabled="request.arguments.length>1">
<el-tooltip class="item-tabs" effect="dark" content="地址栏中被斜杠/分隔的参数如updateapi/{id}" placement="top-start" slot="label"> <el-tooltip class="item-tabs" effect="dark" :content="$t('api_test.definition.request.rest_info')" placement="top-start" slot="label">
<span> <span>
{{$t('api_test.definition.request.rest_param')}} {{$t('api_test.definition.request.rest_param')}}
<div class="el-step__icon is-text ms-api-col ms-query" v-if="request.rest.length>1"> <div class="el-step__icon is-text ms-api-col ms-query" v-if="request.rest.length>1">
@ -49,7 +49,7 @@
<!-- 认证配置 --> <!-- 认证配置 -->
<el-tab-pane :label="$t('api_test.definition.request.auth_config')" name="authConfig"> <el-tab-pane :label="$t('api_test.definition.request.auth_config')" name="authConfig">
<el-tooltip class="item-tabs" effect="dark" content="请求需要进行权限校验" placement="top-start" slot="label"> <el-tooltip class="item-tabs" effect="dark" :content="$t('api_test.definition.request.auth_config_info')" placement="top-start" slot="label">
<span>{{$t('api_test.definition.request.auth_config')}}</span> <span>{{$t('api_test.definition.request.auth_config')}}</span>
</el-tooltip> </el-tooltip>
@ -58,31 +58,30 @@
</el-tabs> </el-tabs>
</div> </div>
<!--定制脚本-->
<div v-for="row in request.hashTree" :key="row.id" v-loading="isReloadData"> <div v-for="row in request.hashTree" :key="row.id" v-loading="isReloadData">
<ms-jsr233-processor v-if="row.label ==='JSR223 PreProcessor'" @remove="remove" :is-read-only="false" title="前置脚本" style-type="warning" <!-- 前置脚本 -->
<ms-jsr233-processor v-if="row.label ==='JSR223 PreProcessor'" @remove="remove" :is-read-only="false" :title="$t('api_test.definition.request.pre_script')" style-type="warning"
:jsr223-processor="row"/> :jsr223-processor="row"/>
<!--后置脚本-->
<ms-jsr233-processor v-if="row.label ==='JSR223 PostProcessor'" @remove="remove" :is-read-only="false" title="后置脚本" style-type="success" <ms-jsr233-processor v-if="row.label ==='JSR223 PostProcessor'" @remove="remove" :is-read-only="false" :title="$t('api_test.definition.request.post_script')" style-type="success"
:jsr223-processor="row"/> :jsr223-processor="row"/>
<!--断言规则-->
<ms-api-assertions v-if="row.type==='Assertions'" @remove="remove" :is-read-only="isReadOnly" :assertions="row"/>
<!--提取规则-->
<ms-api-extract :is-read-only="isReadOnly" @remove="remove" v-if="row.type==='Extract'" :extract="row"/>
<ms-api-assertions v-if="row.type==='Assertions'" :is-read-only="isReadOnly" :assertions="row"/>
</div> </div>
<!--
<ms-api-extract :is-read-only="isReadOnly" v-if="row.label==='JSONPostProcessor'" :extract="row"/>
-->
</el-col> </el-col>
<!--操作按钮-->
<el-col :span="3" class="ms-left-cell"> <el-col :span="3" class="ms-left-cell">
<el-button class="ms-left-buttion" size="small" type="warning" @click="addPre" plain>+前置脚本</el-button> <el-button class="ms-left-buttion" size="small" type="warning" @click="addPre" plain>+{{$t('api_test.definition.request.pre_script')}}</el-button>
<br/> <br/>
<el-button class="ms-left-buttion" size="small" type="success" @click="addPost" plain>+后置脚本</el-button> <el-button class="ms-left-buttion" size="small" type="success" @click="addPost" plain>+{{$t('api_test.definition.request.post_script')}}</el-button>
<br/> <br/>
<el-button class="ms-left-buttion" size="small" type="danger" @click="addAssertions" plain>+断言规则</el-button> <el-button class="ms-left-buttion" size="small" type="danger" @click="addAssertions" plain>+{{$t('api_test.definition.request.assertions_rule')}}</el-button>
<br/> <br/>
<el-button class="ms-left-buttion" size="small" type="info" @click="addExtract" plain>+提取参数</el-button> <el-button class="ms-left-buttion" size="small" type="info" @click="addExtract" plain>+{{$t('api_test.definition.request.extract_param')}}</el-button>
</el-col> </el-col>
</el-row> </el-row>
</template> </template>
@ -99,7 +98,7 @@
import {createComponent} from "../jmeter/components"; import {createComponent} from "../jmeter/components";
import MsApiAssertions from "../assertion/ApiAssertions"; import MsApiAssertions from "../assertion/ApiAssertions";
import MsApiExtract from "../extract/ApiExtract"; import MsApiExtract from "../extract/ApiExtract";
import {Assertions} from "../../model/ApiTestModel"; import {Assertions, Extract} from "../../model/ApiTestModel";
export default { export default {
name: "MsApiHttpRequestForm", name: "MsApiHttpRequestForm",
@ -143,17 +142,8 @@
}, },
headerSuggestions: REQUEST_HEADERS, headerSuggestions: REQUEST_HEADERS,
isReloadData: false, isReloadData: false,
assertions: [],
} }
}, },
created() {
this.request.hashTree.forEach(row => {
if (row.type === "Assertions") {
row = new Assertions({text: row.text, regex: row.regex, jsonPath: row.jsonPath, jsr223: row.jsr223, xpath2: row.xpath2, duration: row.duration});
}
})
console.log(this.assertions)
},
methods: { methods: {
addPre() { addPre() {
let jsr223PreProcessor = createComponent("JSR223PreProcessor"); let jsr223PreProcessor = createComponent("JSR223PreProcessor");
@ -166,13 +156,12 @@
this.reload(); this.reload();
}, },
addAssertions() { addAssertions() {
//let jsonPathAssertion = createComponent("JSONPathAssertion");
let assertions = new Assertions(); let assertions = new Assertions();
this.request.hashTree.push(assertions); this.request.hashTree.push(assertions);
this.reload(); this.reload();
}, },
addExtract() { addExtract() {
let jsonPostProcessor = createComponent("JSONPostProcessor"); let jsonPostProcessor = new Extract();
this.request.hashTree.push(jsonPostProcessor); this.request.hashTree.push(jsonPostProcessor);
this.reload(); this.reload();
}, },

View File

@ -16,6 +16,15 @@
<pre>{{response.responseResult.console}}</pre> <pre>{{response.responseResult.console}}</pre>
</el-tab-pane> </el-tab-pane>
<el-tab-pane :label="$t('api_report.assertions')" name="assertions" class="pane assertions">
<ms-assertion-results :assertions="response.responseResult.assertions"/>
</el-tab-pane>
<el-tab-pane :label="$t('api_test.request.extract.label')" name="label" class="pane">
<pre>{{response.responseResult.vars}}</pre>
</el-tab-pane>
<el-tab-pane v-if="activeName == 'body'" :disabled="true" name="mode" class="pane cookie"> <el-tab-pane v-if="activeName == 'body'" :disabled="true" name="mode" class="pane cookie">
<template v-slot:label> <template v-slot:label>
<ms-dropdown :commands="modes" :default-command="mode" @command="modeChange"/> <ms-dropdown :commands="modes" :default-command="mode" @command="modeChange"/>

View File

@ -901,6 +901,7 @@ export class Duration extends AssertionType {
export class Extract extends BaseConfig { export class Extract extends BaseConfig {
constructor(options) { constructor(options) {
super(); super();
this.type = "Extract";
this.regex = []; this.regex = [];
this.json = []; this.json = [];
this.xpath = []; this.xpath = [];

@ -1 +1 @@
Subproject commit eb237fb6bfeba8d99e4db52450ae92f3cdd4ea33 Subproject commit 33bbdb3f528c914bf333b2c1839dd6d3bbd9b569

View File

@ -518,6 +518,13 @@ export default {
response_body: "Response body", response_body: "Response body",
console: "Console", console: "Console",
status_code: "Status code", status_code: "Status code",
query_info: "Follow the address bar? The following parameters, such as updateapi?id=112",
rest_info: "Slash/separated parameters in the address bar, such as updateapi/{id}",
auth_config_info: "Request requires permission verification",
pre_script: "Prescript",
post_script: "Postscript",
extract_param: "Extract parameters",
} }
}, },
environment: { environment: {

View File

@ -519,7 +519,12 @@ export default {
response_body: "响应体", response_body: "响应体",
console: "控制台", console: "控制台",
status_code: "状态码", status_code: "状态码",
query_info: "地址栏中跟在?后面的参数,如updateapi?id=112",
rest_info: "地址栏中被斜杠/分隔的参数如updateapi/{id}",
auth_config_info: "请求需要进行权限校验",
pre_script: "前置脚本",
post_script: "后置脚本",
extract_param:"提取参数",
} }
}, },
environment: { environment: {

View File

@ -519,6 +519,12 @@ export default {
response_body: "響應體", response_body: "響應體",
console: "控制臺", console: "控制臺",
status_code: "狀態碼", status_code: "狀態碼",
query_info: "地址欄中跟在?後面的參數,如updateapi?id=112",
rest_info: "地址欄中被斜杠/分隔的參數如updateapi/{id}",
auth_config_info: "請求需要進行權限校驗",
pre_script: "前置腳本",
post_script: "後置腳本",
extract_param: "提取參數",
} }
}, },