playwright-slack-report-burak 2.1.0 → 2.2.0

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.
@@ -6,38 +6,120 @@
6
6
  /* eslint-disable class-methods-use-this */
7
7
  /* eslint-disable no-param-reassign */
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
+ const fs = require('fs');
10
+ const path = require('path');
11
+ const axios = require('axios');
12
+ const { execSync } = require('child_process');
13
+
9
14
  class ResultsParser {
10
15
  result;
11
16
  separateFlakyTests;
17
+ totalShardCount = process.env.CIRCLE_NODE_TOTAL ? parseInt(process.env.CIRCLE_NODE_TOTAL, 10) : 1;
18
+ shardIndex = process.env.CIRCLE_NODE_INDEX ? parseInt(process.env.CIRCLE_NODE_INDEX, 10) : 0;
12
19
  constructor(options = { separateFlakyTests: false }) {
13
20
  this.result = [];
14
21
  this.separateFlakyTests = options.separateFlakyTests;
15
22
  }
16
23
  async getParsedResults() {
24
+ const summary = {
25
+ total: 0,
26
+ passed: 0,
27
+ failed: 0,
28
+ flaky: 0,
29
+ skipped: 0,
30
+ failures: [],
31
+ tests: [],
32
+ };
17
33
  const failures = await this.getFailures();
18
34
  const flakes = await this.getFlakes();
19
35
  let passes = await this.getPasses();
20
36
  /*if (this.separateFlakyTests) {
21
37
  passes = this.doSeparateFlakyTests(passes, flakes);
22
38
  }*/
23
- const summary = {
24
- passed: passes.length,
25
- failed: failures.length,
26
- flaky: (this.separateFlakyTests && flakes.length > 0) ?
27
- flakes.length : 0,
39
+
40
+ // Define the directory to store node summaries
41
+ const summariesDir = path.join('./', 'playwright-report');
42
+
43
+ if (!fs.existsSync(summariesDir)) {
44
+ fs.mkdirSync(summariesDir, { recursive: true });
45
+ }
46
+
47
+ // Determine the current node index
48
+ const currentNodeIndex = this.shardIndex;
49
+
50
+ // Define the file for the current node's summary
51
+ const nodeSummaryFile = path.join(summariesDir, `node_summary_${currentNodeIndex}.json`);
52
+
53
+ const totalTestCasesForNode = this.result.reduce((acc, suite) => acc + suite.testSuite.tests.length, 0);
54
+
55
+ // Initialize or read existing summary from file
56
+ let nodeSummary = {
57
+ total: totalTestCasesForNode,
58
+ passed: 0,
59
+ failed: 0,
60
+ flaky: 0,
28
61
  skipped: 0,
29
- failures,
62
+ failures: [],
30
63
  tests: [],
31
64
  };
65
+
66
+ // Create the file if it doesn't exist
67
+ if (!fs.existsSync(nodeSummaryFile)) {
68
+ fs.writeFileSync(nodeSummaryFile, JSON.stringify(nodeSummary, null, 2));
69
+ } else {
70
+ nodeSummary = JSON.parse(fs.readFileSync(nodeSummaryFile, 'utf-8'));
71
+ }
72
+
73
+ // Update the node summary with new test results
32
74
  for (const suite of this.result) {
33
- summary.tests = summary.tests.concat(suite.testSuite.tests);
75
+ nodeSummary.tests = nodeSummary.tests.concat(suite.testSuite.tests);
34
76
  for (const test of suite.testSuite.tests) {
35
77
  if (test.status === 'skipped') {
36
- summary.skipped += 1;
78
+ nodeSummary.skipped += 1;
79
+ }
80
+ if (test.status === 'failed' || test.status === 'timedOut') {
81
+ nodeSummary.failed += 1;
82
+ }
83
+ if (this.separateFlakyTests && test.status === 'passed' && test.retry > 0) {
84
+ nodeSummary.flaky += 1;
85
+ }
86
+ if (test.status === 'passed' && (!this.separateFlakyTests || test.retry === 0)) {
87
+ nodeSummary.passed += 1;
88
+ }
89
+ }
90
+ }
91
+
92
+ // Write the updated summary back to the file for the current node
93
+ fs.writeFileSync(nodeSummaryFile, JSON.stringify(nodeSummary, null, 2));
94
+
95
+ if (this.shardIndex === 0 && this.totalShardCount > 1) {
96
+ await this.fetchAllArtifacts();
97
+ }
98
+
99
+ console.log('Contents of playwright-report for node', this.shardIndex, ':', fs.readdirSync(summariesDir));
100
+
101
+ if (this.allNodeSummaryFilesExist() && this.allBlobZipsExist() && this.shardIndex === 0) {
102
+ // Merge all node summaries into the final summary
103
+ for (let i = 0; i < this.totalShardCount; i++) {
104
+ const nodeSummaryFile = path.join(summariesDir, `node_summary_${i}.json`);
105
+ const fileToRead = nodeSummaryFile;
106
+
107
+ if (fs.existsSync(fileToRead)) {
108
+ const nodeSummary = JSON.parse(fs.readFileSync(fileToRead, 'utf-8'));
109
+ summary.total += nodeSummary.total;
110
+ summary.passed += nodeSummary.passed;
111
+ summary.failed += nodeSummary.failed;
112
+ summary.flaky += nodeSummary.flaky;
113
+ summary.skipped += nodeSummary.skipped;
114
+ summary.failures = summary.failures.concat(nodeSummary.failures);
115
+ summary.tests = summary.tests.concat(nodeSummary.tests);
37
116
  }
38
117
  }
118
+ this.mergeReports();
119
+ return summary;
120
+ } else {
121
+ return { stopReporter: true, currentShardIndex: this.shardIndex, totalShardCount: this.totalShardCount };
39
122
  }
40
- return summary;
41
123
  }
42
124
  async getFailures() {
43
125
  const failures = [];
@@ -179,5 +261,86 @@ class ResultsParser {
179
261
  }
180
262
  return [..._passes.values()];
181
263
  }*/
264
+ allNodeSummaryFilesExist() {
265
+ console.log('Checking if all node summary files exist...');
266
+ const summariesDir = path.join('./', 'playwright-report');
267
+
268
+ for (let i = 0; i < this.totalShardCount; i++) {
269
+ const nodeSummaryFile = path.join(summariesDir, `node_summary_${i}.json`);
270
+ if (!fs.existsSync(nodeSummaryFile)) {
271
+ return false;
272
+ }
273
+ }
274
+ return true;
275
+ }
276
+
277
+ allBlobZipsExist() {
278
+ console.log('Checking if all blob zips exist...');
279
+ const summariesDir = path.join('./', 'playwright-report');
280
+
281
+ for (let i = 0; i < this.totalShardCount; i++) {
282
+ const blobZipFile = path.join(summariesDir, `blob-report-node-${i}.zip`);
283
+ if (!fs.existsSync(blobZipFile)) {
284
+ return false;
285
+ }
286
+ }
287
+ return true;
288
+ }
289
+
290
+ async fetchAllArtifacts() {
291
+ const summariesDir = path.join('./', 'playwright-report');
292
+ while (!this.allNodeSummaryFilesExist() || !this.allBlobZipsExist()) {
293
+ console.log('Waiting for all blob zips to exist...');
294
+ if (!fs.existsSync(summariesDir)) {
295
+ fs.mkdirSync(summariesDir, { recursive: true });
296
+ }
297
+ for (let i = 1; i < this.totalShardCount; i++) {
298
+ await this.fetchArtifact(i, `node_summary_${i}.json`, summariesDir);
299
+ await this.fetchArtifact(i, `blob-report-node-${i}.zip`, summariesDir);
300
+ }
301
+ }
302
+ }
303
+
304
+ async fetchArtifact(i, file, summariesDir) {
305
+ const circleciToken = process.env.safetywingtest_CIRCLECI_API_TOKEN;
306
+ const circleciJobId = process.env.CIRCLE_WORKFLOW_JOB_ID || '5175a4cb-273e-42fd-a5a6-40c3dc31c9de';
307
+ const circleciApiUrl = `https://output.circle-artifacts.com/output/job/${circleciJobId}/artifacts/${i}/html-report/${file}`;
308
+ const filePath = path.join(summariesDir, file);
309
+
310
+ while (true) {
311
+ try {
312
+ const response = await axios.get(circleciApiUrl, {
313
+ headers: {
314
+ 'Circle-Token': circleciToken,
315
+ },
316
+ responseType: 'arraybuffer'
317
+ });
318
+
319
+ fs.writeFileSync(filePath, response.data);
320
+ console.log(`Successfully fetched file ${file} from shard ${i}`);
321
+ break; // Exit the loop if the file is fetched successfully
322
+ } catch (error) {
323
+ if (error.response && error.response.status === 404) {
324
+ console.warn(`File ${file} not found at ${circleciApiUrl}. Retrying in 10 seconds...`);
325
+ await new Promise(resolve => setTimeout(resolve, 10000)); // Wait for 10 seconds
326
+ } else {
327
+ console.error(`Failed to fetch file ${file} from shard ${i}!`, error);
328
+ break; // Exit the loop on other errors
329
+ }
330
+ }
331
+ }
332
+ }
333
+ mergeReports() {
334
+ try {
335
+ // Execute the command to merge reports
336
+ execSync(`npx playwright merge-reports --reporter html -c ./playwright-report playwright-report`);
337
+ //new Promise(resolve => setTimeout(resolve, 5000));
338
+ console.log('Reports merged successfully.');
339
+ console.log('Contents of playwright-report:', fs.readdirSync('./playwright-report'));
340
+ } catch (error) {
341
+ // Log a warning instead of throwing an error
342
+ console.warn('Warning: Failed to merge reports. This may not affect the overall process.', error.message);
343
+ }
344
+ }
182
345
  }
183
346
  exports.default = ResultsParser;
@@ -71,6 +71,11 @@ class SlackReporter {
71
71
  return;
72
72
  }
73
73
  const resultSummary = await this.resultsParser.getParsedResults();
74
+ // SHARDING SUPPORT - Stop slack messages for non-zero index node
75
+ if (process.env.CIRCLE_NODE_INDEX && process.env.CIRCLE_NODE_INDEX !== '0') {
76
+ this.log(`❌ Stopping reporter for non-zero index node ${process.env.CIRCLE_NODE_INDEX} of ${process.env.CIRCLE_NODE_TOTAL}`);
77
+ return;
78
+ }
74
79
  resultSummary.meta = this.meta;
75
80
  const maxRetry = Math.max(...resultSummary.tests.map((o) => o.retry));
76
81
  if (this.sendResults === 'on-failure'
package/package.json CHANGED
@@ -30,7 +30,7 @@
30
30
  "lint-fix": "npx eslint . --ext .ts --fix"
31
31
  },
32
32
  "name": "playwright-slack-report-burak",
33
- "version": "2.1.0",
33
+ "version": "2.2.0",
34
34
  "main": "index.js",
35
35
  "types": "dist/index.d.ts",
36
36
  "repository": "git@github.com:ryanrosello-og/playwright-slack-report.git",