@testomatio/reporter 1.0.9-beta.1 → 1.0.9-beta.2

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.
@@ -3,15 +3,18 @@ const Mocha = require('mocha');
3
3
  const debug = require('debug')('@testomatio/reporter:adapter:mocha');
4
4
  const chalk = require('chalk');
5
5
  const TestomatClient = require('../client');
6
- const { STATUS } = require('../constants');
7
- const { parseTest, specificTestInfo } = require('../util');
6
+ const { STATUS, TESTOMAT_TMP_STORAGE } = require('../constants');
7
+ const { parseTest, specificTestInfo, fileSystem } = require('../util');
8
8
  const ArtifactStorage = require('../_ArtifactStorageOld');
9
9
 
10
- const { EVENT_RUN_BEGIN, EVENT_RUN_END, EVENT_TEST_FAIL, EVENT_TEST_PASS, EVENT_TEST_PENDING } = Mocha.Runner.constants;
10
+ const { EVENT_RUN_BEGIN, EVENT_RUN_END, EVENT_TEST_FAIL, EVENT_TEST_PASS, EVENT_TEST_PENDING } =
11
+ Mocha.Runner.constants;
11
12
 
12
13
  function MochaReporter(runner, opts) {
13
14
  Mocha.reporters.Base.call(this, runner);
14
- let passes = 0; let failures = 0; let skipped = 0;
15
+ let passes = 0;
16
+ let failures = 0;
17
+ let skipped = 0;
15
18
  let artifactStore;
16
19
 
17
20
  const apiKey = opts?.reporterOptions?.apiKey || process.env.TESTOMATIO;
@@ -25,33 +28,33 @@ function MochaReporter(runner, opts) {
25
28
  runner.on(EVENT_RUN_BEGIN, () => {
26
29
  client.createRun();
27
30
 
28
- const params = {
29
- toFile: true
31
+ const params = {
32
+ toFile: true,
30
33
  };
31
-
32
- artifactStore = runner._workerReporter !== undefined
33
- ? new ArtifactStorage(params)
34
- : new ArtifactStorage();
34
+
35
+ artifactStore = runner._workerReporter !== undefined ? new ArtifactStorage(params) : new ArtifactStorage();
36
+
37
+ fileSystem.clearDir(TESTOMAT_TMP_STORAGE.mainDir);
35
38
  });
36
39
 
37
- runner.on(EVENT_TEST_PASS, async(test) => {
40
+ runner.on(EVENT_TEST_PASS, async test => {
38
41
  passes += 1;
39
42
  console.log(chalk.bold.green('✔'), test.fullTitle());
40
43
  const testId = parseTest(test.title);
41
44
 
42
- const specificTest = specificTestInfo(test);
45
+ const specificTest = specificTestInfo(test);
43
46
  const content = await artifactStore.artifactByTestName(specificTest);
44
47
 
45
48
  debug(`test=${specificTest} content = `, content);
46
49
 
47
50
  client.addTestRun(
48
- STATUS.PASSED,
51
+ STATUS.PASSED,
49
52
  {
50
53
  test_id: testId,
51
54
  title: test.title,
52
55
  time: test.duration,
53
56
  },
54
- content
57
+ content,
55
58
  );
56
59
  });
57
60
 
@@ -66,24 +69,25 @@ function MochaReporter(runner, opts) {
66
69
  });
67
70
  });
68
71
 
69
- runner.on(EVENT_TEST_FAIL, async(test, err) => {
72
+ runner.on(EVENT_TEST_FAIL, async (test, err) => {
70
73
  failures += 1;
71
74
  console.log(chalk.bold.red('✖'), test.fullTitle(), chalk.gray(err.message));
72
75
  const testId = parseTest(test.title);
73
76
 
74
- const specificTest = specificTestInfo(test);
77
+ const specificTest = specificTestInfo(test);
75
78
  const content = await artifactStore.artifactByTestName(specificTest);
76
79
 
77
80
  debug(`fail test=${specificTest} content = `, content);
78
81
 
79
82
  client.addTestRun(
80
- STATUS.FAILED, {
83
+ STATUS.FAILED,
84
+ {
81
85
  error: err,
82
86
  test_id: testId,
83
87
  title: test.title,
84
88
  time: test.duration,
85
- },
86
- content
89
+ },
90
+ content,
87
91
  );
88
92
  });
89
93
 
@@ -56,7 +56,8 @@ program
56
56
  const client = new TestomatClient({ apiKey, title, parallel: true });
57
57
 
58
58
  if(filter) {
59
- const [pipe, opts] = filter.split(":");
59
+ const [pipe, ...optsArray] = filter.split(":");
60
+ const opts = optsArray.join(":");
60
61
 
61
62
  try {
62
63
  const tests = await client.prepareRun({pipe, opts});
@@ -11,7 +11,8 @@ const getTestIdFromTestTitle = require('./util').parseTest;
11
11
  class DataStorage {
12
12
  /**
13
13
  * Creates data storage instance for specific data type.
14
- * Stores data to global variable or to file depending on what is applicable for current test runner.
14
+ * Stores data to global variable or to file depending on what is applicable for current test runner
15
+ * (running environment).
15
16
  * dataType: 'log' | 'artifact' | ...
16
17
  * @param {*} dataType storage type (like 'log', 'artifact'); could be any string, used only to define file path
17
18
  */
@@ -19,7 +20,7 @@ class DataStorage {
19
20
  // if (!dataType) throw new Error('Data type is required when creating data storage');
20
21
  this.dataType = dataType || 'data';
21
22
  this.dataDirName = this.dataType;
22
-
23
+ this.isFileStorage = true;
23
24
  this._refreshStorageType();
24
25
 
25
26
  // dir is created in any case (not to recheck its existence every time)
@@ -37,14 +38,6 @@ class DataStorage {
37
38
  // jest
38
39
  if (process.env.JEST_WORKER_ID) return 'jest';
39
40
 
40
- // mocha
41
- try {
42
- // @ts-expect-error mocha is defined only in mocha environment
43
- if (typeof mocha !== 'undefined') return 'mocha';
44
- } catch (e) {
45
- // ignore
46
- }
47
-
48
41
  // codeceptjs
49
42
  // @ts-expect-error codeceptjs is defined only in codeceptjs environment
50
43
  if (global.codeceptjs) return 'codeceptjs';
@@ -55,6 +48,8 @@ class DataStorage {
55
48
 
56
49
  if (process.env.PLAYWRIGHT_TEST_BASE_URL) return 'playwright';
57
50
 
51
+ // mocha - can't detect
52
+
58
53
  return null;
59
54
  }
60
55
 
@@ -69,15 +64,24 @@ class DataStorage {
69
64
 
70
65
  let testId = this._tryToRetrieveTestId(context) || null;
71
66
 
72
- if (this.runningEnvironment === 'codeceptjs') testId = testId ?? global.testomatioDataStore?.currentlyRunningTestId;
73
- if (this.runningEnvironment === 'jest') testId = testId ?? JestReporter.getIdOfCurrentlyRunningTest();
74
- // logs in playwright are gathered by pw framwork itself
67
+ if (this.runningEnvironment === 'codeceptjs') {
68
+ this.isFileStorage = false;
69
+ testId = testId ?? global.testomatioDataStore?.currentlyRunningTestId;
70
+ }
71
+ if (this.runningEnvironment === 'jest') {
72
+ testId = testId ?? JestReporter.getIdOfCurrentlyRunningTest();
73
+ this.isFileStorage = true;
74
+ }
75
+ // logs in playwright are gathered by pw framework itself
75
76
  if (this.runningEnvironment === 'playwright' && this.dataType === 'log') return;
76
77
 
77
78
  // get id from global store
78
79
  if (global.testomatioDataStore && global.testomatioDataStore.currentlyRunningTestId)
79
80
  testId = testId ?? global.testomatioDataStore?.currentlyRunningTestId;
80
81
 
82
+ if (!testId && global?.currentlyRunningTestTitle)
83
+ testId = this._tryToRetrieveTestId(global.currentlyRunningTestTitle);
84
+
81
85
  // if testId is not provided, data is be saved to `{dataType}_other` file;
82
86
  if (!testId) {
83
87
  debug(`No test id provided for ${this.dataType} data: ${data}`);
@@ -116,17 +120,26 @@ class DataStorage {
116
120
  return null;
117
121
  }
118
122
 
119
- let testData = null;
123
+ let testData = '';
120
124
 
121
125
  if (global?.testomatioDataStore) {
122
126
  testData = this._getDataFromGlobalVar(testId);
123
- if (testData) return testData;
127
+ // these frameworks use global variable storage
128
+ if (testData && ['cucumber', 'codeceptjs'].includes(this.runningEnvironment)) return testData;
124
129
  }
125
130
 
126
- if (this.isFileStorage || !testData) {
127
- testData = this._getDataFromFile(testId);
128
- if (testData) return testData;
129
- }
131
+ /* condition is removed for mocha
132
+ mocha has created a global storage, but just in some cases, generally it is not available
133
+ */
134
+ // if (this.isFileStorage || !testData) {
135
+ // testData = this._getDataFromFile(testId);
136
+ // if (testData) return testData;
137
+ // }
138
+
139
+ const testDataFromFile = this._getDataFromFile(testId);
140
+ testData += testDataFromFile || '';
141
+
142
+ if (testData) return testData;
130
143
 
131
144
  debug(`No ${this.dataType} data for test id ${testId} in both file and global variable`);
132
145
  return testData;
@@ -162,7 +175,6 @@ class DataStorage {
162
175
  * Because storage instance is created before the test runner is started. And storage type could be changed
163
176
  */
164
177
  _refreshStorageType() {
165
- this.isFileStorage = !global.testomatioDataStore;
166
178
  /*
167
179
  FYI:
168
180
  If this storage instance is used within test runner, it works fine.
@@ -174,8 +186,8 @@ class DataStorage {
174
186
  */
175
187
  this.runningEnvironment = this.getRunningEnviroment();
176
188
 
177
- // some test frameworks do not persist global variables, thus file storage is used for them
178
- if (this.runningEnvironment === 'jest') this.isFileStorage = true;
189
+ // some test frameworks do not persist global variables, thus file storage is used for them (by default)
190
+ if (['playwright', 'codeceptjs'].includes(this.runningEnvironment)) this.isFileStorage = false;
179
191
  }
180
192
 
181
193
  _getDataFromGlobalVar(testId) {
@@ -186,7 +198,7 @@ class DataStorage {
186
198
  return testData;
187
199
  }
188
200
  debug(`No ${this.dataType} data for test id ${testId} in <global> storage`);
189
- return null;
201
+ return '';
190
202
  } catch (e) {
191
203
  // there could be no data, ignore
192
204
  }
@@ -201,11 +213,11 @@ class DataStorage {
201
213
  return testData;
202
214
  }
203
215
  debug(`No ${this.dataType} data for test id ${testId} in <file> storage`);
204
- return null;
216
+ return '';
205
217
  } catch (e) {
206
218
  // there could be no data, ignore
207
219
  }
208
- return null;
220
+ return '';
209
221
  }
210
222
 
211
223
  _putDataToGlobalVar(data, testId) {
@@ -238,7 +250,6 @@ module.exports.DataStorage = DataStorage;
238
250
  // TODO: use .env
239
251
  // TODO: ability to intercept multiple loggers (upd: no need)
240
252
 
241
-
242
253
  /* Cypress
243
254
  Parallelization is only available if using Cypress Dashboard??
244
255
  */
@@ -10,24 +10,42 @@ class JavaAdapter extends Adapter {
10
10
 
11
11
  formatTest(t) {
12
12
  const fileParts = t.suite_title.split('.')
13
- const example = t.title.match(/\[(.*)\]/)?.[1];
14
- if (example) t.example = { "#": example }
15
13
 
16
14
  t.file = namespaceToFileName(t.suite_title);
17
15
  t.title = t.title.split('(')[0];
16
+
17
+ // detect params
18
+ const paramMatches = t.title.match(/\[(.*?)\]/g);
19
+
20
+ if (paramMatches) {
21
+ const params = paramMatches.map((_match, index) => `param${index + 1}`);
22
+ if (params.length === 1) params[0] = 'param';
23
+ let paramIndex = 0;
24
+
25
+ t.title = t.title.replace(/: \[(.*?)\]/g, () => {
26
+ if (params.length < 2) return `\${param}`
27
+ const paramName = params[paramIndex] || `param${paramIndex + 1}`;
28
+ paramIndex++;
29
+ return `\${${paramName}}`;
30
+ });
31
+ const example = {};
32
+ paramMatches.forEach((match, index) => { example[params[index]] = match.replace(/[[\]]/g, '') });
33
+ t.example = example;
34
+ }
35
+
18
36
  t.suite_title = fileParts[fileParts.length - 1].replace(/\$/g, ' | ')
19
37
  return t;
20
38
  }
21
39
 
22
- formatStack(t) {
23
- const stack = super.formatStack(t);
40
+ // formatStack(t) {
41
+ // const stack = super.formatStack(t);
24
42
 
25
- const file = t.suite_title.split('.');
43
+ // const file = t.suite_title.split('.');
26
44
 
27
- const fileLine = `at .*${file[file.length - 1]}\.java:(\\d+)` // eslint-disable-line no-useless-escape
28
- const regexp = new RegExp(fileLine,"g")
29
- return stack.replace(regexp, `${this.opts.javaTests}${path.sep}${namespaceToFileName(t.suite_title)}:$1:`);
30
- }
45
+ // const fileLine = `at .*${file[file.length - 1]}\.java:(\\d+)` // eslint-disable-line no-useless-escape
46
+ // const regexp = new RegExp(fileLine,"g")
47
+ // return stack.replace(regexp, `${this.opts.javaTests}${path.sep}${namespaceToFileName(t.suite_title)}:$1:`);
48
+ // }
31
49
  }
32
50
 
33
51
  function namespaceToFileName(fileName) {
package/lib/logger.js CHANGED
@@ -53,6 +53,20 @@ class Logger {
53
53
  }
54
54
  },
55
55
  };
56
+
57
+ // add beforeEach hook for mocha. it does not override existing hook, just add new one
58
+ try {
59
+ // @ts-ignore
60
+ if (!beforeEach) return;
61
+ // @ts-ignore
62
+ beforeEach(function () {
63
+ if (this.currentTest?.__mocha_id__) {
64
+ global.testTitle = this.currentTest.fullTitle();
65
+ }
66
+ });
67
+ } catch (e) {
68
+ // ignore
69
+ }
56
70
  }
57
71
 
58
72
  /**
@@ -115,6 +129,10 @@ class Logger {
115
129
  _log(strings, ...args) {
116
130
  // entity which is used to define testId
117
131
  let context = null;
132
+
133
+ // get testId for mocha
134
+ context = global.testTitle ?? null;
135
+
118
136
  // last argument could contain testId
119
137
  const testId = this._helpers.parseLastArgToGetTestId(...args);
120
138
  if (testId) {
@@ -152,7 +170,7 @@ class Logger {
152
170
  }
153
171
 
154
172
  /**
155
- * This function is a wrapper for all logging methods (not to repeat the same code)
173
+ * This function is a wrapper for each logging methods (log, warn, error etc) (not to repeat the same code)
156
174
  * @param {*} argsArray
157
175
  * @param {*} level
158
176
  * @returns
@@ -162,6 +180,10 @@ class Logger {
162
180
 
163
181
  // entity which is used to define testId
164
182
  let context = null;
183
+
184
+ // get context for mocha
185
+ context = global.testTitle ?? null;
186
+
165
187
  // last argument could contain testId
166
188
  const testId = this._helpers.parseLastArgToGetTestId(...argsArray);
167
189
  if (testId) {
@@ -299,3 +321,41 @@ module.exports = logger;
299
321
 
300
322
  // TODO: parse passed arguments as {level: 'str', message: 'str'} because some loggers use such syntax;
301
323
  // upd: did not face such loggers, but still could be useful
324
+
325
+ /* Cypress
326
+ There is no listener like "after:test" in cypress, only "after:spec" is available.
327
+ Thus, cannot separate logs even when I gather them (because I don't know when the test is done, just know about suite).
328
+ Also there is no easy way to access the message from cy.log() function.
329
+ (Using testomatio logger – logger.log() is not convenient because Cypress chains its commands,
330
+ thus such command will interrupt the chain.)
331
+
332
+ (Could not implement intercepting of cy.log('message'));
333
+ I found the only ability to get any logs using .task('log', 'message') (this is custom, not default cypress command)
334
+ and then intercept it with:
335
+ on('task', {
336
+ log (message) {
337
+ console.log(message)
338
+ return null
339
+ }
340
+ })
341
+
342
+ but:
343
+ 1) it does not solve problem with getting current running testId;
344
+ 2) leads to warning "Warning: Multiple attempts to register the following task(s):".)
345
+
346
+ My way to get test id:
347
+ add cypress command to save test title to file)):
348
+ Cypress.Commands.add('writeTestTitleToFile', () => {
349
+ const testTitle = cy.state('runnable').title;
350
+ cy.writeFile('testomatio_test_title', testTitle);
351
+ });
352
+
353
+ Finally, in the test it will look like:
354
+ cy
355
+ .writeTestTitleToFile() // <<<
356
+ .task('log', 'This is a log message from the test') // <<<
357
+
358
+ .get('element)
359
+ .type('text')
360
+ .click()
361
+ */
package/lib/pipe/misc.js CHANGED
@@ -41,7 +41,7 @@ function generateFilterRequestParams(params) {
41
41
  return {
42
42
  params: {
43
43
  type,
44
- id,
44
+ id: encodeURIComponent(id),
45
45
  api_key: apiKey
46
46
  },
47
47
  responseType: "json"
@@ -76,15 +76,16 @@ function updateFilterType(type) {
76
76
  const filterTypes = [
77
77
  "tag-name",
78
78
  "plan-id",
79
- "label-name"
79
+ "label",
80
+ "jira-ticket",
80
81
  ];
81
82
 
82
83
  const filterApi = [
83
84
  "tag",
84
85
  "plan",
85
86
  "label",
86
- // "issue", //TODO: WIP
87
- // "jira" //TODO: WIP
87
+ "jira",
88
+ // "ims-issue", //TODO: WIP
88
89
  ];
89
90
 
90
91
  if (!filterTypes.includes(typeLowerCase)) {
package/lib/util.js CHANGED
@@ -201,6 +201,51 @@ const foundedTestLog = (app, tests) => {
201
201
  : console.log(app, `✅ We found ${n} tests!`);
202
202
  }
203
203
 
204
+ const humanize = (text) => {
205
+ text = decamelize(text);
206
+ return text.replace(/_./g, match => ` ${ match.charAt(1).toUpperCase()}`)
207
+ .trim()
208
+ .replace(/^(.)|\s(.)/g, ($1) => $1.toUpperCase()).trim()
209
+ .replace(/\sA\s/g, ' a ') // replace a|the
210
+ .replace(/\sThe\s/g, ' the ') // replace a|the
211
+ .replace(/^Test\s/, '')
212
+ .replace(/^Should\s/, '')
213
+ }
214
+
215
+ /**
216
+ * From https://github.com/sindresorhus/decamelize/blob/main/index.js
217
+ * @param {*} text
218
+ * @returns
219
+ */
220
+ const decamelize = (text) => {
221
+ const separator = '_';
222
+ const replacement = `$1${separator}$2`;
223
+
224
+ // Split lowercase sequences followed by uppercase character.
225
+ // `dataForUSACounties` → `data_For_USACounties`
226
+ // `myURLstring → `my_URLstring`
227
+ let decamelized = text.replace(
228
+ /([\p{Lowercase_Letter}\d])(\p{Uppercase_Letter})/gu,
229
+ replacement,
230
+ );
231
+
232
+ // Lowercase all single uppercase characters. As we
233
+ // want to preserve uppercase sequences, we cannot
234
+ // simply lowercase the separated string at the end.
235
+ // `data_For_USACounties` → `data_for_USACounties`
236
+ decamelized = decamelized.replace(
237
+ /((?<![\p{Uppercase_Letter}\d])[\p{Uppercase_Letter}\d](?![\p{Uppercase_Letter}\d]))/gu,
238
+ $0 => $0.toLowerCase(),
239
+ );
240
+
241
+ // Remaining uppercase sequences will be separated from lowercase sequences.
242
+ // `data_For_USACounties` → `data_for_USA_counties`
243
+ return decamelized.replace(
244
+ /(\p{Uppercase_Letter}+)(\p{Uppercase_Letter}\p{Lowercase_Letter}+)/gu,
245
+ (_, $1, $2) => $1 + separator + $2.toLowerCase(),
246
+ );
247
+ };
248
+
204
249
  module.exports = {
205
250
  isSameTest,
206
251
  fetchSourceCode,
@@ -213,5 +258,6 @@ module.exports = {
213
258
  ansiRegExp,
214
259
  parseTest,
215
260
  parseSuite,
261
+ humanize,
216
262
  foundedTestLog
217
263
  }
package/lib/xmlReader.js CHANGED
@@ -8,7 +8,7 @@ const { fetchFilesFromStackTrace, fetchSourceCode, fetchSourceCodeFromStackTrace
8
8
  const upload = require('./fileUploader');
9
9
  const pipesFactory = require('./pipe');
10
10
  const adapterFactory = require('./junit-adapter');
11
-
11
+ const { humanize } = require('./util')
12
12
 
13
13
  const TESTOMATIO_URL = process.env.TESTOMATIO_URL || "https://app.testomat.io";
14
14
  const TESTOMATIO = process.env.TESTOMATIO; // key?
@@ -35,7 +35,7 @@ class XmlReader {
35
35
  group_title: TESTOMATIO_RUNGROUP_TITLE,
36
36
  };
37
37
  this.runId = opts.runId || TESTOMATIO_RUN;
38
- this.adapter = adapterFactory(null, opts)
38
+ this.adapter = adapterFactory(opts.lang?.toLowerCase(), opts)
39
39
  if (!this.adapter) throw new Error('XML adapter for this format not found');
40
40
 
41
41
  this.opts = opts || {};
@@ -53,8 +53,12 @@ class XmlReader {
53
53
  }
54
54
 
55
55
  connectAdapter() {
56
- if (this.opts.javaTests) return adapterFactory('java', this.opts);
57
- return adapterFactory(this.stats.language, this.opts);
56
+ if (this.opts.javaTests) {
57
+ this.adapter = adapterFactory('java', this.opts);
58
+ return this.adapter;
59
+ }
60
+ this.adapter = adapterFactory(this.stats.language, this.opts);
61
+ return this.adapter;
58
62
  }
59
63
 
60
64
  parse(fileName) {
@@ -70,6 +74,8 @@ class XmlReader {
70
74
  return this.processTRX(jsonResult);
71
75
  } else if (jsonResult['test-run']) {
72
76
  return this.processNUnit(jsonResult['test-run']);
77
+ } else if (jsonResult.assemblies) {
78
+ return this.processXUnit(jsonResult.assemblies);
73
79
  } else {
74
80
  console.log(jsonResult)
75
81
  throw new Error("Format can't be parsed")
@@ -190,6 +196,79 @@ class XmlReader {
190
196
  };
191
197
  }
192
198
 
199
+ processXUnit(assemblies) {
200
+ const tests = [];
201
+
202
+ assemblies = Array.isArray(assemblies.assembly) ? assemblies.assembly : [assemblies.assembly];
203
+
204
+ assemblies.forEach(assembly => {
205
+ const { collection } = assembly;
206
+
207
+ const suites = Array.isArray(collection) ? collection : [collection];
208
+
209
+ suites.forEach(suite => {
210
+ const { test } = suite;
211
+ if (!test) return;
212
+ const cases = Array.isArray(test) ? test : [test];
213
+ cases.forEach(testCase => {
214
+ const { type, time, result } = testCase;
215
+
216
+ let message = '';
217
+ let stack = '';
218
+
219
+ if (testCase.failure) {
220
+ message = testCase.failure.message;
221
+ stack = testCase.failure['stack-trace']
222
+ }
223
+ if (testCase.reason) {
224
+ message = testCase.reason.message;
225
+ }
226
+
227
+ let status = STATUS.PASSED;
228
+ if (result === 'Pass') status = STATUS.PASSED;
229
+ if (result === 'Fail') status = STATUS.FAILED;
230
+ if (result === 'Skip') status = STATUS.SKIPPED;
231
+
232
+ const pathParts = type.split('.');
233
+ const suite_title = pathParts[pathParts.length - 1];
234
+ const file = pathParts.slice(0, -1).join('/');
235
+ const title = testCase.method || testCase.name.split('.').pop();
236
+ const run_time = parseFloat(time) * 1000;
237
+
238
+ tests.push({
239
+ create: true,
240
+ stack,
241
+ message,
242
+ file,
243
+ status,
244
+ title,
245
+ suite_title,
246
+ run_time,
247
+ });
248
+
249
+ });
250
+ });
251
+ });
252
+
253
+ const hasFailures = tests.filter(t => t.status === STATUS.FAILED).length > 0;
254
+ const status = hasFailures ? STATUS.FAILED : STATUS.PASSED;
255
+
256
+ this.tests = tests;
257
+
258
+ debug(tests);
259
+
260
+ return {
261
+ status,
262
+ create_tests: true,
263
+ name: 'xUnit',
264
+ tests_count: tests.length,
265
+ passed_count: tests.filter(t => t.status === STATUS.PASSED).length,
266
+ failed_count: tests.filter(t => t.status === STATUS.FAILED).length,
267
+ skipped_count: tests.filter(t => t.status === STATUS.SKIPPED).length,
268
+ tests,
269
+ };
270
+ }
271
+
193
272
  calculateStats() {
194
273
  this.stats = {
195
274
  ...this.stats,
@@ -241,18 +320,7 @@ class XmlReader {
241
320
 
242
321
  this.adapter.formatTest(t)
243
322
 
244
- t.title = t.title
245
- // insert a space before all caps
246
- .replace(/([A-Z])/g, ' $1')
247
- // _ chars to spaces
248
- .replace(/_/g, ' ')
249
- // uppercase the first character
250
- .replace(/^(.)|\s(.)/g, $1 => $1.toLowerCase())
251
-
252
- // remove standard prefixes
253
- .replace(/^test\s/, '')
254
- .replace(/^should\s/, '')
255
- .trim();
323
+ t.title = humanize(t.title);
256
324
  });
257
325
  }
258
326
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@testomatio/reporter",
3
- "version": "1.0.9-beta.1",
3
+ "version": "1.0.9-beta.2",
4
4
  "description": "Testomatio Reporter Client",
5
5
  "main": "./lib/reporter.js",
6
6
  "typings": "typings/index.d.ts",