@testomatio/reporter 2.0.0-beta-esm → 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 (115) hide show
  1. package/lib/adapter/codecept.d.ts +2 -0
  2. package/lib/adapter/codecept.js +31 -24
  3. package/lib/adapter/cucumber/current.d.ts +14 -0
  4. package/lib/adapter/cucumber/legacy.d.ts +0 -0
  5. package/lib/adapter/cucumber.d.ts +2 -0
  6. package/lib/adapter/cypress-plugin/index.d.ts +2 -0
  7. package/lib/adapter/cypress-plugin/index.js +11 -9
  8. package/lib/adapter/jasmine.d.ts +11 -0
  9. package/lib/adapter/jest.d.ts +13 -0
  10. package/lib/adapter/mocha.d.ts +2 -0
  11. package/lib/adapter/mocha.js +4 -3
  12. package/lib/adapter/playwright.d.ts +14 -0
  13. package/lib/adapter/playwright.js +58 -33
  14. package/lib/adapter/vitest.d.ts +35 -0
  15. package/lib/adapter/vitest.js +6 -6
  16. package/lib/adapter/webdriver.d.ts +24 -0
  17. package/lib/adapter/webdriver.js +34 -6
  18. package/lib/bin/cli.d.ts +2 -0
  19. package/lib/bin/cli.js +228 -0
  20. package/lib/bin/reportXml.d.ts +2 -0
  21. package/lib/bin/reportXml.js +11 -9
  22. package/lib/bin/startTest.d.ts +2 -0
  23. package/lib/bin/startTest.js +9 -5
  24. package/lib/bin/uploadArtifacts.d.ts +2 -0
  25. package/lib/bin/uploadArtifacts.js +81 -0
  26. package/lib/client.d.ts +76 -0
  27. package/lib/client.js +111 -45
  28. package/lib/config.d.ts +1 -0
  29. package/lib/constants.d.ts +25 -0
  30. package/lib/constants.js +5 -1
  31. package/lib/data-storage.d.ts +34 -0
  32. package/lib/data-storage.js +2 -2
  33. package/lib/junit-adapter/adapter.d.ts +9 -0
  34. package/lib/junit-adapter/csharp.d.ts +4 -0
  35. package/lib/junit-adapter/index.d.ts +3 -0
  36. package/lib/junit-adapter/java.d.ts +5 -0
  37. package/lib/junit-adapter/javascript.d.ts +4 -0
  38. package/lib/junit-adapter/python.d.ts +5 -0
  39. package/lib/junit-adapter/ruby.d.ts +4 -0
  40. package/lib/output.d.ts +11 -0
  41. package/lib/package.json +3 -1
  42. package/lib/pipe/bitbucket.d.ts +23 -0
  43. package/lib/pipe/bitbucket.js +2 -2
  44. package/lib/pipe/csv.d.ts +47 -0
  45. package/lib/pipe/csv.js +2 -2
  46. package/lib/pipe/debug.d.ts +29 -0
  47. package/lib/pipe/debug.js +108 -0
  48. package/lib/pipe/github.d.ts +30 -0
  49. package/lib/pipe/github.js +2 -2
  50. package/lib/pipe/gitlab.d.ts +23 -0
  51. package/lib/pipe/gitlab.js +2 -2
  52. package/lib/pipe/html.d.ts +34 -0
  53. package/lib/pipe/html.js +8 -1
  54. package/lib/pipe/index.d.ts +1 -0
  55. package/lib/pipe/index.js +3 -3
  56. package/lib/pipe/testomatio.d.ts +70 -0
  57. package/lib/pipe/testomatio.js +50 -30
  58. package/lib/reporter-functions.d.ts +34 -0
  59. package/lib/reporter-functions.js +17 -7
  60. package/lib/reporter.d.ts +232 -0
  61. package/lib/reporter.js +19 -33
  62. package/lib/services/artifacts.d.ts +33 -0
  63. package/lib/services/index.d.ts +9 -0
  64. package/lib/services/key-values.d.ts +27 -0
  65. package/lib/services/key-values.js +1 -1
  66. package/lib/services/logger.d.ts +64 -0
  67. package/lib/template/testomatio.hbs +651 -1366
  68. package/lib/uploader.d.ts +60 -0
  69. package/lib/uploader.js +312 -0
  70. package/lib/utils/pipe_utils.d.ts +41 -0
  71. package/lib/utils/pipe_utils.js +3 -5
  72. package/lib/utils/utils.d.ts +45 -0
  73. package/lib/utils/utils.js +69 -2
  74. package/lib/xmlReader.d.ts +92 -0
  75. package/lib/xmlReader.js +22 -12
  76. package/package.json +15 -9
  77. package/src/adapter/codecept.js +30 -24
  78. package/src/adapter/cypress-plugin/index.js +5 -3
  79. package/src/adapter/mocha.cjs +1 -1
  80. package/src/adapter/mocha.js +4 -3
  81. package/src/adapter/playwright.js +59 -31
  82. package/src/adapter/vitest.js +6 -6
  83. package/src/adapter/webdriver.js +41 -10
  84. package/src/bin/cli.js +280 -0
  85. package/src/bin/reportXml.js +15 -8
  86. package/src/bin/startTest.js +7 -3
  87. package/src/bin/uploadArtifacts.js +90 -0
  88. package/src/client.js +137 -56
  89. package/src/constants.js +5 -1
  90. package/src/data-storage.js +2 -2
  91. package/src/pipe/bitbucket.js +2 -2
  92. package/src/pipe/csv.js +3 -3
  93. package/src/pipe/debug.js +104 -0
  94. package/src/pipe/github.js +2 -3
  95. package/src/pipe/gitlab.js +6 -6
  96. package/src/pipe/html.js +11 -3
  97. package/src/pipe/index.js +5 -7
  98. package/src/pipe/testomatio.js +72 -67
  99. package/src/reporter-functions.js +18 -7
  100. package/src/reporter.cjs_decprecated +21 -0
  101. package/src/reporter.js +20 -11
  102. package/src/services/key-values.js +1 -1
  103. package/src/services/logger.js +4 -2
  104. package/src/template/testomatio.hbs +651 -1366
  105. package/src/uploader.js +371 -0
  106. package/src/utils/pipe_utils.js +4 -12
  107. package/src/utils/utils.js +48 -6
  108. package/src/xmlReader.js +26 -15
  109. package/lib/adapter/jasmine/jasmine.js +0 -63
  110. package/lib/adapter/mocha/mocha.js +0 -125
  111. package/lib/fileUploader.js +0 -245
  112. package/lib/utils/chalk.js +0 -10
  113. package/src/fileUploader.js +0 -307
  114. package/src/reporter.cjs +0 -22
  115. package/src/utils/chalk.js +0 -13
