在涉及自动化相关的工作中,代码和工具总是完全互斥。两者无法相互迁移,投票时支持用代码实现自动化和用工具支持自动化的人数也不相上下。
用代码实现自动化总会越来越难交接维护。因为大家思路各不相同,有人喜欢简单的数据类型,有人喜欢层层嵌套的集合对象,有时无限解耦到@Test中只有一行代码,有时又能见到@Test中一堆逻辑判断。无注释和不会写也比较常见。而测试又不像开发,有足够的动力和资源投入到代码中,导致自动化项目会越来越凌乱。
而用工具则难以推广。对于管理来说,工具总是更好管理。但对于写自动化的人来说,工具的功能再强大也强不过代码,总会有一些特殊场景工具难以支持,以至于让人弃用。另外,在高熟练度的前提下,一直UI点击并没有比敲代码快,还费手腕。也就是说纯粹的UI操作并不是最理想状态。
让代码和UI相互关联是否能解决此问题?
支持三种编写自动化用例的模式:代码模式、脚本模式、UI模式。
源码见附件
可实现简单的封装、继承、赋值、重载,和@Test、@BeforeClass、@AfterClass、@BeforeSuite、@AfterSuite。
编辑脚本时,可切换脚本模式和UI模式,脚本模式中的脚本可与自动化测试代码相互联动。
模块管理
一个模块相当于一个测试包(project中的测试package),同时也相当于模板中测试类的超类。可设置通用参数,selenium启动参数,@BeforeSuite,@BeforeClass,@AfterClass,@AfterSuite。
用例管理
一个用例相当于一个测试类(有@Test的类),用例必须存在于某一测试模块中,用例中可设置@BeforeClass,@Test,@AfterClass,可随意切换UI或Coding模式,UI模式下点击步骤可进入步骤详情。
套件管理
套件可以包含多个模块的用例,用例列表可同时包括UI和其他接口用例。可以本地执行,任意节点执行,或指定多个节点执行。当指定多个节点时,会分批并行。支持批量重试和单个重试
工作台
可查看任务日历和统计报表,日历中可添加任务
设置
可以设置人员、项目、数据源和执行节点;数据源在执行sql步骤时会用到,执行节点在执行套件时会用到。
套件执行流程
用例执行流程
步骤执行流程
/**
* 自动化执行器
* 每个执行自动化的线程只实例化一个执行器
*/
public class AutoExecutor {
// 一个套件都可能包含多个测试模块,每个测试模块(超类)需对应一个RootClient,主要是WebDriver会不同
private final Map<Integer, RootClient> clientMap = new HashMap<>();
private final Map<Integer, List<StepDTO>> afterSuiteMap = new HashMap<>();
/**
* 使用指定客户端执行单个步骤
*/
private void executeStep(StepDTO stepDTO, RootClient auto) {
String varName;
switch (ModuleTypeEnum.fromCode(stepDTO.getModuleType())) {
case SQL:
varName = auto.sql.execute(stepDTO);
break;
case RPC:
varName = auto.rpc.execute(stepDTO);
break;
case HTTP:
varName = auto.http.execute(stepDTO);
break;
case UI:
varName = auto.ui.execute(stepDTO);
break;
case UTIL:
varName = auto.util.execute(stepDTO);
break;
case ASSERTION:
varName = auto.assertion.execute(stepDTO);
break;
default:
varName = DefaultEnum.DEFAULT_CLIENT_ERROR.getValue();
}
stepDTO.setResult(varName);
}
/**
* 处理入参中的变量(相当于传参)
* @param params 包含变量名和变量值的映射关系
*/
private void mergeParam(StepDTO stepDTO, Map<String, String> params) {
if (params == null || params.size() == 0) {
return;
}
List<String> varNames1 = stepDTO.getParameter1() == null ? new ArrayList<>() : StringUtil.getMatch("\\$\\{\\w{1,20}}", stepDTO.getParameter1());
List<String> varNames2 = stepDTO.getParameter2() == null ? new ArrayList<>() : StringUtil.getMatch("\\$\\{\\w{1,20}}", stepDTO.getParameter2());
List<String> varNames3 = stepDTO.getParameter3() == null ? new ArrayList<>() : StringUtil.getMatch("\\$\\{\\w{1,20}}", stepDTO.getParameter3());
if (varNames1.size() != 0) {
for (String varName: varNames1) {
stepDTO.setParameter1(stepDTO.getParameter1().replace(varName, params.get(varName)));
}
}
if (varNames2.size() != 0) {
for (String varName: varNames2) {
stepDTO.setParameter2(stepDTO.getParameter2().replace(varName, params.get(varName)));
}
}
if (varNames3.size() != 0) {
for (String varName: varNames3) {
stepDTO.setParameter3(stepDTO.getParameter3().replace(varName, params.get(varName)));
}
}
}
/**
* 执行步骤列表(相当于执行一个方法)
*/
private boolean executeSteps(List<StepDTO> steps, RootClient auto) {
if (steps == null || steps.size() == 0) {
return true;
}
Map<String, String> params = new HashMap<>();
for (StepDTO oneStep: steps) {
// 局部变量赋值
mergeParam(oneStep, params);
// 执行步骤
executeStep(oneStep, auto);
if (oneStep.getResult().equals(DefaultEnum.DEFAULT_CLIENT_ERROR.getValue())) {
return false;
}
// 赋值
if (!StringUtil.isBlank(oneStep.getVarName())) {
params.put("${" + oneStep.getVarName() + "}", oneStep.getResult());
}
}
return true;
}
/**
* 执行一个测试用例
*/
public Boolean executeCase(CaseDTO caseDTO) {
RootClient auto;
if (clientMap.containsKey(caseDTO.getSupperCaseId())) {
// 通过超类ID,找到对应的执行客户端
auto = clientMap.get(caseDTO.getSupperCaseId());
} else {
// 如果未找到,说明改超类下的用例从未执行过,需要实例化一个客户端
auto = new RootClient(caseDTO);
clientMap.put(caseDTO.getSupperCaseId(), auto);
afterSuiteMap.put(caseDTO.getSupperCaseId(), caseDTO.getAfterSuite());
}
// 套件执行异常,直接失败
if (auto.isBeforeSuiteError) {
caseDTO.setStatus(AutoCaseStatusEnum.FAIL.getCode());
return false;
}
// 步骤为空,直接失败
if (caseDTO.getTest() == null || caseDTO.getTest().size() == 0) {
caseDTO.setStatus(AutoCaseStatusEnum.FAIL.getCode());
return false;
}
// 执行@BeforeSuite,只执行一次
if (!auto.isBeforeSuiteDone) {
if (!executeSteps(caseDTO.getBeforeSuite(), auto)) {
caseDTO.setStatus(AutoCaseStatusEnum.FAIL.getCode());
auto.isBeforeSuiteError = true;
return false;
}
auto.isBeforeSuiteDone = true;
}
// 执行超类中的@BeforeClass
if (!executeSteps(caseDTO.getSupperBeforeClass(), auto)) {
caseDTO.setStatus(AutoCaseStatusEnum.FAIL.getCode());
return false;
}
// 执行测试类中的@BeforeClass
if (!executeSteps(caseDTO.getBeforeClass(), auto)) {
caseDTO.setStatus(AutoCaseStatusEnum.FAIL.getCode());
return false;
}
// 执行测试类中的@Test
if (!executeSteps(caseDTO.getTest(), auto)) {
caseDTO.setStatus(AutoCaseStatusEnum.FAIL.getCode());
return false;
}
// @Test中的步骤全部执行完成且都为true,则用例执行成功
caseDTO.setStatus(AutoCaseStatusEnum.SUCCESS.getCode());
// 执行测试类中的@AfterClass
executeSteps(caseDTO.getAfterClass(), auto);
// 执行超类中的@AfterClass
executeSteps(caseDTO.getSupperAfterClass(), auto);
return true;
}
/**
* 执行@AfterSuite,然后关闭执行器申请的所有资源
*/
public void close() {
// 一个套件可能会有多个测试模块(超类),每个模块申请的客户端都要一一关闭
for (Integer key: clientMap.keySet()) {
// 执行超类中的@AfterSuite
if (afterSuiteMap.get(key) != null) {
for (StepDTO oneStep: afterSuiteMap.get(key)) {
executeStep(oneStep, clientMap.get(key));
}
}
// 关闭申请过的资源
clientMap.get(key).http.close();
clientMap.get(key).ui.close();
}
}
}
public class RootClient {
public SqlClient sql = new SqlClient();
public UiClient ui = new UiClient();
public HttpClient http = new HttpClient();
public RpcClient rpc = new RpcClient();
public UtilClient util = new UtilClient();
public AssertionClient assertion = new AssertionClient(ui);
public Boolean isBeforeSuiteDone = false;
public Boolean isBeforeSuiteError = false;
public RootClient(CaseDTO caseDTO) {
// 非UI自动化,不用额外初始化
if (caseDTO.getUiType() == null) {
return;
}
// 是UI自动化,需要初始化对应的webdriver
switch (ConfigTypeEnum.fromCode(caseDTO.getUiType())) {
case CHROME:
ui.initChrome(caseDTO.getUiArgument());
break;
case FIREFOX:
// todo init
break;
case ANDROID:
ui.initAndroid();
break;
}
}
}
List<String> steps = StringUtil.getMatch("(String[ ]{1,4}\\w{1,20}[ ]{1,4}=[ ]{0,4})?auto\\.(ui|http|sql|rpc|util|po|assertion|undefined)\\.\\w+\\(.*\\);", scriptVO.getScript());
/**
* 转换步骤模式,将步骤脚本转换成结构化步骤
*
* @param autoStepVO - 带脚本的完整步骤对象
*/
public AutoStepVO change2UiMode(AutoStepVO autoStepVO, Integer supperCaseId, Integer projectId) {
if (autoStepVO == null) {
return null;
}
// 脚本范例:auto.methodType.methodName(methodParam);
String script = autoStepVO.getScript();
// 步骤有赋值,先取变量名,再去除多余字符
if (script.startsWith("String")) {
// 取String和=之间的内容,再去空格
String varName = script.substring(6, script.indexOf("=")).trim();
autoStepVO.setVarName(varName);
script = script.substring(script.indexOf("auto."));
}
// 截取模块名,取第1个'.'到第2个'.'之间的内容,如:auto.ui.click(xpath)会取ui
String moduleName = script.substring(5, script.indexOf(".", 5));
// 截取方法名,取第2个'.'到第1个'('之间的内容,如:auto.ui.click(xpath)会取click
String methodName = script.substring(script.indexOf(".", 5) + 1, script.indexOf("("));
// 截取步骤入参,入参中的双引号,需要已被转义
String methodParam;
if (script.contains("(\"") && script.contains("\")")) {
methodParam = script.substring(script.indexOf("(\"") + 2, script.lastIndexOf("\")"));
} else {
methodParam = null;
}
// 截取多个参数,如:("xpath","key") (根据实际情况使用)
String[] params = StringUtil.isBlank(methodParam) ? null : methodParam.split("\",\\s{0,4}\"");
String param1, param2, param3;
if (params == null || params.length == 0) {
// 方法无入参
param1 = null;
param2 = null;
param3 = null;
} else if (params.length == 1) {
// 方法有一个入参
param1 = params[0];
param2 = null;
param3 = null;
} else if (params.length == 2) {
// 方法有二个入参
param1 = params[0];
param2 = params[1];
param3 = null;
} else {
// 方法有三个入参
param1 = params[0];
param2 = params[1];
param3 = params[2];
}
// 通过模块名和方法名,解析脚本步骤类型和方法类型的枚举值
switch (ModuleTypeEnum.fromName(moduleName)) {
case SQL: // 执行sql类型的步骤
autoStepVO.setModuleType(ModuleTypeEnum.SQL.getCode());
if (SqlEnum.SQL_EXECUTE_BY_JSON.getName().equals(methodName)) {
// 脚本范例:auto.sql.executeByJson("json")
autoStepVO.setMethodType(SqlEnum.SQL_EXECUTE_BY_JSON.getCode());
} else {
// 脚本范例:auto.sql.dbName("sql")
autoStepVO.setMethodType(SqlEnum.DB_NAME.getCode());
autoStepVO.setMethodId(resourceMapper.selectDataSource(methodName, projectId).getId());
}
break;
case PO: // 被封装的方法
autoStepVO.setModuleType(ModuleTypeEnum.PO.getCode());
if (PoEnum.PO_EXECUTE_BY_JSON.getName().equals(methodName)) {
// 脚本范例:auto.po.executeByJson("json")
autoStepVO.setMethodType(PoEnum.PO_EXECUTE_BY_JSON.getCode());
} else {
// 脚本范例:auto.po.poName("param1","param2","param3")
autoStepVO.setMethodType(PoEnum.PO_NAME.getCode());
autoStepVO.setMethodId(autoCaseMapper.selectPo(methodName, supperCaseId).getId());
}
break;
case UI: // ui类型的步骤
autoStepVO.setModuleType(ModuleTypeEnum.UI.getCode());
switch (UiEnum.fromName(methodName)) {
case OPEN_URL: // 脚本范例:auto.ui.openUrl("url")
autoStepVO.setMethodType(UiEnum.OPEN_URL.getCode());
break;
case CLICK: // 脚本范例:auto.ui.click("xpath") 或 auto.ui.click("xpath", "1");
autoStepVO.setMethodType(UiEnum.CLICK.getCode());
break;
case CLICK_BY_JS:
autoStepVO.setMethodType(UiEnum.CLICK_BY_JS.getCode());
break;
case CLICK_BY_MOVE:
autoStepVO.setMethodType(UiEnum.CLICK_BY_MOVE.getCode());
break;
case SEND_KEY: // 脚本范例:auto.ui.sendKey("key") 或 auto.ui.sendKey("xpath","key") 或 auto.ui.sendKey("xpath","key", "index")
autoStepVO.setMethodType(UiEnum.SEND_KEY.getCode());
break;
case SEND_KEY_BY_ENTER:
autoStepVO.setMethodType(UiEnum.SEND_KEY_BY_ENTER.getCode());
break;
case SWITCH_TAB: // 脚本范例:auto.ui.switchTab()
autoStepVO.setMethodType(UiEnum.SWITCH_TAB.getCode());
break;
case MOVE: // 脚本范例:auto.ui.move("xpath")
autoStepVO.setMethodType(UiEnum.MOVE.getCode());
break;
case CLEAR_COOKIES:
autoStepVO.setMethodType(UiEnum.CLEAR_COOKIES.getCode());
break;
case DRAG:
autoStepVO.setMethodType(UiEnum.DRAG.getCode());
break;
case EXECUTE_JS:
autoStepVO.setMethodType(UiEnum.EXECUTE_JS.getCode());
break;
}
break;
case HTTP: // http类型的步骤
autoStepVO.setModuleType(ModuleTypeEnum.HTTP.getCode());
switch (HttpEnum.fromName(methodName)) {
case GET: // 脚本范例:auto.http.get("url")
autoStepVO.setMethodType(HttpEnum.GET.getCode());
break;
case POST: // 脚本范例:auto.http.post("url","body")
autoStepVO.setMethodType(HttpEnum.POST.getCode());
break;
case PUT: // 脚本范例:auto.http.put("url","body")
autoStepVO.setMethodType(HttpEnum.PUT.getCode());
break;
case DELETE: // 脚本范例:auto.http.delete("url","body")
autoStepVO.setMethodType(HttpEnum.DELETE.getCode());
break;
}
break;
case RPC: // rpc类型的步骤
autoStepVO.setModuleType(ModuleTypeEnum.RPC.getCode());
if (RpcEnum.RPC_EXECUTE_BY_JSON.getName().equals(methodName)) {
// 脚本范例:auto.rpc.executeByJson("json")
autoStepVO.setMethodType(RpcEnum.RPC_EXECUTE_BY_JSON.getCode());
} else {
// 脚本范例:auto.rpc.invoke("uri","paramType","paramValue")
autoStepVO.setMethodType(RpcEnum.INVOKE.getCode());
}
break;
case UTIL: // 工具类型的步骤
autoStepVO.setModuleType(ModuleTypeEnum.UTIL.getCode());
switch (UtilEnum.fromName(methodName)) {
case SLEEP: // 脚本范例:auto.util.sleep("1")
autoStepVO.setMethodType(UtilEnum.SLEEP.getCode());
break;
case GET_JSON: // 脚本范例:auto.util.getJson("key","json")
autoStepVO.setMethodType(UtilEnum.GET_JSON.getCode());
break;
case GET_JSON_ANY: // 脚本范例:auto.util.getJsonAny("key","json")
autoStepVO.setMethodType(UtilEnum.GET_JSON_ANY.getCode());
break;
case GET_TIME: // 脚本范例:auto.util.getTime()
autoStepVO.setMethodType(UtilEnum.GET_TIME.getCode());
break;
case GET_RANDOM: // 脚本范例:auto.util.getJsonAny("1","100")
autoStepVO.setMethodType(UtilEnum.GET_RANDOM.getCode());
break;
}
break;
case ASSERTION: // 断言类型的步骤
autoStepVO.setModuleType(ModuleTypeEnum.ASSERTION.getCode());
switch (AssertionEnum.fromName(methodName)) {
case IS_EQUALS: // 脚本范例:auto.assertion.isEquals("actual","expect")
autoStepVO.setMethodType(AssertionEnum.IS_EQUALS.getCode());
break;
case IS_DELETED: // 脚本范例:auto.assertion.isDelete("actual")
autoStepVO.setMethodType(AssertionEnum.IS_DELETED.getCode());
break;
case IS_NOT_DELETED: // 脚本范例:auto.assertion.isNotDelete("actual")
autoStepVO.setMethodType(AssertionEnum.IS_NOT_DELETED.getCode());
break;
case IS_GREATER: // 脚本范例:auto.assertion.isGreater("actual","expect")
autoStepVO.setMethodType(AssertionEnum.IS_GREATER.getCode());
break;
case IS_SMALLER: // 脚本范例:auto.assertion.isSmaller("actual","expect")
autoStepVO.setMethodType(AssertionEnum.IS_SMALLER.getCode());
break;
case IS_CONTAINS: // 脚本范例:auto.assertion.isContains("actual","expect")
autoStepVO.setMethodType(AssertionEnum.IS_CONTAINS.getCode());
break;
case IS_BE_CONTAINS: // 脚本范例:auto.assertion.isBeContains("actual","expect")
autoStepVO.setMethodType(AssertionEnum.IS_BE_CONTAINS.getCode());
break;
case IS_XPATH_EXIST: // 脚本范例:auto.assertion.isXpathExist("actual")
autoStepVO.setMethodType(AssertionEnum.IS_XPATH_EXIST.getCode());
break;
case IS_XPATH_NOT_EXIST: // 脚本范例:auto.assertion.isXpathNotExist("actual")
autoStepVO.setMethodType(AssertionEnum.IS_XPATH_NOT_EXIST.getCode());
break;
}
break;
default:
autoStepVO.setModuleType(ModuleTypeEnum.UNDEFINED_MODULE.getCode());
break;
}
// 设置方法名称
autoStepVO.setMethodName(methodName);
// 设置方法入参
autoStepVO.setParameter1(param1);
autoStepVO.setParameter2(param2);
autoStepVO.setParameter3(param3);
return autoStepVO;
}
<template>
<div>
<!--coding模式-->
<div v-if="isCoding">
<el-divider content-position="right">
<span>{{name}}</span>
<el-tooltip class="item" effect="dark" :content="pageControl.desc" placement="top-start">
<i class="el-icon-info"></i>
</el-tooltip>
<el-divider direction="vertical"></el-divider>
<el-button @click="updateScript" type="text">保存脚本</el-button>
</el-divider>
<codemirror ref="cm" v-model="pageData.script" @keyHandled="save" :options="pageControl.options"></codemirror>
</div>
<!--ui模式-->
<div v-else>
<el-divider content-position="right">
<span>{{name}}</span>
<el-tooltip class="item" effect="dark" :content="pageControl.desc" placement="top-start">
<i class="el-icon-info"></i>
</el-tooltip>
<el-divider direction="vertical"></el-divider>
<el-button @click="createRelatedStep" type="text">新增</el-button>
<el-button v-if="pageData.stepList !== null && pageData.stepList.length !== 0" @click="deleteStep" type="text">删除</el-button>
</el-divider>
<!--列表-->
<el-table border :data="pageData.stepList" @row-click="edit" :row-style="{cursor: 'pointer'}" size="mini" style="width: 100%">
<el-table-column label="步骤简介" width="200" show-overflow-tooltip>
<template slot-scope="scope">
{{getStepDesc(scope.row.autoStep)}}
</template>
</el-table-column>
<el-table-column label="注释" width="100" show-overflow-tooltip>
<template slot-scope="scope">
{{scope.row.autoStep.name}}
</template>
</el-table-column>
<el-table-column label="执行结果" show-overflow-tooltip>
<template slot-scope="scope">
{{scope.row.autoStep.result}}
</template>
</el-table-column>
</el-table>
</div>
<!--弹窗-->
<el-dialog v-if="pageControl.isEditStep" :visible.sync="pageControl.isEditStep" title="编辑步骤" width="65%" append-to-body>
<tl-step-detail :case-step="pageControl.selectedStep" :visible.sync="pageControl.isEditStep" is-case-step></tl-step-detail>
</el-dialog>
</div>
</template>
<script>
import { codemirror } from 'vue-codemirror'
import 'codemirror/lib/codemirror.css'
import 'codemirror/theme/ayu-mirage.css'
import 'codemirror/mode/javascript/javascript.js'
import {createRelatedStepAPI, updateScriptAPI, removeRelatedStepAPI} from '@/api/autoCase'
import tlStepDetail from '@/component/stepDetail'
export default {
components: {tlStepDetail, codemirror},
props: {
name: {
type: String
},
isCoding: {
type: Boolean,
default: false
},
autoCase: {
type: Object,
default: null
}
},
data () {
return {
pageData: {
caseId: 0,
supperCaseId: 0,
type: 0,
script: '',
stepList: [],
projectId: 0
},
pageControl: {
isEditStep: false,
isCoding: this.$store.state.isCoding,
updateLock: false,
desc: null,
script: '',
// relationType: 0,
selectedStep: {},
options: {
// 语言及语法模式
mode: 'javascript',
// 主题
theme: 'ayu-mirage',
// 显示函数
// line: true,
lineNumbers: true,
// 软换行
lineWrapping: true,
// 重写Ctrl-S
extraKeys: {
'Ctrl-S': function (cm) {
console.info('ctrl ssss')
}
}
}
}
}
},
watch: {
'autoCase': function (val) {
this.pageControl.isCoding = this.$store.state.isCoding
this.setPageData()
}
},
created: function () {
this.setPageData()
},
methods: {
setPageData () {
this.pageData.caseId = this.autoCase.caseId
this.pageData.supperCaseId = this.autoCase.supperCaseId
this.pageData.projectId = this.autoCase.projectId
if (this.name === '@Test') {
this.pageControl.desc = '用例主体,相当于@Test,步骤会按列表显示的顺序执行'
this.pageData.type = 2
this.pageData.script = this.autoCase.testScript
this.pageData.stepList = this.autoCase.testList
} else if (this.name === '@BeforeClass') {
this.pageControl.desc = '前置步骤,相当于@BeforeTest,在@Test前执行'
this.pageData.type = 1
this.pageData.script = this.autoCase.beforeClassScript
this.pageData.stepList = this.autoCase.beforeClassList
} else if (this.name === '@AfterClass') {
this.pageControl.desc = '收尾步骤,相当于@AfterTest,在@Test后执行'
this.pageData.type = 3
this.pageData.script = this.autoCase.afterClassScript
this.pageData.stepList = this.autoCase.afterClassList
} else if (this.name === '@BeforeSuite') {
this.pageControl.desc = '套件总前置步骤,相当于@BeforeSuite,在@BeforeTest前执行'
this.pageData.type = 4
this.pageData.script = this.autoCase.beforeSuiteScript
this.pageData.stepList = this.autoCase.beforeSuiteList
} else if (this.name === '@AfterSuite') {
this.pageControl.desc = '套件总收尾步骤,相当于@AfterSuite,在@AfterTest后执行'
this.pageData.type = 5
this.pageData.script = this.autoCase.afterSuiteScript
this.pageData.stepList = this.autoCase.afterSuiteList
} else {
this.$message.error('未知步骤类型')
}
},
getStepDesc (step) {
console.info(step)
switch (step.moduleType) {
case 1: // PO
switch (step.methodType) {
case 1: // poName
return '调用PO方法:' + step.methodName
case 2: // json
return '执行PO方法,Json格式'
default:
return '未知步骤'
}
case 2: // SQL
switch (step.methodType) {
case 1: // dbName
return '在' + step.methodName + '中执行SQL'
case 2: // json
return '执行SQL,Json格式'
default:
return '未知步骤'
}
case 3: // RPC
switch (step.methodType) {
case 1: // invoke
return '调用RPC接口'
case 2: // json
return '调用RPC接口,Json格式'
default:
return '未知步骤'
}
case 4: // HTTP
switch (step.methodType) {
case 1: // get
return '调用GET接口'
case 2: // post
return '调用POST接口'
case 3: // put
return '调用PUT接口'
case 4: // delete
return '调用DELETE接口'
case 5: // getForHeader
return '通过GET接口获取response header'
case 6: // postForHeader
return '通过POST接口获取response header'
case 7: // setDefaultHeader
return '设置默认请求头'
case 8: // setDefaultUrl
return '设置默认Url(环境)'
default:
return '未知步骤'
}
case 5: // UI
switch (step.methodType) {
case 1: // openUrl
return '访问指定网址'
case 2: // click
return '点击指定XPATH'
case 3: // clickByJs
return '点击指定XPATH,通过JS'
case 4: // clickByMove
return '先移动到指定XPATH,再点击'
case 5: // sendKey
return '输入指定字符串'
case 6: // sendKeyByEnter
return '输入指定字符串,再按ENTER键'
case 7: // move
return '移动到指定XPATH'
case 8: // drag
return '鼠标拖拽'
case 9: // executeJs
return '执行JS'
case 10: // switchTab
return '先切换到最新标签页,然后关闭其它'
case 11: // clearCookies
return '删除COOKIES'
default:
return '未知步骤'
}
case 6: // UTIL
switch (step.methodType) {
case 1: // sleep
return '强制等待' + step.parameter1 + '秒'
case 2: // getJson
return '获取JSON中指定KEY的值'
case 3: // getJsonAny
return '获取JSON或子JSON中指定KEY的值'
case 4: // random
return '获取随机数'
case 5: // getTime
return '获取当前系统时间'
default:
return '未知步骤'
}
case 7: // ASSERTION
switch (step.methodType) {
case 1: // isEquals
return '校验实际值是否等于预期值'
case 2: // isContains
return '校验实际值是否包含预期值'
case 3: // isBeContains
return '校验预期值是否包含实际值'
case 4: // isDeleted
return '校验实际值是否逻辑删除'
case 5: // isNotDeleted
return '校验实际值是否未逻辑删除'
case 6: // isGreater
return '校验实际值是否大于预期值'
case 7: // isSmaller
return '校验实际值是否小于预期值'
case 8: // isXpathExist
return '校验指定XPATH是否在页面中存在'
case 9: // isXpathNotExist
return '校验指定XPATH是否在页面中不存在'
default:
return '未知步骤'
}
default:
return '未知步骤'
}
},
deleteStep () {
removeRelatedStepAPI(this.pageData.stepList.pop()).then(response => {
if (response.data.success === true) {
this.$message.success('删除步骤成功')
}
})
},
createRelatedStep () {
createRelatedStepAPI({
caseId: this.pageData.caseId,
sequence: this.pageData.stepList !== null ? this.pageData.stepList.length + 1 : 1,
type: this.pageData.type
}).then(response => {
if (response.data.success === true) {
if (this.pageData.stepList == null) {
this.pageData.stepList = [response.data.data]
} else {
this.pageData.stepList.push(response.data.data)
}
this.$message.success('创建关联步骤成功,请自行编辑')
}
})
},
updateScript () {
updateScriptAPI(this.pageData).then(response => {
if (response.data.success === true) {
this.pageData = response.data.data
this.$message.success('脚本已更新')
}
this.pageControl.updateLock = false
})
},
edit (row, event, column) {
console.info(row.autoStep)
this.pageControl.selectedStep = row.autoStep
this.pageControl.isEditStep = true
},
save (cm, key) {
if (key === 'Ctrl-S' && this.pageControl.updateLock === false) {
this.pageControl.updateLock = true
this.updateScript()
}
}
}
}
</script>
<style scoped>
</style>
工具已经过基本自测,后续仍有更新
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。