ortoni-report 4.0.2 → 4.0.4
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 +10 -0
- package/dist/chunk-L6VOLEP2.mjs +752 -0
- package/dist/cli.js +27 -21
- package/dist/cli.mjs +1 -1
- package/dist/helpers/HTMLGenerator.d.ts +89 -0
- package/dist/helpers/HTMLGenerator.js +164 -0
- package/dist/helpers/databaseManager.d.ts +35 -0
- package/dist/helpers/databaseManager.js +267 -0
- package/dist/helpers/fileManager.d.ts +8 -0
- package/dist/helpers/fileManager.js +60 -0
- package/dist/helpers/markdownConverter.d.ts +1 -0
- package/dist/helpers/markdownConverter.js +14 -0
- package/dist/helpers/resultProcessor.d.ts +10 -0
- package/dist/helpers/resultProcessor.js +60 -0
- package/dist/helpers/serverManager.d.ts +6 -0
- package/dist/helpers/serverManager.js +15 -0
- package/dist/helpers/templateLoader.d.ts +15 -0
- package/dist/helpers/templateLoader.js +88 -0
- package/dist/index.html +2 -2
- package/dist/mergeData.d.ts +13 -0
- package/dist/mergeData.js +182 -0
- package/dist/ortoni-report.js +72 -64
- package/dist/ortoni-report.mjs +2 -2
- package/dist/types/reporterConfig.d.ts +86 -0
- package/dist/types/reporterConfig.js +1 -0
- package/dist/types/testResults.d.ts +31 -0
- package/dist/types/testResults.js +1 -0
- package/dist/utils/attachFiles.d.ts +4 -0
- package/dist/utils/attachFiles.js +87 -0
- package/dist/utils/expressServer.d.ts +1 -0
- package/dist/utils/expressServer.js +61 -0
- package/dist/utils/groupProjects.d.ts +3 -0
- package/dist/utils/groupProjects.js +30 -0
- package/dist/utils/utils.d.ts +15 -0
- package/dist/utils/utils.js +93 -0
- package/package.json +1 -1
- package/readme.md +1 -1
package/dist/cli.js
CHANGED
|
@@ -34,24 +34,6 @@ var path3 = __toESM(require("path"));
|
|
|
34
34
|
// src/helpers/databaseManager.ts
|
|
35
35
|
var import_sqlite = require("sqlite");
|
|
36
36
|
var import_sqlite3 = __toESM(require("sqlite3"));
|
|
37
|
-
|
|
38
|
-
// src/utils/utils.ts
|
|
39
|
-
function formatDateLocal(dateInput) {
|
|
40
|
-
const date = typeof dateInput === "string" ? new Date(dateInput) : dateInput;
|
|
41
|
-
const options = {
|
|
42
|
-
year: "numeric",
|
|
43
|
-
month: "short",
|
|
44
|
-
day: "2-digit",
|
|
45
|
-
hour: "2-digit",
|
|
46
|
-
minute: "2-digit",
|
|
47
|
-
hour12: true,
|
|
48
|
-
timeZoneName: "short"
|
|
49
|
-
// or "Asia/Kolkata"
|
|
50
|
-
};
|
|
51
|
-
return new Intl.DateTimeFormat(void 0, options).format(date);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// src/helpers/databaseManager.ts
|
|
55
37
|
var DatabaseManager = class {
|
|
56
38
|
constructor() {
|
|
57
39
|
this.db = null;
|
|
@@ -175,7 +157,8 @@ var DatabaseManager = class {
|
|
|
175
157
|
);
|
|
176
158
|
return results.map((result) => ({
|
|
177
159
|
...result,
|
|
178
|
-
run_date:
|
|
160
|
+
run_date: result.run_date
|
|
161
|
+
// Return raw ISO string to avoid parsing issues in browser
|
|
179
162
|
}));
|
|
180
163
|
} catch (error) {
|
|
181
164
|
console.error("OrtoniReport: Error getting test history:", error);
|
|
@@ -258,7 +241,8 @@ var DatabaseManager = class {
|
|
|
258
241
|
);
|
|
259
242
|
return rows.reverse().map((row) => ({
|
|
260
243
|
...row,
|
|
261
|
-
run_date:
|
|
244
|
+
run_date: row.run_date,
|
|
245
|
+
// Return raw ISO string for chart compatibility
|
|
262
246
|
avg_duration: Math.round(row.avg_duration || 0)
|
|
263
247
|
// raw ms avg
|
|
264
248
|
}));
|
|
@@ -351,6 +335,28 @@ function groupResults(config, results) {
|
|
|
351
335
|
}
|
|
352
336
|
}
|
|
353
337
|
|
|
338
|
+
// src/utils/utils.ts
|
|
339
|
+
function formatDateLocal(dateInput) {
|
|
340
|
+
if (!dateInput) return "N/A";
|
|
341
|
+
try {
|
|
342
|
+
const date = typeof dateInput === "string" ? new Date(dateInput) : dateInput;
|
|
343
|
+
if (isNaN(date.getTime())) {
|
|
344
|
+
return "N/A";
|
|
345
|
+
}
|
|
346
|
+
const options = {
|
|
347
|
+
year: "numeric",
|
|
348
|
+
month: "short",
|
|
349
|
+
day: "2-digit",
|
|
350
|
+
hour: "2-digit",
|
|
351
|
+
minute: "2-digit",
|
|
352
|
+
hour12: true
|
|
353
|
+
};
|
|
354
|
+
return new Intl.DateTimeFormat("en-US", options).format(date);
|
|
355
|
+
} catch (e) {
|
|
356
|
+
return "N/A";
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
354
360
|
// src/helpers/HTMLGenerator.ts
|
|
355
361
|
var HTMLGenerator = class {
|
|
356
362
|
constructor(ortoniConfig, dbManager) {
|
|
@@ -424,7 +430,7 @@ var HTMLGenerator = class {
|
|
|
424
430
|
results,
|
|
425
431
|
projectSet
|
|
426
432
|
);
|
|
427
|
-
const lastRunDate = (/* @__PURE__ */ new Date())
|
|
433
|
+
const lastRunDate = formatDateLocal(/* @__PURE__ */ new Date());
|
|
428
434
|
const testHistories = await Promise.all(
|
|
429
435
|
results.map(async (result) => {
|
|
430
436
|
const testId = `${result.filePath}:${result.projectName}:${result.title}`;
|
package/dist/cli.mjs
CHANGED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { TestResultData } from "../types/testResults";
|
|
2
|
+
import { OrtoniReportConfig } from "../types/reporterConfig";
|
|
3
|
+
import { DatabaseManager } from "./databaseManager";
|
|
4
|
+
export declare class HTMLGenerator {
|
|
5
|
+
private ortoniConfig;
|
|
6
|
+
private dbManager?;
|
|
7
|
+
constructor(ortoniConfig: OrtoniReportConfig, dbManager?: DatabaseManager);
|
|
8
|
+
generateFinalReport(filteredResults: TestResultData[], totalDuration: number, results: TestResultData[], projectSet: Set<string>): Promise<{
|
|
9
|
+
summary: {
|
|
10
|
+
overAllResult: {
|
|
11
|
+
pass: number;
|
|
12
|
+
fail: number;
|
|
13
|
+
skip: number;
|
|
14
|
+
retry: number;
|
|
15
|
+
flaky: number;
|
|
16
|
+
total: number;
|
|
17
|
+
};
|
|
18
|
+
successRate: string;
|
|
19
|
+
lastRunDate: string;
|
|
20
|
+
totalDuration: number;
|
|
21
|
+
stats: {
|
|
22
|
+
projectNames: string[];
|
|
23
|
+
totalTests: number[];
|
|
24
|
+
passedTests: number[];
|
|
25
|
+
failedTests: number[];
|
|
26
|
+
skippedTests: number[];
|
|
27
|
+
retryTests: number[];
|
|
28
|
+
flakyTests: number[];
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
testResult: {
|
|
32
|
+
tests: any;
|
|
33
|
+
testHistories: {
|
|
34
|
+
testId: string;
|
|
35
|
+
history: any[];
|
|
36
|
+
}[];
|
|
37
|
+
allTags: string[];
|
|
38
|
+
set: Set<string>;
|
|
39
|
+
};
|
|
40
|
+
userConfig: {
|
|
41
|
+
projectName: string;
|
|
42
|
+
authorName: string;
|
|
43
|
+
type: string;
|
|
44
|
+
title: string;
|
|
45
|
+
};
|
|
46
|
+
userMeta: {
|
|
47
|
+
meta: Record<string, string>;
|
|
48
|
+
};
|
|
49
|
+
preferences: {
|
|
50
|
+
logo: string;
|
|
51
|
+
showProject: boolean;
|
|
52
|
+
};
|
|
53
|
+
analytics: {
|
|
54
|
+
reportData: {
|
|
55
|
+
summary: {};
|
|
56
|
+
trends: {};
|
|
57
|
+
flakyTests: any[];
|
|
58
|
+
slowTests: any[];
|
|
59
|
+
note: string;
|
|
60
|
+
} | {
|
|
61
|
+
summary: {};
|
|
62
|
+
trends: {};
|
|
63
|
+
flakyTests: any[];
|
|
64
|
+
slowTests: any[];
|
|
65
|
+
note?: undefined;
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
}>;
|
|
69
|
+
/**
|
|
70
|
+
* Return safe analytics/report data.
|
|
71
|
+
* If no dbManager is provided, return empty defaults and a note explaining why.
|
|
72
|
+
*/
|
|
73
|
+
getReportData(): Promise<{
|
|
74
|
+
summary: {};
|
|
75
|
+
trends: {};
|
|
76
|
+
flakyTests: any[];
|
|
77
|
+
slowTests: any[];
|
|
78
|
+
note: string;
|
|
79
|
+
} | {
|
|
80
|
+
summary: {};
|
|
81
|
+
trends: {};
|
|
82
|
+
flakyTests: any[];
|
|
83
|
+
slowTests: any[];
|
|
84
|
+
note?: undefined;
|
|
85
|
+
}>;
|
|
86
|
+
private prepareReportData;
|
|
87
|
+
private calculateProjectResults;
|
|
88
|
+
private extractProjectStats;
|
|
89
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { groupResults } from "../utils/groupProjects";
|
|
2
|
+
import { formatDateLocal } from "../utils/utils";
|
|
3
|
+
export class HTMLGenerator {
|
|
4
|
+
constructor(ortoniConfig, dbManager) {
|
|
5
|
+
this.ortoniConfig = ortoniConfig;
|
|
6
|
+
this.dbManager = dbManager; // may be undefined in CI or when saveHistory=false
|
|
7
|
+
}
|
|
8
|
+
async generateFinalReport(filteredResults, totalDuration, results, projectSet) {
|
|
9
|
+
const data = await this.prepareReportData(filteredResults, totalDuration, results, projectSet);
|
|
10
|
+
return data;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Return safe analytics/report data.
|
|
14
|
+
* If no dbManager is provided, return empty defaults and a note explaining why.
|
|
15
|
+
*/
|
|
16
|
+
async getReportData() {
|
|
17
|
+
if (!this.dbManager) {
|
|
18
|
+
return {
|
|
19
|
+
summary: {},
|
|
20
|
+
trends: {},
|
|
21
|
+
flakyTests: [],
|
|
22
|
+
slowTests: [],
|
|
23
|
+
note: "Test history/trends are unavailable (saveHistory disabled or DB not initialized).",
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
const [summary, trends, flakyTests, slowTests] = await Promise.all([
|
|
28
|
+
this.dbManager.getSummaryData
|
|
29
|
+
? this.dbManager.getSummaryData()
|
|
30
|
+
: Promise.resolve({}),
|
|
31
|
+
this.dbManager.getTrends
|
|
32
|
+
? this.dbManager.getTrends()
|
|
33
|
+
: Promise.resolve({}),
|
|
34
|
+
this.dbManager.getFlakyTests
|
|
35
|
+
? this.dbManager.getFlakyTests()
|
|
36
|
+
: Promise.resolve([]),
|
|
37
|
+
this.dbManager.getSlowTests
|
|
38
|
+
? this.dbManager.getSlowTests()
|
|
39
|
+
: Promise.resolve([]),
|
|
40
|
+
]);
|
|
41
|
+
return {
|
|
42
|
+
summary: summary ?? {},
|
|
43
|
+
trends: trends ?? {},
|
|
44
|
+
flakyTests: flakyTests ?? [],
|
|
45
|
+
slowTests: slowTests ?? [],
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
console.warn("HTMLGenerator: failed to read analytics from DB, continuing without history.", err);
|
|
50
|
+
return {
|
|
51
|
+
summary: {},
|
|
52
|
+
trends: {},
|
|
53
|
+
flakyTests: [],
|
|
54
|
+
slowTests: [],
|
|
55
|
+
note: "Test history/trends could not be loaded due to a DB error.",
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
async prepareReportData(filteredResults, totalDuration, results, projectSet) {
|
|
60
|
+
const totalTests = filteredResults.length;
|
|
61
|
+
const passedTests = results.filter((r) => r.status === "passed").length;
|
|
62
|
+
const flakyTests = results.filter((r) => r.status === "flaky").length;
|
|
63
|
+
const failed = filteredResults.filter((r) => r.status === "failed" || r.status === "timedOut").length;
|
|
64
|
+
const successRate = totalTests === 0
|
|
65
|
+
? "0.00"
|
|
66
|
+
: (((passedTests + flakyTests) / totalTests) * 100).toFixed(2);
|
|
67
|
+
const allTags = new Set();
|
|
68
|
+
results.forEach((result) => (result.testTags || []).forEach((tag) => allTags.add(tag)));
|
|
69
|
+
const projectResults = this.calculateProjectResults(filteredResults, results, projectSet);
|
|
70
|
+
const lastRunDate = formatDateLocal(new Date());
|
|
71
|
+
// Fetch per-test histories only if DB manager exists; otherwise return empty history arrays.
|
|
72
|
+
const testHistories = await Promise.all(results.map(async (result) => {
|
|
73
|
+
const testId = `${result.filePath}:${result.projectName}:${result.title}`;
|
|
74
|
+
if (!this.dbManager || !this.dbManager.getTestHistory) {
|
|
75
|
+
return {
|
|
76
|
+
testId,
|
|
77
|
+
history: [],
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
try {
|
|
81
|
+
const history = await this.dbManager.getTestHistory(testId);
|
|
82
|
+
return {
|
|
83
|
+
testId,
|
|
84
|
+
history: history ?? [],
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
catch (err) {
|
|
88
|
+
// If a single test history fails, return empty history for that test and continue
|
|
89
|
+
console.warn(`HTMLGenerator: failed to read history for ${testId}`, err);
|
|
90
|
+
return {
|
|
91
|
+
testId,
|
|
92
|
+
history: [],
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
}));
|
|
96
|
+
// Fetch analytics/reportData using the safe getter (this will handle missing DB)
|
|
97
|
+
const reportData = await this.getReportData();
|
|
98
|
+
return {
|
|
99
|
+
summary: {
|
|
100
|
+
overAllResult: {
|
|
101
|
+
pass: passedTests,
|
|
102
|
+
fail: failed,
|
|
103
|
+
skip: results.filter((r) => r.status === "skipped").length,
|
|
104
|
+
retry: results.filter((r) => r.retryAttemptCount).length,
|
|
105
|
+
flaky: flakyTests,
|
|
106
|
+
total: filteredResults.length,
|
|
107
|
+
},
|
|
108
|
+
successRate,
|
|
109
|
+
lastRunDate,
|
|
110
|
+
totalDuration,
|
|
111
|
+
stats: this.extractProjectStats(projectResults),
|
|
112
|
+
},
|
|
113
|
+
testResult: {
|
|
114
|
+
tests: groupResults(this.ortoniConfig, results),
|
|
115
|
+
testHistories,
|
|
116
|
+
allTags: Array.from(allTags),
|
|
117
|
+
set: projectSet,
|
|
118
|
+
},
|
|
119
|
+
userConfig: {
|
|
120
|
+
projectName: this.ortoniConfig.projectName,
|
|
121
|
+
authorName: this.ortoniConfig.authorName,
|
|
122
|
+
type: this.ortoniConfig.testType,
|
|
123
|
+
title: this.ortoniConfig.title,
|
|
124
|
+
},
|
|
125
|
+
userMeta: {
|
|
126
|
+
meta: this.ortoniConfig.meta,
|
|
127
|
+
},
|
|
128
|
+
preferences: {
|
|
129
|
+
logo: this.ortoniConfig.logo || undefined,
|
|
130
|
+
showProject: this.ortoniConfig.showProject || false,
|
|
131
|
+
},
|
|
132
|
+
analytics: {
|
|
133
|
+
reportData: reportData,
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
calculateProjectResults(filteredResults, results, projectSet) {
|
|
138
|
+
return Array.from(projectSet).map((projectName) => {
|
|
139
|
+
const projectTests = filteredResults.filter((r) => r.projectName === projectName);
|
|
140
|
+
const allProjectTests = results.filter((r) => r.projectName === projectName);
|
|
141
|
+
return {
|
|
142
|
+
projectName,
|
|
143
|
+
passedTests: projectTests.filter((r) => r.status === "passed").length,
|
|
144
|
+
failedTests: projectTests.filter((r) => r.status === "failed" || r.status === "timedOut").length,
|
|
145
|
+
skippedTests: allProjectTests.filter((r) => r.status === "skipped")
|
|
146
|
+
.length,
|
|
147
|
+
retryTests: allProjectTests.filter((r) => r.retryAttemptCount).length,
|
|
148
|
+
flakyTests: allProjectTests.filter((r) => r.status === "flaky").length,
|
|
149
|
+
totalTests: projectTests.length,
|
|
150
|
+
};
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
extractProjectStats(projectResults) {
|
|
154
|
+
return {
|
|
155
|
+
projectNames: projectResults.map((result) => result.projectName),
|
|
156
|
+
totalTests: projectResults.map((result) => result.totalTests),
|
|
157
|
+
passedTests: projectResults.map((result) => result.passedTests),
|
|
158
|
+
failedTests: projectResults.map((result) => result.failedTests),
|
|
159
|
+
skippedTests: projectResults.map((result) => result.skippedTests),
|
|
160
|
+
retryTests: projectResults.map((result) => result.retryTests),
|
|
161
|
+
flakyTests: projectResults.map((result) => result.flakyTests),
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { TestResultData } from "../types/testResults";
|
|
2
|
+
export declare class DatabaseManager {
|
|
3
|
+
private db;
|
|
4
|
+
initialize(dbPath: string): Promise<void>;
|
|
5
|
+
private createTables;
|
|
6
|
+
private createIndexes;
|
|
7
|
+
saveTestRun(): Promise<number | null>;
|
|
8
|
+
saveTestResults(runId: number, results: TestResultData[]): Promise<void>;
|
|
9
|
+
getTestHistory(testId: string, limit?: number): Promise<any[]>;
|
|
10
|
+
close(): Promise<void>;
|
|
11
|
+
getSummaryData(): Promise<{
|
|
12
|
+
totalRuns: number;
|
|
13
|
+
totalTests: number;
|
|
14
|
+
passed: number;
|
|
15
|
+
failed: number;
|
|
16
|
+
passRate: number;
|
|
17
|
+
avgDuration: number;
|
|
18
|
+
}>;
|
|
19
|
+
getTrends(limit?: number): Promise<{
|
|
20
|
+
run_date: string;
|
|
21
|
+
passed: number;
|
|
22
|
+
failed: number;
|
|
23
|
+
avg_duration: number;
|
|
24
|
+
}[]>;
|
|
25
|
+
getFlakyTests(limit?: number): Promise<{
|
|
26
|
+
test_id: string;
|
|
27
|
+
total: number;
|
|
28
|
+
flaky: number;
|
|
29
|
+
avg_duration: number;
|
|
30
|
+
}[]>;
|
|
31
|
+
getSlowTests(limit?: number): Promise<{
|
|
32
|
+
test_id: string;
|
|
33
|
+
avg_duration: number;
|
|
34
|
+
}[]>;
|
|
35
|
+
}
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import { open } from "sqlite";
|
|
2
|
+
import sqlite3 from "sqlite3";
|
|
3
|
+
export class DatabaseManager {
|
|
4
|
+
constructor() {
|
|
5
|
+
this.db = null;
|
|
6
|
+
}
|
|
7
|
+
async initialize(dbPath) {
|
|
8
|
+
try {
|
|
9
|
+
this.db = await open({
|
|
10
|
+
filename: dbPath,
|
|
11
|
+
driver: sqlite3.Database,
|
|
12
|
+
});
|
|
13
|
+
await this.createTables();
|
|
14
|
+
await this.createIndexes();
|
|
15
|
+
}
|
|
16
|
+
catch (error) {
|
|
17
|
+
console.error("OrtoniReport: Error initializing database:", error);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
async createTables() {
|
|
21
|
+
if (!this.db) {
|
|
22
|
+
console.error("OrtoniReport: Database not initialized");
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
await this.db.exec(`
|
|
27
|
+
CREATE TABLE IF NOT EXISTS test_runs (
|
|
28
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
29
|
+
run_date TEXT
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
CREATE TABLE IF NOT EXISTS test_results (
|
|
33
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
34
|
+
run_id INTEGER,
|
|
35
|
+
test_id TEXT,
|
|
36
|
+
status TEXT,
|
|
37
|
+
duration INTEGER, -- store duration as raw ms
|
|
38
|
+
error_message TEXT,
|
|
39
|
+
FOREIGN KEY (run_id) REFERENCES test_runs (id)
|
|
40
|
+
);
|
|
41
|
+
`);
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
console.error("OrtoniReport: Error creating tables:", error);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
async createIndexes() {
|
|
48
|
+
if (!this.db) {
|
|
49
|
+
console.error("OrtoniReport: Database not initialized");
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
await this.db.exec(`
|
|
54
|
+
CREATE INDEX IF NOT EXISTS idx_test_id ON test_results (test_id);
|
|
55
|
+
CREATE INDEX IF NOT EXISTS idx_run_id ON test_results (run_id);
|
|
56
|
+
`);
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
console.error("OrtoniReport: Error creating indexes:", error);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
async saveTestRun() {
|
|
63
|
+
if (!this.db) {
|
|
64
|
+
console.error("OrtoniReport: Database not initialized");
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
const runDate = new Date().toISOString();
|
|
69
|
+
const { lastID } = await this.db.run(`
|
|
70
|
+
INSERT INTO test_runs (run_date)
|
|
71
|
+
VALUES (?)
|
|
72
|
+
`, [runDate]);
|
|
73
|
+
return lastID;
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
console.error("OrtoniReport: Error saving test run:", error);
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
async saveTestResults(runId, results) {
|
|
81
|
+
if (!this.db) {
|
|
82
|
+
console.error("OrtoniReport: Database not initialized");
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
try {
|
|
86
|
+
await this.db.exec("BEGIN TRANSACTION;");
|
|
87
|
+
const stmt = await this.db.prepare(`
|
|
88
|
+
INSERT INTO test_results (run_id, test_id, status, duration, error_message)
|
|
89
|
+
VALUES (?, ?, ?, ?, ?)
|
|
90
|
+
`);
|
|
91
|
+
for (const result of results) {
|
|
92
|
+
await stmt.run([
|
|
93
|
+
runId,
|
|
94
|
+
`${result.filePath}:${result.projectName}:${result.title}`,
|
|
95
|
+
result.status,
|
|
96
|
+
result.duration,
|
|
97
|
+
result.errors.join("\n"),
|
|
98
|
+
]);
|
|
99
|
+
}
|
|
100
|
+
await stmt.finalize();
|
|
101
|
+
await this.db.exec("COMMIT;");
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
await this.db.exec("ROLLBACK;");
|
|
105
|
+
console.error("OrtoniReport: Error saving test results:", error);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
async getTestHistory(testId, limit = 10) {
|
|
109
|
+
if (!this.db) {
|
|
110
|
+
console.error("OrtoniReport: Database not initialized");
|
|
111
|
+
return [];
|
|
112
|
+
}
|
|
113
|
+
try {
|
|
114
|
+
const results = await this.db.all(`
|
|
115
|
+
SELECT tr.status, tr.duration, tr.error_message, trun.run_date
|
|
116
|
+
FROM test_results tr
|
|
117
|
+
JOIN test_runs trun ON tr.run_id = trun.id
|
|
118
|
+
WHERE tr.test_id = ?
|
|
119
|
+
ORDER BY trun.run_date DESC
|
|
120
|
+
LIMIT ?
|
|
121
|
+
`, [testId, limit]);
|
|
122
|
+
return results.map((result) => ({
|
|
123
|
+
...result,
|
|
124
|
+
run_date: result.run_date, // Return raw ISO string to avoid parsing issues in browser
|
|
125
|
+
}));
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
console.error("OrtoniReport: Error getting test history:", error);
|
|
129
|
+
return [];
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
async close() {
|
|
133
|
+
if (this.db) {
|
|
134
|
+
try {
|
|
135
|
+
await this.db.close();
|
|
136
|
+
}
|
|
137
|
+
catch (error) {
|
|
138
|
+
console.error("OrtoniReport: Error closing database:", error);
|
|
139
|
+
}
|
|
140
|
+
finally {
|
|
141
|
+
this.db = null;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
async getSummaryData() {
|
|
146
|
+
if (!this.db) {
|
|
147
|
+
console.error("OrtoniReport: Database not initialized");
|
|
148
|
+
return {
|
|
149
|
+
totalRuns: 0,
|
|
150
|
+
totalTests: 0,
|
|
151
|
+
passed: 0,
|
|
152
|
+
failed: 0,
|
|
153
|
+
passRate: 0,
|
|
154
|
+
avgDuration: 0,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
try {
|
|
158
|
+
const summary = await this.db.get(`
|
|
159
|
+
SELECT
|
|
160
|
+
(SELECT COUNT(*) FROM test_runs) as totalRuns,
|
|
161
|
+
(SELECT COUNT(*) FROM test_results) as totalTests,
|
|
162
|
+
(SELECT COUNT(*) FROM test_results WHERE status = 'passed') as passed,
|
|
163
|
+
(SELECT COUNT(*) FROM test_results WHERE status = 'failed') as failed,
|
|
164
|
+
(SELECT AVG(duration) FROM test_results) as avgDuration
|
|
165
|
+
`);
|
|
166
|
+
const passRate = summary.totalTests
|
|
167
|
+
? ((summary.passed / summary.totalTests) * 100).toFixed(2)
|
|
168
|
+
: 0;
|
|
169
|
+
return {
|
|
170
|
+
totalRuns: summary.totalRuns,
|
|
171
|
+
totalTests: summary.totalTests,
|
|
172
|
+
passed: summary.passed,
|
|
173
|
+
failed: summary.failed,
|
|
174
|
+
passRate: parseFloat(passRate.toString()),
|
|
175
|
+
avgDuration: Math.round(summary.avgDuration || 0), // raw ms avg
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
console.error("OrtoniReport: Error getting summary data:", error);
|
|
180
|
+
return {
|
|
181
|
+
totalRuns: 0,
|
|
182
|
+
totalTests: 0,
|
|
183
|
+
passed: 0,
|
|
184
|
+
failed: 0,
|
|
185
|
+
passRate: 0,
|
|
186
|
+
avgDuration: 0,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
async getTrends(limit = 100) {
|
|
191
|
+
if (!this.db) {
|
|
192
|
+
console.error("OrtoniReport: Database not initialized");
|
|
193
|
+
return [];
|
|
194
|
+
}
|
|
195
|
+
try {
|
|
196
|
+
const rows = await this.db.all(`
|
|
197
|
+
SELECT trun.run_date,
|
|
198
|
+
SUM(CASE WHEN tr.status = 'passed' THEN 1 ELSE 0 END) AS passed,
|
|
199
|
+
SUM(CASE WHEN tr.status = 'failed' THEN 1 ELSE 0 END) AS failed,
|
|
200
|
+
AVG(tr.duration) AS avg_duration
|
|
201
|
+
FROM test_results tr
|
|
202
|
+
JOIN test_runs trun ON tr.run_id = trun.id
|
|
203
|
+
GROUP BY trun.run_date
|
|
204
|
+
ORDER BY trun.run_date DESC
|
|
205
|
+
LIMIT ?
|
|
206
|
+
`, [limit]);
|
|
207
|
+
return rows.reverse().map((row) => ({
|
|
208
|
+
...row,
|
|
209
|
+
run_date: row.run_date,
|
|
210
|
+
avg_duration: Math.round(row.avg_duration || 0), // raw ms avg
|
|
211
|
+
}));
|
|
212
|
+
}
|
|
213
|
+
catch (error) {
|
|
214
|
+
console.error("OrtoniReport: Error getting trends data:", error);
|
|
215
|
+
return [];
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
async getFlakyTests(limit = 10) {
|
|
219
|
+
if (!this.db) {
|
|
220
|
+
console.error("OrtoniReport: Database not initialized");
|
|
221
|
+
return [];
|
|
222
|
+
}
|
|
223
|
+
try {
|
|
224
|
+
return await this.db.all(`
|
|
225
|
+
SELECT
|
|
226
|
+
test_id,
|
|
227
|
+
COUNT(*) AS total,
|
|
228
|
+
SUM(CASE WHEN status = 'flaky' THEN 1 ELSE 0 END) AS flaky,
|
|
229
|
+
AVG(duration) AS avg_duration
|
|
230
|
+
FROM test_results
|
|
231
|
+
GROUP BY test_id
|
|
232
|
+
HAVING flaky > 0
|
|
233
|
+
ORDER BY flaky DESC
|
|
234
|
+
LIMIT ?
|
|
235
|
+
`, [limit]);
|
|
236
|
+
}
|
|
237
|
+
catch (error) {
|
|
238
|
+
console.error("OrtoniReport: Error getting flaky tests:", error);
|
|
239
|
+
return [];
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
async getSlowTests(limit = 10) {
|
|
243
|
+
if (!this.db) {
|
|
244
|
+
console.error("OrtoniReport: Database not initialized");
|
|
245
|
+
return [];
|
|
246
|
+
}
|
|
247
|
+
try {
|
|
248
|
+
const rows = await this.db.all(`
|
|
249
|
+
SELECT
|
|
250
|
+
test_id,
|
|
251
|
+
AVG(duration) AS avg_duration
|
|
252
|
+
FROM test_results
|
|
253
|
+
GROUP BY test_id
|
|
254
|
+
ORDER BY avg_duration DESC
|
|
255
|
+
LIMIT ?
|
|
256
|
+
`, [limit]);
|
|
257
|
+
return rows.map((row) => ({
|
|
258
|
+
test_id: row.test_id,
|
|
259
|
+
avg_duration: Math.round(row.avg_duration || 0), // raw ms avg
|
|
260
|
+
}));
|
|
261
|
+
}
|
|
262
|
+
catch (error) {
|
|
263
|
+
console.error("OrtoniReport: Error getting slow tests:", error);
|
|
264
|
+
return [];
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare class FileManager {
|
|
2
|
+
private folderPath;
|
|
3
|
+
constructor(folderPath: string);
|
|
4
|
+
ensureReportDirectory(): void;
|
|
5
|
+
writeReportFile(filename: string, data: unknown): Promise<string>;
|
|
6
|
+
writeRawFile(filename: string, data: unknown): string;
|
|
7
|
+
copyTraceViewerAssets(skip: boolean): void;
|
|
8
|
+
}
|