@@ -0,0 +1,371 @@
1
+ import createDebugMessages from 'debug';
2
+ import { S3 } from '@aws-sdk/client-s3';
3
+ import { Upload } from '@aws-sdk/lib-storage';
4
+ import fs from 'fs';
5
+ import os from 'os';
6
+ import path from 'path';
7
+ import promiseRetry from 'promise-retry';
8
+ import pc from 'picocolors';
9
+ import { APP_PREFIX } from './constants.js';
10
+ import { filesize as prettyBytes } from 'filesize';
11
+
12
+ const debug = createDebugMessages('@testomatio/reporter:file-uploader');
13
+
14
+ export class S3Uploader {
15
+ constructor() {
16
+ this.isEnabled = undefined;
17
+ this.storeEnabled = true;
18
+ this.config = undefined;
19
+
20
+ /**
21
+ * @type {{path: string, size: number}[]}
22
+ */
23
+ this.skippedUploads = [];
24
+ this.failedUploads = [];
25
+ /**
26
+ * @type {{path: string, size: number, link: string}[]}
27
+ */
28
+ this.successfulUploads = [];
29
+
30
+ this.configKeys = [
31
+ 'S3_ENDPOINT',
32
+ 'S3_REGION',
33
+ 'S3_BUCKET',
34
+ 'S3_ACCESS_KEY_ID',
35
+ 'S3_SECRET_ACCESS_KEY',
36
+ 'S3_SESSION_TOKEN',
37
+ 'S3_FORCE_PATH_STYLE',
38
+ 'TESTOMATIO_DISABLE_ARTIFACTS',
39
+ 'TESTOMATIO_PRIVATE_ARTIFACTS',
40
+ 'TESTOMATIO_ARTIFACT_MAX_SIZE_MB',
41
+ ];
42
+ }
43
+
44
+ resetConfig() {
45
+ this.config = undefined;
46
+ this.isEnabled = undefined;
47
+ }
48
+
49
+ /**
50
+ *
51
+ * @returns {Record<string, string>}
52
+ */
53
+ getConfig() {
54
+ if (this.config) return this.config;
55
+ this.config = this.configKeys.reduce((acc, key) => {
56
+ acc[key] = process.env[key];
57
+ return acc;
58
+ }, {});
59
+ return this.config;
60
+ }
61
+
62
+ getMaskedConfig() {
63
+ return Object.fromEntries(
64
+ Object.entries(this.getConfig()).map(([key, value]) => [
65
+ key,
66
+ key === 'S3_SECRET_ACCESS_KEY' || key === 'S3_ACCESS_KEY_ID' ? '***' : value,
67
+ ]),
68
+ );
69
+ }
70
+
71
+ checkEnabled() {
72
+ if (this.isEnabled !== undefined) return this.isEnabled;
73
+
74
+ const { S3_BUCKET, TESTOMATIO_DISABLE_ARTIFACTS } = this.getConfig();
75
+ if (!S3_BUCKET) debug(`Artifacts uploading is disabled because S3_BUCKET is not set`);
76
+ this.isEnabled = !!(S3_BUCKET && !TESTOMATIO_DISABLE_ARTIFACTS);
77
+
78
+ if (this.isEnabled) debug('S3 uploader is enabled');
79
+ debug(this.getMaskedConfig());
80
+
81
+ return this.isEnabled;
82
+ }
83
+
84
+ enableLogStorage() {
85
+ this.storeEnabled = true;
86
+ }
87
+
88
+ disableLogStorage() {
89
+ this.storeEnabled = false;
90
+ }
91
+
92
+ /**
93
+ *
94
+ * @param {*} Body
95
+ * @param {*} Key
96
+ * @param {{path: string, size?: number}} file
97
+ * @returns
98
+ */
99
+ async #uploadToS3(Body, Key, file) {
100
+ const { S3_BUCKET, TESTOMATIO_PRIVATE_ARTIFACTS } = this.getConfig();
101
+ const ACL = TESTOMATIO_PRIVATE_ARTIFACTS ? 'private' : 'public-read';
102
+
103
+ if (!S3_BUCKET || !Body) {
104
+ console.log(
105
+ APP_PREFIX,
106
+ pc.bold(pc.red(`Failed uploading '${Key}'. Please check S3 credentials`)),
107
+ this.getMaskedConfig(),
108
+ );
109
+ return;
110
+ }
111
+
112
+ debug('Uploading to S3:', Key);
113
+
114
+ const s3Config = this.#getS3Config();
115
+ const s3 = new S3(s3Config);
116
+ const params = {
117
+ Bucket: S3_BUCKET,
118
+ Key,
119
+ Body,
120
+ };
121
+ // disable ACL for I AM roles
122
+ if (!s3Config.credentials.sessionToken) {
123
+ params.ACL = ACL;
124
+ }
125
+
126
+ try {
127
+ const upload = new Upload({ client: s3, params });
128
+
129
+ const link = await this.getS3LocationLink(upload);
130
+ this.successfulUploads.push({ path: file.path, size: file.size, link });
131
+ debug(`📤 Uploaded artifact. File: ${file.path}, size: ${prettyBytes(file.size)}, link: ${link}`);
132
+ return link;
133
+ } catch (e) {
134
+ this.failedUploads.push({ path: file.path, size: file.size });
135
+ debug('S3 uploading error:', e);
136
+ console.log(APP_PREFIX, 'Upload failed:', e.message, '\nConfig:\n', this.getMaskedConfig());
137
+ }
138
+ }
139
+
140
+ /**
141
+ * Returns an array of uploaded files
142
+ *
143
+ * @returns {{rid: string, file: string, uploaded: boolean}[]}
144
+ */
145
+ readUploadedFiles(runId) {
146
+ const tempFilePath = this.#getFilePathWithUploadsList(runId);
147
+
148
+ debug('Reading file', tempFilePath);
149
+
150
+ if (!fs.existsSync(tempFilePath)) {
151
+ debug('File not found:', tempFilePath);
152
+ return [];
153
+ }
154
+
155
+ const stats = fs.statSync(tempFilePath);
156
+ debug('Artifacts file stats:', +stats.mtime);
157
+ debug('Current time:', +new Date());
158
+ const diff = +new Date() - +stats.mtime;
159
+ debug('Diff:', diff);
160
+ const diffHours = diff / 1000 / 60 / 60;
161
+ debug('Diff hours:', diffHours);
162
+ if (diffHours > 3) {
163
+ console.log(APP_PREFIX, "Artifacts file is too old, can't process artifacts. Please re-run the tests.");
164
+ return [];
165
+ }
166
+
167
+ const data = fs.readFileSync(tempFilePath, 'utf8');
168
+ debug('Artifacts file contents:', data);
169
+ const lines = data.split('\n').filter(Boolean);
170
+ return lines.map(line => JSON.parse(line));
171
+ }
172
+
173
+ #getFilePathWithUploadsList(runId) {
174
+ const tempFilePath = path.join(os.tmpdir(), `testomatio.run.${runId}.json`);
175
+ if (!fs.existsSync(tempFilePath)) {
176
+ debug('Creating artifacts file:', tempFilePath);
177
+ fs.writeFileSync(tempFilePath, '');
178
+ }
179
+ return tempFilePath;
180
+ }
181
+
182
+ storeUploadedFile(filePath, runId, rid, uploaded = false) {
183
+ if (!this.storeEnabled) return;
184
+
185
+ if (!filePath || !runId || !rid) return;
186
+
187
+ const tempFilePath = this.#getFilePathWithUploadsList(runId);
188
+
189
+ if (typeof filePath === 'object') {
190
+ filePath = filePath.path;
191
+ }
192
+
193
+ if (typeof filePath === 'string' && !path.isAbsolute(filePath)) {
194
+ filePath = path.join(process.cwd(), filePath);
195
+ }
196
+
197
+ const data = { rid, file: filePath, uploaded };
198
+ const jsonLine = `${JSON.stringify(data)}\n`;
199
+ fs.appendFileSync(tempFilePath, jsonLine);
200
+ }
201
+
202
+ /**
203
+ * @param {*} filePath
204
+ * @param {*} pathInS3 contains runId, rid and filename
205
+ * @returns
206
+ */
207
+ async uploadFileByPath(filePath, pathInS3) {
208
+ // sometimes artifacts uploading started before createRun function completion
209
+ this.isEnabled = this.isEnabled ?? this.checkEnabled();
210
+
211
+ const [runId, rid] = pathInS3;
212
+
213
+ if (!filePath) return;
214
+
215
+ let fileSize = null;
216
+ let fileSizeInMb = null;
217
+
218
+ try {
219
+ // file may not exist
220
+ fileSize = fs.statSync(filePath).size;
221
+ fileSizeInMb = Number((fileSize / (1024 * 1024)).toFixed(2));
222
+ } catch (e) {
223
+ debug(`File ${filePath} does not exist`);
224
+ }
225
+
226
+ if (!this.isEnabled) {
227
+ this.storeUploadedFile(filePath, runId, rid, false);
228
+ this.skippedUploads.push({ path: filePath, size: fileSize });
229
+ return;
230
+ }
231
+
232
+ const { S3_BUCKET, TESTOMATIO_ARTIFACT_MAX_SIZE_MB } = this.getConfig();
233
+
234
+ debug('Started upload', filePath, 'to', S3_BUCKET);
235
+
236
+ const isFileExist = await this.checkArtifactExistsInFileSystem(filePath, 20, 500);
237
+
238
+ if (!isFileExist) {
239
+ console.error(pc.yellow(`Artifacts file ${filePath} does not exist. Skipping...`));
240
+ return;
241
+ }
242
+
243
+ // skipping artifact only if: 1. storing to file is enabled, 2. max size is set and 3. file size exceeds the limit
244
+ if (
245
+ this.storeEnabled &&
246
+ TESTOMATIO_ARTIFACT_MAX_SIZE_MB &&
247
+ fileSizeInMb > parseFloat(TESTOMATIO_ARTIFACT_MAX_SIZE_MB)
248
+ ) {
249
+ const skippedArtifact = { path: filePath, size: fileSize };
250
+ this.storeUploadedFile(filePath, runId, rid, false);
251
+ this.skippedUploads.push(skippedArtifact);
252
+ debug(pc.yellow(`Artifacts file ${JSON.stringify(skippedArtifact)} exceeds the maximum allowed size. Skipping.`));
253
+ return;
254
+ }
255
+ debug('File:', filePath, 'exists, size:', prettyBytes(fileSize));
256
+
257
+ const fileStream = fs.createReadStream(filePath);
258
+ const Key = pathInS3.join('/');
259
+
260
+ const link = await this.#uploadToS3(fileStream, Key, { path: filePath, size: fileSize });
261
+
262
+ this.storeUploadedFile(filePath, runId, rid, !!link);
263
+
264
+ return link;
265
+ }
266
+
267
+ /**
268
+ * @param {Buffer} buffer
269
+ * @param {string[]} pathInS3
270
+ * @returns
271
+ */
272
+ async uploadFileAsBuffer(buffer, pathInS3) {
273
+ if (!this.isEnabled) return;
274
+
275
+ let Key = pathInS3.join('/');
276
+ const ext = this.#getFileExtBase64(buffer);
277
+
278
+ if (ext) {
279
+ Key = `${Key}.${ext}`;
280
+ }
281
+
282
+ return this.#uploadToS3(buffer, Key, { path: Key });
283
+ }
284
+
285
+ async checkArtifactExistsInFileSystem(filePath, attempts = 5, intervalMs = 500) {
286
+ return promiseRetry(
287
+ async (retry, number) => {
288
+ try {
289
+ fs.accessSync(filePath);
290
+ return true;
291
+ } catch (err) {
292
+ if (number === attempts) {
293
+ return false;
294
+ }
295
+ debug(`File not found, retrying (attempt ${number}/${attempts})`);
296
+ await new Promise(resolve => {
297
+ setTimeout(resolve, intervalMs);
298
+ });
299
+ retry(err);
300
+ }
301
+ },
302
+ {
303
+ retries: attempts,
304
+ minTimeout: intervalMs,
305
+ maxTimeout: intervalMs,
306
+ },
307
+ );
308
+ }
309
+
310
+ async getS3LocationLink(out) {
311
+ const response = await out.done();
312
+
313
+ let s3Location = response?.Location?.trim();
314
+
315
+ if (!s3Location) {
316
+ s3Location = out?.singleUploadResult?.Location;
317
+ debug('Uploaded singleUploadResult.Location', s3Location);
318
+
319
+ if (!s3Location) {
320
+ throw new Error("Problems getting the S3 artifact's link. Please check S3 permissions!");
321
+ }
322
+ }
323
+
324
+ // Normalize the URL
325
+ if (!s3Location.startsWith('http')) {
326
+ s3Location = `https://${s3Location}`;
327
+ }
328
+
329
+ return s3Location;
330
+ }
331
+
332
+ #getFileExtBase64(str) {
333
+ const type = str.charAt(0);
334
+
335
+ return (
336
+ {
337
+ '/': 'jpg',
338
+ i: 'png',
339
+ R: 'gif',
340
+ U: 'webp',
341
+ }[type] || ''
342
+ );
343
+ }
344
+
345
+ #getS3Config() {
346
+ const { S3_REGION, S3_SESSION_TOKEN, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, S3_FORCE_PATH_STYLE, S3_ENDPOINT } =
347
+ this.getConfig();
348
+
349
+ const cfg = {
350
+ region: S3_REGION,
351
+ credentials: {
352
+ accessKeyId: S3_ACCESS_KEY_ID,
353
+ secretAccessKey: S3_SECRET_ACCESS_KEY,
354
+ },
355
+ };
356
+
357
+ if (S3_FORCE_PATH_STYLE) {
358
+ cfg.forcePathStyle = !['false', '0'].includes(String(S3_FORCE_PATH_STYLE || '').toLowerCase());
359
+ }
360
+
361
+ if (S3_SESSION_TOKEN) {
362
+ cfg.credentials.sessionToken = S3_SESSION_TOKEN;
363
+ }
364
+
365
+ if (S3_ENDPOINT) {
366
+ cfg.endpoint = S3_ENDPOINT;
367
+ }
368
+
369
+ return cfg;
370
+ }
371
+ }
@@ -1,4 +1,3 @@
1
- import { upload } from '../fileUploader.js';
2
1
  import { APP_PREFIX } from '../constants.js';
