playwright-slack-report-burak 3.3.0 → 3.5.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.
@@ -7,14 +7,15 @@
7
7
  /* eslint-disable no-param-reassign */
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
9
  const fs = require('fs');
10
+ const os = require('os');
10
11
  const path = require('path');
11
12
  const axios = require('axios');
12
- const { exec } = require('child_process');
13
+ const { exec, execFileSync } = require('child_process');
13
14
  let AdmZip;
14
15
  try {
15
16
  AdmZip = require('adm-zip');
16
17
  } catch (e) {
17
- // adm-zip is required for GitHub Actions artifact extraction
18
+ // adm-zip is required for unzipping Playwright blob reports during merge
18
19
  AdmZip = null;
19
20
  }
20
21
 
@@ -27,8 +28,6 @@ const SUMMARIES_DIR = path.join('./', 'playwright-artifacts');
27
28
  const PLAYWRIGHT_REPORT_DIR = path.join('./', 'playwright-report');
28
29
 
29
30
  // Environment variables
30
- const GITHUB_TOKEN = process.env.SAFETYWINGTEST_GITHUB_TOKEN;
31
- const GITHUB_REPOSITORY = process.env.GITHUB_REPOSITORY;
32
31
  const GITHUB_RUN_ID = process.env.GITHUB_RUN_ID || 'local';
33
32
  const GITHUB_RUN_ATTEMPT = process.env.GITHUB_RUN_ATTEMPT || '1';
34
33
 
@@ -38,11 +37,15 @@ const GCS_SERVICE_ACCOUNT_KEY = process.env.GCS_SERVICE_ACCOUNT_KEY;
38
37
  const GCS_REPORTS_FOLDER_PREFIX = process.env.GCS_REPORTS_FOLDER_PREFIX;
39
38
  const GCS_UPLOAD_API_BASE_URL = process.env.GCS_UPLOAD_API_BASE_URL;
40
39
 
41
- // GitHub API endpoints (for artifact fetching)
42
- const GITHUB_API_BASE_URL = 'https://api.github.com';
40
+ // MinIO artifact storage configuration (artifacts uploaded by shards 2+ as
41
+ // <run_id>-<run_attempt>/shard-<shard>.tar.zst, a zstd-compressed tar of playwright-report)
42
+ const MINIO_ENDPOINT = process.env.MINIO_ENDPOINT;
43
+ const MINIO_ACCESS_KEY = process.env.MINIO_ACCESS_KEY;
44
+ const MINIO_SECRET_KEY = process.env.MINIO_SECRET_KEY;
45
+ const MINIO_BUCKET = process.env.MINIO_BUCKET;
46
+ const MINIO_ALIAS = 'shard-artifacts';
43
47
 
44
- // Helper functions for building GitHub API URLs
45
- const getGitHubArtifactsApiUrl = () => `${GITHUB_API_BASE_URL}/repos/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}/artifacts`;
48
+ const getMinioObjectPath = (shard) => `${MINIO_ALIAS}/${MINIO_BUCKET}/${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}/shard-${shard}.tar.zst`;
46
49
 
47
50
  // Helper functions for report paths
48
51
  const getRunFolder = () => `${GCS_REPORTS_FOLDER_PREFIX}/${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}`;
@@ -432,46 +435,33 @@ class ResultsParser {
432
435
  }
433
436
 
