@testomatio/reporter 1.6.13 → 2.0.1-beta-esm

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 (142) hide show
  1. package/lib/adapter/codecept.d.ts +2 -0
  2. package/lib/adapter/codecept.js +295 -335
  3. package/lib/adapter/cucumber/current.d.ts +14 -0
  4. package/lib/adapter/cucumber/current.js +195 -203
  5. package/lib/adapter/cucumber/legacy.d.ts +0 -0
  6. package/lib/adapter/cucumber/legacy.js +130 -155
  7. package/lib/adapter/cucumber.d.ts +2 -0
  8. package/lib/adapter/cucumber.js +5 -16
  9. package/lib/adapter/cypress-plugin/index.d.ts +2 -0
  10. package/lib/adapter/cypress-plugin/index.js +93 -105
  11. package/lib/adapter/jasmine.d.ts +11 -0
  12. package/lib/adapter/jasmine.js +54 -53
  13. package/lib/adapter/jest.d.ts +13 -0
  14. package/lib/adapter/jest.js +97 -99
  15. package/lib/adapter/mocha.d.ts +2 -0
  16. package/lib/adapter/mocha.js +112 -140
  17. package/lib/adapter/playwright.d.ts +14 -0
  18. package/lib/adapter/playwright.js +195 -231
  19. package/lib/adapter/vitest.d.ts +35 -0
  20. package/lib/adapter/vitest.js +150 -149
  21. package/lib/adapter/webdriver.d.ts +24 -0
  22. package/lib/adapter/webdriver.js +134 -119
  23. package/lib/bin/cli.d.ts +2 -0
  24. package/lib/bin/cli.js +164 -211
  25. package/lib/bin/reportXml.d.ts +2 -0
  26. package/lib/bin/reportXml.js +49 -52
  27. package/lib/bin/startTest.d.ts +2 -0
  28. package/lib/bin/startTest.js +82 -95
  29. package/lib/bin/uploadArtifacts.d.ts +2 -0
  30. package/lib/bin/uploadArtifacts.js +55 -61
  31. package/lib/client.d.ts +76 -0
  32. package/lib/client.js +411 -465
  33. package/lib/config.d.ts +1 -0
  34. package/lib/config.js +16 -21
  35. package/lib/constants.d.ts +25 -0
  36. package/lib/constants.js +50 -44
  37. package/lib/data-storage.d.ts +34 -0
  38. package/lib/data-storage.js +206 -188
  39. package/lib/junit-adapter/adapter.d.ts +9 -0
  40. package/lib/junit-adapter/adapter.js +17 -20
  41. package/lib/junit-adapter/csharp.d.ts +4 -0
  42. package/lib/junit-adapter/csharp.js +18 -14
  43. package/lib/junit-adapter/index.d.ts +3 -0
  44. package/lib/junit-adapter/index.js +27 -25
  45. package/lib/junit-adapter/java.d.ts +5 -0
  46. package/lib/junit-adapter/java.js +41 -53
  47. package/lib/junit-adapter/javascript.d.ts +4 -0
  48. package/lib/junit-adapter/javascript.js +30 -27
  49. package/lib/junit-adapter/python.d.ts +5 -0
  50. package/lib/junit-adapter/python.js +38 -37
  51. package/lib/junit-adapter/ruby.d.ts +4 -0
  52. package/lib/junit-adapter/ruby.js +11 -8
  53. package/lib/output.d.ts +11 -0
  54. package/lib/output.js +44 -52
  55. package/lib/package.json +3 -0
  56. package/lib/pipe/bitbucket.d.ts +23 -0
  57. package/lib/pipe/bitbucket.js +210 -229
  58. package/lib/pipe/csv.d.ts +47 -0
  59. package/lib/pipe/csv.js +113 -126
  60. package/lib/pipe/debug.d.ts +29 -0
  61. package/lib/pipe/debug.js +104 -99
  62. package/lib/pipe/github.d.ts +30 -0
  63. package/lib/pipe/github.js +186 -213
  64. package/lib/pipe/gitlab.d.ts +23 -0
  65. package/lib/pipe/gitlab.js +166 -207
  66. package/lib/pipe/html.d.ts +34 -0
  67. package/lib/pipe/html.js +260 -319
  68. package/lib/pipe/index.d.ts +1 -0
  69. package/lib/pipe/index.js +84 -66
  70. package/lib/pipe/testomatio.d.ts +70 -0
  71. package/lib/pipe/testomatio.js +413 -462
  72. package/lib/reporter-functions.d.ts +34 -0
  73. package/lib/reporter-functions.js +28 -26
  74. package/lib/reporter.d.ts +232 -0
  75. package/lib/reporter.js +34 -29
  76. package/lib/services/artifacts.d.ts +33 -0
  77. package/lib/services/artifacts.js +55 -51
  78. package/lib/services/index.d.ts +9 -0
  79. package/lib/services/index.js +14 -12
  80. package/lib/services/key-values.d.ts +27 -0
  81. package/lib/services/key-values.js +56 -53
  82. package/lib/services/logger.d.ts +64 -0
  83. package/lib/services/logger.js +227 -245
  84. package/lib/template/testomatio.hbs +651 -1366
  85. package/lib/uploader.d.ts +60 -0
  86. package/lib/uploader.js +291 -360
  87. package/lib/utils/pipe_utils.d.ts +41 -0
  88. package/lib/utils/pipe_utils.js +89 -85
  89. package/lib/utils/utils.d.ts +45 -0
  90. package/lib/utils/utils.js +347 -307
  91. package/lib/xmlReader.d.ts +92 -0
  92. package/lib/xmlReader.js +490 -529
  93. package/package.json +57 -15
  94. package/src/adapter/codecept.js +375 -0
  95. package/src/adapter/cucumber/current.js +228 -0
  96. package/src/adapter/cucumber/legacy.js +158 -0
  97. package/src/adapter/cucumber.js +4 -0
  98. package/src/adapter/cypress-plugin/index.js +112 -0
  99. package/src/adapter/jasmine.js +60 -0
  100. package/src/adapter/jest.js +107 -0
  101. package/src/adapter/mocha.cjs +2 -0
  102. package/src/adapter/mocha.js +157 -0
  103. package/src/adapter/playwright.js +250 -0
  104. package/src/adapter/vitest.js +183 -0
  105. package/src/adapter/webdriver.js +142 -0
  106. package/src/bin/cli.js +280 -0
  107. package/src/bin/reportXml.js +74 -0
  108. package/src/bin/startTest.js +123 -0
  109. package/src/bin/uploadArtifacts.js +90 -0
  110. package/src/client.js +504 -0
  111. package/src/config.js +30 -0
  112. package/src/constants.js +53 -0
  113. package/src/data-storage.js +204 -0
  114. package/src/junit-adapter/adapter.js +23 -0
  115. package/src/junit-adapter/csharp.js +16 -0
  116. package/src/junit-adapter/index.js +28 -0
  117. package/src/junit-adapter/java.js +58 -0
  118. package/src/junit-adapter/javascript.js +31 -0
  119. package/src/junit-adapter/python.js +42 -0
  120. package/src/junit-adapter/ruby.js +10 -0
  121. package/src/output.js +57 -0
  122. package/src/pipe/bitbucket.js +254 -0
  123. package/src/pipe/csv.js +140 -0
  124. package/src/pipe/debug.js +104 -0
  125. package/src/pipe/github.js +233 -0
  126. package/src/pipe/gitlab.js +229 -0
  127. package/src/pipe/html.js +374 -0
  128. package/src/pipe/index.js +71 -0
  129. package/src/pipe/testomatio.js +503 -0
  130. package/src/reporter-functions.js +55 -0
  131. package/src/reporter.cjs_decprecated +21 -0
  132. package/src/reporter.js +33 -0
  133. package/src/services/artifacts.js +59 -0
  134. package/src/services/index.js +13 -0
  135. package/src/services/key-values.js +59 -0
  136. package/src/services/logger.js +316 -0
  137. package/src/template/emptyData.svg +23 -0
  138. package/src/template/testomatio.hbs +706 -0
  139. package/src/uploader.js +371 -0
  140. package/src/utils/pipe_utils.js +119 -0
  141. package/src/utils/utils.js +383 -0
  142. package/src/xmlReader.js +562 -0