3
2
 
4
3
  /**
@@ -8,16 +7,16 @@ import { APP_PREFIX } from '../constants.js';
8
7
  function setS3Credentials(artifacts) {
9
8
  if (!Object.keys(artifacts).length) return;
10
9
 
11
- console.log(APP_PREFIX, 'S3 were credentials obtained from Testomat.io...');
10
+ console.log(APP_PREFIX, 'S3 credentials obtained from Testomat.io...');
12
11
 
13
12
  if (artifacts.ACCESS_KEY_ID) process.env.S3_ACCESS_KEY_ID = artifacts.ACCESS_KEY_ID;
14
13
  if (artifacts.SECRET_ACCESS_KEY) process.env.S3_SECRET_ACCESS_KEY = artifacts.SECRET_ACCESS_KEY;
15
14
  if (artifacts.REGION) process.env.S3_REGION = artifacts.REGION;
16
15
  if (artifacts.BUCKET) process.env.S3_BUCKET = artifacts.BUCKET;
17
- if (artifacts.ENDPOINT) process.env.S3_ENDPOINT = artifacts.ENDPOINT;
18
16
  if (artifacts.SESSION_TOKEN) process.env.S3_SESSION_TOKEN = artifacts.SESSION_TOKEN;
19
17
  if (artifacts.presign) process.env.TESTOMATIO_PRIVATE_ARTIFACTS = '1';
20
- upload.resetConfig();
18
+ // endpoint is not received from the server; and shuld be empty if IAM used (credentails obtained from the testomat)
19
+ process.env.S3_ENDPOINT = artifacts.ENDPOINT || '';
21
20
  }
22
21
 
23
22
  /**
@@ -117,11 +116,4 @@ function fullName(t) {
117
116
  return line;
118
117
  }
119
118
 
120
- export {
121
- updateFilterType,
122
- parseFilterParams,
123
- generateFilterRequestParams,
124
- setS3Credentials,
125
- statusEmoji,
126
- fullName,
127
- };
119
+ export { updateFilterType, parseFilterParams, generateFilterRequestParams, setS3Credentials, statusEmoji, fullName };
@@ -1,9 +1,10 @@
1
1
  import { URL } from 'url';
2
- import { sep, basename } from 'path';
2
+ import path, { sep, basename } from 'path';
3
3
  import pc from 'picocolors';
4
4
  import fs from 'fs';
5
5
  import isValid from 'is-valid-path';
6
6
  import createDebugMessages from 'debug';
7
+ import os from 'os';
7
8
 
8
9
  const debug = createDebugMessages('@testomatio/reporter:util');
9
10
 
@@ -320,7 +321,46 @@ const testRunnerHelper = {
320
321
  },
321
322
  };
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
+
323
362
  export {
363
+ ansiRegExp,
324
364
  isSameTest,
325
365
  fetchSourceCode,
326
366
  fetchSourceCodeFromStackTrace,
@@ -328,14 +368,16 @@ export {
328
368
  fetchIdFromOutput,
329
369
  fetchFilesFromStackTrace,
330
370
  fileSystem,
371
+ foundedTestLog,
372
+ formatStep,
331
373
  getCurrentDateTime,
332
- specificTestInfo,
333
- isValidUrl,
334
- ansiRegExp,
335
374
  getTestomatIdFromTestTitle,
336
- parseSuite,
337
375
  humanize,
376
+ isValidUrl,
377
+ parseSuite,
378
+ readLatestRunId,
338
379
  removeColorCodes,
339
- foundedTestLog,
380
+ specificTestInfo,
381
+ storeRunId,
340
382
  testRunnerHelper,
341
383
  };
package/src/xmlReader.js CHANGED
@@ -14,10 +14,10 @@ import {
14
14
  fetchIdFromCode,
15
15
  humanize,
16
16
  } from './utils/utils.js';
17
- import {pipesFactory} from './pipe/index.js';
17
+ import { pipesFactory } from './pipe/index.js';
18
18
  import adapterFactory from './junit-adapter/index.js';
19
- import {config} from './config.js';
20
- import { upload } from './fileUploader.js';
19
+ import { config } from './config.js';
20
+ import { S3Uploader } from './uploader.js';
21
21
 
22
22
  // @ts-ignore this line will be removed in compiled code, because __dirname is defined in commonjs
23
23
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -26,7 +26,8 @@ const debug = createDebugMessages('@testomatio/reporter:xml');
26
26
  const ridRunId = randomUUID();
27
27
 
28
28
  const TESTOMATIO_URL = process.env.TESTOMATIO_URL || 'https://app.testomat.io';
29
- const { TESTOMATIO_RUNGROUP_TITLE, TESTOMATIO_TITLE, TESTOMATIO_ENV, TESTOMATIO_RUN } = process.env;
29
+ const { TESTOMATIO_RUNGROUP_TITLE, TESTOMATIO_TITLE, TESTOMATIO_ENV, TESTOMATIO_RUN, TESTOMATIO_MARK_DETACHED } =
30
+ process.env;
30
31
 
31
32
  const options = {
32
33
  ignoreDeclaration: true,
@@ -46,6 +47,7 @@ class XmlReader {
46
47
  title: TESTOMATIO_TITLE,
47
48
  env: TESTOMATIO_ENV,
48
49
  group_title: TESTOMATIO_RUNGROUP_TITLE,
50
+ detach: TESTOMATIO_MARK_DETACHED,
49
51
  // batch uploading is implemented for xml already
50
52
  isBatchEnabled: false,
51
53
  };
@@ -61,7 +63,7 @@ class XmlReader {
61
63
  this.tests = [];
62
64
  this.stats = {};
63
65
  this.stats.language = opts.lang?.toLowerCase();
64
- this.filesToUpload = {};
66
+ this.uploader = new S3Uploader();
65
67
 
66
68
  // @ts-ignore
67
69
  const packageJsonPath = path.resolve(__dirname, '..', 'package.json');
@@ -122,17 +124,25 @@ class XmlReader {
122
124
  const hasFailures = resultTests.filter(t => t.status === 'failed').length > 0;
123
125
  const status = failures > 0 || errors > 0 || hasFailures ? 'failed' : 'passed';
124
126
 
127
+ const time = testsuite.time || 0;
128
+ // debug('time', jsonSuite, time)
129
+ if (time) {
130
+ if (!this.stats.duration) this.stats.duration = 0;
131
+ this.stats.duration += parseFloat(time);
132
+ }
133
+
125
134
  this.tests = this.tests.concat(resultTests);
126
135
 
127
136
  return {
128
- status,
129
137
  create_tests: true,
138
+ duration: parseFloat(time),
139
+ failed_count: parseInt(failures, 10),
130
140
  name,
131
- tests_count: parseInt(tests, 10),
132
141
  passed_count: parseInt(tests, 10) - parseInt(failures, 10),
133
- failed_count: parseInt(failures, 10),
134
142
  skipped_count: 0,
143
+ status,
135
144
  tests: resultTests,
145
+ tests_count: parseInt(tests, 10),
136
146
  };
137
147
  }
138
148
 
@@ -300,6 +310,7 @@ class XmlReader {
300
310
  calculateStats() {
301
311
  this.stats = {
302
312
  ...this.stats,
313
+ detach: this.requestParams.detach,
303
314
  status: 'passed',
304
315
  create_tests: true,
305
316
  tests_count: 0,
@@ -389,7 +400,7 @@ class XmlReader {
389
400
  if (!files.length) continue;
390
401
 
391
402
  const runId = this.runId || this.store.runId || Date.now().toString();
392
- test.artifacts = await Promise.all(files.map(f => upload.uploadFileByPath(f, runId)));
403
+ test.artifacts = await Promise.all(files.map(f => this.uploader.uploadFileByPath(f, [runId])));
393
404
  console.log(APP_PREFIX, `🗄️ Uploaded ${pc.bold(`${files.length} artifacts`)} for test ${test.title}`);
394
405
  }
395
406
  }
@@ -406,7 +417,9 @@ class XmlReader {
406
417
  debug('Run', runParams);
407
418
  this.pipes = this.pipes || (await this.pipesPromise);
408
419
 
409
- return Promise.all(this.pipes.map(p => p.createRun(runParams)));
420
+ const run = await Promise.all(this.pipes.map(p => p.createRun(runParams)));
421
+ this.uploader.checkEnabled();
422
+ return run;
410
423
  }
411
424
 
412
425
  async uploadData() {
@@ -417,18 +430,16 @@ class XmlReader {
417
430
  this.formatErrors();
418
431
  this.formatTests();
419
432
 
420
- debug('Uploading data', {
421
- ...this.stats,
422
- tests: this.tests,
423
- });
424
-
425
433
  const dataString = {
426
434
  ...this.stats,
427
435
  api_key: this.requestParams.apiKey,
428
436
  status: 'finished',
437
+ duration: this.stats.duration,
429
438
  tests: this.tests,
430
439
  };
431
440
 
441
+ debug('Uploading data', dataString);
442
+
432
443
  this.pipes = this.pipes || (await this.pipesPromise);
433
444
  return Promise.all(this.pipes.map(p => p.finishRun(dataString)));
434
445
  }
@@ -1,63 +0,0 @@
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.JasmineReporter = void 0;
7
- const client_js_1 = __importDefault(require("../../client.js"));
8
- const utils_js_1 = require("../../utils/utils.js");
9
- const constants_js_1 = require("../../constants.js");
10
- class JasmineReporter {
11
- constructor(options) {
12
- this.testTimeMap = {};
13
- this.client = new client_js_1.default({ apiKey: options?.apiKey });
14
- this.client.createRun();
15
- }
16
- getDuration(test) {
17
- if (this.testTimeMap[test.id]) {
18
- return Date.now() - this.testTimeMap[test.id];
19
- }
20
- return 0;
21
- }
22
- specStarted(result) {
23
- this.testTimeMap[result.id] = Date.now();
24
- }
25
- specDone(result) {
26
- if (!this.client)
27
- return;
28
- const title = result.description;
29
- const { status } = result;
30
- let errorMessage = '';
31
- for (let i = 0; i < result.failedExpectations.length; i += 1) {
32
- errorMessage = `${errorMessage}Failure: ${result.failedExpectations[i].message}\n`;
33
- errorMessage = `${errorMessage}\n ${result.failedExpectations[i].stack}`;
34
- }
35
- console.log(`${title} : ${constants_js_1.STATUS.PASSED}`);
36
- console.log(errorMessage);
37
- const testId = (0, utils_js_1.getTestomatIdFromTestTitle)(title);
38
- errorMessage = errorMessage.replace((0, utils_js_1.ansiRegExp)(), '');
39
- this.client.addTestRun(status, {
40
- error: result.failedExpectations[0],
41
- message: errorMessage,
42
- test_id: testId,
43
- title,
44
- time: this.getDuration(result),
45
- });
46
- }
47
- jasmineDone(suiteInfo, done) {
48
- if (!this.client)
49
- return;
50
- const { overallStatus } = suiteInfo;
51
- const status = overallStatus === 'failed' ? constants_js_1.STATUS.FAILED : constants_js_1.STATUS.PASSED;
52
- // @ts-ignore
53
- this.client.updateRunStatus(status).then(() => done);
54
- }
55
- }
56
- exports.JasmineReporter = JasmineReporter;
57
- module.exports = JasmineReporter;
58
-
59
- module.exports.JasmineReporter = JasmineReporter;
60
-
61
- module.exports.JasmineReporter = JasmineReporter;
62
-
63
- module.exports.exports = exports;