434
437
  async fetchArtifactForShard(shard) {
435
- const githubApiUrl = getGitHubArtifactsApiUrl();
436
- if (!AdmZip) {
437
- throw new Error('adm-zip is required for GitHub Actions artifact extraction. Please install it: npm install adm-zip');
438
+ if (!MINIO_ENDPOINT || !MINIO_ACCESS_KEY || !MINIO_SECRET_KEY || !MINIO_BUCKET) {
439
+ throw new Error('MinIO configuration is required: set MINIO_ENDPOINT, MINIO_ACCESS_KEY, MINIO_SECRET_KEY and MINIO_BUCKET');
438
440
  }
441
+ const objectPath = getMinioObjectPath(shard);
442
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), `mc-shard-${shard}-`));
443
+ const mcConfigDir = path.join(tmpDir, '.mc');
444
+ const tarFile = path.join(tmpDir, `shard-${shard}.tar.zst`);
439
445
  try {
440
- const listResponse = await axios.get(githubApiUrl, {
441
- headers: {
442
- 'Authorization': `token ${GITHUB_TOKEN}`,
443
- 'Accept': 'application/vnd.github.v3+json'
444
- },
445
- params: { per_page: 100 }
446
- });
447
- const runPrefix = `${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}`;
448
- const artifact = (listResponse.data.artifacts || []).find(
449
- (a) => {
450
- const name = a.name || '';
451
- return name.startsWith(runPrefix) && name.endsWith(`-artifacts-shard-${shard}`);
452
- }
453
- );
454
- if (!artifact?.archive_download_url) return false;
455
- const downloadResponse = await axios.get(artifact.archive_download_url, {
456
- headers: {
457
- 'Authorization': `token ${GITHUB_TOKEN}`,
458
- 'Accept': 'application/vnd.github.v3+json'
459
- },
460
- responseType: 'arraybuffer'
461
- });
462
- const zip = new AdmZip(downloadResponse.data);
446
+ // Pass credentials as separate args (not embedded in a URL) to avoid encoding issues.
447
+ execFileSync('mc', ['--config-dir', mcConfigDir, 'alias', 'set', MINIO_ALIAS, MINIO_ENDPOINT, MINIO_ACCESS_KEY, MINIO_SECRET_KEY], { stdio: 'ignore' });
448
+ // mc exits non-zero when the object isn't uploaded yet; the caller retries.
449
+ execFileSync('mc', ['--config-dir', mcConfigDir, 'cp', objectPath, tarFile], { stdio: 'ignore' });
450
+ // The tar holds playwright-report/<file>, matching the upload action's archive layout.
451
+ execFileSync('bash', ['-c', `zstd -dc "${tarFile}" | tar -xf - -C "${tmpDir}"`], { stdio: 'ignore' });
463
452
  const targetFiles = [`node_summary_${shard}.json`, `blob-report-node-${shard}.zip`];
464
- for (const entry of zip.getEntries()) {
465
- const baseName = path.basename(entry.entryName);
466
- if (targetFiles.includes(baseName)) {
467
- fs.writeFileSync(path.join(SUMMARIES_DIR, baseName), entry.getData());
468
- console.log(`Successfully fetched ${baseName} from artifact ${artifact.name}`);
453
+ for (const file of targetFiles) {
454
+ const extracted = path.join(tmpDir, 'playwright-report', file);
455
+ if (fs.existsSync(extracted)) {
456
+ fs.copyFileSync(extracted, path.join(SUMMARIES_DIR, file));
457
+ console.log(`Successfully fetched ${file} from MinIO object ${objectPath}`);
469
458
  }
470
459
  }
471
460
  return true;
472
461
  } catch (error) {
473
- if (error.response?.status === 404) return false;
474
- throw error;
462
+ return false;
463
+ } finally {
464
+ fs.rmSync(tmpDir, { recursive: true, force: true });
475
465
  }
476
466
  }
477
467
  async mergeReports() {
@@ -85,6 +85,10 @@ class SlackReporter {
85
85
  this.log('⏩ Slack reporter - no failures found');
86
86
  return;
87
87
  }
88
+ if (this.sendResults === 'branch') {
89
+ this.log('🛑 Branch mode: skipping Slack post (GCS upload completed)');
90
+ return;
91
+ }
88
92
  const agent = this.proxy ? new https_proxy_agent_1.HttpsProxyAgent(this.proxy) : undefined;
89
93
  if (this.slackWebHookUrl) {
90
94
  const webhook = new webhook_1.IncomingWebhook(this.slackWebHookUrl, { agent });
@@ -184,10 +188,10 @@ class SlackReporter {
184
188
  };
185
189
  }
186
190
  if (!this.sendResults
187
- || !['always', 'on-failure', 'off'].includes(this.sendResults)) {
191
+ || !['always', 'on-failure', 'off', 'branch'].includes(this.sendResults)) {
188
192
  return {
189
193
  okToProceed: false,
190
- message: "❌ \"sendResults\" is not valid. Expecting one of ['always', 'on-failure', 'off'].",
194
+ message: "❌ \"sendResults\" is not valid. Expecting one of ['always', 'on-failure', 'off', 'branch'].",
191
195
  };
192
196
  }
193
197
  if (!this.sendResults || this.slackChannels?.length === 0) {
package/package.json CHANGED
@@ -33,7 +33,7 @@
33
33
  "lint-fix": "npx eslint . --ext .ts --fix"
34
34
  },
35
35
  "name": "playwright-slack-report-burak",
36
- "version": "3.3.0",
36
+ "version": "3.5.0",
37
37
  "main": "index.js",
38
38
  "types": "dist/index.d.ts",
39
39
  "author": "Burak B. <burak.boluk@hotmail.com>",