@testomatio/reporter 2.3.9-beta-bin-fix → 2.3.9

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.
Files changed (45) hide show
  1. package/README.md +2 -1
  2. package/lib/adapter/codecept.js +12 -9
  3. package/lib/bin/cli.js +14 -4
  4. package/lib/bin/reportXml.js +5 -2
  5. package/lib/client.d.ts +1 -11
  6. package/lib/client.js +39 -142
  7. package/lib/junit-adapter/csharp.d.ts +0 -1
  8. package/lib/junit-adapter/csharp.js +43 -7
  9. package/lib/junit-adapter/nunit-parser.d.ts +82 -0
  10. package/lib/junit-adapter/nunit-parser.js +433 -0
  11. package/lib/pipe/bitbucket.js +5 -5
  12. package/lib/pipe/gitlab.js +4 -4
  13. package/lib/pipe/testomatio.d.ts +2 -1
  14. package/lib/pipe/testomatio.js +19 -14
  15. package/lib/reporter-functions.js +1 -3
  16. package/lib/reporter.d.ts +19 -9
  17. package/lib/reporter.js +40 -5
  18. package/lib/uploader.js +4 -0
  19. package/lib/utils/log-formatter.d.ts +28 -0
  20. package/lib/utils/log-formatter.js +127 -0
  21. package/lib/utils/utils.js +189 -24
  22. package/lib/xmlReader.d.ts +32 -26
  23. package/lib/xmlReader.js +121 -52
  24. package/package.json +8 -4
  25. package/src/adapter/codecept.js +19 -19
  26. package/src/adapter/mocha.js +1 -1
  27. package/src/adapter/playwright.js +2 -2
  28. package/src/bin/cli.js +16 -4
  29. package/src/bin/reportXml.js +5 -2
  30. package/src/client.js +47 -116
  31. package/src/junit-adapter/csharp.js +48 -6
  32. package/src/junit-adapter/nunit-parser.js +474 -0
  33. package/src/pipe/bitbucket.js +5 -5
  34. package/src/pipe/debug.js +1 -2
  35. package/src/pipe/gitlab.js +4 -4
  36. package/src/pipe/testomatio.js +75 -80
  37. package/src/reporter-functions.js +2 -3
  38. package/src/reporter.js +6 -4
  39. package/src/services/links.js +1 -1
  40. package/src/uploader.js +5 -0
  41. package/src/utils/log-formatter.js +113 -0
  42. package/src/utils/utils.js +202 -22
  43. package/src/xmlReader.js +144 -46
  44. package/types/types.d.ts +364 -0
  45. package/types/vitest.types.d.ts +93 -0
