@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
package/src/client.js CHANGED
@@ -4,24 +4,26 @@ import { minimatch } from 'minimatch';
4
4
  import fs from 'fs';
5
5
  import pc from 'picocolors';
6
6
  import { randomUUID } from 'crypto';
7
- import {upload} from './fileUploader.js';
8
7
  import { APP_PREFIX, STATUS } from './constants.js';
9
8
  import { pipesFactory } from './pipe/index.js';
10
9
  import { glob } from 'glob';
11
- import path, { sep} from 'path';
10
+ import path, { sep } from 'path';
12
11
  import { fileURLToPath } from 'node:url';
12
+ import { S3Uploader } from './uploader.js';
13
+ import { formatStep, storeRunId } from './utils/utils.js';
14
+ import { filesize as prettyBytes } from 'filesize';
13
15
 
14
16
  const debug = createDebugMessages('@testomatio/reporter:client');
15
17
 
16
18
  // removed __dirname usage, because:
17
19
  // 1. replaced with ESM syntax (import.meta.url), but it throws an error on tsc compilation;
18
- // 2. got error "__dirname already defined" in compiles js code (cjs dir)
20
+ // 2. got error "__dirname already defined" in compiles js code (cjs dir)
19
21
 
20
22
  let listOfTestFilesToExcludeFromReport = null;
21
23
 
22
24
  /**
23
- * @typedef {import('../types').TestData} TestData
24
- * @typedef {import('../types').PipeResult} PipeResult
25
+ * @typedef {import('../types/types.js').TestData} TestData
26
+ * @typedef {import('../types/types.js').PipeResult} PipeResult
25
27
  */
26
28
 
