Merge remote-tracking branch 'origin/master'

This commit is contained in:
2025-03-20 07:30:14 +08:00
13 changed files with 261 additions and 81 deletions

View File

@@ -22,14 +22,17 @@ public class MySQLExecutor {
* @param user 数据库用户名 * @param user 数据库用户名
* @param password 数据库密码 * @param password 数据库密码
* @param columnNameList 所有列名集合 * @param columnNameList 所有列名集合
* @param sqlSpecialResultMap 存储sql特有的响应信息字段结果值
* @return 查询结果(每行数据为一个 Map * @return 查询结果(每行数据为一个 Map
*/ */
public static List<Map<String, Object>> executeQuery(String sql, String url, String user, String password, List<String> columnNameList) { public static List<Map<String, Object>> executeQuery(String sql, String url, String user, String password, List<String> columnNameList, Map<String, String> sqlSpecialResultMap) {
List<Map<String, Object>> result = new ArrayList<>(); List<Map<String, Object>> result = new ArrayList<>();
try (Connection connection = DriverManager.getConnection(url, user, password); try (Connection connection = DriverManager.getConnection(url, user, password);
Statement statement = connection.createStatement()) { Statement statement = connection.createStatement()) {
// 执行查询限制最大100行 boolean isResultSet = statement.execute(sql.replaceAll(";", ""));
try (ResultSet resultSet = statement.executeQuery(sql.replaceAll(";", "") + " LIMIT 100")) { if (isResultSet) {
// 处理查询结果
ResultSet resultSet = statement.getResultSet();
// 获取结果集的元数据 // 获取结果集的元数据
ResultSetMetaData metaData = resultSet.getMetaData(); ResultSetMetaData metaData = resultSet.getMetaData();
int columnCount = metaData.getColumnCount(); int columnCount = metaData.getColumnCount();
@@ -52,6 +55,10 @@ public class MySQLExecutor {
} }
result.add(row); result.add(row);
} }
} else {
// 处理非查询语句
int rowsAffected = statement.getUpdateCount();
sqlSpecialResultMap.put("${AFFECT_ROWS}", String.valueOf(rowsAffected));
} }
} catch (SQLException e) { } catch (SQLException e) {
throw new RuntimeException(e); throw new RuntimeException(e);

View File

@@ -17,6 +17,7 @@ import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import java.util.List; import java.util.List;
import java.util.concurrent.CompletableFuture;
/** /**
* 用例Controller * 用例Controller
@@ -87,6 +88,10 @@ public class TestCaseController extends BaseController {
@PostMapping("/run") @PostMapping("/run")
public AjaxResult run(@RequestBody IDQO qo) { public AjaxResult run(@RequestBody IDQO qo) {
log.info("执行用例 id:{}, jmeterHomePath:{}", qo.getId(), jmeterHomePath); log.info("执行用例 id:{}, jmeterHomePath:{}", qo.getId(), jmeterHomePath);
return success(testCaseService.executeTestCaseById(qo.getId(), jmeterHomePath)); // 异步执行任务
CompletableFuture.runAsync(() -> {
testCaseService.executeTestCaseById(qo.getId(), jmeterHomePath);
});
return toAjax(true);
} }
} }

View File

@@ -6,16 +6,18 @@ import com.test.common.core.domain.AjaxResult;
import com.test.common.core.page.TableDataInfo; import com.test.common.core.page.TableDataInfo;
import com.test.common.enums.BusinessType; import com.test.common.enums.BusinessType;
import com.test.common.utils.DateUtils; import com.test.common.utils.DateUtils;
import com.test.test.domain.TestCase; import com.test.common.utils.SecurityUtils;
import com.test.test.domain.TestTask; import com.test.test.domain.TestTask;
import com.test.test.domain.qo.GroupIdQO; import com.test.test.domain.qo.GroupIdQO;
import com.test.test.domain.qo.IDQO; import com.test.test.domain.qo.IDQO;
import com.test.test.service.ITestTaskService; import com.test.test.service.ITestTaskService;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import java.util.List; import java.util.List;
import java.util.concurrent.CompletableFuture;
/** /**
* 定时任务Controller * 定时任务Controller
@@ -23,6 +25,10 @@ import java.util.List;
@RestController @RestController
@RequestMapping("/test/task") @RequestMapping("/test/task")
public class TestTaskController extends BaseController { public class TestTaskController extends BaseController {
@Value("${test.jmeterHomePath:/opt/apache-jmeter}")
private String jmeterHomePath;
@Resource @Resource
private ITestTaskService testTaskService; private ITestTaskService testTaskService;
@@ -73,5 +79,18 @@ public class TestTaskController extends BaseController {
return toAjax(testTaskService.deleteTestTaskById(qo.getId())); return toAjax(testTaskService.deleteTestTaskById(qo.getId()));
} }
/**
* 执行任务
*/
@Log(title = "任务", businessType = BusinessType.OTHER)
@PostMapping("/run")
public AjaxResult run(@RequestBody IDQO qo) {
// 异步执行任务
CompletableFuture.runAsync(() -> {
testTaskService.executeTestTaskById(qo.getId(), 2, null, jmeterHomePath, SecurityUtils.getUsername());
});
return toAjax(true);
}
} }

View File

@@ -280,8 +280,10 @@ public class TestCaseServiceImpl implements ITestCaseService
} }
String url = "jdbc:mysql://"+ testDatasource.getHost() +":"+ testDatasource.getPort() +"/" + testDatasource.getDbName(); String url = "jdbc:mysql://"+ testDatasource.getHost() +":"+ testDatasource.getPort() +"/" + testDatasource.getDbName();
List<String> columnNameList = new ArrayList<>(); List<String> columnNameList = new ArrayList<>();
// sql查询特有字段结果存储
Map<String, String> sqlSpecialResultMap = new HashMap<>();
// 获取所有sql查询集合对象 // 获取所有sql查询集合对象
List<Map<String, Object>> resultMapList = MySQLExecutor.executeQuery(testCaseStep.getSqlCommand(), url, testDatasource.getUsername(), testDatasource.getPassword(), columnNameList); List<Map<String, Object>> resultMapList = MySQLExecutor.executeQuery(testCaseStep.getSqlCommand(), url, testDatasource.getUsername(), testDatasource.getPassword(), columnNameList, sqlSpecialResultMap);
if (!CollectionUtils.isEmpty(resultMapList)) { if (!CollectionUtils.isEmpty(resultMapList)) {
SqlResult sqlResult = new SqlResult(); SqlResult sqlResult = new SqlResult();
sqlResult.setColumnNameList(columnNameList); sqlResult.setColumnNameList(columnNameList);
@@ -293,7 +295,7 @@ public class TestCaseServiceImpl implements ITestCaseService
testCaseResult.setSqlResult(gson.toJson(sqlResult)); testCaseResult.setSqlResult(gson.toJson(sqlResult));
// 处理参数提取 // 处理参数提取
Map<String, String> assignmentResultMap = new HashMap<>(); Map<String, String> assignmentResultMap = new HashMap<>();
String assignmentResult = this.dealDataSourceTestCaseStepAssignment(resultMapList, assignmentResultMap, assignment); String assignmentResult = this.dealDataSourceTestCaseStepAssignment(resultMapList, assignmentResultMap, sqlSpecialResultMap, assignment);
testCaseResult.setAssignment(assignmentResult); testCaseResult.setAssignment(assignmentResult);
// 根据提取结果处理校验规则 // 根据提取结果处理校验规则
String assertionResult = this.dealTestCaseStepAssertion(assignmentResultMap, assertion); String assertionResult = this.dealTestCaseStepAssertion(assignmentResultMap, assertion);
@@ -522,10 +524,11 @@ public class TestCaseServiceImpl implements ITestCaseService
* 处理数据源响应字段提取 * 处理数据源响应字段提取
* @param resultMapList * @param resultMapList
* @param assignmentResultMap * @param assignmentResultMap
* @param sqlSpecialResultMap 存储sql特有的响应信息字段结果值
* @param assignment * @param assignment
* @return 处理后的提取结果 * @return 处理后的提取结果
*/ */
private String dealDataSourceTestCaseStepAssignment(List<Map<String, Object>> resultMapList, Map<String, String> assignmentResultMap, String assignment) { private String dealDataSourceTestCaseStepAssignment(List<Map<String, Object>> resultMapList, Map<String, String> assignmentResultMap, Map<String, String> sqlSpecialResultMap, String assignment) {
if (!StringUtils.isEmpty(assignment) && !"[]".equals(assignment)) { if (!StringUtils.isEmpty(assignment) && !"[]".equals(assignment)) {
Gson gson = new Gson(); Gson gson = new Gson();
String responseBody = gson.toJson(resultMapList); String responseBody = gson.toJson(resultMapList);
@@ -547,6 +550,20 @@ public class TestCaseServiceImpl implements ITestCaseService
String value = JMeterUtil.extractFieldValue(responseBody, path); String value = JMeterUtil.extractFieldValue(responseBody, path);
ruleResult.setValue(value); ruleResult.setValue(value);
} }
} else if ("${RESULT_DATA_LENGTH}".equals(content)) {
if (!CollectionUtils.isEmpty(resultMapList)) {
String value = String.valueOf(resultMapList.size());
ruleResult.setValue(value);
} else {
ruleResult.setValue("0");
}
} else if ("${AFFECT_ROWS}".equals(content) || "${CHANGE_ROWS}".equals(content)) {
String affectRows = sqlSpecialResultMap.get("${AFFECT_ROWS}");
if (affectRows == null) {
ruleResult.setValue("0");
} else {
ruleResult.setValue(affectRows);
}
} }
assignmentResultMap.put(rule.getName(), ruleResult.getValue()); assignmentResultMap.put(rule.getName(), ruleResult.getValue());
ruleResultList.add(ruleResult); ruleResultList.add(ruleResult);
@@ -576,6 +593,13 @@ public class TestCaseServiceImpl implements ITestCaseService
String source = rule.getSource(); String source = rule.getSource();
String target = rule.getTarget(); String target = rule.getTarget();
String fn = rule.getFn(); String fn = rule.getFn();
if (source.equals("${RESPONSE_BODY}")) {
source = "responseBody";
} else if (source.equals("${RESPONSE_CODE}")) {
source = "responseCode";
} else if (source.equals("${RESPONSE_HEADER}")) {
source = "responseHeader";
}
String value = assignmentResultMap.get(source); String value = assignmentResultMap.get(source);
ruleResult.setValue(value); ruleResult.setValue(value);
if ("等于".equals(fn)) { if ("等于".equals(fn)) {

View File

@@ -63,7 +63,7 @@ public class TestCaseStepServiceImpl implements ITestCaseStepService {
int i = 1; int i = 1;
for (TestCaseResult testCaseResult : testCaseResultList) { for (TestCaseResult testCaseResult : testCaseResultList) {
String dateStr = DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, testCaseResult.getExecuteTime()); String dateStr = DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, testCaseResult.getExecuteTime());
testCaseResult.setTitle("结果" + i + "" + dateStr + "),状态:" + testCaseResult.getStatus()); testCaseResult.setTitle("结果" + i + "" + dateStr + "),状态:" + testCaseResult.getStatus() + "耗时ms" + testCaseResult.getUseTime());
i ++; i ++;
} }
} }

View File

@@ -110,9 +110,8 @@ public class TestTaskServiceImpl implements ITestTaskService {
@Override @Override
public boolean executeTestTaskById(Long id, Integer triggerType, String environment, String jmeterHomePath, String operUser) { public boolean executeTestTaskById(Long id, Integer triggerType, String environment, String jmeterHomePath, String operUser) {
TestTask testTask = testTaskMapper.selectTestTaskById(id); TestTask testTask = testTaskMapper.selectTestTaskById(id);
if (testTask == null || testTask.getStatus() == null if (testTask == null || "2".equals(testTask.getDelFlag())) {
|| testTask.getStatus() > 0 || "2".equals(testTask.getDelFlag())) { log.error("定时任务已删除,不能执行!");
log.error("定时任务已删除或未启用,不能执行!");
return false; return false;
} }
TestTaskLog testTaskLog = new TestTaskLog(); TestTaskLog testTaskLog = new TestTaskLog();

View File

@@ -44,3 +44,12 @@ export function delTask(id) {
data: {id} data: {id}
}) })
} }
// 执行用例
export function runTask(id) {
return request({
url: '/test/task/run',
method: 'post',
data: {id}
})
}

