playwright-slack-report-burak 3.0.32 ā 3.0.34
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/dist/src/ResultsParser.js +146 -359
- package/package.json +1 -1
|
@@ -9,9 +9,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
9
9
|
const fs = require('fs');
|
|
10
10
|
const path = require('path');
|
|
11
11
|
const axios = require('axios');
|
|
12
|
-
const { exec
|
|
13
|
-
const { promisify } = require('util');
|
|
14
|
-
const execAsync = promisify(exec);
|
|
12
|
+
const { exec } = require('child_process');
|
|
15
13
|
let AdmZip;
|
|
16
14
|
try {
|
|
17
15
|
AdmZip = require('adm-zip');
|
|
@@ -20,17 +18,9 @@ try {
|
|
|
20
18
|
AdmZip = null;
|
|
21
19
|
}
|
|
22
20
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const packageJsonPath = path.join(__dirname, '../../package.json');
|
|
27
|
-
if (fs.existsSync(packageJsonPath)) {
|
|
28
|
-
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
29
|
-
packageVersion = packageJson.version || 'unknown';
|
|
30
|
-
}
|
|
31
|
-
} catch (e) {
|
|
32
|
-
// Version detection failed, use default
|
|
33
|
-
}
|
|
21
|
+
const SUMMARIES_DIR = path.join('./', 'playwright-report');
|
|
22
|
+
const NODE_ARTIFACTS_DIR = path.join(SUMMARIES_DIR, 'nodeArtifacts');
|
|
23
|
+
const FINAL_OUTPUT_DIR = path.join(SUMMARIES_DIR, 'finalOutput');
|
|
34
24
|
|
|
35
25
|
class ResultsParser {
|
|
36
26
|
result;
|
|
@@ -45,77 +35,6 @@ class ResultsParser {
|
|
|
45
35
|
constructor(options = { separateFlakyTests: false }) {
|
|
46
36
|
this.result = [];
|
|
47
37
|
this.separateFlakyTests = options.separateFlakyTests;
|
|
48
|
-
// Log package version
|
|
49
|
-
console.log(`š¦ [ResultsParser] playwright-slack-report-burak v${packageVersion}`);
|
|
50
|
-
}
|
|
51
|
-
/**
|
|
52
|
-
* Recursively print directory tree structure
|
|
53
|
-
* @param {string} dirPath - Directory path to print
|
|
54
|
-
* @param {string} prefix - Prefix for tree visualization
|
|
55
|
-
* @param {number} maxDepth - Maximum depth to traverse (default: 5)
|
|
56
|
-
*/
|
|
57
|
-
printDirectoryTree(dirPath, prefix = '', maxDepth = 5) {
|
|
58
|
-
if (maxDepth <= 0) return;
|
|
59
|
-
if (!fs.existsSync(dirPath)) {
|
|
60
|
-
console.log(`${prefix}${path.basename(dirPath)}/ (does not exist)`);
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
try {
|
|
65
|
-
const stats = fs.statSync(dirPath);
|
|
66
|
-
if (!stats.isDirectory()) {
|
|
67
|
-
const size = stats.size;
|
|
68
|
-
const sizeStr = size > 1024 ? `${(size / 1024).toFixed(2)}KB` : `${size}B`;
|
|
69
|
-
console.log(`${prefix}${path.basename(dirPath)} (${sizeStr})`);
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const dirName = path.basename(dirPath) || dirPath;
|
|
74
|
-
console.log(`${prefix}${dirName}/`);
|
|
75
|
-
|
|
76
|
-
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
77
|
-
entries.sort((a, b) => {
|
|
78
|
-
if (a.isDirectory() && !b.isDirectory()) return -1;
|
|
79
|
-
if (!a.isDirectory() && b.isDirectory()) return 1;
|
|
80
|
-
return a.name.localeCompare(b.name);
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
entries.forEach((entry, index) => {
|
|
84
|
-
const isLast = index === entries.length - 1;
|
|
85
|
-
const newPrefix = prefix + (isLast ? 'āāā ' : 'āāā ');
|
|
86
|
-
const nextPrefix = prefix + (isLast ? ' ' : 'ā ');
|
|
87
|
-
const fullPath = path.join(dirPath, entry.name);
|
|
88
|
-
|
|
89
|
-
if (entry.isDirectory()) {
|
|
90
|
-
this.printDirectoryTree(fullPath, nextPrefix, maxDepth - 1);
|
|
91
|
-
} else {
|
|
92
|
-
try {
|
|
93
|
-
const fileStats = fs.statSync(fullPath);
|
|
94
|
-
const size = fileStats.size;
|
|
95
|
-
const sizeStr = size > 1024 * 1024
|
|
96
|
-
? `${(size / (1024 * 1024)).toFixed(2)}MB`
|
|
97
|
-
: size > 1024
|
|
98
|
-
? `${(size / 1024).toFixed(2)}KB`
|
|
99
|
-
: `${size}B`;
|
|
100
|
-
console.log(`${newPrefix}${entry.name} (${sizeStr})`);
|
|
101
|
-
} catch (err) {
|
|
102
|
-
console.log(`${newPrefix}${entry.name} (error reading)`);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
});
|
|
106
|
-
} catch (error) {
|
|
107
|
-
console.log(`${prefix}${path.basename(dirPath)}/ (error: ${error.message})`);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Update shard information after detection (e.g., from GitHub API)
|
|
113
|
-
* @param {number} shardIndex - The current shard index (1-based, shard 1 is aggregator)
|
|
114
|
-
* @param {number} totalShardCount - The total number of shards
|
|
115
|
-
*/
|
|
116
|
-
updateShardInfo(shardIndex, totalShardCount) {
|
|
117
|
-
this.shardIndex = shardIndex;
|
|
118
|
-
this.totalShardCount = totalShardCount;
|
|
119
38
|
}
|
|
120
39
|
async getParsedResults() {
|
|
121
40
|
const summary = {
|
|
@@ -135,17 +54,15 @@ class ResultsParser {
|
|
|
135
54
|
}*/
|
|
136
55
|
|
|
137
56
|
// Define the directory to store node summaries
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
if (!fs.existsSync(summariesDir)) {
|
|
141
|
-
fs.mkdirSync(summariesDir, { recursive: true });
|
|
57
|
+
if (!fs.existsSync(SUMMARIES_DIR)) {
|
|
58
|
+
fs.mkdirSync(SUMMARIES_DIR, { recursive: true });
|
|
142
59
|
}
|
|
143
60
|
|
|
144
61
|
// Determine the current node index
|
|
145
62
|
const currentNodeIndex = this.shardIndex;
|
|
146
63
|
|
|
147
64
|
// Define the file for the current node's summary
|
|
148
|
-
const nodeSummaryFile = path.join(
|
|
65
|
+
const nodeSummaryFile = path.join(SUMMARIES_DIR, `node_summary_${currentNodeIndex}.json`);
|
|
149
66
|
|
|
150
67
|
const totalTestCasesForNode = this.result.reduce((acc, suite) => acc + suite.testSuite.tests.length, 0);
|
|
151
68
|
|
|
@@ -174,9 +91,11 @@ class ResultsParser {
|
|
|
174
91
|
|
|
175
92
|
if (this.shardIndex === 1 && this.totalShardCount > 1) {
|
|
176
93
|
if (process.env.CI) {
|
|
94
|
+
console.log('Fetching all artifacts...');
|
|
177
95
|
await this.fetchAllArtifacts();
|
|
178
96
|
} else {
|
|
179
97
|
while (!this.allNodeSummaryFilesExist() || !this.allBlobZipsExist()) {
|
|
98
|
+
console.log('Waiting for all both to exist...');
|
|
180
99
|
await new Promise(resolve => setTimeout(resolve, 1000)); // Wait for 1 second
|
|
181
100
|
}
|
|
182
101
|
}
|
|
@@ -184,9 +103,8 @@ class ResultsParser {
|
|
|
184
103
|
|
|
185
104
|
if (this.shardIndex === 1 && this.allNodeSummaryFilesExist() && this.allBlobZipsExist()) {
|
|
186
105
|
// Merge all node summaries into the final summary
|
|
187
|
-
// Loop from 1 to totalShardCount (1-based indexing)
|
|
188
106
|
for (let i = 1; i <= this.totalShardCount; i++) {
|
|
189
|
-
const nodeSummaryFile = path.join(
|
|
107
|
+
const nodeSummaryFile = path.join(SUMMARIES_DIR, `node_summary_${i}.json`);
|
|
190
108
|
const fileToRead = nodeSummaryFile;
|
|
191
109
|
|
|
192
110
|
if (fs.existsSync(fileToRead)) {
|
|
@@ -221,7 +139,7 @@ class ResultsParser {
|
|
|
221
139
|
}
|
|
222
140
|
}
|
|
223
141
|
|
|
224
|
-
|
|
142
|
+
this.mergeReports();
|
|
225
143
|
}
|
|
226
144
|
return summary;
|
|
227
145
|
}
|
|
@@ -398,11 +316,9 @@ class ResultsParser {
|
|
|
398
316
|
}*/
|
|
399
317
|
allNodeSummaryFilesExist() {
|
|
400
318
|
console.log('Checking if all node summary files exist...');
|
|
401
|
-
const summariesDir = path.join('./', 'playwright-report');
|
|
402
319
|
|
|
403
|
-
// Check files from 1 to totalShardCount (1-based indexing)
|
|
404
320
|
for (let i = 1; i <= this.totalShardCount; i++) {
|
|
405
|
-
const nodeSummaryFile = path.join(
|
|
321
|
+
const nodeSummaryFile = path.join(SUMMARIES_DIR, `node_summary_${i}.json`);
|
|
406
322
|
if (!fs.existsSync(nodeSummaryFile)) {
|
|
407
323
|
return false;
|
|
408
324
|
}
|
|
@@ -412,11 +328,9 @@ class ResultsParser {
|
|
|
412
328
|
|
|
413
329
|
allBlobZipsExist() {
|
|
414
330
|
console.log('Checking if all blob zips exist...');
|
|
415
|
-
const summariesDir = path.join('./', 'playwright-report');
|
|
416
331
|
|
|
417
|
-
// Check files from 1 to totalShardCount (1-based indexing)
|
|
418
332
|
for (let i = 1; i <= this.totalShardCount; i++) {
|
|
419
|
-
const blobZipFile = path.join(
|
|
333
|
+
const blobZipFile = path.join(NODE_ARTIFACTS_DIR, `blob-report-node-${i}.zip`);
|
|
420
334
|
if (!fs.existsSync(blobZipFile)) {
|
|
421
335
|
return false;
|
|
422
336
|
}
|
|
@@ -425,81 +339,63 @@ class ResultsParser {
|
|
|
425
339
|
}
|
|
426
340
|
|
|
427
341
|
async fetchAllArtifacts() {
|
|
428
|
-
|
|
429
|
-
if (!fs.existsSync(
|
|
430
|
-
fs.mkdirSync(
|
|
342
|
+
// Ensure playwright-report directory exists
|
|
343
|
+
if (!fs.existsSync(SUMMARIES_DIR)) {
|
|
344
|
+
fs.mkdirSync(SUMMARIES_DIR, { recursive: true });
|
|
431
345
|
}
|
|
432
346
|
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
// We need to fetch from shards 2, 3, 4, ... (all except shard 1, using 1-based indexing)
|
|
437
|
-
for (let i = 1; i <= this.totalShardCount; i++) {
|
|
438
|
-
if (i === 1) continue; // Skip shard 1 (we are shard 1)
|
|
439
|
-
console.log(`Shard 1: Fetching artifacts from shard ${i}...`);
|
|
440
|
-
await this.fetchArtifact(i, `node_summary_${i}.json`, summariesDir);
|
|
441
|
-
await this.fetchArtifact(i, `blob-report-node-${i}.zip`, summariesDir);
|
|
347
|
+
// Create nodeArtifacts directory under playwright-report
|
|
348
|
+
if (!fs.existsSync(NODE_ARTIFACTS_DIR)) {
|
|
349
|
+
fs.mkdirSync(NODE_ARTIFACTS_DIR, { recursive: true });
|
|
442
350
|
}
|
|
443
351
|
|
|
444
|
-
//
|
|
445
|
-
|
|
446
|
-
const
|
|
352
|
+
// Copy artifacts from node 1 to nodeArtifacts
|
|
353
|
+
// Check multiple potential locations for node 1's blob file
|
|
354
|
+
const possibleNode1BlobSources = [
|
|
355
|
+
path.join(SUMMARIES_DIR, 'blob-report-node-1.zip'),
|
|
356
|
+
path.join('./', 'blob-report-node-1.zip'),
|
|
357
|
+
path.join(SUMMARIES_DIR, 'blob-report-1.zip'),
|
|
358
|
+
];
|
|
447
359
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
if (!fs.existsSync(nodeSummaryFile)) {
|
|
458
|
-
console.log(`Shard 1: Re-fetching missing node_summary_${i}.json...`);
|
|
459
|
-
await this.fetchArtifact(i, `node_summary_${i}.json`, summariesDir);
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
if (!fs.existsSync(blobZipFile)) {
|
|
463
|
-
console.log(`Shard 1: Re-fetching missing blob-report-node-${i}.zip...`);
|
|
464
|
-
await this.fetchArtifact(i, `blob-report-node-${i}.zip`, summariesDir);
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
retryCount++;
|
|
469
|
-
if (!this.allNodeSummaryFilesExist() || !this.allBlobZipsExist()) {
|
|
470
|
-
await new Promise(resolve => setTimeout(resolve, 10000)); // Wait 10 seconds before retry
|
|
360
|
+
const node1BlobDest = path.join(NODE_ARTIFACTS_DIR, 'blob-report-node-1.zip');
|
|
361
|
+
let node1BlobCopied = false;
|
|
362
|
+
|
|
363
|
+
for (const source of possibleNode1BlobSources) {
|
|
364
|
+
if (fs.existsSync(source)) {
|
|
365
|
+
fs.copyFileSync(source, node1BlobDest);
|
|
366
|
+
console.log(`Copied ${source} to nodeArtifacts`);
|
|
367
|
+
node1BlobCopied = true;
|
|
368
|
+
break;
|
|
471
369
|
}
|
|
472
370
|
}
|
|
473
371
|
|
|
474
|
-
if (
|
|
475
|
-
console.
|
|
476
|
-
}
|
|
477
|
-
|
|
372
|
+
if (!node1BlobCopied) {
|
|
373
|
+
console.warn('Warning: blob-report-node-1.zip not found in expected locations');
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
while (!this.allNodeSummaryFilesExist() || !this.allBlobZipsExist()) {
|
|
377
|
+
console.log('Waiting for all blob zips to exist...');
|
|
378
|
+
if (!fs.existsSync(SUMMARIES_DIR)) {
|
|
379
|
+
fs.mkdirSync(SUMMARIES_DIR, { recursive: true });
|
|
380
|
+
}
|
|
381
|
+
for (let i = 2; i <= this.totalShardCount; i++) {
|
|
382
|
+
await this.fetchArtifact(i, `node_summary_${i}.json`, SUMMARIES_DIR);
|
|
383
|
+
await this.fetchArtifact(i, `blob-report-node-${i}.zip`, NODE_ARTIFACTS_DIR);
|
|
384
|
+
}
|
|
478
385
|
}
|
|
479
386
|
}
|
|
480
387
|
|
|
481
|
-
async fetchArtifact(i, file,
|
|
482
|
-
const filePath = path.join(
|
|
388
|
+
async fetchArtifact(i, file, targetDir) {
|
|
389
|
+
const filePath = path.join(targetDir, file);
|
|
483
390
|
await this.fetchArtifactFromGitHubActions(i, file, filePath);
|
|
484
391
|
}
|
|
485
392
|
|
|
486
393
|
async fetchArtifactFromGitHubActions(i, file, filePath) {
|
|
487
394
|
const githubToken = process.env.SAFETYWINGTEST_GITHUB_TOKEN;
|
|
488
395
|
const repo = process.env.GITHUB_REPOSITORY;
|
|
489
|
-
const runId = process.env.GITHUB_RUN_ID;
|
|
490
|
-
|
|
491
|
-
if (!githubToken || !repo) {
|
|
492
|
-
console.error('GitHub Actions: Missing SAFETYWINGTEST_GITHUB_TOKEN or GITHUB_REPOSITORY');
|
|
493
|
-
return;
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
// GitHub Actions artifact API endpoint
|
|
497
396
|
const githubApiUrl = `https://api.github.com/repos/${repo}/actions/artifacts`;
|
|
498
397
|
|
|
499
|
-
// Try multiple artifact naming patterns
|
|
500
|
-
// 1. html-report-{i} (1-based workflow style)
|
|
501
|
-
// 2. blob-report-node-{i} (direct blob reports)
|
|
502
|
-
// 3. test-results-{i} (alternative naming)
|
|
398
|
+
// Try multiple artifact naming patterns
|
|
503
399
|
const artifactNamePatterns = [
|
|
504
400
|
`html-report-${i}`,
|
|
505
401
|
`blob-report-node-${i}`,
|
|
@@ -527,10 +423,7 @@ class ResultsParser {
|
|
|
527
423
|
a.name.includes(`shard-${i}`) ||
|
|
528
424
|
a.name.includes(`node-${i}`)
|
|
529
425
|
);
|
|
530
|
-
if (artifact)
|
|
531
|
-
console.log(`Found artifact: ${artifact.name} (matched pattern: ${pattern})`);
|
|
532
|
-
break;
|
|
533
|
-
}
|
|
426
|
+
if (artifact) break;
|
|
534
427
|
}
|
|
535
428
|
|
|
536
429
|
if (artifact && artifact.archive_download_url) {
|
|
@@ -550,69 +443,15 @@ class ResultsParser {
|
|
|
550
443
|
|
|
551
444
|
// Extract ZIP and find the specific file
|
|
552
445
|
const zip = new AdmZip(downloadResponse.data);
|
|
553
|
-
|
|
554
|
-
// Try direct file match first
|
|
555
|
-
let zipEntry = zip.getEntry(file);
|
|
556
|
-
|
|
557
|
-
// If not found, try common path variations
|
|
558
|
-
if (!zipEntry) {
|
|
559
|
-
const possiblePaths = [
|
|
560
|
-
file,
|
|
561
|
-
`playwright-report/${file}`,
|
|
562
|
-
`html-report-${i}/${file}`,
|
|
563
|
-
`html-report-${i}/playwright-report/${file}`,
|
|
564
|
-
];
|
|
565
|
-
for (const possiblePath of possiblePaths) {
|
|
566
|
-
zipEntry = zip.getEntry(possiblePath);
|
|
567
|
-
if (zipEntry) break;
|
|
568
|
-
}
|
|
569
|
-
}
|
|
446
|
+
const zipEntry = zip.getEntry(file) || zip.getEntry(`playwright-report/${file}`);
|
|
570
447
|
|
|
571
448
|
if (zipEntry) {
|
|
572
449
|
fs.writeFileSync(filePath, zipEntry.getData());
|
|
573
|
-
console.log(`Successfully fetched file ${file} from
|
|
450
|
+
console.log(`Successfully fetched file ${file} from shard ${i}`);
|
|
574
451
|
break;
|
|
575
452
|
} else {
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
zip.extractAllTo(extractPath, true);
|
|
579
|
-
|
|
580
|
-
// Look for the file recursively
|
|
581
|
-
const searchInDir = (dir, targetFile) => {
|
|
582
|
-
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
583
|
-
for (const entry of entries) {
|
|
584
|
-
const fullPath = path.join(dir, entry.name);
|
|
585
|
-
if (entry.isDirectory()) {
|
|
586
|
-
const found = searchInDir(fullPath, targetFile);
|
|
587
|
-
if (found) return found;
|
|
588
|
-
} else {
|
|
589
|
-
// Check exact match
|
|
590
|
-
if (entry.name === targetFile) {
|
|
591
|
-
return fullPath;
|
|
592
|
-
}
|
|
593
|
-
// Check if ends with target file
|
|
594
|
-
if (entry.name.endsWith(targetFile)) {
|
|
595
|
-
return fullPath;
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
return null;
|
|
600
|
-
};
|
|
601
|
-
|
|
602
|
-
// Try to find the file recursively
|
|
603
|
-
let foundPath = searchInDir(extractPath, file);
|
|
604
|
-
|
|
605
|
-
if (foundPath) {
|
|
606
|
-
fs.copyFileSync(foundPath, filePath);
|
|
607
|
-
console.log(`Successfully fetched file ${file} from GitHub Actions shard ${i} (artifact: ${artifact.name})`);
|
|
608
|
-
// Clean up temp directory
|
|
609
|
-
fs.rmSync(extractPath, { recursive: true, force: true });
|
|
610
|
-
break;
|
|
611
|
-
} else {
|
|
612
|
-
console.warn(`File ${file} not found in artifact ${artifact.name}. Retrying in 10 seconds...`);
|
|
613
|
-
fs.rmSync(extractPath, { recursive: true, force: true });
|
|
614
|
-
await new Promise(resolve => setTimeout(resolve, 10000));
|
|
615
|
-
}
|
|
453
|
+
console.warn(`File ${file} not found in artifact ${artifact.name}. Retrying in 10 seconds...`);
|
|
454
|
+
await new Promise(resolve => setTimeout(resolve, 10000));
|
|
616
455
|
}
|
|
617
456
|
} else {
|
|
618
457
|
console.warn(`Artifact not found (tried: ${artifactNamePatterns.join(', ')}). Retrying in 10 seconds...`);
|
|
@@ -620,177 +459,125 @@ class ResultsParser {
|
|
|
620
459
|
}
|
|
621
460
|
} catch (error) {
|
|
622
461
|
if (error.response && error.response.status === 404) {
|
|
623
|
-
console.warn(`
|
|
462
|
+
console.warn(`File ${file} not found. Retrying in 10 seconds...`);
|
|
624
463
|
await new Promise(resolve => setTimeout(resolve, 10000));
|
|
625
464
|
} else {
|
|
626
|
-
console.error(`Failed to fetch
|
|
627
|
-
|
|
465
|
+
console.error(`Failed to fetch file ${file} from shard ${i}!`, error);
|
|
466
|
+
break;
|
|
628
467
|
}
|
|
629
468
|
}
|
|
630
469
|
}
|
|
631
470
|
}
|
|
632
|
-
|
|
471
|
+
mergeReports() {
|
|
472
|
+
let breakMergeWaiter = false;
|
|
633
473
|
try {
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
console.log('\n=== Starting merge process ===');
|
|
639
|
-
console.log(`Summaries directory: ${path.resolve(summariesDir)}`);
|
|
640
|
-
console.log(`Blob reports directory: ${path.resolve(blobReportsDir)}`);
|
|
641
|
-
console.log(`Merged blobs directory: ${path.resolve(mergedBlobsDir)}`);
|
|
642
|
-
|
|
643
|
-
// Show initial directory structure
|
|
644
|
-
console.log('\nš Initial playwright-report directory structure:');
|
|
645
|
-
if (fs.existsSync(summariesDir)) {
|
|
646
|
-
this.printDirectoryTree(summariesDir);
|
|
647
|
-
} else {
|
|
648
|
-
console.log(' (directory does not exist yet)');
|
|
474
|
+
// Ensure playwright-report directory exists
|
|
475
|
+
if (!fs.existsSync(SUMMARIES_DIR)) {
|
|
476
|
+
fs.mkdirSync(SUMMARIES_DIR, { recursive: true });
|
|
649
477
|
}
|
|
650
478
|
|
|
651
|
-
// Create directory
|
|
652
|
-
if (!fs.existsSync(
|
|
653
|
-
|
|
479
|
+
// Create finalOutput directory under playwright-report
|
|
480
|
+
if (!fs.existsSync(FINAL_OUTPUT_DIR)) {
|
|
481
|
+
fs.mkdirSync(FINAL_OUTPUT_DIR, { recursive: true });
|
|
654
482
|
}
|
|
655
483
|
|
|
656
|
-
//
|
|
657
|
-
|
|
658
|
-
|
|
484
|
+
// Extract blob files before merging (Playwright merge-reports expects extracted blob directories)
|
|
485
|
+
// We'll extract them temporarily for merging, then extract again for resource copying
|
|
486
|
+
const tempMergeDir = path.join(NODE_ARTIFACTS_DIR, 'temp-merge');
|
|
487
|
+
if (!fs.existsSync(tempMergeDir)) {
|
|
488
|
+
fs.mkdirSync(tempMergeDir, { recursive: true });
|
|
659
489
|
}
|
|
660
490
|
|
|
661
|
-
// Extract all blob
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
const blobZipFile = path.join(summariesDir, `blob-report-node-${i}.zip`);
|
|
666
|
-
if (fs.existsSync(blobZipFile)) {
|
|
667
|
-
if (!AdmZip) {
|
|
668
|
-
console.warn('adm-zip is required for blob extraction. Falling back to HTML report merge.');
|
|
669
|
-
break;
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
const zip = new AdmZip(blobZipFile);
|
|
673
|
-
const extractPath = path.join(blobReportsDir, `node-${i}`);
|
|
674
|
-
zip.extractAllTo(extractPath, true);
|
|
675
|
-
console.log(`Extracted blob report from shard ${i}`);
|
|
676
|
-
hasBlobReports = true;
|
|
677
|
-
}
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
// Show directory structure after extraction
|
|
681
|
-
if (hasBlobReports) {
|
|
682
|
-
console.log('\nš Directory structure after blob extraction:');
|
|
683
|
-
this.printDirectoryTree(summariesDir);
|
|
491
|
+
// Extract all blob zip files to temp directory for merging
|
|
492
|
+
if (!AdmZip) {
|
|
493
|
+
console.error('adm-zip is required for blob extraction. Please install it: npm install adm-zip');
|
|
494
|
+
return;
|
|
684
495
|
}
|
|
685
496
|
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
let tempConfigPath = null;
|
|
690
|
-
try {
|
|
691
|
-
// Merge blob reports - use config file to specify output directory
|
|
692
|
-
// This prevents wiping the playwright-report directory
|
|
693
|
-
const blobReportsRelativePath = path.relative(summariesDir, blobReportsDir);
|
|
694
|
-
const mergedBlobsAbsolutePath = path.resolve(mergedBlobsDir);
|
|
695
|
-
console.log(`Merging blob reports from: ${blobReportsRelativePath}`);
|
|
696
|
-
console.log(`Output directory: ${mergedBlobsAbsolutePath}`);
|
|
697
|
-
|
|
698
|
-
// Create temporary config file to specify output folder
|
|
699
|
-
tempConfigPath = path.join(summariesDir, 'merge-config.js');
|
|
700
|
-
const tempConfigAbsolutePath = path.resolve(tempConfigPath);
|
|
701
|
-
// Escape backslashes and single quotes in path for JavaScript string
|
|
702
|
-
const escapedPath = mergedBlobsAbsolutePath.replace(/\\/g, '/').replace(/'/g, "\\'");
|
|
703
|
-
const configContent = `module.exports = {
|
|
704
|
-
reporter: [['html', { outputFolder: '${escapedPath}' }]]
|
|
705
|
-
};`;
|
|
706
|
-
fs.writeFileSync(tempConfigPath, configContent);
|
|
707
|
-
console.log(`Created temporary config file: ${tempConfigAbsolutePath}`);
|
|
708
|
-
console.log(`Config content: ${configContent}`);
|
|
709
|
-
|
|
710
|
-
// Use absolute path for config file to avoid path resolution issues
|
|
711
|
-
const mergeCommand = `npx playwright merge-reports --reporter html --config "${tempConfigAbsolutePath}" "${blobReportsRelativePath}"`;
|
|
712
|
-
console.log(`Executing: ${mergeCommand}`);
|
|
713
|
-
const mergeResult = await execAsync(mergeCommand, { cwd: summariesDir });
|
|
714
|
-
console.log('Merge command output:', mergeResult.stdout);
|
|
715
|
-
if (mergeResult.stderr) {
|
|
716
|
-
console.warn('Merge command stderr:', mergeResult.stderr);
|
|
717
|
-
}
|
|
718
|
-
console.log('Blob reports merged successfully with traces preserved.');
|
|
719
|
-
|
|
720
|
-
// Show directory structure after merge
|
|
721
|
-
console.log('\nš Directory structure after merge:');
|
|
722
|
-
this.printDirectoryTree(summariesDir);
|
|
723
|
-
console.log('\nš Merged blobs directory contents:');
|
|
724
|
-
this.printDirectoryTree(mergedBlobsDir);
|
|
725
|
-
} catch (error) {
|
|
726
|
-
console.warn('Warning: Failed to merge blob reports. Falling back to HTML merge.', error.message);
|
|
727
|
-
if (error.stdout) console.log('Merge stdout:', error.stdout);
|
|
728
|
-
if (error.stderr) console.warn('Merge stderr:', error.stderr);
|
|
729
|
-
|
|
730
|
-
// Show directory structure after failed merge
|
|
731
|
-
console.log('\nš Directory structure after failed merge:');
|
|
732
|
-
this.printDirectoryTree(summariesDir);
|
|
733
|
-
|
|
734
|
-
// Fallback to HTML merge
|
|
735
|
-
execSync(`npx playwright merge-reports --reporter html -c ./playwright-report playwright-report`, { stdio: 'inherit' });
|
|
736
|
-
} finally {
|
|
737
|
-
// Clean up temporary config file
|
|
738
|
-
if (tempConfigPath) {
|
|
739
|
-
const configPathToDelete = fs.existsSync(tempConfigPath) ? tempConfigPath : path.resolve(tempConfigPath);
|
|
740
|
-
if (fs.existsSync(configPathToDelete)) {
|
|
497
|
+
for (let i = 1; i <= this.totalShardCount; i++) {
|
|
498
|
+
const blobZipFile = path.join(NODE_ARTIFACTS_DIR, `blob-report-node-${i}.zip`);
|
|
499
|
+
if (fs.existsSync(blobZipFile)) {
|
|
741
500
|
try {
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
501
|
+
const zip = new AdmZip(blobZipFile);
|
|
502
|
+
zip.extractAllTo(tempMergeDir, true);
|
|
503
|
+
console.log(`Extracted blob-report-node-${i}.zip for merging`);
|
|
504
|
+
} catch (error) {
|
|
505
|
+
console.warn(`Failed to extract blob-report-node-${i}.zip for merging:`, error.message);
|
|
746
506
|
}
|
|
747
|
-
}
|
|
748
507
|
}
|
|
749
|
-
}
|
|
750
|
-
} else {
|
|
751
|
-
// Fallback to HTML merge if no blob reports found
|
|
752
|
-
console.log('No blob reports found, falling back to HTML report merge...');
|
|
753
|
-
execSync(`npx playwright merge-reports --reporter html -c ./playwright-report playwright-report`, { stdio: 'inherit' });
|
|
754
508
|
}
|
|
755
509
|
|
|
756
|
-
//
|
|
757
|
-
|
|
758
|
-
const startWaitTime = new Date().getTime();
|
|
759
|
-
const indexHtmlPath = path.join(mergedBlobsDir, 'index.html');
|
|
510
|
+
// Execute the command to merge reports FROM tempMergeDir INTO finalOutput
|
|
511
|
+
exec(`npx playwright merge-reports --reporter html ${tempMergeDir} ${FINAL_OUTPUT_DIR}`);
|
|
760
512
|
|
|
761
|
-
|
|
762
|
-
while (!fs.existsSync(
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
if (
|
|
768
|
-
|
|
769
|
-
this.printDirectoryTree(summariesDir);
|
|
770
|
-
console.log('\nš Merged blobs directory detailed contents:');
|
|
771
|
-
this.printDirectoryTree(mergedBlobsDir);
|
|
772
|
-
} else {
|
|
773
|
-
console.log('Merged blobs directory does not exist!');
|
|
513
|
+
// Wait until index.html exists in finalOutput
|
|
514
|
+
while (!fs.existsSync(path.join(FINAL_OUTPUT_DIR, 'index.html'))) {
|
|
515
|
+
console.log('Waiting 2 seconds for merged html report to be generated...');
|
|
516
|
+
const currentTime = new Date().getTime();
|
|
517
|
+
breakMergeWaiter = false;
|
|
518
|
+
while (!breakMergeWaiter) {
|
|
519
|
+
if(new Date().getTime() - currentTime > 2000) {
|
|
520
|
+
breakMergeWaiter = true;
|
|
774
521
|
}
|
|
775
|
-
break;
|
|
776
522
|
}
|
|
777
|
-
console.log(`Waiting for merged html report to be generated... (${Math.round(elapsed/1000)}s elapsed)`);
|
|
778
|
-
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
779
523
|
}
|
|
524
|
+
console.log('Reports merged successfully.');
|
|
780
525
|
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
console.log('\nš Final directory structure:');
|
|
784
|
-
this.printDirectoryTree(summariesDir);
|
|
785
|
-
} else {
|
|
786
|
-
console.warn('\nā ļø Warning: Merged report index.html not found, but continuing...');
|
|
787
|
-
console.log('\nš Final directory structure:');
|
|
788
|
-
this.printDirectoryTree(summariesDir);
|
|
789
|
-
}
|
|
526
|
+
// Once merge is complete, unzip all blobs in nodeArtifacts into separate folders
|
|
527
|
+
this.unzipBlobsAndCopyResources(NODE_ARTIFACTS_DIR, FINAL_OUTPUT_DIR);
|
|
790
528
|
} catch (error) {
|
|
791
529
|
// Log a warning instead of throwing an error
|
|
792
530
|
console.warn('Warning: Failed to merge reports. This may not affect the overall process.', error.message);
|
|
793
531
|
}
|
|
794
532
|
}
|
|
533
|
+
|
|
534
|
+
unzipBlobsAndCopyResources(nodeArtifactsDir, finalOutputDir) {
|
|
535
|
+
if (!AdmZip) {
|
|
536
|
+
console.error('adm-zip is required for blob extraction. Please install it: npm install adm-zip');
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// Unzip all blob files in nodeArtifacts into separate folders
|
|
541
|
+
for (let i = 1; i <= this.totalShardCount; i++) {
|
|
542
|
+
const blobZipFile = path.join(nodeArtifactsDir, `blob-report-node-${i}.zip`);
|
|
543
|
+
if (fs.existsSync(blobZipFile)) {
|
|
544
|
+
const extractDir = path.join(nodeArtifactsDir, `node-${i}`);
|
|
545
|
+
if (!fs.existsSync(extractDir)) {
|
|
546
|
+
fs.mkdirSync(extractDir, { recursive: true });
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
try {
|
|
550
|
+
const zip = new AdmZip(blobZipFile);
|
|
551
|
+
zip.extractAllTo(extractDir, true);
|
|
552
|
+
console.log(`Extracted blob-report-node-${i}.zip to ${extractDir}`);
|
|
553
|
+
|
|
554
|
+
// Check if this folder contains a "resources" folder with zip files
|
|
555
|
+
const resourcesDir = path.join(extractDir, 'resources');
|
|
556
|
+
if (fs.existsSync(resourcesDir) && fs.statSync(resourcesDir).isDirectory()) {
|
|
557
|
+
const files = fs.readdirSync(resourcesDir);
|
|
558
|
+
const zipFiles = files.filter(file => file.endsWith('.zip'));
|
|
559
|
+
|
|
560
|
+
if (zipFiles.length > 0) {
|
|
561
|
+
// Copy the resources folder to finalOutput
|
|
562
|
+
const finalResourcesDir = path.join(finalOutputDir, 'resources');
|
|
563
|
+
if (!fs.existsSync(finalResourcesDir)) {
|
|
564
|
+
fs.mkdirSync(finalResourcesDir, { recursive: true });
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// Copy each zip file to finalOutput/resources
|
|
568
|
+
for (const zipFile of zipFiles) {
|
|
569
|
+
const sourceFile = path.join(resourcesDir, zipFile);
|
|
570
|
+
const destFile = path.join(finalResourcesDir, zipFile);
|
|
571
|
+
fs.copyFileSync(sourceFile, destFile);
|
|
572
|
+
console.log(`Copied ${zipFile} to finalOutput/resources`);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
} catch (error) {
|
|
577
|
+
console.warn(`Failed to extract or process blob-report-node-${i}.zip:`, error.message);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
}
|
|
795
582
|
}
|
|
796
583
|
exports.default = ResultsParser;
|
package/package.json
CHANGED