27
29
  class Client {
@@ -29,12 +31,12 @@ class Client {
29
31
  * Create a Testomat client instance
30
32
  * @returns
31
33
  */
32
- // eslint-disable-next-line
34
+ // eslint-disable-next-line
33
35
  constructor(params = {}) {
34
- this.uuid = randomUUID();
36
+ this.paramsForPipesFactory = params;
37
+ this.pipeStore = {};
38
+ this.runId = randomUUID(); // will be replaced by real run id
35
39
  this.queue = Promise.resolve();
36
- this.totalUploaded = 0;
37
- this.failedToUpload = 0;
38
40
 
39
41
  // @ts-ignore this line will be removed in compiled code, because __dirname is defined in commonjs
40
42
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -47,6 +49,7 @@ class Client {
47
49
  }
48
50
  this.executionList = Promise.resolve();
49
51
 
52
+ this.uploader = new S3Uploader();
50
53
  }
51
54
 
52
55
  /**
@@ -66,8 +69,7 @@ class Client {
66
69
  * or resolves to undefined if no valid results are found or if all pipes are disabled.
67
70
  */
68
71
  async prepareRun(params) {
69
- const store = {};
70
- this.pipes = await pipesFactory(params, store);
72
+ this.pipes = await pipesFactory(params || this.paramsForPipesFactory || {}, this.pipeStore);
71
73
  const { pipe, pipeOptions } = params;
72
74
  // all pipes disabled, skipping
73
75
  if (!this.pipes.some(p => p.isEnabled)) {
@@ -110,7 +112,8 @@ class Client {
110
112
  * @returns {Promise<any>} - resolves to Run id which should be used to update / add test
111
113
  */
112
114
  async createRun(params) {
113
- if (!this.pipes || !this.pipes.length) this.pipes = await pipesFactory(params || {}, {});
115
+ if (!this.pipes || !this.pipes.length)
116
+ this.pipes = await pipesFactory(params || this.paramsForPipesFactory || {}, this.pipeStore);
114
117
  debug('Creating run...');
115
118
  // all pipes disabled, skipping
116
119
  if (!this.pipes?.filter(p => p.isEnabled).length) return Promise.resolve();
@@ -118,6 +121,12 @@ class Client {
118
121
  this.queue = this.queue
119
122
  .then(() => Promise.all(this.pipes.map(p => p.createRun())))
120
123
  .catch(err => console.log(APP_PREFIX, err))
124
+ .then(() => {
125
+ const runId = this.pipeStore?.runId;
126
+ if (runId) this.runId = runId;
127
+ storeRunId(this.runId);
128
+ })
129
+ .then(() => this.uploader.checkEnabled())
121
130
  .then(() => undefined); // fixes return type
122
131
  // debug('Run', this.queue);
123
132
  return this.queue;
@@ -165,9 +174,45 @@ class Client {
165
174
  suite_id,
166
175
  test_id,
167
176
  manuallyAttachedArtifacts,
168
- meta,
169
177
  } = testData;
170
- let { message = '' } = testData;
178
+ let { message = '', meta = {} } = testData;
179
+
180
+ // stringify meta values and limit keys and values length to 255
181
+ meta = Object.entries(meta)
182
+ .filter(([, value]) => value !== null && value !== undefined)
183
+ .map(([key, value]) => {
184
+ try {
185
+ if (typeof value === 'object') {
186
+ value = JSON.stringify(value);
187
+ } else if (typeof value !== 'string') {
188
+ try {
189
+ value = value.toString();
190
+ } catch (err) {
191
+ console.warn(APP_PREFIX, `Can't convert meta value to string`, err);
192
+ }
193
+ }
194
+
195
+ if (value?.length > 255) {
196
+ value = value.substring(0, 255);
197
+ debug(APP_PREFIX, `Meta info value "${value}" is too long, trimmed to 255 characters`);
198
+ }
199
+
200
+ if (key?.length > 255) {
201
+ const newKey = key.substring(0, 255);
202
+ debug(APP_PREFIX, `Meta info key "${key}" is too long, trimmed to 255 characters`);
203
+ return [newKey, value];
204
+ }
205
+
206
+ return [key, value];
207
+ } catch (err) {
208
+ debug(APP_PREFIX, `Error while processing meta info key ${key}`, err);
209
+ return [null, null];
210
+ }
211
+ })
212
+ .reduce((acc, [key, value]) => {
213
+ if (key) acc[key] = value;
214
+ return acc;
215
+ }, {});
171
216
 
172
217
  let errorFormatted = '';
173
218
  if (error) {
@@ -183,24 +228,24 @@ class Client {
183
228
 
184
229
  const uploadedFiles = [];
185
230
 
186
- for (const f of files) {
187
- uploadedFiles.push(upload.uploadFileByPath(f, this.uuid));
231
+ for (let f of files) {
232
+ if (!f) continue; // f === null
233
+ if (typeof f === 'object') {
234
+ if (!f.path) continue;
235
+
236
+ f = f.path;
237
+ }
238
+
239
+ uploadedFiles.push(this.uploader.uploadFileByPath(f, [this.runId, rid, path.basename(f)]));
188
240
  }
189
241
 
190
242
  for (const [idx, buffer] of filesBuffers.entries()) {
191
243
  const fileName = `${idx + 1}-${title.replace(/\s+/g, '-')}`;
192
- uploadedFiles.push(upload.uploadFileAsBuffer(buffer, fileName, this.uuid));
244
+ uploadedFiles.push(this.uploader.uploadFileAsBuffer(buffer, [this.runId, rid, fileName]));
193
245
  }
194
246
 
195
247
  const artifacts = (await Promise.all(uploadedFiles)).filter(n => !!n);
196
248
 
197
- if (artifacts.length < uploadedFiles.length) {
198
- const failedUploading = uploadedFiles.length - artifacts.length;
199
- this.failedToUpload += failedUploading;
200
- }
201
-
202
- this.totalUploaded += artifacts.length;
203
-
204
249
  const data = {
205
250
  rid,
206
251
  files,
@@ -258,27 +303,81 @@ class Client {
258
303
  this.queue = this.queue
259
304
  .then(() => Promise.all(this.pipes.map(p => p.finishRun(runParams))))
260
305
  .then(() => {
261
- debug('TOTAL artifacts', this.totalUploaded);
262
- if (this.totalUploaded && !upload.isArtifactsEnabled())
263
- debug(`${this.totalUploaded} artifacts are not uploaded, because artifacts uploading is not enabled`);
306
+ if (!this.uploader.isEnabled) return;
307
+
308
+ const filesizeStrMaxLength = 7;
309
+
310
+ if (this.uploader.successfulUploads.length) {
311
+ debug('\n', APP_PREFIX, `🗄️ ${this.uploader.successfulUploads.length} artifacts uploaded to S3 bucket`);
312
+ const uploadedArtifacts = this.uploader.successfulUploads.map(file => ({
313
+ relativePath: file.path.replace(process.cwd(), ''),
314
+ link: file.link,
315
+ sizePretty: prettyBytes(file.size, { round: 0 }).toString(),
316
+ }));
317
+
318
+ uploadedArtifacts.forEach(upload => {
319
+ debug(
320
+ `🟢Uploaded artifact`,
321
+ `${upload.relativePath},`,
322
+ 'size:',
323
+ `${upload.sizePretty},`,
324
+ 'link:',
325
+ `${upload.link}`,
326
+ );
327
+ });
328
+ }
264
329
 
265
- if (this.totalUploaded && upload.isArtifactsEnabled()) {
330
+ if (this.uploader.failedUploads.length) {
266
331
  console.log(
267
332
  APP_PREFIX,
268
- `🗄️ ${this.totalUploaded} artifacts ${
269
- process.env.TESTOMATIO_PRIVATE_ARTIFACTS ? 'privately' : pc.bold('publicly')
270
- } uploaded to S3 bucket`,
333
+ `🗄️ ${this.uploader.failedUploads.length} artifacts 🔴${pc.bold('failed')} to upload`,
271
334
  );
335
+ const failedUploads = this.uploader.failedUploads.map(file => ({
336
+ relativePath: file.path.replace(process.cwd(), ''),
337
+ sizePretty: prettyBytes(file.size, { round: 0 }).toString(),
338
+ }));
272
339
 
273
- if (this.failedToUpload > 0) {
340
+ const pathPadding = Math.max(...failedUploads.map(upload => upload.relativePath.length)) + 1;
341
+
342
+ failedUploads.forEach(upload => {
274
343
  console.log(
275
- APP_PREFIX,
276
- pc.yellow(
277
- `Some artifacts were not uploaded. ${this.failedToUpload} artifacts could not be uploaded.
278
- Run tests with DEBUG="@testomatio/reporter:file-uploader" to see details"`,
279
- ),
344
+ ` ${pc.gray('|')} 🔴 ${upload.relativePath.padEnd(pathPadding)} ${pc.gray(
345
+ `| ${upload.sizePretty.padStart(filesizeStrMaxLength)} |`,
346
+ )}`,
280
347
  );
281
- }
348
+ });
349
+ }
350
+
351
+ if (this.uploader.skippedUploads.length) {
352
+ console.log(
353
+ '\n',
354
+ APP_PREFIX,
355
+ `🗄️ ${pc.bold(this.uploader.skippedUploads.length)} artifacts uploading 🟡${pc.bold('skipped')}`,
356
+ );
357
+ const skippedUploads = this.uploader.skippedUploads.map(file => ({
358
+ relativePath: file.path.replace(process.cwd(), ''),
359
+ sizePretty: file.size === null ? 'unknown' : prettyBytes(file.size, { round: 0 }).toString(),
360
+ }));
361
+ const pathPadding = Math.max(...skippedUploads.map(upload => upload.relativePath.length)) + 1;
362
+ skippedUploads.forEach(upload => {
363
+ console.log(
364
+ ` ${pc.gray('|')} 🟡 ${upload.relativePath.padEnd(pathPadding)} ${pc.gray(
365
+ `| ${upload.sizePretty.padStart(filesizeStrMaxLength)} |`,
366
+ )}`,
367
+ );
368
+ });
369
+ }
370
+
371
+ if (this.uploader.skippedUploads.length || this.uploader.failedUploads.length) {
372
+ const command = `TESTOMATIO=<your_api_key> TESTOMATIO_RUN=${
373
+ this.runId
374
+ } npx @testomatio/reporter upload-artifacts`;
375
+ const numberOfNotUploadedArtifacts = this.uploader.skippedUploads.length + this.uploader.failedUploads.length;
376
+ console.log(
377
+ APP_PREFIX,
378
+ `${numberOfNotUploadedArtifacts} artifacts were not uploaded.
379
+ Run "${pc.magenta(command)}" with valid S3 credentials to upload skipped & failed artifacts`,
380
+ );
282
381
  }
283
382
  })
284
383
  .catch(err => console.log(APP_PREFIX, err));
@@ -365,24 +464,6 @@ function isNotInternalFrame(frame) {
365
464
  );
366
465
  }
367
466
 
368
- function formatStep(step, shift = 0) {
369
- const prefix = ' '.repeat(shift);
370
-
371
- const lines = [];
372
-
373
- if (step.error) {
374
- lines.push(`${prefix}${pc.red(step.title)} ${pc.gray(`${step.duration}ms`)}`);
375
- } else {
376
- lines.push(`${prefix}${step.title} ${pc.gray(`${step.duration}ms`)}`);
377
- }
378
-
379
- for (const child of step.steps || []) {
380
- lines.push(...formatStep(child, shift + 2));
381
- }
382
-
383
- return lines;
384
- }
385
-
386
467
  /**
387
468
  *
388
469
  * @param {TestData} testData
package/src/constants.js CHANGED
@@ -3,7 +3,11 @@ import os from 'os';
3
3
  import path from 'path';
4
4
 
5
5
  const APP_PREFIX = pc.gray('[TESTOMATIO]');
6
- const AXIOS_TIMEOUT = 20 * 1000; // sum = 20sec
6
+ const TESTOMATIO_REQUEST_TIMEOUT = parseInt(process.env.TESTOMATIO_REQUEST_TIMEOUT, 10);
7
+ if (TESTOMATIO_REQUEST_TIMEOUT) {
8
+ console.log(`${APP_PREFIX} Request timeout is set to ${TESTOMATIO_REQUEST_TIMEOUT / 1000}s`);
9
+ }
10
+ const AXIOS_TIMEOUT = TESTOMATIO_REQUEST_TIMEOUT || 20 * 1000;
7
11
 
8
12
  const TESTOMAT_TMP_STORAGE_DIR = path.join(os.tmpdir(), 'testomatio_tmp');
9
13
 
@@ -52,7 +52,7 @@ class DataStorage {
52
52
 
53
53
  context = context || this.context || testRunnerHelper.getNameOfCurrentlyRunningTest();
54
54
  if (!context) {
55
- debug(`No context provided for "${dataType}" data:`, data);
55
+ // debug(`No context provided for "${dataType}" data:`, data);
56
56
  return;
57
57
  }
58
58
  const contextHash = stringToMD5Hash(context);
@@ -101,7 +101,7 @@ class DataStorage {
101
101
  if (testDataFromFile.length) {
102
102
  return testDataFromFile;
103
103
  }
104
- debug(`No "${dataType}" data for context "${contextHash}" in both file and global variable`);
104
+ // debug(`No "${dataType}" data for context "${contextHash}" in both file and global variable`);
105
105
 
106
106
  // in case no data found for context
107
107
  return null;
@@ -15,8 +15,8 @@ const debug = createDebugMessages('@testomatio/reporter:pipe:bitbucket');
15
15
 
16
16
  /**
17
17
  * @class BitbucketPipe
18
- * @typedef {import('../../types').Pipe} Pipe
19
- * @typedef {import('../../types').TestData} TestData
18
+ * @typedef {import('../../types/types.js').Pipe} Pipe
19
+ * @typedef {import('../../types/types.js').TestData} TestData
20
20
  */
21
21
  export class BitbucketPipe {
22
22
  constructor(params, store = {}) {
package/src/pipe/csv.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import createDebugMessages from 'debug';
2
2
  import path from 'path';
3
3
  import fs from 'fs';
4
- import {createObjectCsvWriter} from 'csv-writer';
4
+ import { createObjectCsvWriter } from 'csv-writer';
5
5
  import pc from 'picocolors';
6
6
  import merge from 'lodash.merge';
7
7
  import { isSameTest, getCurrentDateTime, ansiRegExp } from '../utils/utils.js';
@@ -9,8 +9,8 @@ import { CSV_HEADERS } from '../constants.js';
9
9
 
10
10
  const debug = createDebugMessages('@testomatio/reporter:pipe:csv');
11
11
  /**
12
- * @typedef {import('../../types').Pipe} Pipe
13
- * @typedef {import('../../types').TestData} TestData
12
+ * @typedef {import('../../types/types.js').Pipe} Pipe
13
+ * @typedef {import('../../types/types.js').TestData} TestData
14
14
  * @class CsvPipe
15
15
  * @implements {Pipe}
16
16
  */
@@ -0,0 +1,104 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import os from 'os';
4
+ import createDebugMessages from 'debug';
5
+ import { APP_PREFIX } from '../constants.js';
6
+ import prettyMs from 'pretty-ms';
7
+
8
+ const debug = createDebugMessages('@testomatio/reporter:pipe:debug');
9
+
10
+ export class DebugPipe {
11
+ constructor(params, store) {
12
+ this.params = params || {};
13
+ this.store = store || {};
14
+
15
+ this.isEnabled = !!process.env.TESTOMATIO_DEBUG || !!process.env.DEBUG;
16
+ if (this.isEnabled) {
17
+ this.batch = {
18
+ isEnabled: this.params.isBatchEnabled ?? !process.env.TESTOMATIO_DISABLE_BATCH_UPLOAD ?? true,
19
+ intervalFunction: null,
20
+ intervalTime: 5000,
21
+ tests: [],
22
+ batchIndex: 0,
23
+ };
24
+ this.logFilePath = path.join(os.tmpdir(), `testomatio.debug.${Date.now()}.json`);
25
+
26
+ debug('Creating debug file:', this.logFilePath);
27
+ fs.writeFileSync(this.logFilePath, '');
28
+ console.log(APP_PREFIX, '🪲. Debug created:');
29
+ this.testomatioEnvVars = Object.keys(process.env)
30
+ .filter(key => key.startsWith('TESTOMATIO_'))
31
+ .reduce((acc, key) => {
32
+ acc[key] = process.env[key];
33
+ return acc;
34
+ }, {});
35
+ this.logToFile({ datetime: new Date().toISOString(), timestamp: Date.now() });
36
+ this.logToFile({ data: 'variables', testomatioEnvVars: this.testomatioEnvVars });
37
+ this.logToFile({ data: 'store', store: this.store || {} });
38
+ // Bind batchUpload to the instance
39
+ this.batchUpload = this.batchUpload.bind(this);
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Logs data to a file if logging is enabled.
45
+ *
46
+ * @param {Object} logData - The data to be logged.
47
+ * @returns {Promise<void>} A promise that resolves when the log data has been appended to the file.
48
+ */
49
+ logToFile(logData) {
50
+ if (!this.isEnabled) return;
51
+ const timePassedFromLastAction = Date.now() - (this.lastActionTimestamp || Date.now());
52
+ this.lastActionTimestamp = Date.now();
53
+
54
+ const logLine = JSON.stringify({ t: `+${prettyMs(timePassedFromLastAction)}`, ...logData });
55
+ fs.appendFileSync(this.logFilePath, `${logLine}\n`);
56
+ }
57
+
58
+ async prepareRun(opts) {
59
+ if (!this.isEnabled) return [];
60
+
61
+ this.logToFile({ action: 'prepareRun', data: opts });
62
+ }
63
+
64
+ async createRun(params = {}) {
65
+ if (!this.isEnabled) return;
66
+ if (params.isBatchEnabled === true || params.isBatchEnabled === false) this.batch.isEnabled = params.isBatchEnabled;
67
+
68
+ if (!this.isEnabled) return {};
69
+ if (this.batch.isEnabled) this.batch.intervalFunction = setInterval(this.batchUpload, this.batch.intervalTime);
70
+
71
+ this.logToFile({ action: 'createRun', params });
72
+ }
73
+
74
+ async addTest(data) {
75
+ if (!this.isEnabled) return;
76
+
77
+ if (!this.batch.isEnabled) this.logToFile({ action: 'addTest', testId: data });
78
+ else this.batch.tests.push(data);
79
+
80
+ if (!this.batch.intervalFunction) await this.batchUpload();
81
+ }
82
+
83
+ async batchUpload() {
84
+ this.batch.batchIndex++;
85
+ if (!this.batch.isEnabled) return;
86
+ if (!this.batch.tests.length) return;
87
+
88
+ const testsToSend = this.batch.tests.splice(0);
89
+
90
+ this.logToFile({ action: 'addTestsBatch', tests: testsToSend });
91
+ }
92
+
93
+ async finishRun(params) {
94
+ if (!this.isEnabled) return;
95
+ this.logToFile({ actions: 'finishRun', params });
96
+ await this.batchUpload();
97
+ if (this.batch.intervalFunction) clearInterval(this.batch.intervalFunction);
98
+ console.log(APP_PREFIX, '🪲. Debug Saved to', this.logFilePath);
99
+ }
100
+
101
+ toString() {
102
+ return 'Debug Reporter';
103
+ }
104
+ }
@@ -10,10 +10,9 @@ import { statusEmoji, fullName } from '../utils/pipe_utils.js';
10
10
 
11
11
  const debug = createDebugMessages('@testomatio/reporter:pipe:github');
12
12
 
13
-
14
13
  /**
15
- * @typedef {import('../../types').Pipe} Pipe
16
- * @typedef {import('../../types').TestData} TestData
14
+ * @typedef {import('../../types/types.js').Pipe} Pipe
15
+ * @typedef {import('../../types/types.js').TestData} TestData
17
16
  * @class GitHubPipe
18
17
  * @implements {Pipe}
19
18
  */
@@ -15,8 +15,8 @@ const debug = createDebugMessages('@testomatio/reporter:pipe:gitlab');
15
15
 
16
16
  /**
17
17
  * @class GitLabPipe
18
- * @typedef {import('../../types').Pipe} Pipe
19
- * @typedef {import('../../types').TestData} TestData
18
+ * @typedef {import('../../types/types.js').Pipe} Pipe
19
+ * @typedef {import('../../types/types.js').TestData} TestData
20
20
  */
21
21
  class GitLabPipe {
22
22
  constructor(params, store = {}) {
@@ -81,13 +81,13 @@ class GitLabPipe {
81
81
  let summary = `${this.hiddenCommentData}
82
82
 
83
83
  | [![Testomat.io Report](${testomatLogoURL})](https://testomat.io) | ${statusEmoji(
84
- runParams.status,
85
- )} ${runParams.status.toUpperCase()} ${statusEmoji(runParams.status)} |
84
+ runParams.status,
85
+ )} ${runParams.status.toUpperCase()} ${statusEmoji(runParams.status)} |
86
86
  | --- | --- |
87
87
  | Tests | ✔️ **${this.tests.length}** tests run |
88
88
  | Summary | ${statusEmoji('failed')} **${failedCount}** failed; ${statusEmoji(
89
- 'passed',
90
- )} **${passedCount}** passed; **${statusEmoji('skipped')}** ${skippedCount} skipped |
89
+ 'passed',
90
+ )} **${passedCount}** passed; **${statusEmoji('skipped')}** ${skippedCount} skipped |
91
91
  | Duration | 🕐 **${humanizeDuration(
92
92
  parseInt(
93
93
  this.tests.reduce((a, t) => a + (t.run_time || 0), 0),
package/src/pipe/html.js CHANGED
@@ -5,9 +5,9 @@ import path from 'path';
5
5
  import pc from 'picocolors';
6
6
  import handlebars from 'handlebars';
7
7
  import fileUrl from 'file-url';
8
- import { fileSystem, isSameTest, ansiRegExp } from '../utils/utils.js';
8
+ import { fileSystem, isSameTest, ansiRegExp, formatStep } from '../utils/utils.js';
9
9
  import { HTML_REPORT } from '../constants.js';
10
- import { fileURLToPath } from "node:url";
10
+ import { fileURLToPath } from 'node:url';
11
11
 
12
12
  const debug = createDebugMessages('@testomatio/reporter:pipe:html');
13
13
 
@@ -72,7 +72,7 @@ class HtmlPipe {
72
72
 
73
73
  /**
74
74
  * Add test data to the result array for saving. As a result of this function, we get a result object to save.
75
- * @param {import('../../types').RunData} test - object which includes each test entry.
75
+ * @param {import('../../types/types.js').RunData} test - object which includes each test entry.
76
76
  */
77
77
  addTest(test) {
78
78
  if (!this.isEnabled) return;
@@ -128,6 +128,14 @@ class HtmlPipe {
128
128
  }
129
129
 
130
130
  tests.forEach(test => {
131
+ // steps could be an array or a string
132
+ test.steps = Array.isArray(test.steps)
133
+ ? (test.steps = test.steps
134
+ .map(step => formatStep(step))
135
+ .flat()
136
+ .join('\n'))
137
+ : test.steps;
138
+
131
139
  if (!test.message?.trim()) {
132
140
  test.message = "This test has no 'message' code";
133
141
  }
package/src/pipe/index.js CHANGED
@@ -7,7 +7,8 @@ import GitHubPipe from './github.js';
7
7
  import GitLabPipe from './gitlab.js';
8
8
  import CsvPipe from './csv.js';
9
9
  import HtmlPipe from './html.js';
10
- import {BitbucketPipe} from './bitbucket.js';
10
+ import { BitbucketPipe } from './bitbucket.js';
11
+ import { DebugPipe } from './debug.js';
11
12
 
12
13
  export async function pipesFactory(params, opts) {
13
14
  const extraPipes = [];
@@ -47,6 +48,7 @@ export async function pipesFactory(params, opts) {
47
48
  new CsvPipe(params, opts),
48
49
  new HtmlPipe(params, opts),
49
50
  new BitbucketPipe(params, opts),
51
+ new DebugPipe(params, opts),
50
52
  ...extraPipes,
51
53
  ];
52
54
 
@@ -55,13 +57,9 @@ export async function pipesFactory(params, opts) {
55
57
  console.log(
56
58
  APP_PREFIX,
57
59
  pc.cyan('Pipes:'),
58
- pc.cyan(
59
- pipesEnabled
60
- .map(p => p.toString())
61
- .join(', ') || 'No pipes enabled',
62
- ),
60
+ pc.cyan(pipesEnabled.map(p => p.toString()).join(', ') || 'No pipes enabled'),
63
61
  );
64
-
62
+
65
63
  if (!pipesEnabled.length) {
66
64
  console.log(
67
65
  APP_PREFIX,