玖叶教程网

前端编程开发入门

企业级UI自动化平台—添加脚本

之前章节我们讲了部门部门管理,项目管理,这节我们继续实现添加脚本。

那么关联关系就是,在部门对应的项目中添加多个脚本。实现这个模块我们需要完成三步操作。

1:完成数据表的创建及与部门表和项目表之间的关联关系

2:完成前端界面布局

3:完成对应后端接口的开发

首先完成第一步,数据表的创建,既然是添加脚本,那么我们能够想到的字段就有:

脚本名称,上传的图片名称,操作的类型(点击、滑动、校验...),执行的步骤,操作类型的次数,生成脚本的类型。

对应如下:

ui自动化平台添加脚本管理模块:

SET NAMES utf8;

SET FOREIGN_KEY_CHECKS = 0;

DROP TABLE IF EXISTS `script_images`;

CREATE TABLE `script_images` (

`id` int(11) NOT NULL AUTO_INCREMENT,

`script_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,

`image_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,

`action_type` enum('click','assert','exist') CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,

`step_number` int(11) NULL DEFAULT NULL,

`ftp_path` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,

`department_id` int(11) NULL DEFAULT NULL,

`project_id` int(11) NULL DEFAULT NULL,

`repeatCount` int(11) NULL DEFAULT NULL,

`select_type` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,

PRIMARY KEY (`id`) USING BTREE,

INDEX `department_id`(`department_id`) USING BTREE,

INDEX `project_id`(`project_id`) USING BTREE,

CONSTRAINT `script_images_ibfk_1` FOREIGN KEY (`department_id`) REFERENCES `department` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,

CONSTRAINT `script_images_ibfk_2` FOREIGN KEY (`project_id`) REFERENCES `project` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT

) ENGINE = InnoDB AUTO_INCREMENT = 24 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

SET FOREIGN_KEY_CHECKS = 1;

各个字段看名字对应即可。里面定义了部门表和项目表的主外键关联关系,之前章节详细讲过,这里不在啰嗦。

接下来我们实现前端部分,直接看下前端需要的功能界面。


完整逻辑为:

  1. 加载用户界面,界面中包含选择部门、选择项目、脚本类型和脚本名称的输入项,输入项选择后,用户可以上传图片。
  2. 上传图片后,根据这张图片,可以选择执行的动作:点击、校验或是否存在。这三种动作分别对应到按钮:“点击”,“校验”,“是否存在”。
  3. 选择动作后,图片数据被保存,并且动作按钮会被禁用,直到上传下一张图片。
  4. 当所有图片上传并选择动作完成后,用户可以点击“生成测试脚本”按钮生成脚本。
  5. 在生成脚本的过程中,显示加载动画和提示,完成后隐藏加载动画,并提示测试脚本已经生成。

在脚本的实现中使用了Ajax进行异步通信,主要进行了以下操作:

  • 保存图片数据
  • 上传图片
  • 获取部门信息和对应的项目信息
  • 生成测试脚本

在获取部门信息和项目信息时,使用GET方法接收服务器返回的数据,并更新选择部门和项目的下拉列表;在保存图片数据、上传图片和生成测试脚本时,使用POST方法将数据发送到服务器。

完整实现为:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>测试脚本生成</title>
<!--  <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>-->
  <style>
    /*body {*/
    /*  font-family: Arial, sans-serif;*/
    /*  margin: 20px;*/
    /*}*/

    h1 {
      text-align: center;
      color: #3c3c44;
      font-weight: bold;
    }

    label {
      margin-top: 10px;
      display: block;
      color: #1c1c1d;
    }

    select,
    input[type=text],
    input[type=file] {
      width: 100%;
      padding: 12px;
      margin-top: 4px;
      display: inline-block;
      border: 1px solid #ccc;
    }

    input[type=file] {
      padding: 3px;
    }

    button {
      border: none;
      color: white;
      padding: 14px 28px;
      font-size: 16px;
      cursor: pointer;
      border-radius: 4px;
      margin-top: 16px;
    }

    #click_btn {
      background-color: #4CAF50;
    }

    #assert_btn {
      background-color: #FF9800;
    }

    #generate_script_btn {
      background-color: #2196F3;
    }
  </style>
