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.
Files changed (37) hide show
  1. package/changelog.md +10 -0
  2. package/dist/chunk-L6VOLEP2.mjs +752 -0
  3. package/dist/cli.js +27 -21
  4. package/dist/cli.mjs +1 -1
  5. package/dist/helpers/HTMLGenerator.d.ts +89 -0
  6. package/dist/helpers/HTMLGenerator.js +164 -0
  7. package/dist/helpers/databaseManager.d.ts +35 -0
  8. package/dist/helpers/databaseManager.js +267 -0
  9. package/dist/helpers/fileManager.d.ts +8 -0
  10. package/dist/helpers/fileManager.js +60 -0
  11. package/dist/helpers/markdownConverter.d.ts +1 -0
  12. package/dist/helpers/markdownConverter.js +14 -0
  13. package/dist/helpers/resultProcessor.d.ts +10 -0
  14. package/dist/helpers/resultProcessor.js +60 -0
  15. package/dist/helpers/serverManager.d.ts +6 -0
  16. package/dist/helpers/serverManager.js +15 -0
  17. package/dist/helpers/templateLoader.d.ts +15 -0
  18. package/dist/helpers/templateLoader.js +88 -0
  19. package/dist/index.html +2 -2
  20. package/dist/mergeData.d.ts +13 -0
  21. package/dist/mergeData.js +182 -0
  22. package/dist/ortoni-report.js +72 -64
  23. package/dist/ortoni-report.mjs +2 -2
  24. package/dist/types/reporterConfig.d.ts +86 -0
  25. package/dist/types/reporterConfig.js +1 -0
  26. package/dist/types/testResults.d.ts +31 -0
  27. package/dist/types/testResults.js +1 -0
  28. package/dist/utils/attachFiles.d.ts +4 -0
  29. package/dist/utils/attachFiles.js +87 -0
  30. package/dist/utils/expressServer.d.ts +1 -0
  31. package/dist/utils/expressServer.js +61 -0
  32. package/dist/utils/groupProjects.d.ts +3 -0
  33. package/dist/utils/groupProjects.js +30 -0
  34. package/dist/utils/utils.d.ts +15 -0
  35. package/dist/utils/utils.js +93 -0
  36. package/package.json +1 -1
  37. package/readme.md +1 -1