View File

@@ -1,13 +1,29 @@
<template> <template>
<div class="app-container" v-loading="loading"> <div class="app-container" v-loading="loading">
<el-form :model="form" ref="form"> <el-form :model="form" ref="form">
<el-form-item label="用例名称" prop="name"> <el-row :gutter="20">
<el-input v-model="form.name" placeholder="请输入用例名称"/> <el-col :span="12">
</el-form-item> <el-form-item label="用例名称" prop="name">
<el-input v-model="name" placeholder="请输入用例名称" :disabled="isDisabled"/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="用例状态" prop="status">
<el-select v-model="status" placeholder="请选择用例状态" :disabled="isDisabled">
<el-option
v-for="item in caseStatusOptions"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
</el-form> </el-form>
<el-tabs> <el-tabs>
<el-tab-pane label="用例编排"> <el-tab-pane label="接口编排">
<step @submit="submit"/> <step @data-from-child="handleChildData" @submit="submit"/>
</el-tab-pane> </el-tab-pane>
<!-- <el-tab-pane label="操作日志">--> <!-- <el-tab-pane label="操作日志">-->
<!-- </el-tab-pane>--> <!-- </el-tab-pane>-->
@@ -27,19 +43,37 @@ export default {
getCase(this.$route.query.id).then(res => { getCase(this.$route.query.id).then(res => {
this.form = res.data; this.form = res.data;
this.name = res.data.name this.name = res.data.name
this.status = res.data.status
}) })
}, },
data(){ data(){
return { return {
form: {}, form: {},
name: "", name: "",
status: "",
isDisabled: true,
caseStatusOptions: [{
value: 1,
label: '草稿'
}, {
value: 2,
label: '通过'
}, {
value: 3,
label: '不通过'
}],
loading: false loading: false
} }
}, },
methods: { methods: {
handleChildData(data) {
this.isDisabled = data;
},
submit(list) { submit(list) {
this.loading = true; this.loading = true;
if (this.form.name !== this.name) { if (this.form.name !== this.name || this.form.status !== this.status) {
this.form.name = this.name;
this.form.status = this.status;
updateCase(this.form).then(res => { updateCase(this.form).then(res => {
this.saveStep(list) this.saveStep(list)
}) })
@@ -96,7 +130,7 @@ export default {
}).then(res => { }).then(res => {
this.$message.success("修改成功"); this.$message.success("修改成功");
this.loading = false; this.loading = false;
this.$tab.closeOpenPage({path: "/case"}); // this.$tab.closeOpenPage({path: "/case"});
}) })
} }
} }

