playwright-slack-report-burak 3.0.21 → 3.0.22
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 +15 -65
- package/dist/src/SlackReporter.js +13 -305
- package/package.json +1 -1
|
@@ -33,16 +33,12 @@ try {
|
|
|
33
33
|
class ResultsParser {
|
|
34
34
|
result;
|
|
35
35
|
separateFlakyTests;
|
|
36
|
-
// Handle both 0-based and 1-based shard indexing
|
|
37
|
-
// If MATRIX_SHARD is not set, try to infer from workflow (1-based) or default to 0
|
|
38
36
|
totalShardCount = process.env.TOTAL_SHARDS
|
|
39
37
|
? parseInt(process.env.TOTAL_SHARDS, 10)
|
|
40
|
-
:
|
|
38
|
+
: 1;
|
|
41
39
|
shardIndex = process.env.SHARD_INDEX !== undefined
|
|
42
40
|
? parseInt(process.env.SHARD_INDEX, 10)
|
|
43
|
-
:
|
|
44
|
-
? parseInt(process.env.MATRIX_SHARD, 10)
|
|
45
|
-
: (process.env.MATRIX_INDEX !== undefined ? parseInt(process.env.MATRIX_INDEX, 10) : 0));
|
|
41
|
+
: 1;
|
|
46
42
|
|
|
47
43
|
constructor(options = { separateFlakyTests: false }) {
|
|
48
44
|
this.result = [];
|
|
@@ -438,17 +434,14 @@ class ResultsParser {
|
|
|
438
434
|
// GitHub Actions artifact API endpoint
|
|
439
435
|
const githubApiUrl = `https://api.github.com/repos/${repo}/actions/artifacts`;
|
|
440
436
|
|
|
441
|
-
// Try multiple artifact naming patterns:
|
|
442
|
-
// 1. html-report-{i} (1-based workflow style
|
|
443
|
-
// 2. blob-report-node-{i} (direct blob reports
|
|
444
|
-
// 3. test-results-{i} (alternative naming
|
|
445
|
-
// Note: i is 1-based internally now, so we use it directly for GitHub Actions artifacts
|
|
437
|
+
// Try multiple artifact naming patterns (all 1-based):
|
|
438
|
+
// 1. html-report-{i} (1-based workflow style)
|
|
439
|
+
// 2. blob-report-node-{i} (direct blob reports)
|
|
440
|
+
// 3. test-results-{i} (alternative naming)
|
|
446
441
|
const artifactNamePatterns = [
|
|
447
|
-
`html-report-${i}`,
|
|
448
|
-
`blob-report-node-${i}`,
|
|
449
|
-
`
|
|
450
|
-
`blob-report-node-${i - 1}`, // 0-based direct blob (fallback)
|
|
451
|
-
`test-results-${i}`, // Alternative naming
|
|
442
|
+
`html-report-${i}`,
|
|
443
|
+
`blob-report-node-${i}`,
|
|
444
|
+
`test-results-${i}`,
|
|
452
445
|
];
|
|
453
446
|
|
|
454
447
|
while (true) {
|
|
@@ -469,10 +462,8 @@ class ResultsParser {
|
|
|
469
462
|
for (const pattern of artifactNamePatterns) {
|
|
470
463
|
artifact = listResponse.data.artifacts.find(
|
|
471
464
|
(a) => a.name === pattern ||
|
|
472
|
-
a.name.includes(`shard-${i}`) ||
|
|
473
|
-
a.name.includes(`
|
|
474
|
-
a.name.includes(`node-${i}`) ||
|
|
475
|
-
a.name.includes(`node-${i - 1}`)
|
|
465
|
+
a.name.includes(`shard-${i}`) ||
|
|
466
|
+
a.name.includes(`node-${i}`)
|
|
476
467
|
);
|
|
477
468
|
if (artifact) {
|
|
478
469
|
console.log(`Found artifact: ${artifact.name} (matched pattern: ${pattern})`);
|
|
@@ -506,9 +497,8 @@ class ResultsParser {
|
|
|
506
497
|
const possiblePaths = [
|
|
507
498
|
file,
|
|
508
499
|
`playwright-report/${file}`,
|
|
509
|
-
`playwright-report/${file}`,
|
|
510
500
|
`html-report-${i}/${file}`,
|
|
511
|
-
`html-report-${i
|
|
501
|
+
`html-report-${i}/playwright-report/${file}`,
|
|
512
502
|
];
|
|
513
503
|
for (const possiblePath of possiblePaths) {
|
|
514
504
|
zipEntry = zip.getEntry(possiblePath);
|
|
@@ -526,22 +516,18 @@ class ResultsParser {
|
|
|
526
516
|
zip.extractAllTo(extractPath, true);
|
|
527
517
|
|
|
528
518
|
// Look for the file recursively
|
|
529
|
-
const searchInDir = (dir, targetFile
|
|
519
|
+
const searchInDir = (dir, targetFile) => {
|
|
530
520
|
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
531
521
|
for (const entry of entries) {
|
|
532
522
|
const fullPath = path.join(dir, entry.name);
|
|
533
523
|
if (entry.isDirectory()) {
|
|
534
|
-
const found = searchInDir(fullPath, targetFile
|
|
524
|
+
const found = searchInDir(fullPath, targetFile);
|
|
535
525
|
if (found) return found;
|
|
536
526
|
} else {
|
|
537
527
|
// Check exact match
|
|
538
528
|
if (entry.name === targetFile) {
|
|
539
529
|
return fullPath;
|
|
540
530
|
}
|
|
541
|
-
// Check alternate filename (e.g., node_summary_0.json when we need node_summary_1.json)
|
|
542
|
-
if (alternateTargetFile && entry.name === alternateTargetFile) {
|
|
543
|
-
return fullPath;
|
|
544
|
-
}
|
|
545
531
|
// Check if ends with target file
|
|
546
532
|
if (entry.name.endsWith(targetFile)) {
|
|
547
533
|
return fullPath;
|
|
@@ -551,45 +537,9 @@ class ResultsParser {
|
|
|
551
537
|
return null;
|
|
552
538
|
};
|
|
553
539
|
|
|
554
|
-
// Try to find the file
|
|
540
|
+
// Try to find the file recursively
|
|
555
541
|
let foundPath = searchInDir(extractPath, file);
|
|
556
542
|
|
|
557
|
-
// If file uses index 0 pattern (like node_summary_0.json or blob-report-node-0.zip)
|
|
558
|
-
// but we need it for a different shard, try to find any matching pattern
|
|
559
|
-
if (!foundPath && (file.includes('_0') || file.includes('-node-0'))) {
|
|
560
|
-
// Recursively search for any node_summary or blob-report file
|
|
561
|
-
const findAllFiles = (dir, pattern) => {
|
|
562
|
-
const results = [];
|
|
563
|
-
try {
|
|
564
|
-
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
565
|
-
for (const entry of entries) {
|
|
566
|
-
const fullPath = path.join(dir, entry.name);
|
|
567
|
-
if (entry.isDirectory()) {
|
|
568
|
-
results.push(...findAllFiles(fullPath, pattern));
|
|
569
|
-
} else if (entry.name.includes(pattern)) {
|
|
570
|
-
results.push(fullPath);
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
} catch (e) {
|
|
574
|
-
// Ignore errors
|
|
575
|
-
}
|
|
576
|
-
return results;
|
|
577
|
-
};
|
|
578
|
-
|
|
579
|
-
// Find any node_summary or blob-report files
|
|
580
|
-
const matchingFiles = findAllFiles(extractPath, file.includes('node_summary') ? 'node_summary' : 'blob-report');
|
|
581
|
-
|
|
582
|
-
if (matchingFiles.length > 0) {
|
|
583
|
-
foundPath = matchingFiles[0];
|
|
584
|
-
// Copy to the expected path with correct shard index
|
|
585
|
-
fs.copyFileSync(foundPath, filePath);
|
|
586
|
-
console.log(`Found and copied file ${path.basename(foundPath)} -> ${file} from artifact ${artifact.name}`);
|
|
587
|
-
// Clean up temp directory
|
|
588
|
-
fs.rmSync(extractPath, { recursive: true, force: true });
|
|
589
|
-
break;
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
|
|
593
543
|
if (foundPath) {
|
|
594
544
|
fs.copyFileSync(foundPath, filePath);
|
|
595
545
|
console.log(`Successfully fetched file ${file} from GitHub Actions shard ${i} (artifact: ${artifact.name})`);
|
|
@@ -90,182 +90,6 @@ class SlackReporter {
|
|
|
90
90
|
onTestEnd(test, result) {
|
|
91
91
|
this.resultsParser.addTestResult(test.parent.title, test, this.browsers);
|
|
92
92
|
}
|
|
93
|
-
/**
|
|
94
|
-
* Detect GitHub Actions shard information using GitHub API
|
|
95
|
-
* Returns { shardIndex: number, totalShards: number } or null if detection fails
|
|
96
|
-
*/
|
|
97
|
-
async detectGitHubActionsShardInfo() {
|
|
98
|
-
// Check if we're in GitHub Actions
|
|
99
|
-
if (!process.env.GITHUB_ACTIONS || process.env.GITHUB_ACTIONS !== 'true') {
|
|
100
|
-
return null;
|
|
101
|
-
}
|
|
102
|
-
// Try GITHUB_TOKEN first (automatic), then fallback to custom token
|
|
103
|
-
const githubToken = process.env.GITHUB_TOKEN || process.env.SAFETYWINGTEST_GITHUB_TOKEN;
|
|
104
|
-
const repo = process.env.GITHUB_REPOSITORY;
|
|
105
|
-
const runId = process.env.GITHUB_RUN_ID;
|
|
106
|
-
const currentJobId = process.env.GITHUB_JOB;
|
|
107
|
-
if (!githubToken || !repo || !runId) {
|
|
108
|
-
return null;
|
|
109
|
-
}
|
|
110
|
-
if (!axios) {
|
|
111
|
-
return null;
|
|
112
|
-
}
|
|
113
|
-
try {
|
|
114
|
-
// Get workflow run details
|
|
115
|
-
const apiUrl = `https://api.github.com/repos/${repo}/actions/runs/${runId}/jobs`;
|
|
116
|
-
const response = await axios.get(apiUrl, {
|
|
117
|
-
headers: {
|
|
118
|
-
'Accept': 'application/vnd.github+json',
|
|
119
|
-
'Authorization': `Bearer ${githubToken}`,
|
|
120
|
-
'X-GitHub-Api-Version': '2022-11-28'
|
|
121
|
-
},
|
|
122
|
-
params: {
|
|
123
|
-
per_page: 100
|
|
124
|
-
}
|
|
125
|
-
});
|
|
126
|
-
const jobs = response.data.jobs || [];
|
|
127
|
-
if (jobs.length === 0) {
|
|
128
|
-
return null;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// Helper function to normalize job names by removing shard indicators
|
|
132
|
-
// e.g., "Run tests (shard 3/4)" -> "Run tests"
|
|
133
|
-
const normalizeJobName = (name) => {
|
|
134
|
-
if (!name) return '';
|
|
135
|
-
// Remove common matrix patterns: (shard X/Y), (X, Y), [X], etc.
|
|
136
|
-
return name
|
|
137
|
-
.replace(/\s*\(shard\s+\d+\/\d+\)/gi, '')
|
|
138
|
-
.replace(/\s*\(\d+\/\d+\)/g, '')
|
|
139
|
-
.replace(/\s*\(\d+,\s*\d+\)/g, '')
|
|
140
|
-
.replace(/\s*\[\d+\]/g, '')
|
|
141
|
-
.trim();
|
|
142
|
-
};
|
|
143
|
-
|
|
144
|
-
// Group jobs by normalized name to find matrix jobs
|
|
145
|
-
const jobGroups = {};
|
|
146
|
-
for (const job of jobs) {
|
|
147
|
-
const jobName = job.name || '';
|
|
148
|
-
const normalizedName = normalizeJobName(jobName);
|
|
149
|
-
if (!jobGroups[normalizedName]) {
|
|
150
|
-
jobGroups[normalizedName] = [];
|
|
151
|
-
}
|
|
152
|
-
jobGroups[normalizedName].push(job);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// Find the job group that contains the current job
|
|
156
|
-
let currentJob = null;
|
|
157
|
-
let jobGroup = null;
|
|
158
|
-
|
|
159
|
-
// Strategy 1: Find the currently running job (status: in_progress or completed recently)
|
|
160
|
-
const runningJobs = jobs.filter(j => j.status === 'in_progress' || j.status === 'queued');
|
|
161
|
-
|
|
162
|
-
if (runningJobs.length === 1) {
|
|
163
|
-
// Perfect - only one job is running, must be us
|
|
164
|
-
currentJob = runningJobs[0];
|
|
165
|
-
const normalizedName = normalizeJobName(currentJob.name);
|
|
166
|
-
jobGroup = jobGroups[normalizedName];
|
|
167
|
-
} else if (runningJobs.length > 1) {
|
|
168
|
-
// Multiple running jobs - try to match by job ID or name
|
|
169
|
-
for (const job of runningJobs) {
|
|
170
|
-
// Check if this job's name contains the GITHUB_JOB id
|
|
171
|
-
// But be more specific - avoid matching "Generate test matrix" when looking for "test"
|
|
172
|
-
const jobIdLower = currentJobId.toLowerCase();
|
|
173
|
-
const jobNameLower = (job.name || '').toLowerCase();
|
|
174
|
-
|
|
175
|
-
// Match if job name starts with or equals the job ID (more specific)
|
|
176
|
-
if (jobNameLower.startsWith(jobIdLower) ||
|
|
177
|
-
jobNameLower.includes(`${jobIdLower} `) ||
|
|
178
|
-
jobNameLower.includes(`${jobIdLower}(`) ||
|
|
179
|
-
jobNameLower === jobIdLower) {
|
|
180
|
-
currentJob = job;
|
|
181
|
-
const normalizedName = normalizeJobName(job.name);
|
|
182
|
-
jobGroup = jobGroups[normalizedName];
|
|
183
|
-
break;
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// Still no match? Take the first running job that looks like a test job
|
|
188
|
-
if (!currentJob) {
|
|
189
|
-
for (const job of runningJobs) {
|
|
190
|
-
if (job.name && (job.name.includes('shard') || job.name.includes('test') || job.name.includes('matrix'))) {
|
|
191
|
-
currentJob = job;
|
|
192
|
-
const normalizedName = normalizeJobName(job.name);
|
|
193
|
-
jobGroup = jobGroups[normalizedName];
|
|
194
|
-
break;
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// Strategy 2: If no running job found, look at all jobs
|
|
201
|
-
if (!currentJob) {
|
|
202
|
-
for (const job of jobs) {
|
|
203
|
-
const jobIdLower = currentJobId.toLowerCase();
|
|
204
|
-
const jobNameLower = (job.name || '').toLowerCase();
|
|
205
|
-
|
|
206
|
-
if (jobNameLower.startsWith(jobIdLower) ||
|
|
207
|
-
jobNameLower.includes(`${jobIdLower} `) ||
|
|
208
|
-
jobNameLower.includes(`${jobIdLower}(`)) {
|
|
209
|
-
currentJob = job;
|
|
210
|
-
const normalizedName = normalizeJobName(job.name);
|
|
211
|
-
jobGroup = jobGroups[normalizedName];
|
|
212
|
-
break;
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
// Fallback: Use the largest job group (likely the matrix jobs)
|
|
218
|
-
if (!jobGroup || jobGroup.length === 0) {
|
|
219
|
-
let largestGroup = [];
|
|
220
|
-
for (const [, groupJobs] of Object.entries(jobGroups)) {
|
|
221
|
-
if (groupJobs.length > largestGroup.length) {
|
|
222
|
-
largestGroup = groupJobs;
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
jobGroup = largestGroup;
|
|
226
|
-
currentJob = jobGroup[0];
|
|
227
|
-
}
|
|
228
|
-
// Sort jobs by name or ID to get consistent ordering
|
|
229
|
-
jobGroup.sort((a, b) => {
|
|
230
|
-
if (a.name && b.name) {
|
|
231
|
-
return a.name.localeCompare(b.name);
|
|
232
|
-
}
|
|
233
|
-
return a.id - b.id;
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
// Find index of current job in the sorted group
|
|
237
|
-
const currentJobIndex = jobGroup.findIndex(j => j.id === currentJob?.id || j.name === currentJob?.name);
|
|
238
|
-
|
|
239
|
-
// Try to extract shard number from job name (e.g., "shard 3/4" -> index 3)
|
|
240
|
-
// GitHub Actions uses 1-based indexing (shard 1, 2, 3, 4), we keep it 1-based internally for shard 1 logic
|
|
241
|
-
let shardIndex = currentJobIndex >= 0 ? currentJobIndex : 0;
|
|
242
|
-
if (currentJob && currentJob.name) {
|
|
243
|
-
const shardMatch = currentJob.name.match(/shard\s+(\d+)\/(\d+)/i);
|
|
244
|
-
if (shardMatch) {
|
|
245
|
-
const shardNum = parseInt(shardMatch[1], 10); // 1-based from GitHub Actions (1, 2, 3, 4)
|
|
246
|
-
const totalFromName = parseInt(shardMatch[2], 10);
|
|
247
|
-
// Keep GitHub Actions 1-based indexing (shard 1 -> index 1, shard 2 -> index 2, etc.)
|
|
248
|
-
// This allows shard 1 to be the aggregator
|
|
249
|
-
shardIndex = shardNum;
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
// If we couldn't extract from name but have job index, convert from 0-based array index to 1-based
|
|
254
|
-
// GitHub Actions jobs are numbered starting from 1, so array index 0 = shard 1, index 1 = shard 2, etc.
|
|
255
|
-
// We need to convert: array index 0 -> shard 1, array index 1 -> shard 2, etc.
|
|
256
|
-
if (shardIndex === currentJobIndex && currentJobIndex >= 0) {
|
|
257
|
-
shardIndex = currentJobIndex + 1; // Convert 0-based array index to 1-based shard index
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
const totalShards = jobGroup.length;
|
|
261
|
-
return {
|
|
262
|
-
shardIndex,
|
|
263
|
-
totalShards
|
|
264
|
-
};
|
|
265
|
-
} catch (error) {
|
|
266
|
-
return null;
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
93
|
async onEnd() {
|
|
270
94
|
const { okToProceed, message } = this.preChecks();
|
|
271
95
|
if (!okToProceed) {
|
|
@@ -275,108 +99,20 @@ class SlackReporter {
|
|
|
275
99
|
// SHARDING SUPPORT - Stop slack messages for non-shard-1 shard(s)
|
|
276
100
|
// Only shard 1 should post when aggregating results from multiple shards
|
|
277
101
|
|
|
278
|
-
//
|
|
279
|
-
const
|
|
280
|
-
|
|
281
|
-
process.env.GITHUB_RUNNER_NAME !== undefined;
|
|
282
|
-
|
|
283
|
-
// Check Playwright's shard config (highest priority for local runs)
|
|
284
|
-
let playwrightShardInfo = null;
|
|
285
|
-
if (this.fullConfig) {
|
|
286
|
-
if (this.fullConfig.shard) {
|
|
287
|
-
// Playwright shard can be either { current, total } or undefined
|
|
288
|
-
if (this.fullConfig.shard.current !== undefined && this.fullConfig.shard.total !== undefined) {
|
|
289
|
-
playwrightShardInfo = {
|
|
290
|
-
current: this.fullConfig.shard.current,
|
|
291
|
-
total: this.fullConfig.shard.total
|
|
292
|
-
};
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
// Also check if shard info is in process.argv (command line)
|
|
298
|
-
// This is a fallback if config doesn't have it but --shard was passed
|
|
299
|
-
if (!playwrightShardInfo && process.argv) {
|
|
300
|
-
const shardArg = process.argv.find(arg => arg && arg.includes('--shard'));
|
|
301
|
-
if (shardArg) {
|
|
302
|
-
const shardMatch = shardArg.match(/--shard[=:]?(\d+)\/(\d+)/);
|
|
303
|
-
if (shardMatch) {
|
|
304
|
-
playwrightShardInfo = {
|
|
305
|
-
current: parseInt(shardMatch[1], 10),
|
|
306
|
-
total: parseInt(shardMatch[2], 10)
|
|
307
|
-
};
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
// Step 1: Try GitHub Actions API detection (only if actually in GitHub Actions)
|
|
313
|
-
let githubApiShardInfo = null;
|
|
314
|
-
if (isActuallyInGitHubActions) {
|
|
315
|
-
githubApiShardInfo = await this.detectGitHubActionsShardInfo();
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
// Step 2: Detect shard index (priority order: Playwright config > env vars > GitHub API)
|
|
319
|
-
let currentShardIndex;
|
|
320
|
-
if (playwrightShardInfo) {
|
|
321
|
-
// Playwright uses 1-based indexing, keep it 1-based for shard 1 logic
|
|
322
|
-
currentShardIndex = playwrightShardInfo.current.toString();
|
|
323
|
-
} else if (process.env.SHARD_INDEX !== undefined) {
|
|
324
|
-
currentShardIndex = process.env.SHARD_INDEX;
|
|
325
|
-
} else if (process.env.MATRIX_SHARD !== undefined) {
|
|
326
|
-
currentShardIndex = process.env.MATRIX_SHARD;
|
|
327
|
-
} else if (process.env.MATRIX_INDEX !== undefined) {
|
|
328
|
-
currentShardIndex = process.env.MATRIX_INDEX;
|
|
329
|
-
} else if (githubApiShardInfo) {
|
|
330
|
-
currentShardIndex = githubApiShardInfo.shardIndex.toString();
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
// Step 3: Get shard count from multiple sources (priority order)
|
|
334
|
-
let totalShards;
|
|
335
|
-
if (playwrightShardInfo) {
|
|
336
|
-
totalShards = playwrightShardInfo.total;
|
|
337
|
-
} else if (process.env.TOTAL_SHARDS !== undefined) {
|
|
338
|
-
totalShards = parseInt(process.env.TOTAL_SHARDS, 10);
|
|
339
|
-
} else if (process.env.MATRIX_COUNT !== undefined) {
|
|
340
|
-
totalShards = parseInt(process.env.MATRIX_COUNT, 10);
|
|
341
|
-
} else if (githubApiShardInfo && githubApiShardInfo.totalShards > 1) {
|
|
342
|
-
totalShards = githubApiShardInfo.totalShards;
|
|
343
|
-
} else {
|
|
344
|
-
// Fallback to ResultsParser
|
|
345
|
-
totalShards = this.resultsParser.totalShardCount;
|
|
346
|
-
}
|
|
102
|
+
// Get shard info from environment variables
|
|
103
|
+
const currentShardIndex = process.env.SHARD_INDEX;
|
|
104
|
+
const totalShards = process.env.TOTAL_SHARDS ? parseInt(process.env.TOTAL_SHARDS, 10) : 1;
|
|
347
105
|
|
|
348
|
-
//
|
|
349
|
-
const parserShardIndex = this.resultsParser.shardIndex;
|
|
350
|
-
|
|
351
|
-
// If we still don't have shard index, try to infer from parser or GitHub API
|
|
352
|
-
if (currentShardIndex === undefined) {
|
|
353
|
-
if (githubApiShardInfo && githubApiShardInfo.shardIndex !== undefined) {
|
|
354
|
-
currentShardIndex = githubApiShardInfo.shardIndex.toString();
|
|
355
|
-
} else if (parserShardIndex !== undefined) {
|
|
356
|
-
currentShardIndex = parserShardIndex.toString();
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
// CRITICAL: In CI (especially GitHub Actions), if we can't detect shard info, block posting
|
|
106
|
+
// CRITICAL: In CI, if we can't detect shard info, block posting
|
|
361
107
|
// This prevents duplicate messages when running with multiple shards
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
if (process.env.GITHUB_ACTIONS === 'true') {
|
|
366
|
-
this.log(`❌ [Shard Detection] BLOCKING: GitHub Actions detected but shard index cannot be determined.`);
|
|
367
|
-
this.log(`❌ Attempted: GitHub API detection ${githubApiShardInfo ? 'succeeded' : 'failed'}, env vars not set`);
|
|
368
|
-
this.log(`❌ This prevents duplicate messages when running with multiple shards.`);
|
|
369
|
-
} else {
|
|
370
|
-
this.log(`❌ [Shard Detection] BLOCKING: CI environment detected but shard info not set.`);
|
|
371
|
-
this.log(`❌ MATRIX_COUNT: undefined, MATRIX_SHARD: undefined`);
|
|
372
|
-
this.log(`❌ This prevents duplicate messages when running with multiple shards.`);
|
|
373
|
-
}
|
|
108
|
+
if (process.env.CI && currentShardIndex === undefined && totalShards > 1) {
|
|
109
|
+
this.log(`❌ [Shard Detection] BLOCKING: CI environment detected but SHARD_INDEX not set.`);
|
|
110
|
+
this.log(`❌ This prevents duplicate messages when running with multiple shards.`);
|
|
374
111
|
this.log(`❌ Skipping Slack report to prevent duplicate messages from all shards.`);
|
|
375
|
-
this.log(`💡 Fix: Add
|
|
112
|
+
this.log(`💡 Fix: Add SHARD_INDEX env var to your workflow:`);
|
|
376
113
|
this.log(`💡 env:`);
|
|
377
|
-
this.log(`💡
|
|
378
|
-
this.log(`💡
|
|
379
|
-
this.log(`💡 OR ensure SAFETYWINGTEST_GITHUB_TOKEN has workflow permissions for API detection`);
|
|
114
|
+
this.log(`💡 SHARD_INDEX: $\{\{ strategy.job-index \}\} # Only shard 1 should post`);
|
|
115
|
+
this.log(`💡 TOTAL_SHARDS: $\{\{ strategy.job-total \}\}`);
|
|
380
116
|
return;
|
|
381
117
|
}
|
|
382
118
|
|
|
@@ -387,37 +123,11 @@ class SlackReporter {
|
|
|
387
123
|
// This ensures ResultsParser always has correct values, even if we block posting
|
|
388
124
|
this.resultsParser.updateShardInfo(shardIndexNum, totalShards);
|
|
389
125
|
|
|
390
|
-
// For local runs with CI env vars set but not actually in CI, allow posting if single shard
|
|
391
|
-
// or if shard index is 1
|
|
392
|
-
if (!isActuallyInGitHubActions && process.env.CI && totalShards > 1 && currentShardIndex === undefined) {
|
|
393
|
-
this.log(`⚠️ [Shard Detection] Local run with CI env vars but shard info unclear - allowing post (may cause duplicates)`);
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
// CRITICAL: For GitHub Actions with multiple shards, only shard 1 should post
|
|
397
|
-
// If we're in CI with multiple shards and don't know which shard we are, block all posts
|
|
398
|
-
// This prevents duplicate messages when MATRIX_SHARD/MATRIX_INDEX is not set
|
|
399
|
-
// Note: GitHub Actions uses 1-based indexing (shard 1, 2, 3, 4)
|
|
400
|
-
// We use 0-based internally, so GitHub Actions shard 1 = internal index 1
|
|
401
|
-
if (isActuallyInGitHubActions && totalShards > 1 && currentShardIndex === undefined) {
|
|
402
|
-
this.log(`❌ [Shard Detection] BLOCKING: GitHub Actions detected with ${totalShards} shards but shard index not set (MATRIX_SHARD/MATRIX_INDEX missing).`);
|
|
403
|
-
this.log(`❌ Skipping Slack report to prevent duplicate messages from all shards.`);
|
|
404
|
-
this.log(`💡 Fix: Add these env vars to your workflow:`);
|
|
405
|
-
this.log(`💡 env:`);
|
|
406
|
-
this.log(`💡 MATRIX_SHARD: $\{\{ strategy.job-index \}\} # GitHub Actions uses 1-based, convert to 0-based (only shard 1/internal index 1 posts)`);
|
|
407
|
-
this.log(`💡 MATRIX_COUNT: $\{\{ strategy.job-total \}\}`);
|
|
408
|
-
this.log(`💡 OR ensure SAFETYWINGTEST_GITHUB_TOKEN has workflow permissions for API detection`);
|
|
409
|
-
return;
|
|
410
|
-
}
|
|
411
|
-
|
|
412
126
|
// Only allow shard 1 to post when there are multiple shards
|
|
413
127
|
// This ensures only one shard posts the aggregated report
|
|
414
|
-
// Note: GitHub Actions uses 1-based indexing (shard 1, 2, 3, 4)
|
|
415
|
-
// We use 0-based internally, so shard 1 in GitHub Actions = index 1 internally
|
|
416
128
|
if (totalShards > 1 && shardIndexNum !== 1) {
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
this.log(`❌ [Shard Detection] BLOCKING: Non-shard-1 detected (shard ${displayShard} of ${totalShards}${isActuallyInGitHubActions ? ' [GitHub Actions 1-based]' : ''})`);
|
|
420
|
-
this.log(`❌ Stopping reporter for shard ${displayShard} of ${totalShards}`);
|
|
129
|
+
this.log(`❌ [Shard Detection] BLOCKING: Non-shard-1 detected (shard ${shardIndexNum} of ${totalShards})`);
|
|
130
|
+
this.log(`❌ Stopping reporter for shard ${shardIndexNum} of ${totalShards}`);
|
|
421
131
|
this.log(`ℹ️ Only shard 1 will post the aggregated report.`);
|
|
422
132
|
return;
|
|
423
133
|
}
|
|
@@ -426,9 +136,7 @@ class SlackReporter {
|
|
|
426
136
|
if (totalShards === 1) {
|
|
427
137
|
this.log(`✅ [Shard Detection] ALLOWING: Single shard detected. Posting Slack report.`);
|
|
428
138
|
} else {
|
|
429
|
-
|
|
430
|
-
const displayShard = isActuallyInGitHubActions ? shardIndexNum + 1 : shardIndexNum;
|
|
431
|
-
this.log(`✅ [Shard Detection] ALLOWING: Multiple shards detected (${totalShards}). Shard ${displayShard} (index ${shardIndexNum}${isActuallyInGitHubActions ? ', GitHub Actions 1-based' : ''}) will post aggregated report.`);
|
|
139
|
+
this.log(`✅ [Shard Detection] ALLOWING: Multiple shards detected (${totalShards}). Shard ${shardIndexNum} will post aggregated report.`);
|
|
432
140
|
}
|
|
433
141
|
|
|
434
142
|
const resultSummary = await this.resultsParser.getParsedResults();
|
package/package.json
CHANGED