@@ -0,0 +1,60 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { readBundledTemplate } from "./templateLoader";
4
+ export class FileManager {
5
+ constructor(folderPath) {
6
+ this.folderPath = folderPath;
7
+ }
8
+ ensureReportDirectory() {
9
+ const ortoniDataFolder = path.join(this.folderPath, "ortoni-data");
10
+ if (!fs.existsSync(this.folderPath)) {
11
+ fs.mkdirSync(this.folderPath, { recursive: true });
12
+ }
13
+ else {
14
+ if (fs.existsSync(ortoniDataFolder)) {
15
+ fs.rmSync(ortoniDataFolder, { recursive: true, force: true });
16
+ }
17
+ }
18
+ }
19
+ async writeReportFile(filename, data) {
20
+ let html = await readBundledTemplate();
21
+ // let html = fs.readFileSync(templatePath, "utf-8");
22
+ const reportJSON = JSON.stringify({
23
+ data,
24
+ });
25
+ html = html.replace("__ORTONI_TEST_REPORTDATA__", reportJSON);
26
+ const outputPath = path.join(process.cwd(), this.folderPath, filename);
27
+ fs.writeFileSync(outputPath, html);
28
+ return outputPath;
29
+ }
30
+ writeRawFile(filename, data) {
31
+ const outputPath = path.join(process.cwd(), this.folderPath, filename);
32
+ fs.mkdirSync(path.dirname(outputPath), { recursive: true });
33
+ const content = typeof data === "string" ? data : JSON.stringify(data, null, 2);
34
+ fs.writeFileSync(outputPath, content, "utf-8");
35
+ return outputPath;
36
+ }
37
+ copyTraceViewerAssets(skip) {
38
+ if (skip)
39
+ return;
40
+ const traceViewerFolder = path.join(require.resolve("playwright-core"), "..", "lib", "vite", "traceViewer");
41
+ const traceViewerTargetFolder = path.join(this.folderPath, "trace");
42
+ const traceViewerAssetsTargetFolder = path.join(traceViewerTargetFolder, "assets");
43
+ fs.mkdirSync(traceViewerAssetsTargetFolder, { recursive: true });
44
+ // Copy main trace viewer files
45
+ for (const file of fs.readdirSync(traceViewerFolder)) {
46
+ if (file.endsWith(".map") ||
47
+ file.includes("watch") ||
48
+ file.includes("assets"))
49
+ continue;
50
+ fs.copyFileSync(path.join(traceViewerFolder, file), path.join(traceViewerTargetFolder, file));
51
+ }
52
+ // Copy assets
53
+ const assetsFolder = path.join(traceViewerFolder, "assets");
54
+ for (const file of fs.readdirSync(assetsFolder)) {
55
+ if (file.endsWith(".map") || file.includes("xtermModule"))
56
+ continue;
57
+ fs.copyFileSync(path.join(assetsFolder, file), path.join(traceViewerAssetsTargetFolder, file));
58
+ }
59
+ }
60
+ }
@@ -0,0 +1 @@
1
+ export declare function convertMarkdownToHtml(markdownPath: string, htmlOutputPath: string): void;
@@ -0,0 +1,14 @@
1
+ import fs from "fs";
2
+ import { marked } from "marked";
3
+ export function convertMarkdownToHtml(markdownPath, htmlOutputPath) {
4
+ const hasMarkdown = fs.existsSync(markdownPath);
5
+ const markdownContent = hasMarkdown
6
+ ? fs.readFileSync(markdownPath, "utf-8")
7
+ : "";
8
+ const markdownHtml = hasMarkdown ? marked(markdownContent) : "";
9
+ const drawerHtml = `${markdownHtml || ""}`;
10
+ fs.writeFileSync(htmlOutputPath, drawerHtml.trim(), "utf-8");
11
+ if (hasMarkdown) {
12
+ fs.unlinkSync(markdownPath);
13
+ }
14
+ }
@@ -0,0 +1,10 @@
1
+ import { TestCase, TestResult } from "@playwright/test/reporter";
2
+ import { TestResultData } from "../types/testResults";
3
+ import { OrtoniReportConfig } from "../types/reporterConfig";
4
+ export declare class TestResultProcessor {
5
+ private ansiToHtml;
6
+ private projectRoot;
7
+ constructor(projectRoot: string);
8
+ processTestResult(test: TestCase, result: TestResult, projectSet: Set<string>, ortoniConfig: OrtoniReportConfig): TestResultData;
9
+ private processSteps;
10
+ }
@@ -0,0 +1,60 @@
1
+ import AnsiToHtml from "ansi-to-html";
2
+ import path from "path";
3
+ import { attachFiles } from "../utils/attachFiles";
4
+ import { normalizeFilePath, escapeHtml, extractSuites } from "../utils/utils";
5
+ export class TestResultProcessor {
6
+ constructor(projectRoot) {
7
+ this.ansiToHtml = new AnsiToHtml({ fg: "var(--snippet-color)" });
8
+ this.projectRoot = projectRoot;
9
+ }
10
+ processTestResult(test, result, projectSet, ortoniConfig) {
11
+ const status = test.outcome() === "flaky" ? "flaky" : result.status;
12
+ const projectName = test.titlePath()[1];
13
+ projectSet.add(projectName);
14
+ const location = test.location;
15
+ const filePath = normalizeFilePath(test.titlePath()[2]);
16
+ const tagPattern = /@[\w]+/g;
17
+ const title = test.title.replace(tagPattern, "").trim();
18
+ const suite = test.titlePath()[3].replace(tagPattern, "").trim();
19
+ const suiteAndTitle = extractSuites(test.titlePath());
20
+ const suiteHierarchy = suiteAndTitle.hierarchy;
21
+ const testResult = {
22
+ suiteHierarchy,
23
+ key: test.id,
24
+ annotations: test.annotations,
25
+ testTags: test.tags,
26
+ location: `${filePath}:${location.line}:${location.column}`,
27
+ retryAttemptCount: result.retry,
28
+ projectName: projectName,
29
+ suite,
30
+ title,
31
+ status,
32
+ flaky: test.outcome(),
33
+ duration: result.duration,
34
+ errors: result.errors.map((e) => this.ansiToHtml.toHtml(escapeHtml(e.stack || e.toString()))),
35
+ steps: this.processSteps(result.steps),
36
+ logs: this.ansiToHtml.toHtml(escapeHtml(result.stdout
37
+ .concat(result.stderr)
38
+ .map((log) => log)
39
+ .join("\n"))),
40
+ filePath: filePath,
41
+ filters: projectSet,
42
+ base64Image: ortoniConfig.base64Image,
43
+ testId: `${filePath}:${projectName}:${title}`,
44
+ };
45
+ attachFiles(path.join(test.id, `retry-${result.retry}`), result, testResult, ortoniConfig, testResult.steps, testResult.errors);
46
+ return testResult;
47
+ }
48
+ processSteps(steps) {
49
+ return steps.map((step) => {
50
+ const stepLocation = step.location
51
+ ? `${path.relative(this.projectRoot, step.location.file)}:${step.location.line}:${step.location.column}`
52
+ : "";
53
+ return {
54
+ snippet: this.ansiToHtml.toHtml(escapeHtml(step.error?.snippet || "")),
55
+ title: step.title,
56
+ location: step.error ? stepLocation : "",
57
+ };
58
+ });
59
+ }
60
+ }
@@ -0,0 +1,6 @@
1
+ import { OrtoniReportConfig } from "../types/reporterConfig";
2
+ export declare class ServerManager {
3
+ private ortoniConfig;
4
+ constructor(ortoniConfig: OrtoniReportConfig);
5
+ startServer(folderPath: string, outputFilename: string, overAllStatus: string | undefined): Promise<void>;
6
+ }
@@ -0,0 +1,15 @@
1
+ import { startReportServer } from "../utils/expressServer";
2
+ export class ServerManager {
3
+ constructor(ortoniConfig) {
4
+ this.ortoniConfig = ortoniConfig;
5
+ }
6
+ async startServer(folderPath, outputFilename, overAllStatus) {
7
+ const openOption = this.ortoniConfig.open || "never";
8
+ const hasFailures = overAllStatus === "failed";
9
+ if (openOption === "always" ||
10
+ (openOption === "on-failure" && hasFailures)) {
11
+ startReportServer(folderPath, outputFilename, this.ortoniConfig.port, openOption);
12
+ await new Promise((_resolve) => { });
13
+ }
14
+ }
15
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Read the bundled index.html template.
3
+ *
4
+ * Resolution order:
5
+ * 1) require.resolve('<pkgName>/dist/index.html') via require (synchronous; CJS fast path)
6
+ * 2) createRequire(resolve) via dynamic import('module') (async ESM fallback)
7
+ * 3) relative to this file: ../dist/index.html (dev/run-from-repo)
8
+ * 4) node_modules/<pkgName>/dist/index.html from process.cwd()
9
+ * 5) process.cwd()/dist/index.html
10
+ *
11
+ * Returns the template string. Throws a helpful Error if not found.
12
+ *
13
+ * NOTE: This function is async because dynamic import('module') is async in ESM.
14
+ */
15
+ export declare function readBundledTemplate(pkgName?: string): Promise<string>;
@@ -0,0 +1,88 @@
1
+ // src/helpers/templateLoader.ts
2
+ import fs from "fs";
3
+ import path from "path";
4
+ /**
5
+ * Read the bundled index.html template.
6
+ *
7
+ * Resolution order:
8
+ * 1) require.resolve('<pkgName>/dist/index.html') via require (synchronous; CJS fast path)
9
+ * 2) createRequire(resolve) via dynamic import('module') (async ESM fallback)
10
+ * 3) relative to this file: ../dist/index.html (dev/run-from-repo)
11
+ * 4) node_modules/<pkgName>/dist/index.html from process.cwd()
12
+ * 5) process.cwd()/dist/index.html
13
+ *
14
+ * Returns the template string. Throws a helpful Error if not found.
15
+ *
16
+ * NOTE: This function is async because dynamic import('module') is async in ESM.
17
+ */
18
+ export async function readBundledTemplate(pkgName = "ortoni-report") {
19
+ const packagedRel = "dist/index.html";
20
+ // 1) Sync CJS fast-path: if `require` exists (typical for CJS consumers)
21
+ try {
22
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
23
+ if (typeof require === "function") {
24
+ // Use require.resolve to reliably find the file inside installed package
25
+ const resolved = require.resolve(`${pkgName}/${packagedRel}`);
26
+ if (fs.existsSync(resolved)) {
27
+ return fs.readFileSync(resolved, "utf-8");
28
+ }
29
+ }
30
+ }
31
+ catch {
32
+ // ignore and continue to async fallback
33
+ }
34
+ // 2) Async ESM-safe createRequire via dynamic import (no eval)
35
+ try {
36
+ // dynamic import of 'module' works in ESM environments
37
+ const moduleNS = await import("module");
38
+ if (moduleNS && typeof moduleNS.createRequire === "function") {
39
+ const createRequire = moduleNS.createRequire;
40
+ // createRequire needs a filename/URL; use this file (works for both ESM and CJS)
41
+ const req = createRequire(
42
+ // @ts-ignore
43
+ typeof __filename !== "undefined" ? __filename : import.meta.url);
44
+ const resolved = req.resolve(`${pkgName}/${packagedRel}`);
45
+ if (fs.existsSync(resolved)) {
46
+ return fs.readFileSync(resolved, "utf-8");
47
+ }
48
+ }
49
+ }
50
+ catch {
51
+ // ignore and try file fallbacks below
52
+ }
53
+ // 3) Relative to this module (useful in dev or mono-repo)
54
+ try {
55
+ const here = path.resolve(__dirname, "../dist/index.html");
56
+ if (fs.existsSync(here))
57
+ return fs.readFileSync(here, "utf-8");
58
+ }
59
+ catch {
60
+ // ignore
61
+ }
62
+ // 4) node_modules lookup from process.cwd()
63
+ try {
64
+ const nm = path.join(process.cwd(), "node_modules", pkgName, packagedRel);
65
+ if (fs.existsSync(nm))
66
+ return fs.readFileSync(nm, "utf-8");
67
+ }
68
+ catch {
69
+ // ignore
70
+ }
71
+ // 5) fallback to process.cwd()/dist/index.html
72
+ try {
73
+ const alt = path.join(process.cwd(), "dist", "index.html");
74
+ if (fs.existsSync(alt))
75
+ return fs.readFileSync(alt, "utf-8");
76
+ }
77
+ catch {
78
+ // ignore
79
+ }
80
+ // Helpful error for maintainers / users
81
+ throw new Error(`ortoni-report template not found (tried:\n` +
82
+ ` - require.resolve('${pkgName}/${packagedRel}')\n` +
83
+ ` - import('module').createRequire(...).resolve('${pkgName}/${packagedRel}')\n` +
84
+ ` - relative ../dist/index.html\n` +
85
+ ` - ${path.join(process.cwd(), "node_modules", pkgName, packagedRel)}\n` +
86
+ ` - ${path.join(process.cwd(), "dist", "index.html")}\n` +
87
+ `Ensure 'dist/index.html' is present in the published package and package.json 'files' includes 'dist/'.`);
88
+ }