</head>
<body>
<h1>测试脚本生成</h1>
<div id="whole-body">
<label for="select-department">选择部门:</label>
<select id="select-department">
  <option value=""> -- 请选择部门 -- </option>
</select>

<label for="select-project">选择项目:</label>
<select id="select-project">
  <option value=""> -- 请选择项目 -- </option>
</select>
<label for="select_type">脚本类型:</label>
<select id="select_type">
  <option value=""> -- 请选择类型 -- </option>
  <option value="1">windows</option>
  <option value="2">unity</option>
  <option value="3">android</option>
</select>
<label for="script_name">脚本名称:</label>
<input type="text" id="script_name">

<input type="file" id="image_upload">

<br>

<button id="click_btn">点击</button>
<button id="assert_btn">校验</button>
<button id="exist_btn" style="background-color: #9C27B0; color: white; padding: 14px 28px; font-size: 16px; cursor: pointer; border-radius: 4px; margin-top: 16px;">
  是否存在
</button>
<button id="generate_script_btn">生成测试脚本</button>

<p id="action_prompt" style="color: red; font-weight: bold; margin-top: 10px;"></p>
<div id="uploaded_images"></div>

<script>
  var step_number = 0;
  var selected_action = '';
  // 添加 isImageUploaded 和 isActionPerformed 变量
  var isImageUploaded = false;
  var isActionPerformed = false;
  function save_image_data(image_name, action_type, step_number, script_name,ftp_path, department_id, project_id,repeatCount,select_type) {
    $.ajax({
      url: "/save-image-data", // 您的后端接口
      type: "POST",
      contentType: "application/json",
      data: JSON.stringify({
        image_name: image_name,
        action_type: action_type,
        step_number: step_number,
        script_name: script_name,
        // ftp_path: `/bignoxData/bignoxData/software/qa/Mobile/uitest/${script_name}/${image_name}`, // 添加新字段
        department_id: department_id,
        project_id: project_id,
        repeatCount:repeatCount,
        select_type:select_type,
        ftp_path: ftp_path, // 添加新字段
      }),
      success: function (res) {
        console.log("Image data saved successfully");
        console.log(repeatCount);
        console.log(select_type);
        console.log(res); // 添加这一行以查看响应内容
      },
      error: function (err) {
        console.error("Image data save failed");
        console.error(err); // 添加这一行以查看错误内容
      },
    });
  }
  function disableActionButtons() {
    $("#click_btn").attr("disabled", true);
    $("#assert_btn").attr("disabled", true);
  }

  function enableActionButtons() {
    $("#click_btn").attr("disabled", false);
    $("#assert_btn").attr("disabled", false);
  }
  disableActionButtons(); // 最初禁用按钮,直到上传第一张图片
  $("#click_btn").on("click", function () {
    selected_action = 'click';
    var repeatCount = prompt("请输入点击次数", "1");
    repeatCount = parseInt(repeatCount);
    while(isNaN(repeatCount) || repeatCount<1) {
      alert("无效的输入!至少点击一次。")
      repeatCount = prompt("请输入点击次数", "1");
      repeatCount = parseInt(repeatCount);
    }
    $("#action_prompt").text("已选择点击操作");

    if (step_number > 0) {
      var imgCaption = `点击第${step_number}步。`;
      $("#uploaded_images div:last-child p").text(imgCaption);
    }

    disableActionButtons(); // 选择操作后重新禁用按钮
    // 修改并添加以下两行代码到 #click_btn 的事件处理程序中
    const department_id = $('#select-department').val();
    const project_Id = $('#select-project').val();
    const imageUrl = $(this).attr("data-url");
    const select_type = $('#select_type').val();
    save_image_data(imgFile.name, selected_action, step_number, $("#script_name").val(), imageUrl,department_id, project_Id,repeatCount,select_type);
    isActionPerformed = true; // 将 isActionPerformed 设置为 true
    //save_image_data(imgFile.name, selected_action, step_number, $("#script_name").val(), `/uploads${res.image_url}`); // 传递 ftp_path 参数
  });
  //新函数
  $("#image_upload").on("click", function () {
    this.value = null;
  });
  //分割线
  $("#assert_btn").on("click", function () {
    selected_action = 'assert';
    var repeatCount = 1; // 为assert操作设置repeatCount为1
    $("#action_prompt").text("已选择校验操作");

    if (step_number > 0) {
      var imgCaption = `校验第${step_number}步。`;
      $("#uploaded_images div:last-child p").text(imgCaption);
    }

    disableActionButtons(); // 选择操作后重新禁用按钮
    // 修改并添加以下两行代码到 #click_btn 的事件处理程序中
    const department_id = $('#select-department').val();
    const project_Id = $('#select-project').val();
    const imageUrl = $(this).attr("data-url");
    save_image_data(imgFile.name, selected_action, step_number, $("#script_name").val(), imageUrl, department_id, project_Id,select_type,repeatCount);
    isActionPerformed = true; // 将 isActionPerformed 设置为 true
    //save_image_data(imgFile.name, selected_action, step_number, $("#script_name").val(), `/uploads${res.image_url}`); // 传递 ftp_path 参数
  });
  $("#exist_btn").on("click", function () {
    selected_action = 'exist';
    var repeatCount = prompt("请输入循环点击次数", "1");
    repeatCount = parseInt(repeatCount);
    while(isNaN(repeatCount) || repeatCount<1) {
      alert("无效的输入!次数至少为一。")
      repeatCount = prompt("请输入循环点击次数", "1");
      repeatCount = parseInt(repeatCount);
    }

    $("#action_prompt").text("已选择是否存在操作");

    if (step_number > 0) {
      var imgCaption = `是否存在第${step_number}步。`;
      $("#uploaded_images div:last-child p").text(imgCaption);
    }

    disableActionButtons();
    const department_id = $('#select-department').val();
    const project_Id = $('#select-project').val();
    const imageUrl = $(this).attr("data-url");
    save_image_data(imgFile.name, selected_action, step_number, $("#script_name").val(), imageUrl, department_id, project_Id,select_type, repeatCount);
    isActionPerformed = true;
  });
  var imgFile; // 将 imgFile 变量移到外面
  $("#image_upload").on("change", function () {
    var fileInput = this;
    imgFile = fileInput.files[0];
    var imgName = imgFile.name;
    var formData = new FormData();
    formData.append("image", imgFile);
    formData.append("script_name", $("#script_name").val());
    formData.append("department_id", $("#select-department").val()); // 新增加这行
    formData.append("project_id", $("#select-project").val());       // 新增加这行
    formData.append("select_type", $("#select_type").val());       // 新增加这行
    step_number++;

    $.ajax({
      url: "/upload-image", // 您的后端接口
      type: "POST",
      data: formData,
      processData: false,
      contentType: false,
      success: function (res) {
        var newImg = document.createElement("img");
        newImg.src = res.image_url; // 响应中的图片 URL
        newImg.style.maxWidth = "200px";
        newImg.alt = imgName;

        var imageCaption = document.createElement("p");

        var imageContainer = document.createElement("div");
        imageContainer.appendChild(newImg);
        imageContainer.appendChild(imageCaption);
        $("#uploaded_images").append(imageContainer);

        enableActionButtons(); // 上传新图片后启用点击和校验按钮
        $("#click_btn").attr("data-url", res.image_url);
        $("#assert_btn").attr("data-url", res.image_url);
        $("#exist_btn").attr("data-url", res.image_url);
        enableActionButtons(); // 添加这一行代码
        // 在这里设置 isImageUploaded 为 true
        isImageUploaded = true;
      },
      error: function (err) {
        console.error("图片上传失败", err);
      }
    });
  });
  $("#generate_script_btn").on("click", function () {
    if (!isImageUploaded) {
      alert("未上传任何文件!");
      return;
    }
    if (!isActionPerformed) {
      alert("请先选择当前图片要执行的动作!");
      return;
    }

    // 添加的部分: 点击生成后隐藏主页面,显示加载动画和提示
    $('#whole-body').hide();
    $('#loading').show();
    // 更新此部分,使用正确的 ID
    console.log("项目ID: " + $("#select-project").val());
    console.log("部门ID: " + $("#select-department").val());
    var postData = JSON.stringify({
      script_name: $("#script_name").val(),
      department_id: $("#select-department").val(),
      project_id: $("#select-project").val(),
      select_type: $("#select_type").val()
    });
    $.ajax({
      url: "/generate-test-script",
      type: "POST",
      contentType: "application/json",
      data: postData,
      success: function (res) {
        //接收到返回数据后先延时3秒
        setTimeout(function () {
          //隐藏加载动画和提示,显示生成成功提示,并显示主页面
          $('#loading').hide();
          $('#script_gen_prompt').show(); //显示生成成功提示
          alert('测试脚本已生成!');
          setTimeout(function(){
            $('#script_gen_prompt').hide();
            $('#whole-body').show();  //重新显示主页面
          }, 2000);  //2000ms后隐藏成功提示,并重新显示主界面
        }, 3000);   // 延时 3000 ms 显示生成完成提示
      },
      error: function (err) {
        setTimeout(function() {
          $('#loading').hide();
          alert("生成测试脚本失败");
          $('#whole-body').show();  //重新显示主页面
        }, 3000);   // 延时 3000 ms 显示失败提示
        console.error(err)
      }
    });
  });
  <!-- 在现有的 <script> 标签内添加以下代码 -->
  $(document).ready(function () {

    // 获取部门信息并填充到下拉框
    function loadDepartments() {
      $.get("/get-all-departments", function (data) {
        var departments = data.departments;
        $("#select-department").empty();
        $("#select-department").append("<option value=''> -- 请选择部门 -- </option>")
        for (var i = 0; i < departments.length; i++) {
          var option = $("<option>").val(departments[i].id).text(departments[i].name);
          $("#select-department").append(option);
        }
      });
    }

    loadDepartments();
    $("#select-department, #select-project").on("change", function() {
      if ($("#select-department").val() && $("#select-project").val()) {
        $("#script_name").prop("disabled", false);
      } else {
        $("#script_name").prop("disabled", true);
      }
    });
    $("#select-department").on("change", function () {
      var departmentId = $(this).val();
      if (departmentId) {
        // 发送请求获取部门对应项目
        $.get("/get-projects?department_id=" + departmentId, function (data) {
          var projects = data.projects;
          $("#select-project").empty();
          $("#select-project").append("<option value=''> -- 请选择项目 -- </option>");
          for (var i = 0; i < projects.length; i++) {
            var option = $("<option>").val(projects[i].id).text(projects[i].name);
            $("#select-project").append(option);
          }
        });
      } else {
        $("#select-project").empty();
        $("#select-project").append("<option value=''> -- 请先选择部门 -- </option>");
      }
    });

  })
