testbeats 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2021 Anudeep
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,60 @@
1
+ > !IMPORTANT - This npm package has been renamed from [test-results-reporter](https://www.npmjs.com/package/test-results-reporter) to [testbeats](https://www.npmjs.com/package/testbeats). test-results-reporter will soon be phased out, and users are encouraged to transition to testbeats.
2
+
3
+ <span align="center">
4
+
5
+ ![logo](https://github.com/test-results-reporter/testbeats/raw/main/assets/logo.png)
6
+
7
+
8
+ #### Publish test results to Microsoft Teams, Google Chat, Slack and many more.
9
+
10
+ <br />
11
+
12
+ ![Build](https://github.com/test-results-reporter/testbeats/workflows/Build/badge.svg?branch=main)
13
+ ![Downloads](https://img.shields.io/npm/dt/test-results-reporter?logo=npm&label=downloads-old)
14
+ ![Downloads](https://img.shields.io/npm/dt/testbeats?logo=npm)
15
+ ![Size](https://img.shields.io/bundlephobia/minzip/testbeats)
16
+
17
+ [![Stars](https://img.shields.io/github/stars/test-results-reporter/testbeats?style=social)](https://github.com/test-results-reporter/testbeats/stargazers)
18
+ ![Downloads](https://img.shields.io/github/downloads/test-results-reporter/testbeats/total?logo=github)
19
+
20
+ <hr>
21
+
22
+ ### Targets
23
+
24
+ <img height="48" style="margin: 6px;" src="./assets/slack.png" alt="slack" /> <img height="48" style="margin: 6px;" src="./assets/teams.png" alt="teams" /> <img height="48" style="margin: 6px;" src="./assets/chat.png" alt="chat" />
25
+
26
+ ### Extensions
27
+
28
+ <img height="48" style="margin: 6px;" src="./assets/reportportal.jpeg" alt="reportportal" /> <img height="48" style="margin: 6px;" src="./assets/quickchart.png" alt="quickchart" /> <img height="48" style="margin: 6px;" src="./assets/hyperlink.png" alt="hyperlink" /> <img height="48" style="margin: 6px;" src="./assets/mentions.png" alt="mentions" />
29
+
30
+ ### Test Results
31
+
32
+ <img height="48" style="margin: 6px;" src="./assets/testng.png" alt="testng" /> <img height="48" style="margin: 6px;" src="./assets/junit.png" alt="junit" /> <img height="48" style="margin: 6px;" src="./assets/cucumber.png" alt="cucumber" /> <img height="48" style="margin: 6px;" src="./assets/mocha.png" alt="mocha" /> <img height="48" style="margin: 6px;" src="./assets/xunit.png" alt="xunit" /> <img height="48" style="margin: 6px;" src="./assets/jmeter.png" alt="jmeter" />
33
+
34
+ <hr>
35
+
36
+ ## Sample Reports
37
+
38
+ <br>
39
+
40
+ ![teams-summary-report](https://github.com/test-results-reporter/testbeats/raw/main/assets/teams-qc.png)
41
+
42
+ ![slack-summary-report](https://github.com/test-results-reporter/testbeats/raw/main/assets/slack-report-portal-analysis.png)
43
+
44
+ <br />
45
+
46
+ <hr >
47
+
48
+ # [Documentation](https://test-results-reporter.github.io/)
49
+
50
+ </span>
51
+
52
+ <br />
53
+
54
+ ## Need Help
55
+
56
+ We use [Github Discussions](https://github.com/test-results-reporter/testbeats/discussions) to receive feedback, discuss ideas & answer questions. Head over to it and feel free to start a discussion. We are always happy to help 😊.
57
+
58
+ ## Support Us
59
+
60
+ Like this project! Star it on [Github](https://github.com/test-results-reporter/testbeats) ⭐. Your support means a lot to us.
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "testbeats",
3
+ "version": "2.0.0",
4
+ "description": "Publish test results to Microsoft Teams, Google Chat, Slack and InfluxDB",
5
+ "main": "src/index.js",
6
+ "types": "./src/index.d.ts",
7
+ "bin": {
8
+ "testbeats": "src/cli.js"
9
+ },
10
+ "files": [
11
+ "/src"
12
+ ],
13
+ "scripts": {
14
+ "test": "c8 mocha test",
15
+ "build": "pkg --out-path dist ."
16
+ },
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/test-results-reporter/testbeats.git"
20
+ },
21
+ "keywords": [
22
+ "test",
23
+ "results",
24
+ "publish",
25
+ "report",
26
+ "microsoft teams",
27
+ "teams",
28
+ "slack",
29
+ "influx",
30
+ "influxdb",
31
+ "junit",
32
+ "mocha",
33
+ "cucumber",
34
+ "testng",
35
+ "xunit",
36
+ "reportportal",
37
+ "google chat",
38
+ "chat",
39
+ "percy",
40
+ "jmeter"
41
+ ],
42
+ "author": "",
43
+ "license": "ISC",
44
+ "bugs": {
45
+ "url": "https://github.com/test-results-reporter/testbeats/issues"
46
+ },
47
+ "homepage": "https://test-results-reporter.github.io",
48
+ "dependencies": {
49
+ "async-retry": "^1.3.3",
50
+ "dotenv": "^14.3.2",
51
+ "influxdb-lite": "^1.0.0",
52
+ "performance-results-parser": "latest",
53
+ "phin-retry": "^1.0.3",
54
+ "pretty-ms": "^7.0.1",
55
+ "rosters": "0.0.1",
56
+ "sade": "^1.8.1",
57
+ "test-results-parser": "latest"
58
+ },
59
+ "devDependencies": {
60
+ "c8": "^7.12.0",
61
+ "mocha": "^10.1.0",
62
+ "pactum": "^3.2.3",
63
+ "pkg": "^5.8.0"
64
+ }
65
+ }
@@ -0,0 +1,74 @@
1
+ const request = require('phin-retry');
2
+ const TestResult = require('test-results-parser/src/models/TestResult');
3
+ const { getCIInformation } = require('../helpers/ci');
4
+
5
+ function get_base_url() {
6
+ return process.env.TEST_BEATS_URL || "https://app.testbeats.com";
7
+ }
8
+
9
+ /**
10
+ * @param {import('../index').PublishReport} config
11
+ * @param {TestResult} result
12
+ */
13
+ async function run(config, result) {
14
+ if (config.project && config.run && config.api_key) {
15
+ const run_id = await publishTestResults(config, result);
16
+ if (run_id) {
17
+ attachTestBeatsReportHyperLink(config, run_id);
18
+ }
19
+ }
20
+ }
21
+
22
+ /**
23
+ * @param {import('../index').PublishReport} config
24
+ * @param {TestResult} result
25
+ */
26
+ async function publishTestResults(config, result) {
27
+ try {
28
+ const payload = {
29
+ project: config.project,
30
+ run: config.run,
31
+ ...result
32
+ }
33
+ const ci = getCIInformation();
34
+ if (ci) {
35
+ payload.ci_details = [ci];
36
+ }
37
+
38
+ const response = await request.post({
39
+ url: `${get_base_url()}/api/core/v1/test-runs`,
40
+ headers: {
41
+ 'x-api-key': config.api_key
42
+ },
43
+ body: payload
44
+ });
45
+ return response.id;
46
+ } catch (error) {
47
+ console.log("Unable to publish results to TestBeats");
48
+ console.log(error);
49
+ }
50
+ }
51
+
52
+ /**
53
+ * @param {import('../index').PublishReport} config
54
+ * @param {string} run_id
55
+ */
56
+ function attachTestBeatsReportHyperLink(config, run_id) {
57
+ const beats_link = get_test_beats_report_link(run_id);
58
+ if (config.targets) {
59
+ for (const target of config.targets) {
60
+ target.inputs.title_link = beats_link;
61
+ }
62
+ }
63
+ }
64
+
65
+ /**
66
+ *
67
+ * @param {string} run_id
68
+ * @returns
69
+ */
70
+ function get_test_beats_report_link(run_id) {
71
+ return `${get_base_url()}/reports/${run_id}`;
72
+ }
73
+
74
+ module.exports = { run }
package/src/cli.js ADDED
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env node
2
+ require('dotenv').config();
3
+
4
+ const sade = require('sade');
5
+
6
+ const prog = sade('test-results-reporter');
7
+ const publish = require('./commands/publish');
8
+
9
+ prog
10
+ .version('0.0.7')
11
+ .option('-c, --config', 'Provide path to custom config', 'config.json');
12
+
13
+ prog.command('publish')
14
+ .action(async (opts) => {
15
+ try {
16
+ await publish.run(opts);
17
+ } catch (error) {
18
+ console.error(error);
19
+ process.exit(1);
20
+ }
21
+ });
22
+
23
+ prog.parse(process.argv);
@@ -0,0 +1,54 @@
1
+ const path = require('path');
2
+ const trp = require('test-results-parser');
3
+ const prp = require('performance-results-parser');
4
+
5
+ const { processData } = require('../helpers/helper');
6
+ const beats = require('../beats');
7
+ const target_manager = require('../targets');
8
+
9
+ /**
10
+ * @param {import('../index').PublishOptions} opts
11
+ */
12
+ async function run(opts) {
13
+ if (typeof opts.config === 'string') {
14
+ const cwd = process.cwd();
15
+ opts.config = require(path.join(cwd, opts.config));
16
+ }
17
+ const config = processData(opts.config);
18
+ if (config.reports) {
19
+ for (const report of config.reports) {
20
+ await processReport(report);
21
+ }
22
+ } else {
23
+ await processReport(config);
24
+ }
25
+ }
26
+
27
+ /**
28
+ *
29
+ * @param {import('../index').PublishReport} report
30
+ */
31
+ async function processReport(report) {
32
+ const parsed_results = [];
33
+ for (const result_options of report.results) {
34
+ if (result_options.type === 'custom') {
35
+ parsed_results.push(result_options.result);
36
+ } else if (result_options.type === 'jmeter') {
37
+ parsed_results.push(prp.parse(result_options));
38
+ } else {
39
+ parsed_results.push(trp.parse(result_options));
40
+ }
41
+ }
42
+
43
+ for (let i = 0; i < parsed_results.length; i++) {
44
+ const result = parsed_results[i];
45
+ await beats.run(report, result);
46
+ for (const target of report.targets) {
47
+ await target_manager.run(target, result);
48
+ }
49
+ }
50
+ }
51
+
52
+ module.exports = {
53
+ run
54
+ }
@@ -0,0 +1,134 @@
1
+ const { STATUS, HOOK } = require("../helpers/constants");
2
+ const { getCIInformation } = require('../helpers/ci');
3
+ const { addTeamsExtension, addSlackExtension, addChatExtension } = require('../helpers/extension.helper');
4
+ const { getTeamsMetaDataText, getSlackMetaDataText, getChatMetaDataText } = require('../helpers/metadata.helper');
5
+
6
+ /**
7
+ *
8
+ * @param {object} param0 - the payload object
9
+ * @param {import('..').Extension} param0.extension - The result object
10
+ *
11
+ */
12
+ async function run({ target, extension, payload, result }) {
13
+ extension.inputs = Object.assign({}, default_inputs, extension.inputs);
14
+ if (target.name === 'teams') {
15
+ extension.inputs = Object.assign({}, default_inputs_teams, extension.inputs);
16
+ const text = await get_text({ target, extension, result });
17
+ if (text) {
18
+ addTeamsExtension({ payload, extension, text });
19
+ }
20
+ } else if (target.name === 'slack') {
21
+ extension.inputs = Object.assign({}, default_inputs_slack, extension.inputs);
22
+ const text = await get_text({ target, extension, result });
23
+ if (text) {
24
+ addSlackExtension({ payload, extension, text });
25
+ }
26
+ } else if (target.name === 'chat') {
27
+ extension.inputs = Object.assign({}, default_inputs_chat, extension.inputs);
28
+ const text = await get_text({ target, extension, result });
29
+ if (text) {
30
+ addChatExtension({ payload, extension, text });
31
+ }
32
+ }
33
+ }
34
+
35
+ /**
36
+ *
37
+ * @param {import('..').CIInfoInputs} inputs
38
+ */
39
+ function get_repository_elements(inputs) {
40
+ const elements = [];
41
+ const ci = getCIInformation();
42
+ if (inputs.show_repository && ci && ci.repository_url && ci.repository_name) {
43
+ elements.push({ label: 'Repository', key: ci.repository_name, value: ci.repository_url, type: 'hyperlink' });
44
+ }
45
+ if (inputs.show_repository_branch && ci && ci.repository_ref) {
46
+ if (ci.repository_ref.includes('refs/pull')) {
47
+ const pr_url = ci.repository_url + ci.repository_ref.replace('refs/pull/', '/pull/');
48
+ const pr_name = ci.repository_ref.replace('refs/pull/', '').replace('/merge', '');
49
+ elements.push({ label: 'Pull Request', key: pr_name, value: pr_url, type: 'hyperlink' });
50
+ } else {
51
+ const branch_url = ci.repository_url + ci.repository_ref.replace('refs/heads/', '/tree/');
52
+ const branch_name = ci.repository_ref.replace('refs/heads/', '');
53
+ elements.push({ label: 'Branch', key: branch_name, value: branch_url, type: 'hyperlink' });
54
+ }
55
+ }
56
+ return elements;
57
+ }
58
+
59
+ /**
60
+ *
61
+ * @param {import('..').CIInfoInputs} inputs
62
+ */
63
+ function get_build_elements(inputs) {
64
+ let elements = [];
65
+ const ci = getCIInformation();
66
+ if (inputs.show_build && ci && ci.build_url) {
67
+ const name = (ci.build_name || 'Build') + (ci.build_number ? ` #${ci.build_number}` : '');
68
+ elements.push({ label: 'Build', key: name, value: ci.build_url, type: 'hyperlink' });
69
+ }
70
+ if (inputs.data) {
71
+ elements = elements.concat(inputs.data);
72
+ }
73
+ return elements;
74
+ }
75
+
76
+ async function get_text({ target, extension, result }) {
77
+ const repository_elements = get_repository_elements(extension.inputs);
78
+ const build_elements = get_build_elements(extension.inputs);
79
+ if (target.name === 'teams') {
80
+ const repository_text = await getTeamsMetaDataText({ elements: repository_elements, target, extension, result, default_condition: default_options.condition });
81
+ const build_text = await getTeamsMetaDataText({ elements: build_elements, target, extension, result, default_condition: default_options.condition });
82
+ if (build_text) {
83
+ return `${repository_text ? `${repository_text}\n\n` : '' }${build_text}`;
84
+ } else {
85
+ return repository_text;
86
+ }
87
+ } else if (target.name === 'slack') {
88
+ const repository_text = await getSlackMetaDataText({ elements: repository_elements, target, extension, result, default_condition: default_options.condition });
89
+ const build_text = await getSlackMetaDataText({ elements: build_elements, target, extension, result, default_condition: default_options.condition });
90
+ if (build_text) {
91
+ return `${repository_text ? `${repository_text}\n` : '' }${build_text}`;
92
+ } else {
93
+ return repository_text;
94
+ }
95
+ } else if (target.name === 'chat') {
96
+ const repository_text = await getChatMetaDataText({ elements: repository_elements, target, extension, result, default_condition: default_options.condition });
97
+ const build_text = await getChatMetaDataText({ elements: build_elements, target, extension, result, default_condition: default_options.condition });
98
+ if (build_text) {
99
+ return `${repository_text ? `${repository_text}<br>` : '' }${build_text}`;
100
+ } else {
101
+ return repository_text;
102
+ }
103
+ }
104
+ }
105
+
106
+ const default_options = {
107
+ hook: HOOK.AFTER_SUMMARY,
108
+ condition: STATUS.PASS_OR_FAIL,
109
+ }
110
+
111
+ const default_inputs = {
112
+ title: '',
113
+ show_repository: true,
114
+ show_repository_branch: true,
115
+ show_build: true,
116
+ }
117
+
118
+ const default_inputs_teams = {
119
+
120
+ separator: true
121
+ }
122
+
123
+ const default_inputs_slack = {
124
+ separator: false
125
+ }
126
+
127
+ const default_inputs_chat = {
128
+ separator: true
129
+ }
130
+
131
+ module.exports = {
132
+ run,
133
+ default_options
134
+ }
@@ -0,0 +1,29 @@
1
+ const path = require('path');
2
+ const { HOOK, STATUS } = require('../helpers/constants');
3
+
4
+ /**
5
+ *
6
+ * @param {object} param0
7
+ * @param {import('../index').CustomExtension} param0.extension
8
+ */
9
+ async function run({ target, extension, payload, root_payload, result }) {
10
+ if (typeof extension.inputs.load === 'string') {
11
+ const cwd = process.cwd();
12
+ const extension_runner = require(path.join(cwd, extension.inputs.load));
13
+ await extension_runner.run({ target, extension, payload, root_payload, result });
14
+ } else if (typeof extension.inputs.load === 'function') {
15
+ await extension.inputs.load({ target, extension, payload, root_payload, result });
16
+ } else {
17
+ throw `Invalid 'load' input in custom extension - ${extension.inputs.load}`;
18
+ }
19
+ }
20
+
21
+ const default_options = {
22
+ hook: HOOK.END,
23
+ condition: STATUS.PASS_OR_FAIL
24
+ }
25
+
26
+ module.exports = {
27
+ run,
28
+ default_options
29
+ }
@@ -0,0 +1,60 @@
1
+ const { STATUS, HOOK } = require("../helpers/constants");
2
+ const { addChatExtension, addSlackExtension, addTeamsExtension } = require('../helpers/extension.helper');
3
+ const { getTeamsMetaDataText, getSlackMetaDataText, getChatMetaDataText } = require("../helpers/metadata.helper");
4
+
5
+ async function run({ target, extension, payload, result }) {
6
+ const elements = get_elements(extension.inputs.links);
7
+ if (target.name === 'teams') {
8
+ extension.inputs = Object.assign({}, default_inputs_teams, extension.inputs);
9
+ const text = await getTeamsMetaDataText({ elements, target, extension, result, default_condition: default_options.condition });
10
+ if (text) {
11
+ addTeamsExtension({ payload, extension, text });
12
+ }
13
+ } else if (target.name === 'slack') {
14
+ extension.inputs = Object.assign({}, default_inputs_slack, extension.inputs);
15
+ extension.inputs.block_type = 'context';
16
+ const text = await getSlackMetaDataText({ elements, target, extension, result, default_condition: default_options.condition });
17
+ if (text) {
18
+ addSlackExtension({ payload, extension, text });
19
+ }
20
+ } else if (target.name === 'chat') {
21
+ extension.inputs = Object.assign({}, default_inputs_chat, extension.inputs);
22
+ const text = await getChatMetaDataText({ elements, target, extension, result, default_condition: default_options.condition });
23
+ if (text) {
24
+ addChatExtension({ payload, extension, text });
25
+ }
26
+ }
27
+ }
28
+
29
+ /**
30
+ *
31
+ * @param {import("..").Link[]} links
32
+ */
33
+ function get_elements(links) {
34
+ return links.map(_ => { return { key: _.text, value: _.url, type: 'hyperlink', condition: _.condition } });
35
+ }
36
+
37
+ const default_options = {
38
+ hook: HOOK.END,
39
+ condition: STATUS.PASS_OR_FAIL,
40
+ }
41
+
42
+ const default_inputs_teams = {
43
+ title: '',
44
+ separator: true
45
+ }
46
+
47
+ const default_inputs_slack = {
48
+ title: '',
49
+ separator: false
50
+ }
51
+
52
+ const default_inputs_chat = {
53
+ title: '',
54
+ separator: true
55
+ }
56
+
57
+ module.exports = {
58
+ run,
59
+ default_options
60
+ }
@@ -0,0 +1,63 @@
1
+ const hyperlinks = require('./hyperlinks');
2
+ const mentions = require('./mentions');
3
+ const rp_analysis = require('./report-portal-analysis');
4
+ const rp_history = require('./report-portal-history');
5
+ const qc_test_summary = require('./quick-chart-test-summary');
6
+ const percy_analysis = require('./percy-analysis');
7
+ const custom = require('./custom');
8
+ const metadata = require('./metadata');
9
+ const ci_info = require('./ci-info');
10
+ const { EXTENSION } = require('../helpers/constants');
11
+ const { checkCondition } = require('../helpers/helper');
12
+
13
+ async function run(options) {
14
+ const { target, result, hook } = options;
15
+ const extensions = target.extensions || [];
16
+ for (let i = 0; i < extensions.length; i++) {
17
+ const extension = extensions[i];
18
+ const extension_runner = getExtensionRunner(extension);
19
+ const extension_options = Object.assign({}, extension_runner.default_options, extension);
20
+ if (extension_options.hook === hook) {
21
+ if (await checkCondition({ condition: extension_options.condition, result, target, extension })) {
22
+ extension.outputs = {};
23
+ options.extension = extension;
24
+ try {
25
+ await extension_runner.run(options);
26
+ } catch (error) {
27
+ console.log('Failed to run extension');
28
+ console.log(extension);
29
+ console.log(error);
30
+ }
31
+ }
32
+ }
33
+ }
34
+ }
35
+
36
+ function getExtensionRunner(extension) {
37
+ switch (extension.name) {
38
+ case EXTENSION.HYPERLINKS:
39
+ return hyperlinks;
40
+ case EXTENSION.MENTIONS:
41
+ return mentions;
42
+ case EXTENSION.REPORT_PORTAL_ANALYSIS:
43
+ return rp_analysis;
44
+ case EXTENSION.REPORT_PORTAL_HISTORY:
45
+ return rp_history;
46
+ case EXTENSION.QUICK_CHART_TEST_SUMMARY:
47
+ return qc_test_summary;
48
+ case EXTENSION.PERCY_ANALYSIS:
49
+ return percy_analysis;
50
+ case EXTENSION.CUSTOM:
51
+ return custom;
52
+ case EXTENSION.METADATA:
53
+ return metadata;
54
+ case EXTENSION.CI_INFO:
55
+ return ci_info;
56
+ default:
57
+ return require(extension.name);
58
+ }
59
+ }
60
+
61
+ module.exports = {
62
+ run
63
+ }