@@ -0,0 +1,433 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.NUnitXmlParser = void 0;
7
+ const debug_1 = __importDefault(require("debug"));
8
+ const constants_js_1 = require("../constants.js");
9
+ const utils_js_1 = require("../utils/utils.js");
10
+ const debug = (0, debug_1.default)('@testomatio/reporter:nunit-parser');
11
+ /**
12
+ * Enhanced NUnit XML Parser that properly handles test-suite hierarchy
13
+ * and parameterized tests
14
+ */
15
+ class NUnitXmlParser {
16
+ constructor(options = {}) {
17
+ this.options = options;
18
+ this.tests = [];
19
+ this.stats = {
20
+ total: 0,
21
+ passed: 0,
22
+ failed: 0,
23
+ skipped: 0,
24
+ inconclusive: 0,
25
+ };
26
+ }
27
+ /**
28
+ * Parse NUnit XML test-run structure
29
+ * @param {Object} testRun - Parsed XML test-run object
30
+ * @returns {Object} - Parsed test results
31
+ */
32
+ parseTestRun(testRun) {
33
+ debug('Parsing NUnit test-run');
34
+ // Extract run-level statistics
35
+ this.stats = {
36
+ total: parseInt(testRun.total || 0, 10),
37
+ passed: parseInt(testRun.passed || 0, 10),
38
+ failed: parseInt(testRun.failed || 0, 10),
39
+ skipped: parseInt(testRun.skipped || 0, 10),
40
+ inconclusive: parseInt(testRun.inconclusive || 0, 10),
41
+ };
42
+ // Process the root test-suite
43
+ if (testRun['test-suite']) {
44
+ this.parseTestSuite(testRun['test-suite'], []);
45
+ }
46
+ debug(`Parsed ${this.tests.length} tests from NUnit XML`);
47
+ return {
48
+ status: testRun.result?.toLowerCase() || 'unknown',
49
+ create_tests: true,
50
+ tests_count: this.tests.length,
51
+ passed_count: this.tests.filter(t => t.status === constants_js_1.STATUS.PASSED).length,
52
+ failed_count: this.tests.filter(t => t.status === constants_js_1.STATUS.FAILED).length,
53
+ skipped_count: this.tests.filter(t => t.status === constants_js_1.STATUS.SKIPPED).length,
54
+ tests: this.tests,
55
+ };
56
+ }
57
+ /**
58
+ * Recursively parse test-suite elements based on their type
59
+ * @param {Object|Array} testSuite - Test suite object or array
60
+ * @param {Array} parentPath - Current path in the hierarchy
61
+ */
62
+ parseTestSuite(testSuite, parentPath = []) {
63
+ if (!testSuite)
64
+ return;
65
+ // Handle arrays of test suites
66
+ if (Array.isArray(testSuite)) {
67
+ testSuite.forEach(suite => this.parseTestSuite(suite, parentPath));
68
+ return;
69
+ }
70
+ const suiteType = testSuite.type;
71
+ const suiteName = testSuite.name;
72
+ const fullName = testSuite.fullname;
73
+ debug(`Processing test-suite: type=${suiteType}, name=${suiteName}`);
74
+ switch (suiteType) {
75
+ case 'Assembly':
76
+ // Assembly level - ignore the name, just process children
77
+ debug('Processing Assembly level - ignoring name, processing children');
78
+ this.processChildren(testSuite, parentPath);
79
+ break;
80
+ case 'TestSuite':
81
+ // Namespace/grouping level - add to path but don't create test
82
+ debug(`Processing TestSuite level - adding '${suiteName}' to path`);
83
+ // Avoid adding duplicate suite names to the path
84
+ const newPath = parentPath[parentPath.length - 1] === suiteName ? [...parentPath] : [...parentPath, suiteName];
85
+ this.processChildren(testSuite, newPath);
86
+ break;
87
+ case 'TestFixture':
88
+ // Test class level - add to path and process test cases
89
+ debug(`Processing TestFixture level - test class '${suiteName}'`);
90
+ const testFixturePath = [...parentPath, suiteName];
91
+ this.processChildren(testSuite, testFixturePath);
92
+ break;
93
+ case 'ParameterizedMethod':
94
+ // Parameterized method level - process test cases directly
95
+ debug(`Processing ParameterizedMethod level - method '${suiteName}'`);
96
+ // Don't add to path, just process children directly
97
+ this.processChildren(testSuite, parentPath);
98
+ break;
99
+ default:
100
+ debug(`Unknown test-suite type: ${suiteType}, treating as TestSuite`);
101
+ const unknownPath = [...parentPath, suiteName];
102
+ this.processChildren(testSuite, unknownPath);
103
+ break;
104
+ }
105
+ }
106
+ /**
107
+ * Process child elements of a test suite
108
+ * @param {Object} testSuite - Test suite object
109
+ * @param {Array} currentPath - Current path in hierarchy
110
+ */
111
+ processChildren(testSuite, currentPath) {
112
+ // Process test-cases first (to maintain order)
113
+ if (testSuite['test-case']) {
114
+ this.parseTestCases(testSuite['test-case'], currentPath, testSuite);
115
+ }
116
+ // Process nested test-suites
117
+ if (testSuite['test-suite']) {
118
+ this.parseTestSuite(testSuite['test-suite'], currentPath);
119
+ }
120
+ }
121
+ /**
122
+ * Parse test-case elements (actual tests)
123
+ * @param {Object|Array} testCases - Test case object or array
124
+ * @param {Array} suitePath - Path to the test suite
125
+ * @param {Object} parentSuite - Parent test suite for context
126
+ */
127
+ parseTestCases(testCases, suitePath, parentSuite) {
128
+ if (!testCases)
129
+ return;
130
+ // Handle arrays of test cases
131
+ if (!Array.isArray(testCases)) {
132
+ testCases = [testCases];
133
+ }
134
+ testCases.forEach(testCase => {
135
+ const parsedTest = this.parseTestCase(testCase, suitePath, parentSuite);
136
+ if (parsedTest) {
137
+ this.tests.push(parsedTest);
138
+ }
139
+ });
140
+ }
141
+ /**
142
+ * Parse individual test case
143
+ * @param {Object} testCase - Test case object
144
+ * @param {Array} suitePath - Path to the test suite
145
+ * @param {Object} parentSuite - Parent test suite for context
146
+ * @returns {Object|null} - Parsed test object
147
+ */
148
+ parseTestCase(testCase, suitePath, parentSuite) {
149
+ if (!testCase || !testCase.name) {
150
+ debug('Skipping test case without name');
151
+ return null;
152
+ }
153
+ // Use Description from properties if available (for SpecFlow tests), otherwise use name
154
+ let testName = testCase.name;
155
+ if (testCase.properties && testCase.properties.property) {
156
+ const properties = Array.isArray(testCase.properties.property)
157
+ ? testCase.properties.property
158
+ : [testCase.properties.property];
159
+ const descriptionProperty = properties.find(p => p.name === 'Description');
160
+ if (descriptionProperty && descriptionProperty.value) {
161
+ // Clean up SpecFlow description format: [C211256] Allow mobile print behavior -> Allow mobile print behavior
162
+ testName = descriptionProperty.value.replace(/^\[[^\]]+\]\s*/, '');
163
+ }
164
+ }
165
+ const fullName = testCase.fullname;
166
+ const methodName = testCase.methodname || this.extractMethodName(testName);
167
+ const className = testCase.classname || parentSuite?.name;
168
+ debug(`Parsing test case: ${testName}`);
169
+ debug(`Test case structure:`, JSON.stringify(testCase, null, 2));
170
+ // Extract parameters if this is a parameterized test
171
+ const { baseMethodName, parameters, isParameterized } = this.extractParameters(testName);
172
+ // Determine test status
173
+ let status = constants_js_1.STATUS.PASSED;
174
+ if (testCase.result) {
175
+ switch (testCase.result.toLowerCase()) {
176
+ case 'passed':
177
+ status = constants_js_1.STATUS.PASSED;
178
+ break;
179
+ case 'failed':
180
+ status = constants_js_1.STATUS.FAILED;
181
+ break;
182
+ case 'skipped':
183
+ case 'ignored':
184
+ status = constants_js_1.STATUS.SKIPPED;
185
+ break;
186
+ case 'inconclusive':
187
+ status = constants_js_1.STATUS.SKIPPED; // Treat inconclusive as skipped
188
+ break;
189
+ default:
190
+ status = constants_js_1.STATUS.PASSED;
191
+ }
192
+ }
193
+ // Extract error information
194
+ let message = '';
195
+ let stack = '';
196
+ const files = [];
197
+ // Extract attachments (NUnit format)
198
+ if (testCase.attachments) {
199
+ const attachments = Array.isArray(testCase.attachments.attachment)
200
+ ? testCase.attachments.attachment
201
+ : [testCase.attachments.attachment];
202
+ const attachmentFiles = attachments.filter(a => a && a.filePath).map(a => a.filePath);
203
+ files.push(...attachmentFiles);
204
+ }
205
+ if (testCase.failure) {
206
+ message = testCase.failure.message || '';
207
+ stack = testCase.failure['stack-trace'] || testCase.failure['#text'] || '';
208
+ }
209
+ if (testCase.output) {
210
+ const outputText = typeof testCase.output === 'string' ? testCase.output : testCase.output['#text'];
211
+ const stackFiles = (0, utils_js_1.fetchFilesFromStackTrace)(outputText);
212
+ files.push(...stackFiles);
213
+ if (outputText) {
214
+ debug(`Found output in test case: ${outputText.substring(0, 100)}...`);
215
+ stack = `${stack}\n\n${outputText}`.trim();
216
+ }
217
+ else {
218
+ debug('No output text found in test case');
219
+ }
220
+ }
221
+ else {
222
+ debug('No output found in test case');
223
+ }
224
+ // Extract test ID and tags from properties
225
+ let testId = null;
226
+ let tags = [];
227
+ if (testCase.properties && testCase.properties.property) {
228
+ const properties = Array.isArray(testCase.properties.property)
229
+ ? testCase.properties.property
230
+ : [testCase.properties.property];
231
+ const idProperty = properties.find(p => p.name === 'ID');
232
+ if (idProperty) {
233
+ testId = idProperty.value;
234
+ // Remove @ and T prefixes if present
235
+ if (testId.startsWith('@'))
236
+ testId = testId.slice(1);
237
+ if (testId.startsWith('T'))
238
+ testId = testId.slice(1);
239
+ }
240
+ // Extract Category properties as tags
241
+ const categoryProperties = properties.filter(p => p.name === 'Category');
242
+ tags = categoryProperties.map(p => p.value);
243
+ }
244
+ // If no test ID found in properties, try to extract from output
245
+ if (!testId && testCase.output) {
246
+ const outputText = typeof testCase.output === 'string' ? testCase.output : testCase.output['#text'];
247
+ if (outputText) {
248
+ debug(`Looking for test ID in output: ${outputText.substring(0, 200)}...`);
249
+ const idMatch = outputText.match(/\[ID\]\s+tid:\/\/@T([a-f0-9]{8})/i);
250
+ if (idMatch) {
251
+ testId = idMatch[1];
252
+ debug(`Found test ID in output: ${testId}`);
253
+ }
254
+ else {
255
+ debug('No test ID found in output');
256
+ }
257
+ }
258
+ }
259
+ // Build file path from suite path and class name
260
+ const filePath = this.buildFilePath(suitePath, className, parentSuite);
261
+ // For parameterized tests, format example as expected by Testomatio API
262
+ // Convert array of parameters to object with numeric keys
263
+ let example = null;
264
+ if (isParameterized && parameters.length > 0) {
265
+ example = {};
266
+ parameters.forEach((param, index) => {
267
+ example[index] = param;
268
+ });
269
+ }
270
+ return {
271
+ // For runs: use full test name with parameters (TestBooleanValue(true))
272
+ // For import: API will group by base name using the example field
273
+ title: testName, // Full name with parameters for run display
274
+ methodName: baseMethodName || methodName || testName,
275
+ fullName: fullName,
276
+ suitePath: suitePath,
277
+ suite_title: className || suitePath[suitePath.length - 1] || 'Unknown',
278
+ file: filePath,
279
+ files: files, // Array of files that will be attached
280
+ status: status,
281
+ message: message,
282
+ stack: stack,
283
+ run_time: parseFloat(testCase.duration || testCase.time || 0) * 1000,
284
+ test_id: testId,
285
+ tags: tags, // Array of category tags from properties
286
+ create: true,
287
+ retry: false,
288
+ // Parameterized test metadata
289
+ example: example, // Parameters as object for API grouping
290
+ isParameterized: isParameterized,
291
+ parameters: parameters, // Keep original array for reference
292
+ baseMethodName: baseMethodName,
293
+ };
294
+ }
295
+ /**
296
+ * Extract method name and parameters from test name
297
+ * @param {string} testName - Full test name
298
+ * @returns {Object} - Extracted information
299
+ */
300
+ extractParameters(testName) {
301
+ const paramMatch = testName.match(/^(.+?)\((.+)\)$/);
302
+ if (paramMatch) {
303
+ const baseMethodName = paramMatch[1].trim();
304
+ const paramString = paramMatch[2];
305
+ // Parse parameters - handle quoted strings and nested structures
306
+ const parameters = this.parseParameterString(paramString);
307
+ return {
308
+ baseMethodName: baseMethodName,
309
+ parameters: parameters,
310
+ isParameterized: true,
311
+ };
312
+ }
313
+ return {
314
+ baseMethodName: testName,
315
+ parameters: [],
316
+ isParameterized: false,
317
+ };
318
+ }
319
+ /**
320
+ * Parse parameter string into array of parameters
321
+ * @param {string} paramString - Parameter string
322
+ * @returns {Array} - Array of parameters
323
+ */
324
+ parseParameterString(paramString) {
325
+ const parameters = [];
326
+ let current = '';
327
+ let inQuotes = false;
328
+ let quoteChar = null;
329
+ let depth = 0;
330
+ for (let i = 0; i < paramString.length; i++) {
331
+ const char = paramString[i];
332
+ if (!inQuotes && (char === '"' || char === "'")) {
333
+ inQuotes = true;
334
+ quoteChar = char;
335
+ current += char;
336
+ }
337
+ else if (inQuotes && char === quoteChar) {
338
+ inQuotes = false;
339
+ quoteChar = null;
340
+ current += char;
341
+ }
342
+ else if (!inQuotes && char === '(') {
343
+ depth++;
344
+ current += char;
345
+ }
346
+ else if (!inQuotes && char === ')') {
347
+ depth--;
348
+ current += char;
349
+ }
350
+ else if (!inQuotes && char === ',' && depth === 0) {
351
+ parameters.push(current.trim());
352
+ current = '';
353
+ }
354
+ else {
355
+ current += char;
356
+ }
357
+ }
358
+ if (current.trim()) {
359
+ parameters.push(current.trim());
360
+ }
361
+ // Clean up parameters - remove quotes if they wrap the entire parameter and filter empty ones
362
+ return parameters
363
+ .map(param => {
364
+ param = param.trim();
365
+ if ((param.startsWith('"') && param.endsWith('"')) || (param.startsWith("'") && param.endsWith("'"))) {
366
+ return param.slice(1, -1);
367
+ }
368
+ return param;
369
+ })
370
+ .filter(p => !!p);
371
+ }
372
+ /**
373
+ * Extract method name from test name (fallback)
374
+ * @param {string} testName - Test name
375
+ * @returns {string} - Method name
376
+ */
377
+ extractMethodName(testName) {
378
+ // Remove parameters if present
379
+ const paramMatch = testName.match(/^(.+?)\(/);
380
+ return paramMatch ? paramMatch[1].trim() : testName;
381
+ }
382
+ /**
383
+ * Build file path from suite path and class name
384
+ * @param {Array} suitePath - Suite path array
385
+ * @param {string} className - Class name
386
+ * @param {Object} parentSuite - Parent suite for context
387
+ * @returns {string} - File path
388
+ */
389
+ buildFilePath(suitePath, className, parentSuite) {
390
+ // Try to get file path from parent suite
391
+ if (parentSuite && parentSuite.filepath) {
392
+ return parentSuite.filepath;
393
+ }
394
+ // Build path from suite hierarchy
395
+ const pathParts = [...suitePath];
396
+ if (className && !pathParts.includes(className)) {
397
+ pathParts.push(className);
398
+ }
399
+ // Convert to file path format
400
+ return pathParts.join('/') + '.cs'; // Assume C# for NUnit
401
+ }
402
+ /**
403
+ * Group parameterized tests by base method name
404
+ * @param {Array} tests - Array of parsed tests
405
+ * @returns {Object} - Grouped tests
406
+ */
407
+ groupParameterizedTests(tests) {
408
+ const grouped = {};
409
+ tests.forEach(test => {
410
+ const key = test.isParameterized
411
+ ? `${test.suitePath.join('.')}.${test.baseMethodName}`
412
+ : `${test.suitePath.join('.')}.${test.title}`;
413
+ if (!grouped[key]) {
414
+ grouped[key] = {
415
+ baseTest: {
416
+ name: test.baseMethodName || test.title,
417
+ suitePath: test.suitePath,
418
+ suite_title: test.suite_title,
419
+ file: test.file,
420
+ isParameterized: test.isParameterized,
421
+ },
422
+ variations: [],
423
+ };
424
+ }
425
+ grouped[key].variations.push(test);
426
+ });
427
+ return grouped;
428
+ }
429
+ }
430
+ exports.NUnitXmlParser = NUnitXmlParser;
431
+ module.exports = NUnitXmlParser;
432
+
433
+ module.exports.NUnitXmlParser = NUnitXmlParser;
@@ -73,8 +73,8 @@ class BitbucketPipe {
73
73
  baseURL: 'https://api.bitbucket.org/2.0',
74
74
  headers: {
75
75
  'Content-Type': 'application/json',
76
- 'Authorization': `Bearer ${this.token}`
77
- }
76
+ Authorization: `Bearer ${this.token}`,
77
+ },
78
78
  });
