feat(接口自动化): 循环控制器

This commit is contained in:
fit2-zhao 2021-01-06 18:26:52 +08:00
parent 3789365746
commit f096e1e52b
13 changed files with 460 additions and 45 deletions

View File

@ -11,6 +11,7 @@ import io.metersphere.api.dto.definition.request.assertions.MsAssertions;
import io.metersphere.api.dto.definition.request.auth.MsAuthManager;
import io.metersphere.api.dto.definition.request.configurations.MsHeaderManager;
import io.metersphere.api.dto.definition.request.controller.MsIfController;
import io.metersphere.api.dto.definition.request.controller.MsLoopController;
import io.metersphere.api.dto.definition.request.extract.MsExtract;
import io.metersphere.api.dto.definition.request.processors.MsJSR223Processor;
import io.metersphere.api.dto.definition.request.processors.post.MsJSR223PostProcessor;
@ -56,11 +57,12 @@ import java.util.List;
@JsonSubTypes.Type(value = MsConstantTimer.class, name = "ConstantTimer"),
@JsonSubTypes.Type(value = MsIfController.class, name = "IfController"),
@JsonSubTypes.Type(value = MsScenario.class, name = "scenario"),
@JsonSubTypes.Type(value = MsLoopController.class, name = "LoopController"),
})
@JSONType(seeAlso = {MsHTTPSamplerProxy.class, MsHeaderManager.class, MsJSR223Processor.class, MsJSR223PostProcessor.class,
MsJSR223PreProcessor.class, MsTestPlan.class, MsThreadGroup.class, AuthManager.class, MsAssertions.class,
MsExtract.class, MsTCPSampler.class, MsDubboSampler.class, MsJDBCSampler.class, MsConstantTimer.class, MsIfController.class, MsScenario.class}, typeKey = "type")
MsExtract.class, MsTCPSampler.class, MsDubboSampler.class, MsJDBCSampler.class, MsConstantTimer.class, MsIfController.class, MsScenario.class, MsLoopController.class}, typeKey = "type")
@Data
public abstract class MsTestElement {
private String type;

View File

@ -0,0 +1,126 @@
package io.metersphere.api.dto.definition.request.controller;
import com.alibaba.fastjson.annotation.JSONField;
import com.alibaba.fastjson.annotation.JSONType;
import io.metersphere.api.dto.definition.request.MsTestElement;
import io.metersphere.api.dto.definition.request.ParameterConfig;
import io.metersphere.api.dto.definition.request.controller.loop.CountController;
import io.metersphere.api.dto.definition.request.controller.loop.MsForEachController;
import io.metersphere.api.dto.definition.request.controller.loop.MsWhileController;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.jmeter.control.ForeachController;
import org.apache.jmeter.control.GenericController;
import org.apache.jmeter.control.LoopController;
import org.apache.jmeter.control.WhileController;
import org.apache.jmeter.save.SaveService;
import org.apache.jmeter.testelement.TestElement;
import org.apache.jorphan.collections.HashTree;
import java.util.List;
@Data
@EqualsAndHashCode(callSuper = true)
@JSONType(typeName = "LoopController")
public class MsLoopController extends MsTestElement {
private String type = "LoopController";
@JSONField(ordinal = 20)
private String loopType;
@JSONField(ordinal = 21)
private CountController countController;
@JSONField(ordinal = 22)
private MsForEachController forEachController;
@JSONField(ordinal = 23)
private MsWhileController whileController;
public void toHashTree(HashTree tree, List<MsTestElement> hashTree, ParameterConfig config) {
if (!this.isEnable()) {
return;
}
GenericController controller = controller();
if (controller == null)
return;
final HashTree groupTree = tree.add(controller);
if (CollectionUtils.isNotEmpty(hashTree)) {
hashTree.forEach(el -> {
el.toHashTree(groupTree, el.getHashTree(), config);
});
}
}
private LoopController loopController() {
LoopController loopController = new LoopController();
loopController.setEnabled(true);
loopController.setName(this.getLabel());
loopController.setProperty(TestElement.TEST_CLASS, LoopController.class.getName());
loopController.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("LoopControlPanel"));
loopController.setContinueForever(countController.isProceed());
loopController.setLoops(countController.getLoops());
return loopController;
}
public String getCondition() {
String variable = "\"" + this.whileController.getVariable() + "\"";
String operator = this.whileController.getOperator();
String value = "\"" + this.whileController.getValue() + "\"";
if (StringUtils.contains(operator, "~")) {
value = "\".*" + this.whileController.getValue() + ".*\"";
}
if (StringUtils.equals(operator, "is empty")) {
variable = "empty(" + variable + ")";
operator = "";
value = "";
}
if (StringUtils.equals(operator, "is not empty")) {
variable = "!empty(" + variable + ")";
operator = "";
value = "";
}
return "${__jexl3(" + variable + operator + value + ")}";
}
private WhileController whileController() {
WhileController controller = new WhileController();
controller.setEnabled(true);
controller.setName(this.getLabel());
controller.setProperty(TestElement.TEST_CLASS, WhileController.class.getName());
controller.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("WhileControllerGui"));
controller.setCondition(getCondition());
return controller;
}
private ForeachController foreachController() {
ForeachController controller = new ForeachController();
controller.setEnabled(true);
controller.setName(this.getLabel());
controller.setProperty(TestElement.TEST_CLASS, ForeachController.class.getName());
controller.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("ForeachControlPanel"));
controller.setInputVal(this.forEachController.getInputVal());
controller.setReturnVal(this.forEachController.getReturnVal());
controller.setUseSeparator(true);
return controller;
}
private GenericController controller() {
if (StringUtils.equals(this.loopType, "WHILE") && this.whileController != null) {
return whileController();
}
if (StringUtils.equals(this.loopType, "FOREACH") && this.forEachController != null) {
return foreachController();
}
if (StringUtils.equals(this.loopType, "LOOP_COUNT") && this.countController != null) {
return loopController();
}
return null;
}
}

