haven-cypress-integration 1.6.2 → 2.0.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.
@@ -1,66 +1,75 @@
1
- const fs = require("fs");
2
- const path = require("path");
3
- const axios = require("axios");
4
- const glob = require("glob");
5
- // AWS SDK removed - EC2 instance handles S3 uploads
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const https = require('https');
6
4
 
7
- // Paths
5
+ // Shared paths provided by HAVEN runner
8
6
  const automationCasesPath = "/shared/automation-cases.json";
9
7
  const postBaseUrlPath = "/shared/result-post-url.txt";
10
8
  const triggeredByPath = "/shared/triggered-by.txt";
11
- const baseUrl = fs.readFileSync(postBaseUrlPath, "utf-8").trim();
12
9
 
13
- // Read triggered_by value
10
+ const baseUrl = fs.readFileSync(postBaseUrlPath, "utf-8").trim();
14
11
  let triggeredBy = null;
15
12
  try {
16
13
  if (fs.existsSync(triggeredByPath)) {
17
14
  triggeredBy = fs.readFileSync(triggeredByPath, "utf-8").trim();
18
15
  }
19
16
  } catch (err) {
20
- console.warn("⚠️ Could not read triggered-by.txt:", err.message);
17
+ console.warn("Could not read triggered-by.txt:", err.message);
21
18
  }
22
19
 
