yapi-plugin-intl-auto-test
Version:
YAPI自动化测试插件,支持在YAPI设置测试计划,历史测试结果存入ES,界面显示测试结果,自定义通知。
540 lines (484 loc) • 18.7 kB
JavaScript
const openController = require('controllers/open.js');
const userModel = require('models/user.js');
const interfaceColModel = require('models/interfaceCol.js');
const interfaceCaseModel = require('models/interfaceCase.js');
const projectModel = require('models/project.js');
const groupModel = require('models/group.js');
const testResultModel = require('./../models/result');
const testPlanModel = require('./../models/plan');
const yapi = require('yapi.js');
const _ = require('underscore');
const renderToHtml = require('../../../server/utils/reportHtml');
const tools = require('../utils/tools');
const Config = require('../utils/config');
const elasticsearch = require('../elasticsearch');
const ops = Config.instance;
const logType = "intl-auto-test";
class testResultController extends openController {
constructor(ctx) {
super(ctx);
this.testResultModel = yapi.getInst(testResultModel);
this.testPlanModel = yapi.getInst(testPlanModel);
this.projectModel = yapi.getInst(projectModel);
this.groupModel = yapi.getInst(groupModel);
this.userModel = yapi.getInst(userModel);
this.elasticsearch = yapi.getInst(elasticsearch);
this.indexName = `${ops.indexName}`;
this.esStorage = true;
const esHost = `${ops.esHost}`;
if (esHost === 'undefined' || esHost === "") {
this.esStorage = false;
}
this.schemaMap = {
runAutoTest: {
'*id': 'number',
extraIds: 'string',
project_id: 'string',
token: 'string',
mode: {
type: 'string',
default: 'json'
},
email: {
type: 'boolean',
default: true
},
download: {
type: 'boolean',
default: false
},
run_descendants: {
type: 'boolean',
default: false
},
closeRemoveAdditional: true
}
};
}
/**
* 执行测试计划
* @param {*} ctx
*/
async runAutoTest(ctx) {
const projectId = ctx.params.project_id;
const planId = ctx.params.plan_id || -1;
const startTime = new Date().getTime();
let records = (this.records = {});
let reports = (this.reports = {});
const testList = [],
testColNames = [];
let id = ctx.params.id;
const originUrl = "http://11.186.140.107";
let extraIds = ctx.params.extraIds ? ctx.params.extraIds.split(',') : [];
const curEnvList = this.handleEvnParams(ctx.params);
const nestedColData = await yapi.commons.getCol(projectId, false, id);
if (!nestedColData) {
return (ctx.body = yapi.commons.resReturn(null, 40022, 'collection id值不存在'));
}
const projectInfo = await this.projectModel.get(projectId);
const planInfo = await this.testPlanModel.find(planId);
extraIds.push(id);
if (ctx.params.run_descendants) {
extraIds = extraIds.concat(nestedColData.descendants)
}
//get user info added by liyue
// const userId = this.getUid();
// const userInfo = await this.userModel.findById(userId);
//get group info
const groupInfo = await this.groupModel.get(projectInfo.group_id);
const search = (tree, target) => {
if (tree._id === target) {
return tree.treePath;
}
if (tree.children && tree.children.length >0) {
for (const child of tree.children) {
const found = search(child, target);
if (found) {
return found;
}
}
}
};
// console.log("result.js this.esStorage:",this.esStorage);
// if (this.esStorage){
// //make es index avaliable
// console.log("result.js line 128")
// await this.elasticsearch.client.indicesExists(this.indexName).then(exists => {
// if (!exists) {
// console.log('Index does not exist. Creating...');
// return this.elasticsearch.indexCreate(this.indexName);
// } else {
// console.log('Index exists.');
// }
// }).then(() => {
// console.log('Index created successfully.');
// }).catch(error => {
// console.error('Error:', error);
// });
// }
for (let idIndex = 0; idIndex < extraIds.length; idIndex++) {
//add-by-liyue-start
const colStartTime = new Date().getTime();
//id means colId
id = extraIds[idIndex];
let caseList = await yapi.commons.getCaseList(id);
if (caseList.errcode !== 0) {
ctx.body = caseList;
}
let colData = await this.interfaceColModel.get(id);
// console.log("result.js colData:",colData);
let treePath = search(nestedColData, id);
treePath.push(id);
var grandColName = '';
if (colData.parent_id && colData.parent_id !== -1) {
let grandColData = await this.interfaceColModel.get(colData.parent_id);
grandColName = grandColData.name;
}
const colDataInfo = caseList.colData || {};
testColNames.push(colDataInfo.name);
caseList = caseList.data;
// console.log("result.js caseList:",caseList);
let colTestList = new Array();
let colReportsList = new Array();
let stoped = false;
for (let i = 0, l = caseList.length; i < l; i++) {
let item = caseList[i];
const projectEvn = await this.projectModel.getByEnv(item.project_id);
item.id = item._id;
const curEnvItem = _.find(curEnvList, key => {
return key.project_id == item.project_id;
});
item.case_env = curEnvItem ? curEnvItem.curEnv || item.case_env : item.case_env;
item.req_headers = this.handleReqHeader(item.req_headers, projectEvn.env, item.case_env);
item.pre_script = projectInfo.pre_script;
item.after_script = projectInfo.after_script;
item.env = projectEvn.env;
let result = {};
try {
if (item.enable_script && item.enable_delay) {
const delayTime = item.delay_time;
await sleep(delayTime);
}
if (!stoped) {
yapi.commons.log(`TestCase Name:${item.casename}`, logType);
// console.log("line 204 item:",item)
result = await this.handleTest(item);
yapi.commons.log(`TestCase Name:${item.casename} Complete`, logType);
} else {
result.status = null;
result.statusText = 'stoped';
result.id = item.id;
result.name = item.casename;
result.path = item.path;
result.code = 2;
}
} catch (err) {
result = err;
}
reports[item.id] = result;
records[item.id] = {
params: result.params,
body: result.res_body
};
result.res_body = JSON.stringify(result.res_body);
result.data = JSON.stringify(result.data);
result.params = JSON.stringify(result.params);
result.col_name = colData.name;
result.col_id = id;
result.grand_col_name = grandColName;
result.treePath = treePath;
testList.push(result);
colTestList.push(result);
colReportsList.push(result);
if (!stoped && item.enable_script && result.code == 1 && item.enable_stop) { // 中断循环
stoped = true;
}
}
const colEndTime = new Date().getTime();
const message = await getMessage(colTestList);
// col finish
const esData = {
source: `${ops.dataSource}`,
status: message.len === message.successNum ? 'success' : 'fail',
message: message,
numbs: colTestList.length,
group_id: projectInfo.group_id,
group_name: groupInfo.group_name,
project_id: colDataInfo.project_id,
project_name: projectInfo.name,
test_col_id: colDataInfo._id,
test_col_name: colDataInfo.name,
test_start: datetimeFormatLong2String(colStartTime),
test_end: datetimeFormatLong2String(colEndTime),
runTime: ((colEndTime - colStartTime) / 1000) + 's',
user_id: "auto-test",
list: colReportsList
}
if (planId != -1 && !planInfo) {
esData.plan_id = planId;
esData.plan_name = planInfo.plan_name;
esData.plan_cron = planInfo.plan_cron;
esData.plan_url = planInfo.plan_url;
}
// if (this.esStorage) {
// //insert data into es
// //const indexName = `${this.indexName}-${groupInfo.group_name}-${projectInfo.name}`;
// yapi.commons.log('test result inserting into es', logType);
// const insert = await this.elasticsearch.insertData(this.indexName, esData);
// yapi.commons.log(`Plan:${planInfo.plan_name},Colletions:${colDataInfo.name} test result indesert into es, success: ${insert}`, logType);
// }
}
function sleep(time) {
return new Promise((resolve) => setTimeout(resolve, time));
}
async function getColTestCaseList(projectId) {
/*
获取每个用例集合信息
*/
let colTestCaseList = []
let result = await yapi.commons.getCol(projectId, true)
result.forEach(obj => {
if (obj.is_test_case) {
let colTestCaseObj = {}
colTestCaseObj.col_id = obj._id
if (obj.caseList.length > 0) {
let testCaseIdList = []
obj.caseList.forEach(testCaseObj => {
testCaseIdList.push(testCaseObj._id)
})
colTestCaseObj.caseIdList = testCaseIdList
}
colTestCaseObj.status = []
colTestCaseList.push(colTestCaseObj)
}
})
return colTestCaseList
}
async function getMessage(testList) {
var failedCases = [];
var successNum = 0,
failedNum = 0,
stopedNum = 0,
len = 0,
msg = '';
// 获取用例id
let resTestCaseIdList = await yapi.commons.getSingleTestCaseList(projectId)
let colTestCaseResultList = await getColTestCaseList(projectId)
for (let testCase of testList) {
// 判断单条用例结果并统计
if (resTestCaseIdList.indexOf(testCase.id) != -1) {
len++;
testCase.code == 0 ? successNum++ : failedNum++
continue
}
//判断用例集合执行结果
for (let colTestCaseRes of colTestCaseResultList) {
if (colTestCaseRes.col_id == testCase.col_id && colTestCaseRes.caseIdList.indexOf(testCase.id) != -1) {
testCase.code == 0 ? colTestCaseRes.status.push('success') : colTestCaseRes.status.push('fail')
continue
}
}
testCase.code == 2 ? stopedNum++ : ''
}
for (let colTestCase of colTestCaseResultList) {
len++;
colTestCase.status.indexOf("fail") == -1 ? successNum++ : failedNum++
}
if (failedNum === 0 && stopedNum === 0) {
msg = `一共 ${len} 条测试用例,全部验证通过`;
} else {
msg = `一共 ${len} 条测试用例,${successNum} 个验证通过, ${failedNum} 个未通过, ${stopedNum} 个未执行`;
}
if (failedCases.length >= 11) {
failedCases = failedCases.slice(0, 10);
failedCases.push('...')
}
return { msg, len, successNum, failedNum, stopedNum, failedCases };
}
// format time to ISO8601
function datetimeFormatLong2String(longMillTypeDate) {
const date = new Date();
date.setTime(longMillTypeDate);
return date.toISOString()
}
const endTime = new Date().getTime();
const executionTime = (endTime - startTime) / 1000;
const reportsResult = {
message: await getMessage(testList),
runTime: executionTime + 's',
startTime: startTime,
numbs: testList.length,
list: testList
};
const mode = ctx.params.mode || 'html';
if (ctx.params.download === true) {
ctx.set('Content-Disposition', `attachment; filename=test.${mode}`);
}
try {
const testData = {
project_id: projectId,
plan_id: planId,
uid: 11,
col_names: testColNames,
env: curEnvList,
test_url: ctx.href,
status: reportsResult.message.len === reportsResult.message.successNum ? "成功" : "失败",
data: reportsResult
};
const saveResult = await this.testResultModel.save(testData);
const autoTestUrl = `${originUrl}/api/open/plugin/test/result?id=${saveResult._id}`;
if (planId && planId !== -1) {
await this.testPlanModel.update(planId, { last_test_time: yapi.commons.time() });
// let plan = await this.testPlanModel.find(planId);
const trigger = planInfo.notice_trigger, notifier = planInfo.notifier ? planInfo.notifier.url : "";
const successNum = reportsResult.message.successNum;
const failedNum = reportsResult.message.failedNum;
const isSend = (trigger === "any")
|| (trigger === "success" && reportsResult.message.failedNum === 0)
|| ((trigger === "fail" || trigger === "part" || trigger === "existFail") && failedNum > 0);
// || (trigger === "fail" && successNum === 0)
// || (trigger === "part" && successNum < reportsResult.message.len && successNum > 0);
const fail = `未通过用例:<font color="blue"> ${reportsResult.message.failedCases} </font>`;
const success = ``;
const webhook = planInfo.webhook;
const webhookUrl = planInfo.webhook_url;
const webhookData = planInfo.webhook_data;
const failedCasesStr = `${reportsResult.message.failedCases}`;
const failedCasesArr = failedCasesStr.split(',');
if (isSend) {
// wework
if (notifier) {
const colorV = testData.status === "成功" ? "#058205" : "red";
const content = `测试结果:${planInfo.plan_name} 执行<font color="${colorV}">${testData.status}</font>\n${reportsResult.message.msg}
${testData.status === '失败' ? fail : success}
\n访问以下[链接查看](${autoTestUrl})测试结果详情`;
tools.sendWorkWX(notifier, content);
}
// webhook
if (webhook) {
try {
const postData = JSON.parse(webhookData)
const ydata = {
planName: planInfo.plan_name,
status: testData.status,
failedCases: failedCasesArr,
detail: `${autoTestUrl}`
}
postData.yapi = ydata;
const webHookResult = await tools.sendWebHook(webhookUrl, postData);
// yapi.commons.log(`webHookResult external:${JSON.stringify(webHookResult.data)}`);
await this.testPlanModel.update(planId, { webhook_status: `成功,${JSON.stringify(webHookResult.data)}` });
} catch (error) {
yapi.commons.log(`webhook send error:${error.message}`, logType);
await this.testPlanModel.update(planId, { webhook_status: `${error.message}` });
}
}
}
}
if (ctx.params.email === true && reportsResult.message.failedNum !== 0) {
yapi.commons.sendNotice(projectId, {
title: `YApi自动化测试报告`,
content: `
<html>
<head>
<title>测试报告</title>
<meta charset="utf-8" />
<body>
<div>
<h3>测试结果:</h3>
<p>${reportsResult.message.msg}</p>
<h3>访问以下链接查看测试结果详情:</h3>
<p><a herf="${autoTestUrl}">${autoTestUrl}</a></p>
</div>
</body>
</html>`
});
}
} catch (e) {
yapi.commons.log("result.js line 430");
yapi.commons.log(e, 'error');
}
if (ctx.params.mode === 'json') {
return (ctx.body = reportsResult);
} else {
return (ctx.body = await renderToHtml(reportsResult));
}
}
/**
* 获取项目下的测试结果
* @param {*} ctx
*/
async getTestResults(ctx) {
try {
const projectId = ctx.params.project_id;
const planId = ctx.params.plan_id;
let results;
if (projectId) {
results = await this.testResultModel.findByProject(projectId)
}
if (planId) {
results = await this.testResultModel.findByPlan(planId)
}
ctx.body = yapi.commons.resReturn(results);
} catch (e) {
yapi.commons.log("result.js line 457");
ctx.body = yapi.commons.resReturn(null, 401, e.message);
}
}
/**
* 获取计划下的最新测试结果
* @param {*} ctx
*/
async getLatestTestResults(ctx) {
try {
const planId = ctx.params.plan_id;
let results;
if (planId) {
let history = await this.testResultModel.findLatestByPlan(planId)
if (history.length > 0) {
results = history[0]
} else {
results = {}
}
} else {
ctx.body = yapi.commons.resReturn(null, 401, 'plan id needed');
}
ctx.body = yapi.commons.resReturn(results);
} catch (e) {
yapi.commons.log("result.js line 482");
ctx.body = yapi.commons.resReturn(null, 401, e.message);
}
}
/**
* 获取测试结果
* @param {*} ctx
*/
async getTestResult(ctx) {
// if (!this.$tokenAuth && !this.$auth) {
// return (ctx.body = yapi.commons.resReturn(null, 40022, 'token 验证失败'));
// }
try {
const id = ctx.params.id;
const results = await this.testResultModel.get(id)
ctx.body = await renderToHtml(results.data);
} catch (e) {
yapi.commons.log("result.js line 502");
ctx.body = yapi.commons.resReturn(null, 401, e.message);
}
}
/**
* 清空测试结果
* @param {*} ctx
*/
async delTestResults(ctx) {
try {
const plan_id = ctx.params.plan_id;
const result = await this.testResultModel.deleteAll(plan_id);
ctx.body = yapi.commons.resReturn(result);
} catch (e) {
yapi.commons.log("result.js line 517");
ctx.body = yapi.commons.resReturn(null, 401, e.message);
}
}
}
module.exports = testResultController;