playwright-slack-report-burak 3.0.18 → 3.0.19

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.
@@ -64,7 +64,7 @@ class ResultsParser {
64
64
  }
65
65
  /**
66
66
  * Update shard information after detection (e.g., from GitHub API)
67
- * @param {number} shardIndex - The current shard index (0-based)
67
+ * @param {number} shardIndex - The current shard index (1-based, shard 1 is aggregator)
68
68
  * @param {number} totalShardCount - The total number of shards
69
69
  */
70
70
  updateShardInfo(shardIndex, totalShardCount) {
@@ -130,8 +130,8 @@ class ResultsParser {
130
130
  console.log(`🔍 [ResultsParser] Current shard: ${this.shardIndex}, Total shards: ${this.totalShardCount}`);
131
131
  console.log(`🔍 [ResultsParser] Created node summary file: ${nodeSummaryFile}`);
132
132
 
133
- if (this.shardIndex === 0 && this.totalShardCount > 1) {
134
- console.log(`🔍 [ResultsParser] Shard 0 detected with ${this.totalShardCount} total shards - will fetch artifacts from other shards`);
133
+ if (this.shardIndex === 1 && this.totalShardCount > 1) {
134
+ console.log(`🔍 [ResultsParser] Shard 1 detected with ${this.totalShardCount} total shards - will fetch artifacts from other shards`);
135
135
  if (process.env.CI) {
136
136
  console.log('🔍 [ResultsParser] CI environment detected - fetching all artifacts from GitHub Actions...');
137
137
  await this.fetchAllArtifacts();
@@ -143,13 +143,14 @@ class ResultsParser {
143
143
  }
144
144
  }
145
145
  } else {
146
- console.log(`🔍 [ResultsParser] Not shard 0 (current: ${this.shardIndex}) or single shard (total: ${this.totalShardCount}) - skipping artifact fetch`);
146
+ console.log(`🔍 [ResultsParser] Not shard 1 (current: ${this.shardIndex}) or single shard (total: ${this.totalShardCount}) - skipping artifact fetch`);
147
147
  }
148
148
 
149
- if (this.shardIndex === 0 && this.allNodeSummaryFilesExist() && this.allBlobZipsExist()) {
150
- console.log(`🔍 [ResultsParser] Shard 0: All artifacts available, merging results from all shards...`);
149
+ if (this.shardIndex === 1 && this.allNodeSummaryFilesExist() && this.allBlobZipsExist()) {
150
+ console.log(`🔍 [ResultsParser] Shard 1: All artifacts available, merging results from all shards...`);
151
151
  // Merge all node summaries into the final summary
152
- for (let i = 0; i < this.totalShardCount; i++) {
152
+ // Loop from 1 to totalShardCount (1-based indexing)
153
+ for (let i = 1; i <= this.totalShardCount; i++) {
153
154
  const nodeSummaryFile = path.join(summariesDir, `node_summary_${i}.json`);
154
155
  const fileToRead = nodeSummaryFile;
155
156
 
@@ -364,7 +365,8 @@ class ResultsParser {
364
365
  console.log('Checking if all node summary files exist...');
365
366
  const summariesDir = path.join('./', 'playwright-report');
366
367
 
367
- for (let i = 0; i < this.totalShardCount; i++) {
368
+ // Check files from 1 to totalShardCount (1-based indexing)
369
+ for (let i = 1; i <= this.totalShardCount; i++) {
368
370
  const nodeSummaryFile = path.join(summariesDir, `node_summary_${i}.json`);
369
371
  if (!fs.existsSync(nodeSummaryFile)) {
370
372
  return false;
@@ -377,7 +379,8 @@ class ResultsParser {
377
379
  console.log('Checking if all blob zips exist...');
378
380
  const summariesDir = path.join('./', 'playwright-report');
379
381
 
380
- for (let i = 0; i < this.totalShardCount; i++) {
382
+ // Check files from 1 to totalShardCount (1-based indexing)
383
+ for (let i = 1; i <= this.totalShardCount; i++) {
381
384
  const blobZipFile = path.join(summariesDir, `blob-report-node-${i}.zip`);
382
385
  if (!fs.existsSync(blobZipFile)) {
383
386
  return false;
@@ -392,12 +395,13 @@ class ResultsParser {
392
395
  fs.mkdirSync(summariesDir, { recursive: true });
393
396
  }
394
397
 
395
- console.log(`Shard 0: Waiting for artifacts from ${this.totalShardCount - 1} other shard(s)...`);
398
+ console.log(`Shard 1: Waiting for artifacts from ${this.totalShardCount - 1} other shard(s)...`);
396
399
 
397
- // Fetch artifacts for shards 1 to totalShardCount-1 (since we're shard 0)
398
- // We need to fetch from all other shards before proceeding
399
- for (let i = 1; i < this.totalShardCount; i++) {
400
- console.log(`Shard 0: Fetching artifacts from shard ${i}...`);
400
+ // Fetch artifacts for all shards except shard 1 (since we're shard 1)
401
+ // We need to fetch from shards 2, 3, 4, ... (all except shard 1, using 1-based indexing)
402
+ for (let i = 1; i <= this.totalShardCount; i++) {
403
+ if (i === 1) continue; // Skip shard 1 (we are shard 1)
404
+ console.log(`Shard 1: Fetching artifacts from shard ${i}...`);
401
405
  await this.fetchArtifact(i, `node_summary_${i}.json`, summariesDir);
402
406
  await this.fetchArtifact(i, `blob-report-node-${i}.zip`, summariesDir);
403
407
  }
@@ -407,20 +411,21 @@ class ResultsParser {
407
411
  const maxRetries = 60; // Wait up to 10 minutes (60 * 10 seconds)
408
412
 
409
413
  while ((!this.allNodeSummaryFilesExist() || !this.allBlobZipsExist()) && retryCount < maxRetries) {
410
- console.log(`Shard 0: Waiting for all artifacts to be available (attempt ${retryCount + 1}/${maxRetries})...`);
414
+ console.log(`Shard 1: Waiting for all artifacts to be available (attempt ${retryCount + 1}/${maxRetries})...`);
411
415
 
412
416
  // Re-fetch any missing artifacts
413
- for (let i = 1; i < this.totalShardCount; i++) {
417
+ for (let i = 1; i <= this.totalShardCount; i++) {
418
+ if (i === 1) continue; // Skip shard 1 (we are shard 1)
414
419
  const nodeSummaryFile = path.join(summariesDir, `node_summary_${i}.json`);
415
420
  const blobZipFile = path.join(summariesDir, `blob-report-node-${i}.zip`);
416
421
 
417
422
  if (!fs.existsSync(nodeSummaryFile)) {
418
- console.log(`Shard 0: Re-fetching missing node_summary_${i}.json...`);
423
+ console.log(`Shard 1: Re-fetching missing node_summary_${i}.json...`);
419
424
  await this.fetchArtifact(i, `node_summary_${i}.json`, summariesDir);
420
425
  }
421
426
 
422
427
  if (!fs.existsSync(blobZipFile)) {
423
- console.log(`Shard 0: Re-fetching missing blob-report-node-${i}.zip...`);
428
+ console.log(`Shard 1: Re-fetching missing blob-report-node-${i}.zip...`);
424
429
  await this.fetchArtifact(i, `blob-report-node-${i}.zip`, summariesDir);
425
430
  }
426
431
  }
@@ -432,9 +437,9 @@ class ResultsParser {
432
437
  }
433
438
 
434
439
  if (this.allNodeSummaryFilesExist() && this.allBlobZipsExist()) {
435
- console.log(`Shard 0: Successfully fetched all artifacts from ${this.totalShardCount - 1} other shard(s).`);
440
+ console.log(`Shard 1: Successfully fetched all artifacts from ${this.totalShardCount - 1} other shard(s).`);
436
441
  } else {
437
- console.warn(`Shard 0: Warning - Some artifacts may still be missing after ${maxRetries} attempts.`);
442
+ console.warn(`Shard 1: Warning - Some artifacts may still be missing after ${maxRetries} attempts.`);
438
443
  }
439
444
  }
440
445
 
@@ -449,10 +454,10 @@ class ResultsParser {
449
454
  const match = artifactName.match(/-(\d+)$/);
450
455
  if (match) {
451
456
  const artifactShard = parseInt(match[1], 10);
452
- // If artifact uses 1-based indexing, convert to 0-based for internal use
453
- // html-report-1 (workflow) -> shard 0 (internal)
454
- // html-report-2 (workflow) -> shard 1 (internal)
455
- return artifactShard - 1;
457
+ // Artifacts use 1-based indexing, and we now use 1-based internally
458
+ // html-report-1 (workflow) -> shard 1 (internal)
459
+ // html-report-2 (workflow) -> shard 2 (internal)
460
+ return artifactShard;
456
461
  }
457
462
  // Fallback to the provided index
458
463
  return i;
@@ -472,16 +477,16 @@ class ResultsParser {
472
477
  const githubApiUrl = `https://api.github.com/repos/${repo}/actions/artifacts`;
473
478
 
474
479
  // Try multiple artifact naming patterns:
475
- // 1. html-report-{i} (1-based workflow style)
476
- // 2. blob-report-node-{i} (direct blob reports)
477
- // 3. test-results-{i} (alternative naming)
478
- // Note: i is 0-based internally, so for 1-based workflows we need i+1
480
+ // 1. html-report-{i} (1-based workflow style, i is already 1-based)
481
+ // 2. blob-report-node-{i} (direct blob reports, i is 1-based)
482
+ // 3. test-results-{i} (alternative naming, i is 1-based)
483
+ // Note: i is 1-based internally now, so we use it directly for GitHub Actions artifacts
479
484
  const artifactNamePatterns = [
480
- `html-report-${i + 1}`, // 1-based workflow style (most common)
481
- `blob-report-node-${i}`, // 0-based direct blob
482
- `blob-report-node-${i + 1}`, // 1-based direct blob
483
- `html-report-${i}`, // 0-based html-report
484
- `test-results-${i + 1}`, // Alternative naming
485
+ `html-report-${i}`, // 1-based workflow style (i is already 1-based)
486
+ `blob-report-node-${i}`, // 1-based direct blob
487
+ `html-report-${i - 1}`, // 0-based html-report (fallback)
488
+ `blob-report-node-${i - 1}`, // 0-based direct blob (fallback)
489
+ `test-results-${i}`, // Alternative naming
485
490
  ];
486
491
 
487
492
  while (true) {
@@ -503,9 +508,9 @@ class ResultsParser {
503
508
  artifact = listResponse.data.artifacts.find(
504
509
  (a) => a.name === pattern ||
505
510
  a.name.includes(`shard-${i}`) ||
506
- a.name.includes(`shard-${i + 1}`) ||
511
+ a.name.includes(`shard-${i - 1}`) ||
507
512
  a.name.includes(`node-${i}`) ||
508
- a.name.includes(`node-${i + 1}`)
513
+ a.name.includes(`node-${i - 1}`)
509
514
  );
510
515
  if (artifact) {
511
516
  console.log(`Found artifact: ${artifact.name} (matched pattern: ${pattern})`);
@@ -540,8 +545,8 @@ class ResultsParser {
540
545
  file,
541
546
  `playwright-report/${file}`,
542
547
  `playwright-report/${file}`,
543
- `html-report-${i + 1}/${file}`,
544
548
  `html-report-${i}/${file}`,
549
+ `html-report-${i - 1}/${file}`,
545
550
  ];
546
551
  for (const possiblePath of possiblePaths) {
547
552
  zipEntry = zip.getEntry(possiblePath);
@@ -259,19 +259,29 @@ class SlackReporter {
259
259
  // Find index of current job in the sorted group
260
260
  const currentJobIndex = jobGroup.findIndex(j => j.id === currentJob?.id || j.name === currentJob?.name);
261
261
 
262
- // Try to extract shard number from job name (e.g., "shard 3/4" -> index 2)
262
+ // Try to extract shard number from job name (e.g., "shard 3/4" -> index 3)
263
+ // GitHub Actions uses 1-based indexing (shard 1, 2, 3, 4), we keep it 1-based internally for shard 1 logic
263
264
  let shardIndex = currentJobIndex >= 0 ? currentJobIndex : 0;
264
265
  if (currentJob && currentJob.name) {
265
266
  const shardMatch = currentJob.name.match(/shard\s+(\d+)\/(\d+)/i);
266
267
  if (shardMatch) {
267
- const shardNum = parseInt(shardMatch[1], 10);
268
+ const shardNum = parseInt(shardMatch[1], 10); // 1-based from GitHub Actions (1, 2, 3, 4)
268
269
  const totalFromName = parseInt(shardMatch[2], 10);
269
- // Convert to 0-based index (shard 3/4 -> index 2)
270
- shardIndex = shardNum - 1;
271
- this.log(`🔍 [GitHub Actions Detection] Extracted shard info from name: ${shardNum}/${totalFromName} -> index ${shardIndex}`);
270
+ // Keep GitHub Actions 1-based indexing (shard 1 -> index 1, shard 2 -> index 2, etc.)
271
+ // This allows shard 1 to be the aggregator
272
+ shardIndex = shardNum;
273
+ this.log(`🔍 [GitHub Actions Detection] Extracted shard info from name: ${shardNum}/${totalFromName} (GitHub Actions 1-based) -> index ${shardIndex} (internal 1-based)`);
272
274
  }
273
275
  }
274
276
 
277
+ // If we couldn't extract from name but have job index, convert from 0-based array index to 1-based
278
+ // GitHub Actions jobs are numbered starting from 1, so array index 0 = shard 1, index 1 = shard 2, etc.
279
+ // We need to convert: array index 0 -> shard 1, array index 1 -> shard 2, etc.
280
+ if (shardIndex === currentJobIndex && currentJobIndex >= 0) {
281
+ shardIndex = currentJobIndex + 1; // Convert 0-based array index to 1-based shard index
282
+ this.log(`🔍 [GitHub Actions Detection] Using array index ${currentJobIndex} -> shard ${shardIndex} (1-based)`);
283
+ }
284
+
275
285
  const totalShards = jobGroup.length;
276
286
  this.log(`🔍 [GitHub Actions Detection] Detected via API:`);
277
287
  this.log(`🔍 Total jobs in group: ${totalShards}`);
@@ -320,8 +330,8 @@ class SlackReporter {
320
330
  this.log(message);
321
331
  return;
322
332
  }
323
- // SHARDING SUPPORT - Stop slack messages for non-zero index shard(s)
324
- // Only shard 0 (0-based) should post when aggregating results from multiple shards
333
+ // SHARDING SUPPORT - Stop slack messages for non-shard-1 shard(s)
334
+ // Only shard 1 should post when aggregating results from multiple shards
325
335
 
326
336
  this.log('🔍 [Shard Detection] Starting shard detection logic...');
327
337
 
@@ -382,7 +392,7 @@ class SlackReporter {
382
392
  current: parseInt(shardMatch[1], 10),
383
393
  total: parseInt(shardMatch[2], 10)
384
394
  };
385
- this.log(`🔍 [Shard Detection] Found shard in command line args: ${playwrightShardInfo.current}/${playwrightShardInfo.total}`);
395
+ this.log(`🔍 [Shard Detection] Found shard in command line args: ${playwrightShardInfo.current}/${playwrightShardInfo.total} (1-based)`);
386
396
  }
387
397
  }
388
398
  }
@@ -399,9 +409,9 @@ class SlackReporter {
399
409
  // Step 2: Detect shard index (priority order: Playwright config > env vars > GitHub API)
400
410
  let currentShardIndex;
401
411
  if (playwrightShardInfo) {
402
- // Playwright uses 1-based indexing, convert to 0-based
403
- currentShardIndex = (playwrightShardInfo.current - 1).toString();
404
- this.log(`🔍 [Shard Detection] Using Playwright shard config (converted to 0-based): "${currentShardIndex}"`);
412
+ // Playwright uses 1-based indexing, keep it 1-based for shard 1 logic
413
+ currentShardIndex = playwrightShardInfo.current.toString();
414
+ this.log(`🔍 [Shard Detection] Using Playwright shard config (1-based): "${currentShardIndex}"`);
405
415
  } else if (process.env.SHARD_INDEX !== undefined) {
406
416
  currentShardIndex = process.env.SHARD_INDEX;
407
417
  this.log(`🔍 [Shard Detection] Using SHARD_INDEX env var: "${currentShardIndex}"`);
@@ -463,6 +473,7 @@ class SlackReporter {
463
473
  // CRITICAL: In CI (especially GitHub Actions), if we can't detect shard info, block posting
464
474
  // This prevents duplicate messages when running with multiple shards
465
475
  // BUT: Don't block if we're running locally (even if CI env vars are set)
476
+ // Note: GitHub Actions uses 1-based indexing (shard 1, 2, 3, 4), but we use 0-based internally
466
477
  if (isActuallyInGitHubActions && currentShardIndex === undefined) {
467
478
  if (process.env.GITHUB_ACTIONS === 'true') {
468
479
  this.log(`❌ [Shard Detection] BLOCKING: GitHub Actions detected but shard index cannot be determined.`);
@@ -476,14 +487,14 @@ class SlackReporter {
476
487
  this.log(`❌ Skipping Slack report to prevent duplicate messages from all shards.`);
477
488
  this.log(`💡 Fix: Add these env vars to your workflow:`);
478
489
  this.log(`💡 env:`);
479
- this.log(`💡 MATRIX_SHARD: $\{\{ strategy.job-index \}\} # 0-based (only shard 0 posts)`);
490
+ this.log(`💡 MATRIX_SHARD: $\{\{ strategy.job-index \}\} # GitHub Actions uses 1-based, convert to 0-based (only shard 1/internal index 1 posts)`);
480
491
  this.log(`💡 MATRIX_COUNT: $\{\{ strategy.job-total \}\}`);
481
492
  this.log(`💡 OR ensure SAFETYWINGTEST_GITHUB_TOKEN has workflow permissions for API detection`);
482
493
  return;
483
494
  }
484
495
 
485
- // Convert to number for comparison (default to 0 if undefined)
486
- const shardIndexNum = currentShardIndex !== undefined ? parseInt(currentShardIndex, 10) : 0;
496
+ // Convert to number for comparison (default to 1 if undefined, since shard 1 is the aggregator)
497
+ const shardIndexNum = currentShardIndex !== undefined ? parseInt(currentShardIndex, 10) : 1;
487
498
 
488
499
  this.log(`🔍 [Shard Detection] Final values:`);
489
500
  this.log(`🔍 currentShardIndex (string): ${currentShardIndex !== undefined ? `"${currentShardIndex}"` : 'undefined'}`);
@@ -495,30 +506,37 @@ class SlackReporter {
495
506
  this.resultsParser.updateShardInfo(shardIndexNum, totalShards);
496
507
 
497
508
  // For local runs with CI env vars set but not actually in CI, allow posting if single shard
498
- // or if shard index is 0
509
+ // or if shard index is 1
499
510
  if (!isActuallyInGitHubActions && process.env.CI && totalShards > 1 && currentShardIndex === undefined) {
500
511
  this.log(`âš ī¸ [Shard Detection] Local run with CI env vars but shard info unclear - allowing post (may cause duplicates)`);
501
512
  }
502
513
 
503
- // CRITICAL: For GitHub Actions with multiple shards, only shard 0 should post
514
+ // CRITICAL: For GitHub Actions with multiple shards, only shard 1 should post
504
515
  // If we're in CI with multiple shards and don't know which shard we are, block all posts
505
516
  // This prevents duplicate messages when MATRIX_SHARD/MATRIX_INDEX is not set
517
+ // Note: GitHub Actions uses 1-based indexing (shard 1, 2, 3, 4)
518
+ // We use 0-based internally, so GitHub Actions shard 1 = internal index 1
506
519
  if (isActuallyInGitHubActions && totalShards > 1 && currentShardIndex === undefined) {
507
520
  this.log(`❌ [Shard Detection] BLOCKING: GitHub Actions detected with ${totalShards} shards but shard index not set (MATRIX_SHARD/MATRIX_INDEX missing).`);
508
521
  this.log(`❌ Skipping Slack report to prevent duplicate messages from all shards.`);
509
522
  this.log(`💡 Fix: Add these env vars to your workflow:`);
510
523
  this.log(`💡 env:`);
511
- this.log(`💡 MATRIX_SHARD: $\{\{ strategy.job-index \}\} # 0-based (only shard 0 posts)`);
524
+ this.log(`💡 MATRIX_SHARD: $\{\{ strategy.job-index \}\} # GitHub Actions uses 1-based, convert to 0-based (only shard 1/internal index 1 posts)`);
512
525
  this.log(`💡 MATRIX_COUNT: $\{\{ strategy.job-total \}\}`);
526
+ this.log(`💡 OR ensure SAFETYWINGTEST_GITHUB_TOKEN has workflow permissions for API detection`);
513
527
  return;
514
528
  }
515
529
 
516
- // Only allow shard 0 (0-based) to post when there are multiple shards
530
+ // Only allow shard 1 to post when there are multiple shards
517
531
  // This ensures only one shard posts the aggregated report
518
- if (totalShards > 1 && shardIndexNum !== 0) {
519
- this.log(`❌ [Shard Detection] BLOCKING: Non-zero shard detected (shard ${currentShardIndex} of ${totalShards})`);
520
- this.log(`❌ Stopping reporter for non-zero index shard ${currentShardIndex} of ${totalShards}`);
521
- this.log(`â„šī¸ Only shard 0 will post the aggregated report.`);
532
+ // Note: GitHub Actions uses 1-based indexing (shard 1, 2, 3, 4)
533
+ // We use 0-based internally, so shard 1 in GitHub Actions = index 1 internally
534
+ if (totalShards > 1 && shardIndexNum !== 1) {
535
+ // Convert back to 1-based for display if from GitHub Actions
536
+ const displayShard = isActuallyInGitHubActions ? shardIndexNum + 1 : shardIndexNum;
537
+ this.log(`❌ [Shard Detection] BLOCKING: Non-shard-1 detected (shard ${displayShard} of ${totalShards}${isActuallyInGitHubActions ? ' [GitHub Actions 1-based]' : ''})`);
538
+ this.log(`❌ Stopping reporter for shard ${displayShard} of ${totalShards}`);
539
+ this.log(`â„šī¸ Only shard 1 will post the aggregated report.`);
522
540
  return;
523
541
  }
524
542
 
@@ -526,7 +544,9 @@ class SlackReporter {
526
544
  if (totalShards === 1) {
527
545
  this.log(`✅ [Shard Detection] ALLOWING: Single shard detected. Posting Slack report.`);
528
546
  } else {
529
- this.log(`✅ [Shard Detection] ALLOWING: Multiple shards detected (${totalShards}). Shard ${currentShardIndex} (index ${shardIndexNum}) will post aggregated report.`);
547
+ // Convert back to 1-based for display if from GitHub Actions
548
+ const displayShard = isActuallyInGitHubActions ? shardIndexNum + 1 : shardIndexNum;
549
+ this.log(`✅ [Shard Detection] ALLOWING: Multiple shards detected (${totalShards}). Shard ${displayShard} (index ${shardIndexNum}${isActuallyInGitHubActions ? ', GitHub Actions 1-based' : ''}) will post aggregated report.`);
530
550
  }
531
551
 
532
552
  const resultSummary = await this.resultsParser.getParsedResults();
package/package.json CHANGED
@@ -32,7 +32,7 @@
32
32
  "lint-fix": "npx eslint . --ext .ts --fix"
33
33
  },
34
34
  "name": "playwright-slack-report-burak",
35
- "version": "3.0.18",
35
+ "version": "3.0.19",
36
36
  "main": "index.js",
37
37
  "types": "dist/index.d.ts",
38
38
  "author": "Burak B. <burak.boluk@hotmail.com>",