playwright-slack-report-burak 3.0.17 → 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.
@@ -35,10 +35,14 @@ class ResultsParser {
35
35
  separateFlakyTests;
36
36
  // Handle both 0-based and 1-based shard indexing
37
37
  // If MATRIX_SHARD is not set, try to infer from workflow (1-based) or default to 0
38
- totalShardCount = process.env.MATRIX_COUNT ? parseInt(process.env.MATRIX_COUNT, 10) : 1;
39
- shardIndex = process.env.MATRIX_SHARD !== undefined
40
- ? parseInt(process.env.MATRIX_SHARD, 10)
41
- : (process.env.MATRIX_INDEX !== undefined ? parseInt(process.env.MATRIX_INDEX, 10) : 0);
38
+ totalShardCount = process.env.TOTAL_SHARDS
39
+ ? parseInt(process.env.TOTAL_SHARDS, 10)
40
+ : (process.env.MATRIX_COUNT ? parseInt(process.env.MATRIX_COUNT, 10) : 1);
41
+ shardIndex = process.env.SHARD_INDEX !== undefined
42
+ ? parseInt(process.env.SHARD_INDEX, 10)
43
+ : (process.env.MATRIX_SHARD !== undefined
44
+ ? parseInt(process.env.MATRIX_SHARD, 10)
45
+ : (process.env.MATRIX_INDEX !== undefined ? parseInt(process.env.MATRIX_INDEX, 10) : 0));
42
46
 
43
47
  // Determine if we're using 1-based indexing (workflow-style) or 0-based
44
48
  // This is detected by checking if artifacts exist with 1-based naming
@@ -50,6 +54,8 @@ class ResultsParser {
50
54
  console.log(`đŸ“Ļ [ResultsParser] playwright-slack-report-burak v${packageVersion}`);
51
55
  // Log shard detection in ResultsParser
52
56
  console.log('🔍 [ResultsParser] Initialized with shard detection:');
57
+ console.log(`🔍 SHARD_INDEX env: ${process.env.SHARD_INDEX !== undefined ? `"${process.env.SHARD_INDEX}"` : 'undefined'}`);
58
+ console.log(`🔍 TOTAL_SHARDS env: ${process.env.TOTAL_SHARDS !== undefined ? `"${process.env.TOTAL_SHARDS}"` : 'undefined'}`);
53
59
  console.log(`🔍 MATRIX_SHARD env: ${process.env.MATRIX_SHARD !== undefined ? `"${process.env.MATRIX_SHARD}"` : 'undefined'}`);
54
60
  console.log(`🔍 MATRIX_INDEX env: ${process.env.MATRIX_INDEX !== undefined ? `"${process.env.MATRIX_INDEX}"` : 'undefined'}`);
55
61
  console.log(`🔍 MATRIX_COUNT env: ${process.env.MATRIX_COUNT !== undefined ? `"${process.env.MATRIX_COUNT}"` : 'undefined'}`);
@@ -58,7 +64,7 @@ class ResultsParser {
58
64
  }
59
65
  /**
60
66
  * Update shard information after detection (e.g., from GitHub API)
61
- * @param {number} shardIndex - The current shard index (0-based)
67
+ * @param {number} shardIndex - The current shard index (1-based, shard 1 is aggregator)
62
68
  * @param {number} totalShardCount - The total number of shards
63
69
  */
64
70
  updateShardInfo(shardIndex, totalShardCount) {
@@ -124,8 +130,8 @@ class ResultsParser {
124
130
  console.log(`🔍 [ResultsParser] Current shard: ${this.shardIndex}, Total shards: ${this.totalShardCount}`);
125
131
  console.log(`🔍 [ResultsParser] Created node summary file: ${nodeSummaryFile}`);
126
132
 
127
- if (this.shardIndex === 0 && this.totalShardCount > 1) {
128
- 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`);
129
135
  if (process.env.CI) {
130
136
  console.log('🔍 [ResultsParser] CI environment detected - fetching all artifacts from GitHub Actions...');
131
137
  await this.fetchAllArtifacts();
@@ -137,13 +143,14 @@ class ResultsParser {
137
143
  }
138
144
  }
139
145
  } else {
140
- 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`);
141
147
  }
142
148
 
143
- if (this.shardIndex === 0 && this.allNodeSummaryFilesExist() && this.allBlobZipsExist()) {
144
- 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...`);
145
151
  // Merge all node summaries into the final summary
146
- 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++) {
147
154
  const nodeSummaryFile = path.join(summariesDir, `node_summary_${i}.json`);
148
155
  const fileToRead = nodeSummaryFile;
149
156
 
@@ -358,7 +365,8 @@ class ResultsParser {
358
365
  console.log('Checking if all node summary files exist...');
359
366
  const summariesDir = path.join('./', 'playwright-report');
360
367
 
361
- 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++) {
362
370
  const nodeSummaryFile = path.join(summariesDir, `node_summary_${i}.json`);
363
371
  if (!fs.existsSync(nodeSummaryFile)) {
364
372
  return false;
@@ -371,7 +379,8 @@ class ResultsParser {
371
379
  console.log('Checking if all blob zips exist...');
372
380
  const summariesDir = path.join('./', 'playwright-report');
373
381
 
374
- 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++) {
375
384
  const blobZipFile = path.join(summariesDir, `blob-report-node-${i}.zip`);
376
385
  if (!fs.existsSync(blobZipFile)) {
377
386
  return false;
@@ -386,12 +395,13 @@ class ResultsParser {
386
395
  fs.mkdirSync(summariesDir, { recursive: true });
387
396
  }
388
397
 
389
- 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)...`);
390
399
 
391
- // Fetch artifacts for shards 1 to totalShardCount-1 (since we're shard 0)
392
- // We need to fetch from all other shards before proceeding
393
- for (let i = 1; i < this.totalShardCount; i++) {
394
- 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}...`);
395
405
  await this.fetchArtifact(i, `node_summary_${i}.json`, summariesDir);
396
406
  await this.fetchArtifact(i, `blob-report-node-${i}.zip`, summariesDir);
397
407
  }
@@ -401,20 +411,21 @@ class ResultsParser {
401
411
  const maxRetries = 60; // Wait up to 10 minutes (60 * 10 seconds)
402
412
 
403
413
  while ((!this.allNodeSummaryFilesExist() || !this.allBlobZipsExist()) && retryCount < maxRetries) {
404
- 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})...`);
405
415
 
406
416
  // Re-fetch any missing artifacts
407
- 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)
408
419
  const nodeSummaryFile = path.join(summariesDir, `node_summary_${i}.json`);
409
420
  const blobZipFile = path.join(summariesDir, `blob-report-node-${i}.zip`);
410
421
 
411
422
  if (!fs.existsSync(nodeSummaryFile)) {
412
- console.log(`Shard 0: Re-fetching missing node_summary_${i}.json...`);
423
+ console.log(`Shard 1: Re-fetching missing node_summary_${i}.json...`);
413
424
  await this.fetchArtifact(i, `node_summary_${i}.json`, summariesDir);
414
425
  }
415
426
 
416
427
  if (!fs.existsSync(blobZipFile)) {
417
- 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...`);
418
429
  await this.fetchArtifact(i, `blob-report-node-${i}.zip`, summariesDir);
419
430
  }
420
431
  }
@@ -426,9 +437,9 @@ class ResultsParser {
426
437
  }
427
438
 
428
439
  if (this.allNodeSummaryFilesExist() && this.allBlobZipsExist()) {
429
- 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).`);
430
441
  } else {
431
- 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.`);
432
443
  }
433
444
  }
434
445
 
@@ -443,10 +454,10 @@ class ResultsParser {
443
454
  const match = artifactName.match(/-(\d+)$/);
444
455
  if (match) {
445
456
  const artifactShard = parseInt(match[1], 10);
446
- // If artifact uses 1-based indexing, convert to 0-based for internal use
447
- // html-report-1 (workflow) -> shard 0 (internal)
448
- // html-report-2 (workflow) -> shard 1 (internal)
449
- 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;
450
461
  }
451
462
  // Fallback to the provided index
452
463
  return i;
@@ -466,16 +477,16 @@ class ResultsParser {
466
477
  const githubApiUrl = `https://api.github.com/repos/${repo}/actions/artifacts`;
467
478
 
468
479
  // Try multiple artifact naming patterns:
469
- // 1. html-report-{i} (1-based workflow style)
470
- // 2. blob-report-node-{i} (direct blob reports)
471
- // 3. test-results-{i} (alternative naming)
472
- // 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
473
484
  const artifactNamePatterns = [
474
- `html-report-${i + 1}`, // 1-based workflow style (most common)
475
- `blob-report-node-${i}`, // 0-based direct blob
476
- `blob-report-node-${i + 1}`, // 1-based direct blob
477
- `html-report-${i}`, // 0-based html-report
478
- `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
479
490
  ];
480
491
 
481
492
  while (true) {
@@ -497,9 +508,9 @@ class ResultsParser {
497
508
  artifact = listResponse.data.artifacts.find(
498
509
  (a) => a.name === pattern ||
499
510
  a.name.includes(`shard-${i}`) ||
500
- a.name.includes(`shard-${i + 1}`) ||
511
+ a.name.includes(`shard-${i - 1}`) ||
501
512
  a.name.includes(`node-${i}`) ||
502
- a.name.includes(`node-${i + 1}`)
513
+ a.name.includes(`node-${i - 1}`)
503
514
  );
504
515
  if (artifact) {
505
516
  console.log(`Found artifact: ${artifact.name} (matched pattern: ${pattern})`);
@@ -534,8 +545,8 @@ class ResultsParser {
534
545
  file,
535
546
  `playwright-report/${file}`,
536
547
  `playwright-report/${file}`,
537
- `html-report-${i + 1}/${file}`,
538
548
  `html-report-${i}/${file}`,
549
+ `html-report-${i - 1}/${file}`,
539
550
  ];
540
551
  for (const possiblePath of possiblePaths) {
541
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
 
@@ -343,6 +353,8 @@ class SlackReporter {
343
353
 
344
354
  // Check for Playwright shard environment variables (Playwright may set these)
345
355
  this.log(`🔍 PLAYWRIGHT_SHARD: ${process.env.PLAYWRIGHT_SHARD !== undefined ? `"${process.env.PLAYWRIGHT_SHARD}"` : 'undefined'}`);
356
+ this.log(`🔍 SHARD_INDEX: ${process.env.SHARD_INDEX !== undefined ? `"${process.env.SHARD_INDEX}"` : 'undefined'}`);
357
+ this.log(`🔍 TOTAL_SHARDS: ${process.env.TOTAL_SHARDS !== undefined ? `"${process.env.TOTAL_SHARDS}"` : 'undefined'}`);
346
358
 
347
359
  // Check Playwright's shard config (highest priority for local runs)
348
360
  let playwrightShardInfo = null;
@@ -380,7 +392,7 @@ class SlackReporter {
380
392
  current: parseInt(shardMatch[1], 10),
381
393
  total: parseInt(shardMatch[2], 10)
382
394
  };
383
- 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)`);
384
396
  }
