playwright-slack-report-burak 3.0.5 → 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/SlackReporter.js +175 -13
- package/package.json +1 -1
|
@@ -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) {
|
|
@@ -78,22 +191,48 @@ class SlackReporter {
|
|
|
78
191
|
// Log all relevant environment variables
|
|
79
192
|
this.log(`🔍 [Shard Detection] Environment variables:`);
|
|
80
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'}`);
|
|
81
196
|
this.log(`🔍 MATRIX_SHARD: ${process.env.MATRIX_SHARD !== undefined ? `"${process.env.MATRIX_SHARD}"` : 'undefined'}`);
|
|
82
197
|
this.log(`🔍 MATRIX_INDEX: ${process.env.MATRIX_INDEX !== undefined ? `"${process.env.MATRIX_INDEX}"` : 'undefined'}`);
|
|
83
198
|
this.log(`🔍 MATRIX_COUNT: ${process.env.MATRIX_COUNT !== undefined ? `"${process.env.MATRIX_COUNT}"` : 'undefined'}`);
|
|
84
199
|
|
|
85
|
-
//
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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)
|
|
90
208
|
let currentShardIndex = process.env.MATRIX_SHARD !== undefined
|
|
91
209
|
? process.env.MATRIX_SHARD
|
|
92
210
|
: (process.env.MATRIX_INDEX !== undefined ? process.env.MATRIX_INDEX : undefined);
|
|
93
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
|
+
|
|
94
218
|
this.log(`🔍 [Shard Detection] Initial currentShardIndex: ${currentShardIndex !== undefined ? `"${currentShardIndex}"` : 'undefined'}`);
|
|
95
219
|
|
|
96
|
-
// Get shard
|
|
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
|
|
97
236
|
const parserShardIndex = this.resultsParser.shardIndex;
|
|
98
237
|
const parserTotalShards = this.resultsParser.totalShardCount;
|
|
99
238
|
|
|
@@ -101,15 +240,38 @@ class SlackReporter {
|
|
|
101
240
|
this.log(`🔍 parserShardIndex: ${parserShardIndex !== undefined ? parserShardIndex : 'undefined'}`);
|
|
102
241
|
this.log(`🔍 parserTotalShards: ${parserTotalShards !== undefined ? parserTotalShards : 'undefined'}`);
|
|
103
242
|
|
|
104
|
-
// Use parser values if env vars not set
|
|
105
|
-
const totalShards = process.env.MATRIX_COUNT ? parseInt(process.env.MATRIX_COUNT, 10) : parserTotalShards;
|
|
106
|
-
|
|
107
243
|
this.log(`🔍 [Shard Detection] Calculated totalShards: ${totalShards}`);
|
|
108
244
|
|
|
109
|
-
// If we still don't have shard index, try to infer from parser
|
|
110
|
-
if (currentShardIndex === undefined
|
|
111
|
-
|
|
112
|
-
|
|
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;
|
|
113
275
|
}
|
|
114
276
|
|
|
115
277
|
// Convert to number for comparison (default to 0 if undefined)
|
package/package.json
CHANGED