@@ -0,0 +1,383 @@
1
+ import { URL } from 'url';
2
+ import path, { sep, basename } from 'path';
3
+ import pc from 'picocolors';
4
+ import fs from 'fs';
5
+ import isValid from 'is-valid-path';
6
+ import createDebugMessages from 'debug';
7
+ import os from 'os';
8
+
9
+ const debug = createDebugMessages('@testomatio/reporter:util');
10
+
11
+ /**
12
+ * @param {String} testTitle - Test title
13
+ *
14
+ * @returns {String|null} testId
15
+ */
16
+ const getTestomatIdFromTestTitle = testTitle => {
17
+ if (!testTitle) return null;
18
+
19
+ const captures = testTitle.match(/@T[\w\d]{8}/);
20
+
21
+ if (captures) {
22
+ return captures[0];
23
+ }
24
+
25
+ return null;
26
+ };
27
+
28
+ /**
29
+ * @param {String} suiteTitle - suite title
30
+ *
31
+ * @returns {String|null} suiteId
32
+ */
33
+ const parseSuite = suiteTitle => {
34
+ const captures = suiteTitle.match(/@S[\w\d]{8}/);
35
+ if (captures) {
36
+ return captures[1];
37
+ }
38
+
39
+ return null;
40
+ };
41
+
42
+ const ansiRegExp = () => {
43
+ const pattern = [
44
+ '[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)',
45
+ '(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))',
46
+ ].join('|');
47
+
48
+ return new RegExp(pattern, 'g');
49
+ };
50
+
51
+ const isValidUrl = s => {
52
+ try {
53
+ // eslint-disable-next-line no-new
54
+ new URL(s);
55
+ return true;
56
+ } catch (err) {
57
+ return false;
58
+ }
59
+ };
60
+
61
+ const fileMatchRegex = /file:(\/\/?[^:\s]+?\.(png|avi|webm|jpg|html|txt))/gi;
62
+
63
+ const fetchFilesFromStackTrace = (stack = '') => {
64
+ const files = Array.from(stack.matchAll(fileMatchRegex))
65
+ .map(f => f[1].trim())
66
+ .map(f => (f.startsWith('//') ? f.substring(1) : f));
67
+
68
+ debug('Found files in stack trace: ', files);
69
+
70
+ return files.filter(f => {
71
+ const isFile = fs.existsSync(f);
72
+ if (!isFile) debug('File %s could not be found and uploaded as artifact', f);
73
+ return isFile;
74
+ });
75
+ };
76
+
77
+ const fetchSourceCodeFromStackTrace = (stack = '') => {
78
+ const stackLines = stack
79
+ .split('\n')
80
+ .filter(l => l.includes(':'))
81
+ // .map(l => l.match(/\[(.*?)\]/)?.[1] || l) // minitest format
82
+ // .map(l => l.split(':')[0])
83
+ .map(l => l.trim())
84
+ .map(l => l.split(' ').find(p => p.includes(':')) || '')
85
+ .filter(l => isValid(l?.split(':')[0]))
86
+
87
+ // // filter out 3rd party libs
88
+ .filter(l => !l?.includes(`vendor${sep}`))
89
+ .filter(l => !l?.includes(`node_modules${sep}`))
90
+ .filter(l => fs.existsSync(l.split(':')[0]))
91
+ .filter(l => fs.lstatSync(l.split(':')[0]).isFile());
92
+
93
+ if (!stackLines.length) return '';
94
+
95
+ const [file, line] = stackLines[0].split(':');
96
+
97
+ const prepend = 3;
98
+ const source = fetchSourceCode(fs.readFileSync(file).toString(), { line, prepend, limit: 7 });
99
+
100
+ if (!source) return '';
101
+
102
+ return source
103
+ .split('\n')
104
+ .map((l, i) => {
105
+ if (i === prepend) return `${line} > ${pc.bold(l)}`;
106
+ return `${+line - prepend + i} | ${l}`;
107
+ })
108
+ .join('\n');
109
+ };
110
+
111
+ const TEST_ID_REGEX = /@T([\w\d]{8})/;
112
+
113
+ const fetchIdFromCode = (code, opts = {}) => {
114
+ const comments = code
115
+ .split('\n')
116
+ .map(l => l.trim())
117
+ .filter(l => {
118
+ switch (opts.lang) {
119
+ case 'ruby':
120
+ case 'python':
121
+ return l.startsWith('# ');
122
+ default:
123
+ return l.startsWith('// ');
124
+ }
125
+ });
126
+
127
+ return comments.find(c => c.match(TEST_ID_REGEX))?.match(TEST_ID_REGEX)?.[1];
128
+ };
129
+
130
+ const fetchIdFromOutput = output => {
131
+ const lines = output
132
+ .split('\n')
133
+ .map(l => l.trim())
134
+ .filter(l => l.startsWith('tid://'));
135
+
136
+ return lines.find(c => c.match(TEST_ID_REGEX))?.match(TEST_ID_REGEX)?.[1];
137
+ };
138
+
139
+ const fetchSourceCode = (contents, opts = {}) => {
140
+ if (!opts.title && !opts.line) return '';
141
+
142
+ // code fragment is 20 lines
143
+ const limit = opts.limit || 50;
144
+ let lineIndex;
145
+ if (opts.line) lineIndex = opts.line - 1;
146
+ const lines = contents.split('\n');
147
+
148
+ // remove special chars from title
149
+ if (!lineIndex && opts.title) {
150
+ const title = opts.title.replace(/[([@].*/g, '');
151
+
152
+ if (opts.lang === 'java') {
153
+ lineIndex = lines.findIndex(l => l.includes(`test${title}`));
154
+ if (lineIndex === -1) lineIndex = lines.findIndex(l => l.includes(`@DisplayName("${title}`));
155
+ if (lineIndex === -1) lineIndex = lines.findIndex(l => l.includes(`public void ${title}`));
156
+ if (lineIndex === -1) lineIndex = lines.findIndex(l => l.includes(`${title}(`));
157
+ } else {
158
+ lineIndex = lines.findIndex(l => l.includes(title));
159
+ }
160
+ }
161
+
162
+ if (opts.prepend) {
163
+ lineIndex -= opts.prepend;
164
+ }
165
+
166
+ if (lineIndex) {
167
+ const result = [];
168
+ for (let i = lineIndex; i < lineIndex + limit; i++) {
169
+ if (lines[i] === undefined) continue;
170
+
171
+ if (i > lineIndex + 2 && !opts.prepend) {
172
+ // annotation
173
+ if (opts.lang === 'php' && lines[i].trim().startsWith('#[')) break;
174
+ if (opts.lang === 'php' && lines[i].includes(' private function ')) break;
175
+ if (opts.lang === 'php' && lines[i].includes(' protected function ')) break;
176
+ if (opts.lang === 'php' && lines[i].includes(' public function ')) break;
177
+ if (opts.lang === 'python' && lines[i].trim().match(/^@\w+/)) break;
178
+ if (opts.lang === 'python' && lines[i].includes(' def ')) break;
179
+ if (opts.lang === 'ruby' && lines[i].includes(' def ')) break;
180
+ if (opts.lang === 'ruby' && lines[i].includes(' test ')) break;
181
+ if (opts.lang === 'ruby' && lines[i].includes(' it ')) break;
182
+ if (opts.lang === 'ruby' && lines[i].includes(' specify ')) break;
183
+ if (opts.lang === 'ruby' && lines[i].includes(' context ')) break;
184
+ if (opts.lang === 'ts' && lines[i].includes(' it(')) break;
185
+ if (opts.lang === 'ts' && lines[i].includes(' test(')) break;
186
+ if (opts.lang === 'js' && lines[i].includes(' it(')) break;
187
+ if (opts.lang === 'js' && lines[i].includes(' test(')) break;
188
+ if (opts.lang === 'java' && lines[i].trim().match(/^@\w+/)) break;
189
+ if (opts.lang === 'java' && lines[i].includes(' public void ')) break;
190
+ if (opts.lang === 'java' && lines[i].includes(' class ')) break;
191
+ }
192
+ result.push(lines[i]);
193
+ }
194
+ return result.join('\n');
195
+ }
196
+ };
197
+
198
+ const isSameTest = (test, t) =>
199
+ typeof t === 'object' &&
200
+ typeof test === 'object' &&
201
+ t.title === test.title &&
202
+ t.suite_title === test.suite_title &&
203
+ Object.values(t.example || {}) === Object.values(test.example || {}) &&
204
+ t.test_id === test.test_id;
205
+
206
+ const getCurrentDateTime = () => {
207
+ const today = new Date();
208
+
209
+ return `${today.getFullYear()}_${
210
+ today.getMonth() + 1
211
+ }_${today.getDate()}_${today.getHours()}_${today.getMinutes()}_${today.getSeconds()}`;
212
+ };
213
+
214
+ /**
215
+ * @param {Object} test - Test adapter object
216
+ *
217
+ * @returns {String|null} testInfo as one string
218
+ */
219
+ const specificTestInfo = test => {
220
+ // TODO: afterEach has another context.... need to add specific handler, maybe...
221
+ if (test?.title && test?.file) {
222
+ return `${basename(test.file).split('.').join('#')}#${test.title.split(' ').join('#')}`;
223
+ }
224
+
225
+ return null;
226
+ };
227
+
228
+ const fileSystem = {
229
+ createDir(dirPath) {
230
+ if (!fs.existsSync(dirPath)) {
231
+ fs.mkdirSync(dirPath, { recursive: true });
232
+ debug('Created dir: ', dirPath);
233
+ }
234
+ },
235
+ clearDir(dirPath) {
236
+ if (fs.existsSync(dirPath)) {
237
+ fs.rmSync(dirPath, { recursive: true });
238
+ debug(`Dir ${dirPath} was deleted`);
239
+ } else {
240
+ debug(`Trying to delete ${dirPath} but it doesn't exist`);
241
+ }
242
+ },
243
+ };
244
+
245
+ const foundedTestLog = (app, tests) => {
246
+ const n = tests.length;
247
+
248
+ return n === 1 ? console.log(app, `✅ We found one test!`) : console.log(app, `✅ We found ${n} tests!`);
249
+ };
250
+
251
+ const humanize = text => {
252
+ // if there are no spaces, decamelize
253
+ if (!text.trim().includes(' ')) text = decamelize(text);
254
+
255
+ return text
256
+ .replace(/_./g, match => ` ${match.charAt(1).toUpperCase()}`)
257
+ .trim()
258
+ .replace(/^(.)|\s(.)/g, $1 => $1.toUpperCase())
259
+ .trim()
260
+ .replace(/\sA\s/g, ' a ') // replace a|the
261
+ .replace(/\sThe\s/g, ' the ') // replace a|the
262
+ .replace(/^Test\s/, '')
263
+ .replace(/^Should\s/, '');
264
+ };
265
+
266
+ /**
267
+ * From https://github.com/sindresorhus/decamelize/blob/main/index.js
268
+ * @param {*} text
269
+ * @returns
270
+ */
271
+ const decamelize = text => {
272
+ const separator = '_';
273
+ const replacement = `$1${separator}$2`;
274
+
275
+ // Split lowercase sequences followed by uppercase character.
276
+ // `dataForUSACounties` → `data_For_USACounties`
277
+ // `myURLstring → `my_URLstring`
278
+ let decamelized = text.replace(/([\p{Lowercase_Letter}\d])(\p{Uppercase_Letter})/gu, replacement);
279
+
280
+ // Lowercase all single uppercase characters. As we
281
+ // want to preserve uppercase sequences, we cannot
282
+ // simply lowercase the separated string at the end.
283
+ // `data_For_USACounties` → `data_for_USACounties`
284
+ decamelized = decamelized.replace(
285
+ /((?<![\p{Uppercase_Letter}\d])[\p{Uppercase_Letter}\d](?![\p{Uppercase_Letter}\d]))/gu,
286
+ $0 => $0.toLowerCase(),
287
+ );
288
+
289
+ // Remaining uppercase sequences will be separated from lowercase sequences.
290
+ // `data_For_USACounties` → `data_for_USA_counties`
291
+ return decamelized.replace(
292
+ /(\p{Uppercase_Letter}+)(\p{Uppercase_Letter}\p{Lowercase_Letter}+)/gu,
293
+ (_, $1, $2) => $1 + separator + $2.toLowerCase(),
294
+ );
295
+ };
296
+
297
+ /**
298
+ * Used to remove color codes
299
+ * @param {*} input
300
+ * @returns
301
+ */
302
+ function removeColorCodes(input) {
303
+ // eslint-disable-next-line no-control-regex
304
+ return input.replace(/\x1b\[[0-9;]*m/g, '');
305
+ }
306
+
307
+ const testRunnerHelper = {
308
+ // for Jest
309
+ getNameOfCurrentlyRunningTest: () => {
310
+ if (global.testomatioTestTitle) return global.testomatioTestTitle;
311
+
312
+ if (!process.env.JEST_WORKER_ID) return null;
313
+ try {
314
+ // TODO: expect?.getState()?.testPath + ' ' + expect?.getState()?.currentTestName
315
+ // @ts-expect-error "expect" could only be defined inside Jest environement (forbidden to import it outside)
316
+ // eslint-disable-next-line no-undef
317
+ return expect?.getState()?.currentTestName;
318
+ } catch (e) {
319
+ return null;
320
+ }
321
+ },
322
+ };
323
+
324
+ function storeRunId(runId) {
325
+ if (!runId || runId === 'undefined') return;
326
+ const filePath = path.join(os.tmpdir(), `testomatio.latest.run`);
327
+ fs.writeFileSync(filePath, runId);
328
+ }
329
+
330
+ function readLatestRunId() {
331
+ try {
332
+ const filePath = path.join(os.tmpdir(), `testomatio.latest.run`);
333
+ const stats = fs.statSync(filePath);
334
+ const diff = +new Date() - +stats.mtime;
335
+ const diffHours = diff / 1000 / 60 / 60;
336
+ if (diffHours > 1) return;
337
+
338
+ return fs.readFileSync(filePath)?.toString()?.trim();
339
+ } catch (e) {
340
+ return null;
341
+ }
342
+ }
343
+
344
+ function formatStep(step, shift = 0) {
345
+ const prefix = ' '.repeat(shift);
346
+
347
+ const lines = [];
348
+
349
+ if (step.error) {
350
+ lines.push(`${prefix}${pc.red(step.title)} ${pc.gray(`${step.duration}ms`)}`);
351
+ } else {
352
+ lines.push(`${prefix}${step.title} ${pc.gray(`${step.duration}ms`)}`);
353
+ }
354
+
355
+ for (const child of step.steps || []) {
356
+ lines.push(...formatStep(child, shift + 2));
357
+ }
358
+
359
+ return lines;
360
+ }
361
+
362
+ export {
363
+ ansiRegExp,
364
+ isSameTest,
365
+ fetchSourceCode,
366
+ fetchSourceCodeFromStackTrace,
367
+ fetchIdFromCode,
368
+ fetchIdFromOutput,
369
+ fetchFilesFromStackTrace,
370
+ fileSystem,
371
+ foundedTestLog,
372
+ formatStep,
373
+ getCurrentDateTime,
374
+ getTestomatIdFromTestTitle,
375
+ humanize,
376
+ isValidUrl,
377
+ parseSuite,
378
+ readLatestRunId,
379
+ removeColorCodes,
380
+ specificTestInfo,
381
+ storeRunId,
382
+ testRunnerHelper,
383
+ };