playwright-slack-report-burak 3.0.4 → 3.0.6

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.
@@ -34,6 +34,13 @@ class ResultsParser {
34
34
  constructor(options = { separateFlakyTests: false }) {
35
35
  this.result = [];
36
36
  this.separateFlakyTests = options.separateFlakyTests;
37
+ // Log shard detection in ResultsParser
38
+ console.log('🔍 [ResultsParser] Initialized with shard detection:');
39
+ console.log(`🔍 MATRIX_SHARD env: ${process.env.MATRIX_SHARD !== undefined ? `"${process.env.MATRIX_SHARD}"` : 'undefined'}`);
40
+ console.log(`🔍 MATRIX_INDEX env: ${process.env.MATRIX_INDEX !== undefined ? `"${process.env.MATRIX_INDEX}"` : 'undefined'}`);
41
+ console.log(`🔍 MATRIX_COUNT env: ${process.env.MATRIX_COUNT !== undefined ? `"${process.env.MATRIX_COUNT}"` : 'undefined'}`);
42
+ console.log(`🔍 Calculated shardIndex: ${this.shardIndex}`);
43
+ console.log(`🔍 Calculated totalShardCount: ${this.totalShardCount}`);
37
44
  }
38
45
  async getParsedResults() {
39
46
  const summary = {
@@ -90,19 +97,27 @@ class ResultsParser {
90
97
  // Create the file
91
98
  fs.writeFileSync(nodeSummaryFile, JSON.stringify(nodeSummary, null, 2));
92
99
 
100
+ console.log(`🔍 [ResultsParser] Current shard: ${this.shardIndex}, Total shards: ${this.totalShardCount}`);
101
+ console.log(`🔍 [ResultsParser] Created node summary file: ${nodeSummaryFile}`);
102
+
93
103
  if (this.shardIndex === 0 && this.totalShardCount > 1) {
104
+ console.log(`🔍 [ResultsParser] Shard 0 detected with ${this.totalShardCount} total shards - will fetch artifacts from other shards`);
94
105
  if (process.env.CI) {
95
- console.log('Fetching all artifacts from GitHub Actions...');
106
+ console.log('🔍 [ResultsParser] CI environment detected - fetching all artifacts from GitHub Actions...');
96
107
  await this.fetchAllArtifacts();
97
108
  } else {
109
+ console.log('🔍 [ResultsParser] Local environment - waiting for artifacts to appear...');
98
110
  while (!this.allNodeSummaryFilesExist() || !this.allBlobZipsExist()) {
99
- console.log('Waiting for all both to exist...');
111
+ console.log('🔍 [ResultsParser] Waiting for all artifacts to exist...');
100
112
  await new Promise(resolve => setTimeout(resolve, 1000)); // Wait for 1 second
101
113
  }
102
114
  }
115
+ } else {
116
+ console.log(`🔍 [ResultsParser] Not shard 0 (current: ${this.shardIndex}) or single shard (total: ${this.totalShardCount}) - skipping artifact fetch`);
103
117
  }
104
118
 
105
119
  if (this.shardIndex === 0 && this.allNodeSummaryFilesExist() && this.allBlobZipsExist()) {
120
+ console.log(`🔍 [ResultsParser] Shard 0: All artifacts available, merging results from all shards...`);
106
121
  // Merge all node summaries into the final summary
107
122
  for (let i = 0; i < this.totalShardCount; i++) {
108
123
  const nodeSummaryFile = path.join(summariesDir, `node_summary_${i}.json`);
@@ -6,6 +6,13 @@ const webhook_1 = require("@slack/webhook");
6
6
  const ResultsParser_1 = require("./ResultsParser");
7
7
  const SlackClient_1 = require("./SlackClient");
8
8
  const SlackWebhookClient_1 = require("./SlackWebhookClient");
9
+ let axios;
10
+ try {
11
+ axios = require('axios');
12
+ } catch (e) {
13
+ // axios optional for GitHub Actions API detection
14
+ axios = null;
15
+ }
9
16
  class SlackReporter {
10
17
  customLayout;
11
18
  customLayoutAsync;
@@ -64,6 +71,112 @@ class SlackReporter {
64
71
  onTestEnd(test, result) {
65
72
  this.resultsParser.addTestResult(test.parent.title, test, this.browsers);
66
73
  }
74
+ /**
75
+ * Detect GitHub Actions shard information using GitHub API
76
+ * Returns { shardIndex: number, totalShards: number } or null if detection fails
77
+ */
78
+ async detectGitHubActionsShardInfo() {
79
+ // Check if we're in GitHub Actions
80
+ if (!process.env.GITHUB_ACTIONS || process.env.GITHUB_ACTIONS !== 'true') {
81
+ return null;
82
+ }
83
+ const githubToken = process.env.GITHUB_TOKEN;
84
+ const repo = process.env.GITHUB_REPOSITORY;
85
+ const runId = process.env.GITHUB_RUN_ID;
86
+ const currentJobId = process.env.GITHUB_JOB;
87
+ if (!githubToken || !repo || !runId) {
88
+ this.log(`🔍 [GitHub Actions Detection] Missing required env vars: GITHUB_TOKEN=${!!githubToken}, GITHUB_REPOSITORY=${!!repo}, GITHUB_RUN_ID=${!!runId}`);
89
+ return null;
90
+ }
91
+ if (!axios) {
92
+ this.log(`🔍 [GitHub Actions Detection] axios not available, skipping API detection`);
93
+ return null;
94
+ }
95
+ try {
96
+ // Get workflow run details
97
+ const apiUrl = `https://api.github.com/repos/${repo}/actions/runs/${runId}/jobs`;
98
+ const response = await axios.get(apiUrl, {
99
+ headers: {
100
+ 'Authorization': `token ${githubToken}`,
101
+ 'Accept': 'application/vnd.github.v3+json'
102
+ },
103
+ params: {
104
+ per_page: 100
105
+ }
106
+ });
107
+ const jobs = response.data.jobs || [];
108
+ if (jobs.length === 0) {
109
+ this.log(`🔍 [GitHub Actions Detection] No jobs found in workflow run`);
110
+ return null;
111
+ }
112
+ // Filter jobs that are part of the same workflow run and have the same name (matrix jobs)
113
+ // Group by job name to find matrix jobs
114
+ const jobGroups = {};
115
+ for (const job of jobs) {
116
+ const jobName = job.name || '';
117
+ if (!jobGroups[jobName]) {
118
+ jobGroups[jobName] = [];
119
+ }
120
+ jobGroups[jobName].push(job);
121
+ }
122
+ // Find the job group that contains the current job
123
+ let currentJob = null;
124
+ let jobGroup = null;
125
+ for (const [jobName, jobsInGroup] of Object.entries(jobGroups)) {
126
+ currentJob = jobsInGroup.find(j => j.id.toString() === currentJobId || j.name === currentJobId);
127
+ if (currentJob) {
128
+ jobGroup = jobsInGroup;
129
+ break;
130
+ }
131
+ }
132
+ // If we can't find current job by ID, try to match by name from GITHUB_JOB
133
+ if (!currentJob && currentJobId) {
134
+ for (const [jobName, jobsInGroup] of Object.entries(jobGroups)) {
135
+ if (jobName.includes(currentJobId) || currentJobId.includes(jobName)) {
136
+ jobGroup = jobsInGroup;
137
+ // Try to find the job by looking at run_attempt or other identifiers
138
+ currentJob = jobsInGroup[0];
139
+ break;
140
+ }
141
+ }
142
+ }
143
+ // If still no match, use all jobs (might be a single job or we can't determine)
144
+ if (!jobGroup || jobGroup.length === 0) {
145
+ jobGroup = jobs;
146
+ currentJob = jobs[0];
147
+ }
148
+ // Sort jobs by name or ID to get consistent ordering
149
+ jobGroup.sort((a, b) => {
150
+ if (a.name && b.name) {
151
+ return a.name.localeCompare(b.name);
152
+ }
153
+ return a.id - b.id;
154
+ });
155
+ // Find index of current job
156
+ const currentJobIndex = jobGroup.findIndex(j =>
157
+ j.id === currentJob?.id ||
158
+ j.name === currentJob?.name ||
159
+ (currentJobId && (j.name === currentJobId || j.name?.includes(currentJobId)))
160
+ );
161
+ const shardIndex = currentJobIndex >= 0 ? currentJobIndex : 0;
162
+ const totalShards = jobGroup.length;
163
+ this.log(`🔍 [GitHub Actions Detection] Detected via API:`);
164
+ this.log(`🔍 Total jobs in group: ${totalShards}`);
165
+ this.log(`🔍 Current job index: ${shardIndex}`);
166
+ this.log(`🔍 Current job ID: ${currentJobId}`);
167
+ this.log(`🔍 Current job name: ${currentJob?.name || 'unknown'}`);
168
+ return {
169
+ shardIndex,
170
+ totalShards
171
+ };
172
+ } catch (error) {
173
+ this.log(`🔍 [GitHub Actions Detection] Failed to detect via API: ${error.message}`);
174
+ if (error.response) {
175
+ this.log(`🔍 Status: ${error.response.status}, Data: ${JSON.stringify(error.response.data)}`);
176
+ }
177
+ return null;
178
+ }
179
+ }
67
180
  async onEnd() {
68
181
  const { okToProceed, message } = this.preChecks();
69
182
  if (!okToProceed) {
@@ -73,19 +186,107 @@ class SlackReporter {
73
186
  // SHARDING SUPPORT - Stop slack messages for non-zero index shard(s)
74
187
  // Only shard 0 (0-based) should post when aggregating results from multiple shards
75
188
 
76
- // Detect shard index from multiple sources (GitHub Actions compatible)
77
- let currentShardIndex = process.env.strategy.job-total !== undefined
78
- ? process.env.strategy.job-total
79
- : undefined;
189
+ this.log('🔍 [Shard Detection] Starting shard detection logic...');
190
+
191
+ // Log all relevant environment variables
192
+ this.log(`🔍 [Shard Detection] Environment variables:`);
193
+ this.log(`🔍 CI: ${process.env.CI !== undefined ? `"${process.env.CI}"` : 'undefined'}`);
194
+ this.log(`🔍 GITHUB_ACTIONS: ${process.env.GITHUB_ACTIONS !== undefined ? `"${process.env.GITHUB_ACTIONS}"` : 'undefined'}`);
195
+ this.log(`🔍 GITHUB_JOB: ${process.env.GITHUB_JOB !== undefined ? `"${process.env.GITHUB_JOB}"` : 'undefined'}`);
196
+ this.log(`🔍 MATRIX_SHARD: ${process.env.MATRIX_SHARD !== undefined ? `"${process.env.MATRIX_SHARD}"` : 'undefined'}`);
197
+ this.log(`🔍 MATRIX_INDEX: ${process.env.MATRIX_INDEX !== undefined ? `"${process.env.MATRIX_INDEX}"` : 'undefined'}`);
198
+ this.log(`🔍 MATRIX_COUNT: ${process.env.MATRIX_COUNT !== undefined ? `"${process.env.MATRIX_COUNT}"` : 'undefined'}`);
199
+
200
+ // Step 1: Try GitHub Actions API detection (most robust for GitHub Actions)
201
+ let githubApiShardInfo = null;
202
+ if (process.env.GITHUB_ACTIONS === 'true') {
203
+ this.log(`🔍 [Shard Detection] Attempting GitHub Actions API detection...`);
204
+ githubApiShardInfo = await this.detectGitHubActionsShardInfo();
205
+ }
206
+
207
+ // Step 2: Detect shard index from environment variables (priority)
208
+ let currentShardIndex = process.env.MATRIX_SHARD !== undefined
209
+ ? process.env.MATRIX_SHARD
210
+ : (process.env.MATRIX_INDEX !== undefined ? process.env.MATRIX_INDEX : undefined);
211
+
212
+ // Step 3: Use GitHub API detection if env vars not set
213
+ if (currentShardIndex === undefined && githubApiShardInfo) {
214
+ currentShardIndex = githubApiShardInfo.shardIndex.toString();
215
+ this.log(`🔍 [Shard Detection] Using GitHub API detected shardIndex: "${currentShardIndex}"`);
216
+ }
217
+
218
+ this.log(`🔍 [Shard Detection] Initial currentShardIndex: ${currentShardIndex !== undefined ? `"${currentShardIndex}"` : 'undefined'}`);
219
+
220
+ // Step 4: Get shard count from multiple sources (priority order)
221
+ let totalShards;
222
+ if (process.env.MATRIX_COUNT !== undefined) {
223
+ totalShards = parseInt(process.env.MATRIX_COUNT, 10);
224
+ this.log(`🔍 [Shard Detection] Using MATRIX_COUNT env var: ${totalShards}`);
225
+ } else if (githubApiShardInfo && githubApiShardInfo.totalShards > 1) {
226
+ totalShards = githubApiShardInfo.totalShards;
227
+ this.log(`🔍 [Shard Detection] Using GitHub API detected totalShards: ${totalShards}`);
228
+ } else {
229
+ // Fallback to ResultsParser
230
+ const parserTotalShards = this.resultsParser.totalShardCount;
231
+ totalShards = parserTotalShards;
232
+ this.log(`🔍 [Shard Detection] Using ResultsParser totalShards: ${totalShards}`);
233
+ }
234
+
235
+ // Get shard info from ResultsParser for fallback
236
+ const parserShardIndex = this.resultsParser.shardIndex;
237
+ const parserTotalShards = this.resultsParser.totalShardCount;
238
+
239
+ this.log(`🔍 [Shard Detection] ResultsParser values:`);
240
+ this.log(`🔍 parserShardIndex: ${parserShardIndex !== undefined ? parserShardIndex : 'undefined'}`);
241
+ this.log(`🔍 parserTotalShards: ${parserTotalShards !== undefined ? parserTotalShards : 'undefined'}`);
242
+
243
+ this.log(`🔍 [Shard Detection] Calculated totalShards: ${totalShards}`);
244
+
245
+ // If we still don't have shard index, try to infer from parser or GitHub API
246
+ if (currentShardIndex === undefined) {
247
+ if (githubApiShardInfo && githubApiShardInfo.shardIndex !== undefined) {
248
+ currentShardIndex = githubApiShardInfo.shardIndex.toString();
249
+ this.log(`🔍 [Shard Detection] Using GitHub API shardIndex as fallback: "${currentShardIndex}"`);
250
+ } else if (parserShardIndex !== undefined) {
251
+ currentShardIndex = parserShardIndex.toString();
252
+ this.log(`🔍 [Shard Detection] Using parserShardIndex as fallback: "${currentShardIndex}"`);
253
+ }
254
+ }
255
+
256
+ // CRITICAL: In CI (especially GitHub Actions), if we can't detect shard info, block posting
257
+ // This prevents duplicate messages when running with multiple shards
258
+ if (process.env.CI && currentShardIndex === undefined) {
259
+ if (process.env.GITHUB_ACTIONS === 'true') {
260
+ this.log(`❌ [Shard Detection] BLOCKING: GitHub Actions detected but shard index cannot be determined.`);
261
+ this.log(`❌ Attempted: GitHub API detection ${githubApiShardInfo ? 'succeeded' : 'failed'}, env vars not set`);
262
+ this.log(`❌ This prevents duplicate messages when running with multiple shards.`);
263
+ } else {
264
+ this.log(`❌ [Shard Detection] BLOCKING: CI environment detected but shard info not set.`);
265
+ this.log(`❌ MATRIX_COUNT: undefined, MATRIX_SHARD: undefined`);
266
+ this.log(`❌ This prevents duplicate messages when running with multiple shards.`);
267
+ }
268
+ this.log(`❌ Skipping Slack report to prevent duplicate messages from all shards.`);
269
+ this.log(`💡 Fix: Add these env vars to your workflow:`);
270
+ this.log(`💡 env:`);
271
+ this.log(`💡 MATRIX_SHARD: $\{\{ strategy.job-index \}\} # 0-based (only shard 0 posts)`);
272
+ this.log(`💡 MATRIX_COUNT: $\{\{ strategy.job-total \}\}`);
273
+ this.log(`💡 OR ensure GITHUB_TOKEN has workflow permissions for API detection`);
274
+ return;
275
+ }
80
276
 
81
277
  // Convert to number for comparison (default to 0 if undefined)
82
278
  const shardIndexNum = currentShardIndex !== undefined ? parseInt(currentShardIndex, 10) : 0;
83
279
 
280
+ this.log(`🔍 [Shard Detection] Final values:`);
281
+ this.log(`🔍 currentShardIndex (string): ${currentShardIndex !== undefined ? `"${currentShardIndex}"` : 'undefined'}`);
282
+ this.log(`🔍 shardIndexNum (number): ${shardIndexNum}`);
283
+ this.log(`🔍 totalShards: ${totalShards}`);
284
+
84
285
  // CRITICAL: For GitHub Actions with multiple shards, only shard 0 should post
85
286
  // If we're in CI with multiple shards and don't know which shard we are, block all posts
86
287
  // This prevents duplicate messages when MATRIX_SHARD/MATRIX_INDEX is not set
87
288
  if (process.env.CI && totalShards > 1 && currentShardIndex === undefined) {
88
- this.log(`❌ GitHub Actions detected with ${totalShards} shards but shard index not set (MATRIX_SHARD/MATRIX_INDEX missing).`);
289
+ this.log(`❌ [Shard Detection] BLOCKING: CI environment detected with ${totalShards} shards but shard index not set (MATRIX_SHARD/MATRIX_INDEX missing).`);
89
290
  this.log(`❌ Skipping Slack report to prevent duplicate messages from all shards.`);
90
291
  this.log(`💡 Fix: Add these env vars to your workflow:`);
91
292
  this.log(`💡 env:`);
@@ -97,6 +298,7 @@ class SlackReporter {
97
298
  // Only allow shard 0 (0-based) to post when there are multiple shards
98
299
  // This ensures only one shard posts the aggregated report
99
300
  if (totalShards > 1 && shardIndexNum !== 0) {
301
+ this.log(`❌ [Shard Detection] BLOCKING: Non-zero shard detected (shard ${currentShardIndex} of ${totalShards})`);
100
302
  this.log(`❌ Stopping reporter for non-zero index shard ${currentShardIndex} of ${totalShards}`);
101
303
  this.log(`â„šī¸ Only shard 0 will post the aggregated report.`);
102
304
  return;
@@ -104,9 +306,9 @@ class SlackReporter {
104
306
 
105
307
  // Single shard - always allow posting
106
308
  if (totalShards === 1) {
107
- this.log(`â„šī¸ Single shard detected. Posting Slack report.`);
309
+ this.log(`✅ [Shard Detection] ALLOWING: Single shard detected. Posting Slack report.`);
108
310
  } else {
109
- this.log(`â„šī¸ Multiple shards detected (${totalShards}). Shard ${currentShardIndex} will post aggregated report.`);
311
+ this.log(`✅ [Shard Detection] ALLOWING: Multiple shards detected (${totalShards}). Shard ${currentShardIndex} (index ${shardIndexNum}) will post aggregated report.`);
110
312
  }
111
313
 
112
314
  const resultSummary = await this.resultsParser.getParsedResults();
package/package.json CHANGED
@@ -31,7 +31,7 @@
31
31
  "lint-fix": "npx eslint . --ext .ts --fix"
32
32
  },
33
33
  "name": "playwright-slack-report-burak",
34
- "version": "3.0.4",
34
+ "version": "3.0.6",
35
35
  "main": "index.js",
36
36
  "types": "dist/index.d.ts",
37
37
  "author": "Burak B. <burak.boluk@hotmail.com>",