api测试环境配置

This commit is contained in:
chenjianxing 2020-07-06 11:53:36 +08:00
parent 4edf7c2a4c
commit 785705e69e
11 changed files with 264 additions and 64 deletions

View File

@ -11,6 +11,8 @@ public class Request {
private String name;
private String url;
private String method;
private Boolean useEnvironment;
private String path;
private List<KeyValue> parameters;
private List<KeyValue> headers;
private Body body;

View File

@ -8,6 +8,7 @@ import java.util.List;
public class Scenario {
private String name;
private String url;
private String environmentId;
private List<KeyValue> variables;
private List<KeyValue> headers;
private List<Request> requests;

View File

@ -50,7 +50,7 @@
<ms-schedule-config :schedule="test.schedule" :save="saveCronExpression" @scheduleChange="saveSchedule" :check-open="checkScheduleEdit"/>
</el-row>
</el-header>
<ms-api-scenario-config :is-read-only="isReadOnly" :scenarios="test.scenarioDefinition" ref="config"/>
<ms-api-scenario-config :is-read-only="isReadOnly" :scenarios="test.scenarioDefinition" :project-id="test.projectId" ref="config"/>
</el-container>
</el-card>
</div>

View File

@ -1,5 +1,5 @@
<template>
<el-dialog :title="'环境配置'" :visible.sync="visible" class="environment-dialog">
<el-dialog :title="'环境配置'" :visible.sync="visible" class="environment-dialog" @close="close">
<el-container v-loading="result.loading">
<ms-aside-item :title="'环境列表'" :data="environments" :item-operators="environmentOperators" :add-fuc="addEnvironment"
:delete-fuc="deleteEnvironment" @itemSelected="environmentSelected" ref="environmentItems"/>
@ -93,6 +93,9 @@
},
getDefaultEnvironment() {
return {variables: [{}], headers: [{}], protocol: 'https', projectId: this.projectId};
},
close() {
this.$emit('close');
}
}
}

View File

@ -1,7 +1,7 @@
<template>
<div class="request-container">
<draggable :list="requests" group="Request" class="request-draggable" ghost-class="request-ghost">
<div class="request-item" v-for="(request, index) in requests" :key="index" @click="select(request)"
<draggable :list="this.scenario.requests" group="Request" class="request-draggable" ghost-class="request-ghost">
<div class="request-item" v-for="(request, index) in this.scenario.requests" :key="index" @click="select(request)"
:class="{'selected': isSelected(request)}">
<el-row type="flex">
<div class="request-method">
@ -40,7 +40,7 @@
components: {draggable},
props: {
requests: Array,
scenario: Object,
open: Function,
isReadOnly: {
type: Boolean,
@ -65,15 +65,15 @@
methods: {
createRequest: function () {
let request = new Request();
this.requests.push(request);
this.scenario.requests.push(request);
},
copyRequest: function (index) {
let request = this.requests[index];
this.requests.push(new Request(request));
let request = this.scenario.requests[index];
this.scenario.requests.push(new Request(request));
},
deleteRequest: function (index) {
this.requests.splice(index, 1);
if (this.requests.length === 0) {
this.scenario.requests.splice(index, 1);
if (this.scenario.requests.length === 0) {
this.createRequest();
}
},
@ -88,13 +88,17 @@
}
},
select: function (request) {
request.environment = this.scenario.environment;
if (!request.useEnvironment) {
request.useEnvironment = false;
}
this.selected = request;
this.open(request);
}
},
created() {
this.select(this.requests[0]);
this.select(this.scenario.requests[0]);
}
}
</script>

View File