79
79
  debug('Bitbucket Pipe: Enabled');
80
80
  }
@@ -185,7 +185,7 @@ class BitbucketPipe {
185
185
  const addCommentResponse = await this.client.request({
186
186
  method: 'POST',
187
187
  url: commentsRequestURL,
188
- data: { content: { raw: body } }
188
+ data: { content: { raw: body } },
189
189
  });
190
190
  const commentID = addCommentResponse.data.id;
191
191
  // eslint-disable-next-line max-len
@@ -212,7 +212,7 @@ async function deletePreviousReport(client, commentsRequestURL, hiddenCommentDat
212
212
  try {
213
213
  const response = await client.request({
214
214
  method: 'GET',
215
- url: commentsRequestURL
215
+ url: commentsRequestURL,
216
216
  });
217
217
  comments = response.data.values;
218
218
  }
@@ -229,7 +229,7 @@ async function deletePreviousReport(client, commentsRequestURL, hiddenCommentDat
229
229
  const deleteCommentURL = `${commentsRequestURL}/${comment.id}`;
230
230
  await client.request({
231
231
  method: 'DELETE',
232
- url: deleteCommentURL
232
+ url: deleteCommentURL,
233
233
  });
234
234
  }
235
235
  catch (e) {
@@ -43,7 +43,7 @@ class GitLabPipe {
43
43
  baseURL: 'https://gitlab.com/api/v4',
44
44
  headers: {
45
45
  'Content-Type': 'application/json',
46
- }
46
+ },
47
47
  });
48
48
  debug('GitLab Pipe: Enabled');
49
49
  }
@@ -142,7 +142,7 @@ class GitLabPipe {
142
142
  method: 'POST',
143
143
  url: commentsRequestURL,
144
144
  params: { access_token: this.token },
145
- data: { body }
145
+ data: { body },
146
146
  });
147
147
  const commentID = addCommentResponse.data.id;
148
148
  // eslint-disable-next-line max-len
@@ -169,7 +169,7 @@ async function deletePreviousReport(client, commentsRequestURL, hiddenCommentDat
169
169
  const response = await client.request({
170
170
  method: 'GET',
171
171
  url: commentsRequestURL,
172
- params: { access_token: token }
172
+ params: { access_token: token },
173
173
  });
174
174
  comments = response.data;
175
175
  }
@@ -187,7 +187,7 @@ async function deletePreviousReport(client, commentsRequestURL, hiddenCommentDat
187
187
  await client.request({
188
188
  method: 'DELETE',
189
189
  url: deleteCommentURL,
190
- params: { access_token: token }
190
+ params: { access_token: token },
191
191
  });
192
192
  }
193
193
  catch (e) {
@@ -47,11 +47,12 @@ declare class TestomatioPipe implements Pipe {
47
47
  prepareRun(opts: any): Promise<string[]>;
48
48
  /**
49
49
  * Creates a new run on Testomat.io
50
- * @param {{isBatchEnabled?: boolean}} params
50
+ * @param {{isBatchEnabled?: boolean, kind?: string}} params
51
51
  * @returns Promise<void>
52
52
  */
53
53
  createRun(params?: {
54
54
  isBatchEnabled?: boolean;
55
+ kind?: string;
55
56
  }): Promise<void>;
56
57
  runUrl: string;
57
58
  runPublicUrl: any;
@@ -60,7 +60,7 @@ class TestomatioPipe {
60
60
  retry: constants_js_1.REPORTER_REQUEST_RETRIES.retriesPerRequest,
61
61
  retryDelay: constants_js_1.REPORTER_REQUEST_RETRIES.retryTimeout,
62
62
  httpMethodsToRetry: ['GET', 'PUT', 'HEAD', 'OPTIONS', 'DELETE', 'POST'],
63
- shouldRetry: (error) => {
63
+ shouldRetry: error => {
64
64
  if (!error.response)
65
65
  return false;
66
66
  switch (error.response?.status) {
@@ -73,8 +73,8 @@ class TestomatioPipe {
73
73
  break;
74
74
  }
75
75
  return error.response?.status >= 401; // Retry on 401+ and 5xx
76
- }
77
- }
76
+ },
77
+ },
78
78
  });
79
79
  this.isEnabled = true;
80
80
  // do not finish this run (for parallel testing)
@@ -148,7 +148,7 @@ class TestomatioPipe {
148
148
  }
149
149
  /**
150
150
  * Creates a new run on Testomat.io
151
- * @param {{isBatchEnabled?: boolean}} params
151
+ * @param {{isBatchEnabled?: boolean, kind?: string}} params
152
152
  * @returns Promise<void>
153
153
  */
154
154
  async createRun(params = {}) {
@@ -184,6 +184,7 @@ class TestomatioPipe {
184
184
  label: this.label,
185
185
  shared_run: this.sharedRun,
186
186
  shared_run_timeout: this.sharedRunTimeout,
187
+ kind: params.kind,
187
188
  }).filter(([, value]) => !!value));
188
189
  debug(' >>>>>> Run params', JSON.stringify(runParams, null, 2));
189
190
  if (this.runId) {
@@ -193,7 +194,7 @@ class TestomatioPipe {
193
194
  method: 'PUT',
194
195
  url: `/api/reporter/${this.runId}`,
195
196
  data: runParams,
196
- responseType: 'json'
197
+ responseType: 'json',
197
198
  });
198
199
  if (resp.data.artifacts)
199
200
  (0, pipe_utils_js_1.setS3Credentials)(resp.data.artifacts);