23
- (async () => {
24
- let automationIds = null;
25
- const formatted = [];
26
- const foundIds = new Set();
27
-
20
+ function findJsonFiles(dir) {
21
+ const files = [];
28
22
  try {
29
- const expectedCases = JSON.parse(
30
- fs.readFileSync(automationCasesPath, "utf-8")
31
- );
32
- const ids = expectedCases
33
- .map((c) => (c.automation_id || "").trim())
34
- .filter(Boolean);
35
- if (ids.length > 0) automationIds = new Set(ids);
23
+ const entries = fs.readdirSync(dir);
24
+ for (const entry of entries) {
25
+ if (entry.endsWith('.json')) {
26
+ files.push(path.join(dir, entry));
27
+ }
28
+ }
36
29
  } catch (err) {
37
- console.warn(
38
- "⚠️ No valid automation-cases.json found. Using @regression fallback."
39
- );
30
+ console.error("Failed to read directory:", dir, err.message);
40
31
  }
32
+ return files;
33
+ }
41
34
 
42
- const mochawesomeDir = path.resolve("results/mochawesome");
43
- console.log("Looking for mochawesome JSONs in:", `${mochawesomeDir}/*.json`);
44
- const jsonFiles = glob.sync(`${mochawesomeDir}/*.json`);
35
+ function parseMochawesomeJson(jsonPath) {
36
+ const results = [];
37
+ const foundIds = new Set();
38
+ let combinedResults = { results: [] };
45
39
 
46
- if (jsonFiles.length === 0) {
47
- console.error("❌ No mochawesome report files found.");
48
- process.exit(1);
49
- }
40
+ // If specific JSON path provided, use it
41
+ if (jsonPath && fs.existsSync(jsonPath)) {
42
+ try {
43
+ const data = JSON.parse(fs.readFileSync(jsonPath, 'utf-8'));
44
+ if (data?.results) {
45
+ combinedResults.results.push(...data.results);
46
+ }
47
+ } catch (err) {
48
+ console.error("Failed to parse provided JSON:", err.message);
49
+ }
50
+ } else {
51
+ // Fallback: scan mochawesome directory
52
+ const mochawesomeDir = path.resolve("results/mochawesome");
53
+ console.log("Looking for mochawesome JSONs in:", `${mochawesomeDir}/*.json`);
54
+ const jsonFiles = findJsonFiles(mochawesomeDir);
55
+
56
+ if (jsonFiles.length === 0) {
57
+ console.error("No mochawesome report files found.");
58
+ return { results: [], foundIds: new Set() };
59
+ }
50
60
 
51
- console.log(`📄 Found ${jsonFiles.length} mochawesome report(s).`);
52
- const combinedResults = { results: [] };
61
+ console.log(`Found ${jsonFiles.length} mochawesome report(s).`);
53
62
 
54
- try {
55
63
  for (const file of jsonFiles) {
56
- const data = JSON.parse(fs.readFileSync(file, "utf-8"));
57
- if (data?.results) {
58
- combinedResults.results.push(...data.results);
64
+ try {
65
+ const data = JSON.parse(fs.readFileSync(file, "utf-8"));
66
+ if (data?.results) {
67
+ combinedResults.results.push(...data.results);
68
+ }
69
+ } catch (err) {
70
+ console.error("Failed to parse mochawesome JSON:", err.message);
59
71
  }
60
72
  }
61
- } catch (err) {
62
- console.error("❌ Failed to parse mochawesome JSON:", err.message);
63
- process.exit(1);
64
73
  }
65
74
 
66
75
  function walkSuites(suite) {
@@ -77,7 +86,8 @@ try {
77
86
  ? test.title.join(" ")
78
87
  : test.title;
79
88
 
80
- const allTags = [...(titleCombined.match(/@[\w-]+/g) || [])];
89
+ // Extract automation IDs from title
90
+ const automationIdMatch = titleCombined.match(/@?TC-AUTO-[\w.-]+/g) || [];
81
91
 
82
92
  const status =
83
93
  test.state === "passed" || test.pass === true
@@ -86,140 +96,110 @@ try {
86
96
  ? "skipped"
87
97
  : "fail";
88
98
 
89
- if (!automationIds) {
90
- allTags
91
- .filter((tag) => /^TC-AUTO-\w+$/i.test(tag))
92
- .forEach((tag) => {
93
- formatted.push({ automation_id: tag, status });
94
- foundIds.add(tag);
95
- });
96
- } else {
97
- allTags
98
- .filter((tag) => automationIds.has(tag))
99
- .forEach((tag) => {
100
- formatted.push({ automation_id: tag, status });
101
- foundIds.add(tag);
102
- });
103
- }
99
+ automationIdMatch.forEach((tag) => {
100
+ const normalizedTag = tag.startsWith('@') ? tag : '@' + tag;
101
+ results.push({ automation_id: normalizedTag, status });
102
+ foundIds.add(normalizedTag.replace(/^@/, ''));
103
+ });
104
104
  });
105
105
  }
106
106
 
107
107
  (combinedResults.results || []).forEach(walkSuites);
108
+ return { results, foundIds };
109
+ }
108
110
 
109
- const notFound = automationIds
110
- ? [...automationIds].filter((id) => !foundIds.has(id))
111
- : [];
111
+ async function postJson(url, payload) {
112
+ return new Promise((resolve, reject) => {
113
+ const data = JSON.stringify(payload);
114
+ const urlObj = new URL(url);
115
+ const options = {
116
+ hostname: urlObj.hostname,
117
+ port: urlObj.port || 443,
118
+ path: urlObj.pathname + urlObj.search,
119
+ method: 'POST',
120
+ headers: {
121
+ 'Content-Type': 'application/json',
122
+ 'Content-Length': Buffer.byteLength(data)
123
+ }
124
+ };
112
125
 
113
- const planId = process.env.PLAN_ID || "unknown";
114
- const runId = process.env.RUN_ID || "unknown";
115
- const testEnvironment = process.env.TEST_ENVIRONMENT || "QA";
126
+ const req = https.request(options, res => {
127
+ let body = '';
128
+ res.on('data', c => body += c);
129
+ res.on('end', () => resolve({ statusCode: res.statusCode, body }));
130
+ });
131
+ req.on('error', reject);
132
+ req.write(data);
133
+ req.end();
134
+ });
135
+ }
116
136
 
117
- if (!planId || !runId || isNaN(Number(planId)) || isNaN(Number(runId))) {
118
- console.error("❌ Invalid or missing PLAN_ID / RUN_ID");
119
- process.exit(1);
120
- }
137
+ async function main() {
138
+ const planId = process.env.PLAN_ID || 'unknown_plan';
139
+ const runId = process.env.RUN_ID || 'unknown_run';
140
+ const testEnvironment = process.env.TEST_ENVIRONMENT || 'QA';
141
+ const jsonReportPath = process.argv[2];
121
142
 
122
- await postResults(formatted, notFound, planId, runId, testEnvironment);
123
- await postSummary(formatted, notFound, planId, runId, testEnvironment);
124
- })();
143
+ const { results: formattedResults, foundIds } = parseMochawesomeJson(jsonReportPath);
125
144
 
126
- async function postResults(formattedResults, notFoundList, planId, runId, testEnvironment) {
127
- const postUrl = baseUrl; // baseUrl already includes the complete endpoint
128
- console.log(`🔗 Posting to URL: ${postUrl}`);
145
+ console.log(`Looking for results in: ${jsonReportPath || 'results/mochawesome/*.json'}`);
146
+ console.log(`Found ${formattedResults.length} result(s).`);
129
147
 
148
+ // not_found from automation-cases.json (optional)
149
+ const normalizeId = (id) => id.replace(/^@/, '');
150
+ let notFoundList = [];
151
+ try {
152
+ const automationCases = JSON.parse(fs.readFileSync(automationCasesPath, 'utf-8'));
153
+ const expected = new Set((automationCases || []).map(a => normalizeId(a.automation_id)));
154
+ notFoundList = [...expected].filter(id => !foundIds.has(id));
155
+ } catch { }
156
+
157
+ // Post results
158
+ const resultsUrl = baseUrl;
130
159
  const payload = {
131
160
  planId,
132
161
  runId,
133
162
  environment: testEnvironment,
134
163
  results: formattedResults,
135
164
  not_found: notFoundList,
136
- triggered_by: triggeredBy,
165
+ triggered_by: triggeredBy
137
166
  };
138
-
139
- console.log(
140
- "📤 Posting result from sync report:",
141
- JSON.stringify(payload, null, 2)
142
- );
167
+ console.log('Posting result payload:', JSON.stringify(payload, null, 2));
168
+ console.log(`Posting to URL: ${resultsUrl}`);
143
169
 
144
170
  try {
145
- const response = await axios.post(postUrl, payload, {
146
- headers: { "Content-Type": "application/json" },
147
- });
148
- console.log("✅ Test case results posted:", response.status);
149
- } catch (err) {
150
- console.error(
151
- "❌ Failed to post test case results:",
152
- err.response?.status,
153
- err.response?.data || err.message
154
- );
155
- process.exit(1);
171
+ const res = await postJson(resultsUrl, payload);
172
+ console.log('Post status:', res.statusCode, res.body);
173
+ } catch (e) {
174
+ console.error('Failed to post results:', e.message);
156
175
  }
157
- }
158
176
 
159
- async function postSummary(results, notFound, planId, runId, testEnvironment) {
160
- // Replace test-results with test-run-summary in the baseUrl
161
- const url = baseUrl.replace('/api/test-results', '/api/test-run-summary');
177
+ // Post summary
178
+ const summaryUrl = baseUrl.replace('/api/test-results', '/api/test-run-summary');
179
+ const passedCount = formattedResults.filter(r => r.status === 'pass').length;
180
+ const failedCount = formattedResults.filter(r => r.status === 'fail').length;
181
+ const skippedCount = formattedResults.filter(r => r.status === 'skipped').length;
182
+ const overallStatus = failedCount > 0 ? 'failed' : (formattedResults.length > 0 ? 'completed' : 'no_tests');
162
183
 
163
- const logs =
164
- (await copyLogToShared(planId, runId)) || "Log copy failed or missing";
165
-
166
- const summaryPayload = {
167
- runId,
184
+ const summary = {
168
185
  planId,
186
+ runId,
169
187
  environment: testEnvironment,
170
- status: computeOverallStatus(results, notFound),
171
- logs,
172
- result: {
173
- total: results.length,
174
- passed: results.filter((r) => r.status === "pass").length,
175
- failed: results.filter((r) => r.status === "fail").length,
176
- skipped: results.filter((r) => r.status === "skipped").length,
177
- not_found: notFound.length,
178
- },
188
+ status: overallStatus,
189
+ total: formattedResults.length,
190
+ passed: passedCount,
191
+ failed: failedCount,
192
+ skipped: skippedCount
179
193
  };
180
-
181
- console.log("📤 Posting test run summary to:", url);
182
- console.log("📦 Summary payload:", JSON.stringify(summaryPayload, null, 2));
194
+ console.log(`Posting summary to: ${summaryUrl}`);
195
+ console.log('Summary payload:', JSON.stringify(summary, null, 2));
183
196
 
184
197
  try {
185
- const resp = await axios.post(url, summaryPayload, {
186
- headers: { "Content-Type": "application/json" },
187
- });
188
- console.log("✅ Test run summary posted:", resp.data.message);
189
- } catch (err) {
190
- console.error(
191
- "❌ Failed to post summary:",
192
- err.response?.data || err.message
193
- );
198
+ const res = await postJson(summaryUrl, summary);
199
+ console.log('Summary status:', res.statusCode, res.body);
200
+ } catch (e) {
201
+ console.error('Failed to post summary:', e.message);
194
202
  }
195
203
  }
196
204
 
197
- async function copyLogToShared(planId, runId) {
198
- const logsPath = "results/logs.txt";
199
-
200
- if (!fs.existsSync(logsPath)) {
201
- console.warn("⚠️ No logs.txt file found to copy.");
202
- return null;
203
- }
204
-
205
- const sharedDir = "/shared/test-logs";
206
- if (!fs.existsSync(sharedDir)) {
207
- fs.mkdirSync(sharedDir, { recursive: true });
208
- }
209
-
210
- try {
211
- const destPath = path.join(sharedDir, "run_log.txt");
212
- fs.copyFileSync(logsPath, destPath);
213
- console.log(`📁 Logs copied to shared volume: ${destPath}`);
214
- return destPath;
215
- } catch (err) {
216
- console.error("❌ Failed to copy log to shared volume:", err.message);
217
- return null;
218
- }
219
- }
220
-
221
- function computeOverallStatus(results, notFound) {
222
- if (!results.length && !notFound.length) return "skipped";
223
- const hasFail = results.some((r) => r.status === "fail");
224
- return hasFail ? "failed" : "passed";
225
- }
205
+ main().catch(e => { console.error(e); process.exit(1); });