View File

@@ -99,7 +99,7 @@
<el-table-column label="提取对象"> <el-table-column label="提取对象">
<template slot-scope="scope"> <template slot-scope="scope">
<el-select v-model="form.assignment[scope.$index].content" style="width: 100%;" placeholder="请选择提取对象"> <el-select v-model="form.assignment[scope.$index].content" style="width: 100%;" placeholder="请选择提取对象">
<el-option v-for="dict in dict.type.extract_object" :key="dict.value" :label="dict.label" :value="dict.value" @change="e => handleTableEdit(e, 'assignment', scope)"/> <el-option v-for="dict in extract_object" :key="dict.value" :label="dict.label" :value="dict.value" @change="e => handleTableEdit(e, 'assignment', scope)"/>
</el-select> </el-select>
<!-- <el-input placeholder="请输入提取对象" v-model="form.assignment[scope.$index].content" @input="e => handleTableEdit(e, 'assignment', scope)" clearable/>--> <!-- <el-input placeholder="请输入提取对象" v-model="form.assignment[scope.$index].content" @input="e => handleTableEdit(e, 'assignment', scope)" clearable/>-->
</template> </template>
@@ -130,9 +130,6 @@
<el-tab-pane label="响应体" name="responseBody"> <el-tab-pane label="响应体" name="responseBody">
<codemirror v-model="resultLog.responseBody" :options="editorOptions" v-if="resultLog.responseBody"/> <codemirror v-model="resultLog.responseBody" :options="editorOptions" v-if="resultLog.responseBody"/>
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="sql结果" name="sqlResult">
<codemirror v-model="resultLog.sqlResult" :options="editorOptions" v-if="resultLog.sqlResult"/>
</el-tab-pane>
</el-tabs> </el-tabs>
</el-collapse-item> </el-collapse-item>
</el-collapse> </el-collapse>
@@ -146,7 +143,10 @@
</el-table-column> </el-table-column>
<el-table-column label="对象"> <el-table-column label="对象">
<template slot-scope="scope"> <template slot-scope="scope">
<el-input placeholder="请输入对象" v-model="form.assertion[scope.$index].source" @input="e => handleTableEdit(e, 'assertion', scope)" clearable/> <el-select v-model="form.assertion[scope.$index].source" style="width: 100%;" filterable allow-create placeholder="请选择对象">
<el-option v-for="dict in extract_object" :key="dict.value" :label="dict.label" :value="dict.value" @change="e => handleTableEdit(e, 'assertion', scope)"/>
</el-select>
<!-- <el-input placeholder="请输入对象" v-model="form.assertion[scope.$index].source" @input="e => handleTableEdit(e, 'assertion', scope)" clearable/>-->
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="条件"> <el-table-column label="条件">
@@ -206,7 +206,7 @@ import {getCaseStepResultLog} from "@/api/test/caseStep";
export default { export default {
components: {FolderPage, JsonEditorVue, codemirror}, components: {FolderPage, JsonEditorVue, codemirror},
dicts: ['http_method', 'http_protocol','extract_type','extract_object','assert_condition'], dicts: ['http_method', 'http_protocol','extract_type','assert_condition'],
props: { props: {
form: { form: {
type: Object type: Object
@@ -231,6 +231,16 @@ export default {
pageSize: 10, pageSize: 10,
groupId: null, groupId: null,
}, },
extract_object: [{
value: '${RESPONSE_BODY}',
label: '${RESPONSE_BODY}'
}, {
value: '${RESPONSE_HEADER}',
label: '${RESPONSE_HEADER}'
}, {
value: '${RESPONSE_CODE}',
label: '${RESPONSE_CODE}'
}],
resultLog: { resultLog: {
requestHeader: '', requestHeader: '',
requestBody: '', requestBody: '',
@@ -238,7 +248,6 @@ export default {
responseBody: '', responseBody: '',
sqlResult: '' sqlResult: ''
}, },
title: '111111',
activeNames: 'requestHeader', activeNames: 'requestHeader',
activeTabs: 'requestHeader', activeTabs: 'requestHeader',
editorOptions: { editorOptions: {

View File

@@ -20,12 +20,18 @@
</el-table-column> </el-table-column>
<el-table-column label="提取方式"> <el-table-column label="提取方式">
<template slot-scope="scope"> <template slot-scope="scope">
<el-input placeholder="请输入提取方式" v-model="form.assignment[scope.$index].type" @input="e => handleTableEdit(e, 'assignment', scope)" clearable/> <el-select v-model="form.assignment[scope.$index].type" style="width: 100%;" placeholder="请选择提取方式">
<el-option v-for="dict in dict.type.extract_type" :key="dict.value" :label="dict.label" :value="dict.value" @change="e => handleTableEdit(e, 'assignment', scope)"/>
</el-select>
<!-- <el-input placeholder="请输入提取方式" v-model="form.assignment[scope.$index].type" @input="e => handleTableEdit(e, 'assignment', scope)" clearable/>-->
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="提取对象"> <el-table-column label="提取对象">
<template slot-scope="scope"> <template slot-scope="scope">
<el-input placeholder="请输入提取对象" v-model="form.assignment[scope.$index].content" @input="e => handleTableEdit(e, 'assignment', scope)" clearable/> <el-select v-model="form.assignment[scope.$index].content" style="width: 100%;" placeholder="请选择提取对象">
<el-option v-for="dict in extract_object" :key="dict.value" :label="dict.label" :value="dict.value" @change="e => handleTableEdit(e, 'assignment', scope)"/>
</el-select>
<!-- <el-input placeholder="请输入提取对象" v-model="form.assignment[scope.$index].content" @input="e => handleTableEdit(e, 'assignment', scope)" clearable/>-->
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="提取表达式"> <el-table-column label="提取表达式">
@@ -39,6 +45,15 @@
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<el-collapse v-model="activeNames" accordion v-if="form.testCaseResultList" @change="handleLogChange">
<el-collapse-item :title="itemSub.title" :name="itemSub.title" v-for="(itemSub, index) in form.testCaseResultList" :key="index">
<el-tabs v-model="activeTabs">
<el-tab-pane label="sql结果" name="sqlResult">
<codemirror v-model="resultLog.sqlResult" :options="editorOptions" v-if="resultLog.sqlResult"/>
</el-tab-pane>
</el-tabs>
</el-collapse-item>
</el-collapse>
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="校验" name="assertion"> <el-tab-pane label="校验" name="assertion">
<el-table :data="form.assertion"> <el-table :data="form.assertion">
@@ -49,12 +64,18 @@
</el-table-column> </el-table-column>
<el-table-column label="对象"> <el-table-column label="对象">
<template slot-scope="scope"> <template slot-scope="scope">
<el-input placeholder="请输入对象" v-model="form.assertion[scope.$index].source" @input="e => handleTableEdit(e, 'assertion', scope)" clearable/> <el-select v-model="form.assertion[scope.$index].source" style="width: 100%;" filterable allow-create placeholder="请选择对象">
<el-option v-for="dict in extract_object" :key="dict.value" :label="dict.label" :value="dict.value" @change="e => handleTableEdit(e, 'assertion', scope)"/>
</el-select>
<!-- <el-input placeholder="请输入对象" v-model="form.assertion[scope.$index].source" @input="e => handleTableEdit(e, 'assertion', scope)" clearable/>-->
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="条件"> <el-table-column label="条件">
<template slot-scope="scope"> <template slot-scope="scope">
<el-input placeholder="请输入条件" v-model="form.assertion[scope.$index].fn" @input="e => handleTableEdit(e, 'assertion', scope)" clearable/> <el-select v-model="form.assertion[scope.$index].fn" style="width: 100%;" placeholder="请选择条件">
<el-option v-for="dict in dict.type.assert_condition" :key="dict.value" :label="dict.label" :value="dict.value" @change="e => handleTableEdit(e, 'assertion', scope)"/>
</el-select>
<!-- <el-input placeholder="请输入条件" v-model="form.assertion[scope.$index].fn" @input="e => handleTableEdit(e, 'assertion', scope)" clearable/>-->
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="内容"> <el-table-column label="内容">
@@ -79,9 +100,11 @@ import "codemirror/lib/codemirror.css";
import "codemirror/mode/sql/sql"; // 语言模式 import "codemirror/mode/sql/sql"; // 语言模式
import "codemirror/theme/dracula.css"; import "codemirror/theme/dracula.css";
import "codemirror/addon/display/autorefresh"; import "codemirror/addon/display/autorefresh";
import {getCaseStepResultLog} from "@/api/test/caseStep";
export default { export default {
components: { codemirror }, components: { codemirror },
dicts: ['extract_type','assert_condition'],
props: { props: {
form: { form: {
type: Object type: Object
@@ -93,6 +116,33 @@ export default {
data() { data() {
return { return {
activeName: "sql", activeName: "sql",
extract_object: [{
value: '${RESULT_DATA}',
label: '${RESULT_DATA}'
},{
value: '${RESULT_DATA_LENGTH}',
label: '${RESULT_DATA_LENGTH}'
},
// {
// value: '${INSERT_ID}',
// label: '${INSERT_ID}'
// },
{
value: '${AFFECT_ROWS}',
label: '${AFFECT_ROWS}'
},{
value: '${CHANGE_ROWS}',
label: '${CHANGE_ROWS}'
}],
resultLog: {
requestHeader: '',
requestBody: '',
responseHeader: '',
responseBody: '',
sqlResult: ''
},
activeNames: 'sqlResult',
activeTabs: 'sqlResult',
editorOptions: { editorOptions: {
autoRefresh: true, autoRefresh: true,
mode: "sql", // 代码语言 mode: "sql", // 代码语言
@@ -102,6 +152,16 @@ export default {
} }
} }
}, },
methods: {
handleLogChange(val) {
const selectedItem = this.form.testCaseResultList.find(item => item.title === val);
if (selectedItem) {
getCaseStepResultLog(selectedItem.id).then(res => {
this.resultLog = res.data;
});
}
},
}
} }
</script> </script>

View File

@@ -1,14 +1,18 @@
<template> <template>
<div v-loading="loading"> <div v-loading="loading">
<div style="margin-bottom:20px;"> <div style="margin-bottom:20px;" v-if="isVisible">
<el-dropdown trigger="click" @command="handleAdd"> <el-dropdown trigger="click" @command="handleAdd">
<el-button type="primary" plain icon="el-icon-plus" size="mini">添加步骤</el-button> <el-button type="primary" plain icon="el-icon-plus" size="mini">添加步骤</el-button>
<el-button type="primary" plain icon="el-icon-caret-right" size="mini" @click="handleRun(id)">调试</el-button> <el-button type="primary" plain icon="el-icon-caret-right" size="mini" :disabled="isVisible">调试</el-button>
<el-dropdown-menu slot="dropdown"> <el-dropdown-menu slot="dropdown">
<el-dropdown-item v-for="dict in dict.type.step_type" :command="dict.value" :key="dict.value">{{ dict.label }}</el-dropdown-item> <el-dropdown-item v-for="dict in dict.type.step_type" :command="dict.value" :key="dict.value">{{ dict.label }}</el-dropdown-item>
</el-dropdown-menu> </el-dropdown-menu>
</el-dropdown> </el-dropdown>
</div> </div>
<div style="margin-bottom:20px;" v-if="!isVisible">
<el-button type="primary" plain icon="el-icon-edit" size="mini" @click="caseEditVisible">用例编排</el-button>
<el-button type="primary" plain icon="el-icon-caret-right" size="mini" @click="handleRun(id)">调试</el-button>
</div>
<div> <div>
</div> </div>
<el-collapse v-model="activeName" accordion v-if="list && list.length"> <el-collapse v-model="activeName" accordion v-if="list && list.length">
@@ -32,9 +36,14 @@
</el-collapse> </el-collapse>
<el-empty v-else/> <el-empty v-else/>
<div class="footer"> <div class="footer">
<el-button type="primary" @click="submit"> </el-button> <el-button type="primary" @click="submit" v-if="isVisible"> </el-button>
<el-button @click="cancel"> </el-button> <el-button @click="cancel"> </el-button>
</div> </div>
<el-dialog title="操作日志详细" :visible.sync="open" >
<div v-if="open">
<report :sid="caseSID"/>
</div>
</el-dialog>
</div> </div>
</template> </template>
@@ -48,18 +57,22 @@ import page3 from "./page3.vue"
import page4 from "./page4.vue" import page4 from "./page4.vue"
import {listHttp} from "@/api/test/http"; import {listHttp} from "@/api/test/http";
import {listDatasource} from "@/api/test/database"; import {listDatasource} from "@/api/test/database";
import Report from "@/views/test/case/report/index.vue";
export default { export default {
dicts: ['step_type'], dicts: ['step_type'],
components: {draggable, page1, page2, page3, page4}, components: {Report, draggable, page1, page2, page3, page4},
data() { data() {
return { return {
id: '', id: '',
isVisible: false,
loading: false, loading: false,
open: false,
activeName: null, activeName: null,
list: [], list: [],
hosts: [], hosts: [],
datasourceList: [], datasourceList: [],
caseSID: null,
}; };
}, },
created() { created() {
@@ -145,11 +158,17 @@ export default {
}, },
methods: { methods: {
caseEditVisible() {
this.isVisible = true;
this.$emit('data-from-child', false);
},
handleRun(id) { handleRun(id) {
this.$modal.confirm('是否确认执行用例?').then(function () { this.$modal.confirm('是否确认执行用例?').then(function () {
return runCase(id); return runCase(id);
}).then(() => { }).then(() => {
this.$modal.msgSuccess("执行成功"); this.$modal.msgSuccess("执行成功");
this.caseSID = res.msg
this.open = true;
}); });
}, },
// 拖拽结束后更新排序值 // 拖拽结束后更新排序值
@@ -197,10 +216,14 @@ export default {
}); });
}, },
submit() { submit() {
this.isVisible = false;
this.$emit('data-from-child', true);
this.$emit("submit", this.list); this.$emit("submit", this.list);
}, },
cancel() { cancel() {
this.$tab.closeOpenPage({path: "/case"}); this.isVisible = false;
this.$tab.refreshPage({path: "/case/detail/?id=" + this.id})
// this.$tab.closeOpenPage({path: "/case"});
}, },
handleDel(index) { handleDel(index) {
this.list.splice(index, 1) this.list.splice(index, 1)

View File

@@ -3,23 +3,23 @@
<el-collapse-item :name="index" v-for="(item, index) in list" :key="index" v-if="!item.parentId"> <el-collapse-item :name="index" v-for="(item, index) in list" :key="index" v-if="!item.parentId">
<template slot="title"> <template slot="title">
<el-button size="mini" plain type="warning" icon="el-icon-rank" circle style="margin-right: 12px" class="drag-handle"/> <el-button size="mini" plain type="warning" icon="el-icon-rank" circle style="margin-right: 12px" class="drag-handle"/>
<el-tag effect="plain" style="width: 70px;text-align: center;margin-right: 12px;" class="drag-handle">{{ dict.type.step_type.findLast(t => t.value == item.type).label }}</el-tag> <el-tag effect="plain" style="width: 70px;text-align: center;margin-right: 12px;" class="drag-handle">{{ dict.type.step_type.findLast(t => t.value == item.type) ? dict.type.step_type.findLast(t => t.value == item.type).label : "?" }}</el-tag>
<el-tag v-if="item.name" effect="plain" style="width: 240px;text-align: center;margin-right: 12px;" type="info" class="drag-handle">{{ item.name }}</el-tag> <el-tag v-if="item.name" effect="plain" style="width: 240px;text-align: center;margin-right: 12px;" type="info" class="drag-handle">{{ item.name }}</el-tag>
<el-tag v-if="item.datasourceId && item.type < 3" effect="plain" style="width: 240px;text-align: center;margin-right: 12px;" type="info" class="drag-handle">{{ datasourceList.findLast(t => t.id === item.datasourceId).name }}</el-tag> <el-tag v-if="item.datasourceId && item.type < 3" effect="plain" style="width: 240px;text-align: center;margin-right: 12px;" type="info" class="drag-handle">{{ datasourceList.findLast(t => t.id === item.datasourceId) ? datasourceList.findLast(t => t.id === item.datasourceId).name : "?"}}</el-tag>
<el-tag v-if="item.requestUrl" effect="plain" style="text-align: center;margin-right: 12px;" type="success" class="drag-handle">{{ item.requestUrl }}</el-tag> <el-tag v-if="item.requestUrl" effect="plain" style="text-align: center;margin-right: 12px;" type="success" class="drag-handle">{{ item.requestUrl }}</el-tag>
<el-tag effect="plain" style="text-align: center;margin-right: 12px;" class="drag-handle">{{ index + 1 }}</el-tag> <el-tag effect="plain" style="text-align: center;margin-right: 12px;" class="drag-handle">{{ index + 1 }}</el-tag>
</template> </template>
<el-tabs v-model="tabsActiveName" v-if="item.type == 2 || item.type == 1"> <el-tabs v-model="tabsActiveName" v-if="item.type == 2 || item.type == 1">
<el-tab-pane v-if="item.type == 1" label="Headers" name="Headers"> <el-tab-pane v-if="item.type == 1" label="Headers" name="Headers">
<el-table :data="form.requestHeader"> <el-table :data="item.requestHeader">
<el-table-column label="参数名" prop="key" /> <el-table-column label="参数名" prop="key" />
<el-table-column label="示例值" prop="value" /> <el-table-column label="值" prop="value" />
</el-table> </el-table>
</el-tab-pane> </el-tab-pane>
<el-tab-pane v-if="item.type == 1" label="Params" name="Params"> <el-tab-pane v-if="item.type == 1" label="Params" name="Params">
<el-table :data="form.requestParams"> <el-table :data="item.requestParams">
<el-table-column label="参数名" prop="key" /> <el-table-column label="参数名" prop="key" />
<el-table-column label="示例值" prop="value" /> <el-table-column label="值" prop="value" />
</el-table> </el-table>
</el-tab-pane> </el-tab-pane>
<el-tab-pane v-if="item.type == 1" label="Body" name="Body"> <el-tab-pane v-if="item.type == 1" label="Body" name="Body">
@@ -28,12 +28,16 @@
<el-tab-pane v-if="item.type == 2" label="SQL指令" name="sql"> <el-tab-pane v-if="item.type == 2" label="SQL指令" name="sql">
<div>{{item.sqlCommand}}</div> <div>{{item.sqlCommand}}</div>
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="提取" name="assignment"> <el-tab-pane v-if="item.type == 2" label="结果" name="sql">
<el-table :data="item.sqlResult"> <JsonEditorVue mode="text" v-model="item.sqlResult" class="jse-theme-dark"/>
</el-tab-pane>
<el-tab-pane v-if="item.type == 1" label="提取" name="assignment">
<el-table :data="item.assignment">
<el-table-column label="变量名" prop="name" /> <el-table-column label="变量名" prop="name" />
<el-table-column label="提取方式" prop="type" /> <el-table-column label="提取方式" prop="type" />
<el-table-column label="提取对象" prop="content" /> <el-table-column label="提取对象" prop="content" />
<el-table-column label="提取表达式" prop="path" /> <el-table-column label="提取表达式" prop="path" />
<el-table-column label="提取结果" prop="value" />
</el-table> </el-table>
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="校验" name="assertion"> <el-tab-pane label="校验" name="assertion">
@@ -42,6 +46,8 @@
<el-table-column label="对象" prop="source" /> <el-table-column label="对象" prop="source" />
<el-table-column label="条件" prop="fn" /> <el-table-column label="条件" prop="fn" />
<el-table-column label="内容" prop="target" /> <el-table-column label="内容" prop="target" />
<el-table-column label="实际值" prop="value" />
<el-table-column label="结果" prop="result" />
</el-table> </el-table>
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
@@ -57,55 +63,33 @@
</template> </template>
<el-tabs v-model="childrenTabsActiveName" v-if="item.type == 2 || item.type == 1"> <el-tabs v-model="childrenTabsActiveName" v-if="item.type == 2 || item.type == 1">
<el-tab-pane v-if="item.type == 1" label="Headers" name="Headers"> <el-tab-pane v-if="item.type == 1" label="Headers" name="Headers">
<el-table :data="form.requestHeader"> <el-table :data="item.requestHeader">
<el-table-column label="参数名"> <el-table-column label="参数名" prop="key" />
<template slot-scope="scope"> <el-table-column label="值" prop="value" />
<el-input placeholder="请输入参数名" v-model="form.requestHeader[scope.$index].key" @input="e => handleTableEdit(e, 'header', scope)" clearable/>
</template>
</el-table-column>
<el-table-column label="示例值">
<template slot-scope="scope">
<el-input placeholder="请输入参数名" v-model="form.requestHeader[scope.$index].value" @input="e => handleTableEdit(e, 'header', scope)" clearable/>
</template>
</el-table-column>
<el-table-column label="操作" width="60">
<template slot-scope="scope">
<el-button v-if="form.requestHeader.length > scope.$index+1" size="mini" type="text" icon="el-icon-delete" @click="handleDelete('header', scope)">删除</el-button>
</template>
</el-table-column>
</el-table> </el-table>
</el-tab-pane> </el-tab-pane>
<el-tab-pane v-if="item.type == 1" label="Params" name="Params"> <el-tab-pane v-if="item.type == 1" label="Params" name="Params">
<el-table :data="form.requestParams"> <el-table :data="item.requestParams">
<el-table-column label="参数名"> <el-table-column label="参数名" prop="key" />
<template slot-scope="scope"> <el-table-column label="值" prop="value" />
<el-input placeholder="请输入参数名" v-model="form.requestParams[scope.$index].key" @input="e => handleTableEdit(e, 'param', scope)" clearable/>
</template>
</el-table-column>
<el-table-column label="示例值">
<template slot-scope="scope">
<el-input placeholder="请输入示例值" v-model="form.requestParams[scope.$index].value" @input="e => handleTableEdit(e, 'param', scope)" clearable/>
</template>
</el-table-column>
<el-table-column label="操作" width="60">
<template slot-scope="scope">
<el-button v-if="form.requestParams.length > scope.$index+1" size="mini" type="text" icon="el-icon-delete" @click="handleDelete('param', scope)">删除</el-button>
</template>
</el-table-column>
</el-table> </el-table>
</el-tab-pane> </el-tab-pane>
<el-tab-pane v-if="item.type == 1" label="Body" name="Body"> <el-tab-pane v-if="item.type == 1" label="Body" name="Body">
<JsonEditorVue mode="text" v-model="form.requestBody" class="jse-theme-dark"/> <JsonEditorVue mode="text" v-model="item.requestBody" class="jse-theme-dark"/>
</el-tab-pane> </el-tab-pane>
<el-tab-pane v-if="item.type == 2" label="SQL指令" name="sql"> <el-tab-pane v-if="item.type == 2" label="SQL指令" name="sql">
<div>{{item.sqlCommand}}</div> <div>{{item.sqlCommand}}</div>
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="提取" name="assignment"> <el-tab-pane v-if="item.type == 2" label="结果" name="sql">
<el-table :data="item.sqlResult"> <JsonEditorVue mode="text" v-model="item.sqlResult" class="jse-theme-dark"/>
</el-tab-pane>
<el-tab-pane v-if="item.type == 1" label="提取" name="assignment">
<el-table :data="item.assignment">
<el-table-column label="变量名" prop="name" /> <el-table-column label="变量名" prop="name" />
<el-table-column label="提取方式" prop="type" /> <el-table-column label="提取方式" prop="type" />
<el-table-column label="提取对象" prop="content" /> <el-table-column label="提取对象" prop="content" />
<el-table-column label="提取表达式" prop="path" /> <el-table-column label="提取表达式" prop="path" />
<el-table-column label="提取结果" prop="value" />
</el-table> </el-table>
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="校验" name="assertion"> <el-tab-pane label="校验" name="assertion">
@@ -114,6 +98,8 @@
<el-table-column label="对象" prop="source" /> <el-table-column label="对象" prop="source" />
<el-table-column label="条件" prop="fn" /> <el-table-column label="条件" prop="fn" />
<el-table-column label="内容" prop="target" /> <el-table-column label="内容" prop="target" />
<el-table-column label="实际值" prop="value" />
<el-table-column label="结果" prop="result" />
</el-table> </el-table>
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>

View File

@@ -10,7 +10,7 @@
<el-table v-loading="loading" :data="dataList" @row-click="handleRowClick"> <el-table v-loading="loading" :data="dataList" @row-click="handleRowClick">
<el-table-column label="自动化测试名称" align="center" prop="name"/> <el-table-column label="自动化测试名称" align="center" prop="name"/>
<el-table-column label="cron表达式" align="center" prop="crontab"/> <el-table-column label="cron表达式" align="center" prop="crontab"/>
<el-table-column label="定时任务开关" align="center" prop="status" :formatter="row => row.status ? '开启' : '关闭'"/> <el-table-column label="定时任务开关" align="center" prop="status" :formatter="row => row.status === 0? '开启' : '关闭'"/>
<el-table-column label="创建人" align="center" prop="createBy"/> <el-table-column label="创建人" align="center" prop="createBy"/>
<el-table-column label="创建时间" align="center" prop="createTime"/> <el-table-column label="创建时间" align="center" prop="createTime"/>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width"> <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
@@ -29,7 +29,7 @@
<script> <script>
import FolderPage from "@/components/FolderPage/index.vue"; import FolderPage from "@/components/FolderPage/index.vue";
import {addTask, delTask, listTask} from "@/api/test/task"; import {addTask, delTask, listTask, runTask} from "@/api/test/task";
export default { export default {
name: "Task", name: "Task",
@@ -94,7 +94,12 @@ export default {
}); });
}, },
handleRun(id) { handleRun(id) {
this.$modal.confirm('是否确认立即执行任务?').then(function () {
return runTask(id);
}).then(() => {
this.getList();
this.$modal.msgSuccess("执行成功");
});
} }
} }
} }