@testomatio/reporter 2.9.1-beta.1-allure-chunking → 2.9.1-beta.3-allure-steps

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.
@@ -44,6 +44,19 @@ declare class AllureReader {
44
44
  overwrite: boolean;
45
45
  };
46
46
  mapStatus(status: any): any;
47
+ /**
48
+ * Map an Allure step status to the Testomat.io Step status enum
49
+ * (`passed | failed | none | custom`, see testomat-api-definition.yml).
50
+ *
51
+ * Allure marks a step `broken` when it threw an unexpected error — that is a
52
+ * failure for reporting purposes, matching how `mapStatus` treats tests.
53
+ * `skipped` and anything unknown/absent become `none` (the neutral value),
54
+ * since the step enum has no `skipped`.
55
+ *
56
+ * @param {string} status - Allure step status
57
+ * @returns {'passed'|'failed'|'none'} Testomat.io step status
58
+ */
59
+ mapStepStatus(status: string): "passed" | "failed" | "none";
47
60
  extractSuiteTitle(result: any): any;
48
61
  stripNamespace(suiteName: any): any;
49
62
  extractFile(result: any): string;
@@ -52,6 +65,34 @@ declare class AllureReader {
52
65
  extractLinks(result: any): {
53
66
  label: string;
54
67
  }[];
68
+ /**
69
+ * Extract a Testomat.io test id from Allure links so reported tests match
70
+ * existing cases instead of creating duplicates.
71
+ *
72
+ * Allure's `@TmsLink("T1a2b3c4d")` produces a link with `type: "tms"`. Some exporters
73
+ * omit the type but still point the link URL at a Testomat.io test page; both are
74
+ * accepted. The link `name` is used as the id (falling back to the last URL segment).
75
+ *
76
+ * @param {object} result - Parsed Allure result JSON
77
+ * @returns {string|null} Normalized test id, or null when no usable link exists
78
+ */
79
+ extractTestId(result: object): string | null;
80
+ /**
81
+ * Normalize a value into a Testomat.io test id.
82
+ *
83
+ * Testomat.io test ids are exactly **8 word characters**. The value may arrive bare
84
+ * (`1a2b3c4d`), or carrying the `T` / `@T` markers Testomat uses in code and titles
85
+ * (`T1a2b3c4d`, `@T1a2b3c4d`). The markers are removed only when doing so still leaves
86
+ * a valid 8-char id, so a real id that happens to start with `T` is preserved.
87
+ *
88
+ * Anything that does not resolve to a valid 8-char id — a numeric Allure TestOps id
89
+ * like `12345`, a JIRA key, a 6-digit TMS number — is rejected (returns null) so we
90
+ * never send an unmatchable id that would create duplicates.
91
+ *
92
+ * @param {string|number|null|undefined} value
93
+ * @returns {string|null} The bare 8-char id, or null when the value is not a valid id
94
+ */
95
+ normalizeTestId(value: string | number | null | undefined): string | null;
55
96
  convertSteps(steps: any, depth?: number): any;
56
97
  calculateRunTime(item: any): number;
57
98
  convertParameters(parameters: any): {};
@@ -169,6 +169,12 @@ class AllureReader {
169
169
  create: true,
170
170
  overwrite: true,
171
171
  };
172
+ // Use the @TmsLink / Testomat.io link as the test id so reported tests MATCH
173
+ // existing cases instead of creating duplicates on every run.
174
+ const testId = this.extractTestId(result);
175
+ if (testId) {
176
+ test.test_id = testId;
177
+ }
172
178
  // Add description if present
173
179
  if (result.description) {
174
180
  test.description = result.description;
@@ -203,6 +209,28 @@ class AllureReader {
203
209
  };
204
210
  return statusMap[status] || 'failed';
205
211
  }
212
+ /**
213
+ * Map an Allure step status to the Testomat.io Step status enum
214
+ * (`passed | failed | none | custom`, see testomat-api-definition.yml).
215
+ *
216
+ * Allure marks a step `broken` when it threw an unexpected error — that is a
217
+ * failure for reporting purposes, matching how `mapStatus` treats tests.
218
+ * `skipped` and anything unknown/absent become `none` (the neutral value),
219
+ * since the step enum has no `skipped`.
220
+ *
221
+ * @param {string} status - Allure step status
222
+ * @returns {'passed'|'failed'|'none'} Testomat.io step status
223
+ */
224
+ mapStepStatus(status) {
225
+ const statusMap = {
226
+ passed: 'passed',
227
+ failed: 'failed',
228
+ broken: 'failed',
229
+ skipped: 'none',
230
+ pending: 'none',
231
+ };
232
+ return statusMap[status] || 'none';
233
+ }
206
234
  extractSuiteTitle(result) {
207
235
  const labels = result.labels || [];
208
236
  // Only use suite label for suite_title
@@ -296,6 +324,56 @@ class AllureReader {
296
324
  }
297
325
  return links.length > 0 ? links : undefined;
298
326
  }
327
+ /**
328
+ * Extract a Testomat.io test id from Allure links so reported tests match
329
+ * existing cases instead of creating duplicates.
330
+ *
331
+ * Allure's `@TmsLink("T1a2b3c4d")` produces a link with `type: "tms"`. Some exporters
332
+ * omit the type but still point the link URL at a Testomat.io test page; both are
333
+ * accepted. The link `name` is used as the id (falling back to the last URL segment).
334
+ *
335
+ * @param {object} result - Parsed Allure result JSON
336
+ * @returns {string|null} Normalized test id, or null when no usable link exists
337
+ */
338
+ extractTestId(result) {
339
+ const links = result.links || [];
340
+ if (!links.length)
341
+ return null;
342
+ const isTmsLink = l => typeof l?.type === 'string' && l.type.toLowerCase() === 'tms';
343
+ const isTestomatioLink = l => typeof l?.url === 'string' && /testomat\.io\/[^\s]*\/test\//i.test(l.url);
344
+ const link = links.find(isTmsLink) || links.find(isTestomatioLink);
345
+ if (!link)
346
+ return null;
347
+ // Prefer the explicit link name; fall back to the id segment of a Testomat.io URL.
348
+ let id = this.normalizeTestId(link.name);
349
+ if (!id && typeof link.url === 'string') {
350
+ const fromUrl = link.url.match(/\/test\/([\w\d]{8})(?=$|[/?#])/i);
351
+ if (fromUrl)
352
+ id = fromUrl[1];
353
+ }
354
+ return id;
355
+ }
356
+ /**
357
+ * Normalize a value into a Testomat.io test id.
358
+ *
359
+ * Testomat.io test ids are exactly **8 word characters**. The value may arrive bare
360
+ * (`1a2b3c4d`), or carrying the `T` / `@T` markers Testomat uses in code and titles
361
+ * (`T1a2b3c4d`, `@T1a2b3c4d`). The markers are removed only when doing so still leaves
362
+ * a valid 8-char id, so a real id that happens to start with `T` is preserved.
363
+ *
364
+ * Anything that does not resolve to a valid 8-char id — a numeric Allure TestOps id
365
+ * like `12345`, a JIRA key, a 6-digit TMS number — is rejected (returns null) so we
366
+ * never send an unmatchable id that would create duplicates.
367
+ *
368
+ * @param {string|number|null|undefined} value
369
+ * @returns {string|null} The bare 8-char id, or null when the value is not a valid id
370
+ */
371
+ normalizeTestId(value) {
372
+ if (value === null || value === undefined)
373
+ return null;
374
+ const match = value.toString().trim().match(/^@?T?([\w\d]{8})$/);
375
+ return match ? match[1] : null;
376
+ }
299
377
  convertSteps(steps, depth = 0) {
300
378
  if (depth >= 10)
301
379
  return null;
@@ -304,6 +382,7 @@ class AllureReader {
304
382
  const convertedStep = {
305
383
  category: 'user',
306
384
  title: step.name || step.title || 'Unknown step',
385
+ status: this.mapStepStatus(step.status),
307
386
  duration: this.calculateRunTime(step),
308
387
  steps: this.convertSteps(step.steps || [], depth + 1),
309
388
  };
@@ -413,8 +492,10 @@ class AllureReader {
413
492
  t.code = code;
414
493
  debug('Fetched code for test %s', t.title);
415
494
  }
495
+ // Don't override an id already taken from a @TmsLink — the link is the
496
+ // explicit, source-independent match key the client maintains.
416
497
  const testId = (0, utils_js_1.fetchIdFromCode)(contents, { lang: this.getLanguage() });
417
- if (testId) {
498
+ if (testId && !t.test_id) {
418
499
  t.test_id = testId;
419
500
  debug('Fetched test id %s for test %s', testId, t.title);
420
501
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@testomatio/reporter",
3
- "version": "2.9.1-beta.1-allure-chunking",
3
+ "version": "2.9.1-beta.3-allure-steps",
4
4
  "description": "Testomatio Reporter Client",
5
5
  "engines": {
6
6
  "node": ">=18"
@@ -193,6 +193,13 @@ class AllureReader {
193
193
  overwrite: true,
194
194
  };
195
195
 
196
+ // Use the @TmsLink / Testomat.io link as the test id so reported tests MATCH
197
+ // existing cases instead of creating duplicates on every run.
198
+ const testId = this.extractTestId(result);
199
+ if (testId) {
200
+ test.test_id = testId;
201
+ }
202
+
196
203
  // Add description if present
197
204
  if (result.description) {
198
205
  test.description = result.description;
@@ -233,6 +240,29 @@ class AllureReader {
233
240
  return statusMap[status] || 'failed';
234
241
  }
235
242
 
243
+ /**
244
+ * Map an Allure step status to the Testomat.io Step status enum
245
+ * (`passed | failed | none | custom`, see testomat-api-definition.yml).
246
+ *
247
+ * Allure marks a step `broken` when it threw an unexpected error — that is a
248
+ * failure for reporting purposes, matching how `mapStatus` treats tests.
249
+ * `skipped` and anything unknown/absent become `none` (the neutral value),
250
+ * since the step enum has no `skipped`.
251
+ *
252
+ * @param {string} status - Allure step status
253
+ * @returns {'passed'|'failed'|'none'} Testomat.io step status
254
+ */
255
+ mapStepStatus(status) {
256
+ const statusMap = {
257
+ passed: 'passed',
258
+ failed: 'failed',
259
+ broken: 'failed',
260
+ skipped: 'none',
261
+ pending: 'none',
262
+ };
263
+ return statusMap[status] || 'none';
264
+ }
265
+
236
266
  extractSuiteTitle(result) {
237
267
  const labels = result.labels || [];
238
268
 
@@ -346,6 +376,58 @@ class AllureReader {
346
376
  return links.length > 0 ? links : undefined;
347
377
  }
348
378
 
379
+ /**
380
+ * Extract a Testomat.io test id from Allure links so reported tests match
381
+ * existing cases instead of creating duplicates.
382
+ *
383
+ * Allure's `@TmsLink("T1a2b3c4d")` produces a link with `type: "tms"`. Some exporters
384
+ * omit the type but still point the link URL at a Testomat.io test page; both are
385
+ * accepted. The link `name` is used as the id (falling back to the last URL segment).
386
+ *
387
+ * @param {object} result - Parsed Allure result JSON
388
+ * @returns {string|null} Normalized test id, or null when no usable link exists
389
+ */
390
+ extractTestId(result) {
391
+ const links = result.links || [];
392
+ if (!links.length) return null;
393
+
394
+ const isTmsLink = l => typeof l?.type === 'string' && l.type.toLowerCase() === 'tms';
395
+ const isTestomatioLink = l => typeof l?.url === 'string' && /testomat\.io\/[^\s]*\/test\//i.test(l.url);
396
+
397
+ const link = links.find(isTmsLink) || links.find(isTestomatioLink);
398
+ if (!link) return null;
399
+
400
+ // Prefer the explicit link name; fall back to the id segment of a Testomat.io URL.
401
+ let id = this.normalizeTestId(link.name);
402
+ if (!id && typeof link.url === 'string') {
403
+ const fromUrl = link.url.match(/\/test\/([\w\d]{8})(?=$|[/?#])/i);
404
+ if (fromUrl) id = fromUrl[1];
405
+ }
406
+
407
+ return id;
408
+ }
409
+
410
+ /**
411
+ * Normalize a value into a Testomat.io test id.
412
+ *
413
+ * Testomat.io test ids are exactly **8 word characters**. The value may arrive bare
414
+ * (`1a2b3c4d`), or carrying the `T` / `@T` markers Testomat uses in code and titles
415
+ * (`T1a2b3c4d`, `@T1a2b3c4d`). The markers are removed only when doing so still leaves
416
+ * a valid 8-char id, so a real id that happens to start with `T` is preserved.
417
+ *
418
+ * Anything that does not resolve to a valid 8-char id — a numeric Allure TestOps id
419
+ * like `12345`, a JIRA key, a 6-digit TMS number — is rejected (returns null) so we
420
+ * never send an unmatchable id that would create duplicates.
421
+ *
422
+ * @param {string|number|null|undefined} value
423
+ * @returns {string|null} The bare 8-char id, or null when the value is not a valid id
424
+ */
425
+ normalizeTestId(value) {
426
+ if (value === null || value === undefined) return null;
427
+ const match = value.toString().trim().match(/^@?T?([\w\d]{8})$/);
428
+ return match ? match[1] : null;
429
+ }
430
+
349
431
  convertSteps(steps, depth = 0) {
350
432
  if (depth >= 10) return null;
351
433
 
@@ -354,6 +436,7 @@ class AllureReader {
354
436
  const convertedStep = {
355
437
  category: 'user',
356
438
  title: step.name || step.title || 'Unknown step',
439
+ status: this.mapStepStatus(step.status),
357
440
  duration: this.calculateRunTime(step),
358
441
  steps: this.convertSteps(step.steps || [], depth + 1),
359
442
  };
@@ -482,8 +565,10 @@ class AllureReader {
482
565
  debug('Fetched code for test %s', t.title);
483
566
  }
484
567
 
568
+ // Don't override an id already taken from a @TmsLink — the link is the
569
+ // explicit, source-independent match key the client maintains.
485
570
  const testId = fetchIdFromCode(contents, { lang: this.getLanguage() });
486
- if (testId) {
571
+ if (testId && !t.test_id) {
487
572
  t.test_id = testId;
488
573
  debug('Fetched test id %s for test %s', testId, t.title);
489
574
  }