testbeats 2.5.0 → 2.7.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testbeats",
3
- "version": "2.5.0",
3
+ "version": "2.7.0",
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",
@@ -71,6 +71,20 @@ class BeatsApi {
71
71
  });
72
72
  }
73
73
 
74
+
75
+ /**
76
+ * @param {string} run_id
77
+ * @returns {import('./beats.types').IFailureSignature[]}
78
+ */
79
+ getFailureSignatures(run_id) {
80
+ return request.get({
81
+ url: `${this.getBaseUrl()}/api/core/v1/automation/test-run-executions/${run_id}/failure-signatures`,
82
+ headers: {
83
+ 'x-api-key': this.config.api_key
84
+ }
85
+ });
86
+ }
87
+
74
88
  /**
75
89
  *
76
90
  * @param {string} run_id
@@ -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, PROCESS_STATUS } = require('../helpers/constants');
4
+ const { HOOK, PROCESS_STATUS, EXTENSION } = require('../helpers/constants');
5
5
  const TestResult = require('test-results-parser/src/models/TestResult');
6
6
  const { BeatsAttachments } = require('./beats.attachments');
7
7
 
@@ -55,6 +55,9 @@ class Beats {
55
55
  if (this.ci) {
56
56
  payload.ci_details = [this.ci];
57
57
  }
58
+ if (this.config.metadata) {
59
+ payload.metadata = Object.assign(this.result.metadata || {}, this.config.metadata);
60
+ }
58
61
  return payload;
59
62
  }
60
63
 
@@ -96,6 +99,7 @@ class Beats {
96
99
  await this.#attachFailureSummary();
97
100
  await this.#attachFailureAnalysis();
98
101
  await this.#attachSmartAnalysis();
102
+ await this.#attachFailureSignatures();
99
103
  await this.#attachErrorClusters();
100
104
  }
101
105
 
@@ -166,6 +170,30 @@ class Beats {
166
170
  }
167
171
  }
168
172
 
173
+ async #attachFailureSignatures() {
174
+ if (this.result.status !== 'FAIL') {
175
+ return;
176
+ }
177
+ if (this.config.show_failure_signatures === false) {
178
+ return;
179
+ }
180
+ try {
181
+ logger.info('🔍 Fetching Failure Signatures...');
182
+ const signatures = await this.api.getFailureSignatures(this.test_run_id);
183
+ this.config.extensions.push({
184
+ name: EXTENSION.FAILURE_SIGNATURES,
185
+ hook: HOOK.AFTER_SUMMARY,
186
+ order: 400,
187
+ inputs: {
188
+ data: signatures
189
+ }
190
+ });
191
+ }
192
+ catch (error) {
193
+ logger.error(`❌ Unable to attach failure signatures: ${error.message}`, error);
194
+ }
195
+ }
196
+
169
197
  #getDelay() {
170
198
  if (process.env.TEST_BEATS_DELAY) {
171
199
  return parseInt(process.env.TEST_BEATS_DELAY);
@@ -200,10 +228,11 @@ class Beats {
200
228
  }
201
229
 
202
230
  async #attachErrorClusters() {
203
- if (this.result.status !== 'FAIL') {
231
+ // Legacy feature: only attach if explicitly enabled
232
+ if (this.config.show_error_clusters !== true) {
204
233
  return;
205
234
  }
206
- if (this.config.show_error_clusters === false) {
235
+ if (this.result.status !== 'FAIL') {
207
236
  return;
208
237
  }
209
238
  try {
@@ -212,7 +241,7 @@ class Beats {
212
241
  this.config.extensions.push({
213
242
  name: 'error-clusters',
214
243
  hook: HOOK.AFTER_SUMMARY,
215
- order: 400,
244
+ order: 401,
216
245
  inputs: {
217
246
  data: res.values
218
247
  }
@@ -38,3 +38,11 @@ export type IFailureAnalysisMetric = {
38
38
  name: string
39
39
  count: number
40
40
  }
41
+
42
+
43
+ export type IFailureSignature = {
44
+ id: string
45
+ signature: string
46
+ failure_type: string
47
+ count: number
48
+ }
@@ -432,7 +432,8 @@ class GenerateConfigCommand {
432
432
  { title: 'Metadata', value: 'metadata' },
433
433
  { title: 'AI Failure Summary', value: 'ai-failure-summary' },
434
434
  { title: 'Smart Analysis', value: 'smart-analysis' },
435
- { title: 'Error Clusters', value: 'error-clusters' }
435
+ { title: 'Failure Signatures', value: 'failure-signatures' },
436
+ { title: 'Error Clusters (Legacy)', value: 'error-clusters' }
436
437
  ];
437
438
  }
438
439
  }
@@ -0,0 +1,43 @@
1
+ const { BaseExtension } = require('./base.extension');
2
+ const { STATUS, HOOK } = require("../helpers/constants");
3
+ const { truncate } = require('../helpers/helper');
4
+
5
+ class FailureSignaturesExtension extends BaseExtension {
6
+
7
+ constructor(target, extension, result, payload, root_payload) {
8
+ super(target, extension, result, payload, root_payload);
9
+ this.#setDefaultOptions();
10
+ this.#setDefaultInputs();
11
+ this.updateExtensionInputs();
12
+ }
13
+
14
+ run() {
15
+ this.#setText();
16
+ this.attach();
17
+ }
18
+
19
+ #setDefaultOptions() {
20
+ this.default_options.hook = HOOK.AFTER_SUMMARY,
21
+ this.default_options.condition = STATUS.PASS_OR_FAIL;
22
+ }
23
+
24
+ #setDefaultInputs() {
25
+ this.default_inputs.title = 'Top Failures';
26
+ this.default_inputs.title_link = '';
27
+ }
28
+
29
+ #setText() {
30
+ const signatures = this.extension.inputs.data;
31
+ if (!signatures || !signatures.length) {
32
+ return;
33
+ }
34
+
35
+ const texts = [];
36
+ for (const signature of signatures) {
37
+ texts.push(`${truncate(signature.signature, 150)} - ${this.bold(`(x${signature.count})`)}`);
38
+ }
39
+ this.text = this.platform.bullets(texts);
40
+ }
41
+ }
42
+
43
+ module.exports = { FailureSignaturesExtension }
@@ -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 { FailureSignaturesExtension } = require('./failure-signatures.extension');
16
17
  const { FailureAnalysisExtension } = require('./failure-analysis.extension');
17
18
  const { BrowserstackExtension } = require('./browserstack.extension');
18
19
 
@@ -75,6 +76,8 @@ function getExtensionRunner(extension, options) {
75
76
  return new ErrorClustersExtension(options.target, extension, options.result, options.payload, options.root_payload);
76
77
  case EXTENSION.BROWSERSTACK:
77
78
  return new BrowserstackExtension(options.target, extension, options.result, options.payload, options.root_payload);
79
+ case EXTENSION.FAILURE_SIGNATURES:
80
+ return new FailureSignaturesExtension(options.target, extension, options.result, options.payload, options.root_payload);
78
81
  default:
79
82
  return require(extension.name);
80
83
  }
@@ -27,6 +27,7 @@ const EXTENSION = Object.freeze({
27
27
  FAILURE_ANALYSIS: 'failure-analysis',
28
28
  SMART_ANALYSIS: 'smart-analysis',
29
29
  ERROR_CLUSTERS: 'error-clusters',
30
+ FAILURE_SIGNATURES: 'failure-signatures',
30
31
  BROWSERSTACK: 'browserstack',
31
32
  HYPERLINKS: 'hyperlinks',
32
33
  MENTIONS: 'mentions',
package/src/index.d.ts CHANGED
@@ -20,10 +20,10 @@ export interface IExtension {
20
20
  condition?: Condition;
21
21
  hook?: Hook;
22
22
  order?: number;
23
- inputs?: ReportPortalAnalysisInputs | ReportPortalHistoryInputs | HyperlinkInputs | MentionInputs | QuickChartTestSummaryInputs | PercyAnalysisInputs | CustomExtensionInputs | MetadataInputs | CIInfoInputs | AIFailureSummaryInputs | BrowserstackInputs;
23
+ inputs?: ReportPortalAnalysisInputs | ReportPortalHistoryInputs | HyperlinkInputs | MentionInputs | QuickChartTestSummaryInputs | PercyAnalysisInputs | CustomExtensionInputs | MetadataInputs | CIInfoInputs | AIFailureSummaryInputs | BrowserstackInputs | FailureSignaturesInputs | ErrorClustersInputs;
24
24
  }
25
25
 
26
- export type ExtensionName = 'report-portal-analysis' | 'hyperlinks' | 'mentions' | 'report-portal-history' | 'quick-chart-test-summary' | 'metadata' | 'ci-info' | 'custom' | 'ai-failure-summary';
26
+ export type ExtensionName = 'report-portal-analysis' | 'hyperlinks' | 'mentions' | 'report-portal-history' | 'quick-chart-test-summary' | 'metadata' | 'ci-info' | 'custom' | 'ai-failure-summary' | 'failure-signatures' | 'error-clusters';
27
27
  export type Hook = 'start' | 'end' | 'after-summary';
28
28
  export type TargetName = 'slack' | 'teams' | 'chat' | 'github' | 'github-output' | 'custom' | 'delay';
29
29
  export type PublishReportType = 'test-summary' | 'test-summary-slim' | 'failure-details';
@@ -86,6 +86,23 @@ export interface AIFailureSummaryInputs extends ExtensionInputs {
86
86
  failure_summary: string;
87
87
  }
88
88
 
89
+ export interface FailureSignaturesInputs extends ExtensionInputs {
90
+ data?: Array<{
91
+ id: string;
92
+ signature: string;
93
+ failure_type: string;
94
+ count: number;
95
+ }>;
96
+ }
97
+
98
+ export interface ErrorClustersInputs extends ExtensionInputs {
99
+ data?: Array<{
100
+ test_failure_id: string;
101
+ failure: string;
102
+ count: number;
103
+ }>;
104
+ }
105
+
89
106
  export interface BrowserStackAutomationBuild {
90
107
  name: string;
91
108
  hashed_id: string;
@@ -305,10 +322,13 @@ export interface PublishReport {
305
322
  api_key?: string;
306
323
  project?: string;
307
324
  run?: string;
325
+ metadata?: Record<string, string>;
308
326
  show_failure_summary?: boolean;
309
327
  show_failure_analysis?: boolean;
310
328
  show_smart_analysis?: boolean;
329
+ /** @deprecated Use show_failure_signatures instead. Legacy feature - only enabled when explicitly set to true. */
311
330
  show_error_clusters?: boolean;
331
+ show_failure_signatures?: boolean;
312
332
  targets?: ITarget[];
313
333
  extensions?: IExtension[];
314
334
  results?: ParseOptions[] | PerformanceParseOptions[] | CustomResultOptions[];
@@ -318,6 +338,7 @@ export interface PublishConfig {
318
338
  api_key?: string;
319
339
  project?: string;
320
340
  run?: string;
341
+ metadata?: Record<string, string>;
321
342
  targets?: ITarget[];
322
343
  extensions?: IExtension[];
323
344
  results?: ParseOptions[] | PerformanceParseOptions[] | CustomResultOptions[];