前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >可变模式的自动化工具

可变模式的自动化工具

原创
作者头像
骆小生
修改2023-04-10 16:35:46
3720
修改2023-04-10 16:35:46
举报

一、简介

1.1 背景

在涉及自动化相关的工作中,代码和工具总是完全互斥。两者无法相互迁移,投票时支持用代码实现自动化和用工具支持自动化的人数也不相上下。

用代码实现自动化总会越来越难交接维护。因为大家思路各不相同,有人喜欢简单的数据类型,有人喜欢层层嵌套的集合对象,有时无限解耦到@Test中只有一行代码,有时又能见到@Test中一堆逻辑判断。无注释和不会写也比较常见。而测试又不像开发,有足够的动力和资源投入到代码中,导致自动化项目会越来越凌乱。

而用工具则难以推广。对于管理来说,工具总是更好管理。但对于写自动化的人来说,工具的功能再强大也强不过代码,总会有一些特殊场景工具难以支持,以至于让人弃用。另外,在高熟练度的前提下,一直UI点击并没有比敲代码快,还费手腕。也就是说纯粹的UI操作并不是最理想状态。

1.2 解决方案的思考

让代码和UI相互关联是否能解决此问题?

支持三种编写自动化用例的模式:代码模式、脚本模式、UI模式。

源码见附件

二、功能

2.1 特色功能

可实现简单的封装、继承、赋值、重载,和@Test、@BeforeClass、@AfterClass、@BeforeSuite、@AfterSuite。

编辑脚本时,可切换脚本模式和UI模式,脚本模式中的脚本可与自动化测试代码相互联动。

2.2 主要功能

模块管理

一个模块相当于一个测试包(project中的测试package),同时也相当于模板中测试类的超类。可设置通用参数,selenium启动参数,@BeforeSuite,@BeforeClass,@AfterClass,@AfterSuite。

  1. 列表包含增删改查功能
  2. 详情可直接编辑
  3. 可以设置参数,对超类下的子类仍有效
  4. 设置webDriver启动参数,如果非ui用例则不需要
  5. 设置@BeforeSuite,@BeforeClass,@AfterClass,@AfterSuite
模块管理页面总览
模块管理页面总览

用例管理

一个用例相当于一个测试类(有@Test的类),用例必须存在于某一测试模块中,用例中可设置@BeforeClass,@Test,@AfterClass,可随意切换UI或Coding模式,UI模式下点击步骤可进入步骤详情。

  1. 列表包含基本增删改查功能
  2. 新增用例后,也相当于继承了当前超类
  3. 新增PO也是新增公共方法,只会在当前模块下可用
用例管理列表
用例管理列表
  1. 用例详情,点击可切换模式
  2. 点执行可以直接在localhost执行
  3. 可以直接编辑脚本,ctrl+s或点保持脚本会解析并保持
  4. UI模式中会直接显示上次执行用例的结果,新增和删除都只会对最后一行生效
  5. 可以编辑不同步骤类型
  6. 选好步骤类型后可再选择类型中的方法
  7. 可以填入参,入参最多有三个,可以为空
  8. 赋值后的变量可用于后面传参
用例详情-脚本模式
用例详情-脚本模式
用例详情-UI模式
用例详情-UI模式
步骤详情
步骤详情

套件管理

套件可以包含多个模块的用例,用例列表可同时包括UI和其他接口用例。可以本地执行,任意节点执行,或指定多个节点执行。当指定多个节点时,会分批并行。支持批量重试和单个重试

  1. 点执行套件会在执行环境中执行套件中的所有用例,可以批量重试失败的用例
  2. 可以指定执行环境,localhost为本机执行,任意机器会选择任意空闲机器,指定机器则会分批在指定机器中执行所有用例
  3. 为套件关联用例,可以同时包含UI和接口用例
  4. 会显示每个用例的执行情况,可以单个重试失败的用例
套件执行总览
套件执行总览

2.3 其他功能

工作台

可查看任务日历和统计报表,日历中可添加任务

报表
报表
日历
日历

设置

可以设置人员、项目、数据源和执行节点;数据源在执行sql步骤时会用到,执行节点在执行套件时会用到。

三、设计

3.1 技术架构

工具前后端技术架构
工具前后端技术架构

3.2 数据模型

3.3 核心流程

套件执行流程

用例执行流程

步骤执行流程

四、特色代码

4.1 自动化执行器

代码语言:javascript
复制
/**
 * 自动化执行器
 * 每个执行自动化的线程只实例化一个执行器
 */
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();
        }
    }

}

4.2 步骤执行总客户端

代码语言:javascript
复制
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;
        }
    }
}

4.3 把整块脚本解析成步骤

代码语言:javascript
复制
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());

4.4 把步骤解析成结构化数据

代码语言:javascript
复制
/**
 * 转换步骤模式,将步骤脚本转换成结构化步骤
 *
 * @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;
}

4.5 前端步骤列表&代码编辑器组件

代码语言:javascript
复制
<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 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、简介
    • 1.1 背景
      • 1.2 解决方案的思考
      • 二、功能
        • 2.1 特色功能
          • 2.2 主要功能
            • 2.3 其他功能
            • 三、设计
              • 3.1 技术架构
                • 3.2 数据模型
                  • 3.3 核心流程
                  • 四、特色代码
                    • 4.1 自动化执行器
                      • 4.2 步骤执行总客户端
                        • 4.3 把整块脚本解析成步骤
                          • 4.4 把步骤解析成结构化数据
                            • 4.5 前端步骤列表&代码编辑器组件
                            • 五、总结
                            相关产品与服务
                            腾讯云 BI
                            腾讯云 BI(Business Intelligence,BI)提供从数据源接入、数据建模到数据可视化分析全流程的BI能力,帮助经营者快速获取决策数据依据。系统采用敏捷自助式设计,使用者仅需通过简单拖拽即可完成原本复杂的报表开发过程,并支持报表的分享、推送等企业协作场景。
                            领券
                            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档