testbeats 2.1.3 → 2.1.4
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/package.json +6 -6
- package/src/beats/beats.js +33 -18
- package/src/beats/beats.types.d.ts +6 -0
- package/src/cli.js +3 -2
- package/src/extensions/failure-analysis.extension.js +58 -0
- package/src/extensions/index.js +3 -0
- package/src/extensions/smart-analysis.extension.js +2 -2
- package/src/helpers/ci.js +53 -0
- package/src/helpers/constants.js +9 -0
- package/src/index.d.ts +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "testbeats",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.4",
|
|
4
4
|
"description": "Publish test results to Microsoft Teams, Google Chat, Slack and InfluxDB",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"types": "./src/index.d.ts",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"homepage": "https://testbeats.com",
|
|
48
48
|
"dependencies": {
|
|
49
49
|
"async-retry": "^1.3.3",
|
|
50
|
-
"dotenv": "^
|
|
50
|
+
"dotenv": "^16.4.5",
|
|
51
51
|
"form-data-lite": "^1.0.3",
|
|
52
52
|
"influxdb-lite": "^1.0.0",
|
|
53
53
|
"performance-results-parser": "latest",
|
|
@@ -58,12 +58,12 @@
|
|
|
58
58
|
"test-results-parser": "0.2.5"
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
|
61
|
-
"c8": "^
|
|
62
|
-
"mocha": "^10.
|
|
61
|
+
"c8": "^10.1.2",
|
|
62
|
+
"mocha": "^10.7.3",
|
|
63
63
|
"mocha-junit-reporter": "^2.2.1",
|
|
64
64
|
"mocha-multi-reporters": "^1.5.1",
|
|
65
|
-
"pactum": "^3.
|
|
66
|
-
"pkg": "^5.8.
|
|
65
|
+
"pactum": "^3.7.1",
|
|
66
|
+
"pkg": "^5.8.1"
|
|
67
67
|
},
|
|
68
68
|
"engines": {
|
|
69
69
|
"node": ">=14.0.0"
|
package/src/beats/beats.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const { getCIInformation } = require('../helpers/ci');
|
|
2
2
|
const logger = require('../utils/logger');
|
|
3
3
|
const { BeatsApi } = require('./beats.api');
|
|
4
|
-
const { HOOK } = require('../helpers/constants');
|
|
4
|
+
const { HOOK, PROCESS_STATUS } = require('../helpers/constants');
|
|
5
5
|
const TestResult = require('test-results-parser/src/models/TestResult');
|
|
6
6
|
const { BeatsAttachments } = require('./beats.attachments');
|
|
7
7
|
|
|
@@ -31,9 +31,7 @@ class Beats {
|
|
|
31
31
|
await this.#publishTestResults();
|
|
32
32
|
await this.#uploadAttachments();
|
|
33
33
|
this.#updateTitleLink();
|
|
34
|
-
await this.#
|
|
35
|
-
await this.#attachSmartAnalysis();
|
|
36
|
-
await this.#attachErrorClusters();
|
|
34
|
+
await this.#attachExtensions();
|
|
37
35
|
}
|
|
38
36
|
|
|
39
37
|
#setCIInfo() {
|
|
@@ -104,13 +102,20 @@ class Beats {
|
|
|
104
102
|
}
|
|
105
103
|
}
|
|
106
104
|
|
|
107
|
-
async #
|
|
105
|
+
async #attachExtensions() {
|
|
108
106
|
if (!this.test_run_id) {
|
|
109
107
|
return;
|
|
110
108
|
}
|
|
111
109
|
if (!this.config.targets) {
|
|
112
110
|
return;
|
|
113
111
|
}
|
|
112
|
+
await this.#attachFailureSummary();
|
|
113
|
+
await this.#attachFailureAnalysis();
|
|
114
|
+
await this.#attachSmartAnalysis();
|
|
115
|
+
await this.#attachErrorClusters();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async #attachFailureSummary() {
|
|
114
119
|
if (this.result.status !== 'FAIL') {
|
|
115
120
|
return;
|
|
116
121
|
}
|
|
@@ -132,13 +137,29 @@ class Beats {
|
|
|
132
137
|
}
|
|
133
138
|
}
|
|
134
139
|
|
|
135
|
-
async #
|
|
136
|
-
if (
|
|
140
|
+
async #attachFailureAnalysis() {
|
|
141
|
+
if (this.result.status !== 'FAIL') {
|
|
137
142
|
return;
|
|
138
143
|
}
|
|
139
|
-
if (
|
|
144
|
+
if (this.config.show_failure_analysis === false) {
|
|
140
145
|
return;
|
|
141
146
|
}
|
|
147
|
+
try {
|
|
148
|
+
logger.info('🪄 Fetching Failure Analysis...');
|
|
149
|
+
await this.#setTestRun('Failure Analysis Status', 'failure_analysis_status');
|
|
150
|
+
this.config.extensions.push({
|
|
151
|
+
name: 'failure-analysis',
|
|
152
|
+
hook: HOOK.AFTER_SUMMARY,
|
|
153
|
+
inputs: {
|
|
154
|
+
data: this.test_run
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
} catch (error) {
|
|
158
|
+
logger.error(`❌ Unable to attach failure analysis: ${error.message}`, error);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async #attachSmartAnalysis() {
|
|
142
163
|
if (this.config.show_smart_analysis === false) {
|
|
143
164
|
return;
|
|
144
165
|
}
|
|
@@ -165,7 +186,7 @@ class Beats {
|
|
|
165
186
|
}
|
|
166
187
|
|
|
167
188
|
async #setTestRun(text, wait_for = 'smart_analysis_status') {
|
|
168
|
-
if (this.test_run && this.test_run[wait_for] ===
|
|
189
|
+
if (this.test_run && this.test_run[wait_for] === PROCESS_STATUS.COMPLETED) {
|
|
169
190
|
return;
|
|
170
191
|
}
|
|
171
192
|
let retry = 3;
|
|
@@ -175,13 +196,13 @@ class Beats {
|
|
|
175
196
|
this.test_run = await this.api.getTestRun(this.test_run_id);
|
|
176
197
|
const status = this.test_run && this.test_run[wait_for];
|
|
177
198
|
switch (status) {
|
|
178
|
-
case
|
|
199
|
+
case PROCESS_STATUS.COMPLETED:
|
|
179
200
|
logger.debug(`☑️ ${text} generated successfully`);
|
|
180
201
|
return;
|
|
181
|
-
case
|
|
202
|
+
case PROCESS_STATUS.FAILED:
|
|
182
203
|
logger.error(`❌ Failed to generate ${text}`);
|
|
183
204
|
return;
|
|
184
|
-
case
|
|
205
|
+
case PROCESS_STATUS.SKIPPED:
|
|
185
206
|
logger.warn(`❗ Skipped generating ${text}`);
|
|
186
207
|
return;
|
|
187
208
|
}
|
|
@@ -191,12 +212,6 @@ class Beats {
|
|
|
191
212
|
}
|
|
192
213
|
|
|
193
214
|
async #attachErrorClusters() {
|
|
194
|
-
if (!this.test_run_id) {
|
|
195
|
-
return;
|
|
196
|
-
}
|
|
197
|
-
if (!this.config.targets) {
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
200
215
|
if (this.result.status !== 'FAIL') {
|
|
201
216
|
return;
|
|
202
217
|
}
|
|
@@ -9,6 +9,12 @@ export type IBeatExecutionMetric = {
|
|
|
9
9
|
added: number
|
|
10
10
|
removed: number
|
|
11
11
|
flaky: number
|
|
12
|
+
product_bugs: number
|
|
13
|
+
environment_issues: number
|
|
14
|
+
automation_bugs: number
|
|
15
|
+
not_a_defects: number
|
|
16
|
+
to_investigate: number
|
|
17
|
+
auto_analysed: number
|
|
12
18
|
failure_summary: any
|
|
13
19
|
failure_summary_provider: any
|
|
14
20
|
failure_summary_model: any
|
package/src/cli.js
CHANGED
|
@@ -6,9 +6,10 @@ const sade = require('sade');
|
|
|
6
6
|
const prog = sade('testbeats');
|
|
7
7
|
const { PublishCommand } = require('./commands/publish.command');
|
|
8
8
|
const logger = require('./utils/logger');
|
|
9
|
+
const pkg = require('../package.json');
|
|
9
10
|
|
|
10
11
|
prog
|
|
11
|
-
.version(
|
|
12
|
+
.version(pkg.version)
|
|
12
13
|
.option('-c, --config', 'path to config file')
|
|
13
14
|
.option('-l, --logLevel', 'Log Level', "INFO")
|
|
14
15
|
.option('--api-key', 'api key')
|
|
@@ -40,4 +41,4 @@ prog.command('publish')
|
|
|
40
41
|
}
|
|
41
42
|
});
|
|
42
43
|
|
|
43
|
-
prog.parse(process.argv);
|
|
44
|
+
prog.parse(process.argv);
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
const { BaseExtension } = require('./base.extension');
|
|
2
|
+
const { STATUS, HOOK } = require("../helpers/constants");
|
|
3
|
+
|
|
4
|
+
class FailureAnalysisExtension extends BaseExtension {
|
|
5
|
+
|
|
6
|
+
constructor(target, extension, result, payload, root_payload) {
|
|
7
|
+
super(target, extension, result, payload, root_payload);
|
|
8
|
+
this.#setDefaultOptions();
|
|
9
|
+
this.#setDefaultInputs();
|
|
10
|
+
this.updateExtensionInputs();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
#setDefaultOptions() {
|
|
14
|
+
this.default_options.hook = HOOK.AFTER_SUMMARY,
|
|
15
|
+
this.default_options.condition = STATUS.PASS_OR_FAIL;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
#setDefaultInputs() {
|
|
19
|
+
this.default_inputs.title = '';
|
|
20
|
+
this.default_inputs.title_link = '';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
run() {
|
|
24
|
+
this.#setText();
|
|
25
|
+
this.attach();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
#setText() {
|
|
29
|
+
const data = this.extension.inputs.data;
|
|
30
|
+
if (!data) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @type {import('../beats/beats.types').IBeatExecutionMetric}
|
|
36
|
+
*/
|
|
37
|
+
const execution_metrics = data.execution_metrics[0];
|
|
38
|
+
|
|
39
|
+
if (!execution_metrics) {
|
|
40
|
+
logger.warn('⚠️ No execution metrics found. Skipping.');
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const failure_analysis = [];
|
|
45
|
+
|
|
46
|
+
if (execution_metrics.to_investigate) {
|
|
47
|
+
failure_analysis.push(`🔎 To Investigate: ${execution_metrics.to_investigate}`);
|
|
48
|
+
}
|
|
49
|
+
if (execution_metrics.auto_analysed) {
|
|
50
|
+
failure_analysis.push(`🪄 Auto Analysed: ${execution_metrics.auto_analysed}`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
this.text = failure_analysis.join(' • ');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
module.exports = { FailureAnalysisExtension };
|
package/src/extensions/index.js
CHANGED
|
@@ -13,6 +13,7 @@ const { EXTENSION } = require('../helpers/constants');
|
|
|
13
13
|
const { checkCondition } = require('../helpers/helper');
|
|
14
14
|
const logger = require('../utils/logger');
|
|
15
15
|
const { ErrorClustersExtension } = require('./error-clusters.extension');
|
|
16
|
+
const { FailureAnalysisExtension } = require('./failure-analysis.extension');
|
|
16
17
|
|
|
17
18
|
async function run(options) {
|
|
18
19
|
const { target, result, hook } = options;
|
|
@@ -59,6 +60,8 @@ function getExtensionRunner(extension, options) {
|
|
|
59
60
|
return new CIInfoExtension(options.target, extension, options.result, options.payload, options.root_payload);
|
|
60
61
|
case EXTENSION.AI_FAILURE_SUMMARY:
|
|
61
62
|
return new AIFailureSummaryExtension(options.target, extension, options.result, options.payload, options.root_payload);
|
|
63
|
+
case EXTENSION.FAILURE_ANALYSIS:
|
|
64
|
+
return new FailureAnalysisExtension(options.target, extension, options.result, options.payload, options.root_payload);
|
|
62
65
|
case EXTENSION.SMART_ANALYSIS:
|
|
63
66
|
return new SmartAnalysisExtension(options.target, extension, options.result, options.payload, options.root_payload);
|
|
64
67
|
case EXTENSION.ERROR_CLUSTERS:
|
|
@@ -65,13 +65,13 @@ class SmartAnalysisExtension extends BaseExtension {
|
|
|
65
65
|
for (const item of smart_analysis) {
|
|
66
66
|
rows.push(item);
|
|
67
67
|
if (rows.length === 3) {
|
|
68
|
-
texts.push(rows.join('
|
|
68
|
+
texts.push(rows.join(' • '));
|
|
69
69
|
rows.length = 0;
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
if (rows.length > 0) {
|
|
74
|
-
texts.push(rows.join('
|
|
74
|
+
texts.push(rows.join(' • '));
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
this.text = this.mergeTexts(texts);
|
package/src/helpers/ci.js
CHANGED
|
@@ -1,9 +1,21 @@
|
|
|
1
|
+
const os = require('os');
|
|
2
|
+
const pkg = require('../../package.json');
|
|
3
|
+
|
|
1
4
|
const ENV = process.env;
|
|
2
5
|
|
|
3
6
|
/**
|
|
4
7
|
* @returns {import('../extensions/extensions').ICIInfo}
|
|
5
8
|
*/
|
|
6
9
|
function getCIInformation() {
|
|
10
|
+
const ci_info = getBaseCIInfo();
|
|
11
|
+
const system_info = getSystemInfo();
|
|
12
|
+
return {
|
|
13
|
+
...ci_info,
|
|
14
|
+
...system_info
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function getBaseCIInfo() {
|
|
7
19
|
if (ENV.GITHUB_ACTIONS) {
|
|
8
20
|
return getGitHubActionsInformation();
|
|
9
21
|
}
|
|
@@ -16,6 +28,7 @@ function getCIInformation() {
|
|
|
16
28
|
if (ENV.SYSTEM_TEAMFOUNDATIONCOLLECTIONURI) {
|
|
17
29
|
return getAzureDevOpsInformation();
|
|
18
30
|
}
|
|
31
|
+
return getDefaultInformation();
|
|
19
32
|
}
|
|
20
33
|
|
|
21
34
|
function getGitHubActionsInformation() {
|
|
@@ -82,6 +95,46 @@ function getGitLabInformation() {
|
|
|
82
95
|
}
|
|
83
96
|
}
|
|
84
97
|
|
|
98
|
+
function getDefaultInformation() {
|
|
99
|
+
return {
|
|
100
|
+
ci: ENV.TEST_BEATS_CI_NAME,
|
|
101
|
+
git: ENV.TEST_BEATS_CI_GIT,
|
|
102
|
+
repository_url: ENV.TEST_BEATS_CI_REPOSITORY_URL,
|
|
103
|
+
repository_name: ENV.TEST_BEATS_CI_REPOSITORY_NAME,
|
|
104
|
+
repository_ref: ENV.TEST_BEATS_CI_REPOSITORY_REF,
|
|
105
|
+
repository_commit_sha: ENV.TEST_BEATS_CI_REPOSITORY_COMMIT_SHA,
|
|
106
|
+
build_url: ENV.TEST_BEATS_CI_BUILD_URL,
|
|
107
|
+
build_number: ENV.TEST_BEATS_CI_BUILD_NUMBER,
|
|
108
|
+
build_name: ENV.TEST_BEATS_CI_BUILD_NAME,
|
|
109
|
+
build_reason: ENV.TEST_BEATS_CI_BUILD_REASON,
|
|
110
|
+
user: ENV.TEST_BEATS_CI_USER || os.userInfo().username
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function getSystemInfo() {
|
|
115
|
+
function getRuntimeInfo() {
|
|
116
|
+
if (typeof process !== 'undefined' && process.versions && process.versions.node) {
|
|
117
|
+
return { name: 'node', version: process.versions.node };
|
|
118
|
+
} else if (typeof Deno !== 'undefined') {
|
|
119
|
+
return { name: 'deno', version: Deno.version.deno };
|
|
120
|
+
} else if (typeof Bun !== 'undefined') {
|
|
121
|
+
return { name: 'bun', version: Bun.version };
|
|
122
|
+
} else {
|
|
123
|
+
return { name: 'unknown', version: 'unknown' };
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const runtime = getRuntimeInfo();
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
runtime: runtime.name,
|
|
131
|
+
runtime_version: runtime.version,
|
|
132
|
+
os: os.platform(),
|
|
133
|
+
os_version: os.release(),
|
|
134
|
+
testbeats_version: pkg.version
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
85
138
|
module.exports = {
|
|
86
139
|
getCIInformation
|
|
87
140
|
}
|
package/src/helpers/constants.js
CHANGED
|
@@ -21,6 +21,7 @@ const TARGET = Object.freeze({
|
|
|
21
21
|
|
|
22
22
|
const EXTENSION = Object.freeze({
|
|
23
23
|
AI_FAILURE_SUMMARY: 'ai-failure-summary',
|
|
24
|
+
FAILURE_ANALYSIS: 'failure-analysis',
|
|
24
25
|
SMART_ANALYSIS: 'smart-analysis',
|
|
25
26
|
ERROR_CLUSTERS: 'error-clusters',
|
|
26
27
|
HYPERLINKS: 'hyperlinks',
|
|
@@ -39,6 +40,13 @@ const URLS = Object.freeze({
|
|
|
39
40
|
QUICK_CHART: 'https://quickchart.io'
|
|
40
41
|
});
|
|
41
42
|
|
|
43
|
+
const PROCESS_STATUS = Object.freeze({
|
|
44
|
+
RUNNING: 'RUNNING',
|
|
45
|
+
COMPLETED: 'COMPLETED',
|
|
46
|
+
FAILED: 'FAILED',
|
|
47
|
+
SKIPPED: 'SKIPPED',
|
|
48
|
+
});
|
|
49
|
+
|
|
42
50
|
const MIN_NODE_VERSION = 14;
|
|
43
51
|
|
|
44
52
|
module.exports = Object.freeze({
|
|
@@ -47,5 +55,6 @@ module.exports = Object.freeze({
|
|
|
47
55
|
TARGET,
|
|
48
56
|
EXTENSION,
|
|
49
57
|
URLS,
|
|
58
|
+
PROCESS_STATUS,
|
|
50
59
|
MIN_NODE_VERSION
|
|
51
60
|
});
|
package/src/index.d.ts
CHANGED