@ -1,30 +1,47 @@
<template>
<el-form :model="request" :rules="rules" ref="request" label-width="100px">
<el-form-item :label="$t('api_test.request.name')" prop="name">
<el-input :disabled="isReadOnly" v-model="request.name" maxlength="100" show-word-limit/>
</el-form-item>
<el-form-item :label="$t('api_test.request.url')" prop="url">
<el-form-item v-if="!request.useEnvironment" :label="$t('api_test.request.url')" prop="url" class="adjust-margin-bottom">
<el-input :disabled="isReadOnly" v-model="request.url" maxlength="500"
:placeholder="$t('api_test.request.url_description')" @change="urlChange" clearable>
<el-select :disabled="isReadOnly" v-model="request.method" slot="prepend" class="request-method-select"
@change="methodChange">
<el-option label="GET" value="GET"/>
<el-option label="POST" value="POST"/>
<el-option label="PUT" value="PUT"/>
<el-option label="PATCH" value="PATCH"/>
<el-option label="DELETE" value="DELETE"/>
<el-option label="OPTIONS" value="OPTIONS"/>
<el-option label="HEAD" value="HEAD"/>
<el-option label="CONNECT" value="CONNECT"/>
</el-select>
<template v-slot:prepend>
<ApiRequestMethodSelect :is-read-only="isReadOnly" :request="request" @change="methodChange"/>
</template>
</el-input>
</el-form-item>
<el-form-item v-if="request.useEnvironment" :label="'请求路径'" prop="path">
<el-input :disabled="isReadOnly" v-model="request.path" maxlength="500"
:placeholder="'例如:/login'" @change="pathChange" clearable>
<template v-slot:prepend>
<ApiRequestMethodSelect :is-read-only="isReadOnly" :request="request" @change="methodChange"/>
</template>
</el-input>
</el-form-item>
<el-form-item v-if="request.useEnvironment" :label="'请求地址'" class="adjust-margin-bottom">
<el-tag class="environment-display">
<span class="environment-name">{{request.environment ? request.environment.name + ': ' : ''}}</span>
<span class="environment-url">{{displayUrl}}</span>
<span v-if="!displayUrl" class="environment-url-tip">请在场景中配置环境</span>
</el-tag>
</el-form-item>
<el-form-item>
<el-switch
v-model="request.useEnvironment"
active-text="引用环境" @change="useEnvironmentChange">
</el-switch>
</el-form-item>
<el-tabs v-model="activeName">
<el-tab-pane :label="$t('api_test.request.parameters')" name="parameters">
<ms-api-key-value :is-read-only="isReadOnly" :items="request.parameters"
:description="$t('api_test.request.parameters_desc')" @change="parametersChange"/>
:description="$t('api_test.request.parameters_desc')"/>
</el-tab-pane>
<el-tab-pane :label="$t('api_test.request.headers')" name="headers">
<ms-api-key-value :is-read-only="isReadOnly" :items="request.headers"/>
@ -48,10 +65,11 @@
import MsApiAssertions from "./assertion/ApiAssertions";
import {KeyValue, Request} from "../model/ScenarioModel";
import MsApiExtract from "./extract/ApiExtract";
import ApiRequestMethodSelect from "./collapse/ApiRequestMethodSelect";
export default {
name: "MsApiRequestForm",
components: {MsApiExtract, MsApiAssertions, MsApiBody, MsApiKeyValue},
components: {ApiRequestMethodSelect, MsApiExtract, MsApiAssertions, MsApiBody, MsApiKeyValue},
props: {
request: Request,
isReadOnly: {
@ -77,6 +95,9 @@
url: [
{max: 500, required: true, message: this.$t('commons.input_limit', [0, 500]), trigger: 'blur'},
{validator: validateURL, trigger: 'blur'}
],
path: [
{max: 500, required: true, message: this.$t('commons.input_limit', [0, 500]), trigger: 'blur'},
]
}
}
@ -85,39 +106,43 @@
methods: {
urlChange() {
if (!this.request.url) return;
let parameters = [];
let url = this.getURL(this.addProtocol(this.request.url));
if (url) {
this.request.url = decodeURIComponent(url.origin + url.pathname);
}
},
pathChange() {
if (!this.request.path) return;
if (!this.request.path.startsWith('/')) {
this.request.path = '/' + this.request.path;
}
let url = this.getURL(this.displayUrl);
this.request.path = decodeURIComponent(url.pathname);
this.request.urlWirhEnv = decodeURIComponent(url.origin + url.pathname);
},
getURL(urlStr) {
try {
let url = new URL(this.addProtocol(this.request.url));
let url = new URL(urlStr);
url.searchParams.forEach((value, key) => {
if (key && value) {
parameters.push(new KeyValue(key, value));
this.request.parameters.splice(0, 0, new KeyValue(key, value));
}
});
//
parameters.push(new KeyValue());
this.request.parameters = parameters;
this.request.url = this.getURL(url);
return url;
} catch (e) {
this.$error(this.$t('api_test.request.url_invalid'), 2000)
this.$error(this.$t('api_test.request.url_invalid'), 2000);
}
},
methodChange(value) {
if (value === 'GET' && this.activeName === 'body') {
this.activeName = 'parameters';
}
},
parametersChange(parameters) {
if (!this.request.url) return;
let url = new URL(this.addProtocol(this.request.url));
url.search = "";
parameters.forEach(function (parameter) {
if (parameter.name && parameter.value) {
url.searchParams.append(parameter.name, parameter.value);
}
})
this.request.url = this.getURL(url);
useEnvironmentChange(value) {
if (value && !this.request.environment) {
this.$error('请先在场景中添加环境配置!' , 2000);
this.request.useEnvironment = false;
}
},
addProtocol(url) {
if (url) {
@ -126,15 +151,15 @@
}
}
return url;
},
getURL(url) {
return decodeURIComponent(url.origin + url.pathname) + "?" + url.searchParams.toString();
}
},
computed: {
isNotGet() {
return this.request.method !== "GET";
},
displayUrl() {
return this.request.environment ? this.request.environment.protocol + '://' + this.request.environment.socket + (this.request.path ? this.request.path : '') : '';
}
}
}
@ -144,4 +169,28 @@
.request-method-select {
width: 110px;
}
.el-tag {
width: 100%;
height: 40px;
line-height: 40px;
}
.environment-display {
font-size: 14px;
}
.environment-name {
font-weight: bold;
font-style: italic;
}
.adjust-margin-bottom {
margin-bottom: 10px;
}
.environment-url-tip {
color: #F56C6C;
}
</style>

