UNPKG

yapi-plugin-intl-auto-test

Version:

YAPI自动化测试插件,支持在YAPI设置测试计划,历史测试结果存入ES,界面显示测试结果,自定义通知。

540 lines (484 loc) 18.7 kB
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;