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.
- package/README.md +224 -120
- package/bin/haven-cypress.js +46 -59
- package/index.js +87 -215
- package/package.json +4 -3
- package/templates/Dockerfile +32 -22
- package/templates/run-filtered.sh +178 -91
- package/templates/syncCypressResults.js +128 -148
|
@@ -1,66 +1,75 @@
|
|
|
1
|
-
const fs = require(
|
|
2
|
-
const path = require(
|
|
3
|
-
const
|
|
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
|
-
//
|
|
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
|
-
|
|
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("
|
|
17
|
+
console.warn("Could not read triggered-by.txt:", err.message);
|
|
21
18
|
}
|
|
22
19
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const formatted = [];
|
|
26
|
-
const foundIds = new Set();
|
|
27
|
-
|
|
20
|
+
function findJsonFiles(dir) {
|
|
21
|
+
const files = [];
|
|
28
22
|
try {
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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.
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
const
|
|
35
|
+
function parseMochawesomeJson(jsonPath) {
|
|
36
|
+
const results = [];
|
|
37
|
+
const foundIds = new Set();
|
|
38
|
+
let combinedResults = { results: [] };
|
|
45
39
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
|
|
110
|
-
|
|
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
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
|
|
123
|
-
await postSummary(formatted, notFound, planId, runId, testEnvironment);
|
|
124
|
-
})();
|
|
143
|
+
const { results: formattedResults, foundIds } = parseMochawesomeJson(jsonReportPath);
|
|
125
144
|
|
|
126
|
-
|
|
127
|
-
|
|
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
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
console.
|
|
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
|
-
|
|
160
|
-
|
|
161
|
-
const
|
|
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
|
|
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:
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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(
|
|
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
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
console.
|
|
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
|
-
|
|
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); });
|