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.
- package/dist/src/ResultsParser.js +17 -2
- package/dist/src/SlackReporter.js +209 -7
- package/package.json +1 -1
|
@@ -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('
|
|
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
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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(`â
|
|
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(
|
|
309
|
+
this.log(`â
[Shard Detection] ALLOWING: Single shard detected. Posting Slack report.`);
|
|
108
310
|
} else {
|
|
109
|
-
this.log(
|
|
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