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.js
CHANGED
|
@@ -54,17 +54,24 @@ var FileManager = class {
|
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
|
-
writeReportFile(filename,
|
|
57
|
+
writeReportFile(filename, data) {
|
|
58
|
+
const templatePath = import_path.default.join(__dirname, "..", "index.html");
|
|
59
|
+
console.log("temp path - " + templatePath);
|
|
60
|
+
let html = import_fs.default.readFileSync(templatePath, "utf-8");
|
|
61
|
+
const reportJSON = JSON.stringify({
|
|
62
|
+
data
|
|
63
|
+
});
|
|
64
|
+
html = html.replace("__ORTONI_TEST_REPORTDATA__", reportJSON);
|
|
65
|
+
import_fs.default.writeFileSync(filename, html);
|
|
66
|
+
return filename;
|
|
67
|
+
}
|
|
68
|
+
writeRawFile(filename, data) {
|
|
58
69
|
const outputPath = import_path.default.join(process.cwd(), this.folderPath, filename);
|
|
59
|
-
import_fs.default.
|
|
70
|
+
import_fs.default.mkdirSync(import_path.default.dirname(outputPath), { recursive: true });
|
|
71
|
+
const content = typeof data === "string" ? data : JSON.stringify(data, null, 2);
|
|
72
|
+
import_fs.default.writeFileSync(outputPath, content, "utf-8");
|
|
60
73
|
return outputPath;
|
|
61
74
|
}
|
|
62
|
-
readCssContent() {
|
|
63
|
-
return import_fs.default.readFileSync(
|
|
64
|
-
import_path.default.resolve(__dirname, "style", "main.css"),
|
|
65
|
-
"utf-8"
|
|
66
|
-
);
|
|
67
|
-
}
|
|
68
75
|
copyTraceViewerAssets(skip) {
|
|
69
76
|
if (skip) return;
|
|
70
77
|
const traceViewerFolder = import_path.default.join(
|
|
@@ -99,157 +106,48 @@ var FileManager = class {
|
|
|
99
106
|
}
|
|
100
107
|
};
|
|
101
108
|
|
|
102
|
-
// src/helpers/HTMLGenerator.ts
|
|
103
|
-
var import_path3 = __toESM(require("path"));
|
|
104
|
-
|
|
105
109
|
// src/utils/groupProjects.ts
|
|
106
110
|
function groupResults(config, results) {
|
|
107
111
|
if (config.showProject) {
|
|
108
112
|
const groupedResults = results.reduce((acc, result, index) => {
|
|
109
113
|
const testId = `${result.filePath}:${result.projectName}:${result.title}`;
|
|
114
|
+
const key = `${testId}-${result.key}-${result.retryAttemptCount}`;
|
|
110
115
|
const { filePath, suite, projectName } = result;
|
|
111
116
|
acc[filePath] = acc[filePath] || {};
|
|
112
117
|
acc[filePath][suite] = acc[filePath][suite] || {};
|
|
113
118
|
acc[filePath][suite][projectName] = acc[filePath][suite][projectName] || [];
|
|
114
|
-
acc[filePath][suite][projectName].push({ ...result, index, testId });
|
|
119
|
+
acc[filePath][suite][projectName].push({ ...result, index, testId, key });
|
|
115
120
|
return acc;
|
|
116
121
|
}, {});
|
|
117
122
|
return groupedResults;
|
|
118
123
|
} else {
|
|
119
124
|
const groupedResults = results.reduce((acc, result, index) => {
|
|
120
125
|
const testId = `${result.filePath}:${result.projectName}:${result.title}`;
|
|
126
|
+
const key = `${testId}-${result.key}-${result.retryAttemptCount}`;
|
|
121
127
|
const { filePath, suite } = result;
|
|
122
128
|
acc[filePath] = acc[filePath] || {};
|
|
123
129
|
acc[filePath][suite] = acc[filePath][suite] || [];
|
|
124
|
-
acc[filePath][suite].push({ ...result, index, testId });
|
|
130
|
+
acc[filePath][suite].push({ ...result, index, testId, key });
|
|
125
131
|
return acc;
|
|
126
132
|
}, {});
|
|
127
133
|
return groupedResults;
|
|
128
134
|
}
|
|
129
135
|
}
|
|
130
136
|
|
|
131
|
-
// src/utils/utils.ts
|
|
132
|
-
var import_path2 = __toESM(require("path"));
|
|
133
|
-
function msToTime(duration) {
|
|
134
|
-
const milliseconds = Math.floor(duration % 1e3);
|
|
135
|
-
const seconds = Math.floor(duration / 1e3 % 60);
|
|
136
|
-
const minutes = Math.floor(duration / (1e3 * 60) % 60);
|
|
137
|
-
const hours = Math.floor(duration / (1e3 * 60 * 60) % 24);
|
|
138
|
-
let result = "";
|
|
139
|
-
if (hours > 0) {
|
|
140
|
-
result += `${hours}h:`;
|
|
141
|
-
}
|
|
142
|
-
if (minutes > 0 || hours > 0) {
|
|
143
|
-
result += `${minutes < 10 ? "0" + minutes : minutes}m:`;
|
|
144
|
-
}
|
|
145
|
-
if (seconds > 0 || minutes > 0 || hours > 0) {
|
|
146
|
-
result += `${seconds < 10 ? "0" + seconds : seconds}s`;
|
|
147
|
-
}
|
|
148
|
-
if (milliseconds > 0 && !(seconds > 0 || minutes > 0 || hours > 0)) {
|
|
149
|
-
result += `${milliseconds}ms`;
|
|
150
|
-
} else if (milliseconds > 0) {
|
|
151
|
-
result += `:${milliseconds < 100 ? "0" + milliseconds : milliseconds}ms`;
|
|
152
|
-
}
|
|
153
|
-
return result;
|
|
154
|
-
}
|
|
155
|
-
function normalizeFilePath(filePath) {
|
|
156
|
-
const normalizedPath = import_path2.default.normalize(filePath);
|
|
157
|
-
return import_path2.default.basename(normalizedPath);
|
|
158
|
-
}
|
|
159
|
-
function formatDate(date) {
|
|
160
|
-
const day = String(date.getDate()).padStart(2, "0");
|
|
161
|
-
const month = date.toLocaleString("default", { month: "short" });
|
|
162
|
-
const year = date.getFullYear();
|
|
163
|
-
const time = date.toLocaleTimeString();
|
|
164
|
-
return `${day}-${month}-${year} ${time}`;
|
|
165
|
-
}
|
|
166
|
-
function safeStringify(obj, indent = 2) {
|
|
167
|
-
const cache = /* @__PURE__ */ new Set();
|
|
168
|
-
const json = JSON.stringify(
|
|
169
|
-
obj,
|
|
170
|
-
(key, value) => {
|
|
171
|
-
if (typeof value === "object" && value !== null) {
|
|
172
|
-
if (cache.has(value)) {
|
|
173
|
-
return;
|
|
174
|
-
}
|
|
175
|
-
cache.add(value);
|
|
176
|
-
}
|
|
177
|
-
return value;
|
|
178
|
-
},
|
|
179
|
-
indent
|
|
180
|
-
);
|
|
181
|
-
cache.clear();
|
|
182
|
-
return json;
|
|
183
|
-
}
|
|
184
|
-
function ensureHtmlExtension(filename) {
|
|
185
|
-
const ext = import_path2.default.extname(filename);
|
|
186
|
-
if (ext && ext.toLowerCase() === ".html") {
|
|
187
|
-
return filename;
|
|
188
|
-
}
|
|
189
|
-
return `${filename}.html`;
|
|
190
|
-
}
|
|
191
|
-
function escapeHtml(unsafe) {
|
|
192
|
-
if (typeof unsafe !== "string") {
|
|
193
|
-
return String(unsafe);
|
|
194
|
-
}
|
|
195
|
-
return unsafe.replace(/[&<"']/g, function(match) {
|
|
196
|
-
const escapeMap = {
|
|
197
|
-
"&": "&",
|
|
198
|
-
"<": "<",
|
|
199
|
-
">": ">",
|
|
200
|
-
'"': """,
|
|
201
|
-
"'": "'"
|
|
202
|
-
};
|
|
203
|
-
return escapeMap[match] || match;
|
|
204
|
-
});
|
|
205
|
-
}
|
|
206
|
-
function formatDateUTC(date) {
|
|
207
|
-
return date.toISOString();
|
|
208
|
-
}
|
|
209
|
-
function formatDateLocal(isoString) {
|
|
210
|
-
const date = new Date(isoString);
|
|
211
|
-
const options = {
|
|
212
|
-
year: "numeric",
|
|
213
|
-
month: "short",
|
|
214
|
-
day: "2-digit",
|
|
215
|
-
hour: "2-digit",
|
|
216
|
-
minute: "2-digit",
|
|
217
|
-
hour12: true,
|
|
218
|
-
timeZoneName: "shortOffset"
|
|
219
|
-
};
|
|
220
|
-
return new Intl.DateTimeFormat(void 0, options).format(date);
|
|
221
|
-
}
|
|
222
|
-
function formatDateNoTimezone(isoString) {
|
|
223
|
-
const date = new Date(isoString);
|
|
224
|
-
return date.toLocaleString("en-US", {
|
|
225
|
-
dateStyle: "medium",
|
|
226
|
-
timeStyle: "short"
|
|
227
|
-
});
|
|
228
|
-
}
|
|
229
|
-
|
|
230
137
|
// src/helpers/HTMLGenerator.ts
|
|
231
|
-
var import_fs2 = __toESM(require("fs"));
|
|
232
|
-
var import_handlebars = __toESM(require("handlebars"));
|
|
233
138
|
var HTMLGenerator = class {
|
|
234
139
|
constructor(ortoniConfig, dbManager) {
|
|
235
140
|
this.ortoniConfig = ortoniConfig;
|
|
236
|
-
this.registerHandlebarsHelpers();
|
|
237
|
-
this.registerPartials();
|
|
238
141
|
this.dbManager = dbManager;
|
|
239
142
|
}
|
|
240
|
-
async
|
|
143
|
+
async generateFinalReport(filteredResults, totalDuration, results, projectSet) {
|
|
241
144
|
const data = await this.prepareReportData(
|
|
242
145
|
filteredResults,
|
|
243
146
|
totalDuration,
|
|
244
147
|
results,
|
|
245
148
|
projectSet
|
|
246
149
|
);
|
|
247
|
-
|
|
248
|
-
import_path3.default.resolve(__dirname, "views", "main.hbs"),
|
|
249
|
-
"utf-8"
|
|
250
|
-
);
|
|
251
|
-
const template = import_handlebars.default.compile(templateSource);
|
|
252
|
-
return template({ ...data, inlineCss: cssContent });
|
|
150
|
+
return data;
|
|
253
151
|
}
|
|
254
152
|
async getReportData() {
|
|
255
153
|
return {
|
|
@@ -259,18 +157,6 @@ var HTMLGenerator = class {
|
|
|
259
157
|
slowTests: await this.dbManager.getSlowTests()
|
|
260
158
|
};
|
|
261
159
|
}
|
|
262
|
-
async chartTrendData() {
|
|
263
|
-
return {
|
|
264
|
-
labels: (await this.getReportData()).trends.map(
|
|
265
|
-
(t) => formatDateNoTimezone(t.run_date)
|
|
266
|
-
),
|
|
267
|
-
passed: (await this.getReportData()).trends.map((t) => t.passed),
|
|
268
|
-
failed: (await this.getReportData()).trends.map((t) => t.failed),
|
|
269
|
-
avgDuration: (await this.getReportData()).trends.map(
|
|
270
|
-
(t) => t.avg_duration
|
|
271
|
-
)
|
|
272
|
-
};
|
|
273
|
-
}
|
|
274
160
|
async prepareReportData(filteredResults, totalDuration, results, projectSet) {
|
|
275
161
|
const totalTests = filteredResults.length;
|
|
276
162
|
const passedTests = results.filter((r) => r.status === "passed").length;
|
|
@@ -288,8 +174,7 @@ var HTMLGenerator = class {
|
|
|
288
174
|
results,
|
|
289
175
|
projectSet
|
|
290
176
|
);
|
|
291
|
-
const
|
|
292
|
-
const localRunDate = formatDateLocal(utcRunDate);
|
|
177
|
+
const lastRunDate = (/* @__PURE__ */ new Date()).toLocaleString();
|
|
293
178
|
const testHistories = await Promise.all(
|
|
294
179
|
results.map(async (result) => {
|
|
295
180
|
const testId = `${result.filePath}:${result.projectName}:${result.title}`;
|
|
@@ -301,34 +186,42 @@ var HTMLGenerator = class {
|
|
|
301
186
|
})
|
|
302
187
|
);
|
|
303
188
|
return {
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
189
|
+
summary: {
|
|
190
|
+
overAllResult: {
|
|
191
|
+
pass: passedTests,
|
|
192
|
+
fail: failed,
|
|
193
|
+
skip: results.filter((r) => r.status === "skipped").length,
|
|
194
|
+
retry: results.filter((r) => r.retryAttemptCount).length,
|
|
195
|
+
flaky: flakyTests,
|
|
196
|
+
total: filteredResults.length
|
|
197
|
+
},
|
|
198
|
+
successRate,
|
|
199
|
+
lastRunDate,
|
|
200
|
+
totalDuration,
|
|
201
|
+
stats: this.extractProjectStats(projectResults)
|
|
202
|
+
},
|
|
203
|
+
testResult: {
|
|
204
|
+
tests: groupResults(this.ortoniConfig, results),
|
|
205
|
+
testHistories,
|
|
206
|
+
allTags: Array.from(allTags),
|
|
207
|
+
set: projectSet
|
|
208
|
+
},
|
|
209
|
+
userConfig: {
|
|
210
|
+
projectName: this.ortoniConfig.projectName,
|
|
211
|
+
authorName: this.ortoniConfig.authorName,
|
|
212
|
+
type: this.ortoniConfig.testType,
|
|
213
|
+
title: this.ortoniConfig.title
|
|
214
|
+
},
|
|
215
|
+
userMeta: {
|
|
216
|
+
meta: this.ortoniConfig.meta
|
|
217
|
+
},
|
|
218
|
+
preferences: {
|
|
219
|
+
logo: this.ortoniConfig.logo || void 0,
|
|
220
|
+
showProject: this.ortoniConfig.showProject || false
|
|
221
|
+
},
|
|
222
|
+
analytics: {
|
|
223
|
+
reportData: await this.getReportData()
|
|
224
|
+
}
|
|
332
225
|
};
|
|
333
226
|
}
|
|
334
227
|
calculateProjectResults(filteredResults, results, projectSet) {
|
|
@@ -346,7 +239,7 @@ var HTMLGenerator = class {
|
|
|
346
239
|
(r) => r.status === "failed" || r.status === "timedOut"
|
|
347
240
|
).length,
|
|
348
241
|
skippedTests: allProjectTests.filter((r) => r.status === "skipped").length,
|
|
349
|
-
retryTests: allProjectTests.filter((r) => r.
|
|
242
|
+
retryTests: allProjectTests.filter((r) => r.retryAttemptCount).length,
|
|
350
243
|
flakyTests: allProjectTests.filter((r) => r.status === "flaky").length,
|
|
351
244
|
totalTests: projectTests.length
|
|
352
245
|
};
|
|
@@ -363,59 +256,18 @@ var HTMLGenerator = class {
|
|
|
363
256
|
flakyTests: projectResults.map((result) => result.flakyTests)
|
|
364
257
|
};
|
|
365
258
|
}
|
|
366
|
-
registerHandlebarsHelpers() {
|
|
367
|
-
import_handlebars.default.registerHelper("joinWithSpace", (array) => array.join(" "));
|
|
368
|
-
import_handlebars.default.registerHelper("json", (context) => safeStringify(context));
|
|
369
|
-
import_handlebars.default.registerHelper(
|
|
370
|
-
"eq",
|
|
371
|
-
(actualStatus, expectedStatus) => actualStatus === expectedStatus
|
|
372
|
-
);
|
|
373
|
-
import_handlebars.default.registerHelper(
|
|
374
|
-
"includes",
|
|
375
|
-
(actualStatus, expectedStatus) => actualStatus.includes(expectedStatus)
|
|
376
|
-
);
|
|
377
|
-
import_handlebars.default.registerHelper("gr", (count) => count > 0);
|
|
378
|
-
import_handlebars.default.registerHelper("or", function(a3, b2) {
|
|
379
|
-
return a3 || b2;
|
|
380
|
-
});
|
|
381
|
-
import_handlebars.default.registerHelper("concat", function(...args) {
|
|
382
|
-
args.pop();
|
|
383
|
-
return args.join("");
|
|
384
|
-
});
|
|
385
|
-
}
|
|
386
|
-
registerPartials() {
|
|
387
|
-
[
|
|
388
|
-
"head",
|
|
389
|
-
"sidebar",
|
|
390
|
-
"testPanel",
|
|
391
|
-
"summaryCard",
|
|
392
|
-
"userInfo",
|
|
393
|
-
"project",
|
|
394
|
-
"testStatus",
|
|
395
|
-
"testIcons",
|
|
396
|
-
"analytics"
|
|
397
|
-
].forEach((partialName) => {
|
|
398
|
-
import_handlebars.default.registerPartial(
|
|
399
|
-
partialName,
|
|
400
|
-
import_fs2.default.readFileSync(
|
|
401
|
-
import_path3.default.resolve(__dirname, "views", `${partialName}.hbs`),
|
|
402
|
-
"utf-8"
|
|
403
|
-
)
|
|
404
|
-
);
|
|
405
|
-
});
|
|
406
|
-
}
|
|
407
259
|
};
|
|
408
260
|
|
|
409
261
|
// src/helpers/resultProcessor .ts
|
|
410
262
|
var import_ansi_to_html = __toESM(require("ansi-to-html"));
|
|
411
|
-
var
|
|
263
|
+
var import_path4 = __toESM(require("path"));
|
|
412
264
|
|
|
413
265
|
// src/utils/attachFiles.ts
|
|
414
|
-
var
|
|
415
|
-
var
|
|
266
|
+
var import_path2 = __toESM(require("path"));
|
|
267
|
+
var import_fs3 = __toESM(require("fs"));
|
|
416
268
|
|
|
417
269
|
// src/helpers/markdownConverter.ts
|
|
418
|
-
var
|
|
270
|
+
var import_fs2 = __toESM(require("fs"));
|
|
419
271
|
|
|
420
272
|
// node_modules/marked/lib/marked.esm.js
|
|
421
273
|
function M() {
|
|
@@ -1540,108 +1392,44 @@ var Ft = T.parse;
|
|
|
1540
1392
|
var Qt = b.lex;
|
|
1541
1393
|
|
|
1542
1394
|
// src/helpers/markdownConverter.ts
|
|
1543
|
-
function convertMarkdownToHtml(markdownPath, htmlOutputPath
|
|
1544
|
-
const hasMarkdown =
|
|
1545
|
-
const markdownContent = hasMarkdown ?
|
|
1395
|
+
function convertMarkdownToHtml(markdownPath, htmlOutputPath) {
|
|
1396
|
+
const hasMarkdown = import_fs2.default.existsSync(markdownPath);
|
|
1397
|
+
const markdownContent = hasMarkdown ? import_fs2.default.readFileSync(markdownPath, "utf-8") : "";
|
|
1546
1398
|
const markdownHtml = hasMarkdown ? k(markdownContent) : "";
|
|
1547
|
-
const
|
|
1548
|
-
|
|
1549
|
-
<div>
|
|
1550
|
-
<pre><code>${step.snippet}</code></pre>
|
|
1551
|
-
${step.location ? `<p><em>Location: ${escapeHtml(step.location)}</em></p>` : ""}
|
|
1552
|
-
</div>`
|
|
1553
|
-
).join("\n");
|
|
1554
|
-
const errorHtml = resultError.map((error) => `<pre><code>${error}</code></pre>`).join("\n");
|
|
1555
|
-
const fullHtml = `
|
|
1556
|
-
<!DOCTYPE html>
|
|
1557
|
-
<html lang="en">
|
|
1558
|
-
<head>
|
|
1559
|
-
<meta charset="UTF-8" />
|
|
1560
|
-
<title>Ortoni Error Report</title>
|
|
1561
|
-
<style>
|
|
1562
|
-
body { font-family: sans-serif; padding: 2rem; line-height: 1.6; max-width: 900px; margin: auto; }
|
|
1563
|
-
code, pre { background: #f4f4f4; padding: 0.5rem; border-radius: 5px; display: block; overflow-x: auto; }
|
|
1564
|
-
h1, h2, h3 { color: #444; }
|
|
1565
|
-
hr { margin: 2em 0; }
|
|
1566
|
-
#copyBtn {
|
|
1567
|
-
background-color: #007acc;
|
|
1568
|
-
color: white;
|
|
1569
|
-
border: none;
|
|
1570
|
-
padding: 0.5rem 1rem;
|
|
1571
|
-
margin-bottom: 1rem;
|
|
1572
|
-
border-radius: 5px;
|
|
1573
|
-
cursor: pointer;
|
|
1574
|
-
}
|
|
1575
|
-
#copyBtn:hover {
|
|
1576
|
-
background-color: #005fa3;
|
|
1577
|
-
}
|
|
1578
|
-
</style>
|
|
1579
|
-
</head>
|
|
1580
|
-
<body>
|
|
1581
|
-
<button id="copyBtn">\u{1F4CB} Copy All</button>
|
|
1582
|
-
<script>
|
|
1583
|
-
document.getElementById("copyBtn").addEventListener("click", () => {
|
|
1584
|
-
const content = document.getElementById("markdownContent").innerText;
|
|
1585
|
-
navigator.clipboard.writeText(content).then(() => {
|
|
1586
|
-
// change button text to indicate success
|
|
1587
|
-
const button = document.getElementById("copyBtn");
|
|
1588
|
-
button.textContent = "\u2705 Copied!";
|
|
1589
|
-
setTimeout(() => {
|
|
1590
|
-
button.textContent = "\u{1F4CB} Copy All"
|
|
1591
|
-
}, 2000);
|
|
1592
|
-
}).catch(err => {
|
|
1593
|
-
console.error("Failed to copy text: ", err);
|
|
1594
|
-
alert("Failed to copy text. Please try manually.");
|
|
1595
|
-
});
|
|
1596
|
-
});
|
|
1597
|
-
</script>
|
|
1598
|
-
<div id="markdownContent">
|
|
1599
|
-
<h1>Instructions</h1>
|
|
1600
|
-
<ul>
|
|
1601
|
-
<li>Following Playwright test failed.</li>
|
|
1602
|
-
<li>Explain why, be concise, respect Playwright best practices.</li>
|
|
1603
|
-
<li>Provide a snippet of code with the fix, if possible.</li>
|
|
1604
|
-
</ul>
|
|
1605
|
-
<h1>Error Details</h1>
|
|
1606
|
-
${errorHtml || "<p>No errors found.</p>"}
|
|
1607
|
-
${stepsHtml || "<p>No step data available.</p>"}
|
|
1608
|
-
${markdownHtml || ""}
|
|
1609
|
-
</div>
|
|
1610
|
-
</body>
|
|
1611
|
-
</html>
|
|
1612
|
-
`;
|
|
1613
|
-
import_fs3.default.writeFileSync(htmlOutputPath, fullHtml, "utf-8");
|
|
1399
|
+
const drawerHtml = `${markdownHtml || ""}`;
|
|
1400
|
+
import_fs2.default.writeFileSync(htmlOutputPath, drawerHtml.trim(), "utf-8");
|
|
1614
1401
|
if (hasMarkdown) {
|
|
1615
|
-
|
|
1402
|
+
import_fs2.default.unlinkSync(markdownPath);
|
|
1616
1403
|
}
|
|
1617
1404
|
}
|
|
1618
1405
|
|
|
1619
1406
|
// src/utils/attachFiles.ts
|
|
1620
1407
|
function attachFiles(subFolder, result, testResult, config, steps, errors) {
|
|
1621
1408
|
const folderPath = config.folderPath || "ortoni-report";
|
|
1622
|
-
const attachmentsFolder =
|
|
1409
|
+
const attachmentsFolder = import_path2.default.join(
|
|
1623
1410
|
folderPath,
|
|
1624
1411
|
"ortoni-data",
|
|
1625
1412
|
"attachments",
|
|
1626
1413
|
subFolder
|
|
1627
1414
|
);
|
|
1628
|
-
if (!
|
|
1629
|
-
|
|
1415
|
+
if (!import_fs3.default.existsSync(attachmentsFolder)) {
|
|
1416
|
+
import_fs3.default.mkdirSync(attachmentsFolder, { recursive: true });
|
|
1630
1417
|
}
|
|
1631
1418
|
if (!result.attachments) return;
|
|
1632
1419
|
const { base64Image } = config;
|
|
1633
1420
|
testResult.screenshots = [];
|
|
1421
|
+
testResult.videoPath = [];
|
|
1634
1422
|
result.attachments.forEach((attachment) => {
|
|
1635
1423
|
const { contentType, name, path: attachmentPath, body } = attachment;
|
|
1636
1424
|
if (!attachmentPath && !body) return;
|
|
1637
|
-
const fileName = attachmentPath ?
|
|
1638
|
-
const relativePath =
|
|
1425
|
+
const fileName = attachmentPath ? import_path2.default.basename(attachmentPath) : `${name}.${getFileExtension(contentType)}`;
|
|
1426
|
+
const relativePath = import_path2.default.join(
|
|
1639
1427
|
"ortoni-data",
|
|
1640
1428
|
"attachments",
|
|
1641
1429
|
subFolder,
|
|
1642
1430
|
fileName
|
|
1643
1431
|
);
|
|
1644
|
-
const fullPath =
|
|
1432
|
+
const fullPath = import_path2.default.join(attachmentsFolder, fileName);
|
|
1645
1433
|
if (contentType === "image/png") {
|
|
1646
1434
|
handleImage(
|
|
1647
1435
|
attachmentPath,
|
|
@@ -1684,13 +1472,13 @@ function handleImage(attachmentPath, body, base64Image, fullPath, relativePath,
|
|
|
1684
1472
|
let screenshotPath = "";
|
|
1685
1473
|
if (attachmentPath) {
|
|
1686
1474
|
try {
|
|
1687
|
-
const screenshotContent =
|
|
1475
|
+
const screenshotContent = import_fs3.default.readFileSync(
|
|
1688
1476
|
attachmentPath,
|
|
1689
1477
|
base64Image ? "base64" : void 0
|
|
1690
1478
|
);
|
|
1691
1479
|
screenshotPath = base64Image ? `data:image/png;base64,${screenshotContent}` : relativePath;
|
|
1692
1480
|
if (!base64Image) {
|
|
1693
|
-
|
|
1481
|
+
import_fs3.default.copyFileSync(attachmentPath, fullPath);
|
|
1694
1482
|
}
|
|
1695
1483
|
} catch (error) {
|
|
1696
1484
|
console.error(
|
|
@@ -1707,13 +1495,17 @@ function handleImage(attachmentPath, body, base64Image, fullPath, relativePath,
|
|
|
1707
1495
|
}
|
|
1708
1496
|
function handleAttachment(attachmentPath, fullPath, relativePath, resultKey, testResult, steps, errors) {
|
|
1709
1497
|
if (attachmentPath) {
|
|
1710
|
-
|
|
1711
|
-
|
|
1498
|
+
import_fs3.default.copyFileSync(attachmentPath, fullPath);
|
|
1499
|
+
if (resultKey === "videoPath") {
|
|
1500
|
+
testResult[resultKey]?.push(relativePath);
|
|
1501
|
+
} else if (resultKey === "tracePath") {
|
|
1502
|
+
testResult[resultKey] = relativePath;
|
|
1503
|
+
}
|
|
1712
1504
|
}
|
|
1713
1505
|
if (resultKey === "markdownPath" && errors) {
|
|
1714
1506
|
const htmlPath = fullPath.replace(/\.md$/, ".html");
|
|
1715
1507
|
const htmlRelativePath = relativePath.replace(/\.md$/, ".html");
|
|
1716
|
-
convertMarkdownToHtml(fullPath, htmlPath
|
|
1508
|
+
convertMarkdownToHtml(fullPath, htmlPath);
|
|
1717
1509
|
testResult[resultKey] = htmlRelativePath;
|
|
1718
1510
|
return;
|
|
1719
1511
|
}
|
|
@@ -1728,6 +1520,61 @@ function getFileExtension(contentType) {
|
|
|
1728
1520
|
return extensions[contentType] || "unknown";
|
|
1729
1521
|
}
|
|
1730
1522
|
|
|
1523
|
+
// src/utils/utils.ts
|
|
1524
|
+
var import_path3 = __toESM(require("path"));
|
|
1525
|
+
function normalizeFilePath(filePath) {
|
|
1526
|
+
const normalizedPath = import_path3.default.normalize(filePath);
|
|
1527
|
+
return import_path3.default.basename(normalizedPath);
|
|
1528
|
+
}
|
|
1529
|
+
function ensureHtmlExtension(filename) {
|
|
1530
|
+
const ext = import_path3.default.extname(filename);
|
|
1531
|
+
if (ext && ext.toLowerCase() === ".html") {
|
|
1532
|
+
return filename;
|
|
1533
|
+
}
|
|
1534
|
+
return `${filename}.html`;
|
|
1535
|
+
}
|
|
1536
|
+
function escapeHtml(unsafe) {
|
|
1537
|
+
if (typeof unsafe !== "string") {
|
|
1538
|
+
return String(unsafe);
|
|
1539
|
+
}
|
|
1540
|
+
return unsafe.replace(/[&<"']/g, function(match) {
|
|
1541
|
+
const escapeMap = {
|
|
1542
|
+
"&": "&",
|
|
1543
|
+
"<": "<",
|
|
1544
|
+
">": ">",
|
|
1545
|
+
'"': """,
|
|
1546
|
+
"'": "'"
|
|
1547
|
+
};
|
|
1548
|
+
return escapeMap[match] || match;
|
|
1549
|
+
});
|
|
1550
|
+
}
|
|
1551
|
+
function formatDateLocal(dateInput) {
|
|
1552
|
+
const date = typeof dateInput === "string" ? new Date(dateInput) : dateInput;
|
|
1553
|
+
const options = {
|
|
1554
|
+
year: "numeric",
|
|
1555
|
+
month: "short",
|
|
1556
|
+
day: "2-digit",
|
|
1557
|
+
hour: "2-digit",
|
|
1558
|
+
minute: "2-digit",
|
|
1559
|
+
hour12: true,
|
|
1560
|
+
timeZoneName: "short"
|
|
1561
|
+
// or "Asia/Kolkata"
|
|
1562
|
+
};
|
|
1563
|
+
return new Intl.DateTimeFormat(void 0, options).format(date);
|
|
1564
|
+
}
|
|
1565
|
+
function extractSuites(titlePath) {
|
|
1566
|
+
const tagPattern = /@[\w]+/g;
|
|
1567
|
+
const suiteParts = titlePath.slice(3, titlePath.length - 1).map((p) => p.replace(tagPattern, "").trim());
|
|
1568
|
+
return {
|
|
1569
|
+
hierarchy: suiteParts.join(" > "),
|
|
1570
|
+
// full hierarchy
|
|
1571
|
+
topLevelSuite: suiteParts[0] ?? "",
|
|
1572
|
+
// first suite
|
|
1573
|
+
parentSuite: suiteParts[suiteParts.length - 1] ?? ""
|
|
1574
|
+
// last suite
|
|
1575
|
+
};
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1731
1578
|
// src/helpers/resultProcessor .ts
|
|
1732
1579
|
var TestResultProcessor = class {
|
|
1733
1580
|
constructor(projectRoot) {
|
|
@@ -1743,19 +1590,21 @@ var TestResultProcessor = class {
|
|
|
1743
1590
|
const tagPattern = /@[\w]+/g;
|
|
1744
1591
|
const title = test.title.replace(tagPattern, "").trim();
|
|
1745
1592
|
const suite = test.titlePath()[3].replace(tagPattern, "").trim();
|
|
1593
|
+
const suiteAndTitle = extractSuites(test.titlePath());
|
|
1594
|
+
const suiteHierarchy = suiteAndTitle.hierarchy;
|
|
1746
1595
|
const testResult = {
|
|
1747
|
-
|
|
1596
|
+
suiteHierarchy,
|
|
1597
|
+
key: test.id,
|
|
1748
1598
|
annotations: test.annotations,
|
|
1749
1599
|
testTags: test.tags,
|
|
1750
1600
|
location: `${filePath}:${location.line}:${location.column}`,
|
|
1751
|
-
|
|
1752
|
-
isRetry: result.retry,
|
|
1601
|
+
retryAttemptCount: result.retry,
|
|
1753
1602
|
projectName,
|
|
1754
1603
|
suite,
|
|
1755
1604
|
title,
|
|
1756
1605
|
status,
|
|
1757
1606
|
flaky: test.outcome(),
|
|
1758
|
-
duration:
|
|
1607
|
+
duration: result.duration,
|
|
1759
1608
|
errors: result.errors.map(
|
|
1760
1609
|
(e) => this.ansiToHtml.toHtml(escapeHtml(e.stack || e.toString()))
|
|
1761
1610
|
),
|
|
@@ -1767,7 +1616,8 @@ var TestResultProcessor = class {
|
|
|
1767
1616
|
),
|
|
1768
1617
|
filePath,
|
|
1769
1618
|
filters: projectSet,
|
|
1770
|
-
base64Image: ortoniConfig.base64Image
|
|
1619
|
+
base64Image: ortoniConfig.base64Image,
|
|
1620
|
+
testId: `${filePath}:${projectName}:${title}`
|
|
1771
1621
|
};
|
|
1772
1622
|
attachFiles(
|
|
1773
1623
|
test.id,
|
|
@@ -1781,7 +1631,7 @@ var TestResultProcessor = class {
|
|
|
1781
1631
|
}
|
|
1782
1632
|
processSteps(steps) {
|
|
1783
1633
|
return steps.map((step) => {
|
|
1784
|
-
const stepLocation = step.location ? `${
|
|
1634
|
+
const stepLocation = step.location ? `${import_path4.default.relative(this.projectRoot, step.location.file)}:${step.location.line}:${step.location.column}` : "";
|
|
1785
1635
|
return {
|
|
1786
1636
|
snippet: this.ansiToHtml.toHtml(escapeHtml(step.error?.snippet || "")),
|
|
1787
1637
|
title: step.title,
|
|
@@ -1793,14 +1643,14 @@ var TestResultProcessor = class {
|
|
|
1793
1643
|
|
|
1794
1644
|
// src/utils/expressServer.ts
|
|
1795
1645
|
var import_express = __toESM(require("express"));
|
|
1796
|
-
var
|
|
1646
|
+
var import_path5 = __toESM(require("path"));
|
|
1797
1647
|
var import_child_process = require("child_process");
|
|
1798
1648
|
function startReportServer(reportFolder, reportFilename, port = 2004, open2) {
|
|
1799
1649
|
const app = (0, import_express.default)();
|
|
1800
1650
|
app.use(import_express.default.static(reportFolder));
|
|
1801
1651
|
app.get("/", (_req, res) => {
|
|
1802
1652
|
try {
|
|
1803
|
-
res.sendFile(
|
|
1653
|
+
res.sendFile(import_path5.default.resolve(reportFolder, reportFilename));
|
|
1804
1654
|
} catch (error) {
|
|
1805
1655
|
console.error("Ortoni-Report: Error sending report file:", error);
|
|
1806
1656
|
res.status(500).send("Error loading report");
|
|
@@ -1907,7 +1757,7 @@ var DatabaseManager = class {
|
|
|
1907
1757
|
run_id INTEGER,
|
|
1908
1758
|
test_id TEXT,
|
|
1909
1759
|
status TEXT,
|
|
1910
|
-
duration
|
|
1760
|
+
duration INTEGER, -- store duration as raw ms
|
|
1911
1761
|
error_message TEXT,
|
|
1912
1762
|
FOREIGN KEY (run_id) REFERENCES test_runs (id)
|
|
1913
1763
|
);
|
|
@@ -1967,6 +1817,7 @@ var DatabaseManager = class {
|
|
|
1967
1817
|
`${result.filePath}:${result.projectName}:${result.title}`,
|
|
1968
1818
|
result.status,
|
|
1969
1819
|
result.duration,
|
|
1820
|
+
// store raw ms
|
|
1970
1821
|
result.errors.join("\n")
|
|
1971
1822
|
]);
|
|
1972
1823
|
}
|
|
@@ -2033,7 +1884,7 @@ var DatabaseManager = class {
|
|
|
2033
1884
|
(SELECT COUNT(*) FROM test_results) as totalTests,
|
|
2034
1885
|
(SELECT COUNT(*) FROM test_results WHERE status = 'passed') as passed,
|
|
2035
1886
|
(SELECT COUNT(*) FROM test_results WHERE status = 'failed') as failed,
|
|
2036
|
-
(SELECT AVG(
|
|
1887
|
+
(SELECT AVG(duration) FROM test_results) as avgDuration
|
|
2037
1888
|
`);
|
|
2038
1889
|
const passRate = summary.totalTests ? (summary.passed / summary.totalTests * 100).toFixed(2) : 0;
|
|
2039
1890
|
return {
|
|
@@ -2043,6 +1894,7 @@ var DatabaseManager = class {
|
|
|
2043
1894
|
failed: summary.failed,
|
|
2044
1895
|
passRate: parseFloat(passRate.toString()),
|
|
2045
1896
|
avgDuration: Math.round(summary.avgDuration || 0)
|
|
1897
|
+
// raw ms avg
|
|
2046
1898
|
};
|
|
2047
1899
|
} catch (error) {
|
|
2048
1900
|
console.error("OrtoniReport: Error getting summary data:", error);
|
|
@@ -2067,7 +1919,7 @@ var DatabaseManager = class {
|
|
|
2067
1919
|
SELECT trun.run_date,
|
|
2068
1920
|
SUM(CASE WHEN tr.status = 'passed' THEN 1 ELSE 0 END) AS passed,
|
|
2069
1921
|
SUM(CASE WHEN tr.status = 'failed' THEN 1 ELSE 0 END) AS failed,
|
|
2070
|
-
AVG(
|
|
1922
|
+
AVG(tr.duration) AS avg_duration
|
|
2071
1923
|
FROM test_results tr
|
|
2072
1924
|
JOIN test_runs trun ON tr.run_id = trun.id
|
|
2073
1925
|
GROUP BY trun.run_date
|
|
@@ -2080,6 +1932,7 @@ var DatabaseManager = class {
|
|
|
2080
1932
|
...row,
|
|
2081
1933
|
run_date: formatDateLocal(row.run_date),
|
|
2082
1934
|
avg_duration: Math.round(row.avg_duration || 0)
|
|
1935
|
+
// raw ms avg
|
|
2083
1936
|
}));
|
|
2084
1937
|
} catch (error) {
|
|
2085
1938
|
console.error("OrtoniReport: Error getting trends data:", error);
|
|
@@ -2097,7 +1950,8 @@ var DatabaseManager = class {
|
|
|
2097
1950
|
SELECT
|
|
2098
1951
|
test_id,
|
|
2099
1952
|
COUNT(*) AS total,
|
|
2100
|
-
SUM(CASE WHEN status = 'flaky' THEN 1 ELSE 0 END) AS flaky
|
|
1953
|
+
SUM(CASE WHEN status = 'flaky' THEN 1 ELSE 0 END) AS flaky,
|
|
1954
|
+
AVG(duration) AS avg_duration
|
|
2101
1955
|
FROM test_results
|
|
2102
1956
|
GROUP BY test_id
|
|
2103
1957
|
HAVING flaky > 0
|
|
@@ -2121,7 +1975,7 @@ var DatabaseManager = class {
|
|
|
2121
1975
|
`
|
|
2122
1976
|
SELECT
|
|
2123
1977
|
test_id,
|
|
2124
|
-
AVG(
|
|
1978
|
+
AVG(duration) AS avg_duration
|
|
2125
1979
|
FROM test_results
|
|
2126
1980
|
GROUP BY test_id
|
|
2127
1981
|
ORDER BY avg_duration DESC
|
|
@@ -2132,6 +1986,7 @@ var DatabaseManager = class {
|
|
|
2132
1986
|
return rows.map((row) => ({
|
|
2133
1987
|
test_id: row.test_id,
|
|
2134
1988
|
avg_duration: Math.round(row.avg_duration || 0)
|
|
1989
|
+
// raw ms avg
|
|
2135
1990
|
}));
|
|
2136
1991
|
} catch (error) {
|
|
2137
1992
|
console.error("OrtoniReport: Error getting slow tests:", error);
|
|
@@ -2141,7 +1996,7 @@ var DatabaseManager = class {
|
|
|
2141
1996
|
};
|
|
2142
1997
|
|
|
2143
1998
|
// src/ortoni-report.ts
|
|
2144
|
-
var
|
|
1999
|
+
var import_path6 = __toESM(require("path"));
|
|
2145
2000
|
var OrtoniReport = class {
|
|
2146
2001
|
constructor(ortoniConfig = {}) {
|
|
2147
2002
|
this.ortoniConfig = ortoniConfig;
|
|
@@ -2172,8 +2027,9 @@ var OrtoniReport = class {
|
|
|
2172
2027
|
this.testResultProcessor = new TestResultProcessor(config.rootDir);
|
|
2173
2028
|
this.fileManager.ensureReportDirectory();
|
|
2174
2029
|
await this.dbManager.initialize(
|
|
2175
|
-
|
|
2030
|
+
import_path6.default.join(this.folderPath, "ortoni-data-history.sqlite")
|
|
2176
2031
|
);
|
|
2032
|
+
this.config = config?.shard;
|
|
2177
2033
|
}
|
|
2178
2034
|
onStdOut(chunk, _test, _result) {
|
|
2179
2035
|
if (this.reportsCount == 1 && this.showConsoleLogs) {
|
|
@@ -2206,23 +2062,35 @@ var OrtoniReport = class {
|
|
|
2206
2062
|
this.overAllStatus = result.status;
|
|
2207
2063
|
if (this.shouldGenerateReport) {
|
|
2208
2064
|
const filteredResults = this.results.filter(
|
|
2209
|
-
(r) => r.status !== "skipped"
|
|
2065
|
+
(r) => r.status !== "skipped"
|
|
2210
2066
|
);
|
|
2211
|
-
const totalDuration =
|
|
2212
|
-
|
|
2067
|
+
const totalDuration = result.duration;
|
|
2068
|
+
if (this.config && this.config.total > 1) {
|
|
2069
|
+
const shard = this.config;
|
|
2070
|
+
const shardFile = `ortoni-shard-${shard.current}-of-${shard.total}.json`;
|
|
2071
|
+
const shardData = {
|
|
2072
|
+
status: result.status,
|
|
2073
|
+
duration: totalDuration,
|
|
2074
|
+
results: this.results,
|
|
2075
|
+
projectSet: Array.from(this.projectSet)
|
|
2076
|
+
};
|
|
2077
|
+
this.fileManager.writeRawFile(shardFile, shardData);
|
|
2078
|
+
console.log(`\u{1F4E6} OrtoniReport wrote shard file: ${shardFile}`);
|
|
2079
|
+
this.shouldGenerateReport = false;
|
|
2080
|
+
return;
|
|
2081
|
+
}
|
|
2213
2082
|
const runId = await this.dbManager.saveTestRun();
|
|
2214
2083
|
if (runId !== null) {
|
|
2215
2084
|
await this.dbManager.saveTestResults(runId, this.results);
|
|
2216
|
-
const
|
|
2085
|
+
const finalReportData = await this.htmlGenerator.generateFinalReport(
|
|
2217
2086
|
filteredResults,
|
|
2218
2087
|
totalDuration,
|
|
2219
|
-
cssContent,
|
|
2220
2088
|
this.results,
|
|
2221
2089
|
this.projectSet
|
|
2222
2090
|
);
|
|
2223
2091
|
this.outputPath = this.fileManager.writeReportFile(
|
|
2224
2092
|
this.outputFilename,
|
|
2225
|
-
|
|
2093
|
+
finalReportData
|
|
2226
2094
|
);
|
|
2227
2095
|
} else {
|
|
2228
2096
|
console.error("OrtoniReport: Error saving test run to database");
|