之前章节我们讲了部门部门管理,项目管理,这节我们继续实现添加脚本。
那么关联关系就是,在部门对应的项目中添加多个脚本。实现这个模块我们需要完成三步操作。
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;
各个字段看名字对应即可。里面定义了部门表和项目表的主外键关联关系,之前章节详细讲过,这里不在啰嗦。
接下来我们实现前端部分,直接看下前端需要的功能界面。
完整逻辑为:
- 加载用户界面,界面中包含选择部门、选择项目、脚本类型和脚本名称的输入项,输入项选择后,用户可以上传图片。
- 上传图片后,根据这张图片,可以选择执行的动作:点击、校验或是否存在。这三种动作分别对应到按钮:“点击”,“校验”,“是否存在”。
- 选择动作后,图片数据被保存,并且动作按钮会被禁用,直到上传下一张图片。
- 当所有图片上传并选择动作完成后,用户可以点击“生成测试脚本”按钮生成脚本。
- 在生成脚本的过程中,显示加载动画和提示,完成后隐藏加载动画,并提示测试脚本已经生成。
在脚本的实现中使用了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)})
综上:添加脚本的功能逻辑开发完毕。
下一节:执行脚本逻辑开发。敬请期待
本文暂时没有评论,来添加一个吧(●'◡'●)