testbeats
Version:
Publish test results to Microsoft Teams, Google Chat, Slack and InfluxDB
362 lines (330 loc) • 10 kB
JavaScript
const request = require('phin-retry');
const { getPercentage, truncate, getPrettyDuration } = require('../helpers/helper');
const { getValidMetrics, getMetricValuesText } = require('../helpers/performance');
const extension_manager = require('../extensions');
const { HOOK, STATUS } = require('../helpers/constants');
const logger = require('../utils/logger');
const TestResult = require('test-results-parser/src/models/TestResult');
const PerformanceTestResult = require('performance-results-parser/src/models/PerformanceTestResult');
const { BaseTarget } = require('./base.target');
/**
* @param {object} param0
* @param {PerformanceTestResult | TestResult} param0.result
* @returns
*/
async function run({ result, target }) {
setTargetInputs(target);
const root_payload = getRootPayload();
const payload = getMainPayload(target);
if (result instanceof PerformanceTestResult) {
await setPerformancePayload({ result, target, payload, root_payload });
} else {
await setFunctionalPayload({ result, target, payload, root_payload });
}
setRootPayload(root_payload, payload);
logger.info(`🔔 Publishing results to Teams...`);
return request.post({
url: target.inputs.url,
body: root_payload
});
}
async function setPerformancePayload({ result, target, payload, root_payload }) {
await extension_manager.run({ result, target, payload, root_payload, hook: HOOK.START });
setTitleBlock({ result, target, payload });
await setMainBlockForPerformance({ result, target, payload });
await extension_manager.run({ result, target, payload, root_payload, hook: HOOK.AFTER_SUMMARY });
await setTransactionBlock({ result, target, payload });
await extension_manager.run({ result, target, payload, root_payload, hook: HOOK.END });
}
async function setFunctionalPayload({ result, target, payload, root_payload }) {
await extension_manager.run({ result, target, payload, root_payload, hook: HOOK.START });
setTitleBlock({ result, target, payload });
setMainBlock({ result, target, payload });
await extension_manager.run({ result, target, payload, root_payload, hook: HOOK.AFTER_SUMMARY });
setSuiteBlock({ result, target, payload });
await extension_manager.run({ result, target, payload, root_payload, hook: HOOK.END });
}
function setTargetInputs(target) {
target.inputs = Object.assign({}, default_inputs, target.inputs);
if (target.inputs.publish === 'test-summary-slim') {
target.inputs.include_suites = false;
}
if (target.inputs.publish === 'failure-details') {
target.inputs.include_failure_details = true;
}
}
function getMainPayload(target) {
const main = {
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"type": "AdaptiveCard",
"version": "1.0",
"body": [],
"actions": []
};
if (target.inputs.width) {
if (!main.msteams) {
main.msteams = {};
}
main.msteams.width = target.inputs.width;
}
return main;
}
function getTitleText(result, target) {
const title = target.inputs.title ? target.inputs.title : result.name;
if (target.inputs.title_suffix) {
return `${title} ${target.inputs.title_suffix}`;
}
return `${title}`;
}
function setTitleBlock({ result, target, payload }) {
const title = getTitleText(result, target);
const emoji = result.status === 'PASS' ? '✅' : '❌';
let text = '';
if (target.inputs.include_suites === false) {
text = `${emoji} ${title}`;
} else if (result.suites && result.suites.length > 1 || result.transactions && result.transactions.length > 1) {
text = title;
} else {
text = `${emoji} ${title}`;
}
if (target.inputs.title_link) {
text = `[${text}](${target.inputs.title_link})`
}
payload.body.push({
"type": "TextBlock",
"text": text,
"size": "medium",
"weight": "bolder",
"wrap": true
});
}
function setMainBlock({ result, target, payload }) {
const facts = [];
const percentage = getPercentage(result.passed, result.total);
facts.push({
"title": "Results:",
"value": `${result.passed} / ${result.total} Passed (${percentage}%)`
});
facts.push({
"title": "Duration:",
"value": `${getPrettyDuration(result.duration, target.inputs.duration)}`
});
payload.body.push({
"type": "FactSet",
"facts": facts
});
}
function setSuiteBlock({ result, target, payload }) {
let suite_attachments_length = 0;
if (target.inputs.include_suites) {
for (let i = 0; i < result.suites.length && suite_attachments_length < target.inputs.max_suites; i++) {
const suite = result.suites[i];
if (target.inputs.only_failures && suite.status !== 'FAIL') {
continue;
}
// if suites length eq to 1 then main block will include suite summary
if (result.suites.length > 1) {
payload.body.push(...getSuiteSummary({ suite, target }));
suite_attachments_length += 1;
}
if (target.inputs.include_failure_details) {
payload.body.push(...getFailureDetailsFactSets(suite));
}
}
}
}
function getSuiteSummary({ suite, target }) {
const tg = new TeamsTarget({ target });
// const platform = getPlatform(TARGET.TEAMS);
const suite_title = tg.getSuiteTitle(suite);
const suite_results = tg.getSuiteResults(suite);
const duration = tg.getSuiteDuration(target, suite);
const blocks = [
{
"type": "TextBlock",
"text": suite_title,
"isSubtle": true,
"weight": "bolder",
"wrap": true
},
{
"type": "FactSet",
"facts": [
{
"title": "Results:",
"value": suite_results
},
{
"title": "Duration:",
"value": duration
}
]
}
];
const suite_metadata_text = tg.getSuiteMetaDataText(suite);
if (suite_metadata_text) {
blocks.push({
"type": "TextBlock",
"text": suite_metadata_text,
"wrap": true
});
}
return blocks;
}
function getFailureDetailsFactSets(suite) {
const fact_sets = [];
const cases = suite.cases;
for (let i = 0; i < cases.length; i++) {
const test_case = cases[i];
if (test_case.status === 'FAIL') {
fact_sets.push({
"type": "FactSet",
"facts": [
{
"title": "Test:",
"value": test_case.name
},
{
"title": "Error:",
"value": truncate(test_case.failure ?? 'N/A', 150)
}
]
});
}
}
return fact_sets;
}
function getRootPayload() {
return {
"type": "message",
"attachments": [
{
"contentType": "application/vnd.microsoft.card.adaptive",
"content": ''
}
]
};
}
function setRootPayload(root_payload, payload) {
root_payload.attachments[0].content = payload;
}
/**
* @param {object} param0
* @param {PerformanceTestResult} param0.result
*/
async function setMainBlockForPerformance({ result, target, payload }) {
const total = result.transactions.length;
const passed = result.transactions.filter(_transaction => _transaction.status === 'PASS').length;
const percentage = getPercentage(passed, total);
payload.body.push({
"type": "FactSet",
"facts": [
{
"title": "Results:",
"value": `${passed} / ${total} Passed (${percentage}%)`
}
]
});
payload.body.push({
"type": "FactSet",
"facts": await getFactMetrics({ metrics: result.metrics, result, target })
});
}
async function getFactMetrics({ metrics, target, result }) {
const facts = [];
const valid_metrics = await getValidMetrics({ metrics, target, result });
for (let i = 0; i < valid_metrics.length; i++) {
const metric = valid_metrics[i];
facts.push({
"title": `${metric.name}:`,
"value": getMetricValuesText({ metrics, metric, target, result })
})
}
return facts;
}
/**
* @param {object} param0
* @param {PerformanceTestResult} param0.result
*/
async function setTransactionBlock({ result, target, payload }) {
if (target.inputs.include_suites) {
for (let i = 0; i < result.transactions.length; i++) {
const transaction = result.transactions[i];
if (target.inputs.only_failures && transaction.status !== 'FAIL') {
continue;
}
// if transactions length eq to 1 then main block will include transaction summary
if (result.transactions.length > 1) {
const emoji = transaction.status === 'PASS' ? '✅' : '❌';
payload.body.push({
"type": "TextBlock",
"text": `${emoji} ${transaction.name}`,
"isSubtle": true,
"weight": "bolder",
"wrap": true
});
payload.body.push({
"type": "FactSet",
"facts": await getFactMetrics({ metrics: transaction.metrics, result, target })
});
}
}
}
}
const default_options = {
condition: STATUS.PASS_OR_FAIL
}
const default_inputs = {
publish: 'test-summary',
include_suites: true,
max_suites: 10,
only_failures: true,
include_failure_details: false,
width: '',
duration: '',
metrics: [
{
"name": "Samples",
},
{
"name": "Duration",
"condition": "always",
"fields": ["avg", "p95"]
}
]
}
async function handleErrors({ target, errors }) {
let title = 'Error: Reporting Test Results';
title = target.inputs.title ? title + ' - ' + target.inputs.title : title;
const root_payload = getRootPayload();
const payload = getMainPayload(target);
payload.body.push({
"type": "TextBlock",
"text": title,
"size": "medium",
"weight": "bolder",
"wrap": true
});
payload.body.push({
"type": "TextBlock",
"text": errors.join('\n'),
"size": "medium",
"weight": "bolder",
"wrap": true
});
setRootPayload(root_payload, payload);
return request.post({
url: target.inputs.url,
body: root_payload
});
}
class TeamsTarget extends BaseTarget {
constructor({ target }) {
super({ target });
}
}
module.exports = {
run,
handleErrors,
default_options
}