View File

@ -25,7 +25,7 @@
</el-dropdown-menu>
</el-dropdown>
</template>
<ms-api-request-config :is-read-only="isReadOnly" :requests="scenario.requests" :open="select"/>
<ms-api-request-config :is-read-only="isReadOnly" :scenario="scenario" :open="select"/>
</ms-api-collapse-item>
</draggable>
</ms-api-collapse>
@ -35,8 +35,8 @@
<el-main class="scenario-main">
<div class="scenario-form">
<ms-api-scenario-form :is-read-only="isReadOnly" :scenario="selected" v-if="isScenario"/>
<ms-api-request-form :is-read-only="isReadOnly" :request="selected" v-if="isRequest"/>
<ms-api-scenario-form :is-read-only="isReadOnly" :scenario="selected" :project-id="projectId" v-if="isScenario"/>
<ms-api-request-form :is-read-only="isReadOnly" :request="selected" :project-id="projectId" v-if="isRequest"/>
</div>
</el-main>
</el-container>
@ -66,6 +66,7 @@
props: {
scenarios: Array,
projectId: String,
isReadOnly: {
type: Boolean,
default: false

View File

@ -1,12 +1,19 @@
<template>
<el-form :model="scenario" :rules="rules" ref="scenario" label-width="100px">
<el-form :model="scenario" :rules="rules" ref="scenario" label-width="100px" v-loading="result.loading">
<el-form-item :label="$t('api_test.scenario.name')" prop="name">
<el-input :disabled="isReadOnly" v-model="scenario.name" maxlength="100" show-word-limit/>
</el-form-item>
<!-- <el-form-item :label="$t('api_test.scenario.base_url')" prop="url">-->
<!-- <el-input :placeholder="$t('api_test.scenario.base_url_description')" v-model="scenario.url" maxlength="100"/>-->
<!-- </el-form-item>-->
<el-form-item :label="'环境'">
<el-select :disabled="isReadOnly" v-model="scenario.environmentId" class="environment-select" @change="environmentChange" clearable>
<el-option v-for="(environment, index) in environments" :key="index" :label="environment.name + ': ' + environment.protocol + '://' + environment.socket" :value="environment.id"/>
<el-button class="environment-button" size="mini" type="primary" @click="openEnvironmentConfig">环境配置</el-button>
</el-select>
</el-form-item>
<!-- <el-form-item :label="$t('api_test.scenario.base_url')" prop="url">-->
<!-- <el-input :placeholder="$t('api_test.scenario.base_url_description')" v-model="scenario.url" maxlength="200"/>-->
<!-- </el-form-item>-->
<el-tabs v-model="activeName">
<el-tab-pane :label="$t('api_test.scenario.variables')" name="parameters">
@ -16,28 +23,38 @@
<ms-api-key-value :is-read-only="isReadOnly" :items="scenario.headers" :description="$t('api_test.scenario.kv_description')"/>
</el-tab-pane>
</el-tabs>
<api-environment-config ref="environmentConfig" @close="environmentConfigClose"/>
</el-form>
</template>
<script>
import MsApiKeyValue from "./ApiKeyValue";
import {Scenario} from "../model/ScenarioModel";
import MsApiScenarioVariables from "./ApiScenarioVariables";
import ApiEnvironmentConfig from "./ApiEnvironmentConfig";
export default {
name: "MsApiScenarioForm",
components: {MsApiScenarioVariables, MsApiKeyValue},
components: {ApiEnvironmentConfig, MsApiScenarioVariables, MsApiKeyValue},
props: {
scenario: Scenario,
projectId: String,
isReadOnly: {
type: Boolean,
default: false
}
},
created() {
this.getEnvironments();
},
data() {
return {
result: {},
activeName: "parameters",
environments: [],
rules: {
name: [
{max: 100, message: this.$t('commons.input_limit', [0, 100]), trigger: 'blur'}
@ -47,10 +64,57 @@
]
}
}
},
methods: {
getEnvironments() {
if (this.projectId) {
this.result = this.$get('/api/environment/list/' + this.projectId, response => {
this.environments = response.data;
for (let i in this.environments) {
if (this.environments[i].id === this.scenario.environmentId) {
this.scenario.environment = this.environments[i];
break;
}
}
});
}
},
environmentChange(value) {
for (let i in this.environments) {
if (this.environments[i].id === value) {
this.scenario.environment = this.environments[i];
this.scenario.requests.forEach(request => {
request.environment = this.environments[i];
});
break;
}
}
if (!value) {
this.scenario.environment = undefined;
this.scenario.requests.forEach(request => {
request.environment = undefined;
});
}
},
openEnvironmentConfig() {
this.$refs.environmentConfig.open(this.projectId);
},
environmentConfigClose() {
this.getEnvironments();
}
}
}
</script>
<style scoped>
.environment-select {
width: 100%;
}
.environment-button {
margin-left: 20px;
padding: 7px;
}
</style>

View File

@ -0,0 +1,28 @@
<template>
<el-select :disabled="isReadOnly" v-model="request.method" class="request-method-select" @change="change">
<el-option label="GET" value="GET"/>
<el-option label="POST" value="POST"/>
<el-option label="PUT" value="PUT"/>
<el-option label="PATCH" value="PATCH"/>
<el-option label="DELETE" value="DELETE"/>
<el-option label="OPTIONS" value="OPTIONS"/>
<el-option label="HEAD" value="HEAD"/>
<el-option label="CONNECT" value="CONNECT"/>
</el-select>
</template>
<script>
export default {
name: "ApiRequestMethodSelect",
props: ['isReadOnly', 'request'],
methods: {
change(value) {
this.$emit('change', value);
}
}
}
</script>
<style scoped>
</style>

View File

@ -225,9 +225,15 @@ export class HTTPSamplerProxy extends DefaultTestElement {
super('HTTPSamplerProxy', 'HttpTestSampleGui', 'HTTPSamplerProxy', testName);
this.request = request || {};
this.stringProp("HTTPSampler.domain", this.request.hostname);
this.stringProp("HTTPSampler.protocol", this.request.protocol.split(":")[0]);
this.stringProp("HTTPSampler.path", this.request.pathname);
if (request.useEnvironment) {
this.stringProp("HTTPSampler.domain", this.request.environment.domain);
this.stringProp("HTTPSampler.protocol", this.request.environment.protocol);
this.stringProp("HTTPSampler.path", this.request.path);
} else {
this.stringProp("HTTPSampler.domain", this.request.hostname);
this.stringProp("HTTPSampler.protocol", this.request.protocol.split(":")[0]);
this.stringProp("HTTPSampler.path", this.request.pathname);
}
this.stringProp("HTTPSampler.method", this.request.method);
this.stringProp("HTTPSampler.contentEncoding", this.request.encoding, "UTF-8");
if (!this.request.port) {

View File

@ -147,6 +147,7 @@ export class Scenario extends BaseConfig {
this.variables = [];
this.headers = [];
this.requests = [];
this.environmentId = undefined;
this.set(options);
this.sets({variables: KeyValue, headers: KeyValue, requests: Request}, options);
@ -164,11 +165,14 @@ export class Scenario extends BaseConfig {
isValid() {
for (let i = 0; i < this.requests.length; i++) {
if (this.requests[i].isValid()) {
return true;
if (!this.requests[i].isValid()) {
return false;
}
}
return false;
if (!this.name) {
return false;
}
return true;
}
}
@ -177,12 +181,15 @@ export class Request extends BaseConfig {
super();
this.name = undefined;
this.url = undefined;
this.path = undefined;
this.method = undefined;
this.parameters = [];
this.headers = [];
this.body = undefined;
this.assertions = undefined;
this.extract = undefined;
this.environment = undefined;
this.useEnvironment = undefined;
this.set(options);
this.sets({parameters: KeyValue, headers: KeyValue}, options);
@ -198,7 +205,7 @@ export class Request extends BaseConfig {
}
isValid() {
return !!this.url && !!this.method
return ((!this.useEnvironment && !!this.url) || (this.useEnvironment && !!this.path && this.environment)) && !!this.method
}
}
@ -392,6 +399,9 @@ class JMXRequest {
this.method = request.method;
this.hostname = decodeURIComponent(url.hostname);
this.pathname = decodeURIComponent(url.pathname);
this.path = decodeURIComponent(request.path);
this.useEnvironment = request.useEnvironment;
this.environment = request.environment;
this.port = url.port;
this.protocol = url.protocol.split(":")[0];
if (this.method.toUpperCase() !== "GET") {
@ -463,6 +473,22 @@ class JMXGenerator {
}
addScenarioVariables(threadGroup, scenario) {
let scenarioVariableKeys = new Set();
scenario.variables.forEach(item => {
scenarioVariableKeys.add(item.name);
});
let environment = scenario.environment;
if (environment) {
let envVariables = environment.variables;
if (!(envVariables instanceof Array)) {
envVariables = JSON.parse(environment.variables);
envVariables.forEach(item => {
if (item.name && !scenarioVariableKeys.has(item.name)) {
scenario.variables.push(new KeyValue(item.name, item.value));
}
})
}
}
let args = this.filterKV(scenario.variables);
if (args.length > 0) {
let name = scenario.name + " Variables"
@ -471,6 +497,22 @@ class JMXGenerator {
}
addScenarioHeaders(threadGroup, scenario) {
let scenarioHeaderKeys = new Set();
scenario.headers.forEach(item => {
scenarioHeaderKeys.add(item.name);
});
let environment = scenario.environment;
if (environment) {
let envHeaders = environment.headers;
if (!(envHeaders instanceof Array)) {
envHeaders = JSON.parse(environment.headers);
envHeaders.forEach(item => {
if (item.name && !scenarioHeaderKeys.has(item.name)) {
scenario.headers.push(new KeyValue(item.name, item.value));
}
})
}
}
let headers = this.filterKV(scenario.headers);
if (headers.length > 0) {
let name = scenario.name + " Headers"