@testomatio/reporter 2.0.1-beta.8 → 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.
- package/lib/adapter/playwright.js +7 -3
- package/lib/pipe/debug.d.ts +1 -0
- package/lib/pipe/debug.js +25 -10
- package/lib/replay.d.ts +13 -0
- package/lib/replay.js +404 -16
- package/lib/utils/constants.d.ts +12 -0
- package/lib/utils/constants.js +15 -0
- package/package.json +1 -1
- package/src/adapter/playwright.js +6 -7
- package/src/pipe/debug.js +27 -12
- package/src/replay.js +415 -18
- package/src/utils/constants.js +12 -0
|
@@ -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
|
@@ -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
|
|
|
@@ -125,20 +126,18 @@ class PlaywrightReporter {
|
|
|
125
126
|
#getArtifactPath(artifact) {
|
|
126
127
|
if (artifact.path) {
|
|
127
128
|
if (path.isAbsolute(artifact.path)) return artifact.path;
|
|
128
|
-
|
|
129
129
|
return path.join(this.config.outputDir || this.config.projects[0].outputDir, artifact.path);
|
|
130
130
|
}
|
|
131
|
-
|
|
132
131
|
if (artifact.body) {
|
|
133
132
|
let filePath = generateTmpFilepath(artifact.name);
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
133
|
+
const hasExtension = artifact.name && path.extname(artifact.name);
|
|
134
|
+
if (!hasExtension && artifact.contentType) {
|
|
135
|
+
const extension = extensionMap[artifact.contentType] || artifact.contentType.split('/')[1];
|
|
136
|
+
if (extension) filePath += `.${extension}`;
|
|
137
|
+
}
|
|
138
138
|
fs.writeFileSync(filePath, artifact.body);
|
|
139
139
|
return filePath;
|
|
140
140
|
}
|
|
141
|
-
|
|
142
141
|
return null;
|
|
143
142
|
}
|
|
144
143
|
|
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
|
|
30
|
-
|
|
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
|
|
33
|
-
if (fs.existsSync(
|
|
34
|
-
fs.
|
|
32
|
+
// Remove existing latest file if it exists
|
|
33
|
+
if (fs.existsSync(this.latestFilePath)) {
|
|
34
|
+
fs.rmSync(this.latestFilePath);
|
|
35
35
|
}
|
|
36
|
-
//
|
|
37
|
-
fs.
|
|
38
|
-
debug('Created
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
//
|
|
76
|
-
|
|
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
|
-
//
|
|
79
|
-
mergedTest.artifacts =
|
|
80
|
-
} else if (
|
|
81
|
-
|
|
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
|
|
107
|
-
const mergedTest = { ...existingTest
|
|
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,
|
|
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
|
-
|
|
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;
|