ortoni-report 3.0.5 → 4.0.2-beta.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/changelog.md +30 -0
- package/dist/chunk-45EJSEX2.mjs +632 -0
- package/dist/chunk-75EAJL2U.mjs +632 -0
- package/dist/chunk-FGIYOFIC.mjs +632 -0
- package/dist/chunk-FHKWBHU6.mjs +633 -0
- package/dist/chunk-GLICR3VS.mjs +637 -0
- package/dist/chunk-HFO6XSKC.mjs +633 -0
- package/dist/chunk-HOZD6YIV.mjs +634 -0
- package/dist/chunk-IJO2YIFE.mjs +637 -0
- package/dist/chunk-INS3E7E6.mjs +638 -0
- package/dist/chunk-JEIWNUQY.mjs +632 -0
- package/dist/chunk-JPLAGYR7.mjs +632 -0
- package/dist/chunk-NM6ULN2O.mjs +632 -0
- package/dist/chunk-OZS6QIJS.mjs +638 -0
- package/dist/chunk-P57227VN.mjs +633 -0
- package/dist/chunk-QMTRYN5N.js +635 -0
- package/dist/chunk-TI33PMMQ.mjs +639 -0
- package/dist/chunk-Z5NBP5TS.mjs +635 -0
- package/dist/cli/cli.cjs +678 -0
- package/dist/cli/cli.d.cts +1 -0
- package/dist/cli/cli.js +580 -11
- package/dist/cli/cli.mjs +62 -5
- package/dist/index.html +21 -0
- package/dist/ortoni-report.cjs +2134 -0
- package/dist/ortoni-report.d.cts +111 -0
- package/dist/ortoni-report.d.mts +3 -12
- package/dist/ortoni-report.d.ts +3 -12
- package/dist/ortoni-report.js +182 -314
- package/dist/ortoni-report.mjs +64 -740
- package/package.json +4 -5
- package/readme.md +26 -33
- package/dist/chunk-AY2PKDHU.mjs +0 -69
- package/dist/chunk-OOALU4XG.mjs +0 -72
- package/dist/chunk-ZSIRUQUA.mjs +0 -68
- package/dist/style/main.css +0 -80
- package/dist/views/analytics.hbs +0 -103
- package/dist/views/head.hbs +0 -11
- package/dist/views/main.hbs +0 -1295
- package/dist/views/project.hbs +0 -238
- package/dist/views/sidebar.hbs +0 -244
- package/dist/views/summaryCard.hbs +0 -15
- package/dist/views/testIcons.hbs +0 -13
- package/dist/views/testPanel.hbs +0 -45
- package/dist/views/testStatus.hbs +0 -9
- package/dist/views/userInfo.hbs +0 -260
package/dist/ortoni-report.mjs
CHANGED
|
@@ -1,388 +1,25 @@
|
|
|
1
1
|
import {
|
|
2
|
+
DatabaseManager,
|
|
3
|
+
FileManager,
|
|
4
|
+
HTMLGenerator,
|
|
2
5
|
__publicField,
|
|
3
|
-
|
|
6
|
+
ensureHtmlExtension,
|
|
7
|
+
escapeHtml,
|
|
8
|
+
extractSuites,
|
|
9
|
+
normalizeFilePath,
|
|
4
10
|
startReportServer
|
|
5
|
-
} from "./chunk-
|
|
6
|
-
|
|
7
|
-
// src/helpers/fileManager.ts
|
|
8
|
-
import fs from "fs";
|
|
9
|
-
import path from "path";
|
|
10
|
-
var FileManager = class {
|
|
11
|
-
constructor(folderPath) {
|
|
12
|
-
this.folderPath = folderPath;
|
|
13
|
-
}
|
|
14
|
-
ensureReportDirectory() {
|
|
15
|
-
const ortoniDataFolder = path.join(this.folderPath, "ortoni-data");
|
|
16
|
-
if (!fs.existsSync(this.folderPath)) {
|
|
17
|
-
fs.mkdirSync(this.folderPath, { recursive: true });
|
|
18
|
-
} else {
|
|
19
|
-
if (fs.existsSync(ortoniDataFolder)) {
|
|
20
|
-
fs.rmSync(ortoniDataFolder, { recursive: true, force: true });
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
writeReportFile(filename, content) {
|
|
25
|
-
const outputPath = path.join(process.cwd(), this.folderPath, filename);
|
|
26
|
-
fs.writeFileSync(outputPath, content);
|
|
27
|
-
return outputPath;
|
|
28
|
-
}
|
|
29
|
-
readCssContent() {
|
|
30
|
-
return fs.readFileSync(
|
|
31
|
-
path.resolve(__dirname, "style", "main.css"),
|
|
32
|
-
"utf-8"
|
|
33
|
-
);
|
|
34
|
-
}
|
|
35
|
-
copyTraceViewerAssets(skip) {
|
|
36
|
-
if (skip) return;
|
|
37
|
-
const traceViewerFolder = path.join(
|
|
38
|
-
__require.resolve("playwright-core"),
|
|
39
|
-
"..",
|
|
40
|
-
"lib",
|
|
41
|
-
"vite",
|
|
42
|
-
"traceViewer"
|
|
43
|
-
);
|
|
44
|
-
const traceViewerTargetFolder = path.join(this.folderPath, "trace");
|
|
45
|
-
const traceViewerAssetsTargetFolder = path.join(
|
|
46
|
-
traceViewerTargetFolder,
|
|
47
|
-
"assets"
|
|
48
|
-
);
|
|
49
|
-
fs.mkdirSync(traceViewerAssetsTargetFolder, { recursive: true });
|
|
50
|
-
for (const file of fs.readdirSync(traceViewerFolder)) {
|
|
51
|
-
if (file.endsWith(".map") || file.includes("watch") || file.includes("assets"))
|
|
52
|
-
continue;
|
|
53
|
-
fs.copyFileSync(
|
|
54
|
-
path.join(traceViewerFolder, file),
|
|
55
|
-
path.join(traceViewerTargetFolder, file)
|
|
56
|
-
);
|
|
57
|
-
}
|
|
58
|
-
const assetsFolder = path.join(traceViewerFolder, "assets");
|
|
59
|
-
for (const file of fs.readdirSync(assetsFolder)) {
|
|
60
|
-
if (file.endsWith(".map") || file.includes("xtermModule")) continue;
|
|
61
|
-
fs.copyFileSync(
|
|
62
|
-
path.join(assetsFolder, file),
|
|
63
|
-
path.join(traceViewerAssetsTargetFolder, file)
|
|
64
|
-
);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
// src/helpers/HTMLGenerator.ts
|
|
70
|
-
import path3 from "path";
|
|
71
|
-
|
|
72
|
-
// src/utils/groupProjects.ts
|
|
73
|
-
function groupResults(config, results) {
|
|
74
|
-
if (config.showProject) {
|
|
75
|
-
const groupedResults = results.reduce((acc, result, index) => {
|
|
76
|
-
const testId = `${result.filePath}:${result.projectName}:${result.title}`;
|
|
77
|
-
const { filePath, suite, projectName } = result;
|
|
78
|
-
acc[filePath] = acc[filePath] || {};
|
|
79
|
-
acc[filePath][suite] = acc[filePath][suite] || {};
|
|
80
|
-
acc[filePath][suite][projectName] = acc[filePath][suite][projectName] || [];
|
|
81
|
-
acc[filePath][suite][projectName].push({ ...result, index, testId });
|
|
82
|
-
return acc;
|
|
83
|
-
}, {});
|
|
84
|
-
return groupedResults;
|
|
85
|
-
} else {
|
|
86
|
-
const groupedResults = results.reduce((acc, result, index) => {
|
|
87
|
-
const testId = `${result.filePath}:${result.projectName}:${result.title}`;
|
|
88
|
-
const { filePath, suite } = result;
|
|
89
|
-
acc[filePath] = acc[filePath] || {};
|
|
90
|
-
acc[filePath][suite] = acc[filePath][suite] || [];
|
|
91
|
-
acc[filePath][suite].push({ ...result, index, testId });
|
|
92
|
-
return acc;
|
|
93
|
-
}, {});
|
|
94
|
-
return groupedResults;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// src/utils/utils.ts
|
|
99
|
-
import path2 from "path";
|
|
100
|
-
function msToTime(duration) {
|
|
101
|
-
const milliseconds = Math.floor(duration % 1e3);
|
|
102
|
-
const seconds = Math.floor(duration / 1e3 % 60);
|
|
103
|
-
const minutes = Math.floor(duration / (1e3 * 60) % 60);
|
|
104
|
-
const hours = Math.floor(duration / (1e3 * 60 * 60) % 24);
|
|
105
|
-
let result = "";
|
|
106
|
-
if (hours > 0) {
|
|
107
|
-
result += `${hours}h:`;
|
|
108
|
-
}
|
|
109
|
-
if (minutes > 0 || hours > 0) {
|
|
110
|
-
result += `${minutes < 10 ? "0" + minutes : minutes}m:`;
|
|
111
|
-
}
|
|
112
|
-
if (seconds > 0 || minutes > 0 || hours > 0) {
|
|
113
|
-
result += `${seconds < 10 ? "0" + seconds : seconds}s`;
|
|
114
|
-
}
|
|
115
|
-
if (milliseconds > 0 && !(seconds > 0 || minutes > 0 || hours > 0)) {
|
|
116
|
-
result += `${milliseconds}ms`;
|
|
117
|
-
} else if (milliseconds > 0) {
|
|
118
|
-
result += `:${milliseconds < 100 ? "0" + milliseconds : milliseconds}ms`;
|
|
119
|
-
}
|
|
120
|
-
return result;
|
|
121
|
-
}
|
|
122
|
-
function normalizeFilePath(filePath) {
|
|
123
|
-
const normalizedPath = path2.normalize(filePath);
|
|
124
|
-
return path2.basename(normalizedPath);
|
|
125
|
-
}
|
|
126
|
-
function formatDate(date) {
|
|
127
|
-
const day = String(date.getDate()).padStart(2, "0");
|
|
128
|
-
const month = date.toLocaleString("default", { month: "short" });
|
|
129
|
-
const year = date.getFullYear();
|
|
130
|
-
const time = date.toLocaleTimeString();
|
|
131
|
-
return `${day}-${month}-${year} ${time}`;
|
|
132
|
-
}
|
|
133
|
-
function safeStringify(obj, indent = 2) {
|
|
134
|
-
const cache = /* @__PURE__ */ new Set();
|
|
135
|
-
const json = JSON.stringify(
|
|
136
|
-
obj,
|
|
137
|
-
(key, value) => {
|
|
138
|
-
if (typeof value === "object" && value !== null) {
|
|
139
|
-
if (cache.has(value)) {
|
|
140
|
-
return;
|
|
141
|
-
}
|
|
142
|
-
cache.add(value);
|
|
143
|
-
}
|
|
144
|
-
return value;
|
|
145
|
-
},
|
|
146
|
-
indent
|
|
147
|
-
);
|
|
148
|
-
cache.clear();
|
|
149
|
-
return json;
|
|
150
|
-
}
|
|
151
|
-
function ensureHtmlExtension(filename) {
|
|
152
|
-
const ext = path2.extname(filename);
|
|
153
|
-
if (ext && ext.toLowerCase() === ".html") {
|
|
154
|
-
return filename;
|
|
155
|
-
}
|
|
156
|
-
return `${filename}.html`;
|
|
157
|
-
}
|
|
158
|
-
function escapeHtml(unsafe) {
|
|
159
|
-
if (typeof unsafe !== "string") {
|
|
160
|
-
return String(unsafe);
|
|
161
|
-
}
|
|
162
|
-
return unsafe.replace(/[&<"']/g, function(match) {
|
|
163
|
-
const escapeMap = {
|
|
164
|
-
"&": "&",
|
|
165
|
-
"<": "<",
|
|
166
|
-
">": ">",
|
|
167
|
-
'"': """,
|
|
168
|
-
"'": "'"
|
|
169
|
-
};
|
|
170
|
-
return escapeMap[match] || match;
|
|
171
|
-
});
|
|
172
|
-
}
|
|
173
|
-
function formatDateUTC(date) {
|
|
174
|
-
return date.toISOString();
|
|
175
|
-
}
|
|
176
|
-
function formatDateLocal(isoString) {
|
|
177
|
-
const date = new Date(isoString);
|
|
178
|
-
const options = {
|
|
179
|
-
year: "numeric",
|
|
180
|
-
month: "short",
|
|
181
|
-
day: "2-digit",
|
|
182
|
-
hour: "2-digit",
|
|
183
|
-
minute: "2-digit",
|
|
184
|
-
hour12: true,
|
|
185
|
-
timeZoneName: "shortOffset"
|
|
186
|
-
};
|
|
187
|
-
return new Intl.DateTimeFormat(void 0, options).format(date);
|
|
188
|
-
}
|
|
189
|
-
function formatDateNoTimezone(isoString) {
|
|
190
|
-
const date = new Date(isoString);
|
|
191
|
-
return date.toLocaleString("en-US", {
|
|
192
|
-
dateStyle: "medium",
|
|
193
|
-
timeStyle: "short"
|
|
194
|
-
});
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// src/helpers/HTMLGenerator.ts
|
|
198
|
-
import fs2 from "fs";
|
|
199
|
-
import Handlebars from "handlebars";
|
|
200
|
-
var HTMLGenerator = class {
|
|
201
|
-
constructor(ortoniConfig, dbManager) {
|
|
202
|
-
this.ortoniConfig = ortoniConfig;
|
|
203
|
-
this.registerHandlebarsHelpers();
|
|
204
|
-
this.registerPartials();
|
|
205
|
-
this.dbManager = dbManager;
|
|
206
|
-
}
|
|
207
|
-
async generateHTML(filteredResults, totalDuration, cssContent, results, projectSet) {
|
|
208
|
-
const data = await this.prepareReportData(
|
|
209
|
-
filteredResults,
|
|
210
|
-
totalDuration,
|
|
211
|
-
results,
|
|
212
|
-
projectSet
|
|
213
|
-
);
|
|
214
|
-
const templateSource = fs2.readFileSync(
|
|
215
|
-
path3.resolve(__dirname, "views", "main.hbs"),
|
|
216
|
-
"utf-8"
|
|
217
|
-
);
|
|
218
|
-
const template = Handlebars.compile(templateSource);
|
|
219
|
-
return template({ ...data, inlineCss: cssContent });
|
|
220
|
-
}
|
|
221
|
-
async getReportData() {
|
|
222
|
-
return {
|
|
223
|
-
summary: await this.dbManager.getSummaryData(),
|
|
224
|
-
trends: await this.dbManager.getTrends(),
|
|
225
|
-
flakyTests: await this.dbManager.getFlakyTests(),
|
|
226
|
-
slowTests: await this.dbManager.getSlowTests()
|
|
227
|
-
};
|
|
228
|
-
}
|
|
229
|
-
async chartTrendData() {
|
|
230
|
-
return {
|
|
231
|
-
labels: (await this.getReportData()).trends.map(
|
|
232
|
-
(t) => formatDateNoTimezone(t.run_date)
|
|
233
|
-
),
|
|
234
|
-
passed: (await this.getReportData()).trends.map((t) => t.passed),
|
|
235
|
-
failed: (await this.getReportData()).trends.map((t) => t.failed),
|
|
236
|
-
avgDuration: (await this.getReportData()).trends.map(
|
|
237
|
-
(t) => t.avg_duration
|
|
238
|
-
)
|
|
239
|
-
};
|
|
240
|
-
}
|
|
241
|
-
async prepareReportData(filteredResults, totalDuration, results, projectSet) {
|
|
242
|
-
const totalTests = filteredResults.length;
|
|
243
|
-
const passedTests = results.filter((r) => r.status === "passed").length;
|
|
244
|
-
const flakyTests = results.filter((r) => r.status === "flaky").length;
|
|
245
|
-
const failed = filteredResults.filter(
|
|
246
|
-
(r) => r.status === "failed" || r.status === "timedOut"
|
|
247
|
-
).length;
|
|
248
|
-
const successRate = ((passedTests + flakyTests) / totalTests * 100).toFixed(2);
|
|
249
|
-
const allTags = /* @__PURE__ */ new Set();
|
|
250
|
-
results.forEach(
|
|
251
|
-
(result) => result.testTags.forEach((tag) => allTags.add(tag))
|
|
252
|
-
);
|
|
253
|
-
const projectResults = this.calculateProjectResults(
|
|
254
|
-
filteredResults,
|
|
255
|
-
results,
|
|
256
|
-
projectSet
|
|
257
|
-
);
|
|
258
|
-
const utcRunDate = formatDateUTC(/* @__PURE__ */ new Date());
|
|
259
|
-
const localRunDate = formatDateLocal(utcRunDate);
|
|
260
|
-
const testHistories = await Promise.all(
|
|
261
|
-
results.map(async (result) => {
|
|
262
|
-
const testId = `${result.filePath}:${result.projectName}:${result.title}`;
|
|
263
|
-
const history = await this.dbManager.getTestHistory(testId);
|
|
264
|
-
return {
|
|
265
|
-
testId,
|
|
266
|
-
history
|
|
267
|
-
};
|
|
268
|
-
})
|
|
269
|
-
);
|
|
270
|
-
return {
|
|
271
|
-
utcRunDate,
|
|
272
|
-
localRunDate,
|
|
273
|
-
testHistories,
|
|
274
|
-
logo: this.ortoniConfig.logo || void 0,
|
|
275
|
-
totalDuration,
|
|
276
|
-
results,
|
|
277
|
-
retryCount: results.filter((r) => r.isRetry).length,
|
|
278
|
-
passCount: passedTests,
|
|
279
|
-
failCount: failed,
|
|
280
|
-
skipCount: results.filter((r) => r.status === "skipped").length,
|
|
281
|
-
flakyCount: flakyTests,
|
|
282
|
-
totalCount: filteredResults.length,
|
|
283
|
-
groupedResults: groupResults(this.ortoniConfig, results),
|
|
284
|
-
projectName: this.ortoniConfig.projectName,
|
|
285
|
-
authorName: this.ortoniConfig.authorName,
|
|
286
|
-
meta: this.ortoniConfig.meta,
|
|
287
|
-
testType: this.ortoniConfig.testType,
|
|
288
|
-
preferredTheme: this.ortoniConfig.preferredTheme,
|
|
289
|
-
successRate,
|
|
290
|
-
lastRunDate: formatDate(/* @__PURE__ */ new Date()),
|
|
291
|
-
projects: projectSet,
|
|
292
|
-
allTags: Array.from(allTags),
|
|
293
|
-
showProject: this.ortoniConfig.showProject || false,
|
|
294
|
-
title: this.ortoniConfig.title || "Ortoni Playwright Test Report",
|
|
295
|
-
chartType: this.ortoniConfig.chartType || "pie",
|
|
296
|
-
reportAnalyticsData: await this.getReportData(),
|
|
297
|
-
chartData: await this.chartTrendData(),
|
|
298
|
-
...this.extractProjectStats(projectResults)
|
|
299
|
-
};
|
|
300
|
-
}
|
|
301
|
-
calculateProjectResults(filteredResults, results, projectSet) {
|
|
302
|
-
return Array.from(projectSet).map((projectName) => {
|
|
303
|
-
const projectTests = filteredResults.filter(
|
|
304
|
-
(r) => r.projectName === projectName
|
|
305
|
-
);
|
|
306
|
-
const allProjectTests = results.filter(
|
|
307
|
-
(r) => r.projectName === projectName
|
|
308
|
-
);
|
|
309
|
-
return {
|
|
310
|
-
projectName,
|
|
311
|
-
passedTests: projectTests.filter((r) => r.status === "passed").length,
|
|
312
|
-
failedTests: projectTests.filter(
|
|
313
|
-
(r) => r.status === "failed" || r.status === "timedOut"
|
|
314
|
-
).length,
|
|
315
|
-
skippedTests: allProjectTests.filter((r) => r.status === "skipped").length,
|
|
316
|
-
retryTests: allProjectTests.filter((r) => r.isRetry).length,
|
|
317
|
-
flakyTests: allProjectTests.filter((r) => r.status === "flaky").length,
|
|
318
|
-
totalTests: projectTests.length
|
|
319
|
-
};
|
|
320
|
-
});
|
|
321
|
-
}
|
|
322
|
-
extractProjectStats(projectResults) {
|
|
323
|
-
return {
|
|
324
|
-
projectNames: projectResults.map((result) => result.projectName),
|
|
325
|
-
totalTests: projectResults.map((result) => result.totalTests),
|
|
326
|
-
passedTests: projectResults.map((result) => result.passedTests),
|
|
327
|
-
failedTests: projectResults.map((result) => result.failedTests),
|
|
328
|
-
skippedTests: projectResults.map((result) => result.skippedTests),
|
|
329
|
-
retryTests: projectResults.map((result) => result.retryTests),
|
|
330
|
-
flakyTests: projectResults.map((result) => result.flakyTests)
|
|
331
|
-
};
|
|
332
|
-
}
|
|
333
|
-
registerHandlebarsHelpers() {
|
|
334
|
-
Handlebars.registerHelper("joinWithSpace", (array) => array.join(" "));
|
|
335
|
-
Handlebars.registerHelper("json", (context) => safeStringify(context));
|
|
336
|
-
Handlebars.registerHelper(
|
|
337
|
-
"eq",
|
|
338
|
-
(actualStatus, expectedStatus) => actualStatus === expectedStatus
|
|
339
|
-
);
|
|
340
|
-
Handlebars.registerHelper(
|
|
341
|
-
"includes",
|
|
342
|
-
(actualStatus, expectedStatus) => actualStatus.includes(expectedStatus)
|
|
343
|
-
);
|
|
344
|
-
Handlebars.registerHelper("gr", (count) => count > 0);
|
|
345
|
-
Handlebars.registerHelper("or", function(a3, b2) {
|
|
346
|
-
return a3 || b2;
|
|
347
|
-
});
|
|
348
|
-
Handlebars.registerHelper("concat", function(...args) {
|
|
349
|
-
args.pop();
|
|
350
|
-
return args.join("");
|
|
351
|
-
});
|
|
352
|
-
}
|
|
353
|
-
registerPartials() {
|
|
354
|
-
[
|
|
355
|
-
"head",
|
|
356
|
-
"sidebar",
|
|
357
|
-
"testPanel",
|
|
358
|
-
"summaryCard",
|
|
359
|
-
"userInfo",
|
|
360
|
-
"project",
|
|
361
|
-
"testStatus",
|
|
362
|
-
"testIcons",
|
|
363
|
-
"analytics"
|
|
364
|
-
].forEach((partialName) => {
|
|
365
|
-
Handlebars.registerPartial(
|
|
366
|
-
partialName,
|
|
367
|
-
fs2.readFileSync(
|
|
368
|
-
path3.resolve(__dirname, "views", `${partialName}.hbs`),
|
|
369
|
-
"utf-8"
|
|
370
|
-
)
|
|
371
|
-
);
|
|
372
|
-
});
|
|
373
|
-
}
|
|
374
|
-
};
|
|
11
|
+
} from "./chunk-JEIWNUQY.mjs";
|
|
375
12
|
|
|
376
13
|
// src/helpers/resultProcessor .ts
|
|
377
14
|
import AnsiToHtml from "ansi-to-html";
|
|
378
|
-
import
|
|
15
|
+
import path2 from "path";
|
|
379
16
|
|
|
380
17
|
// src/utils/attachFiles.ts
|
|
381
|
-
import
|
|
382
|
-
import
|
|
18
|
+
import path from "path";
|
|
19
|
+
import fs2 from "fs";
|
|
383
20
|
|
|
384
21
|
// src/helpers/markdownConverter.ts
|
|
385
|
-
import
|
|
22
|
+
import fs from "fs";
|
|
386
23
|
|
|
387
24
|
// node_modules/marked/lib/marked.esm.js
|
|
388
25
|
function M() {
|
|
@@ -1507,108 +1144,44 @@ var Ft = T.parse;
|
|
|
1507
1144
|
var Qt = b.lex;
|
|
1508
1145
|
|
|
1509
1146
|
// src/helpers/markdownConverter.ts
|
|
1510
|
-
function convertMarkdownToHtml(markdownPath, htmlOutputPath
|
|
1511
|
-
const hasMarkdown =
|
|
1512
|
-
const markdownContent = hasMarkdown ?
|
|
1147
|
+
function convertMarkdownToHtml(markdownPath, htmlOutputPath) {
|
|
1148
|
+
const hasMarkdown = fs.existsSync(markdownPath);
|
|
1149
|
+
const markdownContent = hasMarkdown ? fs.readFileSync(markdownPath, "utf-8") : "";
|
|
1513
1150
|
const markdownHtml = hasMarkdown ? k(markdownContent) : "";
|
|
1514
|
-
const
|
|
1515
|
-
|
|
1516
|
-
<div>
|
|
1517
|
-
<pre><code>${step.snippet}</code></pre>
|
|
1518
|
-
${step.location ? `<p><em>Location: ${escapeHtml(step.location)}</em></p>` : ""}
|
|
1519
|
-
</div>`
|
|
1520
|
-
).join("\n");
|
|
1521
|
-
const errorHtml = resultError.map((error) => `<pre><code>${error}</code></pre>`).join("\n");
|
|
1522
|
-
const fullHtml = `
|
|
1523
|
-
<!DOCTYPE html>
|
|
1524
|
-
<html lang="en">
|
|
1525
|
-
<head>
|
|
1526
|
-
<meta charset="UTF-8" />
|
|
1527
|
-
<title>Ortoni Error Report</title>
|
|
1528
|
-
<style>
|
|
1529
|
-
body { font-family: sans-serif; padding: 2rem; line-height: 1.6; max-width: 900px; margin: auto; }
|
|
1530
|
-
code, pre { background: #f4f4f4; padding: 0.5rem; border-radius: 5px; display: block; overflow-x: auto; }
|
|
1531
|
-
h1, h2, h3 { color: #444; }
|
|
1532
|
-
hr { margin: 2em 0; }
|
|
1533
|
-
#copyBtn {
|
|
1534
|
-
background-color: #007acc;
|
|
1535
|
-
color: white;
|
|
1536
|
-
border: none;
|
|
1537
|
-
padding: 0.5rem 1rem;
|
|
1538
|
-
margin-bottom: 1rem;
|
|
1539
|
-
border-radius: 5px;
|
|
1540
|
-
cursor: pointer;
|
|
1541
|
-
}
|
|
1542
|
-
#copyBtn:hover {
|
|
1543
|
-
background-color: #005fa3;
|
|
1544
|
-
}
|
|
1545
|
-
</style>
|
|
1546
|
-
</head>
|
|
1547
|
-
<body>
|
|
1548
|
-
<button id="copyBtn">\u{1F4CB} Copy All</button>
|
|
1549
|
-
<script>
|
|
1550
|
-
document.getElementById("copyBtn").addEventListener("click", () => {
|
|
1551
|
-
const content = document.getElementById("markdownContent").innerText;
|
|
1552
|
-
navigator.clipboard.writeText(content).then(() => {
|
|
1553
|
-
// change button text to indicate success
|
|
1554
|
-
const button = document.getElementById("copyBtn");
|
|
1555
|
-
button.textContent = "\u2705 Copied!";
|
|
1556
|
-
setTimeout(() => {
|
|
1557
|
-
button.textContent = "\u{1F4CB} Copy All"
|
|
1558
|
-
}, 2000);
|
|
1559
|
-
}).catch(err => {
|
|
1560
|
-
console.error("Failed to copy text: ", err);
|
|
1561
|
-
alert("Failed to copy text. Please try manually.");
|
|
1562
|
-
});
|
|
1563
|
-
});
|
|
1564
|
-
</script>
|
|
1565
|
-
<div id="markdownContent">
|
|
1566
|
-
<h1>Instructions</h1>
|
|
1567
|
-
<ul>
|
|
1568
|
-
<li>Following Playwright test failed.</li>
|
|
1569
|
-
<li>Explain why, be concise, respect Playwright best practices.</li>
|
|
1570
|
-
<li>Provide a snippet of code with the fix, if possible.</li>
|
|
1571
|
-
</ul>
|
|
1572
|
-
<h1>Error Details</h1>
|
|
1573
|
-
${errorHtml || "<p>No errors found.</p>"}
|
|
1574
|
-
${stepsHtml || "<p>No step data available.</p>"}
|
|
1575
|
-
${markdownHtml || ""}
|
|
1576
|
-
</div>
|
|
1577
|
-
</body>
|
|
1578
|
-
</html>
|
|
1579
|
-
`;
|
|
1580
|
-
fs3.writeFileSync(htmlOutputPath, fullHtml, "utf-8");
|
|
1151
|
+
const drawerHtml = `${markdownHtml || ""}`;
|
|
1152
|
+
fs.writeFileSync(htmlOutputPath, drawerHtml.trim(), "utf-8");
|
|
1581
1153
|
if (hasMarkdown) {
|
|
1582
|
-
|
|
1154
|
+
fs.unlinkSync(markdownPath);
|
|
1583
1155
|
}
|
|
1584
1156
|
}
|
|
1585
1157
|
|
|
1586
1158
|
// src/utils/attachFiles.ts
|
|
1587
1159
|
function attachFiles(subFolder, result, testResult, config, steps, errors) {
|
|
1588
1160
|
const folderPath = config.folderPath || "ortoni-report";
|
|
1589
|
-
const attachmentsFolder =
|
|
1161
|
+
const attachmentsFolder = path.join(
|
|
1590
1162
|
folderPath,
|
|
1591
1163
|
"ortoni-data",
|
|
1592
1164
|
"attachments",
|
|
1593
1165
|
subFolder
|
|
1594
1166
|
);
|
|
1595
|
-
if (!
|
|
1596
|
-
|
|
1167
|
+
if (!fs2.existsSync(attachmentsFolder)) {
|
|
1168
|
+
fs2.mkdirSync(attachmentsFolder, { recursive: true });
|
|
1597
1169
|
}
|
|
1598
1170
|
if (!result.attachments) return;
|
|
1599
1171
|
const { base64Image } = config;
|
|
1600
1172
|
testResult.screenshots = [];
|
|
1173
|
+
testResult.videoPath = [];
|
|
1601
1174
|
result.attachments.forEach((attachment) => {
|
|
1602
1175
|
const { contentType, name, path: attachmentPath, body } = attachment;
|
|
1603
1176
|
if (!attachmentPath && !body) return;
|
|
1604
|
-
const fileName = attachmentPath ?
|
|
1605
|
-
const relativePath =
|
|
1177
|
+
const fileName = attachmentPath ? path.basename(attachmentPath) : `${name}.${getFileExtension(contentType)}`;
|
|
1178
|
+
const relativePath = path.join(
|
|
1606
1179
|
"ortoni-data",
|
|
1607
1180
|
"attachments",
|
|
1608
1181
|
subFolder,
|
|
1609
1182
|
fileName
|
|
1610
1183
|
);
|
|
1611
|
-
const fullPath =
|
|
1184
|
+
const fullPath = path.join(attachmentsFolder, fileName);
|
|
1612
1185
|
if (contentType === "image/png") {
|
|
1613
1186
|
handleImage(
|
|
1614
1187
|
attachmentPath,
|
|
@@ -1651,13 +1224,13 @@ function handleImage(attachmentPath, body, base64Image, fullPath, relativePath,
|
|
|
1651
1224
|
let screenshotPath = "";
|
|
1652
1225
|
if (attachmentPath) {
|
|
1653
1226
|
try {
|
|
1654
|
-
const screenshotContent =
|
|
1227
|
+
const screenshotContent = fs2.readFileSync(
|
|
1655
1228
|
attachmentPath,
|
|
1656
1229
|
base64Image ? "base64" : void 0
|
|
1657
1230
|
);
|
|
1658
1231
|
screenshotPath = base64Image ? `data:image/png;base64,${screenshotContent}` : relativePath;
|
|
1659
1232
|
if (!base64Image) {
|
|
1660
|
-
|
|
1233
|
+
fs2.copyFileSync(attachmentPath, fullPath);
|
|
1661
1234
|
}
|
|
1662
1235
|
} catch (error) {
|
|
1663
1236
|
console.error(
|
|
@@ -1674,13 +1247,17 @@ function handleImage(attachmentPath, body, base64Image, fullPath, relativePath,
|
|
|
1674
1247
|
}
|
|
1675
1248
|
function handleAttachment(attachmentPath, fullPath, relativePath, resultKey, testResult, steps, errors) {
|
|
1676
1249
|
if (attachmentPath) {
|
|
1677
|
-
|
|
1678
|
-
|
|
1250
|
+
fs2.copyFileSync(attachmentPath, fullPath);
|
|
1251
|
+
if (resultKey === "videoPath") {
|
|
1252
|
+
testResult[resultKey]?.push(relativePath);
|
|
1253
|
+
} else if (resultKey === "tracePath") {
|
|
1254
|
+
testResult[resultKey] = relativePath;
|
|
1255
|
+
}
|
|
1679
1256
|
}
|
|
1680
1257
|
if (resultKey === "markdownPath" && errors) {
|
|
1681
1258
|
const htmlPath = fullPath.replace(/\.md$/, ".html");
|
|
1682
1259
|
const htmlRelativePath = relativePath.replace(/\.md$/, ".html");
|
|
1683
|
-
convertMarkdownToHtml(fullPath, htmlPath
|
|
1260
|
+
convertMarkdownToHtml(fullPath, htmlPath);
|
|
1684
1261
|
testResult[resultKey] = htmlRelativePath;
|
|
1685
1262
|
return;
|
|
1686
1263
|
}
|
|
@@ -1710,19 +1287,21 @@ var TestResultProcessor = class {
|
|
|
1710
1287
|
const tagPattern = /@[\w]+/g;
|
|
1711
1288
|
const title = test.title.replace(tagPattern, "").trim();
|
|
1712
1289
|
const suite = test.titlePath()[3].replace(tagPattern, "").trim();
|
|
1290
|
+
const suiteAndTitle = extractSuites(test.titlePath());
|
|
1291
|
+
const suiteHierarchy = suiteAndTitle.hierarchy;
|
|
1713
1292
|
const testResult = {
|
|
1714
|
-
|
|
1293
|
+
suiteHierarchy,
|
|
1294
|
+
key: test.id,
|
|
1715
1295
|
annotations: test.annotations,
|
|
1716
1296
|
testTags: test.tags,
|
|
1717
1297
|
location: `${filePath}:${location.line}:${location.column}`,
|
|
1718
|
-
|
|
1719
|
-
isRetry: result.retry,
|
|
1298
|
+
retryAttemptCount: result.retry,
|
|
1720
1299
|
projectName,
|
|
1721
1300
|
suite,
|
|
1722
1301
|
title,
|
|
1723
1302
|
status,
|
|
1724
1303
|
flaky: test.outcome(),
|
|
1725
|
-
duration:
|
|
1304
|
+
duration: result.duration,
|
|
1726
1305
|
errors: result.errors.map(
|
|
1727
1306
|
(e) => this.ansiToHtml.toHtml(escapeHtml(e.stack || e.toString()))
|
|
1728
1307
|
),
|
|
@@ -1734,7 +1313,8 @@ var TestResultProcessor = class {
|
|
|
1734
1313
|
),
|
|
1735
1314
|
filePath,
|
|
1736
1315
|
filters: projectSet,
|
|
1737
|
-
base64Image: ortoniConfig.base64Image
|
|
1316
|
+
base64Image: ortoniConfig.base64Image,
|
|
1317
|
+
testId: `${filePath}:${projectName}:${title}`
|
|
1738
1318
|
};
|
|
1739
1319
|
attachFiles(
|
|
1740
1320
|
test.id,
|
|
@@ -1748,7 +1328,7 @@ var TestResultProcessor = class {
|
|
|
1748
1328
|
}
|
|
1749
1329
|
processSteps(steps) {
|
|
1750
1330
|
return steps.map((step) => {
|
|
1751
|
-
const stepLocation = step.location ? `${
|
|
1331
|
+
const stepLocation = step.location ? `${path2.relative(this.projectRoot, step.location.file)}:${step.location.line}:${step.location.column}` : "";
|
|
1752
1332
|
return {
|
|
1753
1333
|
snippet: this.ansiToHtml.toHtml(escapeHtml(step.error?.snippet || "")),
|
|
1754
1334
|
title: step.title,
|
|
@@ -1777,277 +1357,8 @@ var ServerManager = class {
|
|
|
1777
1357
|
}
|
|
1778
1358
|
};
|
|
1779
1359
|
|
|
1780
|
-
// src/helpers/databaseManager.ts
|
|
1781
|
-
import { open } from "sqlite";
|
|
1782
|
-
import sqlite3 from "sqlite3";
|
|
1783
|
-
var DatabaseManager = class {
|
|
1784
|
-
constructor() {
|
|
1785
|
-
this.db = null;
|
|
1786
|
-
}
|
|
1787
|
-
async initialize(dbPath) {
|
|
1788
|
-
try {
|
|
1789
|
-
this.db = await open({
|
|
1790
|
-
filename: dbPath,
|
|
1791
|
-
driver: sqlite3.Database
|
|
1792
|
-
});
|
|
1793
|
-
await this.createTables();
|
|
1794
|
-
await this.createIndexes();
|
|
1795
|
-
} catch (error) {
|
|
1796
|
-
console.error("OrtoniReport: Error initializing database:", error);
|
|
1797
|
-
}
|
|
1798
|
-
}
|
|
1799
|
-
async createTables() {
|
|
1800
|
-
if (!this.db) {
|
|
1801
|
-
console.error("OrtoniReport: Database not initialized");
|
|
1802
|
-
return;
|
|
1803
|
-
}
|
|
1804
|
-
try {
|
|
1805
|
-
await this.db.exec(`
|
|
1806
|
-
CREATE TABLE IF NOT EXISTS test_runs (
|
|
1807
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1808
|
-
run_date TEXT
|
|
1809
|
-
);
|
|
1810
|
-
|
|
1811
|
-
CREATE TABLE IF NOT EXISTS test_results (
|
|
1812
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1813
|
-
run_id INTEGER,
|
|
1814
|
-
test_id TEXT,
|
|
1815
|
-
status TEXT,
|
|
1816
|
-
duration TEXT,
|
|
1817
|
-
error_message TEXT,
|
|
1818
|
-
FOREIGN KEY (run_id) REFERENCES test_runs (id)
|
|
1819
|
-
);
|
|
1820
|
-
`);
|
|
1821
|
-
} catch (error) {
|
|
1822
|
-
console.error("OrtoniReport: Error creating tables:", error);
|
|
1823
|
-
}
|
|
1824
|
-
}
|
|
1825
|
-
async createIndexes() {
|
|
1826
|
-
if (!this.db) {
|
|
1827
|
-
console.error("OrtoniReport: Database not initialized");
|
|
1828
|
-
return;
|
|
1829
|
-
}
|
|
1830
|
-
try {
|
|
1831
|
-
await this.db.exec(`
|
|
1832
|
-
CREATE INDEX IF NOT EXISTS idx_test_id ON test_results (test_id);
|
|
1833
|
-
CREATE INDEX IF NOT EXISTS idx_run_id ON test_results (run_id);
|
|
1834
|
-
`);
|
|
1835
|
-
} catch (error) {
|
|
1836
|
-
console.error("OrtoniReport: Error creating indexes:", error);
|
|
1837
|
-
}
|
|
1838
|
-
}
|
|
1839
|
-
async saveTestRun() {
|
|
1840
|
-
if (!this.db) {
|
|
1841
|
-
console.error("OrtoniReport: Database not initialized");
|
|
1842
|
-
return null;
|
|
1843
|
-
}
|
|
1844
|
-
try {
|
|
1845
|
-
const runDate = (/* @__PURE__ */ new Date()).toISOString();
|
|
1846
|
-
const { lastID } = await this.db.run(
|
|
1847
|
-
`
|
|
1848
|
-
INSERT INTO test_runs (run_date)
|
|
1849
|
-
VALUES (?)
|
|
1850
|
-
`,
|
|
1851
|
-
[runDate]
|
|
1852
|
-
);
|
|
1853
|
-
return lastID;
|
|
1854
|
-
} catch (error) {
|
|
1855
|
-
console.error("OrtoniReport: Error saving test run:", error);
|
|
1856
|
-
return null;
|
|
1857
|
-
}
|
|
1858
|
-
}
|
|
1859
|
-
async saveTestResults(runId, results) {
|
|
1860
|
-
if (!this.db) {
|
|
1861
|
-
console.error("OrtoniReport: Database not initialized");
|
|
1862
|
-
return;
|
|
1863
|
-
}
|
|
1864
|
-
try {
|
|
1865
|
-
await this.db.exec("BEGIN TRANSACTION;");
|
|
1866
|
-
const stmt = await this.db.prepare(`
|
|
1867
|
-
INSERT INTO test_results (run_id, test_id, status, duration, error_message)
|
|
1868
|
-
VALUES (?, ?, ?, ?, ?)
|
|
1869
|
-
`);
|
|
1870
|
-
for (const result of results) {
|
|
1871
|
-
await stmt.run([
|
|
1872
|
-
runId,
|
|
1873
|
-
`${result.filePath}:${result.projectName}:${result.title}`,
|
|
1874
|
-
result.status,
|
|
1875
|
-
result.duration,
|
|
1876
|
-
result.errors.join("\n")
|
|
1877
|
-
]);
|
|
1878
|
-
}
|
|
1879
|
-
await stmt.finalize();
|
|
1880
|
-
await this.db.exec("COMMIT;");
|
|
1881
|
-
} catch (error) {
|
|
1882
|
-
await this.db.exec("ROLLBACK;");
|
|
1883
|
-
console.error("OrtoniReport: Error saving test results:", error);
|
|
1884
|
-
}
|
|
1885
|
-
}
|
|
1886
|
-
async getTestHistory(testId, limit = 10) {
|
|
1887
|
-
if (!this.db) {
|
|
1888
|
-
console.error("OrtoniReport: Database not initialized");
|
|
1889
|
-
return [];
|
|
1890
|
-
}
|
|
1891
|
-
try {
|
|
1892
|
-
const results = await this.db.all(
|
|
1893
|
-
`
|
|
1894
|
-
SELECT tr.status, tr.duration, tr.error_message, trun.run_date
|
|
1895
|
-
FROM test_results tr
|
|
1896
|
-
JOIN test_runs trun ON tr.run_id = trun.id
|
|
1897
|
-
WHERE tr.test_id = ?
|
|
1898
|
-
ORDER BY trun.run_date DESC
|
|
1899
|
-
LIMIT ?
|
|
1900
|
-
`,
|
|
1901
|
-
[testId, limit]
|
|
1902
|
-
);
|
|
1903
|
-
return results.map((result) => ({
|
|
1904
|
-
...result,
|
|
1905
|
-
run_date: formatDateLocal(result.run_date)
|
|
1906
|
-
}));
|
|
1907
|
-
} catch (error) {
|
|
1908
|
-
console.error("OrtoniReport: Error getting test history:", error);
|
|
1909
|
-
return [];
|
|
1910
|
-
}
|
|
1911
|
-
}
|
|
1912
|
-
async close() {
|
|
1913
|
-
if (this.db) {
|
|
1914
|
-
try {
|
|
1915
|
-
await this.db.close();
|
|
1916
|
-
} catch (error) {
|
|
1917
|
-
console.error("OrtoniReport: Error closing database:", error);
|
|
1918
|
-
} finally {
|
|
1919
|
-
this.db = null;
|
|
1920
|
-
}
|
|
1921
|
-
}
|
|
1922
|
-
}
|
|
1923
|
-
async getSummaryData() {
|
|
1924
|
-
if (!this.db) {
|
|
1925
|
-
console.error("OrtoniReport: Database not initialized");
|
|
1926
|
-
return {
|
|
1927
|
-
totalRuns: 0,
|
|
1928
|
-
totalTests: 0,
|
|
1929
|
-
passed: 0,
|
|
1930
|
-
failed: 0,
|
|
1931
|
-
passRate: 0,
|
|
1932
|
-
avgDuration: 0
|
|
1933
|
-
};
|
|
1934
|
-
}
|
|
1935
|
-
try {
|
|
1936
|
-
const summary = await this.db.get(`
|
|
1937
|
-
SELECT
|
|
1938
|
-
(SELECT COUNT(*) FROM test_runs) as totalRuns,
|
|
1939
|
-
(SELECT COUNT(*) FROM test_results) as totalTests,
|
|
1940
|
-
(SELECT COUNT(*) FROM test_results WHERE status = 'passed') as passed,
|
|
1941
|
-
(SELECT COUNT(*) FROM test_results WHERE status = 'failed') as failed,
|
|
1942
|
-
(SELECT AVG(CAST(duration AS FLOAT)) FROM test_results) as avgDuration
|
|
1943
|
-
`);
|
|
1944
|
-
const passRate = summary.totalTests ? (summary.passed / summary.totalTests * 100).toFixed(2) : 0;
|
|
1945
|
-
return {
|
|
1946
|
-
totalRuns: summary.totalRuns,
|
|
1947
|
-
totalTests: summary.totalTests,
|
|
1948
|
-
passed: summary.passed,
|
|
1949
|
-
failed: summary.failed,
|
|
1950
|
-
passRate: parseFloat(passRate.toString()),
|
|
1951
|
-
avgDuration: Math.round(summary.avgDuration || 0)
|
|
1952
|
-
};
|
|
1953
|
-
} catch (error) {
|
|
1954
|
-
console.error("OrtoniReport: Error getting summary data:", error);
|
|
1955
|
-
return {
|
|
1956
|
-
totalRuns: 0,
|
|
1957
|
-
totalTests: 0,
|
|
1958
|
-
passed: 0,
|
|
1959
|
-
failed: 0,
|
|
1960
|
-
passRate: 0,
|
|
1961
|
-
avgDuration: 0
|
|
1962
|
-
};
|
|
1963
|
-
}
|
|
1964
|
-
}
|
|
1965
|
-
async getTrends(limit = 100) {
|
|
1966
|
-
if (!this.db) {
|
|
1967
|
-
console.error("OrtoniReport: Database not initialized");
|
|
1968
|
-
return [];
|
|
1969
|
-
}
|
|
1970
|
-
try {
|
|
1971
|
-
const rows = await this.db.all(
|
|
1972
|
-
`
|
|
1973
|
-
SELECT trun.run_date,
|
|
1974
|
-
SUM(CASE WHEN tr.status = 'passed' THEN 1 ELSE 0 END) AS passed,
|
|
1975
|
-
SUM(CASE WHEN tr.status = 'failed' THEN 1 ELSE 0 END) AS failed,
|
|
1976
|
-
AVG(CAST(tr.duration AS FLOAT)) AS avg_duration
|
|
1977
|
-
FROM test_results tr
|
|
1978
|
-
JOIN test_runs trun ON tr.run_id = trun.id
|
|
1979
|
-
GROUP BY trun.run_date
|
|
1980
|
-
ORDER BY trun.run_date DESC
|
|
1981
|
-
LIMIT ?
|
|
1982
|
-
`,
|
|
1983
|
-
[limit]
|
|
1984
|
-
);
|
|
1985
|
-
return rows.reverse().map((row) => ({
|
|
1986
|
-
...row,
|
|
1987
|
-
run_date: formatDateLocal(row.run_date),
|
|
1988
|
-
avg_duration: Math.round(row.avg_duration || 0)
|
|
1989
|
-
}));
|
|
1990
|
-
} catch (error) {
|
|
1991
|
-
console.error("OrtoniReport: Error getting trends data:", error);
|
|
1992
|
-
return [];
|
|
1993
|
-
}
|
|
1994
|
-
}
|
|
1995
|
-
async getFlakyTests(limit = 10) {
|
|
1996
|
-
if (!this.db) {
|
|
1997
|
-
console.error("OrtoniReport: Database not initialized");
|
|
1998
|
-
return [];
|
|
1999
|
-
}
|
|
2000
|
-
try {
|
|
2001
|
-
return await this.db.all(
|
|
2002
|
-
`
|
|
2003
|
-
SELECT
|
|
2004
|
-
test_id,
|
|
2005
|
-
COUNT(*) AS total,
|
|
2006
|
-
SUM(CASE WHEN status = 'flaky' THEN 1 ELSE 0 END) AS flaky
|
|
2007
|
-
FROM test_results
|
|
2008
|
-
GROUP BY test_id
|
|
2009
|
-
HAVING flaky > 0
|
|
2010
|
-
ORDER BY flaky DESC
|
|
2011
|
-
LIMIT ?
|
|
2012
|
-
`,
|
|
2013
|
-
[limit]
|
|
2014
|
-
);
|
|
2015
|
-
} catch (error) {
|
|
2016
|
-
console.error("OrtoniReport: Error getting flaky tests:", error);
|
|
2017
|
-
return [];
|
|
2018
|
-
}
|
|
2019
|
-
}
|
|
2020
|
-
async getSlowTests(limit = 10) {
|
|
2021
|
-
if (!this.db) {
|
|
2022
|
-
console.error("OrtoniReport: Database not initialized");
|
|
2023
|
-
return [];
|
|
2024
|
-
}
|
|
2025
|
-
try {
|
|
2026
|
-
const rows = await this.db.all(
|
|
2027
|
-
`
|
|
2028
|
-
SELECT
|
|
2029
|
-
test_id,
|
|
2030
|
-
AVG(CAST(duration AS FLOAT)) AS avg_duration
|
|
2031
|
-
FROM test_results
|
|
2032
|
-
GROUP BY test_id
|
|
2033
|
-
ORDER BY avg_duration DESC
|
|
2034
|
-
LIMIT ?
|
|
2035
|
-
`,
|
|
2036
|
-
[limit]
|
|
2037
|
-
);
|
|
2038
|
-
return rows.map((row) => ({
|
|
2039
|
-
test_id: row.test_id,
|
|
2040
|
-
avg_duration: Math.round(row.avg_duration || 0)
|
|
2041
|
-
}));
|
|
2042
|
-
} catch (error) {
|
|
2043
|
-
console.error("OrtoniReport: Error getting slow tests:", error);
|
|
2044
|
-
return [];
|
|
2045
|
-
}
|
|
2046
|
-
}
|
|
2047
|
-
};
|
|
2048
|
-
|
|
2049
1360
|
// src/ortoni-report.ts
|
|
2050
|
-
import
|
|
1361
|
+
import path3 from "path";
|
|
2051
1362
|
var OrtoniReport = class {
|
|
2052
1363
|
constructor(ortoniConfig = {}) {
|
|
2053
1364
|
this.ortoniConfig = ortoniConfig;
|
|
@@ -2078,8 +1389,9 @@ var OrtoniReport = class {
|
|
|
2078
1389
|
this.testResultProcessor = new TestResultProcessor(config.rootDir);
|
|
2079
1390
|
this.fileManager.ensureReportDirectory();
|
|
2080
1391
|
await this.dbManager.initialize(
|
|
2081
|
-
|
|
1392
|
+
path3.join(this.folderPath, "ortoni-data-history.sqlite")
|
|
2082
1393
|
);
|
|
1394
|
+
this.config = config?.shard;
|
|
2083
1395
|
}
|
|
2084
1396
|
onStdOut(chunk, _test, _result) {
|
|
2085
1397
|
if (this.reportsCount == 1 && this.showConsoleLogs) {
|
|
@@ -2112,23 +1424,35 @@ var OrtoniReport = class {
|
|
|
2112
1424
|
this.overAllStatus = result.status;
|
|
2113
1425
|
if (this.shouldGenerateReport) {
|
|
2114
1426
|
const filteredResults = this.results.filter(
|
|
2115
|
-
(r) => r.status !== "skipped"
|
|
1427
|
+
(r) => r.status !== "skipped"
|
|
2116
1428
|
);
|
|
2117
|
-
const totalDuration =
|
|
2118
|
-
|
|
1429
|
+
const totalDuration = result.duration;
|
|
1430
|
+
if (this.config && this.config.total > 1) {
|
|
1431
|
+
const shard = this.config;
|
|
1432
|
+
const shardFile = `ortoni-shard-${shard.current}-of-${shard.total}.json`;
|
|
1433
|
+
const shardData = {
|
|
1434
|
+
status: result.status,
|
|
1435
|
+
duration: totalDuration,
|
|
1436
|
+
results: this.results,
|
|
1437
|
+
projectSet: Array.from(this.projectSet)
|
|
1438
|
+
};
|
|
1439
|
+
this.fileManager.writeRawFile(shardFile, shardData);
|
|
1440
|
+
console.log(`\u{1F4E6} OrtoniReport wrote shard file: ${shardFile}`);
|
|
1441
|
+
this.shouldGenerateReport = false;
|
|
1442
|
+
return;
|
|
1443
|
+
}
|
|
2119
1444
|
const runId = await this.dbManager.saveTestRun();
|
|
2120
1445
|
if (runId !== null) {
|
|
2121
1446
|
await this.dbManager.saveTestResults(runId, this.results);
|
|
2122
|
-
const
|
|
1447
|
+
const finalReportData = await this.htmlGenerator.generateFinalReport(
|
|
2123
1448
|
filteredResults,
|
|
2124
1449
|
totalDuration,
|
|
2125
|
-
cssContent,
|
|
2126
1450
|
this.results,
|
|
2127
1451
|
this.projectSet
|
|
2128
1452
|
);
|
|
2129
1453
|
this.outputPath = this.fileManager.writeReportFile(
|
|
2130
1454
|
this.outputFilename,
|
|
2131
|
-
|
|
1455
|
+
finalReportData
|
|
2132
1456
|
);
|
|
2133
1457
|
} else {
|
|
2134
1458
|
console.error("OrtoniReport: Error saving test run to database");
|