</script>
</div>
</body>
<div id="loading" style="display: none; text-align: center;">
  <img src="/uploads/static/Picture/gif/xz.gif" style="width: 200px; height: auto; margin: 0 auto;">
  <p style="margin-top: 20px; font-size: 20px; font-weight: bold;">正在生成脚本...</p>
</div>

</html>

对应后端生成脚本的逻辑为:

@app.route('/generate-test-script', methods=['POST'])
def generate_test_script():
    try:
        script_name = request.json.get('script_name')
        script_nameair = request.json.get('script_name') + '.air'
        department_id = int(request.json.get('department_id'))
        select_type = request.json.get('select_type')
        print(select_type)
        repeatCount = request.json.get('repeatCount')
        if request.json:
            project_id = int(request.json.get('project_id'))
            department_id = int(department_id)
            project_id = int(project_id)
            start_package = 'com.noxgroup.game.android.townsurvivor'
            # mycursor.execute("SELECT image_name, action_type, step_number, ftp_path FROM script_images WHERE script_name = %s ORDER BY step_number", (script_name, ))
            # images_data = mycursor.fetchall()
            # mycursor.execute("SELECT name FROM department WHERE id = %s", (department_id, ))
            # department_name = mycursor.fetchone()[0]
            # mycursor.execute("SELECT name FROM project WHERE id = %s and status = 1", (project_id, ))
            # project_name = mycursor.fetchone()[0]
            sql = "SELECT image_name, action_type, step_number, ftp_path,repeatCount FROM script_images WHERE script_name = '{}' ORDER BY step_number".format(
                script_name, )
            print(sql+'lx')
            images_data = db.select_data(sql)
            print('这里出现imagesdata数据')
            print(images_data)
            sql = "SELECT name FROM department WHERE id = {}".format(department_id, )
            department_name = db.select_one_data(sql)[0]
            sql = "SELECT name FROM project WHERE id = {} and status = 1".format(project_id, )
            project_name = db.select_one_data(sql)[0]
            script_folder = os.path.join(os.getcwd(), 'uploads', department_name, project_name, script_nameair)
            if not os.path.exists(script_folder):
                os.makedirs(script_folder)
            with open(os.path.join(script_folder, f"{script_name}.py"), "w") as f:
                # startapkpath = os.path.join(os.getcwd(), 'uploads', department_name, project_name, "uploadfiles")
                # try:
                #     apk_files = glob.glob(f'{startapkpath}/*.apk')
                #     if not apk_files:
                #         print(f"No apk files in {startapkpath}")
                #         return jsonify({"result": "error", "error_message": f"No apk files in {startapkpath}"})
                #     else:
                #         apk_file = apk_files[0]
                # except Exception as e:
                #     print(e)
                # a = APK(apk_file)
                # package = a.get_package()
                # activity = a.get_main_activity()
                if project_name == 'TownSurvivor':
                    ipconnect = '127.0.0.1:{}'.format(config.tsphoneip)
                    print(123)
                elif project_name == '忍者猫':
                    ipconnect = '127.0.0.1:{}'.format(config.maophoneip)
                elif project_name == 'Player':
                    ipconnect = '127.0.0.1:{}'.format(config.maophoneip)
                else:
                    return 'adb erro'
                print(ipconnect)
                f.write("from airtest.core.api import *\n")
                f.write("from airtest.core.settings import Settings as ST \n")
                f.write("ST.THRESHOLD_STRICT = 0.7\n")
                f.write("ST.THRESHOLD = 0.7\n")
                f.write("auto_setup(__file__)\n")
                # f.write("import subprocess\n")
                # # 先构造要写入的子进程调用命令字符串
                # subprocess_cmd = (
                #     f"output = subprocess.check_output('adb -s {ipconnect} shell am start -n {package}/{activity}', "
                #     "shell=True)"
                # )
                #
                # # 然后写入文件
                # f.write(subprocess_cmd + "\n")
                # # f.write("output = subprocess.check_output(f'adb -s {ipconnect} shell am start -n {package}/{activity}', shell=True)\n")
                # f.write("time.sleep(8)\n")
                # f.write("print(output.decode())\n")
                for image_data in images_data:
                    print(image_data)
                    image_name, action_type, step_number, _, repeatCount = image_data  # 接收重复次数
                    if action_type == 'click':
                        if int(repeatCount) > 1:
                            f.write(f"for _ in range({repeatCount}):")  # 添加循环语句用于重复点击
                            f.write(f"\n    if exists(Template(r'{image_name}')):\n")  # 添加循环语句用于重复点击
                            f.write(f"        sleep(1.0)\n")  # 添加循环语句用于重复点击
                            f.write(f"        touch(Template(r'{image_name}'))\n")
                            f.write(f"    else:\n")
                            f.write(f"        break\n")
                        else:
                            f.write(f"touch(Template(r'{image_name}'))\n")
                            f.write(f"time.sleep(1)\n")
                    elif action_type == 'assert':
                        f.write(f"assert_exists(Template(r'{image_name}'))\n")
                        f.write(f"time.sleep(1)\n")
                    elif action_type == 'exist':  # 处理新的action
                        f.write(f"for i in range({repeatCount}):\n")
                        f.write(f"    if exists(Template(r'{image_name}')):\n")
                        f.write(f"        touch(Template(r'{image_name}'))\n")
                        f.write(f"    else:\n")
                        f.write(f"        break\n")

            # 添加保存脚本逻辑
            save_generated_script_to_DB(department_name, project_name, script_name,select_type)
            return jsonify({'result': 'success'}, script_folder)
        else:
            return jsonify({'error': 'No script name found.'}), 400
    except Exception as e:
        print('Error:', e)
        traceback.print_exc()
        return jsonify({"result": "error", "error_message": str(e)})


综上:添加脚本的功能逻辑开发完毕。

下一节:执行脚本逻辑开发。敬请期待

发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言