testbeats 2.0.0 → 2.0.2

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/README.md CHANGED
@@ -1,4 +1,4 @@
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.
1
+ > 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
2
 
3
3
  <span align="center">
4
4
 
@@ -19,35 +19,23 @@
19
19
 
20
20
  <hr>
21
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" />
22
+ </span>
33
23
 
34
- <hr>
24
+ ### Get Started
35
25
 
36
- ## Sample Reports
26
+ TestBeats is a tool designed to streamline the process of publishing test results from various automation testing frameworks to communication platforms like **slack**, **teams** and more for easy access and collaboration. It unifies your test reporting to build quality insights and make faster decisions.
37
27
 
38
- <br>
28
+ Read more about the project at [https://testbeats.com](https://testbeats.com)
39
29
 
40
- ![teams-summary-report](https://github.com/test-results-reporter/testbeats/raw/main/assets/teams-qc.png)
30
+ ### Sample Reports
41
31
 
42
- ![slack-summary-report](https://github.com/test-results-reporter/testbeats/raw/main/assets/slack-report-portal-analysis.png)
32
+ #### Alerts in Slack
43
33
 
44
- <br />
34
+ ![testbeats-failure-summary](./assets/testbeats-slack-failure-summary.png)
45
35
 
46
- <hr >
36
+ #### Results in Portal
47
37
 
48
- # [Documentation](https://test-results-reporter.github.io/)
49
-
50
- </span>
38
+ ![testbeats-failure-summary](./assets/testbeats-failure-summary.png)
51
39
 
52
40
  <br />
53
41
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testbeats",
3
- "version": "2.0.0",
3
+ "version": "2.0.2",
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",
@@ -44,7 +44,7 @@
44
44
  "bugs": {
45
45
  "url": "https://github.com/test-results-reporter/testbeats/issues"
46
46
  },
47
- "homepage": "https://test-results-reporter.github.io",
47
+ "homepage": "https://testbeats.com",
48
48
  "dependencies": {
49
49
  "async-retry": "^1.3.3",
50
50
  "dotenv": "^14.3.2",
@@ -0,0 +1,40 @@
1
+ const request = require('phin-retry');
2
+
3
+ class BeatsApi {
4
+
5
+ /**
6
+ * @param {import('../index').PublishReport} config
7
+ */
8
+ constructor(config) {
9
+ this.config = config;
10
+ }
11
+
12
+ postTestRun(payload) {
13
+ return request.post({
14
+ url: `${this.getBaseUrl()}/api/core/v1/test-runs`,
15
+ headers: {
16
+ 'x-api-key': this.config.api_key
17
+ },
18
+ body: payload
19
+ });
20
+ }
21
+
22
+ /**
23
+ * @param {string} run_id
24
+ * @returns
25
+ */
26
+ getTestRun(run_id) {
27
+ return request.get({
28
+ url: `${this.getBaseUrl()}/api/core/v1/test-runs/key?id=${run_id}`,
29
+ headers: {
30
+ 'x-api-key': this.config.api_key
31
+ }
32
+ });
33
+ }
34
+
35
+ getBaseUrl() {
36
+ return process.env.TEST_BEATS_URL || "https://app.testbeats.com";
37
+ }
38
+ }
39
+
40
+ module.exports = { BeatsApi }
@@ -0,0 +1,151 @@
1
+ const { getCIInformation } = require('../helpers/ci');
2
+ const logger = require('../utils/logger');
3
+ const { BeatsApi } = require('./beats.api');
4
+ const { HOOK } = require('../helpers/constants');
5
+
6
+ class Beats {
7
+
8
+ /**
9
+ * @param {import('../index').PublishReport} config
10
+ * @param {TestResult} result
11
+ */
12
+ constructor(config, result) {
13
+ this.config = config;
14
+ this.result = result;
15
+ this.api = new BeatsApi(config);
16
+ this.test_run_id = '';
17
+ }
18
+
19
+ async publish() {
20
+ this.#setCIInfo();
21
+ this.#setProjectName();
22
+ this.#setRunName();
23
+ this.#setApiKey();
24
+ await this.#publishTestResults();
25
+ this.#updateTitleLink();
26
+ await this.#attachFailureSummary();
27
+ }
28
+
29
+ #setCIInfo() {
30
+ this.ci = getCIInformation();
31
+ }
32
+
33
+ #setProjectName() {
34
+ this.config.project = this.config.project || process.env.TEST_BEATS_PROJECT || (this.ci && this.ci.repository_name) || 'demo-project';
35
+ }
36
+
37
+ #setApiKey() {
38
+ this.config.api_key = this.config.api_key || process.env.TEST_BEATS_API_KEY;
39
+ }
40
+
41
+ #setRunName() {
42
+ this.config.run = this.config.run || process.env.TEST_BEATS_RUN || (this.ci && this.ci.build_name) || 'demo-run';
43
+ }
44
+
45
+ async #publishTestResults() {
46
+ if (!this.config.api_key) {
47
+ logger.warn('😿 No API key provided, skipping publishing results to TestBeats Portal');
48
+ return;
49
+ }
50
+ logger.info("🚀 Publishing results to TestBeats Portal...");
51
+ try {
52
+ const payload = this.#getPayload();
53
+ const response = await this.api.postTestRun(payload);
54
+ this.test_run_id = response.id;
55
+ } catch (error) {
56
+ logger.error(`❌ Unable to publish results to TestBeats Portal: ${error.message}`, error);
57
+ }
58
+ }
59
+
60
+ #getPayload() {
61
+ const payload = {
62
+ project: this.config.project,
63
+ run: this.config.run,
64
+ ...this.result
65
+ }
66
+ if (this.ci) {
67
+ payload.ci_details = [this.ci];
68
+ }
69
+ return payload;
70
+ }
71
+
72
+ #updateTitleLink() {
73
+ if (!this.test_run_id) {
74
+ return;
75
+ }
76
+ if (!this.config.targets) {
77
+ return;
78
+ }
79
+ const link = `${this.api.getBaseUrl()}/reports/${this.test_run_id}`;
80
+ for (const target of this.config.targets) {
81
+ target.inputs.title_link = link;
82
+ }
83
+ }
84
+
85
+ async #attachFailureSummary() {
86
+ if (!this.test_run_id) {
87
+ return;
88
+ }
89
+ if (!this.config.targets) {
90
+ return;
91
+ }
92
+ if (this.result.status !== 'FAIL') {
93
+ return;
94
+ }
95
+ if (this.config.show_failure_summary === false) {
96
+ return;
97
+ }
98
+ const text = await this.#getFailureSummary();
99
+ if (!text) {
100
+ return;
101
+ }
102
+ const extension = this.#getAIFailureSummaryExtension(text);
103
+ for (const target of this.config.targets) {
104
+ target.extensions = target.extensions || [];
105
+ target.extensions.push(extension);
106
+ }
107
+ }
108
+
109
+ async #getFailureSummary() {
110
+ logger.info('✨ Fetching AI Failure Summary...');
111
+ let retry = 3;
112
+ while (retry >= 0) {
113
+ retry = retry - 1;
114
+ await new Promise(resolve => setTimeout(resolve, this.#getDelay()));
115
+ const test_run = await this.api.getTestRun(this.test_run_id);
116
+ const status = test_run && test_run.failure_summary_status;
117
+ switch (status) {
118
+ case 'COMPLETED':
119
+ return test_run.execution_metrics[0].failure_summary;
120
+ case 'FAILED':
121
+ logger.error(`❌ Failed to generate AI Failure Summary`);
122
+ return;
123
+ case 'SKIPPED':
124
+ logger.warn(`❗ Skipped generating AI Failure Summary`);
125
+ return;
126
+ }
127
+ logger.info(`🔄 AI Failure Summary not generated, retrying...`);
128
+ }
129
+ logger.warn(`🙈 AI Failure Summary not generated in given time`);
130
+ }
131
+
132
+ #getDelay() {
133
+ if (process.env.TEST_BEATS_DELAY) {
134
+ return parseInt(process.env.TEST_BEATS_DELAY);
135
+ }
136
+ return 3000;
137
+ }
138
+
139
+ #getAIFailureSummaryExtension(text) {
140
+ return {
141
+ name: 'ai-failure-summary',
142
+ hook: HOOK.AFTER_SUMMARY,
143
+ inputs: {
144
+ failure_summary: text
145
+ }
146
+ };
147
+ }
148
+
149
+ }
150
+
151
+ module.exports = { Beats }
@@ -1,74 +1,13 @@
1
- const request = require('phin-retry');
2
1
  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