View File

@ -0,0 +1,10 @@
package io.metersphere.api.dto.definition.request.controller.loop;
import lombok.Data;
@Data
public class CountController {
private int loops;
private int interval;
private boolean proceed;
}

View File

@ -0,0 +1,10 @@
package io.metersphere.api.dto.definition.request.controller.loop;
import lombok.Data;
@Data
public class MsForEachController {
private String inputVal;
private String returnVal;
private String interval;
}

View File

@ -0,0 +1,11 @@
package io.metersphere.api.dto.definition.request.controller.loop;
import lombok.Data;
@Data
public class MsWhileController {
private String variable;
private String operator;
private String value;
private int timeout;
}

View File

@ -110,6 +110,10 @@ public class HistoricalDataUpgradeService {
BeanUtils.copyBean(element, request1);
((MsHTTPSamplerProxy) element).setProtocol(RequestType.HTTP);
((MsHTTPSamplerProxy) element).setArguments(request1.getParameters());
if (StringUtils.isEmpty(element.getName())) {
element.setName(request1.getPath());
}
element.setType("HTTPSamplerProxy");
}
if (request instanceof DubboRequest) {

View File

@ -152,6 +152,8 @@
<ms-api-scenario-component v-if="data.type==='scenario'" :scenario="data" :node="node" @remove="remove" @copyRow="copyRow"/>
<!--条件控制器-->
<ms-if-controller :controller="data" :node="node" v-if="data.type==='IfController'" @remove="remove" @copyRow="copyRow"/>
<!--循环控制器-->
<ms-loop-controller :controller="data" :node="node" v-if="data.type==='LoopController'" @remove="remove" @copyRow="copyRow"/>
<!--等待控制器-->
<ms-constant-timer :timer="data" :node="node" v-if="data.type==='ConstantTimer'" @remove="remove" @copyRow="copyRow"/>
<!--自定义脚本-->
@ -168,7 +170,8 @@
<!--提取规则-->
<ms-api-extract @remove="remove" @copyRow="copyRow" v-if="data.type==='Extract'" customizeStyle="margin-top: 0px" :extract="data" :node="node"/>
<!--API 导入 -->
<ms-api-component :request="data" :currentScenario="currentScenario" :currentEnvironmentId="currentEnvironmentId" @remove="remove" @copyRow="copyRow" v-if="data.type==='HTTPSamplerProxy'||data.type==='DubboSampler'||data.type==='JDBCSampler'||data.type==='TCPSampler'" :node="node"/>
<ms-api-component :request="data" :currentScenario="currentScenario" :currentEnvironmentId="currentEnvironmentId" @remove="remove" @copyRow="copyRow"
v-if="data.type==='HTTPSamplerProxy'||data.type==='DubboSampler'||data.type==='JDBCSampler'||data.type==='TCPSampler'" :node="node"/>
</template>
</span>
</el-tree>
@ -227,7 +230,7 @@
<script>
import {API_STATUS, PRIORITY} from "../../definition/model/JsonData";
import {WORKSPACE_ID} from '@/common/js/constants';
import {Assertions, Extract, IfController, JSR223Processor, ConstantTimer} from "../../definition/model/ApiTestModel";
import {Assertions, Extract, IfController, JSR223Processor, ConstantTimer, LoopController} from "../../definition/model/ApiTestModel";
import MsJsr233Processor from "./Jsr233Processor";
import {parseEnvironment} from "../../definition/model/EnvironmentModel";
import MsConstantTimer from "./ConstantTimer";
@ -241,7 +244,7 @@
import ApiEnvironmentConfig from "../../definition/components/environment/ApiEnvironmentConfig";
import MsInputTag from "./MsInputTag";
import MsRun from "./DebugRun";
import MsImportApiScenario from "./ImportApiScenario";
import MsLoopController from "./LoopController";
import MsApiScenarioComponent from "./ApiScenarioComponent";
import MsApiReportDetail from "../report/ApiReportDetail";
import MsScenarioParameters from "./ScenarioParameters";
@ -275,6 +278,7 @@
MsApiCustomize,
ApiImport,
InputTag,
MsLoopController,
},
data() {
return {
@ -388,13 +392,13 @@
}
},
{
title: this.$t('api_test.automation.if_controller'),
title: this.$t('api_test.automation.loop_controller'),
show: this.showButton("LoopController"),
titleColor: "#02A7F0",
titleBgColor: "#F4F4F5",
icon: "alt_route",
icon: "next_plan",
click: () => {
this.addComponent('IfController')
this.addComponent('LoopController')
}
},
{
@ -504,8 +508,11 @@
this.customizeRequest = {protocol: "HTTP", type: "API", hashTree: [], referenced: 'Created', active: false};
this.customizeVisible = true;
break;
case ELEMENT_TYPE.LoopController:
this.selectedTreeNode != undefined ? this.selectedTreeNode.hashTree.push(new LoopController()) :
this.scenarioDefinition.push(new LoopController());
break;
case ELEMENT_TYPE.scenario:
// this.scenarioVisible = true;
this.$refs.scenarioRelevance.open();
break;
default:
@ -623,8 +630,7 @@
this.$post('/user/ws/member/tester/list', {workspaceId: workspaceId}, response => {
this.maintainerOptions = response.data;
});
}
,
},
openTagConfig() {
if (!this.projectId) {
this.$error(this.$t('api_test.select_project'));
@ -633,7 +639,8 @@
this.$refs.tag.open();
},
remove(row, node) {
this.$alert(this.$t('api_test.definition.request.delete_confirm') + ' ' + row.name + " ", '', {
let name = row.name === undefined ? "" : row.name;
this.$alert(this.$t('api_test.definition.request.delete_confirm_step') + ' ' + name + " ", '', {
confirmButtonText: this.$t('commons.confirm'),
callback: (action) => {
if (action === 'confirm') {
@ -646,8 +653,7 @@
}
}
});
}
,
},
copyRow(row, node) {
const parent = node.parent
const hashTree = parent.data.hashTree || parent.data;
@ -664,8 +670,7 @@
this.$nextTick(() => {
this.loading = false
})
}
,
},
runDebug() {
/*触发执行操作*/
if (!this.currentEnvironmentId) {
@ -678,8 +683,7 @@
environmentId: this.currentEnvironmentId, hashTree: this.scenarioDefinition
};
this.reportId = getUUID().substring(0, 8);
}
,
},
getEnvironments() {
if (this.projectId) {
this.$get('/api/environment/list/' + this.projectId, response => {
@ -699,16 +703,14 @@
}
});
}
}
,
},
openEnvironmentConfig() {
if (!this.projectId) {
this.$error(this.$t('api_test.select_project'));
return;
}
this.$refs.environmentConfig.open(this.projectId);
}
,
},
environmentConfigClose() {
this.getEnvironments();
}
@ -722,25 +724,21 @@
return true;
}
return false;
}
,
},
allowDrag(draggingNode, dropNode, dropType) {
this.sort();
this.reload();
}
,
},
nodeExpand(data) {
if (data.resourceId) {
this.expandedNode.push(data.resourceId);
}
}
,
},
nodeCollapse(data) {
if (data.resourceId) {
this.expandedNode.splice(this.expandedNode.indexOf(data.resourceId), 1);
}
}
,
},
getPath(id) {
if (id === null) {
return null;
@ -749,8 +747,7 @@
return item.id === id ? item.path : "";
});
return path[0].path;
}
,
},
setFiles(item, bodyUploadFiles, obj) {
if (item.body) {
if (item.body.kvs) {
@ -788,8 +785,7 @@
});
}
}
}
,
},
recursiveFile(arr, bodyUploadFiles, obj) {
arr.forEach(item => {
this.setFiles(item, bodyUploadFiles, obj);
@ -797,8 +793,7 @@
this.recursiveFile(item.hashTree, bodyUploadFiles, obj);
}
});
}
,
},
getBodyUploadFiles(obj) {
let bodyUploadFiles = [];
obj.bodyUploadIds = [];
@ -809,8 +804,7 @@
}
})
return bodyUploadFiles;
}
,
},
editScenario() {
this.$refs['currentScenario'].validate((valid) => {
if (valid) {

View File

@ -0,0 +1,189 @@
<template>
<el-card v-loading="loading">
<el-row>
<div class="el-step__icon is-text ms-api-col">
<div class="el-step__icon-inner">{{controller.index}}</div>
</div>
<el-button class="ms-title-buttion" size="small">{{$t('api_test.automation.loop_controller')}}</el-button>
<el-radio @change="changeRadio" class="ms-radio" v-model="controller.loopType" label="LOOP_COUNT">{{$t('loop.loops_title')}}</el-radio>
<el-radio @change="changeRadio" class="ms-radio" v-model="controller.loopType" label="FOREACH">{{$t('loop.foreach')}}</el-radio>
<el-radio @change="changeRadio" class="ms-radio" v-model="controller.loopType" label="WHILE">{{$t('loop.while')}}</el-radio>
<div style="margin-right: 20px; float: right">
<i class="icon el-icon-arrow-right" :class="{'is-active': controller.active}"
@click="active(controller)"/>
<el-switch v-model="controller.enable" style="margin-left: 10px"/>
<el-button size="mini" icon="el-icon-copy-document" circle @click="copyRow" style="margin-left: 10px"/>
<el-button size="mini" icon="el-icon-delete" type="danger" circle @click="remove" style="margin-left: 10px"/>
</div>
</el-row>
<el-collapse-transition>
<div v-if="controller.active" style="margin-top: 20px;">
<div v-if="controller.loopType==='LOOP_COUNT'">
<el-row>
<el-col :span="8">
<span class="ms-span ms-radio">{{$t('loop.loops')}}</span>
<el-input-number size="small" v-model="controller.countController.loops" :placeholder="$t('commons.millisecond')" :max="1000*10000000" :min="0"/>
<span class="ms-span ms-radio"></span>
</el-col>
<el-col :span="8">
<span class="ms-span ms-radio">{{$t('loop.interval')}}</span>
<el-input-number size="small" v-model="controller.countController.interval" :placeholder="$t('commons.millisecond')" :max="1000*10000000" :min="0"/>
<span class="ms-span ms-radio"></span>
</el-col>
<el-col :span="8">
<span class="ms-span ms-radio">{{$t('loop.proceed')}}</span>
<el-switch v-model="controller.countController.proceed"/>
</el-col>
</el-row>
</div>
<div v-else-if="controller.loopType==='FOREACH'">
<el-row>
<el-col :span="8">
<el-input :placeholder="$t('api_test.request.condition_variable')" v-model="controller.forEachController.inputVal" size="small"/>
</el-col>
<el-col :span="1" style="margin-top: 6px">
<span style="margin:10px 10px 10px">in</span>
</el-col>
<el-col :span="8">
<el-input :placeholder="$t('api_test.request.condition_variable')" v-model="controller.forEachController.returnVal" size="small"/>
</el-col>
<el-col :span="7">
<span class="ms-span ms-radio">{{$t('loop.interval')}}</span>
<el-input-number size="small" v-model="controller.forEachController.interval" :placeholder="$t('commons.millisecond')" :max="1000*10000000" :min="0"/>
<span class="ms-span ms-radio"></span>
</el-col>
</el-row>
</div>
<div v-else>
<el-input size="small" v-model="controller.whileController.variable" style="width: 20%" :placeholder="$t('api_test.request.condition_variable')"/>
<el-select v-model="controller.whileController.operator" :placeholder="$t('commons.please_select')" size="small"
@change="change" style="width: 10%;margin-left: 10px">
<el-option v-for="o in operators" :key="o.value" :label="$t(o.label)" :value="o.value"/>
</el-select>
<el-input size="small" v-model="controller.whileController.value" :placeholder="$t('api_test.value')" v-if="!hasEmptyOperator" style="width: 20%;margin-left: 20px"/>
<span class="ms-span ms-radio">{{$t('loop.timeout')}}</span>
<el-input-number size="small" v-model="controller.whileController.timeout" :placeholder="$t('commons.millisecond')" :max="1000*10000000" :min="0"/>
<span class="ms-span ms-radio"></span>
</div>
</div>
</el-collapse-transition>
</el-card>
</template>
<script>
export default {
name: "MsLoopController",
props: {
controller: {},
node: {},
index: Object,
},
data() {
return {
loading: false,
operators: {
EQ: {
label: "commons.adv_search.operators.equals",
value: "=="
},
NE: {
label: "commons.adv_search.operators.not_equals",
value: "!="
},
LIKE: {
label: "commons.adv_search.operators.like",
value: "=~"
},
NOT_LIKE: {
label: "commons.adv_search.operators.not_like",
value: "!~"
},
GT: {
label: "commons.adv_search.operators.gt",
value: ">"
},
LT: {
label: "commons.adv_search.operators.lt",
value: "<"
},
IS_EMPTY: {
label: "commons.adv_search.operators.is_empty",
value: "is empty"
},
IS_NOT_EMPTY: {
label: "commons.adv_search.operators.is_not_empty",
value: "is not empty"
}
}
}
},
methods: {
remove() {
this.$emit('remove', this.controller, this.node);
},
copyRow() {
this.$emit('copyRow', this.controller, this.node);
},
active(item) {
item.active = !item.active;
this.reload();
},
changeRadio() {
this.controller.active = true;
this.reload();
},
change(value) {
if (value.indexOf("empty") > 0 && !!this.controller.value) {
this.controller.value = "";
}
},
reload() {
this.loading = true
this.$nextTick(() => {
this.loading = false
})
},
},
computed: {
hasEmptyOperator() {
return !!this.controller.operator && this.controller.operator.indexOf("empty") > 0;
}
}
}
</script>
<style scoped>
.ms-api-col {
background-color: #F4F4F5;
border-color: #02A7F0;
margin-right: 10px;
color: #02A7F0;
}
.ms-title-buttion {
background-color: #F4F4F5;
margin-right: 20px;
color: #02A7F0;
}
.icon.is-active {
transform: rotate(90deg);
}
.ms-span {
margin: 10px;
}
.ms-radio {
color: #606266;
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", Arial, sans-serif;
font-size: 13px;
font-weight: normal;
}
</style>

View File

@ -1,5 +1,5 @@
export const ELEMENTS = new Map([
['ALL', ["scenario", "HTTPSamplerProxy", "DubboSampler", "JDBCSampler", "TCPSampler", "OT_IMPORT", "IfController", "ConstantTimer", "JSR223Processor", "CustomizeReq"]],
['ALL', ["scenario", "HTTPSamplerProxy", "DubboSampler", "JDBCSampler", "TCPSampler", "OT_IMPORT", "IfController", "LoopController", "ConstantTimer", "JSR223Processor", "CustomizeReq"]],
['scenario', ["HTTPSamplerProxy", "DubboSampler", "JDBCSampler", "TCPSampler", "CASE", "OT_IMPORT", "IfController", "ConstantTimer", "JSR223Processor", "CustomizeReq"]],
['HTTPSamplerProxy', ["ConstantTimer", "JSR223PreProcessor", "JSR223PostProcessor", "Assertions", "Extract"]],
['DubboSampler', ["ConstantTimer", "JSR223PreProcessor", "JSR223PostProcessor", "Assertions", "Extract"]],
@ -7,6 +7,7 @@ export const ELEMENTS = new Map([
['TCPSampler', ["ConstantTimer", "JSR223PreProcessor", "JSR223PostProcessor", "Assertions", "Extract"]],
['OT_IMPORT', ["ConstantTimer", "JSR223PreProcessor", "JSR223PostProcessor", "Assertions", "Extract"]],
['IfController', ["IfController", "HTTPSamplerProxy", "DubboSampler", "JDBCSampler", "TCPSampler", "OT_IMPORT", "ConstantTimer", "JSR223Processor", "JSR223PreProcessor", "JSR223PostProcessor", "Assertions", "Extract", "CustomizeReq"]],
['LoopController', ["IfController", "HTTPSamplerProxy", "DubboSampler", "JDBCSampler", "TCPSampler", "OT_IMPORT", "ConstantTimer", "JSR223Processor", "JSR223PreProcessor", "JSR223PostProcessor", "Assertions", "Extract", "CustomizeReq"]],
['ConstantTimer', []],
['JSR223Processor', ["ConstantTimer", "JSR223PreProcessor", "JSR223PostProcessor", "Assertions", "Extract"]],
['JSR223PreProcessor', []],
@ -27,6 +28,7 @@ export const ELEMENT_TYPE = {
JSR223PostProcessor: "JSR223PostProcessor",
Assertions: "Assertions",
Extract: "Extract",
CustomizeReq: "CustomizeReq"
CustomizeReq: "CustomizeReq",
LoopController: "LoopController"
}

View File

@ -1016,6 +1016,38 @@ export class IfController extends Controller {
}
}
export class LoopController extends Controller {
constructor(options = {}) {
super("LoopController", options);
this.type = "LoopController";
this.active = false;
this.loopType = "LOOP_COUNT";
this.countController = {loops: 0, interval: 0, proceed: false};
this.forEachController = {inputVal: "", returnVal: "", interval: 0};
this.whileController = {variable: "", operator: "", value: "", timeout: 0};
this.hashTree = [];
this.set(options);
}
isValid() {
if (!!this.operator && this.operator.indexOf("empty") > 0) {
return !!this.variable && !!this.operator;
}
return !!this.variable && !!this.operator && !!this.value;
}
label() {
if (this.isValid()) {
let label = this.variable;
if (this.operator) label += " " + this.operator;
if (this.value) label += " " + this.value;
return label;
}
return "";
}
}
export class Timer extends BaseConfig {
static TYPES = {
CONSTANT_TIMER: "Constant Timer",

View File

@ -540,6 +540,7 @@ export default {
res_param: "Response content",
batch_delete: "Batch deletion",
delete_confirm: "Confirm deletion",
delete_confirm_step: "Confirm deletion step",
assertions_rule: "Assertion rule",
response_header: "Response header",
response_body: "Response body",
@ -570,6 +571,7 @@ export default {
external_import: "External import",
wait_controller: "Wait controller",
if_controller: "If controller",
loop_controller: "Loop Controller",
scenario_import: "Scenario import",
customize_script: "Customize script",
customize_req: "Customize req",
@ -1255,8 +1257,8 @@ export default {
host: 'Host number cannot be empty',
port: 'Port cannot be empty',
account: 'Account cannot be empty',
test_recipients:'Test recipients',
tip:'Tip: use as test mail recipient only',
test_recipients: 'Test recipients',
tip: 'Tip: use as test mail recipient only',
},
i18n: {
@ -1410,5 +1412,15 @@ export default {
nothing: "Nothing",
preview: "Preview",
add_custom: "Add Custom Prop"
},
loop: {
loops_title: "loops",
foreach: "ForEach",
while: "While",
loops: "loops",
interval: "interval",
proceed: "proceed",
timeout: "timeout",
}
};

View File

@ -539,6 +539,7 @@ export default {
res_param: "响应内容",
batch_delete: "批量删除",
delete_confirm: "确认删除接口",
delete_confirm_step: "确认删除步骤",
assertions_rule: "断言规则",
response_header: "响应头",
response_body: "响应体",
@ -569,6 +570,7 @@ export default {
external_import: "外部导入",
wait_controller: "等待控制器",
if_controller: "条件控制器",
loop_controller: "循环控制器",
scenario_import: "场景导入",
customize_script: "自定义脚本",
customize_req: "自定义请求",
@ -1256,8 +1258,8 @@ export default {
host: '主机号不能为空',
port: '端口号不能为空',
account: '账户不能为空',
test_recipients:'测试收件人',
tip:'提示:仅用来作为测试邮件收件人',
test_recipients: '测试收件人',
tip: '提示:仅用来作为测试邮件收件人',
},
i18n: {
home: '首页',
@ -1410,5 +1412,14 @@ export default {
nothing: "无",
preview: "预览",
add_custom: "添加自定义属性"
},
loop: {
loops_title: "次数循环",
foreach: "ForEach 循环",
while: "While 循环",
loops: "循环次数",
interval: "循环间隔",
proceed: "成功后继续循环",
timeout: "循环超时时间",
}
};

View File

@ -539,6 +539,7 @@ export default {
res_param: "響應内容",
batch_delete: "批量删除",
delete_confirm: "確認刪除接口",
delete_confirm_step: "確認刪除步骤",
assertions_rule: "斷言規則",
response_header: "響應頭",
response_body: "響應體",
@ -569,6 +570,7 @@ export default {
external_import: "外部導入",
wait_controller: "等待控制器",
if_controller: "條件控制器",
loop_controller: "循环控制器",
scenario_import: "場景導入",
customize_script: "自定義脚本",
customize_req: "自定義請求",
@ -1255,8 +1257,8 @@ export default {
host: '主機號不能為空',
port: '端口號不能為空',
account: '賬戶不能為空',
test_recipients:'測試收件人',
tip:'提示:僅用來作為測試郵件收件人',
test_recipients: '測試收件人',
tip: '提示:僅用來作為測試郵件收件人',
},
i18n: {
home: '首頁',
@ -1409,5 +1411,15 @@ export default {
nothing: "无",
preview: "预览",
add_custom: "添加自定义属性"
},
loop: {
loops_title: "次數循環",
foreach: "ForEach 循環",
while: "While 循環",
loops: "循環次数",
interval: "循環間隔",
proceed: "成功後繼續循環",
timeout: "循環超時時間",
}
};