385
397
  }
386
398
  }
@@ -397,9 +409,12 @@ class SlackReporter {
397
409
  // Step 2: Detect shard index (priority order: Playwright config > env vars > GitHub API)
398
410
  let currentShardIndex;
399
411
  if (playwrightShardInfo) {
400
- // Playwright uses 1-based indexing, convert to 0-based
401
- currentShardIndex = (playwrightShardInfo.current - 1).toString();
402
- 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}"`);
415
+ } else if (process.env.SHARD_INDEX !== undefined) {
416
+ currentShardIndex = process.env.SHARD_INDEX;
417
+ this.log(`🔍 [Shard Detection] Using SHARD_INDEX env var: "${currentShardIndex}"`);
403
418
  } else if (process.env.MATRIX_SHARD !== undefined) {
404
419
  currentShardIndex = process.env.MATRIX_SHARD;
405
420
  this.log(`🔍 [Shard Detection] Using MATRIX_SHARD env var: "${currentShardIndex}"`);
@@ -418,6 +433,9 @@ class SlackReporter {
418
433
  if (playwrightShardInfo) {
419
434
  totalShards = playwrightShardInfo.total;
420
435
  this.log(`🔍 [Shard Detection] Using Playwright shard config totalShards: ${totalShards}`);
436
+ } else if (process.env.TOTAL_SHARDS !== undefined) {
437
+ totalShards = parseInt(process.env.TOTAL_SHARDS, 10);
438
+ this.log(`🔍 [Shard Detection] Using TOTAL_SHARDS env var: ${totalShards}`);
421
439
  } else if (process.env.MATRIX_COUNT !== undefined) {
422
440
  totalShards = parseInt(process.env.MATRIX_COUNT, 10);
423
441
  this.log(`🔍 [Shard Detection] Using MATRIX_COUNT env var: ${totalShards}`);
@@ -455,6 +473,7 @@ class SlackReporter {
455
473
  // CRITICAL: In CI (especially GitHub Actions), if we can't detect shard info, block posting
456
474
  // This prevents duplicate messages when running with multiple shards
457
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
458
477
  if (isActuallyInGitHubActions && currentShardIndex === undefined) {
459
478
  if (process.env.GITHUB_ACTIONS === 'true') {
460
479
  this.log(`❌ [Shard Detection] BLOCKING: GitHub Actions detected but shard index cannot be determined.`);
@@ -468,14 +487,14 @@ class SlackReporter {
468
487
  this.log(`❌ Skipping Slack report to prevent duplicate messages from all shards.`);
469
488
  this.log(`💡 Fix: Add these env vars to your workflow:`);
470
489
  this.log(`💡 env:`);
471
- 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)`);
472
491
  this.log(`💡 MATRIX_COUNT: $\{\{ strategy.job-total \}\}`);
473
492
  this.log(`💡 OR ensure SAFETYWINGTEST_GITHUB_TOKEN has workflow permissions for API detection`);
474
493
  return;
475
494
  }
476
495
 
477
- // Convert to number for comparison (default to 0 if undefined)
478
- 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;
479
498
 
480
499
  this.log(`🔍 [Shard Detection] Final values:`);
481
500
  this.log(`🔍 currentShardIndex (string): ${currentShardIndex !== undefined ? `"${currentShardIndex}"` : 'undefined'}`);
@@ -487,30 +506,37 @@ class SlackReporter {
487
506
  this.resultsParser.updateShardInfo(shardIndexNum, totalShards);
488
507
 
489
508
  // For local runs with CI env vars set but not actually in CI, allow posting if single shard
490
- // or if shard index is 0
509
+ // or if shard index is 1
491
510
  if (!isActuallyInGitHubActions && process.env.CI && totalShards > 1 && currentShardIndex === undefined) {
492
511
  this.log(`âš ī¸ [Shard Detection] Local run with CI env vars but shard info unclear - allowing post (may cause duplicates)`);
493
512
  }
494
513
 
495
- // 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
496
515
  // If we're in CI with multiple shards and don't know which shard we are, block all posts
497
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
498
519
  if (isActuallyInGitHubActions && totalShards > 1 && currentShardIndex === undefined) {
499
520
  this.log(`❌ [Shard Detection] BLOCKING: GitHub Actions detected with ${totalShards} shards but shard index not set (MATRIX_SHARD/MATRIX_INDEX missing).`);
500
521
  this.log(`❌ Skipping Slack report to prevent duplicate messages from all shards.`);
501
522
  this.log(`💡 Fix: Add these env vars to your workflow:`);
502
523
  this.log(`💡 env:`);
503
- 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)`);
504
525
  this.log(`💡 MATRIX_COUNT: $\{\{ strategy.job-total \}\}`);
526
+ this.log(`💡 OR ensure SAFETYWINGTEST_GITHUB_TOKEN has workflow permissions for API detection`);
505
527
  return;
506
528
  }
507
529
 
508
- // 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
509
531
  // This ensures only one shard posts the aggregated report
510
- if (totalShards > 1 && shardIndexNum !== 0) {
511
- this.log(`❌ [Shard Detection] BLOCKING: Non-zero shard detected (shard ${currentShardIndex} of ${totalShards})`);
512
- this.log(`❌ Stopping reporter for non-zero index shard ${currentShardIndex} of ${totalShards}`);
513
- 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.`);
514
540
  return;
515
541
  }
516
542
 
@@ -518,7 +544,9 @@ class SlackReporter {
518
544
  if (totalShards === 1) {
519
545
  this.log(`✅ [Shard Detection] ALLOWING: Single shard detected. Posting Slack report.`);
520
546
  } else {
521
- 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.`);
522
550
  }
523
551
 
524
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.17",
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>",