@testomatio/reporter 2.0.1-beta.3 → 2.0.1-beta.5-timestamp

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