feat(测试跟踪): UI支持环境
--story=1010692 --user=张大海 【UI测试】支持与环境管理打通 https://www.tapd.cn/55049933/s/1315234
This commit is contained in:
parent
d0cf2b0b33
commit
de8d6ff963
|
@ -18,12 +18,21 @@
|
|||
:content="$t('commons.import')"
|
||||
@click="importJSON"
|
||||
/>
|
||||
<ms-table-button
|
||||
<el-popover
|
||||
v-permission="['PROJECT_ENVIRONMENT:READ+EXPORT']"
|
||||
icon="el-icon-box"
|
||||
:content="$t('commons.export')"
|
||||
@click="exportJSON"
|
||||
/>
|
||||
placement="bottom"
|
||||
trigger="hover"
|
||||
:content="$t('envrionment.export_variable_tip')"
|
||||
width="300">
|
||||
<ms-table-button
|
||||
style="margin-left: 10px"
|
||||
slot="reference"
|
||||
v-permission="['PROJECT_ENVIRONMENT:READ+EXPORT']"
|
||||
icon="el-icon-box"
|
||||
:content="$t('commons.export')"
|
||||
@click="exportJSON"
|
||||
/>
|
||||
</el-popover>
|
||||
<el-link
|
||||
style="margin-left: 10px"
|
||||
@click="batchAdd"
|
||||
|
@ -58,6 +67,31 @@
|
|||
>
|
||||
<ms-table-column prop="num" sortable label="ID" min-width="60">
|
||||
</ms-table-column>
|
||||
|
||||
<ms-table-column
|
||||
prop="scope"
|
||||
sortable
|
||||
:label="$t('commons.scope')"
|
||||
:filters="scopeTypeFilters"
|
||||
:filter-method="filterScope"
|
||||
min-width="120">
|
||||
<template slot-scope="scope">
|
||||
<el-select
|
||||
v-model="scope.row.scope"
|
||||
:placeholder="$t('commons.please_select')"
|
||||
size="mini"
|
||||
@change="changeType(scope.row)"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in scopeTypeFilters"
|
||||
:key="item.value"
|
||||
:label="item.text"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</template>
|
||||
</ms-table-column>
|
||||
|
||||
<ms-table-column
|
||||
prop="name"
|
||||
:label="$t('api_test.variable_name')"
|
||||
|
@ -128,14 +162,14 @@
|
|||
sortable
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
<el-input v-model="scope.row.description" size="mini" />
|
||||
<el-input v-model="scope.row.description" size="mini"/>
|
||||
</template>
|
||||
</ms-table-column>
|
||||
|
||||
<ms-table-column :label="$t('commons.operating')" width="150">
|
||||
<template v-slot:default="scope">
|
||||
<span>
|
||||
<el-switch v-model="scope.row.enable" size="mini" />
|
||||
<el-switch v-model="scope.row.enable" size="mini"/>
|
||||
<el-tooltip
|
||||
effect="dark"
|
||||
:content="$t('commons.remove')"
|
||||
|
@ -171,7 +205,7 @@
|
|||
</ms-table-column>
|
||||
</ms-table>
|
||||
</div>
|
||||
<batch-add-parameter @batchSave="batchSave" ref="batchAdd" />
|
||||
<batch-add-parameter @batchSave="batchSave" ref="batchAdd"/>
|
||||
<api-variable-setting ref="apiVariableSetting"></api-variable-setting>
|
||||
<variable-import
|
||||
ref="variableImport"
|
||||
|
@ -181,7 +215,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { KeyValue } from "../../../model/EnvTestModel";
|
||||
import {KeyValue} from "../../../model/EnvTestModel";
|
||||
import MsApiVariableInput from "./ApiVariableInput";
|
||||
import BatchAddParameter from "./BatchAddParameter";
|
||||
import MsTableButton from "../../MsTableButton";
|
||||
|
@ -189,8 +223,9 @@ import MsTable from "../../table/MsTable";
|
|||
import MsTableColumn from "../../table/MsTableColumn";
|
||||
import ApiVariableSetting from "./ApiVariableSetting";
|
||||
import CsvFileUpload from "./variable/CsvFileUpload";
|
||||
import { downloadFile, getUUID, operationConfirm } from "../../../utils";
|
||||
import {downloadFile, getUUID, operationConfirm} from "../../../utils";
|
||||
import VariableImport from "./variable/VariableImport";
|
||||
import _ from "lodash";
|
||||
|
||||
export default {
|
||||
name: "MsApiScenarioVariables",
|
||||
|
@ -230,15 +265,19 @@ export default {
|
|||
},
|
||||
],
|
||||
typeSelectOptions: [
|
||||
{ value: "CONSTANT", label: this.$t("api_test.automation.constant") },
|
||||
{ value: "LIST", label: this.$t("test_track.case.list") },
|
||||
{ value: "CSV", label: "CSV" },
|
||||
{ value: "COUNTER", label: this.$t("api_test.automation.counter") },
|
||||
{ value: "RANDOM", label: this.$t("api_test.automation.random") },
|
||||
{value: "CONSTANT", label: this.$t("api_test.automation.constant")},
|
||||
{value: "LIST", label: this.$t("test_track.case.list")},
|
||||
{value: "CSV", label: "CSV"},
|
||||
{value: "COUNTER", label: this.$t("api_test.automation.counter")},
|
||||
{value: "RANDOM", label: this.$t("api_test.automation.random")},
|
||||
],
|
||||
variables: {},
|
||||
selectVariable: "",
|
||||
editData: {},
|
||||
scopeTypeFilters: [
|
||||
{text: this.$t("commons.api"), value: "api"},
|
||||
{text: this.$t("commons.ui_test"), value: "ui"},
|
||||
]
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
|
@ -279,15 +318,15 @@ export default {
|
|||
if (repeatKey !== "") {
|
||||
this.$warning(
|
||||
this.$t("api_test.environment.common_config") +
|
||||
"【" +
|
||||
repeatKey +
|
||||
"】" +
|
||||
this.$t("load_test.param_is_duplicate")
|
||||
"【" +
|
||||
repeatKey +
|
||||
"】" +
|
||||
this.$t("load_test.param_is_duplicate")
|
||||
);
|
||||
}
|
||||
if (isNeedCreate) {
|
||||
this.variables.push(
|
||||
new KeyValue({ enable: true, id: getUUID(), type: "CONSTANT" })
|
||||
new KeyValue({enable: true, id: getUUID(), type: "CONSTANT", scope: "api"})
|
||||
);
|
||||
}
|
||||
this.$emit("change", this.variables);
|
||||
|
@ -320,11 +359,11 @@ export default {
|
|||
},
|
||||
querySearch(queryString, cb) {
|
||||
let restaurants = [
|
||||
{ value: "UTF-8" },
|
||||
{ value: "UTF-16" },
|
||||
{ value: "GB2312" },
|
||||
{ value: "ISO-8859-15" },
|
||||
{ value: "US-ASCll" },
|
||||
{value: "UTF-8"},
|
||||
{value: "UTF-16"},
|
||||
{value: "GB2312"},
|
||||
{value: "ISO-8859-15"},
|
||||
{value: "US-ASCll"},
|
||||
];
|
||||
let results = queryString
|
||||
? restaurants.filter(this.createFilter(queryString))
|
||||
|
@ -346,6 +385,9 @@ export default {
|
|||
this.$set(item, "description", item.remark);
|
||||
item.remark = undefined;
|
||||
}
|
||||
if (!item.scope) {
|
||||
this.$set(item, "scope", "api");
|
||||
}
|
||||
index++;
|
||||
});
|
||||
},
|
||||
|
@ -369,7 +411,7 @@ export default {
|
|||
}
|
||||
);
|
||||
},
|
||||
filter() {
|
||||
filter(scope) {
|
||||
let datas = [];
|
||||
this.variables.forEach((item) => {
|
||||
if (this.selectVariable && this.selectVariable != "" && item.name) {
|
||||
|
@ -389,6 +431,29 @@ export default {
|
|||
});
|
||||
this.variables = datas;
|
||||
},
|
||||
filterScope(scope) {
|
||||
let datas = [];
|
||||
let variables = _.cloneDeep(this.variables);
|
||||
variables.forEach((item) => {
|
||||
if (scope == "api") {
|
||||
if (
|
||||
item.scope && item.scope != "api"
|
||||
) {
|
||||
item.hidden = true;
|
||||
} else {
|
||||
item.hidden = undefined;
|
||||
}
|
||||
} else {
|
||||
if (item.scope == scope) {
|
||||
item.hidden = undefined;
|
||||
} else {
|
||||
item.hidden = true;
|
||||
}
|
||||
}
|
||||
datas.push(item);
|
||||
});
|
||||
this.variables = datas;
|
||||
},
|
||||
openSetting(data) {
|
||||
this.$refs.apiVariableSetting.open(data);
|
||||
},
|
||||
|
@ -449,8 +514,15 @@ export default {
|
|||
this.sortParameters();
|
||||
},
|
||||
exportJSON() {
|
||||
if (this.$refs.variableTable.selectIds.length < 1) {
|
||||
this.$warning(this.$t("api_test.environment.select_variable"));
|
||||
let apiVariable = [];
|
||||
this.$refs.variableTable.selectRows.forEach((r) => {
|
||||
if (!r.scope || r.scope != "ui") {
|
||||
apiVariable.push(r);
|
||||
}
|
||||
});
|
||||
|
||||
if (apiVariable.length < 1) {
|
||||
this.$warning(this.$t("api_test.environment.select_api_variable"));
|
||||
return;
|
||||
}
|
||||
let variablesJson = [];
|
||||
|
@ -460,7 +532,7 @@ export default {
|
|||
if (row.type === "CSV") {
|
||||
messages = this.$t("variables.csv_download");
|
||||
}
|
||||
if (row.name) {
|
||||
if (row.name && (!row.scope || row.scope == "api")) {
|
||||
variablesJson.push(row);
|
||||
}
|
||||
});
|
||||
|
@ -496,7 +568,15 @@ export default {
|
|||
},
|
||||
created() {
|
||||
if (this.items.length === 0) {
|
||||
this.items.push(new KeyValue({ enable: true }));
|
||||
this.items.push(new KeyValue({enable: true, scope: "api"}));
|
||||
} else {
|
||||
//历史数据默认是 api 应用场景
|
||||
_.forEach(this.items, item => {
|
||||
if (!item.scope) {
|
||||
this.$set(item, "scope", "api");
|
||||
}
|
||||
})
|
||||
this.variables = this.items;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
|
@ -0,0 +1,140 @@
|
|||
<template>
|
||||
<el-row>
|
||||
<el-col :span="16">
|
||||
<el-select v-model="selfQuantity" placeholder=" " size="mini" filterable default-first-option
|
||||
allow-create
|
||||
class="timing_select" :disabled="selfChoose">
|
||||
<el-option
|
||||
v-for="item in quantityOptions"
|
||||
:key="item"
|
||||
:label="item"
|
||||
:value="item">
|
||||
</el-option>
|
||||
</el-select>
|
||||
<el-select v-model="selfUnit" placeholder=" " size="mini"
|
||||
class="timing_select" :disabled="selfChoose">
|
||||
<el-option
|
||||
v-for="item in unitOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: "MiniTimingItem",
|
||||
components: {},
|
||||
props: {
|
||||
choose: {
|
||||
type: Boolean,
|
||||
default() {
|
||||
return false;
|
||||
},
|
||||
},
|
||||
expr: {
|
||||
type: String,
|
||||
default() {
|
||||
return "";
|
||||
}
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default() {
|
||||
return "";
|
||||
}
|
||||
},
|
||||
shareLink: {
|
||||
type: Boolean,
|
||||
default() {
|
||||
return false;
|
||||
},
|
||||
},
|
||||
unitOptions: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [
|
||||
{value: "D", label: this.$t('commons.date_unit.day')},
|
||||
{value: "M", label: this.$t('commons.date_unit.month')},
|
||||
{value: "Y", label: this.$t('commons.date_unit.year')},
|
||||
];
|
||||
},
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
expr(val) {
|
||||
this.parseExpr(val);
|
||||
},
|
||||
choose(val) {
|
||||
this.selfChoose = val;
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selfQuantity: "",
|
||||
selfUnit: "",
|
||||
selfChoose: this.choose,
|
||||
selfExpr: this.expr,
|
||||
quantityOptions: [
|
||||
"1", "2", "3", "4", "5", "6", "7", "8", "9", "10",
|
||||
"11", "12", "13", "14", "15", "16", "17", "18", "19", "20",
|
||||
"21", "22", "23", "24", "25", "26", "27", "28", "29", "30",
|
||||
"31"
|
||||
],
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
chooseChange(val) {
|
||||
if (val && (!this.selfQuantity || !this.selfUnit)) {
|
||||
this.$warning(this.$t('project.please_select_cleaning_time'));
|
||||
this.selfChoose = false;
|
||||
return false;
|
||||
}
|
||||
if (val && this.selfQuantity) {
|
||||
if (typeof this.selfQuantity !== 'number' && isNaN(parseInt(this.selfQuantity))) {
|
||||
this.$warning(this.$t('api_test.request.time') + this.$t('commons.type_of_integer'));
|
||||
this.selfChoose = false;
|
||||
return false;
|
||||
}
|
||||
if (this.selfQuantity <= 0 || parseInt(this.selfQuantity) <= 0) {
|
||||
this.$warning(this.$t('commons.adv_search.operators.gt') + "0");
|
||||
this.selfChoose = false;
|
||||
return false;
|
||||
}
|
||||
if (Number(this.selfQuantity) > parseInt(this.selfQuantity)) {
|
||||
this.$warning(this.$t('api_test.request.time') + this.$t('commons.type_of_integer'));
|
||||
this.selfChoose = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
this.$emit("update:choose", val);
|
||||
this.$emit("update:expr", parseInt(this.selfQuantity) + this.selfUnit);
|
||||
this.$emit("chooseChange");
|
||||
},
|
||||
parseExpr(expr) {
|
||||
if (!expr) {
|
||||
return;
|
||||
}
|
||||
// 1D 1M 1Y
|
||||
this.selfUnit = expr.substring(expr.length - 1);
|
||||
this.selfQuantity = expr.substring(0, expr.length - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.timing_name {
|
||||
color: var(--primary_color);
|
||||
}
|
||||
|
||||
.timing_select {
|
||||
width: 80px;
|
||||
margin-left: 2px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,72 @@
|
|||
<template>
|
||||
<div class="form-section">
|
||||
<div>
|
||||
<div class="title">{{ title }}</div>
|
||||
|
||||
<el-tooltip class="item" effect="dark" :content="content" placement="top-start">
|
||||
<span
|
||||
:class="{ 'el-icon-arrow-left pointer' : !active, 'el-icon-arrow-down pointer' : active}"
|
||||
@click="active=!active"></span>
|
||||
</el-tooltip>
|
||||
<template v-if="active">
|
||||
<slot></slot>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "FormSection",
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
initActive: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
active: false,
|
||||
content: this.$t('api_test.definition.document.open')
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
initActive: {
|
||||
handler(val) {
|
||||
this.active = val;
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
active: {
|
||||
handler(val) {
|
||||
if (val) {
|
||||
this.content = this.$t('api_test.definition.document.close');
|
||||
} else {
|
||||
this.content = this.$t('api_test.definition.document.open');
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.form-section {
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin-right: 15px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
|
@ -1578,6 +1578,7 @@ const message = {
|
|||
environment_group_id: "Environment Group ID",
|
||||
select_environment: "Please select environment",
|
||||
select_variable: "Please select variable",
|
||||
select_api_variable: "Please select api variable",
|
||||
please_save_test: "Please Save Test First",
|
||||
common_config: "Common Config",
|
||||
http_config: "HTTP Config",
|
||||
|
@ -2927,56 +2928,162 @@ const message = {
|
|||
step_results: "Step results",
|
||||
treatment_method: "Treatment method",
|
||||
scenario_steps: "Scenario steps",
|
||||
basic_information: "Basic information",
|
||||
check_element: "Please select the elements",
|
||||
selenium_tip: "Support Selenium-IDE format",
|
||||
selenium_export_tip: "Export side format",
|
||||
elementObject: "Element Object",
|
||||
elementLocator: "Element Locator",
|
||||
elementType: "Element Type",
|
||||
not_selected: "(Not Selected)",
|
||||
not_selected_location: "(Not Selected Location)",
|
||||
location: "Location",
|
||||
run: "Run",
|
||||
locate_type: "Locate Type",
|
||||
coord: "Coord",
|
||||
enable_or_not: "Enable/Disable",
|
||||
enable: "Enable",
|
||||
disable: "Disable",
|
||||
resolution: "resolution",
|
||||
ignore_fail: "ignore fail",
|
||||
not_ignore_fail: "not ignore fail",
|
||||
cmdValidation: "Assert",
|
||||
cmdValidateValue: "Assert Value",
|
||||
cmdValidateText: "Assert Text",
|
||||
cmdValidateDropdown: "Assert Dropdown",
|
||||
cmdValidateElement: "Assert Element",
|
||||
cmdValidateTitle: "Assert Title",
|
||||
cmdValidation: "Validation",
|
||||
cmdValidateValue: "ValidateValue",
|
||||
cmdValidateText: "ValidateText",
|
||||
cmdValidateDropdown: "ValidateDropdown",
|
||||
cmdValidateElement: "ValidateElement",
|
||||
cmdValidateTitle: "ValidateTitle",
|
||||
cmdOpen: "Open",
|
||||
cmdSelectWindow: "Select Window",
|
||||
cmdSetWindowSize: "Set Window Size",
|
||||
cmdSelectFrame: "Select Frame",
|
||||
cmdDialog: "Dialog Operation",
|
||||
cmdDropdownBox: "Dropdown Operation",
|
||||
submit: "submit",
|
||||
cmdSetItem: "Set Item",
|
||||
cmdWaitElement: "Wait Element",
|
||||
cmdSelectWindow: "SelectWindow",
|
||||
cmdSetWindowSize: "SetWindowSize",
|
||||
cmdSelectFrame: "SelectFrame",
|
||||
cmdDialog: "DialogOperation",
|
||||
cmdDropdownBox: "DropdownBox",
|
||||
submit: "Submit",
|
||||
cmdSetItem: "SetItem",
|
||||
cmdWaitElement: "WaitElement",
|
||||
cmdInput: "Input",
|
||||
cmdMouseClick: "Mouse Click",
|
||||
cmdMouseMove: "Mouse Move",
|
||||
cmdMouseDrag: "Mouse Drag",
|
||||
cmdMouseClick: "MouseClick",
|
||||
cmdMouseMove: "MouseMove",
|
||||
cmdMouseDrag: "MouseDrag",
|
||||
cmdTimes: "Times",
|
||||
cmdForEach: "ForEach",
|
||||
cmdWhile: "While",
|
||||
cmdIf: "If",
|
||||
cmdElse: "Else",
|
||||
cmdElseIf: "ElseIf",
|
||||
close: "close",
|
||||
close: "Close",
|
||||
cmdExtraction: "Extraction",
|
||||
cmdExtractWindow: "Extract window",
|
||||
cmdExtractElement: "Extract Element",
|
||||
cmdExtractWindow: "ExtractWindow",
|
||||
cmdExtractElement: "ExtractElement",
|
||||
valiate_fail: "Validate fail",
|
||||
check_subitem: 'check subitem',
|
||||
basic_information: "Basic information",
|
||||
step_type: "Step type",
|
||||
selenium_tip: "Support Selenium-IDE plugin format import",
|
||||
selenium_export_tip: "Export side file via MeterSphere",
|
||||
elementObject: "Element Object",
|
||||
elementLocator: "Element Locator",
|
||||
elementType: "Category",
|
||||
not_selected: "(No element selected)",
|
||||
not_selected_location: "(No selected element location)",
|
||||
location: "Location",
|
||||
run: "Run",
|
||||
locate_type: "Location method",
|
||||
coord: "coordinate",
|
||||
enable_or_not: "Enable/Disable",
|
||||
enable: "Enable",
|
||||
disable: "Disable",
|
||||
resolution: "resolution",
|
||||
ignore_fail: "Ignore exception and continue execution",
|
||||
not_ignore_fail: "Abort process",
|
||||
input_or_not: "input",
|
||||
input_content: "Input content",
|
||||
insert_content: "Type content",
|
||||
append_content: "Append input",
|
||||
append_tip: "Check, append the input after the existing content;<br/>Uncheck, clear the existing content and then input",
|
||||
pls_input: "Please input content",
|
||||
opt_type: "mode:",
|
||||
yes: "Yes",
|
||||
no: "No",
|
||||
confirm: "OK",
|
||||
cancel: "Cancel",
|
||||
press_button: "Click the popup OK button or Cancel button",
|
||||
param_null: "Parameter cannot be null",
|
||||
operation_object: "Operation object",
|
||||
sub_item: "Sub-item",
|
||||
value: "value",
|
||||
select: "Select",
|
||||
option: "Option ( Option )",
|
||||
index: "Index ( Index )",
|
||||
s_value: "Value ( Value )",
|
||||
text: "Text ( Text )",
|
||||
set_itera: "Set traversal",
|
||||
foreach_tip: "Set loop iteration, support array row data, for example: [1,2,3]; you can also enter variables",
|
||||
intervals: "Interval time",
|
||||
condition_type: "Condition Type",
|
||||
please_select: "Please select",
|
||||
condition_list: "Condition list: set multiple conditions by list",
|
||||
condition_list_: "Condition List",
|
||||
condition_exp: "Conditional expression: If the expression is true, then execute the steps inside",
|
||||
condition_exp_: "Condition expression",
|
||||
expression: "expression",
|
||||
if_tip: "Please use ${var} for variables, and single quotes for strings, such as: ${name} === 'Zhangsan'",
|
||||
input_c_tip: "'The contenteditable attribute of an editable paragraph element must be true to enable input; for example: <p contenteditable="true">This is an editable paragraph. Please try editing the text.< /p>'",
|
||||
input: "input box",
|
||||
editable_p: "Editable paragraph",
|
||||
click_type: "Click method",
|
||||
set_click_point: "Set the mouse click position",
|
||||
click_tip_1: "Check to control the click position of the mouse on the element",
|
||||
element_location: "Element Location",
|
||||
click_point: "Click position",
|
||||
x: "Abscissa",
|
||||
y: "ordinate",
|
||||
click_tip_2: "The upper left corner of the default element is 0, 0; by setting the relative position, control the click position of the mouse on the element",
|
||||
click: "click",
|
||||
dclick: "Double click",
|
||||
press: "press",
|
||||
standup: "Bounce up",
|
||||
mouse_start: "Mouse start position",
|
||||
drag_start: "The position of the starting point of the dragged element",
|
||||
mouse_end: "Mouse end position",
|
||||
drag_end: "The final position of the dragged element",
|
||||
move_type: "Move Type",
|
||||
mouse_location: "Mouse location",
|
||||
relative_location: "relative coordinate location",
|
||||
move_tip: "Relative position, the current position coordinate of the element is 0, 0",
|
||||
mouse_out_e: "Mouse out of element",
|
||||
mouse_in_e: "Mouse in element",
|
||||
mouse_e_to_c: "Mouse mouse from element to coordinate position",
|
||||
url: "Webpage address",
|
||||
sf_tip: "If you are switching frames, you need to pass in the index or element positioning before switching",
|
||||
sf_index: "frame index number",
|
||||
select_index: "Select the frame of the current page;",
|
||||
select_f_tip: "Example: For example, if the index value is entered as 1, the effect will switch to the second frame of the current page (the index value starts from 0)",
|
||||
exit_frame: "Exit the current frame (back to the main page)",
|
||||
select_frame_index: "Switch to the specified frame according to the frame index",
|
||||
select_by_location: "Switch frame according to the positioning method",
|
||||
sw_tip1: "If you switch to the specified window, you need to pass in the handle",
|
||||
handle_id: "Handle ID",
|
||||
window_handle: "Window handle ID",
|
||||
frame_index: "Webpage index number",
|
||||
window_index: "Window web page index number",
|
||||
select_open_window: "Select the number of pages that have been opened;",
|
||||
s_w_t1: "Example: For example, if the index value is entered as 3, then the effect will switch to the third window that has been opened (the index value starts from 1)",
|
||||
switch_by_id: "Switch to the specified window according to the handle ID",
|
||||
switch_by_index: "Switch to the specified window according to the page index number",
|
||||
switch_to_default: "Switch to initial window",
|
||||
ws_tip1: "Specify the size, set the size of the window according to the input width and height",
|
||||
size: "Size:",
|
||||
by_pixel: "in pixels",
|
||||
width: "width",
|
||||
height: "Height",
|
||||
times: "Number of cycles",
|
||||
set_times: "Set the number of times of the loop, you can enter a variable",
|
||||
wait_text: "Wait for text",
|
||||
wait_timeout: "Wait Timeout",
|
||||
wait_for_text: "Wait for the element to be equal to the given value (Text)",
|
||||
wait_for_ele_pre: "Wait for element to exist",
|
||||
wait_for_ele_visible: "Wait for element to show",
|
||||
wait_for_ele_not_visible: "Wait for element not visible",
|
||||
wait_for_ele_not_pre: "Wait for element not present",
|
||||
wait_for_ele_edi: "Wait for element to be editable",
|
||||
wait_for_ele_not_edi: "Wait for element not editable",
|
||||
wait_tip: "For the Text attribute of the element, it refers to the text content displayed on the page, and the waiting timeout time is 15000ms",
|
||||
exe_first: "Execute first and then judge",
|
||||
while_t_1: "Execute first and then judge similar to doWhile , execute the loop body once and then judge the condition",
|
||||
while_t_2: "Please use ${var} for variables and single quotes for strings, such as: ${name} === 'Zhangsan'",
|
||||
loop_time_out: "Loop timeout",
|
||||
operation: "Operation",
|
||||
use_pixel: 'use pixel',
|
||||
fullscreen: 'maximum',
|
||||
swicth_to_default: "switch to origin window",
|
||||
program_controller: 'Process control',
|
||||
input_operation: 'input operation',
|
||||
mouse_operation: 'Mouse operation',
|
||||
element_operation: 'Element operation',
|
||||
dialog_operation: 'Pop-up operation',
|
||||
browser_operation: 'Browser operation',
|
||||
pause: 'Pause',
|
||||
browser: "Browser",
|
||||
inner_drag: "Drag in element",
|
||||
|
@ -2993,7 +3100,7 @@ const message = {
|
|||
custom_command_title: "Command",
|
||||
custom_command_label: "Custom command",
|
||||
automation_list: "Automation list",
|
||||
create_custom_command: "Create command",
|
||||
create_custom_command: "Add command",
|
||||
create_custom_command_label: "Create custom command",
|
||||
import_by_list_label: "UI list import",
|
||||
open_custom_command_label: "Open command",
|
||||
|
@ -3004,14 +3111,161 @@ const message = {
|
|||
delete_scenario_lable: "Delete scenario",
|
||||
delete_command_lable: "Delete command",
|
||||
command_name_label: "Command name",
|
||||
unplanned_module: "Unplanned module",
|
||||
default_module: "Default module",
|
||||
executing: "Executing...",
|
||||
unexecute: "PENDING",
|
||||
check_command: "Please tick the instruction",
|
||||
ip: "ip address",
|
||||
cword: "Word",
|
||||
csentence: "Sentence",
|
||||
cparagraph: "Paragraph",
|
||||
loading: "Loading...",
|
||||
close_dialog: "close",
|
||||
unknown_scenario: "Unknown Scenario",
|
||||
unknown_instruction: "Unknown Instruction",
|
||||
unknown_element: "Unknown Element",
|
||||
scenario_ref_add_warning: "No other steps can be added to the referenced scene/instruction steps and sub steps!",
|
||||
batch_editing_steps: "Batch editing steps",
|
||||
wait_time_config: "Timeout setting",
|
||||
wait_element_timeout: "Wait element timeout",
|
||||
more_config_options: "More advanced settings options",
|
||||
updated_config_info: "The updated options are",
|
||||
config_success: "Config success",
|
||||
cmdFileUpload: "File upload",
|
||||
relevant_file: "Relevant File",
|
||||
validate_tips: "To judge whether the actual result is consistent with the expected one, you can add multiple assertions",
|
||||
instruction: "instruction",
|
||||
screen_tip: "If the scene step triggers a native popup (alert or prompt), or if there is no page, the screenshot will not take effect;",
|
||||
ele_css_attr: "Element CSS attribute",
|
||||
ele_css_tip1: "Such as element CSS properties, color properties, font-size properties, etc.",
|
||||
store_css_attr: "CSS attribute (storeCssAttribute)",
|
||||
validate_type: "Please select an assertion method",
|
||||
expect_value: "Expected value",
|
||||
expect_title: "Please enter the desired page title",
|
||||
title_tip: "Assert whether the title of the current window is consistent with the expected value, if it matches exactly, the assertion succeeds, otherwise it fails",
|
||||
input_var: "Please enter a variable",
|
||||
input_expect: "Please enter the expected value",
|
||||
var_tip: "Assert whether the variable matches the expected value",
|
||||
confirm_after_success: "Whether to click the confirm button after success",
|
||||
input_expect_text: "Please enter the expected popup text",
|
||||
input_window_tip: "Only supports the assertion of pop-up text. If yes, the confirmation button on the pop-up will be clicked after the assertion is successful. If no, no button will be clicked after the assertion is successful",
|
||||
select_value: "The value of the selected element is equal to the desired (SelectedValue)",
|
||||
select_label: "The text displayed by the drop-down box option is equal to the expected (SelectedLabel) ",
|
||||
not_select_value: "The value of the selected element is not equal to the expected (NotSelectedValue)",
|
||||
assert_check: "The element is checked (Checked)",
|
||||
assert_editable: "Element is editable (Editable)",
|
||||
assert_element_present: "Element Present (ElementPresent)",
|
||||
assert_element_not_present: "ElementNotPresent",
|
||||
assert_element_not_checked: "Element is not checked (NotChecked)",
|
||||
assert_element_not_editable: "Element is not editable (NotEditable)",
|
||||
assert_element_not_text: "Element text is not equal to expected (NotText)",
|
||||
assert_text: "Element text equals expected(Text)",
|
||||
assert_value: "The element value is equal to the expected (Value)",
|
||||
script_tip: "Only js script is supported, the set script will be executed in the browser",
|
||||
script_type: "Script Type",
|
||||
set_var: "Set variable",
|
||||
async: "async",
|
||||
sync: "Sync",
|
||||
return: "There is a return value",
|
||||
no_return: "No return value",
|
||||
sample_obj: "Ordinary Object",
|
||||
is_string: "Is it a string type",
|
||||
like_string_tip: "Such as strings, numbers, json objects, arrays, etc.;",
|
||||
like_string_tip2: "Note: If it is not a valid js object type when stored as an object type (such as illegal special characters, space effects), it may generate a report failure.",
|
||||
ele_pro: "Element Properties",
|
||||
like_ele_tip: "such as the element's name attribute, id attribute, value attribute, etc.",
|
||||
xpath_locator: "xpath path",
|
||||
xpath_tip: "Only supports element positioning in xpath mode, and returns a value",
|
||||
store: "Ordinary object (store)",
|
||||
store_text: "Element text (storeText)",
|
||||
store_value: "Element value (storeValue)",
|
||||
store_attr: "Element attribute (storeAttribute)",
|
||||
store_xpath_count: "Number of elements matching xpath (storeXpathCount)",
|
||||
store_window_handle: "Window Handle(storeWindowHandle)",
|
||||
store_title: "Web page title (storeTitle)",
|
||||
wait_time: "Wait time",
|
||||
per_tip: "After enabling the performance mode, the memory and cpu usage will be reduced, and the running results will not show step screenshots",
|
||||
fail_over: "Failed to terminate",
|
||||
validate_tip: "Check means a hard assertion (assert), if the assertion fails, the program will terminate. Unchecked means a soft assertion (verify), if the assertion fails, the program will not terminate.",
|
||||
scenario: "Scenario",
|
||||
extract_type: "Please select the extraction information type",
|
||||
input_handle_name: "Please enter the storage window handle variable name",
|
||||
extract_tip: "Save the extracted content to a variable",
|
||||
input_window_title: "Please enter the variable name to store the title of the webpage",
|
||||
revoke: "Revoke",
|
||||
is_required: "Required",
|
||||
remark: "Remark",
|
||||
result: "Result",
|
||||
var_step: "Variable generation steps",
|
||||
param_required_tip: "After debugging and saving, the usage of variables will be automatically verified. The parameters used in user-defined steps must be filled in; Not required is the redundant parameter not used in the custom step.",
|
||||
name: "Name",
|
||||
parameter_configuration: "Parameter configuration",
|
||||
enter_parameters: "Enter parameters",
|
||||
out_parameters: "Outer parameters",
|
||||
opt_ele: "Operation element",
|
||||
dst_ele: "Destination element",
|
||||
drag_type: "Drag and drop method",
|
||||
drag_end_point: "The final position of the dragged element",
|
||||
add_file: "Add file",
|
||||
file: "File: 【",
|
||||
been_deleted: "】 has been deleted! Please select the file again!",
|
||||
click_force: "Force click",
|
||||
click_tip_3: "Checked, the element is blocked and can be forced to click",
|
||||
pls_sel_loctype: "Please select a location type",
|
||||
pls_input_locator: "Please fill in the location",
|
||||
import_template: "Import Template",
|
||||
download_template: "Download Template",
|
||||
import_desc: "Import Description",
|
||||
el_import_tip_1: "1. If the imported ID already exists, update the element;",
|
||||
el_import_tip_2: "2. If the imported ID is empty or the ID does not exist, add an element;",
|
||||
el_import: "Element import",
|
||||
empty_text: "No data yet",
|
||||
confirm_del_el: "Confirm delete element",
|
||||
confirm_del: "Confirm delete",
|
||||
confirm_del_in : "Confirm delete command",
|
||||
deng: "wait",
|
||||
ge_instruction: "Instructions?",
|
||||
ge_el: "elements?",
|
||||
ge_scenario: "Scenarios?",
|
||||
view_ref: "View Reference",
|
||||
unplanned_element: "Unplanned element",
|
||||
scenario_instruction: "Scenario/Instruction",
|
||||
element_beening: "The element under the module is being",
|
||||
element_beening_desc: "element used be scenario",
|
||||
reference: "reference",
|
||||
continue_or_not: "Whether to continue",
|
||||
continue_or_not_delete: "Whether to continue delete",
|
||||
unplanned_scenario: "Unplanned Scenario",
|
||||
unplanned_module: "Unplanned Module",
|
||||
confrim_del_node: "OK to delete node",
|
||||
and_all_sub_node: "All resources under its subnodes?",
|
||||
instruction_is_referenced: "Instruction is referenced:",
|
||||
scenario_is_referenced: "Scenario is referenced:",
|
||||
confirm_del_ins: "Confirm Delete Instruction",
|
||||
confirm_del_scen: "Confirm delete scene",
|
||||
check_grid: "Connection failed, please check selenium-grid service status",
|
||||
check_grid_ip: "Connection failed, please check selenium-grid address information",
|
||||
view_config: "View configuration information",
|
||||
config_ip: "The local ip and port information are not detected, please check",
|
||||
personal_info: "Personal Information",
|
||||
in_config: "In Settings",
|
||||
},
|
||||
command_steps_label: "Instruction Steps",
|
||||
assert_in_text: "Element text contains expectations (InText)",
|
||||
assert_not_in_text: "Element text does not contain expectations (NotInText)",
|
||||
equal: "equal",
|
||||
not_equal: "not equal to",
|
||||
contain: "contains",
|
||||
not_contain: "Does not contain",
|
||||
greater: "greater than",
|
||||
greater_equal: "greater than or equal to",
|
||||
lower: "less than",
|
||||
lower_equal: "Less than or equal to",
|
||||
null: "empty",
|
||||
not_null: "not null",
|
||||
assertion_configuration: "Assertion Configuration",
|
||||
smart_variable_enable: "Use the current scene variables first",
|
||||
use_origin_variable_scene: "Use original scene variables",
|
||||
use_origin_env_run: "Use original scene environment to execute"},
|
||||
project_application: {
|
||||
workstation: {
|
||||
time_tip: 'Off, no time range is set; On, according to the set time range, enter the list to be updated, if the time range is exceeded, it will be automatically cleared from the list;',
|
||||
|
|
|
@ -490,7 +490,8 @@ const message = {
|
|||
ui_scenario: '未规划场景',
|
||||
ui_module: "未规划模块",
|
||||
},
|
||||
template_delete: "模版删除"
|
||||
template_delete: "模版删除",
|
||||
scope: "应用场景",
|
||||
},
|
||||
login: {
|
||||
normal_Login: "普通登录",
|
||||
|
@ -1587,6 +1588,7 @@ const message = {
|
|||
environment_group_id: "环境组ID",
|
||||
select_environment: "请选择环境",
|
||||
select_variable: "请选择变量",
|
||||
select_api_variable: "请选择接口测试变量",
|
||||
please_save_test: "请先保存测试",
|
||||
common_config: "通用配置",
|
||||
list_info: "列表数据用,分隔",
|
||||
|
@ -2963,7 +2965,7 @@ const message = {
|
|||
cmdElse: "Else",
|
||||
cmdElseIf: "ElseIf",
|
||||
close: "关闭网页",
|
||||
cmdExtraction: "提取参数",
|
||||
cmdExtraction: "数据提取",
|
||||
cmdExtractWindow: "提取窗口信息",
|
||||
cmdExtractElement: "提取元素信息",
|
||||
valiate_fail: "校验失败,请检查必填项",
|
||||
|
@ -2986,6 +2988,113 @@ const message = {
|
|||
treatment_method: "处理方式",
|
||||
scenario_steps: "场景步骤",
|
||||
basic_information: "基础信息",
|
||||
step_type: "步骤类型",
|
||||
input_or_not: "是否输入",
|
||||
input_content: "输入内容",
|
||||
insert_content: "键入内容",
|
||||
append_content: "追加输入",
|
||||
append_tip: "勾选,在现有内容后面追加输入;<br/>不勾选,清空现有内容后再进行输入",
|
||||
pls_input: "请输入内容",
|
||||
opt_type: "操作方式:",
|
||||
yes: "是",
|
||||
no: "否",
|
||||
confirm: "确定",
|
||||
cancel: "取消",
|
||||
press_button: "点击弹窗确定按钮或取消按钮",
|
||||
param_null: "参数不能为空",
|
||||
operation_object: "操作对象",
|
||||
sub_item: "子选项",
|
||||
value: "值",
|
||||
select: "选择",
|
||||
option: "选项( Option )",
|
||||
index: "索引( Index )",
|
||||
s_value: "值( Value )",
|
||||
text: "文本( Text )",
|
||||
set_itera: "设置遍历",
|
||||
foreach_tip: "设置循环迭代,支持数组行数据,例如: [1,2,3];也可输入变量",
|
||||
intervals: "间隔时间",
|
||||
condition_type: "条件类型",
|
||||
please_select: "请选择",
|
||||
condition_list: "条件列表:通过列表的方式设置多个条件",
|
||||
condition_list_: "条件列表",
|
||||
condition_exp: "条件表达式:表达式判断为真,则执行里面的步骤",
|
||||
condition_exp_: "条件表达式",
|
||||
expression: "表达式",
|
||||
if_tip: "变量请使用${var},字符串请加单引号,如:${name} === '张三'",
|
||||
input_c_tip: "'可编辑段落的元素 contenteditable 属性必须为 true, 才可实现输入;例:<p contenteditable="true">这是一段可编辑的段落。请试着编辑该文本。</p>'",
|
||||
input: "输入框",
|
||||
editable_p: "可编辑段落",
|
||||
click_type: "点击方式",
|
||||
set_click_point: "设置鼠标点击位置",
|
||||
click_tip_1: "勾选,可控制鼠标在元素上的点击位置",
|
||||
element_location: "元素位置",
|
||||
click_point: "点击位置",
|
||||
x: "横坐标",
|
||||
y: "纵坐标",
|
||||
click_tip_2: "默认元素的左上角为0,0;通过设置相对位置,控制鼠标在元素上的点击位置",
|
||||
click: "单击",
|
||||
dclick: "双击",
|
||||
press: "按下",
|
||||
standup: "弹起",
|
||||
mouse_start: "鼠标起始位置",
|
||||
drag_start: "被拖拽的元素起点的位置",
|
||||
mouse_end: "鼠标终点位置",
|
||||
drag_end: "被拖拽的元素最终的位置",
|
||||
move_type: "移动方式",
|
||||
mouse_location: "鼠标位置",
|
||||
relative_location: "相对坐标位置",
|
||||
move_tip: "相对位置,元素当前的位置坐标为0,0",
|
||||
mouse_out_e: "鼠标移出元素",
|
||||
mouse_in_e: "鼠标移入元素",
|
||||
mouse_e_to_c: "鼠标从元素移到坐标位置",
|
||||
url: "网页地址",
|
||||
sf_tip: "如果是切换 frame,需要传入索引或者元素定位后再切换",
|
||||
sf_index: "frame 索引号",
|
||||
select_index: "选择当前页面的第几个 frame;",
|
||||
select_f_tip: "例:比如索引值输入 1,那么效果会切换到当前页面的第 2 个 frame(索引值从 0 开始计算)",
|
||||
exit_frame: "退出当前 frame(回到主页面)",
|
||||
select_frame_index: "根据 frame 索引号切换到指定 frame",
|
||||
select_by_location: "根据定位方式切换 frame",
|
||||
sw_tip1: "如果是切换到指定窗口,需要传入句柄",
|
||||
handle_id: "句柄 ID",
|
||||
window_handle: "窗口句柄 ID",
|
||||
frame_index: "网页索引号",
|
||||
window_index: "窗口网页索引号",
|
||||
select_open_window: "选择打开过的第几个网页;",
|
||||
s_w_t1: "例:比如索引值输入 3,那么效果会切换到已经打开过的第 3 个窗口(索引值从 1 开始计算)",
|
||||
switch_by_id: "根据句柄 ID 切换到指定窗口",
|
||||
switch_by_index: "根据网页索引号切换到指定窗口",
|
||||
swicth_to_default: "切换到初始窗口",
|
||||
ws_tip1: "指定尺寸,根据输入的宽度和高度,设置窗口的大小",
|
||||
size: "尺寸:",
|
||||
by_pixel: "以像素为单位",
|
||||
width: "宽度",
|
||||
height: "高度",
|
||||
times: "循环次数",
|
||||
set_times: "设置循环的次数,可输入变量",
|
||||
wait_text: "等待文本",
|
||||
wait_timeout: "等待超时",
|
||||
wait_for_text: "等待元素等于给定的定值(Text)",
|
||||
wait_for_ele_pre: "等待元素存在",
|
||||
wait_for_ele_visible: "等待元素显示",
|
||||
wait_for_ele_not_visible: "等待元素不显示",
|
||||
wait_for_ele_not_pre: "等待元素不存在",
|
||||
wait_for_ele_edi: "等待元素可编辑",
|
||||
wait_for_ele_not_edi: "等待元素不可编辑",
|
||||
wait_tip: "针对元素的Text属性,指页面展示出来的文本内容,等待超时时间为15000ms",
|
||||
exe_first: "先执行后判断",
|
||||
while_t_1: "先执行后判断类似 doWhile ,先执行一次循环体再判断条件",
|
||||
while_t_2: "变量请使用${var},字符串请加单引号,如:${name} === '张三'",
|
||||
loop_time_out: "循环超时时间",
|
||||
operation: '操作',
|
||||
use_pixel: '指定尺寸(像素为单位)',
|
||||
fullscreen: '窗口最大化',
|
||||
program_controller: '流程控制',
|
||||
input_operation: '输入操作',
|
||||
mouse_operation: '鼠标操作',
|
||||
element_operation: '元素操作',
|
||||
dialog_operation: '弹窗操作',
|
||||
browser_operation: '浏览器操作',
|
||||
check_element: "请勾选元素",
|
||||
check_subitem: '请选择子分类',
|
||||
pause: '等待时间',
|
||||
|
@ -3017,13 +3126,157 @@ const message = {
|
|||
command_name_label: "指令名称",
|
||||
command_steps_label: "指令步骤",
|
||||
command_step_info: "在右侧添加指令步骤",
|
||||
default_module: "默认模块",
|
||||
executing: "正在执行...",
|
||||
unexecute: "未执行",
|
||||
check_command: "请勾选指令",
|
||||
ip: "ip地址",
|
||||
cword: "词语",
|
||||
csentence: "句子",
|
||||
cparagraph: "段落",
|
||||
loading: "加载中...",
|
||||
close_dialog: "关闭",
|
||||
unknown_scenario: "创建场景",
|
||||
unknown_instruction: "创建指令",
|
||||
unknown_element: "创建元素",
|
||||
scenario_ref_add_warning: "引用的场景/指令步骤及子步骤都无法添加其他步骤!",
|
||||
batch_editing_steps: "批量编辑步骤",
|
||||
wait_time_config: "超时时间设置",
|
||||
wait_element_timeout: "等待元素超时时间",
|
||||
more_config_options: "更多高级设置选项",
|
||||
updated_config_info: "更新后选项为",
|
||||
config_success: "配置成功",
|
||||
cmdFileUpload: "文件上传",
|
||||
relevant_file: "关联需要上传的文件",
|
||||
validate_tips: "判断实际的结果是否与期望的一致,可添加多条断言",
|
||||
instruction: "指令",
|
||||
screen_tip: "场景步骤如果触发原生弹窗(alert或prompt),或不存在页面时,截图不生效;",
|
||||
ele_css_attr: "元素CSS属性",
|
||||
ele_css_tip1: "如元素的 CSS 属性,color 属性,font-size 属性等",
|
||||
store_css_attr: "CSS属性(storeCssAttribute)",
|
||||
validate_type: "请选择断言方式",
|
||||
expect_value: "期望值",
|
||||
expect_title: "请输入期望的网页标题",
|
||||
title_tip: "断言当前窗口的标题是否跟期望值一致,完全匹配则断言成功,否则失败",
|
||||
input_var: "请输入变量",
|
||||
input_expect: "请输入期望值",
|
||||
var_tip: "断言变量与期望值是否匹配",
|
||||
confirm_after_success: "成功后是否点击确认按钮",
|
||||
input_expect_text: "请输入期望的弹窗文本",
|
||||
input_window_tip: "仅支持弹窗文本的断言,选择是,则断言成功后会点击弹窗上的确认按钮,选择否,则断言成功后不点击任何按钮",
|
||||
select_value: "所选元素的值等于期望(SelectedValue)",
|
||||
select_label: "下拉框选项显示的文本等于期望(SelectedLabel) ",
|
||||
not_select_value: "所选元素的值不等于期望(NotSelectedValue) ",
|
||||
assert_check: "元素被选中(Checked)",
|
||||
assert_editable: "元素可编辑(Editable)",
|
||||
assert_element_present: "元素存在(ElementPresent)",
|
||||
assert_element_not_present: "元素不存在(ElementNotPresent)",
|
||||
assert_element_not_checked: "元素未被选中(NotChecked)",
|
||||
assert_element_not_editable: "元素不可编辑(NotEditable)",
|
||||
assert_element_not_text: "元素文本不等于期望(NotText)",
|
||||
assert_text: "元素文本等于期望(Text)",
|
||||
assert_value: "元素值等于期望(Value)",
|
||||
script_tip: "仅支持js脚本,设置的脚本将在浏览器执行",
|
||||
script_type: "脚本类型",
|
||||
set_var: "设置变量",
|
||||
async: "异步",
|
||||
sync: "同步",
|
||||
return: "有返回值",
|
||||
no_return: "无返回值",
|
||||
sample_obj: "普通对象",
|
||||
is_string: "是否为字符串类型",
|
||||
like_string_tip: "如字符串、数字、json对象、数组等;",
|
||||
like_string_tip2: "注意:作为对象类型存储时如果不是一个合法的 js 对象类型(如非法特殊字符、空格影响),可能会生成报告失败。",
|
||||
ele_pro: "元素属性",
|
||||
like_ele_tip: "如元素的 name 属性,id 属性,value 属性等",
|
||||
xpath_locator: "xpath 路径",
|
||||
xpath_tip: "只支持 xpath 方式的元素定位,返回的是一个数值",
|
||||
store: "普通对象(store)",
|
||||
store_text: "元素文本(storeText)",
|
||||
store_value: "元素值(storeValue)",
|
||||
store_attr: "元素属性(storeAttribute)",
|
||||
store_xpath_count: "匹配 xpath 的元素数量(storeXpathCount)",
|
||||
store_window_handle: "窗口 Handle(storeWindowHandle)",
|
||||
store_title: "网页标题(storeTitle)",
|
||||
wait_time: "等待时间",
|
||||
per_tip: "启用性能模式后,将减少内存和cpu的占用,运行结果不展示步骤截图",
|
||||
fail_over: "失败终止",
|
||||
validate_tip: "勾选表示为硬断言(assert),如果断言失败,程序会终止。不勾选表示为软断言(verify),如果断言失败,程序不会终止。",
|
||||
scenario: "场景",
|
||||
extract_type: "请选择提取信息类型",
|
||||
input_handle_name: "请输入存储窗口 Handle 变量名",
|
||||
extract_tip: "将提取的内容保存到变量中",
|
||||
input_window_title: "请输入存储网页标题变量名",
|
||||
opt_ele: "操作元素",
|
||||
dst_ele: "目标元素",
|
||||
drag_type: "拖拽方式",
|
||||
drag_end_point: "被拖拽的元素最终的位置",
|
||||
revoke: "撤回",
|
||||
is_required: "是否必填",
|
||||
remark: "备注",
|
||||
result: "执行结果",
|
||||
var_step: "变量产生步骤",
|
||||
param_required_tip: "调试保存后,自动校验变量使用情况, 必填为自定义步骤内使用到的参数;非必填为自定义步骤内未使用到的冗余参数",
|
||||
name: "名称",
|
||||
parameter_configuration: "参数配置",
|
||||
enter_parameters: "入参",
|
||||
out_parameters: "出参",
|
||||
add_file: "添加文件",
|
||||
file: "文件: 【",
|
||||
been_deleted: "】 已被删除!请重新选择文件!",
|
||||
click_force: "强制点击",
|
||||
click_tip_3: "勾选,元素被遮挡,可强制点击",
|
||||
pls_sel_loctype: "请选择定位类型",
|
||||
pls_input_locator: "请填写定位",
|
||||
import_template: "导入模板",
|
||||
download_template: "下载模板",
|
||||
import_desc: "导入说明",
|
||||
el_import_tip_1: "1.如果导入的ID已存在,则更新元素;",
|
||||
el_import_tip_2: "2.如果导入的ID为空或ID不存在,则新增元素;",
|
||||
el_import: "元素导入",
|
||||
empty_text: "暂无数据",
|
||||
confirm_del_el: "确认删除元素 ",
|
||||
confirm_del: "确认删除 ",
|
||||
confirm_del_in : "确认删除指令 ",
|
||||
deng: "等 ",
|
||||
ge_instruction: "个指令 ?",
|
||||
ge_el: "个元素 ?",
|
||||
ge_scenario: " 个场景 ?",
|
||||
view_ref: "查看引用",
|
||||
unplanned_element: "未规划元素",
|
||||
scenario_instruction: "场景/指令",
|
||||
element_beening: "模块下的元素被",
|
||||
element_beening_desc: "元素被场景",
|
||||
reference: "引用",
|
||||
continue_or_not: "是否继续",
|
||||
continue_or_not_delete: "是否继续删除",
|
||||
unplanned_scenario: "未规划场景",
|
||||
unplanned_module: "未规划模块",
|
||||
confrim_del_node: "确定删除节点 ",
|
||||
and_all_sub_node: " 及其子节点下所有资源?",
|
||||
instruction_is_referenced: "指令被引用:",
|
||||
scenario_is_referenced: "场景被引用:",
|
||||
confirm_del_ins: "确认删除指令",
|
||||
confirm_del_scen: "确认删除场景",
|
||||
check_grid: "连接失败,请检查 selenium-grid 服务状态",
|
||||
check_grid_ip: "连接失败,请检查 selenium-grid 地址信息",
|
||||
view_config: "查看配置信息",
|
||||
config_ip: "没有检测到本地ip和端口信息,请在",
|
||||
personal_info: "个人信息",
|
||||
in_config: "中设置",
|
||||
assert_in_text: "元素文本包含期望(InText)",
|
||||
assert_not_in_text: "元素文本不包含期望(NotInText)",
|
||||
equal: "等于",
|
||||
not_equal: "不等于",
|
||||
contain: "包含",
|
||||
not_contain: "不包含",
|
||||
greater: "大于",
|
||||
greater_equal: "大于等于",
|
||||
lower: "小于",
|
||||
lower_equal: "小于等于",
|
||||
null: "空",
|
||||
not_null: "非空",
|
||||
assertion_configuration: "断言配置",
|
||||
},
|
||||
project_application: {
|
||||
workstation: {
|
||||
|
@ -3046,6 +3299,9 @@ const message = {
|
|||
scenario_title: "场景测试任务",
|
||||
ui_title: "UI测试任务",
|
||||
perf_title: "性能测试任务"
|
||||
},
|
||||
envrionment: {
|
||||
export_variable_tip: "导出接口测试变量"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1584,6 +1584,7 @@ const message = {
|
|||
environment_group_id: "環境組ID",
|
||||
select_environment: "請選擇環境",
|
||||
select_variable: "請選擇变量",
|
||||
select_api_variable: "請選擇接口測試变量",
|
||||
please_save_test: "請先保存測試",
|
||||
common_config: "通用配置",
|
||||
http_config: "HTTP配置",
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
package io.metersphere.controller.remote;
|
||||
|
||||
import io.metersphere.service.remote.UiTestService;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
@RestController
|
||||
@RequestMapping(path = {
|
||||
"/test/plan/uiScenario/case",
|
||||
"/ui/scenario/module",
|
||||
"/share/test/plan/uiScenario/case",
|
||||
"/ui/automation"
|
||||
})
|
||||
public class UiTestController {
|
||||
@Resource
|
||||
UiTestService uiTestService;
|
||||
|
||||
@PostMapping("/**")
|
||||
public Object list(HttpServletRequest request, @RequestBody Object param) {
|
||||
return uiTestService.post(request.getRequestURI(), param);
|
||||
}
|
||||
|
||||
@GetMapping("/**")
|
||||
public Object get(HttpServletRequest request) {
|
||||
return uiTestService.get(request.getRequestURI());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package io.metersphere.service.remote;
|
||||
|
||||
import io.metersphere.commons.constants.MicroServiceName;
|
||||
import io.metersphere.service.RemoteService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class UiTestService extends RemoteService {
|
||||
|
||||
public UiTestService() {
|
||||
super(MicroServiceName.UI_TEST);
|
||||
}
|
||||
}
|
|
@ -137,7 +137,7 @@ import MsTableOperatorButton from "metersphere-frontend/src/components/MsTableOp
|
|||
import MsTablePagination from "metersphere-frontend/src/components/pagination/TablePagination";
|
||||
import ApiEnvironmentConfig from "metersphere-frontend/src/components/environment/ApiEnvironmentConfig";
|
||||
import {Environment, parseEnvironment} from "metersphere-frontend/src/model/EnvironmentModel";
|
||||
import EnvironmentEdit from "metersphere-frontend/src/components/environment/EnvironmentEdit";
|
||||
import EnvironmentEdit from "./components/EnvironmentEdit";
|
||||
import MsAsideItem from "metersphere-frontend/src/components/MsAsideItem";
|
||||
import MsAsideContainer from "metersphere-frontend/src/components/MsAsideContainer";
|
||||
import ProjectSwitch from "metersphere-frontend/src/components/head/ProjectSwitch";
|
||||
|
|
|
@ -0,0 +1,553 @@
|
|||
<template>
|
||||
<el-main class="environment-edit" style="margin-left: 0px">
|
||||
<el-form :model="environment" :rules="rules" ref="environment" label-width="80px">
|
||||
<el-row>
|
||||
<el-col :span="10" v-if="!isProject">
|
||||
<el-form-item class="project-item" prop="currentProjectId" :label="$t('project.select')">
|
||||
<el-select v-model="environment.currentProjectId" filterable clearable
|
||||
size="small" :disabled="!ifCreate">
|
||||
<el-option v-for="item in projectList" :key="item.id" :label="item.name" :value="item.id"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="10">
|
||||
<el-form-item prop="name" :label="$t('api_test.environment.name')">
|
||||
<el-input v-model="environment.name" :disabled="isReadOnly" :placeholder="this.$t('commons.input_name')"
|
||||
clearable size="small"/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="4" v-if="!hideButton" :offset="isProject ? 10 : 0">
|
||||
<div style="float: right;width: fit-content;">
|
||||
<div style="float: left; margin-right: 8px;">
|
||||
<slot name="other"></slot>
|
||||
</div>
|
||||
<div class="ms_btn">
|
||||
<el-button type="primary" @click="confirm" @keydown.enter.native.prevent size="small">
|
||||
{{ $t('commons.confirm') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-tabs v-model="activeName">
|
||||
|
||||
<el-tab-pane :label="$t('api_test.environment.common_config')" name="common">
|
||||
<ms-environment-common-config :common-config="environment.config.commonConfig" ref="commonConfig"
|
||||
:is-read-only="isReadOnly"/>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane :label="$t('api_test.environment.http_config')" name="http">
|
||||
<ms-environment-http-config :project-id="environment.projectId" :http-config="environment.config.httpConfig"
|
||||
ref="httpConfig" :is-read-only="isReadOnly"/>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="$t('api_test.environment.database_config')" name="sql">
|
||||
<ms-database-config :configs="environment.config.databaseConfigs" :is-read-only="isReadOnly"/>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="$t('api_test.environment.tcp_config')" name="tcp">
|
||||
<ms-tcp-config :config="environment.config.tcpConfig" :is-read-only="isReadOnly"/>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="$t('commons.ssl.config')" name="ssl">
|
||||
<ms-environment-s-s-l-config :project-id="environment.projectId" :ssl-config="environment.config.sslConfig"
|
||||
:is-read-only="isReadOnly"/>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="$t('api_test.definition.request.all_pre_script')" name="prescript">
|
||||
<div style="padding-bottom: 20px;" v-if="!ifCreate">
|
||||
<el-link style="float: right;" type="primary" @click="openHis('preScript')">
|
||||
{{ $t('operating_log.change_history') }}
|
||||
</el-link>
|
||||
</div>
|
||||
<environment-global-script
|
||||
v-if="isRefresh && environment.config.globalScriptConfig && environment.config.preProcessor && environment.config.preStepProcessor"
|
||||
:filter-request.sync="environment.config.globalScriptConfig.filterRequestPreScript"
|
||||
:exec-after-private-script.sync="environment.config.globalScriptConfig.isPreScriptExecAfterPrivateScript"
|
||||
:conn-scenario.sync="environment.config.globalScriptConfig.connScenarioPreScript"
|
||||
:script-processor="environment.config.preProcessor"
|
||||
:scrpit-step-processor="environment.config.preStepProcessor"
|
||||
:is-pre-processor="true"
|
||||
:is-read-only="isReadOnly"
|
||||
@updateGlobalScript="updateGlobalScript"/>
|
||||
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="$t('api_test.definition.request.all_post_script')" name="postscript">
|
||||
<div style="padding-bottom: 20px;" v-if="!ifCreate">
|
||||
<el-link style="float: right;" type="primary" @click="openHis('postScript')">
|
||||
{{ $t('operating_log.change_history') }}
|
||||
</el-link>
|
||||
</div>
|
||||
<environment-global-script
|
||||
v-if="isRefresh && environment.config.globalScriptConfig && environment.config.postProcessor && environment.config.postStepProcessor"
|
||||
:filter-request.sync="environment.config.globalScriptConfig.filterRequestPostScript"
|
||||
:exec-after-private-script.sync="environment.config.globalScriptConfig.isPostScriptExecAfterPrivateScript"
|
||||
:conn-scenario.sync="environment.config.globalScriptConfig.connScenarioPostScript"
|
||||
:script-processor="environment.config.postProcessor"
|
||||
:scrpit-step-processor="environment.config.postStepProcessor"
|
||||
:is-pre-processor="false"
|
||||
:is-read-only="isReadOnly"
|
||||
@updateGlobalScript="updateGlobalScript"/>
|
||||
</el-tab-pane>
|
||||
<!-- 认证配置 -->
|
||||
<el-tab-pane :label="$t('api_test.definition.request.all_auth_config')" name="authConfig" v-if="isRefresh">
|
||||
<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.all_auth_config') }}</span>
|
||||
</el-tooltip>
|
||||
<ms-api-auth-config :is-read-only="isReadOnly" :request="environment.config.authManager"/>
|
||||
</el-tab-pane>
|
||||
<!--全局断言-->
|
||||
<el-tab-pane :label="$t('env_options.all_assertions')" name="assertions">
|
||||
<el-tooltip class="item-tabs" effect="dark" :content="$t('env_options.all_assertions')"
|
||||
placement="top-start" slot="label">
|
||||
<span>{{ $t('env_options.all_assertions') }}</span>
|
||||
</el-tooltip>
|
||||
<div v-if="hasLicense" style="margin-bottom: 15px">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item
|
||||
:label="$t('error_report_library.use_error_report')"
|
||||
prop="status" style="margin-bottom: 0px;">
|
||||
<el-switch :disabled="isReadOnly"
|
||||
v-model="environment.config.useErrorCode" style="margin-right: 10px"/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row v-show="environment.config.useErrorCode" :gutter="20">
|
||||
<el-col style="margin-left: 30px">
|
||||
{{ $t('error_report_library.conflict_with_success') }}
|
||||
<el-switch
|
||||
class="errorReportConfigSwitch"
|
||||
v-model="environment.config.higherThanSuccess"
|
||||
:active-text="$t('error_report_library.option.name')"
|
||||
:inactive-text="$t('api_test.automation.request_success')">
|
||||
</el-switch>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row v-show="environment.config.useErrorCode" :gutter="20">
|
||||
<el-col style="margin-left: 30px">
|
||||
{{ $t('error_report_library.conflict_with_error') }}
|
||||
<el-switch
|
||||
class="errorReportConfigSwitch"
|
||||
v-model="environment.config.higherThanError"
|
||||
:active-text="$t('error_report_library.option.name')"
|
||||
:inactive-text="$t('api_test.automation.request_error')">
|
||||
</el-switch>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
<global-assertions :is-read-only="isReadOnly" :assertions="environment.config.assertions"
|
||||
:is-show-json-path-suggest="false"/>
|
||||
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
</el-form>
|
||||
<ms-change-history ref="changeHistory"/>
|
||||
</el-main>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {editEnv} from "metersphere-frontend/src/api/environment";
|
||||
import MsApiScenarioVariables from "metersphere-frontend/src/components/environment/commons/ApiScenarioVariables";
|
||||
import MsApiKeyValue from "metersphere-frontend/src/components/environment/commons/ApiKeyValue";
|
||||
import MsDialogFooter from "metersphere-frontend/src/components/MsDialogFooter";
|
||||
import {REQUEST_HEADERS} from "metersphere-frontend/src/utils/constants";
|
||||
import {CommonConfig, Environment} from "metersphere-frontend/src/model/EnvironmentModel";
|
||||
import MsApiHostTable from "metersphere-frontend/src/components/environment/commons/ApiHostTable";
|
||||
import MsDatabaseConfig from "metersphere-frontend/src/components/environment/database/DatabaseConfig";
|
||||
import MsEnvironmentHttpConfig from "./EnvironmentHttpConfig";
|
||||
import MsEnvironmentCommonConfig from "metersphere-frontend/src/components/environment/EnvironmentCommonConfig";
|
||||
import MsEnvironmentSSLConfig from "metersphere-frontend/src/components/environment/EnvironmentSSLConfig";
|
||||
import MsApiAuthConfig from "metersphere-frontend/src/components/environment/auth/ApiAuthConfig";
|
||||
import MsTcpConfig from "metersphere-frontend/src/components/environment/tcp/TcpConfig";
|
||||
import {getUUID} from "metersphere-frontend/src/utils";
|
||||
import {hasLicense} from "metersphere-frontend/src/utils/permission";
|
||||
import MsChangeHistory from "metersphere-frontend/src/components/environment/history/EnvHistory";
|
||||
import MsDialogHeader from "metersphere-frontend/src/components/MsDialogHeader";
|
||||
import GlobalAssertions from "metersphere-frontend/src/components/environment/assertion/GlobalAssertions";
|
||||
import EnvironmentGlobalScript from "metersphere-frontend/src/components/environment/EnvironmentGlobalScript";
|
||||
|
||||
export default {
|
||||
name: "EnvironmentEdit",
|
||||
components: {
|
||||
MsTcpConfig,
|
||||
MsApiAuthConfig,
|
||||
MsEnvironmentCommonConfig,
|
||||
MsEnvironmentHttpConfig,
|
||||
MsEnvironmentSSLConfig,
|
||||
MsDatabaseConfig, MsApiHostTable, MsDialogFooter, MsApiKeyValue, MsApiScenarioVariables, MsChangeHistory,
|
||||
MsDialogHeader, GlobalAssertions, EnvironmentGlobalScript
|
||||
},
|
||||
props: {
|
||||
environment: new Environment(),
|
||||
projectId: String,
|
||||
isReadOnly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
hideButton: Boolean,
|
||||
ifCreate: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
projectList: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
isProject: Boolean
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
result: false,
|
||||
envEnable: false,
|
||||
isRefresh: true,
|
||||
rules: {
|
||||
name: [
|
||||
{required: true, message: this.$t('commons.input_name'), trigger: 'blur'},
|
||||
{max: 64, message: this.$t('commons.input_limit', [1, 64]), trigger: 'blur'}
|
||||
],
|
||||
currentProjectId: [
|
||||
{required: true, message: "", trigger: 'blur'},
|
||||
],
|
||||
},
|
||||
headerSuggestions: REQUEST_HEADERS,
|
||||
activeName: 'common'
|
||||
}
|
||||
},
|
||||
created() {
|
||||
if (!this.environment.config.preProcessor) {
|
||||
this.environment.config.preProcessor = {};
|
||||
}
|
||||
if (!this.environment.config.postProcessor) {
|
||||
this.environment.config.postProcessor = {};
|
||||
}
|
||||
if (!this.environment.config.preStepProcessor) {
|
||||
this.environment.config.preStepProcessor = {};
|
||||
}
|
||||
if (!this.environment.config.postStepProcessor) {
|
||||
this.environment.config.postStepProcessor = {};
|
||||
}
|
||||
if (!this.environment.config.globalScriptConfig) {
|
||||
this.environment.config.globalScriptConfig = {
|
||||
filterRequestPreScript: [],
|
||||
filterRequestPostScript: [],
|
||||
isPreScriptExecAfterPrivateScript: false,
|
||||
isPostScriptExecAfterPrivateScript: false,
|
||||
connScenarioPreScript: false,
|
||||
connScenarioPostScript: false,
|
||||
};
|
||||
}
|
||||
if (!this.environment.config.authManager) {
|
||||
this.environment.config.authManager = {'hashTree': []};
|
||||
}
|
||||
if (!this.environment.config.authManager.hashTree) {
|
||||
this.environment.config.authManager.hashTree = [];
|
||||
}
|
||||
if (!this.environment.config.assertions) {
|
||||
this.$set(this.environment.config, 'assertions', {
|
||||
duration: {duration: 0},
|
||||
regex: [],
|
||||
jsonPath: [],
|
||||
xpath2: [],
|
||||
jsr223: [],
|
||||
document: {type: "JSON", data: {json: [], xml: []}},
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
environment: function (o) {
|
||||
if (!this.environment.config.preProcessor) {
|
||||
this.environment.config.preProcessor = {};
|
||||
if (!this.environment.config.preProcessor.script) {
|
||||
this.environment.config.preProcessor.script = "";
|
||||
}
|
||||
}
|
||||
if (!this.environment.config.postProcessor) {
|
||||
this.environment.config.postProcessor = {};
|
||||
if (!this.environment.config.postProcessor.script) {
|
||||
this.environment.config.postProcessor.script = "";
|
||||
}
|
||||
}
|
||||
if (!this.environment.config.preStepProcessor) {
|
||||
this.environment.config.preStepProcessor = {};
|
||||
}
|
||||
if (!this.environment.config.postStepProcessor) {
|
||||
this.environment.config.postStepProcessor = {};
|
||||
}
|
||||
if (!this.environment.config.globalScriptConfig) {
|
||||
this.environment.config.globalScriptConfig = {
|
||||
filterRequestPreScript: [],
|
||||
filterRequestPostScript: [],
|
||||
isPreScriptExecAfterPrivateScript: false,
|
||||
isPostScriptExecAfterPrivateScript: false,
|
||||
connScenarioPreScript: false,
|
||||
connScenarioPostScript: false,
|
||||
};
|
||||
}
|
||||
|
||||
if (!this.environment.config.authManager) {
|
||||
this.environment.config.authManager = {'hashTree': []};
|
||||
}
|
||||
if (!this.environment.config.authManager.hashTree) {
|
||||
this.environment.config.authManager.hashTree = [];
|
||||
}
|
||||
if (!this.environment.config.assertions) {
|
||||
this.$set(this.environment.config, 'assertions', {
|
||||
duration: {duration: 0},
|
||||
regex: [],
|
||||
jsonPath: [],
|
||||
xpath2: [],
|
||||
jsr223: [],
|
||||
document: {type: "JSON", data: {json: [], xml: []}},
|
||||
});
|
||||
}
|
||||
|
||||
this.isRefresh = false;
|
||||
this.$nextTick(() => {
|
||||
this.isRefresh = true;
|
||||
});
|
||||
this.envEnable = o.enable;
|
||||
},
|
||||
//当创建及复制环境所选择的项目变化时,改变当前环境对应的projectId
|
||||
'environment.currentProjectId'() {
|
||||
// el-select什么都不选时值为'',为''的话也会被当成有效的projectId传给后端,转化使其无效
|
||||
if (!this.environment.currentProjectId) {
|
||||
this.environment.projectId = null;
|
||||
} else {
|
||||
this.environment.projectId = this.environment.currentProjectId;
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
hasLicense() {
|
||||
let license = hasLicense();
|
||||
return license;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
updateGlobalScript(isPreScript, filedName, value) {
|
||||
if (isPreScript) {
|
||||
if (filedName === "connScenario") {
|
||||
this.environment.config.globalScriptConfig.connScenarioPreScript = value;
|
||||
} else if (filedName === "execAfterPrivateScript") {
|
||||
this.environment.config.globalScriptConfig.isPreScriptExecAfterPrivateScript = value;
|
||||
} else if (filedName === "filterRequest") {
|
||||
this.environment.config.globalScriptConfig.filterRequestPreScript = value;
|
||||
}
|
||||
} else {
|
||||
if (filedName === "connScenario") {
|
||||
this.environment.config.globalScriptConfig.connScenarioPostScript = value;
|
||||
} else if (filedName === "execAfterPrivateScript") {
|
||||
this.environment.config.globalScriptConfig.isPostScriptExecAfterPrivateScript = value;
|
||||
} else if (filedName === "filterRequest") {
|
||||
this.environment.config.globalScriptConfig.filterRequestPostScript = value;
|
||||
}
|
||||
}
|
||||
},
|
||||
save() {
|
||||
this.$refs['environment'].validate((valid) => {
|
||||
if (valid && this.$refs.commonConfig.validate() && this.$refs.httpConfig.validate()) {
|
||||
this._save(this.environment);
|
||||
}
|
||||
});
|
||||
},
|
||||
openHis(logType) {
|
||||
this.$refs.changeHistory.open(this.environment.id, ["项目-环境设置", "項目-環境設置", "Project environment setting"], logType);
|
||||
},
|
||||
validate() {
|
||||
let isValidate = false;
|
||||
this.$refs['environment'].validate((valid) => {
|
||||
if (valid && this.$refs.commonConfig.validate() && this.$refs.httpConfig.validate()) {
|
||||
isValidate = true;
|
||||
} else {
|
||||
isValidate = false;
|
||||
}
|
||||
});
|
||||
return isValidate;
|
||||
},
|
||||
geFiles(obj) {
|
||||
let uploadFiles = [];
|
||||
obj.uploadIds = [];
|
||||
if (obj.config && obj.config.sslConfig && obj.config.sslConfig.files) {
|
||||
obj.config.sslConfig.files.forEach(item => {
|
||||
if (item.file && item.file.size > 0) {
|
||||
if (!item.id) {
|
||||
item.name = item.file.name;
|
||||
item.id = getUUID();
|
||||
}
|
||||
obj.uploadIds.push(item.id);
|
||||
uploadFiles.push(item.file);
|
||||
}
|
||||
})
|
||||
}
|
||||
return uploadFiles;
|
||||
},
|
||||
getVariablesFiles(obj) {
|
||||
let variablesFiles = [];
|
||||
obj.variablesFilesIds = [];
|
||||
// 场景变量csv 文件
|
||||
if (obj.config.commonConfig.variables) {
|
||||
obj.config.commonConfig.variables.forEach(param => {
|
||||
if (param.type === 'CSV' && param.files) {
|
||||
param.files.forEach(item => {
|
||||
if (item.file && item.file.name) {
|
||||
if (!item.id) {
|
||||
let fileId = getUUID().substring(0, 12);
|
||||
item.name = item.file.name;
|
||||
item.id = fileId;
|
||||
}
|
||||
obj.variablesFilesIds.push(item.id);
|
||||
variablesFiles.push(item.file);
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
}
|
||||
return variablesFiles;
|
||||
},
|
||||
check(items) {
|
||||
let repeatKey = "";
|
||||
items.forEach((item, index) => {
|
||||
items.forEach((row, rowIndex) => {
|
||||
if (item.name === row.name && index !== rowIndex) {
|
||||
repeatKey = item.name;
|
||||
}
|
||||
});
|
||||
});
|
||||
return repeatKey;
|
||||
},
|
||||
_save(environment) {
|
||||
if (!environment.projectId) {
|
||||
this.$warning(this.$t('api_test.select_project'));
|
||||
return;
|
||||
}
|
||||
if (environment && environment.config && environment.config.commonConfig && environment.config.commonConfig.variables) {
|
||||
let repeatKey = this.check(environment.config.commonConfig && environment.config.commonConfig.variables);
|
||||
if (repeatKey !== "") {
|
||||
this.$warning(this.$t('api_test.environment.common_config') + "【" + repeatKey + "】" + this.$t('load_test.param_is_duplicate'));
|
||||
return;
|
||||
}
|
||||
}
|
||||
let message = '';
|
||||
if (environment && environment.config && environment.config.httpConfig && environment.config.httpConfig.conditions) {
|
||||
environment.config.httpConfig.conditions.forEach(env => {
|
||||
if (env.type === "MODULE" && env.details.length === 0) {
|
||||
message += this.$t('load_test.domain') + ":" + env.socket + ":" + this.$t('api_test.environment.module_warning');
|
||||
return;
|
||||
}
|
||||
if (env.type === "PATH" && env.details) {
|
||||
env.details.forEach(item => {
|
||||
if (!item.name) {
|
||||
message += this.$t('load_test.domain') + ":" + env.socket + ":" + this.$t('api_test.environment.path_warning');
|
||||
return;
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
environment.config.commonConfig.variables.forEach(variable => {
|
||||
if (variable.type === 'CSV' && variable.files.length === 0) {
|
||||
message = this.$t('api_test.automation.csv_warning');
|
||||
return;
|
||||
}
|
||||
})
|
||||
if (message) {
|
||||
this.$warning(message);
|
||||
return;
|
||||
}
|
||||
|
||||
let bodyFiles = this.geFiles(environment);
|
||||
let variablesFiles = this.getVariablesFiles(environment);
|
||||
let formData = new FormData();
|
||||
if (bodyFiles) {
|
||||
bodyFiles.forEach(f => {
|
||||
formData.append("files", f);
|
||||
})
|
||||
}
|
||||
if (variablesFiles) {
|
||||
variablesFiles.forEach(f => {
|
||||
formData.append("variablesFiles", f);
|
||||
})
|
||||
}
|
||||
let param = this.buildParam(environment);
|
||||
formData.append('request', new Blob([JSON.stringify(param)], {type: "application/json"}));
|
||||
editEnv(formData, param).then((response) => {
|
||||
this.$success(this.$t('commons.save_success'));
|
||||
this.$emit('refreshAfterSave'); //在EnvironmentList.vue中监听,使在数据修改后进行刷新
|
||||
this.cancel()
|
||||
}, error => {
|
||||
this.$emit('errorRefresh', error);
|
||||
});
|
||||
|
||||
},
|
||||
buildParam: function (environment) {
|
||||
let param = {};
|
||||
Object.assign(param, environment);
|
||||
let hosts = param.config.commonConfig.hosts;
|
||||
if (hosts != undefined) {
|
||||
let validHosts = [];
|
||||
// 去除掉未确认的host
|
||||
hosts.forEach(host => {
|
||||
if (host.status === '') {
|
||||
validHosts.push(host);
|
||||
}
|
||||
});
|
||||
param.config.commonConfig.hosts = validHosts;
|
||||
}
|
||||
param.config = JSON.stringify(param.config);
|
||||
return param;
|
||||
},
|
||||
cancel() {
|
||||
this.$emit('close');
|
||||
},
|
||||
confirm() {
|
||||
this.$emit("confirm");
|
||||
},
|
||||
clearValidate() {
|
||||
this.$refs["environment"].clearValidate();
|
||||
},
|
||||
initVariables() {
|
||||
if (!this.environment.config.commonConfig) {
|
||||
this.$set(this.environment.config, 'commonConfig', new CommonConfig());
|
||||
} else {
|
||||
if (this.environment.config.commonConfig.variables) {
|
||||
this.environment.config.commonConfig.variables.forEach(v => {
|
||||
if (!v.scope) {
|
||||
this.$set(v, "scope", "api");
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.ms-opt-btn {
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
margin-top: 28px;
|
||||
}
|
||||
|
||||
span {
|
||||
display: block;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
span:not(:first-child) {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.errorReportConfigSwitch :deep(.el-switch__label) {
|
||||
color: #D8DAE2;
|
||||
}
|
||||
|
||||
.errorReportConfigSwitch :deep(.is-active) {
|
||||
color: var(--count_number);
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,583 @@
|
|||
<template>
|
||||
|
||||
<el-form :model="condition" :rules="rules" ref="httpConfig" class="ms-el-form-item__content" :disabled="isReadOnly">
|
||||
<div class="ms-border">
|
||||
<el-form-item prop="socket">
|
||||
<el-row type="flex" justify="space-between">
|
||||
<el-col :span="14">
|
||||
<span class="ms-env-span" style="line-height: 30px;">{{ $t('api_test.environment.socket') }}</span>
|
||||
<el-input v-model="condition.socket" style="width: 85%"
|
||||
:placeholder="$t('api_test.request.url_description')" clearable size="small">
|
||||
<template slot="prepend">
|
||||
<el-select v-model="condition.protocol" class="request-protocol-select" size="small">
|
||||
<el-option label="http://" value="http"/>
|
||||
<el-option label="https://" value="https"/>
|
||||
</el-select>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-col>
|
||||
<el-col :span="10">
|
||||
<span style="margin-right: 12px; line-height: 30px;">{{ $t('commons.description') }}</span>
|
||||
<el-input v-model="condition.description" maxlength="200" :show-word-limit="true" size="small"
|
||||
style="width: 70%;"/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form-item>
|
||||
<el-form-item prop="enable">
|
||||
<span class="ms-env-span">{{ $t('api_test.environment.condition_enable') }}</span>
|
||||
<el-radio-group v-model="condition.type" @change="typeChange">
|
||||
<el-radio label="NONE">{{ $t('api_test.definition.document.data_set.none') }}</el-radio>
|
||||
<el-radio label="MODULE">{{ $t('test_track.module.module') }}</el-radio>
|
||||
<el-radio label="PATH">{{ $t('api_test.definition.api_path') }}</el-radio>
|
||||
</el-radio-group>
|
||||
<div v-if="condition.type === 'MODULE'" style="margin-top: 6px">
|
||||
<ms-select-tree size="small" :data="moduleOptions" :default-key="condition.ids" @getValue="setModule"
|
||||
:obj="moduleObj" clearable :checkStrictly="true" multiple v-if="!loading"/>
|
||||
</div>
|
||||
<div v-if="condition.type === 'PATH'" style="margin-top: 6px">
|
||||
<el-input v-model="pathDetails.name" :placeholder="$t('api_test.value')" clearable size="small">
|
||||
<template v-slot:prepend>
|
||||
<el-select v-model="pathDetails.value" class="request-protocol-select" size="small">
|
||||
<el-option :label="$t('api_test.request.assertions.contains')" value="contains"/>
|
||||
<el-option :label="$t('commons.adv_search.operators.equals')" value="equals"/>
|
||||
</el-select>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
|
||||
<!-- 接口测试配置 -->
|
||||
<form-section :title="$t('commons.api')" :init-active=true>
|
||||
<p>{{ $t('api_test.request.headers') }}</p>
|
||||
<el-row>
|
||||
<el-link class="ms-el-link" @click="batchAdd" style="color: #783887"> {{
|
||||
$t("commons.batch_add")
|
||||
}}
|
||||
</el-link>
|
||||
</el-row>
|
||||
<ms-api-key-value :items="condition.headers" :isShowEnable="true" :suggestions="headerSuggestions"/>
|
||||
<div style="margin-top: 20px">
|
||||
<el-button v-if="!condition.id" type="primary" style="float: right" size="mini" @click="add">
|
||||
{{ $t('commons.add') }}
|
||||
</el-button>
|
||||
<div v-else>
|
||||
<el-button type="primary" style="float: right;margin-left: 10px" size="mini" @click="clear">
|
||||
{{ $t('commons.clear') }}
|
||||
</el-button>
|
||||
<el-button type="primary" style="float: right" size="mini" @click="update(condition)">{{
|
||||
$t('commons.update')
|
||||
}}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</form-section>
|
||||
|
||||
<!-- UI 配置 -->
|
||||
<form-section :title="$t('commons.ui_test')" :init-active="false">
|
||||
<el-row :gutter="10" style="padding-top: 10px;">
|
||||
<el-col :span="6">
|
||||
<!-- 浏览器驱动 -->
|
||||
<span style="margin-right: 10px;">{{$t("ui.browser")}}</span>
|
||||
<el-select
|
||||
size="mini"
|
||||
v-model="httpConfig.browser"
|
||||
style="width: 100px"
|
||||
>
|
||||
<el-option
|
||||
v-for="b in browsers"
|
||||
:key="b.value"
|
||||
:value="b.value"
|
||||
:label="b.label"
|
||||
></el-option>
|
||||
</el-select>
|
||||
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<!-- 性能模式 -->
|
||||
<el-checkbox
|
||||
v-model="httpConfig.headlessEnabled"
|
||||
>
|
||||
<span> {{ $t("ui.performance_mode") }}</span>
|
||||
</el-checkbox>
|
||||
<ms-instructions-icon size="10" :content="$t('ui.per_tip')"/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="24">
|
||||
<ms-ui-scenario-cookie-table :items="httpConfig.cookie"/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</form-section>
|
||||
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div class="ms-border">
|
||||
<el-table :data="httpConfig.conditions" highlight-current-row @current-change="selectRow" ref="envTable">
|
||||
<el-table-column prop="socket" :label="$t('load_test.domain')" show-overflow-tooltip width="180">
|
||||
<template v-slot:default="{row}">
|
||||
{{ getUrl(row) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="type" :label="$t('api_test.environment.condition_enable')" show-overflow-tooltip
|
||||
min-width="100px">
|
||||
<template v-slot:default="{row}">
|
||||
{{ getName(row) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="details" show-overflow-tooltip min-width="120px" :label="$t('api_test.value')">
|
||||
<template v-slot:default="{row}">
|
||||
{{ getDetails(row) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" show-overflow-tooltip min-width="120px" :label="$t('commons.create_time')">
|
||||
<template v-slot:default="{row}">
|
||||
<span>{{ row.time | datetimeFormat }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="description" show-overflow-tooltip min-width="120px" :label="$t('commons.description')">
|
||||
<template v-slot:default="{row}">
|
||||
{{ row.description }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('commons.operating')" width="100px">
|
||||
<template v-slot:default="{row}">
|
||||
<div>
|
||||
<ms-table-operator-button :tip="$t('api_test.automation.copy')"
|
||||
icon="el-icon-document-copy" @exec="copy(row)"/>
|
||||
<ms-table-operator-button :tip="$t('api_test.automation.remove')"
|
||||
icon="el-icon-delete" @exec="remove(row)" type="danger"/>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<batch-add-parameter @batchSave="batchSave" ref="batchAdd"/>
|
||||
</div>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {HttpConfig} from "metersphere-frontend/src/model/EnvironmentModel";
|
||||
import {getApiModuleByProjectIdAndProtocol} from "metersphere-frontend/src/api/environment";
|
||||
import MsApiKeyValue from "metersphere-frontend/src/components/environment/commons/ApiKeyValue";
|
||||
import {REQUEST_HEADERS} from "metersphere-frontend/src/utils/constants";
|
||||
import MsSelectTree from "metersphere-frontend/src/components/select-tree/SelectTree";
|
||||
import MsTableOperatorButton from "metersphere-frontend/src/components/MsTableOperatorButton";
|
||||
import {getUUID} from "metersphere-frontend/src/utils";
|
||||
import {KeyValue} from "metersphere-frontend/src/model/EnvTestModel";
|
||||
import Vue from "vue";
|
||||
import BatchAddParameter from "metersphere-frontend/src/components/environment/commons/BatchAddParameter";
|
||||
import FormSection from "metersphere-frontend/src/components/form/FormSection";
|
||||
import MsInstructionsIcon from 'metersphere-frontend/src/components/MsInstructionsIcon';
|
||||
import MsUiScenarioCookieTable from "./ui-related/UiScenarioCookieTable";
|
||||
|
||||
export default {
|
||||
name: "MsEnvironmentHttpConfig",
|
||||
components: {
|
||||
MsUiScenarioCookieTable,
|
||||
FormSection, MsApiKeyValue, MsSelectTree, MsTableOperatorButton, BatchAddParameter, MsInstructionsIcon},
|
||||
props: {
|
||||
httpConfig: new HttpConfig({cookie: []}),
|
||||
projectId: String,
|
||||
isReadOnly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.list();
|
||||
if (this.httpConfig && !this.httpConfig.cookie) {
|
||||
this.$set(this.httpConfig, "cookie", []);
|
||||
}
|
||||
},
|
||||
data() {
|
||||
let socketValidator = (rule, value, callback) => {
|
||||
if (!this.validateSocket(value)) {
|
||||
callback(new Error(this.$t("commons.formatErr")));
|
||||
return false;
|
||||
} else {
|
||||
callback();
|
||||
return true;
|
||||
}
|
||||
};
|
||||
return {
|
||||
headerSuggestions: REQUEST_HEADERS,
|
||||
rules: {
|
||||
socket: [{required: false, validator: socketValidator, trigger: "blur"}],
|
||||
},
|
||||
moduleOptions: [],
|
||||
moduleObj: {
|
||||
id: "id",
|
||||
label: "name",
|
||||
},
|
||||
loading: false,
|
||||
pathDetails: new KeyValue({name: "", value: "contains"}),
|
||||
condition: {
|
||||
type: "NONE",
|
||||
details: [new KeyValue({name: "", value: "contains"})],
|
||||
protocol: "http",
|
||||
socket: "",
|
||||
domain: "",
|
||||
port: 0,
|
||||
headers: [new KeyValue()],
|
||||
headlessEnabled: true,
|
||||
browser : 'CHROME'
|
||||
},
|
||||
beforeCondition: {},
|
||||
browsers: [
|
||||
{
|
||||
label: this.$t("chrome"),
|
||||
value: "CHROME",
|
||||
},
|
||||
{
|
||||
label: this.$t("firefox"),
|
||||
value: "FIREFOX",
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
projectId() {
|
||||
this.list();
|
||||
},
|
||||
httpConfig: function (o) {
|
||||
// 历史数据处理
|
||||
if (this.httpConfig && this.httpConfig.socket && this.httpConfig.conditions && this.httpConfig.conditions.length === 0) {
|
||||
this.condition.type = "NONE";
|
||||
this.condition.socket = this.httpConfig.socket;
|
||||
this.condition.protocol = this.httpConfig.protocol;
|
||||
this.condition.port = this.httpConfig.port;
|
||||
this.condition.domain = this.httpConfig.domain;
|
||||
this.condition.time = new Date().getTime();
|
||||
this.condition.headers = this.httpConfig.headers;
|
||||
this.condition.description = this.httpConfig.description;
|
||||
this.add();
|
||||
}
|
||||
this.condition = {
|
||||
id: undefined,
|
||||
type: "NONE",
|
||||
details: [new KeyValue({name: "", value: "contains"})],
|
||||
protocol: "http",
|
||||
socket: "",
|
||||
domain: "",
|
||||
port: 0,
|
||||
headers: [new KeyValue()]
|
||||
};
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getUrl(row) {
|
||||
return row.protocol + "://" + row.socket;
|
||||
},
|
||||
getName(row) {
|
||||
switch (row.type) {
|
||||
case "NONE":
|
||||
return this.$t("api_test.definition.document.data_set.none");
|
||||
case "MODULE":
|
||||
return this.$t("test_track.module.module");
|
||||
case "PATH":
|
||||
return this.$t("api_test.definition.api_path");
|
||||
}
|
||||
},
|
||||
clearHisData() {
|
||||
this.httpConfig.socket = undefined;
|
||||
this.httpConfig.protocol = undefined;
|
||||
this.httpConfig.port = undefined;
|
||||
this.httpConfig.domain = undefined;
|
||||
},
|
||||
getDetails(row) {
|
||||
if (row && row.type === "MODULE") {
|
||||
if (row.details && row.details instanceof Array) {
|
||||
let value = "";
|
||||
row.details.forEach((item) => {
|
||||
value += item.name + ",";
|
||||
});
|
||||
if (value.endsWith(",")) {
|
||||
value = value.substr(0, value.length - 1);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
} else if (row && row.type === "PATH" && row.details.length > 0 && row.details[0].name) {
|
||||
return row.details[0].value === "equals" ? this.$t("commons.adv_search.operators.equals") + row.details[0].name : this.$t("api_test.request.assertions.contains") + row.details[0].name;
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
},
|
||||
selectRow(row) {
|
||||
this.condition = {
|
||||
type: "NONE",
|
||||
details: [new KeyValue({name: "", value: "contains"})],
|
||||
protocol: "http",
|
||||
socket: "",
|
||||
domain: "",
|
||||
port: 0,
|
||||
headers: [new KeyValue()]
|
||||
};
|
||||
if (row) {
|
||||
this.httpConfig.socket = row.socket;
|
||||
this.httpConfig.protocol = row.protocol;
|
||||
this.httpConfig.port = row.port;
|
||||
this.httpConfig.description = row.description;
|
||||
this.condition = row;
|
||||
if (!this.condition.headers) {
|
||||
this.condition.headers = [new KeyValue()];
|
||||
}
|
||||
if (row.type === "PATH" && row.details.length > 0) {
|
||||
this.pathDetails = JSON.parse(JSON.stringify(row.details[0]));
|
||||
} else if (row.type === "MODULE" && row.details.length > 0) {
|
||||
this.condition.ids = [];
|
||||
row.details.forEach((item) => {
|
||||
this.condition.ids.push(item.value);
|
||||
});
|
||||
}
|
||||
}
|
||||
this.beforeCondition = JSON.parse(JSON.stringify(this.condition));
|
||||
this.reload();
|
||||
},
|
||||
typeChange() {
|
||||
if (this.condition.type === "NONE" && this.condition.id && this.checkNode(this.condition.id)) {
|
||||
this.condition.type = this.beforeCondition.type;
|
||||
this.$warning(this.$t('api_test.environment.repeat_warning'));
|
||||
return;
|
||||
}
|
||||
switch (this.condition.type) {
|
||||
case "NONE":
|
||||
this.condition.details = [];
|
||||
break;
|
||||
case "MODULE":
|
||||
this.condition.details = [];
|
||||
break;
|
||||
case "PATH":
|
||||
this.pathDetails = new KeyValue({name: "", value: "contains"});
|
||||
break;
|
||||
}
|
||||
},
|
||||
list() {
|
||||
if (this.projectId) {
|
||||
this.result = getApiModuleByProjectIdAndProtocol(this.projectId, "HTTP").then((response) => {
|
||||
if (response.data && response.data !== null) {
|
||||
this.moduleOptions = response.data;
|
||||
}
|
||||
});
|
||||
} else { //创建环境时一开始没有传入projectId,就不请求数据了
|
||||
this.moduleOptions = [];
|
||||
}
|
||||
},
|
||||
setModule(id, data) {
|
||||
if (data && data.length > 0) {
|
||||
this.condition.details = [];
|
||||
data.forEach((item) => {
|
||||
this.condition.details.push(new KeyValue({name: item.name, value: item.id}));
|
||||
});
|
||||
}
|
||||
},
|
||||
update() {
|
||||
const index = this.httpConfig.conditions.findIndex((d) => d.id === this.condition.id);
|
||||
this.validateSocket(this.condition.socket);
|
||||
let obj = {
|
||||
id: this.condition.id,
|
||||
type: this.condition.type,
|
||||
domain: this.condition.domain,
|
||||
socket: this.condition.socket,
|
||||
headers: this.condition.headers,
|
||||
protocol: this.condition.protocol,
|
||||
port: this.condition.port,
|
||||
time: this.condition.time
|
||||
};
|
||||
if (obj.type === "PATH") {
|
||||
this.httpConfig.conditions[index].details = [this.pathDetails];
|
||||
} else {
|
||||
obj.details = this.condition.details ? JSON.parse(JSON.stringify(this.condition.details)) : this.condition.details;
|
||||
}
|
||||
if (index !== -1) {
|
||||
Vue.set(this.httpConfig.conditions[index], obj, 1);
|
||||
this.condition = {
|
||||
type: "NONE",
|
||||
details: [new KeyValue({name: "", value: "contains"})],
|
||||
protocol: "http",
|
||||
socket: "",
|
||||
domain: "",
|
||||
headers: [new KeyValue()]
|
||||
};
|
||||
this.reload();
|
||||
}
|
||||
this.$refs.envTable.setCurrentRow(0);
|
||||
},
|
||||
clear() {
|
||||
this.condition = {
|
||||
type: "NONE",
|
||||
details: [new KeyValue({name: "", value: "contains"})],
|
||||
protocol: "http",
|
||||
socket: "",
|
||||
domain: "",
|
||||
headers: [new KeyValue()]
|
||||
};
|
||||
this.$refs.envTable.setCurrentRow(0);
|
||||
},
|
||||
reload() {
|
||||
this.loading = true
|
||||
this.$nextTick(() => {
|
||||
this.loading = false
|
||||
});
|
||||
},
|
||||
checkNode(id) {
|
||||
let index = 1;
|
||||
this.httpConfig.conditions.forEach(item => {
|
||||
if (item.type === "NONE") {
|
||||
if (!id || id !== item.id) {
|
||||
index++;
|
||||
}
|
||||
}
|
||||
})
|
||||
return index > 1;
|
||||
},
|
||||
add() {
|
||||
if (this.condition.type === "NONE" && this.checkNode()) {
|
||||
this.$warning(this.$t('api_test.environment.repeat_warning'));
|
||||
return;
|
||||
}
|
||||
this.validateSocket();
|
||||
let obj = {
|
||||
id: getUUID(),
|
||||
type: this.condition.type,
|
||||
socket: this.condition.socket,
|
||||
protocol: this.condition.protocol,
|
||||
headers: this.condition.headers,
|
||||
domain: this.condition.domain,
|
||||
port: this.condition.port,
|
||||
time: new Date().getTime(),
|
||||
description: this.condition.description
|
||||
};
|
||||
if (this.condition.type === "PATH") {
|
||||
obj.details = [JSON.parse(JSON.stringify(this.pathDetails))];
|
||||
} else {
|
||||
obj.details = this.condition.details ? JSON.parse(JSON.stringify(this.condition.details)) : this.condition.details;
|
||||
}
|
||||
this.httpConfig.conditions.unshift(obj);
|
||||
this.clearHisData();
|
||||
},
|
||||
remove(row) {
|
||||
const index = this.httpConfig.conditions.findIndex((d) => d.id === row.id);
|
||||
this.httpConfig.conditions.splice(index, 1);
|
||||
this.clearHisData();
|
||||
},
|
||||
copy(row) {
|
||||
if (row.type === "NONE") {
|
||||
this.$warning(this.$t('api_test.environment.copy_warning'));
|
||||
return;
|
||||
}
|
||||
const index = this.httpConfig.conditions.findIndex((d) => d.id === row.id);
|
||||
let obj = {
|
||||
id: getUUID(),
|
||||
type: row.type,
|
||||
socket: row.socket,
|
||||
details: row.details,
|
||||
protocol: row.protocol,
|
||||
headers: JSON.parse(JSON.stringify(row.headers)),
|
||||
domain: row.domain,
|
||||
time: new Date().getTime()
|
||||
};
|
||||
if (index != -1) {
|
||||
this.httpConfig.conditions.splice(index, 0, obj);
|
||||
} else {
|
||||
this.httpConfig.conditions.push(obj);
|
||||
}
|
||||
},
|
||||
validateSocket(socket) {
|
||||
if (!socket) return true;
|
||||
let urlStr = this.condition.protocol + "://" + socket;
|
||||
let url = {};
|
||||
try {
|
||||
url = new URL(urlStr);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
this.condition.domain = decodeURIComponent(url.hostname);
|
||||
|
||||
this.condition.port = url.port;
|
||||
let path = url.pathname === "/" ? "" : url.pathname;
|
||||
if (url.port) {
|
||||
this.condition.socket = this.condition.domain + ":" + url.port + path;
|
||||
} else {
|
||||
this.condition.socket = this.condition.domain + path;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
validate() {
|
||||
let isValidate = false;
|
||||
this.$refs["httpConfig"].validate((valid) => {
|
||||
isValidate = valid;
|
||||
});
|
||||
return isValidate;
|
||||
},
|
||||
batchAdd() {
|
||||
this.$refs.batchAdd.open();
|
||||
},
|
||||
_handleBatchVars(data) {
|
||||
let params = data.split("\n");
|
||||
let keyValues = [];
|
||||
params.forEach(item => {
|
||||
if (item) {
|
||||
let line = item.split(/:|:/);
|
||||
let values = item.split(line[0] + ":");
|
||||
let required = false;
|
||||
keyValues.push(new KeyValue({
|
||||
name: line[0],
|
||||
required: required,
|
||||
value: values[1],
|
||||
type: "text",
|
||||
valid: false,
|
||||
file: false,
|
||||
encode: true,
|
||||
enable: true,
|
||||
contentType: "text/plain"
|
||||
}));
|
||||
}
|
||||
});
|
||||
return keyValues;
|
||||
},
|
||||
batchSave(data) {
|
||||
if (data) {
|
||||
let keyValues = this._handleBatchVars(data);
|
||||
keyValues.forEach(keyValue => {
|
||||
let isAdd = true;
|
||||
for (let i in this.condition.headers) {
|
||||
let item = this.condition.headers[i];
|
||||
if (item.name === keyValue.name) {
|
||||
item.value = keyValue.value;
|
||||
isAdd = false;
|
||||
}
|
||||
}
|
||||
if (isAdd) {
|
||||
this.condition.headers.splice(this.condition.headers.indexOf(h => !h.name), 0, keyValue);
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.request-protocol-select {
|
||||
width: 90px;
|
||||
}
|
||||
|
||||
.ms-env-span {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
:deep(.el-form-item) {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.ms-el-form-item__content :deep(.el-form-item__content) {
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.ms-el-link {
|
||||
float: right;
|
||||
margin-right: 45px;
|
||||
}
|
||||
|
||||
</style>
|
|
@ -0,0 +1,28 @@
|
|||
<template>
|
||||
<div>
|
||||
<ms-tag v-if="value == 'Prepare'" type="info" :content="$t('test_track.plan.plan_status_prepare')"/>
|
||||
<ms-tag v-if="value == 'Underway'" type="primary" :content="$t('test_track.plan.plan_status_running')"/>
|
||||
<ms-tag v-if="value == 'Finished'" type="warning" :content="$t('test_track.plan.plan_status_finished')"/>
|
||||
<ms-tag v-if="value == 'Completed'" type="success" :content="$t('test_track.plan.plan_status_completed')"/>
|
||||
<ms-tag v-if="value === 'Trash'" type="danger" effect="plain" :content="$t('test_track.plan.plan_status_trash')"/>
|
||||
<ms-tag v-if="value == 'Archived'" type="danger" :content="$t('test_track.plan.plan_status_archived')"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MsTag from "metersphere-frontend/src/components/MsTag";
|
||||
|
||||
export default {
|
||||
name: "PlanStatusTableItem",
|
||||
components: {MsTag},
|
||||
props: {
|
||||
value: {
|
||||
type: String
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,71 @@
|
|||
<template>
|
||||
<el-button-group>
|
||||
<el-tooltip
|
||||
v-for="item in tabList"
|
||||
:key="item.domKey"
|
||||
class="item"
|
||||
effect="dark"
|
||||
:content="item.tip"
|
||||
:placement="item.placement"
|
||||
>
|
||||
<el-button
|
||||
plain
|
||||
:class="{ active: currentActiveDom === item.domKey }"
|
||||
@click="changeTab(item.domKey)"
|
||||
>{{ item.content }}</el-button
|
||||
>
|
||||
</el-tooltip>
|
||||
</el-button-group>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "ToggleTabs",
|
||||
props: {
|
||||
activeDom: String,
|
||||
tabList: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [
|
||||
{
|
||||
domKey: "default",
|
||||
tip: "default",
|
||||
content: "default",
|
||||
placement: "top",
|
||||
enable: true,
|
||||
},
|
||||
];
|
||||
},
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
currentActiveDom() {
|
||||
return this.activeDom;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
changeTab(domKey) {
|
||||
this.$emit("update:activeDom", domKey);
|
||||
this.$emit("toggleTab", domKey)
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.active {
|
||||
border: solid 1px #6d317c !important;
|
||||
background-color: var(--primary_color) !important;
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
.case-button {
|
||||
border-left: solid 1px var(--primary_color);
|
||||
}
|
||||
|
||||
.item {
|
||||
height: 32px;
|
||||
padding: 5px 8px;
|
||||
border: solid 1px var(--primary_color);
|
||||
}
|
||||
</style>
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,328 @@
|
|||
<template>
|
||||
<div>
|
||||
<slot name="header"></slot>
|
||||
<ms-node-tree
|
||||
:is-display="getIsRelevance"
|
||||
v-loading="result.loading"
|
||||
:tree-nodes="data"
|
||||
:allLabel="$t('ui.custom_command_label')"
|
||||
:type="isReadOnly ? 'view' : 'edit'"
|
||||
:delete-permission="['PROJECT_UI_SCENARIO:READ+DELETE']"
|
||||
:add-permission="['PROJECT_UI_SCENARIO:READ+CREATE']"
|
||||
:update-permission="['PROJECT_UI_SCENARIO:READ+EDIT']"
|
||||
:show-case-num="showCaseNum"
|
||||
:hide-opretor="isTrashData"
|
||||
local-suffix="ui_module"
|
||||
:default-label="'未规划模块'"
|
||||
@add="add"
|
||||
@edit="edit"
|
||||
@drag="drag"
|
||||
@remove="remove"
|
||||
@refresh="list"
|
||||
@filter="filter"
|
||||
@nodeSelectEvent="nodeChange"
|
||||
class="element-node-tree"
|
||||
ref="nodeTree">
|
||||
|
||||
<template v-slot:header>
|
||||
<ms-search-bar
|
||||
:show-operator="showOperator && !isTrashData"
|
||||
:condition="condition"
|
||||
:commands="operators"/>
|
||||
<module-trash-button v-if="!isReadOnly && !isTrashData" :condition="condition" :exe="enableTrash"
|
||||
:total='total'/>
|
||||
</template>
|
||||
|
||||
</ms-node-tree>
|
||||
<ms-add-basis-scenario
|
||||
@saveAsEdit="saveAsEdit"
|
||||
@refresh="refresh"
|
||||
ref="basisScenario"/>
|
||||
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SelectMenu from "metersphere-frontend/src/components/environment/snippet/ext/SelectMenu";
|
||||
import MsNodeTree from "metersphere-frontend/src/components/environment/snippet/ext/NodeTree";
|
||||
import {buildTree} from "metersphere-frontend/src/model/NodeTree";
|
||||
import MsSearchBar from "metersphere-frontend/src/components/search/MsSearchBar";
|
||||
import {getCurrentProjectID} from "metersphere-frontend/src/utils/token";
|
||||
import {
|
||||
addScenarioModule,
|
||||
deleteScenarioModule,
|
||||
dragScenarioModule,
|
||||
editScenarioModule,
|
||||
getScenarioModules,
|
||||
posScenarioModule
|
||||
} from "./ui-scenario";
|
||||
|
||||
export default {
|
||||
name: 'UiCustomCommandModule',
|
||||
components: {
|
||||
MsSearchBar,
|
||||
MsNodeTree,
|
||||
SelectMenu,
|
||||
},
|
||||
props: {
|
||||
currentType: String,
|
||||
isReadOnly: {
|
||||
type: Boolean,
|
||||
default() {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
showOperator: Boolean,
|
||||
relevanceProjectId: String,
|
||||
pageSource: String,
|
||||
total: Number,
|
||||
isTrashData: Boolean,
|
||||
planId: String,
|
||||
showCaseNum: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isPlanModel() {
|
||||
return this.planId ? true : false;
|
||||
},
|
||||
isRelevanceModel() {
|
||||
return this.relevanceProjectId ? true : false;
|
||||
},
|
||||
projectId() {
|
||||
return getCurrentProjectID();
|
||||
},
|
||||
getIsRelevance() {
|
||||
if (this.pageSource !== 'scenario') {
|
||||
return this.openType;
|
||||
} else {
|
||||
return "scenario";
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
openType: 'relevance',
|
||||
result: {},
|
||||
condition: {
|
||||
filterText: "",
|
||||
trashEnable: false
|
||||
},
|
||||
data: [],
|
||||
currentModule: undefined,
|
||||
operators: [
|
||||
{
|
||||
label: this.$t('api_test.api_import.label'),
|
||||
callback: this.handleImport,
|
||||
permissions: ['PROJECT_UI_SCENARIO:READ+IMPORT_SCENARIO']
|
||||
},
|
||||
// Sence v2.4 custom command is no longer supported export IDE,
|
||||
// MS export is supported in subsequent versions
|
||||
|
||||
// {
|
||||
// label: this.$t('report.export'),
|
||||
// callback: this.exportSide,
|
||||
// permissions: ['PROJECT_UI_SCENARIO:READ+EXPORT_SCENARIO']
|
||||
// }
|
||||
]
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.list();
|
||||
},
|
||||
watch: {
|
||||
'condition.filterText'() {
|
||||
this.filter();
|
||||
},
|
||||
'condition.trashEnable'() {
|
||||
this.$emit('enableCustomTrash', this.condition.trashEnable);
|
||||
},
|
||||
relevanceProjectId() {
|
||||
this.list(this.relevanceProjectId);
|
||||
},
|
||||
isTrashData() {
|
||||
this.condition.trashEnable = this.isTrashData;
|
||||
this.list();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
saveAsEdit(data) {
|
||||
data.type = "add";
|
||||
data.scenarioType = this.currentType;
|
||||
this.$emit('saveAsEdit', data);
|
||||
},
|
||||
refresh() {
|
||||
this.$emit("refreshTable");
|
||||
},
|
||||
handleImport() {
|
||||
if (this.projectId) {
|
||||
this.result = this.$get("/ui/scenario/module/list/" + this.projectId + "?type=" + this.currentType).then(response => {
|
||||
if (response.data != undefined && response.data != null) {
|
||||
this.data = response.data;
|
||||
this.data.forEach(node => {
|
||||
buildTree(node, {path: ''});
|
||||
});
|
||||
}
|
||||
});
|
||||
this.$refs.apiImport.open(this.currentModule);
|
||||
}
|
||||
},
|
||||
filter() {
|
||||
this.$refs.nodeTree.filter(this.condition.filterText);
|
||||
},
|
||||
list(projectId) {
|
||||
if (this.isPlanModel) {
|
||||
let url = '/ui/scenario/module/list/plan/' + this.planId;
|
||||
this.$get(url).then(response => {
|
||||
if (response.data != undefined && response.data != null) {
|
||||
this.data = response.data;
|
||||
this.data.forEach(node => {
|
||||
buildTree(node, {path: ''});
|
||||
});
|
||||
}
|
||||
})
|
||||
} else {
|
||||
getScenarioModules(projectId ? projectId : this.projectId, this.isTrashData, this.currentType).then((data) => {
|
||||
if (data) {
|
||||
this.data = data.data;
|
||||
this.data.forEach(node => {
|
||||
buildTree(node, {path: ''});
|
||||
});
|
||||
this.$emit('setCustomModuleOptions', this.data);
|
||||
this.$emit('setCustomNodeTree', this.data);
|
||||
if (this.$refs.nodeTree) {
|
||||
this.$refs.nodeTree.filter(this.condition.filterText);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
edit(param) {
|
||||
param.projectId = this.projectId;
|
||||
param.scenarioType = this.currentType;
|
||||
editScenarioModule(param).then(() => {
|
||||
this.$success(this.$t('commons.save_success'));
|
||||
this.list();
|
||||
});
|
||||
this.refresh()
|
||||
},
|
||||
add(param) {
|
||||
param.projectId = this.projectId;
|
||||
param.scenarioType = this.currentType;
|
||||
if (param && param.level >= 9) {
|
||||
this.list();
|
||||
this.$error("模块树最大深度为8层");
|
||||
return;
|
||||
}
|
||||
addScenarioModule(param).then(() => {
|
||||
this.$success(this.$t('commons.save_success'));
|
||||
this.list();
|
||||
});
|
||||
//解决添加失败 节点仍然存在的问题 此处进行刷新
|
||||
this.refresh();
|
||||
},
|
||||
remove(nodeIds) {
|
||||
deleteScenarioModule(nodeIds).then(() => {
|
||||
this.list();
|
||||
this.refresh();
|
||||
this.$emit("customNodeChange")
|
||||
});
|
||||
},
|
||||
drag(param, list) {
|
||||
param.scenarioType = this.currentType;
|
||||
dragScenarioModule(param).then(() => {
|
||||
posScenarioModule(list).then(() => {
|
||||
this.list();
|
||||
});
|
||||
});
|
||||
this.refresh();
|
||||
},
|
||||
nodeChange(node, nodeIds, pNodes) {
|
||||
this.currentModule = node.data;
|
||||
if (node.data.id === 'root') {
|
||||
this.$emit("customNodeChange", node, [], pNodes);
|
||||
} else {
|
||||
this.$emit("customNodeChange", node, nodeIds, pNodes);
|
||||
}
|
||||
},
|
||||
addScenario() {
|
||||
if (!this.projectId) {
|
||||
this.$warning(this.$t('commons.check_project_tip'));
|
||||
return;
|
||||
}
|
||||
this.$refs.basisScenario.open(this.currentModule);
|
||||
},
|
||||
enableTrash() {
|
||||
this.condition.trashEnable = true;
|
||||
this.$emit('enableCustomTrash', this.condition.trashEnable);
|
||||
},
|
||||
exportSide() {
|
||||
this.$emit('exportSide', this.data);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.node-tree {
|
||||
margin-top: 15px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.ms-el-input {
|
||||
height: 25px;
|
||||
line-height: 25px;
|
||||
}
|
||||
|
||||
.custom-tree-node {
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
font-size: 14px;
|
||||
padding-right: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.father .child {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.father:hover .child {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.node-title {
|
||||
width: 0;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
flex: 1 1 auto;
|
||||
padding: 0 5px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.node-operate > i {
|
||||
color: #409eff;
|
||||
margin: 0 5px;
|
||||
}
|
||||
|
||||
:deep(.el-tree-node__content) {
|
||||
height: 33px;
|
||||
}
|
||||
|
||||
.ms-api-buttion {
|
||||
width: 30px;
|
||||
}
|
||||
|
||||
.element-node-tree {
|
||||
width: 100%;
|
||||
/* min-width: 290px; */
|
||||
}
|
||||
|
||||
:deep(.element-node-tree .recycle .el-col.el-col-3) {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
</style>
|
|
@ -0,0 +1,569 @@
|
|||
<template>
|
||||
<div>
|
||||
<div
|
||||
style="
|
||||
border: 1px #dcdfe6 solid;
|
||||
min-height: 50px;
|
||||
border-radius: 4px;
|
||||
width: 99%;
|
||||
margin-top: 10px;
|
||||
clear: both;
|
||||
"
|
||||
>
|
||||
<ms-table
|
||||
v-loading="loading"
|
||||
row-key="id"
|
||||
:data="variables"
|
||||
:total="items.length"
|
||||
:screen-height="'100px'"
|
||||
:batch-operators="batchButtons"
|
||||
:remember-order="true"
|
||||
:highlightCurrentRow="true"
|
||||
@refresh="onChange"
|
||||
ref="variableTable"
|
||||
>
|
||||
<ms-table-column prop="cookie" label="cookie" min-width="160">
|
||||
<template slot-scope="scope">
|
||||
<el-input
|
||||
v-model="scope.row.cookie"
|
||||
size="mini"
|
||||
:placeholder="$t('cookie')"
|
||||
@change="change"
|
||||
/>
|
||||
</template>
|
||||
</ms-table-column>
|
||||
|
||||
<ms-table-column
|
||||
prop="userName"
|
||||
:label="$t('api_test.request.sql.username')"
|
||||
min-width="200"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
<el-input
|
||||
v-model="scope.row.userName"
|
||||
size="mini"
|
||||
maxlength="200"
|
||||
:placeholder="$t('api_test.request.sql.username')"
|
||||
show-word-limit
|
||||
@change="change"
|
||||
/>
|
||||
</template>
|
||||
</ms-table-column>
|
||||
|
||||
<ms-table-column
|
||||
prop="password"
|
||||
:label="$t('api_test.request.tcp.password')"
|
||||
min-width="140"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
<el-input
|
||||
v-model="scope.row.password"
|
||||
size="mini"
|
||||
maxlength="200"
|
||||
show-password
|
||||
:placeholder="$t('api_test.request.tcp.password')"
|
||||
@change="change"
|
||||
/>
|
||||
</template>
|
||||
</ms-table-column>
|
||||
|
||||
<ms-table-column
|
||||
prop="description"
|
||||
:label="$t('commons.validity_period')"
|
||||
min-width="200"
|
||||
:editContent="'aaa'"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
<mini-timing-item :expr="scope.row.expireTime"></mini-timing-item>
|
||||
</template>
|
||||
</ms-table-column>
|
||||
|
||||
<ms-table-column
|
||||
prop="updateTime"
|
||||
:label="$t('commons.update_time')"
|
||||
min-width="160"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
{{
|
||||
scope.row.updateTime | datetimeFormat
|
||||
}}
|
||||
</template>
|
||||
</ms-table-column>
|
||||
|
||||
<ms-table-column :label="$t('commons.operating')" width="150">
|
||||
<template v-slot:default="scope">
|
||||
<el-switch v-model="scope.row.enable" size="mini"></el-switch>
|
||||
<el-tooltip
|
||||
effect="dark"
|
||||
:content="$t('关联登录场景/指令')"
|
||||
placement="top-start"
|
||||
>
|
||||
<el-button
|
||||
icon="el-icon-setting"
|
||||
circle
|
||||
size="mini"
|
||||
@click="openRelevance"
|
||||
v-if="!existCookieConfig"
|
||||
style="margin-left: 10px"
|
||||
/>
|
||||
|
||||
<el-dropdown @command="handleCommand" v-if="existCookieConfig">
|
||||
<el-button
|
||||
icon="el-icon-paperclip"
|
||||
circle
|
||||
size="mini"
|
||||
style="margin-left: 10px"
|
||||
/>
|
||||
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<el-dropdown-item command="view">查看关联</el-dropdown-item>
|
||||
<el-dropdown-item command="cancelRelevance">取消关联</el-dropdown-item>
|
||||
<el-dropdown-item command="relevance">重新关联</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</ms-table-column>
|
||||
</ms-table>
|
||||
</div>
|
||||
<batch-add-parameter @batchSave="batchSave" ref="batchAdd"/>
|
||||
<api-variable-setting ref="apiVariableSetting"></api-variable-setting>
|
||||
|
||||
<!-- 关联登录获取cookie的场景 -->
|
||||
<ui-scenario-edit-relevance
|
||||
ref="relevanceUiDialog"
|
||||
@reference="reference"
|
||||
:scenarioType="currentRelevanceType"
|
||||
/>
|
||||
|
||||
<variable-import
|
||||
ref="variableImport"
|
||||
@mergeData="mergeData"
|
||||
></variable-import>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {KeyValue} from "metersphere-frontend/src/model/EnvTestModel";
|
||||
import MsApiVariableInput from "metersphere-frontend/src/components/environment/commons/ApiVariableInput";
|
||||
import BatchAddParameter from "metersphere-frontend/src/components/environment/commons/BatchAddParameter";
|
||||
import MsTableButton from "metersphere-frontend/src/components/MsTableButton";
|
||||
import MsTable from "metersphere-frontend/src/components/table/MsTable";
|
||||
import MsTableColumn from "metersphere-frontend/src/components/table/MsTableColumn";
|
||||
import ApiVariableSetting from "metersphere-frontend/src/components/environment/commons/ApiVariableSetting";
|
||||
import CsvFileUpload from "metersphere-frontend/src/components/environment/commons/variable/CsvFileUpload";
|
||||
import {downloadFile, getUUID, operationConfirm} from "metersphere-frontend/src/utils";
|
||||
import VariableImport from "metersphere-frontend/src/components/environment/VariableImport";
|
||||
import _ from "lodash";
|
||||
import MiniTimingItem from "metersphere-frontend/src/components/environment/commons/MiniTimingItem";
|
||||
import UiScenarioEditRelevance from "@/business/menu/environment/components/ui-related/UiScenarioEditRelevance";
|
||||
|
||||
export default {
|
||||
name: "MsUiScenarioCookieTable",
|
||||
components: {
|
||||
BatchAddParameter,
|
||||
MsApiVariableInput,
|
||||
MsTableButton,
|
||||
MsTable,
|
||||
MsTableColumn,
|
||||
ApiVariableSetting,
|
||||
CsvFileUpload,
|
||||
VariableImport,
|
||||
MiniTimingItem,
|
||||
UiScenarioEditRelevance,
|
||||
},
|
||||
props: {
|
||||
items: Array,
|
||||
isReadOnly: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
showVariable: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
showCopy: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
screenHeight: "400px",
|
||||
batchButtons: [
|
||||
{
|
||||
name: this.$t("api_test.definition.request.batch_delete"),
|
||||
handleClick: this.handleDeleteBatch,
|
||||
},
|
||||
],
|
||||
typeSelectOptions: [
|
||||
{value: "CONSTANT", label: this.$t("api_test.automation.constant")},
|
||||
{value: "LIST", label: this.$t("test_track.case.list")},
|
||||
{value: "CSV", label: "CSV"},
|
||||
{value: "COUNTER", label: this.$t("api_test.automation.counter")},
|
||||
{value: "RANDOM", label: this.$t("api_test.automation.random")},
|
||||
],
|
||||
variables: {},
|
||||
selectVariable: "",
|
||||
editData: {},
|
||||
scopeTypeFilters: [
|
||||
{text: this.$t("commons.api"), value: "api"},
|
||||
{text: this.$t("commons.ui_test"), value: "ui"},
|
||||
],
|
||||
currentRelevanceType: 'scenario',
|
||||
existCookieConfig: false
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
items: {
|
||||
handler(v) {
|
||||
this.variables = v;
|
||||
this.sortParameters();
|
||||
},
|
||||
immediate: true,
|
||||
deep: true,
|
||||
},
|
||||
variables: {
|
||||
handler(v) {
|
||||
if (this.variables && this.variables.length && this.variables[0].relevanceId) {
|
||||
this.existCookieConfig = true;
|
||||
} else {
|
||||
this.existCookieConfig = false;
|
||||
}
|
||||
},
|
||||
immediate: true,
|
||||
deep: true,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
remove: function (index) {
|
||||
const dataIndex = this.variables.findIndex((d) => d.name === index.name);
|
||||
this.variables.splice(dataIndex, 1);
|
||||
this.$emit("change", this.variables);
|
||||
},
|
||||
change: function () {
|
||||
let isNeedCreate = true;
|
||||
let removeIndex = -1;
|
||||
let repeatKey = "";
|
||||
this.variables.forEach((item, index) => {
|
||||
this.variables.forEach((row, rowIndex) => {
|
||||
if (item.name === row.name && index !== rowIndex) {
|
||||
repeatKey = item.name;
|
||||
}
|
||||
});
|
||||
if (!item.name && !item.value) {
|
||||
// 多余的空行
|
||||
if (index !== this.items.length - 1) {
|
||||
removeIndex = index;
|
||||
}
|
||||
// 没有空行,需要创建空行
|
||||
isNeedCreate = false;
|
||||
}
|
||||
});
|
||||
if (repeatKey !== "") {
|
||||
this.$warning(
|
||||
this.$t("api_test.environment.common_config") +
|
||||
"【" +
|
||||
repeatKey +
|
||||
"】" +
|
||||
this.$t("load_test.param_is_duplicate")
|
||||
);
|
||||
}
|
||||
this.$emit("change", this.variables);
|
||||
},
|
||||
changeType(data) {
|
||||
data.value = "";
|
||||
if (
|
||||
!data.delimiter ||
|
||||
(!data.files && data.files.length === 0) ||
|
||||
!data.quotedData
|
||||
) {
|
||||
data.delimiter = ",";
|
||||
data.files = [];
|
||||
data.quotedData = "false";
|
||||
}
|
||||
},
|
||||
valueText(data) {
|
||||
switch (data.type) {
|
||||
case "LIST":
|
||||
return this.$t("api_test.environment.list_info");
|
||||
case "CONSTANT":
|
||||
return this.$t("api_test.value");
|
||||
case "COUNTER":
|
||||
case "RANDOM":
|
||||
return this.$t("api_test.environment.advanced_setting");
|
||||
default:
|
||||
return this.$t("api_test.value");
|
||||
}
|
||||
},
|
||||
querySearch(queryString, cb) {
|
||||
let restaurants = [
|
||||
{value: "UTF-8"},
|
||||
{value: "UTF-16"},
|
||||
{value: "GB2312"},
|
||||
{value: "ISO-8859-15"},
|
||||
{value: "US-ASCll"},
|
||||
];
|
||||
let results = queryString
|
||||
? restaurants.filter(this.createFilter(queryString))
|
||||
: restaurants;
|
||||
// 调用 callback 返回建议列表的数据
|
||||
cb(results);
|
||||
},
|
||||
sortParameters() {
|
||||
let index = 1;
|
||||
this.variables.forEach((item) => {
|
||||
item.num = index;
|
||||
if (!item.type || item.type === "text") {
|
||||
item.type = "CONSTANT";
|
||||
}
|
||||
if (!item.id) {
|
||||
item.id = getUUID();
|
||||
}
|
||||
if (item.remark) {
|
||||
this.$set(item, "description", item.remark);
|
||||
item.remark = undefined;
|
||||
}
|
||||
if (!item.scope) {
|
||||
this.$set(item, "scope", "api");
|
||||
}
|
||||
index++;
|
||||
});
|
||||
},
|
||||
handleDeleteBatch() {
|
||||
operationConfirm(
|
||||
this,
|
||||
this.$t("api_test.environment.variables_delete_info") + " ?",
|
||||
() => {
|
||||
let ids = this.$refs.variableTable.selectRows;
|
||||
ids.forEach((row) => {
|
||||
if (row.id) {
|
||||
const index = this.variables.findIndex((d) => d.id === row.id);
|
||||
if (index !== this.variables.length - 1) {
|
||||
this.variables.splice(index, 1);
|
||||
}
|
||||
}
|
||||
});
|
||||
this.sortParameters();
|
||||
this.$refs.variableTable.cancelCurrentRow();
|
||||
this.$refs.variableTable.clear();
|
||||
}
|
||||
);
|
||||
},
|
||||
filter(scope) {
|
||||
let datas = [];
|
||||
this.variables.forEach((item) => {
|
||||
if (this.selectVariable && this.selectVariable != "" && item.name) {
|
||||
if (
|
||||
item.name
|
||||
.toLowerCase()
|
||||
.indexOf(this.selectVariable.toLowerCase()) == -1
|
||||
) {
|
||||
item.hidden = true;
|
||||
} else {
|
||||
item.hidden = undefined;
|
||||
}
|
||||
} else {
|
||||
item.hidden = undefined;
|
||||
}
|
||||
datas.push(item);
|
||||
});
|
||||
this.variables = datas;
|
||||
},
|
||||
filterScope(scope) {
|
||||
let datas = [];
|
||||
let variables = _.cloneDeep(this.variables);
|
||||
variables.forEach((item) => {
|
||||
if (scope == "api") {
|
||||
if (
|
||||
item.scope && item.scope != "api"
|
||||
) {
|
||||
item.hidden = true;
|
||||
} else {
|
||||
item.hidden = undefined;
|
||||
}
|
||||
} else {
|
||||
if (item.scope == scope) {
|
||||
item.hidden = undefined;
|
||||
} else {
|
||||
item.hidden = true;
|
||||
}
|
||||
}
|
||||
datas.push(item);
|
||||
});
|
||||
this.variables = datas;
|
||||
},
|
||||
openSetting(data) {
|
||||
this.$refs.apiVariableSetting.open(data);
|
||||
},
|
||||
isDisable: function (row) {
|
||||
const index = this.variables.findIndex((d) => d.name === row.name);
|
||||
return this.variables.length - 1 !== index;
|
||||
},
|
||||
_handleBatchVars(data) {
|
||||
let params = data.split("\n");
|
||||
let keyValues = [];
|
||||
params.forEach((item) => {
|
||||
if (item) {
|
||||
let line = item.split(/:|:/);
|
||||
let values = item.split(line[0] + ":");
|
||||
let required = false;
|
||||
keyValues.push(
|
||||
new KeyValue({
|
||||
name: line[0],
|
||||
required: required,
|
||||
value: values[1],
|
||||
type: "CONSTANT",
|
||||
valid: false,
|
||||
file: false,
|
||||
encode: true,
|
||||
enable: true,
|
||||
description: undefined,
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
return keyValues;
|
||||
},
|
||||
batchAdd() {
|
||||
this.$refs.batchAdd.open();
|
||||
},
|
||||
batchSave(data) {
|
||||
if (data) {
|
||||
let keyValues = this._handleBatchVars(data);
|
||||
keyValues.forEach((keyValue) => {
|
||||
let isAdd = true;
|
||||
this.variables.forEach((item) => {
|
||||
if (item.name === keyValue.name) {
|
||||
item.value = keyValue.value;
|
||||
isAdd = false;
|
||||
}
|
||||
});
|
||||
if (isAdd) {
|
||||
this.variables.splice(
|
||||
this.variables.indexOf((i) => !i.name),
|
||||
0,
|
||||
keyValue
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
onChange() {
|
||||
this.sortParameters();
|
||||
},
|
||||
exportJSON() {
|
||||
let apiVariable = [];
|
||||
this.$refs.variableTable.selectRows.forEach((r) => {
|
||||
if (!r.scope || r.scope != "ui") {
|
||||
apiVariable.push(r);
|
||||
}
|
||||
});
|
||||
|
||||
if (apiVariable.length < 1) {
|
||||
this.$warning(this.$t("api_test.environment.select_api_variable"));
|
||||
return;
|
||||
}
|
||||
let variablesJson = [];
|
||||
let messages = "";
|
||||
let rows = this.$refs.variableTable.selectRows;
|
||||
rows.forEach((row) => {
|
||||
if (row.type === "CSV") {
|
||||
messages = this.$t("variables.csv_download");
|
||||
}
|
||||
if (row.name && (!row.scope || row.scope == "api")) {
|
||||
variablesJson.push(row);
|
||||
}
|
||||
});
|
||||
if (messages !== "") {
|
||||
this.$warning(messages);
|
||||
return;
|
||||
}
|
||||
downloadFile(
|
||||
"MS_" + variablesJson.length + "_Environments_variables.json",
|
||||
JSON.stringify(variablesJson)
|
||||
);
|
||||
},
|
||||
importJSON() {
|
||||
this.$refs.variableImport.open();
|
||||
},
|
||||
mergeData(data, modeId) {
|
||||
JSON.parse(data).map((importData) => {
|
||||
importData.id = getUUID();
|
||||
importData.enable = true;
|
||||
importData.showMore = false;
|
||||
let sameNameIndex = this.variables.findIndex(
|
||||
(d) => d.name === importData.name
|
||||
);
|
||||
if (sameNameIndex !== -1) {
|
||||
if (modeId === "fullCoverage") {
|
||||
this.variables.splice(sameNameIndex, 1, importData);
|
||||
}
|
||||
} else {
|
||||
this.variables.splice(this.variables.length - 1, 0, importData);
|
||||
}
|
||||
});
|
||||
},
|
||||
handleCommand(c) {
|
||||
switch (c) {
|
||||
case "view":
|
||||
break;
|
||||
case "cancelRelevance":
|
||||
this.variables[0].relevanceId = null;
|
||||
break;
|
||||
case "relevance":
|
||||
this.openRelevance();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
},
|
||||
openRelevance() {
|
||||
this.$refs.relevanceUiDialog.open();
|
||||
},
|
||||
reference(id) {
|
||||
this.variables[0].relevanceId = id.keys().next().value.id;
|
||||
this.$refs.relevanceUiDialog.close();
|
||||
this.$success(this.$t('commons.save_success'));
|
||||
}
|
||||
},
|
||||
created() {
|
||||
if (this.items.length === 0) {
|
||||
this.items.push(new KeyValue({enable: true, expireTime: '1Y'}));
|
||||
} else {
|
||||
//历史数据默认是 api 应用场景
|
||||
_.forEach(this.items, item => {
|
||||
if (!item.scope) {
|
||||
this.$set(item, "scope", "api");
|
||||
}
|
||||
})
|
||||
this.variables = this.items;
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.kv-description {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.kv-checkbox {
|
||||
width: 20px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.kv-row {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.kv-delete {
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
:deep(.table-select-icon) {
|
||||
display: none !important;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,440 @@
|
|||
<template>
|
||||
<test-case-relevance-base
|
||||
:dialog-title="$t('ui.import_by_list_label')"
|
||||
@setProject="setProject"
|
||||
ref="baseRelevance"
|
||||
>
|
||||
<template v-slot:aside>
|
||||
<ui-scenario-module
|
||||
v-if="currentType === 'scenario'"
|
||||
currentType="scenario"
|
||||
style="margin-top: 5px"
|
||||
@nodeSelectEvent="nodeChange"
|
||||
@refreshTable="refresh"
|
||||
@setModuleOptions="setModuleOptions"
|
||||
@enableTrash="false"
|
||||
:is-read-only="true"
|
||||
ref="nodeTree"
|
||||
/>
|
||||
<ui-custom-command-module
|
||||
v-if="currentType === 'customCommand'"
|
||||
currentType="customCommand"
|
||||
style="margin-top: 5px"
|
||||
@customNodeChange="customNodeChange"
|
||||
@refreshTable="refresh"
|
||||
@setCustomModuleOptions="setCustomModuleOptions"
|
||||
@enableTrash="false"
|
||||
:is-read-only="true"
|
||||
ref="customNodeTree"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<ui-scenario-list
|
||||
v-if="currentType === 'scenario'"
|
||||
currentType="scenario"
|
||||
:module-options="moduleOptions"
|
||||
:select-node-ids="selectNodeIds"
|
||||
:select-project-id="projectId"
|
||||
:trash-enable="false"
|
||||
:batch-operators="[]"
|
||||
:is-reference-table="true"
|
||||
@selection="setData"
|
||||
:is-relate="true"
|
||||
:init-ui-table-opretion="'init'"
|
||||
:custom-num="customNum"
|
||||
:showDrag="false"
|
||||
:mode="'import'"
|
||||
ref="uiScenarioList"
|
||||
>
|
||||
<toggle-tabs
|
||||
slot="tabChange"
|
||||
:activeDom.sync="currentType"
|
||||
:tabList="tabList"
|
||||
></toggle-tabs>
|
||||
</ui-scenario-list>
|
||||
|
||||
<ui-custom-command-list
|
||||
v-if="currentType === 'customCommand'"
|
||||
currentType="customCommand"
|
||||
:module-options="customModuleOptions"
|
||||
:select-node-ids="selectCustomNodeIds"
|
||||
:select-project-id="projectId"
|
||||
:trash-enable="false"
|
||||
:batch-operators="[]"
|
||||
:is-reference-table="true"
|
||||
@selection="setData"
|
||||
:is-relate="true"
|
||||
:init-ui-table-opretion="'init'"
|
||||
:custom-num="customNum"
|
||||
:showDrag="false"
|
||||
:mode="'import'"
|
||||
ref="uiCustomScenarioList"
|
||||
>
|
||||
<toggle-tabs
|
||||
slot="tabChange"
|
||||
:activeDom.sync="currentType"
|
||||
:tabList="tabList"
|
||||
></toggle-tabs>
|
||||
</ui-custom-command-list>
|
||||
|
||||
<template v-slot:headerBtn>
|
||||
<!-- todo 场景引用 -->
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="reference"
|
||||
:loading="buttonIsWorking"
|
||||
@keydown.enter.native.prevent
|
||||
size="mini"
|
||||
>
|
||||
{{ $t("api_test.scenario.reference") }}
|
||||
</el-button>
|
||||
</template>
|
||||
</test-case-relevance-base>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MsContainer from "metersphere-frontend/src/components/MsContainer";
|
||||
import MsAsideContainer from "metersphere-frontend/src/components/MsAsideContainer";
|
||||
import MsMainContainer from "metersphere-frontend/src/components/MsMainContainer";
|
||||
import {hasLicense} from "metersphere-frontend/src/utils/permission.js";
|
||||
import {getUUID} from 'metersphere-frontend/src/utils';
|
||||
import RelevanceDialog from "metersphere-frontend/src/components/environment/snippet/ext/RelevanceDialog";
|
||||
import TestCaseRelevanceBase from "metersphere-frontend/src/components/environment/snippet/ext/TestCaseRelevanceBase";
|
||||
import UiScenarioModule from "./UiScenarioModule";
|
||||
import UiScenarioList from "./UiScenarioList";
|
||||
import ToggleTabs from "./ToggleTabs";
|
||||
import UiCustomCommandList from "./UiCustomCommandList";
|
||||
import UiCustomCommandModule from "./UiCustomCommandModule";
|
||||
import {TYPE_TO_C} from "metersphere-frontend/src/model/Setting";
|
||||
|
||||
const VersionSelect = {};
|
||||
|
||||
export default {
|
||||
name: "UiScenarioEditRelevance",
|
||||
components: {
|
||||
UiScenarioModule,
|
||||
VersionSelect: VersionSelect.default,
|
||||
TestCaseRelevanceBase,
|
||||
RelevanceDialog,
|
||||
UiScenarioList,
|
||||
MsMainContainer,
|
||||
MsAsideContainer,
|
||||
MsContainer,
|
||||
ToggleTabs,
|
||||
UiCustomCommandList,
|
||||
UiCustomCommandModule,
|
||||
},
|
||||
props: {
|
||||
scenarioType: String,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
currentType: "scenario",
|
||||
buttonIsWorking: false,
|
||||
result: {},
|
||||
currentProtocol: null,
|
||||
selectNodeIds: [],
|
||||
selectCustomNodeIds: [],
|
||||
moduleOptions: [],
|
||||
customModuleOptions: [],
|
||||
isApiListEnable: true,
|
||||
currentScenario: [],
|
||||
currentScenarioIds: [],
|
||||
projectId: "",
|
||||
customNum: false,
|
||||
versionOptions: [],
|
||||
currentVersion: "",
|
||||
versionEnable: true,
|
||||
tabList: [
|
||||
{
|
||||
domKey: "scenario",
|
||||
tip: this.$t("ui.scenario_title"),
|
||||
content: this.$t("ui.scenario_title"),
|
||||
placement: "left",
|
||||
enable: true,
|
||||
},
|
||||
{
|
||||
domKey: "customCommand",
|
||||
tip: this.$t("ui.custom_command_title"),
|
||||
content: this.$t("ui.custom_command_title"),
|
||||
placement: "right",
|
||||
enable: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
projectId(val) {
|
||||
this.listByType(val);
|
||||
},
|
||||
scenarioType(val) {
|
||||
this.currentType = this.scenarioType;
|
||||
},
|
||||
currentType(val) {
|
||||
this.listByType(this.projectId);
|
||||
this.searchByType();
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
isScenario() {
|
||||
return this.currentType === "scenario";
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
listByType(projectId) {
|
||||
this.$nextTick(() => {
|
||||
this.isScenario
|
||||
? this.$refs.nodeTree.list(this.projectId)
|
||||
: this.$refs.customNodeTree.list(this.projectId);
|
||||
});
|
||||
},
|
||||
changeButtonLoadingType() {
|
||||
this.buttonIsWorking = false;
|
||||
},
|
||||
createScenarioDefinition(scenarios, data, referenced) {
|
||||
let errArr = [];
|
||||
for (let item of data) {
|
||||
let scenarioDefinition = JSON.parse(item.scenarioDefinition);
|
||||
if (!scenarioDefinition) {
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
!scenarioDefinition.hashTree ||
|
||||
scenarioDefinition.hashTree.length <= 0
|
||||
) {
|
||||
errArr.push(scenarioDefinition.name);
|
||||
continue;
|
||||
}
|
||||
if (scenarioDefinition && scenarioDefinition.hashTree) {
|
||||
//处理scenarioDefinition
|
||||
this.handleScenarioDefinition(scenarioDefinition);
|
||||
|
||||
let variables = scenarioDefinition.variables
|
||||
? scenarioDefinition.variables.filter((v) => {
|
||||
return v.name;
|
||||
})
|
||||
: [];
|
||||
|
||||
if (item.scenarioType === "customCommand" && item.commandViewStruct) {
|
||||
let innerVariables = this.handleInnerVariables(
|
||||
item.commandViewStruct
|
||||
);
|
||||
if (innerVariables) {
|
||||
variables = innerVariables;
|
||||
}
|
||||
}
|
||||
let outputVariables = this.handleOutputVariables(
|
||||
item.commandViewStruct
|
||||
);
|
||||
let obj = {
|
||||
//场景的id 存入resourceId中 避免el-tree key重复
|
||||
id: getUUID(),
|
||||
name: item.name,
|
||||
type: this.currentType,
|
||||
variables: this.resetInnerVariable(variables),
|
||||
outputVariables: this.resetOutVariable(outputVariables),
|
||||
environmentMap: scenarioDefinition.environmentMap,
|
||||
referenced: referenced,
|
||||
resourceId: item.id,
|
||||
hashTree: scenarioDefinition.hashTree,
|
||||
projectId: item.projectId,
|
||||
num: item.num,
|
||||
versionName: item.versionName,
|
||||
versionEnable: item.versionEnable,
|
||||
description: item.description,
|
||||
clazzName: this.isScenario
|
||||
? TYPE_TO_C.get("UiScenario")
|
||||
: TYPE_TO_C.get("customCommand"),
|
||||
};
|
||||
scenarios.push(obj);
|
||||
}
|
||||
}
|
||||
|
||||
if (errArr.length > 0) {
|
||||
let msg = errArr.join("、");
|
||||
if (msg && msg.length > 30) {
|
||||
msg = msg.substring(0, 30) + "...";
|
||||
}
|
||||
let str = this.isScenario
|
||||
? this.$t("ui.scenario")
|
||||
: this.$t("ui.instruction");
|
||||
this.$error(msg + " " + str + "为空,导入失败");
|
||||
}
|
||||
|
||||
if (referenced === "REF") {
|
||||
//
|
||||
if (this.isScenario) {
|
||||
//场景
|
||||
this.$EventBus.$emit("handleScenarioREFEvent")
|
||||
}
|
||||
//指令
|
||||
else {
|
||||
this.$EventBus.$emit("handleCustomCommandREFEvent")
|
||||
}
|
||||
}
|
||||
},
|
||||
resetInnerVariable(vars) {
|
||||
if (vars && Array.isArray(vars)) {
|
||||
vars.forEach((v) => {
|
||||
v.id = getUUID();
|
||||
});
|
||||
}
|
||||
return vars;
|
||||
},
|
||||
resetOutVariable(vars) {
|
||||
if (vars && Array.isArray(vars)) {
|
||||
vars.forEach((v) => {
|
||||
v.id = getUUID();
|
||||
});
|
||||
}
|
||||
return vars;
|
||||
},
|
||||
handleInnerVariables(commandViewStruct) {
|
||||
if (!commandViewStruct) {
|
||||
return [];
|
||||
}
|
||||
let struct = JSON.parse(commandViewStruct);
|
||||
if (struct && struct.length > 1) {
|
||||
return struct[1].variables;
|
||||
}
|
||||
return [];
|
||||
},
|
||||
handleOutputVariables(commandViewStruct) {
|
||||
if (!commandViewStruct) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let struct = JSON.parse(commandViewStruct);
|
||||
if (struct && struct.length > 1) {
|
||||
let cur = struct[1].outputVariables;
|
||||
if (cur) {
|
||||
return cur;
|
||||
}
|
||||
}
|
||||
|
||||
return [];
|
||||
},
|
||||
handleScenarioDefinition(scenarioDefinition) {
|
||||
if (!scenarioDefinition || !scenarioDefinition.hashTree || scenarioDefinition.hashTree.length <= 0) {
|
||||
return;
|
||||
}
|
||||
scenarioDefinition.resourceId = scenarioDefinition.id || getUUID();
|
||||
scenarioDefinition.id = getUUID();
|
||||
//重置 出参
|
||||
if (scenarioDefinition.outputVariables) {
|
||||
scenarioDefinition.outputVariables = "";
|
||||
}
|
||||
|
||||
for (let item of scenarioDefinition.hashTree) {
|
||||
item.id = getUUID();
|
||||
//重置 出参
|
||||
if (item.outputVariables) {
|
||||
item.outputVariables = "";
|
||||
}
|
||||
if (item.hashTree) {
|
||||
this.handleScenarioDefinition(item);
|
||||
}
|
||||
}
|
||||
},
|
||||
reference() {
|
||||
this.buttonIsWorking = true;
|
||||
let selectIds = this.scenarioType == "scenario" ? this.$refs.uiScenarioList.getSelectRows() : this.$refs.uiCustomScenarioList.getSelectRows();
|
||||
if (!selectIds || selectIds.size == 0 || selectIds.size > 1) {
|
||||
this.$warning('请(只)选择一个场景/指令作为获取 cookie 的流程!');
|
||||
return;
|
||||
}
|
||||
this.$emit("reference", selectIds);
|
||||
this.buttonIsWorking = false;
|
||||
},
|
||||
close() {
|
||||
this.$refs.baseRelevance.close();
|
||||
},
|
||||
open() {
|
||||
this.buttonIsWorking = false;
|
||||
this.$refs.baseRelevance.open();
|
||||
this.searchByType();
|
||||
},
|
||||
|
||||
/**
|
||||
* common opt
|
||||
*/
|
||||
searchByType() {
|
||||
this.$nextTick(() => {
|
||||
this.isScenario
|
||||
? this.$refs.uiScenarioList.search(this.projectId)
|
||||
: this.$refs.uiCustomScenarioList.search(this.projectId);
|
||||
});
|
||||
},
|
||||
nodeChange(node, nodeIds, pNodes) {
|
||||
this.selectNodeIds = nodeIds;
|
||||
},
|
||||
customNodeChange(node, nodeIds, pNodes) {
|
||||
this.selectCustomNodeIds = nodeIds;
|
||||
},
|
||||
handleProtocolChange(protocol) {
|
||||
this.currentProtocol = protocol;
|
||||
},
|
||||
setModuleOptions(data) {
|
||||
this.moduleOptions = data;
|
||||
},
|
||||
setCustomModuleOptions(data) {
|
||||
this.customModuleOptions = data;
|
||||
},
|
||||
refresh() {
|
||||
this.searchByType();
|
||||
},
|
||||
setData(data) {
|
||||
this.currentScenario = Array.from(data).map((row) => row);
|
||||
this.currentScenarioIds = Array.from(data).map((row) => row.id);
|
||||
},
|
||||
setProject(projectId) {
|
||||
this.projectId = projectId;
|
||||
this.selectNodeIds = [];
|
||||
this.selectCustomNodeIds = [];
|
||||
},
|
||||
getConditions() {
|
||||
return this.getConditionsByType();
|
||||
},
|
||||
getConditionsByType() {
|
||||
try {
|
||||
if (this.isScenario) {
|
||||
return this.$refs.uiScenarioList.getConditions();
|
||||
}
|
||||
return this.$refs.uiCustomScenarioList.getConditions();
|
||||
} catch (e) {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
getVersionOptionList(projectId) {
|
||||
if (hasLicense()) {
|
||||
this.$get("/project/version/get-project-versions/" + projectId).then(
|
||||
(response) => {
|
||||
this.versionOptions = response.data;
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
changeVersion(currentVersion) {
|
||||
if (this.$refs.uiScenarioList) {
|
||||
this.$refs.uiScenarioList.condition.versionId = currentVersion || null;
|
||||
}
|
||||
this.refresh();
|
||||
},
|
||||
checkVersionEnable(projectId) {
|
||||
if (!projectId) {
|
||||
return;
|
||||
}
|
||||
if (hasLicense()) {
|
||||
this.$get("/project/version/enable/" + projectId).then((response) => {
|
||||
this.versionEnable = false;
|
||||
this.$nextTick(() => {
|
||||
this.versionEnable = true;
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,289 @@
|
|||
<template>
|
||||
<div>
|
||||
<slot name="header"></slot>
|
||||
|
||||
<ms-node-tree
|
||||
:is-display="getIsRelevance"
|
||||
v-loading="result.loading"
|
||||
:tree-nodes="data"
|
||||
:allLabel="$t('ui.all_scenario')"
|
||||
:type="isReadOnly ? 'view' : 'edit'"
|
||||
:delete-permission="['PROJECT_UI_SCENARIO:READ+DELETE']"
|
||||
:add-permission="['PROJECT_UI_SCENARIO:READ+CREATE']"
|
||||
:update-permission="['PROJECT_UI_SCENARIO:READ+EDIT']"
|
||||
:default-label="'未规划场景'"
|
||||
:show-case-num="showCaseNum"
|
||||
:hide-opretor="isTrashData"
|
||||
local-suffix="ui_scenario"
|
||||
@add="add"
|
||||
@edit="edit"
|
||||
@drag="drag"
|
||||
@remove="remove"
|
||||
@refresh="list"
|
||||
@filter="filter"
|
||||
@nodeSelectEvent="nodeChange"
|
||||
class="element-node-tree"
|
||||
ref="nodeTree">
|
||||
|
||||
<template v-slot:header>
|
||||
<ms-search-bar
|
||||
:show-operator="showOperator && !isTrashData"
|
||||
:condition="condition"
|
||||
:commands="operators"/>
|
||||
</template>
|
||||
|
||||
</ms-node-tree>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SelectMenu from "metersphere-frontend/src/components/environment/snippet/ext/SelectMenu";
|
||||
import MsNodeTree from "metersphere-frontend/src/components/environment/snippet/ext/NodeTree";
|
||||
import {buildTree} from "metersphere-frontend/src/model/NodeTree";
|
||||
import MsSearchBar from "metersphere-frontend/src/components/search/MsSearchBar";
|
||||
import {getCurrentProjectID} from "metersphere-frontend/src/utils/token";
|
||||
import {
|
||||
addScenarioModule,
|
||||
deleteScenarioModule,
|
||||
dragScenarioModule,
|
||||
editScenarioModule,
|
||||
getScenarioModules,
|
||||
posScenarioModule
|
||||
} from "./ui-scenario";
|
||||
|
||||
export default {
|
||||
name: 'UiScenarioModule',
|
||||
components: {
|
||||
MsSearchBar,
|
||||
MsNodeTree,
|
||||
SelectMenu,
|
||||
},
|
||||
props: {
|
||||
isReadOnly: {
|
||||
type: Boolean,
|
||||
default() {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
showOperator: Boolean,
|
||||
relevanceProjectId: String,
|
||||
pageSource: String,
|
||||
total: Number,
|
||||
isTrashData: Boolean,
|
||||
planId: String,
|
||||
showCaseNum: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isPlanModel() {
|
||||
return this.planId ? true : false;
|
||||
},
|
||||
isRelevanceModel() {
|
||||
return this.relevanceProjectId ? true : false;
|
||||
},
|
||||
projectId() {
|
||||
return getCurrentProjectID();
|
||||
},
|
||||
getIsRelevance() {
|
||||
if (this.pageSource !== 'scenario') {
|
||||
return this.openType;
|
||||
} else {
|
||||
return "scenario";
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
openType: 'relevance',
|
||||
result: {},
|
||||
condition: {
|
||||
filterText: "",
|
||||
trashEnable: false
|
||||
},
|
||||
data: [],
|
||||
currentModule: undefined,
|
||||
operators: [
|
||||
{
|
||||
label: this.$t('api_test.api_import.label'),
|
||||
callback: this.handleImport,
|
||||
permissions: ['PROJECT_UI_SCENARIO:READ+IMPORT_SCENARIO']
|
||||
},
|
||||
{
|
||||
label: this.$t('report.export'),
|
||||
callback: this.exportSide,
|
||||
permissions: ['PROJECT_UI_SCENARIO:READ+EXPORT_SCENARIO']
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.list();
|
||||
},
|
||||
watch: {
|
||||
'condition.filterText'() {
|
||||
this.filter();
|
||||
},
|
||||
'condition.trashEnable'() {
|
||||
this.$emit('enableTrash', this.condition.trashEnable);
|
||||
},
|
||||
relevanceProjectId() {
|
||||
this.list(this.relevanceProjectId);
|
||||
},
|
||||
isTrashData() {
|
||||
this.condition.trashEnable = this.isTrashData;
|
||||
this.list();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
saveAsEdit(data) {
|
||||
data.type = "add";
|
||||
this.$emit('saveAsEdit', data);
|
||||
},
|
||||
refresh() {
|
||||
this.$emit("refreshTable");
|
||||
},
|
||||
filter() {
|
||||
this.$refs.nodeTree.filter(this.condition.filterText);
|
||||
},
|
||||
list(projectId) {
|
||||
getScenarioModules(projectId ? projectId : this.projectId, this.isTrashData).then(data => {
|
||||
if (data) {
|
||||
this.data = data.data;
|
||||
this.data.forEach(node => {
|
||||
buildTree(node, {path: ''});
|
||||
});
|
||||
this.$emit('setModuleOptions', this.data);
|
||||
this.$emit('setNodeTree', this.data);
|
||||
if (this.$refs.nodeTree) {
|
||||
this.$refs.nodeTree.filter(this.condition.filterText);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
edit(param) {
|
||||
param.projectId = this.projectId;
|
||||
editScenarioModule(param).then(() => {
|
||||
this.$success(this.$t('commons.save_success'));
|
||||
this.list();
|
||||
});
|
||||
this.refresh()
|
||||
},
|
||||
add(param) {
|
||||
param.projectId = this.projectId;
|
||||
if (param && param.level >= 9) {
|
||||
this.list();
|
||||
this.$error("模块树最大深度为8层");
|
||||
return;
|
||||
}
|
||||
addScenarioModule(param).then(() => {
|
||||
this.$success(this.$t('commons.save_success'));
|
||||
this.list();
|
||||
});
|
||||
//解决添加失败 节点仍然存在的问题 此处进行刷新
|
||||
this.refresh();
|
||||
},
|
||||
remove(nodeIds) {
|
||||
deleteScenarioModule(nodeIds).then(() => {
|
||||
this.list();
|
||||
this.refresh();
|
||||
this.$emit("nodeSelectEvent")
|
||||
});
|
||||
},
|
||||
drag(param, list) {
|
||||
dragScenarioModule(param).then(() => {
|
||||
posScenarioModule(list).then(() => {
|
||||
this.list();
|
||||
});
|
||||
});
|
||||
this.refresh();
|
||||
},
|
||||
nodeChange(node, nodeIds, pNodes) {
|
||||
this.currentModule = node.data;
|
||||
if (node.data.id === 'root') {
|
||||
this.$emit("nodeSelectEvent", node, [], pNodes);
|
||||
} else {
|
||||
this.$emit("nodeSelectEvent", node, nodeIds, pNodes);
|
||||
}
|
||||
},
|
||||
addScenario() {
|
||||
if (!this.projectId) {
|
||||
this.$warning(this.$t('commons.check_project_tip'));
|
||||
return;
|
||||
}
|
||||
this.$refs.basisScenario.open(this.currentModule);
|
||||
},
|
||||
enableTrash() {
|
||||
this.condition.trashEnable = true;
|
||||
this.$emit('enableTrash', this.condition.trashEnable);
|
||||
},
|
||||
exportSide() {
|
||||
this.$emit('exportSide', this.data);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.node-tree {
|
||||
margin-top: 15px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.ms-el-input {
|
||||
height: 25px;
|
||||
line-height: 25px;
|
||||
}
|
||||
|
||||
.custom-tree-node {
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
font-size: 14px;
|
||||
padding-right: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.father .child {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.father:hover .child {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.node-title {
|
||||
width: 0;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
flex: 1 1 auto;
|
||||
padding: 0 5px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.node-operate > i {
|
||||
color: #409eff;
|
||||
margin: 0 5px;
|
||||
}
|
||||
|
||||
:deep(.el-tree-node__content) {
|
||||
height: 33px;
|
||||
}
|
||||
|
||||
.ms-api-buttion {
|
||||
width: 30px;
|
||||
}
|
||||
|
||||
.element-node-tree {
|
||||
width: 100%;
|
||||
/* min-width: 290px; */
|
||||
}
|
||||
|
||||
:deep(.element-node-tree .recycle .el-col.el-col-3) {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
</style>
|
|
@ -0,0 +1,38 @@
|
|||
import {get, post} from "metersphere-frontend/src/plugins/request"
|
||||
|
||||
let baseUrl = '/ui/scenario/module/';
|
||||
let trashUrl = '/ui/scenario/module/trash/';
|
||||
|
||||
export function getScenarioModules(projectId, isTrashData, type) {
|
||||
let url = isTrashData ? trashUrl : baseUrl;
|
||||
url = url + 'list/' + projectId
|
||||
if(type){
|
||||
url = `${url}?type=${type}`;
|
||||
}
|
||||
return get(url);
|
||||
}
|
||||
|
||||
export function addScenarioModule(param) {
|
||||
return post(baseUrl + 'add', param);
|
||||
}
|
||||
|
||||
export function editScenarioModule(param) {
|
||||
return post(baseUrl + 'edit', param);
|
||||
}
|
||||
|
||||
export function dragScenarioModule(param) {
|
||||
return post(baseUrl + 'drag', param);
|
||||
}
|
||||
|
||||
export function posScenarioModule(param, callback) {
|
||||
return post(baseUrl + 'pos', param, callback);
|
||||
}
|
||||
|
||||
export function deleteScenarioModule(nodeIds, callback) {
|
||||
return post(baseUrl + 'delete', nodeIds, callback);
|
||||
}
|
||||
|
||||
export function getUiAutomationList(currentPage, pageSize, param) {
|
||||
return post("/ui/automation/list/" + currentPage + "/" + pageSize, param);
|
||||
}
|
||||
|
|
@ -35,6 +35,9 @@ const message = {
|
|||
},
|
||||
project_version: {
|
||||
version_time: '版本周期',
|
||||
},
|
||||
environment: {
|
||||
export_variable_tip : "导出接口测试变量"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue