@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
package/lib/xmlReader.js CHANGED
@@ -11,6 +11,7 @@ const fast_xml_parser_1 = require("fast-xml-parser");
11
11
  const constants_js_1 = require("./constants.js");
12
12
  const crypto_1 = require("crypto");
13
13
  const url_1 = require("url");
14
+ const nunit_parser_js_1 = require("./junit-adapter/nunit-parser.js");
14
15
  const utils_js_1 = require("./utils/utils.js");
15
16
  const index_js_1 = require("./pipe/index.js");
16
17
  const index_js_2 = __importDefault(require("./junit-adapter/index.js"));
@@ -20,7 +21,7 @@ const uploader_js_1 = require("./uploader.js");
20
21
  const debug = (0, debug_1.default)('@testomatio/reporter:xml');
21
22
  const ridRunId = (0, crypto_1.randomUUID)();
22
23
  const TESTOMATIO_URL = process.env.TESTOMATIO_URL || 'https://app.testomat.io';
23
- const { TESTOMATIO_RUNGROUP_TITLE, TESTOMATIO_SUITE, TESTOMATIO_MAX_STACK_TRACE, TESTOMATIO_TITLE, TESTOMATIO_ENV, TESTOMATIO_RUN, TESTOMATIO_MARK_DETACHED, } = process.env;
24
+ const { TESTOMATIO_RUNGROUP_TITLE, TESTOMATIO_SUITE, TESTOMATIO_MAX_STACK_TRACE, TESTOMATIO_TITLE, TESTOMATIO_ENV, TESTOMATIO_RUN, TESTOMATIO_MARK_DETACHED, TESTOMATIO_LEGACY_NUNIT, } = process.env;
24
25
  const options = {
25
26
  ignoreDeclaration: true,
26
27
  ignoreAttributes: false,
@@ -54,6 +55,10 @@ class XmlReader {
54
55
  this.stats = {};
55
56
  this.stats.language = opts.lang?.toLowerCase();
56
57
  this.uploader = new uploader_js_1.S3Uploader();
58
+ // Enhanced NUnit parsing - enabled by default for NUnit XML
59
+ // Can be disabled via opts.enhancedNunit = false or TESTOMATIO_LEGACY_NUNIT=1
60
+ this.enhancedNunit = !(0, utils_js_1.transformEnvVarToBoolean)(TESTOMATIO_LEGACY_NUNIT);
61
+ this.groupParameterized = opts.groupParameterized !== false; // Default true, can be disabled
57
62
  // @ts-ignore
58
63
  const packageJsonPath = path_1.default.resolve(__dirname, '..', 'package.json');
59
64
  this.version = JSON.parse(fs_1.default.readFileSync(packageJsonPath).toString()).version;
@@ -102,7 +107,8 @@ class XmlReader {
102
107
  return this.processJUnit(jsonSuite);
103
108
  }
104
109
  processJUnit(jsonSuite) {
105
- const { testsuite, name, tests, failures, errors } = jsonSuite;
110
+ const { testsuite, name, failures, errors } = jsonSuite;
111
+ const tests = testsuite?.tests || jsonSuite.tests;
106
112
  reduceOptions.preferClassname = this.stats.language === 'python';
107
113
  const resultTests = processTestSuite(testsuite);
108
114
  const hasFailures = resultTests.filter(t => t.status === 'failed').length > 0;
@@ -128,6 +134,13 @@ class XmlReader {
128
134
  };
129
135
  }
130
136
  processNUnit(jsonSuite) {
137
+ // Use enhanced NUnit parser if enabled and this is actually NUnit XML
138
+ if (this.enhancedNunit && this.isNUnitXml(jsonSuite)) {
139
+ debug('Using enhanced NUnit parser');
140
+ return this.processNUnitEnhanced(jsonSuite);
141
+ }
142
+ // Fallback to legacy parser for backward compatibility
143
+ debug('Using legacy NUnit parser');
131
144
  const { result, total, passed, failed, inconclusive, skipped } = jsonSuite;
132
145
  reduceOptions.preferClassname = this.stats.language === 'python';
133
146
  const resultTests = processTestSuite(jsonSuite['test-suite']);
@@ -142,65 +155,58 @@ class XmlReader {
142
155
  tests: resultTests,
143
156
  };
144
157
  }
158
+ /**
159
+ * Check if the XML is actually NUnit format (has test-suite hierarchy)
160
+ * @param {Object} jsonSuite - Parsed XML suite object
161
+ * @returns {boolean} - True if this is NUnit XML format
162
+ */
163
+ isNUnitXml(jsonSuite) {
164
+ // NUnit XML has test-suite elements with type attributes
165
+ if (jsonSuite['test-suite']) {
166
+ const testSuite = Array.isArray(jsonSuite['test-suite']) ? jsonSuite['test-suite'][0] : jsonSuite['test-suite'];
167
+ // Check for NUnit-specific test-suite types
168
+ return (testSuite &&
169
+ testSuite.type &&
170
+ ['Assembly', 'TestSuite', 'TestFixture', 'ParameterizedMethod'].includes(testSuite.type));
171
+ }
172
+ return false;
173
+ }
174
+ processNUnitEnhanced(jsonSuite) {
175
+ debug('Processing NUnit XML with enhanced parser');
176
+ try {
177
+ const nunitParser = new nunit_parser_js_1.NUnitXmlParser({
178
+ groupParameterized: this.groupParameterized,
179
+ ...this.opts,
180
+ });
181
+ const result = nunitParser.parseTestRun(jsonSuite);
182
+ // Add parsed tests to our collection
183
+ this.tests = this.tests.concat(result.tests);
184
+ debug(`Enhanced NUnit parser processed ${result.tests.length} tests`);
185
+ return result;
186
+ }
187
+ catch (error) {
188
+ debug('Enhanced NUnit parser failed, falling back to legacy parser:', error.message);
189
+ console.warn(`${constants_js_1.APP_PREFIX} Enhanced NUnit parsing failed, using legacy parser: ${error.message}`);
190
+ // Fallback to legacy parser
191
+ this.enhancedNunit = false;
192
+ return this.processNUnit(jsonSuite);
193
+ }
194
+ }
145
195
  processTRX(jsonSuite) {
146
196
  let defs = jsonSuite?.TestRun?.TestDefinitions?.UnitTest;
147
197
  if (!Array.isArray(defs))
148
198
  defs = [defs].filter(d => !!d);
149
- const tests = defs.map(td => {
150
- const title = td.name.replace(/\(.*?\)/, '').trim();
151
- let example = td.name.match(/\((.*?)\)/);
152
- if (example)
153
- example = { ...example[1].split(',') };
154
- const suite = td.TestMethod.className.split(', ')[0].split('.');
155
- const suite_title = suite.pop();
156
- return {
157
- title,
158
- example,
159
- file: suite.join('/'),
160
- description: td.Description,
161
- suite_title,
162
- id: td.Execution.id,
163
- };
164
- }) || [];
199
+ // Parse test definitions
200
+ const tests = defs.map(td => this._parseTRXTestDefinition(td));
201
+ // Parse test results
165
202
  let result = jsonSuite?.TestRun?.Results?.UnitTestResult;
166
203
  if (!Array.isArray(result))
167
204
  result = [result].filter(d => !!d);
168
- const results = result.map(td => ({
169
- id: td.executionId,
170
- // seconds are used in junit reports, but ms are used by testomatio
171
- run_time: parseFloat(td.duration) * 1000,
172
- status: td.outcome,
173
- stack: td.Output.StdOut,
174
- files: td?.ResultFiles?.ResultFile?.map(rf => rf.path),
175
- }));
176
- results.forEach(r => {
177
- const test = tests.find(t => t.id === r.id) || {};
178
- r.suite_title = test.suite_title;
179
- r.title = test.title?.trim();
180
- if (test.code)
181
- r.code = test.code;
182
- if (test.description)
183
- r.description = test.description;
184
- if (test.example)
185
- r.example = test.example;
186
- if (test.file)
187
- r.file = test.file;
188
- r.create = true;
189
- r.overwrite = true;
190
- if (r.status === 'Passed')
191
- r.status = constants_js_1.STATUS.PASSED;
192
- if (r.status === 'Failed')
193
- r.status = constants_js_1.STATUS.FAILED;
194
- if (r.status === 'Skipped')
195
- r.status = constants_js_1.STATUS.SKIPPED;
196
- delete r.id;
197
- });
205
+ const results = result.map(td => this._parseTRXTestResult(td, tests));
198
206
  debug(results);
199
207
  const counters = jsonSuite?.TestRun?.ResultSummary?.Counters || {};
200
208
  const failed_count = parseInt(counters.failed, 10) + parseInt(counters.error, 10);
201
- let status = constants_js_1.STATUS.PASSED.toString();
202
- if (failed_count > 0)
203
- status = constants_js_1.STATUS.FAILED;
209
+ const status = failed_count > 0 ? constants_js_1.STATUS.FAILED : constants_js_1.STATUS.PASSED.toString();
204
210
  this.tests = results.filter(t => !!t.title);
205
211
  return {
206
212
  status,
@@ -212,6 +218,64 @@ class XmlReader {
212
218
  tests: results,
213
219
  };
214
220
  }
221
+ _parseTRXTestDefinition(td) {
222
+ const title = td.name.replace(/\(.*?\)/, '').trim();
223
+ const exampleMatch = td.name.match(/\((.*?)\)/);
224
+ const example = exampleMatch
225
+ ? {
226
+ ...exampleMatch[1]
227
+ .split(',')
228
+ .map(p => p.trim())
229
+ .filter(p => p !== ''),
230
+ }
231
+ : null;
232
+ const suite = td.TestMethod.className.split(', ')[0].split('.');
233
+ const suite_title = suite.pop();
234
+ // Convert namespace to file path for C#
235
+ const file = `${suite.join('/')}.cs`;
236
+ return {
237
+ title, // Base name without parameters for test import
238
+ example, // Parameters object for parameterized tests
239
+ file, // File path with .cs extension
240
+ description: td.Description,
241
+ suite_title,
242
+ id: td.Execution.id,
243
+ };
244
+ }
245
+ _parseTRXTestResult(td, tests) {
246
+ const test = tests.find(t => t.id === td.executionId) || {};
247
+ const result = {
248
+ suite_title: test.suite_title,
249
+ title: test.title?.trim(),
250
+ file: test.file,
251
+ description: test.description,
252
+ code: test.code,
253
+ run_time: parseFloat(td.duration) * 1000,
254
+ stack: td.Output?.StdOut || '',
255
+ files: td?.ResultFiles?.ResultFile?.map(rf => rf.path),
256
+ create: true,
257
+ overwrite: true,
258
+ };
259
+ // Add example for parameterized tests
260
+ if (test.example) {
261
+ result.example = test.example;
262
+ }
263
+ // Map TRX status to Testomat.io status
264
+ result.status = this._mapTRXStatus(td.outcome);
265
+ return result;
266
+ }
267
+ _mapTRXStatus(outcome) {
268
+ switch (outcome) {
269
+ case 'Passed':
270
+ return constants_js_1.STATUS.PASSED;
271
+ case 'Failed':
272
+ return constants_js_1.STATUS.FAILED;
273
+ case 'Skipped':
274
+ return constants_js_1.STATUS.SKIPPED;
275
+ default:
276
+ return constants_js_1.STATUS.PASSED;
277
+ }
278
+ }
215
279
  processXUnit(assemblies) {
216
280
  const tests = [];
217
281
  assemblies = Array.isArray(assemblies.assembly) ? assemblies.assembly : [assemblies.assembly];
@@ -459,7 +523,12 @@ function reduceTestCases(prev, item) {
459
523
  tags ||= [];
460
524
  const exampleMatches = testCaseItem.name?.match(/\S\((.*?)\)/);
461
525
  if (exampleMatches) {
462
- example = { ...exampleMatches[1].split(',').map(v => v.trim().replace(/[^\w\s-]/g, '')) };
526
+ example = {
527
+ ...exampleMatches[1]
528
+ .split(',')
529
+ .map(v => v.trim().replace(/[^\w\s-]/g, ''))
530
+ .filter(v => v !== ''),
531
+ };
463
532
  title = title.replace(/\(.*?\)/, '').trim();
464
533
  }
465
534
  stack = `${testCaseItem['system-out'] || testCaseItem.output || testCaseItem.log || ''}\n\n${stack}\n\n${suiteOutput}\n\n${suiteErr}`.trim();
package/package.json CHANGED
@@ -1,11 +1,13 @@
1
1
  {
2
2
  "name": "@testomatio/reporter",
3
- "version": "2.3.9-beta-bin-fix",
3
+ "version": "2.3.9",
4
4
  "description": "Testomatio Reporter Client",
5
5
  "engines": {
6
6
  "node": ">=18"
7
7
  },
8
- "typings": "typings/index.d.ts",
8
+ "main": "lib/reporter.js",
9
+ "module": "src/reporter.js",
10
+ "types": "types/types.d.ts",
9
11
  "repository": "git@github.com:testomatio/reporter.git",
10
12
  "author": "Michael Bodnarchuk <davert@testomat.io>,Koushik Mohan <koushikmohan1996@gmail.com>",
11
13
  "license": "MIT",
@@ -45,7 +47,8 @@
45
47
  "bin",
46
48
  "lib",
47
49
  "src",
48
- "testcafe"
50
+ "testcafe",
51
+ "types"
49
52
  ],
50
53
  "scripts": {
51
54
  "clear-exportdir": "rm -rf export/",
@@ -57,7 +60,8 @@
57
60
  "test": "mocha 'tests/unit/**/*_test.js'",
58
61
  "test:playwright": "mocha tests/adapter/playwright.test.js",
59
62
  "test:codecept": "mocha tests/adapter/codecept.test.js tests/adapter/codecept_comprehensive.test.js tests/adapter/codecept_steps_sections.test.js",
60
- "test:frameworks": "npm run test:playwright && npm run test:codecept",
63
+ "test:vitest": "mocha tests/adapter/vitest.test.js",
64
+ "test:frameworks": "npm run test:playwright && npm run test:codecept && npm run test:vitest",
61
65
  "test:all": "npm run test && npm run test:frameworks",
62
66
  "test:adapters": "mocha tests/adapter/*.test.js",
63
67
  "test:codecept:bug948": "mocha tests/adapter/codecept_aftersuite_failure.test.js",
@@ -17,12 +17,15 @@ if (!global.codeceptjs) {
17
17
  // @ts-ignore
18
18
  const { event, recorder, codecept, output } = global.codeceptjs;
19
19
 
20
- const [, MAJOR_VERSION, MINOR_VERSION] = codecept.version().match(/(\d+)\.(\d+)/).map(Number);
20
+ const [, MAJOR_VERSION, MINOR_VERSION] = codecept
21
+ .version()
22
+ .match(/(\d+)\.(\d+)/)
23
+ .map(Number);
21
24
 
22
25
  // Constants for hook execution order
23
26
  const HOOK_EXECUTION_ORDER = {
24
27
  PRE_TEST: ['BeforeSuiteHook', 'BeforeHook'],
25
- POST_TEST: ['AfterHook', 'AfterSuiteHook']
28
+ POST_TEST: ['AfterHook', 'AfterSuiteHook'],
26
29
  };
27
30
 
28
31
  // codeceptjs workers are self-contained
@@ -35,7 +38,9 @@ if (MAJOR_VERSION < 3) {
35
38
  }
36
39
 
37
40
  if (MAJOR_VERSION === 3 && MINOR_VERSION < 7) {
38
- console.log('🔴 CodeceptJS 3.7+ is supported, please upgrade CodeceptJS or use 1.6 version of `@testomatio/reporter`');
41
+ console.log(
42
+ '🔴 CodeceptJS 3.7+ is supported, please upgrade CodeceptJS or use 1.6 version of `@testomatio/reporter`',
43
+ );
39
44
  }
40
45
 
41
46
  function CodeceptReporter(config) {
@@ -57,18 +62,18 @@ function CodeceptReporter(config) {
57
62
  say: output.say,
58
63
  };
59
64
 
60
- output.debug = function(msg) {
65
+ output.debug = function (msg) {
61
66
  originalOutput.debug(msg);
62
67
  dataStorage.putData('log', repeat(this?.stepShift || 0) + pc.cyan(msg.toString()));
63
68
  };
64
69
 
65
- output.say = function(message, color = 'cyan') {
70
+ output.say = function (message, color = 'cyan') {
66
71
  originalOutput.say(message, color);
67
72
  const sayMsg = repeat(this?.stepShift || 0) + ` ${pc.bold(pc[color](message))}`;
68
73
  dataStorage.putData('log', sayMsg);
69
74
  };
70
75
 
71
- output.log = function(msg) {
76
+ output.log = function (msg) {
72
77
  originalOutput.log(msg);
73
78
  dataStorage.putData('log', repeat(this?.stepShift || 0) + pc.gray(msg));
74
79
  };
@@ -108,7 +113,7 @@ function CodeceptReporter(config) {
108
113
  });
109
114
 
110
115
  // Hook event listeners
111
- event.dispatcher.on(event.hook.started, (hook) => {
116
+ event.dispatcher.on(event.hook.started, hook => {
112
117
  output.stepShift = 2;
113
118
  currentHook = hook.name;
114
119
  let title = hook.hookName;
@@ -126,7 +131,6 @@ function CodeceptReporter(config) {
126
131
  services.setContext(null);
127
132
  });
128
133
 
129
-
130
134
  // mark as failed all tests inside the failed hook
131
135
  event.dispatcher.on(event.hook.failed, hook => {
132
136
  if (hook.name !== 'BeforeSuiteHook') return;
@@ -148,7 +152,6 @@ function CodeceptReporter(config) {
148
152
  }
149
153
  });
150
154
 
151
-
152
155
  event.dispatcher.on(event.suite.before, suite => {
153
156
  dataStorage.setContext(suite.fullTitle());
154
157
  });
@@ -167,7 +170,7 @@ function CodeceptReporter(config) {
167
170
  testTimeMap[test.uid] = Date.now();
168
171
  });
169
172
 
170
- event.dispatcher.on(event.all.result, async (result) => {
173
+ event.dispatcher.on(event.all.result, async result => {
171
174
  debug('waiting for all tests to be reported');
172
175
  // all tests were reported and we can upload videos
173
176
  await Promise.all(reportTestPromises);
@@ -327,7 +330,7 @@ function captureHookStep(step, currentHook, hookSteps) {
327
330
  status: step.status,
328
331
  startTime,
329
332
  endTime,
330
- helperMethod: step.helperMethod
333
+ helperMethod: step.helperMethod,
331
334
  });
332
335
  hookSteps.set(currentHook, hookStepsArray);
333
336
  }
@@ -422,13 +425,12 @@ function processTestSteps(steps, hierarchy) {
422
425
  }
423
426
  }
424
427
 
425
-
426
428
  function createSectionStep(metaStep) {
427
429
  return {
428
430
  category: 'user',
429
431
  title: metaStep.toString(), // Use built-in toString method
430
432
  duration: metaStep.duration || 0, // Use built-in duration
431
- steps: []
433
+ steps: [],
432
434
  };
433
435
  }
434
436
 
@@ -439,7 +441,7 @@ function createHookSection(hookName, steps) {
439
441
  category: 'hook',
440
442
  title: formatHookName(hookName),
441
443
  duration: 0,
442
- steps: []
444
+ steps: [],
443
445
  };
444
446
 
445
447
  for (const step of steps) {
@@ -457,7 +459,6 @@ function formatHookName(hookName) {
457
459
  return hookName.replace(/Hook$/, '');
458
460
  }
459
461
 
460
-
461
462
  // Format CodeceptJS step using its built-in methods
462
463
  function formatCodeceptStep(step) {
463
464
  if (!step) return null;
@@ -469,14 +470,14 @@ function formatCodeceptStep(step) {
469
470
  const formattedStep = {
470
471
  category,
471
472
  title,
472
- duration
473
+ duration,
473
474
  };
474
475
 
475
476
  // Add error if step failed
476
477
  if (step.status === 'failed' && step.err) {
477
478
  formattedStep.error = {
478
479
  message: step.err.message || 'Step failed',
479
- stack: step.err.stack || ''
480
+ stack: step.err.stack || '',
480
481
  };
481
482
  }
482
483
 
@@ -500,10 +501,9 @@ function formatHookStep(step) {
500
501
  return {
501
502
  category: 'hook',
502
503
  title,
503
- duration: step.duration || 0
504
+ duration: step.duration || 0,
504
505
  };
505
506
  }
506
507
 
507
-
508
508
  export { CodeceptReporter };
509
509
  export default CodeceptReporter;
@@ -84,7 +84,7 @@ function MochaReporter(runner, opts) {
84
84
  const artifacts = services.artifacts.get(test.fullTitle());
85
85
  const keyValues = services.keyValues.get(test.fullTitle());
86
86
  const links = services.links.get(test.fullTitle());
87
-
87
+
88
88
  client.addTestRun(STATUS.SKIPPED, {
89
89
  title: getTestName(test),
90
90
  suite_title: getSuiteTitle(test),
@@ -254,7 +254,7 @@ function generateTmpFilepath(filename = '') {
254
254
  */
255
255
  function extractTags(test) {
256
256
  const tagsSet = new Set();
257
-
257
+
258
258
  // Extract tags from test title (@tag format)
259
259
  const titleTagsMatch = test.title.match(/@\w+/g);
260
260
  if (titleTagsMatch) {
@@ -262,7 +262,7 @@ function extractTags(test) {
262
262
  tagsSet.add(tag.replace('@', '').toLowerCase());
263
263
  });
264
264
  }
265
-
265
+
266
266
  // Extract tags from test.tags (Playwright built-in tags)
267
267
  if (test.tags && Array.isArray(test.tags)) {
268
268
  test.tags.forEach(tag => {
package/src/bin/cli.js CHANGED
@@ -35,14 +35,20 @@ program
35
35
  program
36
36
  .command('start')
37
37
  .description('Start a new run and return its ID')
38
- .action(async () => {
38
+ .option('--kind <type>', 'Specify run type: automated, manual, or mixed')
39
+ .action(async opts => {
39
40
  cleanLatestRunId();
40
41
 
41
42
  console.log('Starting a new Run on Testomat.io...');
42
43
  const apiKey = process.env['INPUT_TESTOMATIO-KEY'] || config.TESTOMATIO;
43
44
  const client = new TestomatClient({ apiKey });
44
45
 
45
- client.createRun().then(() => {
46
+ const createRunParams = {};
47
+ if (opts.kind) {
48
+ createRunParams.kind = opts.kind;
49
+ }
50
+
51
+ client.createRun(createRunParams).then(() => {
46
52
  console.log(process.env.runId);
47
53
  process.exit(0);
48
54
  });
@@ -75,6 +81,7 @@ program
75
81
  .description('Run tests with the specified command')
76
82
  .argument('<command>', 'Test runner command')
77
83
  .option('--filter <filter>', 'Additional execution filter')
84
+ .option('--kind <type>', 'Specify run type: automated, manual, or mixed')
78
85
  .action(async (command, opts) => {
79
86
  const apiKey = process.env['INPUT_TESTOMATIO-KEY'] || config.TESTOMATIO;
80
87
  const title = process.env.TESTOMATIO_TITLE;
@@ -120,8 +127,13 @@ program
120
127
  });
121
128
  };
122
129
 
130
+ const createRunParams = {};
131
+ if (opts.kind) {
132
+ createRunParams.kind = opts.kind;
133
+ }
134
+
123
135
  if (apiKey) {
124
- await client.createRun().then(runTests);
136
+ await client.createRun(createRunParams).then(runTests);
125
137
  } else {
126
138
  await runTests();
127
139
  }
@@ -158,7 +170,7 @@ program
158
170
  .option('--lang <lang>', 'Language used (python, ruby, java)')
159
171
  .option('--timelimit <time>', 'default time limit in seconds to kill a stuck process')
160
172
  .action(async (pattern, opts) => {
161
- if (!pattern.endsWith('.xml')) {
173
+ if (!pattern.endsWith('.xml') && !pattern.includes('*')) {
162
174
  pattern += '.xml';
163
175
  }
164
176
  let { javaTests, lang } = opts;
@@ -23,7 +23,7 @@ program
23
23
  .option('--timelimit <time>', 'default time limit in seconds to kill a stuck process')
24
24
  .option('--env-file <envfile>', 'Load environment variables from env file')
25
25
  .action(async (pattern, opts) => {
26
- if (!pattern.endsWith('.xml')) {
26
+ if (!pattern.endsWith('.xml') && !pattern.includes('*')) {
27
27
  pattern += '.xml';
28
28
  }
29
29
  let { javaTests, lang } = opts;
@@ -34,7 +34,10 @@ program
34
34
  }
35
35
  lang = lang?.toLowerCase();
36
36
  if (javaTests === true || (lang === 'java' && !javaTests)) javaTests = 'src/test/java';
37
- const runReader = new XmlReader({ javaTests, lang });
37
+ const runReader = new XmlReader({
38
+ javaTests,
39
+ lang,
40
+ });
38
41
  const files = glob.sync(pattern, { cwd: opts.dir || process.cwd() });
39
42
  if (!files.length) {
40
43
  console.log(APP_PREFIX, `Report can't be created. No XML files found 😥`);