@@ -206,7 +207,7 @@ class TestomatioPipe {
206
207
  url: '/api/reporter',
207
208
  data: runParams,
208
209
  maxContentLength: Infinity,
209
- responseType: 'json'
210
+ responseType: 'json',
210
211
  });
211
212
  this.runId = resp.data.uid;
212
213
  this.runUrl = `${this.url}/${resp.data.url.split('/').splice(3).join('/')}`;
@@ -259,15 +260,17 @@ class TestomatioPipe {
259
260
  this.#formatData(data);
260
261
  const json = json_cycle_1.default.stringify(data);
261
262
  debug('Adding test', json);
262
- return this.client.request({
263
+ return this.client
264
+ .request({
263
265
  method: 'POST',
264
266
  url: `/api/reporter/${this.runId}/testrun`,
265
267
  data: json,
266
268
  headers: {
267
269
  'Content-Type': 'application/json',
268
270
  },
269
- maxContentLength: Infinity
270
- }).catch(err => {
271
+ maxContentLength: Infinity,
272
+ })
273
+ .catch(err => {
271
274
  this.requestFailures++;
272
275
  this.notReportedTestsCount++;
273
276
  if (err.response) {
@@ -312,19 +315,21 @@ class TestomatioPipe {
312
315
  // get tests from batch and clear batch
313
316
  const testsToSend = this.batch.tests.splice(0);
314
317
  debug('📨 Batch upload', testsToSend.length, 'tests');
315
- return this.client.request({
318
+ return this.client
319
+ .request({
316
320
  method: 'POST',
317
321
  url: `/api/reporter/${this.runId}/testrun`,
318
322
  data: {
319
323
  api_key: this.apiKey,
320
324
  tests: testsToSend,
321
- batch_index: this.batch.batchIndex
325
+ batch_index: this.batch.batchIndex,
322
326
  },
323
327
  headers: {
324
328
  'Content-Type': 'application/json',
325
329
  },
326
- maxContentLength: Infinity
327
- }).catch(err => {
330
+ maxContentLength: Infinity,
331
+ })
332
+ .catch(err => {
328
333
  this.requestFailures++;
329
334
  this.notReportedTestsCount += testsToSend.length;
330
335
  if (err.response) {
@@ -408,7 +413,7 @@ class TestomatioPipe {
408
413
  status_event,
409
414
  detach: params.detach,
410
415
  tests: params.tests,
411
- }
416
+ },
412
417
  });
413
418
  if (this.runUrl) {
414
419
  console.log(constants_js_1.APP_PREFIX, '📊 Report Saved. Report URL:', picocolors_1.default.magenta(this.runUrl));
@@ -59,9 +59,7 @@ function setLabel(key, value = null) {
59
59
  if (Array.isArray(value)) {
60
60
  return value.forEach(label => setLabel(key, label));
61
61
  }
62
- const labelObject = value !== null && value !== undefined && value !== ''
63
- ? { label: `${key}:${value}` }
64
- : { label: key };
62
+ const labelObject = value !== null && value !== undefined && value !== '' ? { label: `${key}:${value}` } : { label: key };
65
63
  index_js_1.services.links.put([labelObject]);
66
64
  }
67
65
  /**