- }
2
+ const { Beats } = require('./beats');
8
3
 
9
4
  /**
10
5
  * @param {import('../index').PublishReport} config
11
6
  * @param {TestResult} result
12
7
  */
13
8
  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}`;
9
+ const beats = new Beats(config, result);
10
+ await beats.publish();
72
11
  }
73
12
 
74
13
  module.exports = { run }
package/src/cli.js CHANGED
@@ -2,20 +2,24 @@
2
2
  require('dotenv').config();
3
3
 
4
4
  const sade = require('sade');
5
-
6
- const prog = sade('test-results-reporter');
5
+
6
+ const prog = sade('testbeats');
7
7
  const publish = require('./commands/publish');
8
-
8
+ const logger = require('./utils/logger');
9
+
9
10
  prog
10
- .version('0.0.7')
11
- .option('-c, --config', 'Provide path to custom config', 'config.json');
11
+ .version('2.0.1')
12
+ .option('-c, --config', 'Provide path to custom config', 'config.json')
13
+ .option('-l, --logLevel', 'Log Level', "INFO");
12
14
 
13
15
  prog.command('publish')
14
16
  .action(async (opts) => {
15
17
  try {
18
+ logger.setLevel(opts.logLevel);
19
+ logger.info(`Initiating...`);
16
20
  await publish.run(opts);
17
21
  } catch (error) {
18
- console.error(error);
22
+ logger.error(`Report publish failed: ${error.message}`);
19
23
  process.exit(1);
20
24
  }
21
25
  });
@@ -2,33 +2,51 @@ const path = require('path');
2
2
  const trp = require('test-results-parser');
3
3
  const prp = require('performance-results-parser');
4
4
 
5
+ const pkg = require('../../package.json');
5
6
  const { processData } = require('../helpers/helper');
6
7
  const beats = require('../beats');
7
8
  const target_manager = require('../targets');
9
+ const logger = require('../utils/logger');
8
10
 
9
11
  /**
10
- * @param {import('../index').PublishOptions} opts
12
+ * @param {import('../index').PublishOptions} opts
11
13
  */
12
14
  async function run(opts) {
15
+ logger.info(`💡 TestBeats v${pkg.version}`);
16
+ if (!opts) {
17
+ throw new Error('Missing publish options');
18
+ }
19
+ if (!opts.config) {
20
+ throw new Error('Missing publish config');
21
+ }
13
22
  if (typeof opts.config === 'string') {
14
23
  const cwd = process.cwd();
15
- opts.config = require(path.join(cwd, opts.config));
24
+ const file_path = path.join(cwd, opts.config);
25
+ try {
26
+ opts.config = require(path.join(cwd, opts.config));
27
+ } catch (error) {
28
+ throw new Error(`Config file not found: ${file_path}`);
29
+ }
16
30
  }
17
31
  const config = processData(opts.config);
18
32
  if (config.reports) {
19
33
  for (const report of config.reports) {
34
+ validateConfig(report);
20
35
  await processReport(report);
21
36
  }
22
37
  } else {
38
+ validateConfig(config);
23
39
  await processReport(config);
24
40
  }
41
+ logger.info('✅ Results published successfully!');
25
42
  }
26
43
 
27
44
  /**
28
- *
29
- * @param {import('../index').PublishReport} report
45
+ *
46
+ * @param {import('../index').PublishReport} report
30
47
  */
31
48
  async function processReport(report) {
49
+ logger.debug("processReport: Started")
32
50
  const parsed_results = [];
33
51
  for (const result_options of report.results) {
34
52
  if (result_options.type === 'custom') {
@@ -43,10 +61,106 @@ async function processReport(report) {
43
61
  for (let i = 0; i < parsed_results.length; i++) {
44
62
  const result = parsed_results[i];
45
63
  await beats.run(report, result);
46
- for (const target of report.targets) {
47
- await target_manager.run(target, result);
64
+ if (report.targets) {
65
+ for (const target of report.targets) {
66
+ await target_manager.run(target, result);
67
+ }
68
+ } else {
69
+ logger.warn('No targets defined, skipping sending results to targets');
70
+ }
71
+ }
72
+ logger.debug("processReport: Ended")
73
+ }
74
+
75
+ /**
76
+ *
77
+ * @param {import('../index').PublishReport} config
78
+ */
79
+ function validateConfig(config) {
80
+ logger.info("🛠️ Validating configuration...")
81
+ if (!config) {
82
+ throw new Error('Missing configuration');
83
+ }
84
+ validateResults(config);
85
+ validateTargets(config);
86
+ }
87
+
88
+ /**
89
+ *
90
+ * @param {import('../index').PublishReport} config
91
+ */
92
+ function validateResults(config) {
93
+ logger.debug("Validating results...")
94
+ if (!config.results) {
95
+ throw new Error('Missing results properties in config');
96
+ }
97
+ if (!Array.isArray(config.results)) {
98
+ throw new Error('results must be an array');
99
+ }
100
+ if (!config.results.length) {
101
+ throw new Error('At least one result must be defined');
102
+ }
103
+ for (const result of config.results) {
104
+ if (!result.type) {
105
+ throw new Error('Missing result type');
106
+ }
107
+ if (result.type === 'custom') {
108
+ if (!result.result) {
109
+ throw new Error('Missing result');
110
+ }
111
+ } else {
112
+ if (!result.files) {
113
+ throw new Error('Missing result files');
114
+ }
115
+ if (!Array.isArray(result.files)) {
116
+ throw new Error('result files must be an array');
117
+ }
118
+ if (!result.files.length) {
119
+ throw new Error('At least one result file must be defined');
120
+ }
121
+ }
122
+ }
123
+ logger.debug("Validating results - Successful!")
124
+ }
125
+
126
+ /**
127
+ *
128
+ * @param {import('../index').PublishReport} config
129
+ */
130
+ function validateTargets(config) {
131
+ logger.debug("Validating targets...")
132
+ if (!config.targets) {
133
+ logger.warn('⚠️ Targets are not defined in config');
134
+ return;
135
+ }
136
+ if (!Array.isArray(config.targets)) {
137
+ throw new Error('targets must be an array');
138
+ }
139
+ for (const target of config.targets) {
140
+ if (!target.name) {
141
+ throw new Error('missing target name');
142
+ }
143
+ if (target.name === 'slack' || target.name === 'teams' || target.name === 'chat') {
144
+ if (!target.inputs) {
145
+ throw new Error(`missing inputs in ${target.name} target`);
146
+ }
147
+ }
148
+ if (target.inputs) {
149
+ const inputs = target.inputs;
150
+ if (target.name === 'slack' || target.name === 'teams' || target.name === 'chat') {
151
+ if (!inputs.url) {
152
+ throw new Error(`missing url in ${target.name} target inputs`);
153
+ }
154
+ if (typeof inputs.url !== 'string') {
155
+ throw new Error(`url in ${target.name} target inputs must be a string`);
156
+ }
157
+ if (!inputs.url.startsWith('http')) {
158
+ throw new Error(`url in ${target.name} target inputs must start with 'http' or 'https'`);
159
+ }
160
+ }
48
161
  }
49
162
  }
163
+ logger.debug("Validating targets - Successful!")
50
164
  }
51
165
 
52
166
  module.exports = {
@@ -0,0 +1,72 @@
1
+ const { STATUS, HOOK } = require("../helpers/constants");
2
+ const { addChatExtension, addSlackExtension, addTeamsExtension } = require('../helpers/extension.helper');
3
+
4
+ /**
5
+ * @param {object} param0
6
+ * @param {import('..').Target} param0.target
7
+ * @param {import('..').MetadataExtension} param0.extension
8
+ */
9
+ async function run({ target, extension, result, payload, root_payload }) {
10
+ extension.inputs = Object.assign({}, default_inputs, extension.inputs);
11
+ if (target.name === 'teams') {
12
+ extension.inputs = Object.assign({}, default_inputs_teams, extension.inputs);
13
+ await attachForTeams({ target, extension, payload, result });
14
+ } else if (target.name === 'slack') {
15
+ extension.inputs = Object.assign({}, default_inputs_slack, extension.inputs);
16
+ await attachForSlack({ target, extension, payload, result });
17
+ } else if (target.name === 'chat') {
18
+ extension.inputs = Object.assign({}, default_inputs_chat, extension.inputs);
19
+ await attachForChat({ target, extension, payload, result });
20
+ }
21
+ }
22
+
23
+ /**
24
+ * @param {object} param0
25
+ * @param {import('..').MetadataExtension} param0.extension
26
+ */
27
+ async function attachForTeams({ target, extension, payload, result }) {
28
+ const text = extension.inputs.failure_summary
29
+ if (text) {
30
+ addTeamsExtension({ payload, extension, text });
31
+ }
32
+ }
33
+
34
+ async function attachForSlack({ target, extension, payload, result }) {
35
+ const text = extension.inputs.failure_summary
36
+ if (text) {
37
+ addSlackExtension({ payload, extension, text });
38
+ }
39
+ }
40
+
41
+ async function attachForChat({ target, extension, payload, result }) {
42
+ const text = extension.inputs.failure_summary
43
+ if (text) {
44
+ addChatExtension({ payload, extension, text });
45
+ }
46
+ }
47
+
48
+ const default_options = {
49
+ hook: HOOK.AFTER_SUMMARY,
50
+ condition: STATUS.FAIL,
51
+ }
52
+
53
+ const default_inputs = {
54
+ title: 'AI Failure Summary ✨'
55
+ }
56
+
57
+ const default_inputs_teams = {
58
+ separator: true
59
+ }
60
+
61
+ const default_inputs_slack = {
62
+ separator: false
63
+ }
64
+
65
+ const default_inputs_chat = {
66
+ separator: true
67
+ }
68
+
69
+ module.exports = {
70
+ run,
71
+ default_options
72
+ }
@@ -7,8 +7,10 @@ const percy_analysis = require('./percy-analysis');
7
7
  const custom = require('./custom');
8
8
  const metadata = require('./metadata');
9
9
  const ci_info = require('./ci-info');
10
+ const ai_failure_summary = require('./ai-failure-summary');
10
11
  const { EXTENSION } = require('../helpers/constants');
11
12
  const { checkCondition } = require('../helpers/helper');
13
+ const logger = require('../utils/logger');
12
14
 
13
15
  async function run(options) {
14
16
  const { target, result, hook } = options;
@@ -24,9 +26,9 @@ async function run(options) {
24
26
  try {
25
27
  await extension_runner.run(options);
26
28
  } catch (error) {
27
- console.log('Failed to run extension');
28
- console.log(extension);
29
- console.log(error);
29
+ logger.error(`Failed to run extension: ${error.message}`);
30
+ logger.debug(`Extension details`, extension);
31
+ logger.debug(`Error: `, error);
30
32
  }
31
33
  }
32
34
  }
@@ -53,6 +55,8 @@ function getExtensionRunner(extension) {
53
55
  return metadata;
54
56
  case EXTENSION.CI_INFO:
55
57
  return ci_info;
58
+ case EXTENSION.AI_FAILURE_SUMMARY:
59
+ return ai_failure_summary;
56
60
  default:
57
61
  return require(extension.name);
58
62
  }
@@ -1,6 +1,7 @@
1
1
  const { getSuiteHistory, getLastLaunchByName, getLaunchDetails } = require('../helpers/report-portal');
2
2
  const { addChatExtension, addSlackExtension, addTeamsExtension } = require('../helpers/extension.helper');
3
3
  const { HOOK, STATUS } = require('../helpers/constants');
4
+ const logger = require('../utils/logger');
4
5
 
5
6
  async function getLaunchHistory(extension) {
6
7
  const { inputs, outputs } = extension;
@@ -71,8 +72,8 @@ async function run({ extension, target, payload }) {
71
72
  }
72
73
  }
73
74
  } catch (error) {
74
- console.log('Failed to get report portal history');
75
- console.log(error);
75
+ logger.error(`Failed to get report portal history: ${error.message}`);
76
+ logger.debug(`Error: ${error}`);
76
77
  }
77
78
  }
78
79
 
@@ -20,6 +20,7 @@ const TARGET = Object.freeze({
20
20
  });
21
21
 
22
22
  const EXTENSION = Object.freeze({
23
+ AI_FAILURE_SUMMARY: 'ai-failure-summary',
23
24
  HYPERLINKS: 'hyperlinks',
24
25
  MENTIONS: 'mentions',
25
26
  REPORT_PORTAL_ANALYSIS: 'report-portal-analysis',
package/src/index.d.ts CHANGED
@@ -4,7 +4,7 @@ import { Schedule, User } from 'rosters';
4
4
  import { ParseOptions } from 'test-results-parser';
5
5
  import TestResult from 'test-results-parser/src/models/TestResult';
6
6
 
7
- export type ExtensionName = 'report-portal-analysis' | 'hyperlinks' | 'mentions' | 'report-portal-history' | 'quick-chart-test-summary' | 'metadata' | 'ci-info' | 'custom';
7
+ export type ExtensionName = 'report-portal-analysis' | 'hyperlinks' | 'mentions' | 'report-portal-history' | 'quick-chart-test-summary' | 'metadata' | 'ci-info' | 'custom' | 'ai-failure-summary';
8
8
  export type Hook = 'start' | 'end' | 'after-summary';
9
9
  export type TargetName = 'slack' | 'teams' | 'chat' | 'custom' | 'delay';
10
10
  export type PublishReportType = 'test-summary' | 'test-summary-slim' | 'failure-details';
@@ -61,11 +61,15 @@ export interface CIInfoInputs extends ExtensionInputs {
61
61
  data?: Metadata[];
62
62
  }
63
63
 
64
+ export interface AIFailureSummaryInputs extends ExtensionInputs {
65
+ failure_summary: string;
66
+ }
67
+
64
68
  export interface Extension {
65
69
  name: ExtensionName;
66
70
  condition?: Condition;
67
71
  hook?: Hook;
68
- inputs?: ReportPortalAnalysisInputs | ReportPortalHistoryInputs | HyperlinkInputs | MentionInputs | QuickChartTestSummaryInputs | PercyAnalysisInputs | CustomExtensionInputs | MetadataInputs | CIInfoInputs;
72
+ inputs?: ReportPortalAnalysisInputs | ReportPortalHistoryInputs | HyperlinkInputs | MentionInputs | QuickChartTestSummaryInputs | PercyAnalysisInputs | CustomExtensionInputs | MetadataInputs | CIInfoInputs | AIFailureSummaryInputs;
69
73
  }
70
74
 
71
75
  export interface PercyAnalysisInputs extends ExtensionInputs {
@@ -208,8 +212,8 @@ export interface CustomTargetInputs {
208
212
 
209
213
  export interface Target {
210
214
  name: TargetName;
211
- condition: Condition;
212
- inputs: SlackInputs | TeamsInputs | ChatInputs | CustomTargetInputs | InfluxDBTargetInputs;
215
+ condition?: Condition;
216
+ inputs?: SlackInputs | TeamsInputs | ChatInputs | CustomTargetInputs | InfluxDBTargetInputs;
213
217
  extensions?: Extension[];
214
218
  }
215
219
 
@@ -222,6 +226,7 @@ export interface PublishReport {
222
226
  api_key?: string;
223
227
  project?: string;
224
228
  run?: string;
229
+ show_failure_summary?: boolean;
225
230
  targets?: Target[];
226
231
  results?: ParseOptions[] | PerformanceParseOptions[] | CustomResultOptions[];
227
232
  }
@@ -232,7 +237,6 @@ export interface PublishConfig {
232
237
  run?: string;
233
238
  targets?: Target[];
234
239
  results?: ParseOptions[] | PerformanceParseOptions[] | CustomResultOptions[];
235
- reports?: PublishReport[];
236
240
  }
237
241
 
238
242
  export interface PublishOptions {
@@ -0,0 +1,98 @@
1
+ const trm = console;
2
+
3
+ const LEVEL_VERBOSE = 2;
4
+ const LEVEL_TRACE = 3;
5
+ const LEVEL_DEBUG = 4;
6
+ const LEVEL_INFO = 5;
7
+ const LEVEL_WARN = 6;
8
+ const LEVEL_ERROR = 7;
9
+ const LEVEL_SILENT = 8;
10
+
11
+ /**
12
+ * returns log level value
13
+ * @param {string} level - log level
14
+ */
15
+ function getLevelValue(level) {
16
+ const logLevel = level.toUpperCase();
17
+ switch (logLevel) {
18
+ case 'TRACE':
19
+ return LEVEL_TRACE;
20
+ case 'DEBUG':
21
+ return LEVEL_DEBUG;
22
+ case 'INFO':
23
+ return LEVEL_INFO;
24
+ case 'WARN':
25
+ return LEVEL_WARN;
26
+ case 'ERROR':
27
+ return LEVEL_ERROR;
28
+ case 'SILENT':
29
+ return LEVEL_SILENT;
30
+ case 'VERBOSE':
31
+ return LEVEL_VERBOSE;
32
+ default:
33
+ return LEVEL_INFO;
34
+ }
35
+ }
36
+
37
+ class Logger {
38
+
39
+ constructor() {
40
+ this.level = process.env.TESTBEATS_LOG_LEVEL || 'INFO';
41
+ this.levelValue = getLevelValue(this.level);
42
+ if (process.env.TESTBEATS_DISABLE_LOG_COLORS === 'true') {
43
+ options.disableColors = true;
44
+ }
45
+ }
46
+
47
+ /**
48
+ * sets log level
49
+ * @param {('TRACE'|'DEBUG'|'INFO'|'WARN'|'ERROR')} level - log level
50
+ */
51
+ setLevel(level) {
52
+ this.level = level;
53
+ this.levelValue = getLevelValue(this.level);
54
+ }
55
+
56
+ trace(...msg) {
57
+ if (this.levelValue <= LEVEL_TRACE) {
58
+ msg.forEach(m => trm.debug(m));
59
+ }
60
+ }
61
+
62
+ debug(...msg) {
63
+ if (this.levelValue <= LEVEL_DEBUG) {
64
+ msg.forEach(m => trm.debug(m));
65
+ }
66
+ }
67
+
68
+ info(...msg) {
69
+ if (this.levelValue <= LEVEL_INFO) {
70
+ msg.forEach(m => trm.info(m));
71
+ }
72
+ }
73
+
74
+ warn(...msg) {
75
+ if (this.levelValue <= LEVEL_WARN) {
76
+ msg.forEach(m => trm.warn(getMessage(m)));
77
+ }
78
+ }
79
+
80
+ error(...msg) {
81
+ if (this.levelValue <= LEVEL_ERROR) {
82
+ msg.forEach(m => trm.error(getMessage(m)));
83
+ }
84
+ }
85
+
86
+ }
87
+
88
+
89
+ function getMessage(msg) {
90
+ try {
91
+ return typeof msg === 'object' ? JSON.stringify(msg, null, 2) : msg;
92
+ } catch (_) {
93
+ return msg;
94
+ }
95
+ }
96
+
97
+
98
+ module.exports = new Logger();