testbeats 2.6.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.6.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
 
@@ -99,6 +99,7 @@ class Beats {
99
99
  await this.#attachFailureSummary();
100
100
  await this.#attachFailureAnalysis();
101
101
  await this.#attachSmartAnalysis();
102
+ await this.#attachFailureSignatures();
102
103
  await this.#attachErrorClusters();
103
104
  }
104
105
 
@@ -169,6 +170,30 @@ class Beats {
169
170
  }
170
171
  }
171
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
+
172
197
  #getDelay() {
173
198
  if (process.env.TEST_BEATS_DELAY) {
174
199
  return parseInt(process.env.TEST_BEATS_DELAY);
@@ -203,10 +228,11 @@ class Beats {
203
228
  }
204
229
 
205
230
  async #attachErrorClusters() {
206
- if (this.result.status !== 'FAIL') {
231
+ // Legacy feature: only attach if explicitly enabled
232
+ if (this.config.show_error_clusters !== true) {
207
233
  return;
208
234
  }
209
- if (this.config.show_error_clusters === false) {
235
+ if (this.result.status !== 'FAIL') {
210
236
  return;
211
237
  }
212
238
  try {
@@ -215,7 +241,7 @@ class Beats {
215
241
  this.config.extensions.push({
216
242
  name: 'error-clusters',
217
243
  hook: HOOK.AFTER_SUMMARY,
218
- order: 400,
244
+ order: 401,
219
245
  inputs: {
220
246
  data: res.values
221
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;
@@ -309,7 +326,9 @@ export interface PublishReport {
309
326
  show_failure_summary?: boolean;
310
327
  show_failure_analysis?: boolean;
311
328
  show_smart_analysis?: boolean;
329
+ /** @deprecated Use show_failure_signatures instead. Legacy feature - only enabled when explicitly set to true. */
312
330
  show_error_clusters?: boolean;
331
+ show_failure_signatures?: boolean;
313
332
  targets?: ITarget[];
314
333
  extensions?: IExtension[];
315
334
  results?: ParseOptions[] | PerformanceParseOptions[] | CustomResultOptions[];