@testomatio/reporter 2.0.1-beta.7 → 2.0.1-beta.9

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.
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.extensionMap = void 0;
4
+ exports.extensionMap = {
5
+ 'application/json': 'json',
6
+ 'text/plain': 'txt',
7
+ 'image/jpeg': 'jpg',
8
+ 'image/png': 'png',
9
+ 'text/html': 'html',
10
+ 'text/css': 'css',
11
+ 'text/javascript': 'js',
12
+ 'application/pdf': 'pdf',
13
+ 'application/xml': 'xml',
14
+ 'text/xml': 'xml',
15
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@testomatio/reporter",
3
- "version": "2.0.1-beta.7",
3
+ "version": "2.0.1-beta.9",
4
4
  "description": "Testomatio Reporter Client",
5
5
  "engines": {
6
6
  "node": ">=18"
@@ -9,6 +9,7 @@ import TestomatioClient from '../client.js';
9
9
  import { getTestomatIdFromTestTitle, fileSystem } from '../utils/utils.js';
10
10
  import { services } from '../services/index.js';
11
11
  import { dataStorage } from '../data-storage.js';
12
+ import { extensionMap } from '../utils/constants.js';
12
13
 
13
14
  const reportTestPromises = [];
14
15
 
@@ -129,15 +130,9 @@ class PlaywrightReporter {
129
130
  }
130
131
  if (artifact.body) {
131
132
  let filePath = generateTmpFilepath(artifact.name);
132
- // Check if file already has an extension
133
133
  const hasExtension = artifact.name && path.extname(artifact.name);
134
134
  if (!hasExtension && artifact.contentType) {
135
- const mimeType = artifact.contentType.split('/')[1];
136
- const extensionMap = {
137
- jpeg: 'jpg',
138
- plain: 'txt',
139
- };
140
- const extension = extensionMap[mimeType] || mimeType;
135
+ const extension = extensionMap[artifact.contentType] || artifact.contentType.split('/')[1];
141
136
  if (extension) filePath += `.${extension}`;
142
137
  }
143
138
  fs.writeFileSync(filePath, artifact.body);
package/src/pipe/debug.js CHANGED
@@ -26,18 +26,19 @@ export class DebugPipe {
26
26
  debug('Creating debug file:', this.logFilePath);
27
27
  fs.writeFileSync(this.logFilePath, '');
28
28
 
29
- // Create symlink to ensure consistent path to latest debug file
30
- const symlinkPath = path.join(os.tmpdir(), 'testomatio.debug.latest.json');
29
+ // Create latest debug file reference (Windows compatible)
30
+ this.latestFilePath = path.join(os.tmpdir(), 'testomatio.debug.latest.json');
31
31
  try {
32
- // Remove existing symlink if it exists
33
- if (fs.existsSync(symlinkPath)) {
34
- fs.unlinkSync(symlinkPath);
32
+ // Remove existing latest file if it exists
33
+ if (fs.existsSync(this.latestFilePath)) {
34
+ fs.rmSync(this.latestFilePath);
35
35
  }
36
- // Create new symlink pointing to the timestamped debug file
37
- fs.symlinkSync(this.logFilePath, symlinkPath);
38
- debug('Created symlink:', symlinkPath, '->', this.logFilePath);
36
+ // Initialize latest file
37
+ fs.writeFileSync(this.latestFilePath, '');
38
+ debug('Created latest debug file:', this.latestFilePath);
39
39
  } catch (err) {
40
- debug('Failed to create symlink:', err.message);
40
+ debug('Failed to create latest debug file:', err.message);
41
+ this.latestFilePath = null; // Disable latest file if creation fails
41
42
  }
42
43
 
43
44
  console.log(APP_PREFIX, '🪲 Debug file created');
@@ -67,7 +68,19 @@ export class DebugPipe {
67
68
  this.lastActionTimestamp = Date.now();
68
69
 
69
70
  const logLine = JSON.stringify({ t: `+${prettyMs(timePassedFromLastAction)}`, ...logData });
70
- fs.appendFileSync(this.logFilePath, `${logLine}\n`);
71
+ const logLineWithNewline = `${logLine}\n`;
72
+
73
+ // Write to timestamped debug file
74
+ fs.appendFileSync(this.logFilePath, logLineWithNewline);
75
+
76
+ // Also write to latest debug file for Windows compatibility
77
+ if (this.latestFilePath) {
78
+ try {
79
+ fs.appendFileSync(this.latestFilePath, logLineWithNewline);
80
+ } catch (err) {
81
+ debug('Failed to write to latest debug file:', err.message);
82
+ }
83
+ }
71
84
  }
72
85
 
73
86
  async prepareRun(opts) {
@@ -93,8 +106,7 @@ export class DebugPipe {
93
106
  const logData = { action: 'addTest', testId: data };
94
107
  if (this.store.runId) logData.runId = this.store.runId;
95
108
  this.logToFile(logData);
96
- }
97
- else this.batch.tests.push(data);
109
+ } else this.batch.tests.push(data);
98
110
 
99
111
  if (!this.batch.intervalFunction) await this.batchUpload();
100
112
  }
@@ -117,6 +129,9 @@ export class DebugPipe {
117
129
  if (this.batch.intervalFunction) clearInterval(this.batch.intervalFunction);
118
130
  this.logToFile({ action: 'finishRun', params });
119
131
  console.log(APP_PREFIX, '🪲 Debug Saved to', this.logFilePath);
132
+ if (this.latestFilePath) {
133
+ console.log(APP_PREFIX, '🪲 Latest Debug file:', this.latestFilePath);
134
+ }
120
135
  }
121
136
 
122
137
  toString() {
package/src/replay.js CHANGED
@@ -5,6 +5,8 @@ import TestomatClient from './client.js';
5
5
  import { STATUS } from './constants.js';
6
6
  import { config } from './config.js';
7
7
 
8
+ const isEnabled = !!process.env.TESTOMATIO_DEBUG || !!process.env.DEBUG;
9
+
8
10
  export class Replay {
9
11
  constructor(options = {}) {
10
12
  this.apiKey = options.apiKey || config.TESTOMATIO || undefined;
@@ -22,6 +24,100 @@ export class Replay {
22
24
  return path.join(os.tmpdir(), 'testomatio.debug.latest.json');
23
25
  }
24
26
 
27
+ /**
28
+ * Read artifacts file for a specific run
29
+ * @param {string} runId - Run ID to find artifacts for
30
+ * @returns {Array} Array of artifact records with rid and file paths
31
+ */
32
+ readArtifactsFile(runId) {
33
+ if (!runId) return [];
34
+
35
+ const artifactsFile = path.join(os.tmpdir(), `testomatio.run.${runId}.json`);
36
+
37
+ if (!fs.existsSync(artifactsFile)) {
38
+ if (isEnabled) console.log(`No artifacts file found: ${artifactsFile}`);
39
+ return [];
40
+ }
41
+
42
+ try {
43
+ const data = fs.readFileSync(artifactsFile, 'utf-8');
44
+ const lines = data.split('\n').filter(Boolean);
45
+ const artifacts = lines.map(line => JSON.parse(line));
46
+ if (isEnabled) console.log(`Found ${artifacts.length} artifact records in ${artifactsFile}`);
47
+ return artifacts;
48
+ } catch (err) {
49
+ if (isEnabled) console.log(`Error reading artifacts file: ${err.message}`);
50
+ return [];
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Merge artifacts data with test data
56
+ * @param {Array} tests - Array of test objects
57
+ * @param {Array} artifacts - Array of artifact records
58
+ * @returns {Array} Tests with merged artifact data
59
+ */
60
+ mergeArtifactsWithTests(tests, artifacts) {
61
+ if (!artifacts.length) return tests;
62
+
63
+ // Extract runId prefix from first test
64
+ const runIdPrefix = tests[0]?.rid ? tests[0].rid.split('-')[0] + '-' : '';
65
+
66
+ // Group artifacts by RID
67
+ const artifactsByRid = artifacts.reduce((acc, artifact) => {
68
+ const fullRid = runIdPrefix && !artifact.rid.startsWith(runIdPrefix) ? runIdPrefix + artifact.rid : artifact.rid;
69
+
70
+ if (!acc[fullRid]) acc[fullRid] = [];
71
+ acc[fullRid].push(artifact);
72
+ return acc;
73
+ }, {});
74
+
75
+ // Merge artifacts into tests
76
+ let mergedCount = 0;
77
+ const result = tests.map(test => {
78
+ if (!test.rid || !artifactsByRid[test.rid]) return test;
79
+
80
+ const testArtifacts = artifactsByRid[test.rid];
81
+ mergedCount++;
82
+
83
+ if (isEnabled && test.title) {
84
+ console.log(`🔄 Adding artifact info for test: ${test.title}`);
85
+ console.log(` 📎 Existing files: ${(test.files || []).length} | Artifact records: ${testArtifacts.length}`);
86
+ }
87
+
88
+ // Deduplicate artifacts by filename
89
+ const seenFiles = new Set();
90
+ const deduplicatedArtifacts = testArtifacts.filter(artifact => {
91
+ if (!artifact.file) return true;
92
+ const fileName = path.basename(artifact.file);
93
+ if (seenFiles.has(fileName)) {
94
+ if (isEnabled && test.title) {
95
+ console.log(` ⚠️ Removed duplicate artifact: ${fileName}`);
96
+ }
97
+ return false;
98
+ }
99
+ seenFiles.add(fileName);
100
+ return true;
101
+ });
102
+
103
+ if (isEnabled && test.title && deduplicatedArtifacts.length !== testArtifacts.length) {
104
+ console.log(` 🧹 Deduplicated: ${testArtifacts.length} → ${deduplicatedArtifacts.length} artifacts`);
105
+ }
106
+
107
+ return {
108
+ ...test,
109
+ files: test.files || [],
110
+ artifactRecords: deduplicatedArtifacts,
111
+ };
112
+ });
113
+
114
+ if (isEnabled) {
115
+ console.log(`🔄 Added artifact info to ${mergedCount} tests (no file duplication)`);
116
+ }
117
+
118
+ return result;
119
+ }
120
+
25
121
  /**
26
122
  * Parse a debug file and extract test data
27
123
  * @param {string} debugFile - Path to the debug file
@@ -33,7 +129,10 @@ export class Replay {
33
129
  }
34
130
 
35
131
  const fileContent = fs.readFileSync(debugFile, 'utf-8');
36
- const lines = fileContent.trim().split('\n').filter(line => line.trim() !== '');
132
+ const lines = fileContent
133
+ .trim()
134
+ .split('\n')
135
+ .filter(line => line.trim() !== '');
37
136
 
38
137
  if (lines.length === 0) {
39
138
  throw new Error('Debug file is empty');
@@ -72,13 +171,25 @@ export class Replay {
72
171
  Object.keys(test).forEach(key => {
73
172
  if (test[key] !== null && test[key] !== undefined) {
74
173
  if (key === 'files' && Array.isArray(test[key]) && test[key].length > 0) {
75
- // Merge files arrays
76
- mergedTest.files = [...(existingTest.files || []), ...test[key]];
174
+ // Deduplicate files within the array itself
175
+ const seen = new Set();
176
+ mergedTest.files = test[key].filter(file => {
177
+ const fileName =
178
+ typeof file === 'string'
179
+ ? path.basename(file)
180
+ : path.basename(file?.path || file?.file || '');
181
+ if (seen.has(fileName)) return false;
182
+ seen.add(fileName);
183
+ return true;
184
+ });
77
185
  } else if (key === 'artifacts' && Array.isArray(test[key]) && test[key].length > 0) {
78
- // Merge artifacts arrays
79
- mergedTest.artifacts = [...(existingTest.artifacts || []), ...test[key]];
80
- } else if (existingTest[key] === null || existingTest[key] === undefined ||
81
- (Array.isArray(existingTest[key]) && existingTest[key].length === 0)) {
186
+ // Keep the most recent artifacts array (don't merge to avoid duplicates)
187
+ mergedTest.artifacts = test[key];
188
+ } else if (
189
+ existingTest[key] === null ||
190
+ existingTest[key] === undefined ||
191
+ (Array.isArray(existingTest[key]) && existingTest[key].length === 0)
192
+ ) {
82
193
  // Use new value if existing is null/undefined/empty array
83
194
  mergedTest[key] = test[key];
84
195
  }
@@ -103,8 +214,33 @@ export class Replay {
103
214
  // Handle tests with rid (deduplicate)
104
215
  const existingTest = testsMap.get(test.rid);
105
216
  if (existingTest) {
106
- // Merge with existing test
107
- const mergedTest = { ...existingTest, ...test };
217
+ // Merge test data - prioritize non-null/non-empty values (same logic as addTestsBatch)
218
+ const mergedTest = { ...existingTest };
219
+ Object.keys(test).forEach(key => {
220
+ if (test[key] !== null && test[key] !== undefined) {
221
+ if (key === 'files' && Array.isArray(test[key]) && test[key].length > 0) {
222
+ // Deduplicate files within the array itself
223
+ const seen = new Set();
224
+ mergedTest.files = test[key].filter(file => {
225
+ const fileName =
226
+ typeof file === 'string' ? path.basename(file) : path.basename(file?.path || file?.file || '');
227
+ if (seen.has(fileName)) return false;
228
+ seen.add(fileName);
229
+ return true;
230
+ });
231
+ } else if (key === 'artifacts' && Array.isArray(test[key]) && test[key].length > 0) {
232
+ // Keep the most recent artifacts array (don't merge to avoid duplicates)
233
+ mergedTest.artifacts = test[key];
234
+ } else if (
235
+ existingTest[key] === null ||
236
+ existingTest[key] === undefined ||
237
+ (Array.isArray(existingTest[key]) && existingTest[key].length === 0)
238
+ ) {
239
+ // Use new value if existing is null/undefined/empty array
240
+ mergedTest[key] = test[key];
241
+ }
242
+ }
243
+ });
108
244
  testsMap.set(test.rid, mergedTest);
109
245
  } else {
110
246
  testsMap.set(test.rid, { ...test });
@@ -139,7 +275,7 @@ export class Replay {
139
275
  envVars,
140
276
  parseErrors,
141
277
  totalLines: lines.length,
142
- runId
278
+ runId,
143
279
  };
144
280
  }
145
281
 
@@ -174,7 +310,8 @@ export class Replay {
174
310
 
175
311
  // Parse the debug file
176
312
  const debugData = this.parseDebugFile(debugFile);
177
- const { runParams, finishParams, tests, envVars, runId } = debugData;
313
+ const { runParams, finishParams, envVars, runId } = debugData;
314
+ let tests = debugData.tests;
178
315
 
179
316
  this.onLog(`Found ${tests.length} tests to replay`);
180
317
 
@@ -182,10 +319,104 @@ export class Replay {
182
319
  throw new Error('No test data found in debug file');
183
320
  }
184
321
 
322
+ // Read and merge artifacts if runId is available
323
+ if (runId) {
324
+ const artifacts = this.readArtifactsFile(runId);
325
+ if (artifacts.length > 0) {
326
+ if (isEnabled) console.log(`Found ${artifacts.length} artifact records in testomatio.run.${runId}.json`);
327
+ tests = this.mergeArtifactsWithTests(tests, artifacts);
328
+ }
329
+ }
330
+
185
331
  // Restore environment variables
186
332
  this.restoreEnvironmentVariables(envVars);
187
333
 
188
334
  if (this.dryRun) {
335
+ if (isEnabled) console.log('🔍 DRY RUN - Tests to be sent:');
336
+ if (isEnabled) console.log('='.repeat(60));
337
+
338
+ let totalArtifacts = 0;
339
+ let totalFiles = 0;
340
+ let uploadedArtifacts = 0;
341
+ let notUploadedArtifacts = 0;
342
+
343
+ tests.forEach((test, index) => {
344
+ const status = test.status === 'passed' ? '✅' : test.status === 'failed' ? '❌' : '⏭️';
345
+ if (isEnabled) console.log(`${index + 1}. ${status} ${test.title || test.id}`);
346
+ if (isEnabled) console.log(` 📁 File: ${test.file || 'Unknown'}`);
347
+ if (isEnabled) console.log(` 📊 Status: ${test.status}`);
348
+ if (isEnabled) console.log(` 🔑 RID: ${test.rid || 'No RID'}`);
349
+
350
+ if (test.steps && test.steps.length > 0) {
351
+ if (isEnabled) console.log(` 🔄 Steps: ${test.steps.length}`);
352
+ test.steps.slice(0, 3).forEach((step, stepIndex) => {
353
+ const stepStatus = step.status === 'passed' ? '✅' : step.status === 'failed' ? '❌' : '⚪';
354
+ if (isEnabled) console.log(` ${stepIndex + 1}. ${stepStatus} ${step.title || step.name || 'Step'}`);
355
+ });
356
+ if (test.steps.length > 3) {
357
+ if (isEnabled) console.log(` ... also ${test.steps.length - 3} steps`);
358
+ }
359
+ }
360
+
361
+ // Show either artifact records (if available) or files
362
+ if (test.artifactRecords && test.artifactRecords.length > 0) {
363
+ totalArtifacts += test.artifactRecords.length;
364
+ if (isEnabled) console.log(` 📎 Artifacts: ${test.artifactRecords.length}`);
365
+ test.artifactRecords.forEach((artifact, artIndex) => {
366
+ const uploaded =
367
+ artifact.uploaded === true
368
+ ? '✅ Uploaded'
369
+ : artifact.uploaded === false
370
+ ? '📤 Not Uploaded'
371
+ : '⏳ Unknown';
372
+ const fileName = artifact.file ? path.basename(artifact.file) : 'Unknown';
373
+ if (isEnabled) console.log(` ${artIndex + 1}. ${uploaded}: ${fileName}`);
374
+
375
+ // Count artifact upload status
376
+ if (artifact.uploaded === true) {
377
+ uploadedArtifacts++;
378
+ } else if (artifact.uploaded === false) {
379
+ notUploadedArtifacts++;
380
+ }
381
+ });
382
+ } else if (test.files && test.files.length > 0) {
383
+ totalFiles += test.files.length;
384
+ if (isEnabled) console.log(` 📎 Files: ${test.files.length}`);
385
+ test.files.slice(0, 3).forEach((file, fileIndex) => {
386
+ // Handle both string paths and object formats
387
+ let fileName;
388
+ if (typeof file === 'string') {
389
+ fileName = path.basename(file);
390
+ } else if (file && typeof file === 'object') {
391
+ const filePath = file.path || file.file || JSON.stringify(file);
392
+ fileName = file.name || (typeof filePath === 'string' ? path.basename(filePath) : 'Unknown');
393
+ } else {
394
+ fileName = 'Unknown';
395
+ }
396
+ if (isEnabled) console.log(` ${fileIndex + 1}. ${fileName}`);
397
+ });
398
+ if (test.files.length > 3) {
399
+ if (isEnabled) console.log(` ... also ${test.files.length - 3} files`);
400
+ }
401
+ }
402
+
403
+ if (isEnabled) console.log('');
404
+ });
405
+
406
+ if (isEnabled) console.log('='.repeat(60));
407
+ if (isEnabled) console.log('📊 SUMMARY:');
408
+ if (isEnabled) console.log(` 📋 Total tests: ${tests.length}`);
409
+ if (isEnabled) console.log(` 📎 Total files: ${totalFiles}`);
410
+ if (isEnabled) console.log(` 📎 Total artifact records: ${totalArtifacts}`);
411
+ if (totalArtifacts > 0) {
412
+ if (isEnabled) console.log(` ✅ Uploaded: ${uploadedArtifacts}`);
413
+ if (isEnabled) console.log(` 📤 Not Uploaded: ${notUploadedArtifacts}`);
414
+ }
415
+ if (isEnabled) console.log(` 🆔 Run ID: ${runId || 'Will create new'}`);
416
+ if (isEnabled) console.log(` 🌍 Environment: ${envVars.TESTOMATIO_ENV || 'Unknown'}`);
417
+ if (isEnabled) console.log(` 🔗 API URL: ${envVars.TESTOMATIO_URL || 'Default'}`);
418
+ if (isEnabled) console.log('');
419
+ if (isEnabled) console.log('✅ Use without --dry-run to send this data to Testomat.io');
189
420
  return {
190
421
  success: true,
191
422
  testsCount: tests.length,
@@ -193,24 +424,77 @@ export class Replay {
193
424
  finishParams,
194
425
  envVars,
195
426
  runId,
196
- dryRun: true
427
+ dryRun: true,
197
428
  };
198
429
  }
199
430
 
200
431
  // Create client and restore the run
201
432
  const client = new TestomatClient({
202
433
  apiKey: this.apiKey,
203
- isBatchEnabled: true,
434
+ // isBatchEnabled: true,
204
435
  ...runParams,
205
436
  });
206
437
 
438
+ if (isEnabled) {
439
+ console.log('🔧 CLIENT CONFIGURATION:');
440
+ console.log(` 🔑 API Key: ${this.apiKey ? `${this.apiKey.slice(0, 10)}...` : 'NOT SET'}`);
441
+ console.log(` 🏪 Batch enabled: ${runParams.isBatchEnabled || 'unknown'}`);
442
+ console.log(` 📡 Pipes count: ${client.pipes ? client.pipes.length : 0}`);
443
+ if (client.pipes && client.pipes.length > 0) {
444
+ client.pipes.forEach((pipe, index) => {
445
+ console.log(` ${index + 1}. ${pipe.toString()} - enabled: ${pipe.isEnabled}`);
446
+ });
447
+ } else {
448
+ console.log(` ⚠️ WARNING: No pipes configured!`);
449
+ }
450
+ console.log(` 📤 Uploader enabled: ${client.uploader ? client.uploader.isEnabled : 'unknown'}`);
451
+ console.log('');
452
+ }
453
+
207
454
  // Use the stored runId if available, otherwise create a new run
208
455
  if (runId) {
209
456
  this.onLog(`Using existing run ID: ${runId}`);
457
+ if (isEnabled) console.log(`🔄 Restoring run with ID: ${runId}`);
458
+ if (isEnabled) console.log(`📊 Run params:`, JSON.stringify(runParams, null, 2));
210
459
  client.runId = runId;
460
+ // Always call createRun to initialize batch system and update run params
461
+ if (isEnabled) console.log(`🔄 Initializing batch system for existing run...`);
462
+ // await client.createRun({ ...runParams, isBatchEnabled: true });
463
+ await client.createRun(runParams);
464
+ this.onLog(`Create new run`);
211
465
  } else {
212
466
  this.onLog('Publishing to run...');
467
+ if (isEnabled) console.log(`🆕 Creating new run with params:`, JSON.stringify(runParams, null, 2));
213
468
  await client.createRun(runParams);
469
+ if (isEnabled) console.log(`✅ New run created with ID: ${client.runId}`);
470
+ }
471
+
472
+ if (isEnabled) {
473
+ console.log('🔧 POST-INITIALIZATION STATUS:');
474
+ console.log(` 🆔 Final Run ID: ${client.runId}`);
475
+
476
+ // Check each pipe status
477
+ if (!client.pipes?.length) {
478
+ console.log(' ⚠️ No pipes found!');
479
+ } else {
480
+ client.pipes.forEach((pipe, index) => {
481
+ console.log(` 📡 Pipe ${index + 1}: ${pipe.toString()}`);
482
+ console.log(` ✅ Enabled: ${pipe.isEnabled}`);
483
+ console.log(` 🆔 Run ID: ${pipe.runId || 'NOT SET'}`);
484
+
485
+ if (!pipe.toString().includes('Testomatio') || !pipe.batch) return;
486
+
487
+ console.log(` 🔄 Batch enabled: ${pipe.batch.isEnabled}`);
488
+ console.log(` ⏱️ Batch interval: ${pipe.batch.intervalFunction ? 'RUNNING' : 'NOT RUNNING'}`);
489
+ console.log(` 📦 Tests in queue: ${pipe.batch.tests?.length || 0}`);
490
+ });
491
+ }
492
+ console.log('');
493
+ }
494
+
495
+ if (isEnabled) {
496
+ console.log('🚀 SENDING TESTS TO TESTOMAT.IO');
497
+ console.log('='.repeat(60));
214
498
  }
215
499
 
216
500
  // Send each test result
@@ -219,29 +503,119 @@ export class Replay {
219
503
 
220
504
  for (const [index, test] of tests.entries()) {
221
505
  try {
222
- await client.addTestRun(test.status, test);
506
+ if (isEnabled) {
507
+ const status = test.status === 'passed' ? '✅' : test.status === 'failed' ? '❌' : '⏭️';
508
+ console.log(`\n📤 Sending test ${index + 1}/${tests.length}: ${status} ${test.title || test.id}`);
509
+ console.log(` 📁 File: ${test.file || 'Unknown'}`);
510
+ console.log(` 🔑 RID: ${test.rid || 'No RID'}`);
511
+ console.log(` 📊 Status: ${test.status}${test.steps?.length > 0 ? ` | steps: ${test.steps.length}` : ''}`);
512
+
513
+ // Show artifacts info
514
+ if (test.artifactRecords?.length) {
515
+ console.log(` 📎 Artifacts: ${test.artifactRecords.length}`);
516
+ test.artifactRecords.forEach((artifact, i) => {
517
+ const status = artifact.uploaded ? '✅ Uploaded' : '📤 Not Uploaded';
518
+ const name = artifact.file ? path.basename(artifact.file) : 'Unknown';
519
+ console.log(` ${i + 1}. ${status}: ${name}`);
520
+ });
521
+ } else if (test.files?.length) {
522
+ console.log(` 📎 Files: ${test.files.length}`);
523
+ test.files.slice(0, 3).forEach((file, i) => {
524
+ const name =
525
+ typeof file === 'string' ? path.basename(file) : path.basename(file?.path || file?.file || 'Unknown');
526
+ console.log(` ${i + 1}. ${name}`);
527
+ });
528
+ if (test.files.length > 3) {
529
+ console.log(` ... also ${test.files.length - 3} more files`);
530
+ }
531
+ }
532
+
533
+ // Show the actual data payload being sent
534
+ const uniqueFiles = test.files
535
+ ? [
536
+ ...new Set(
537
+ test.files.map(f =>
538
+ typeof f === 'string' ? path.basename(f) : path.basename(f?.path || f?.file || ''),
539
+ ),
540
+ ),
541
+ ].length
542
+ : 0;
543
+
544
+ console.log(
545
+ ` 📦 Payload preview:`,
546
+ JSON.stringify(
547
+ {
548
+ status: test.status,
549
+ title: test.title,
550
+ id: test.id,
551
+ rid: test.rid,
552
+ uniqueFiles,
553
+ steps: test.steps ? test.steps.length : 0,
554
+ artifactRecords: test.artifactRecords ? test.artifactRecords.length : 0,
555
+ },
556
+ null,
557
+ 2,
558
+ ),
559
+ );
560
+ }
561
+
562
+ const addTestRunResult = await client.addTestRun(test.status, test);
223
563
  successCount++;
564
+
224
565
  this.onProgress({
225
566
  current: index + 1,
226
567
  total: tests.length,
227
568
  test,
228
- success: true
569
+ success: true,
229
570
  });
230
571
  } catch (err) {
231
572
  failureCount++;
573
+ if (isEnabled) {
574
+ console.log(` ❌ Failed to send: ${err.message}`);
575
+ console.log(` 🔍 Error details:`, err);
576
+ }
232
577
  this.onError(`Failed to send test ${index + 1}: ${err.message}`);
233
578
  this.onProgress({
234
579
  current: index + 1,
235
580
  total: tests.length,
236
581
  test,
237
582
  success: false,
238
- error: err.message
583
+ error: err.message,
239
584
  });
240
585
  }
241
586
  }
242
587
 
588
+ if (isEnabled) {
589
+ console.log('\n' + '='.repeat(60));
590
+ console.log('🏁 FINISHING RUN');
591
+ console.log(`📊 Finish params:`, JSON.stringify(finishParams, null, 2));
592
+ }
593
+
243
594
  await client.updateRunStatus(finishParams.status || STATUS.FINISHED, finishParams.parallel || false);
244
595
 
596
+ if (isEnabled) {
597
+ console.log(`✅ Run finished with status: ${finishParams.status || STATUS.FINISHED}`);
598
+
599
+ // Wait a bit for batch system to finish sending remaining tests
600
+ const testomatioPipe = client.pipes && client.pipes.find(p => p.toString().includes('Testomatio'));
601
+ if (
602
+ testomatioPipe &&
603
+ testomatioPipe.batch &&
604
+ testomatioPipe.batch.tests &&
605
+ testomatioPipe.batch.tests.length > 0
606
+ ) {
607
+ console.log(`⏳ Waiting for batch system to send ${testomatioPipe.batch.tests.length} remaining tests...`);
608
+ await new Promise(resolve => setTimeout(resolve, 6000)); // Wait 6 seconds for batch upload
609
+
610
+ const remainingAfterWait = testomatioPipe.batch.tests ? testomatioPipe.batch.tests.length : 0;
611
+ if (remainingAfterWait > 0) {
612
+ console.log(`⚠️ ${remainingAfterWait} tests still in batch queue after waiting`);
613
+ } else {
614
+ console.log(`✅ All tests successfully sent via batch system`);
615
+ }
616
+ }
617
+ }
618
+
245
619
  const result = {
246
620
  success: true,
247
621
  testsCount: tests.length,
@@ -250,13 +624,36 @@ export class Replay {
250
624
  runParams,
251
625
  finishParams,
252
626
  envVars,
253
- runId: runId || client.runId
627
+ runId: runId || client.runId,
254
628
  };
255
629
 
630
+ if (isEnabled) {
631
+ console.log('\n' + '='.repeat(60));
632
+ console.log('📊 FINAL SUMMARY:');
633
+ console.log(` 📋 Total tests: ${tests.length}`);
634
+ console.log(` ✅ Successfully sent: ${successCount}`);
635
+ console.log(` ❌ Failed to send: ${failureCount}`);
636
+ console.log(` 🆔 Run ID: ${runId || client.runId}`);
637
+ console.log(` 🌍 Environment: ${envVars.TESTOMATIO_ENV || 'Unknown'}`);
638
+ console.log(` 🔗 API URL: ${envVars.TESTOMATIO_URL || 'Default'}`);
639
+
640
+ // Check final batch status
641
+ if (client.pipes && client.pipes.length > 0) {
642
+ const testomatioPipe = client.pipes.find(p => p.toString().includes('Testomatio'));
643
+ if (testomatioPipe && testomatioPipe.batch) {
644
+ const remainingTests = testomatioPipe.batch.tests ? testomatioPipe.batch.tests.length : 0;
645
+ console.log(` 🔄 Final batch queue: ${remainingTests} tests remaining`);
646
+ console.log(` ⏱️ Batch interval: ${testomatioPipe.batch.intervalFunction ? 'STILL RUNNING' : 'STOPPED'}`);
647
+ }
648
+ }
649
+
650
+ console.log('='.repeat(60));
651
+ }
652
+
256
653
  this.onLog(`Successfully replayed ${successCount}/${tests.length} tests from debug file`);
257
654
 
258
655
  return result;
259
656
  }
260
657
  }
261
658
 
262
- export default Replay;
659
+ export default Replay;
@@ -0,0 +1,12 @@
1
+ export const extensionMap = {
2
+ 'application/json': 'json',
3
+ 'text/plain': 'txt',
4
+ 'image/jpeg': 'jpg',
5
+ 'image/png': 'png',
6
+ 'text/html': 'html',
7
+ 'text/css': 'css',
8
+ 'text/javascript': 'js',
9
+ 'application/pdf': 'pdf',
10
+ 'application/xml': 'xml',
11
+ 'text/xml': 'xml',
12
+ };