ortoni-report 2.0.1 → 2.0.3

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 CHANGED
@@ -1,5 +1,59 @@
1
1
  # Change Log:
2
2
 
3
+ ## Version 2.0.3
4
+
5
+ #### 🚀 New Features
6
+ - **Add screenshot attachment as pagination**: Now supports adding screenshot attachments for easier navigation and pagination.
7
+ - **Bulma Inline CSS**: Integrated Bulma inline CSS for improved styling.
8
+ - **Show card if the count has value**: Cards will now appear only when a valid count is present.
9
+ - **Folder Path**: Introduced a new feature to store the result in the folder path.
10
+
11
+ #### ✨ Improvements
12
+ - **Chart JS as CDN**: Enhanced performance by switching Chart.js to be served via CDN.
13
+ - **Theme icon, navbar & summary icons**: Refined the design of theme icons, navbar, and summary icons for a better user experience.
14
+ - **User meta icons**: Updated and improved the appearance of user meta icons.
15
+ - **Show selected filter on nav-end**: Filters now appear on the nav-end when selected.
16
+ - **Filter logic**: Improved filter functionality by only displaying the filtered items, avoiding the use of the `includes` method.
17
+ - **Logo link to page**: Clicking on the logo now navigates to the corresponding page.
18
+ - **Minor tweaks**: Enhanced overall UI experience
19
+
20
+ ## Version 2.0.2
21
+
22
+ #### 🚀 New Features
23
+
24
+ - **Show or Hide Projects in Test List**
25
+ - You can now show or hide specific projects in the test list.
26
+ - ```showProject: true``` from config
27
+
28
+ - **Document Title from Config**
29
+ - The document title is now configurable via the configuration file, allowing you to customize it to your preference.
30
+
31
+ - **Display Tags in Test Section**
32
+ - Tags associated with tests are now displayed within the test section, giving you a clearer overview of test categories.
33
+
34
+ - **Project and Tags Added to Filter**
35
+ - We've added the ability to filter tests by both project and tags, enhancing the granularity of your test views.
36
+
37
+ - **Display Selected Status on UI**
38
+ - The selected status filter is now visible on the UI, making it easier to track the current filter settings.
39
+
40
+ #### 🛠 Fixes
41
+
42
+ - **Project Drop-down Z-Index on Screenshot**
43
+ - Resolved an issue where the project drop-down menu was being overlapped by screenshots. The z-index has been adjusted for proper layering.
44
+
45
+ - **Project Filter Hide Test Steps**
46
+ - Fixed a bug where applying the project filter would inadvertently hide test steps. Test steps are now correctly displayed based on the filter.
47
+
48
+ #### ✨ Improvements
49
+
50
+ - **Hide Skipped Tests on All Tests Filter**
51
+ - Skipped tests are now hidden by default when using the "All Tests" filter, providing a cleaner and more focused view of relevant tests.
52
+
53
+ - **Colorful Dashboard**
54
+ - We've enhanced the visual appeal of the dashboard with more vibrant and intuitive colors, making it easier to navigate and interpret results.
55
+
56
+
3
57
  ## Version 2.0.1
4
58
  - Fixed local and root path issue of Parcel bundler.
5
59
  - Local config issue
package/dist/cli/cli.js CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
  "use strict";
3
3
  var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  return (mod && mod.__esModule) ? mod : { "default": mod };
@@ -4,6 +4,16 @@ import { Reporter, FullConfig, Suite, TestCase, TestResult, FullResult } from '@
4
4
  * Configuration options for OrtoniReport.
5
5
  */
6
6
  interface OrtoniReportConfig {
7
+ /**
8
+ * The title of the HTML report.
9
+ * @example "Ortoni Playwright Test Report"
10
+ */
11
+ title?: string;
12
+ /**
13
+ * Add project on the list of the tests? (Filtering projects still works if hidden)
14
+ * @example true to display, false to hide.
15
+ */
16
+ showProject?: boolean;
7
17
  /**
8
18
  * The name of the project.
9
19
  * @example "Ortoni Project"
@@ -43,6 +53,11 @@ interface OrtoniReportConfig {
43
53
  * @example "index.html"
44
54
  */
45
55
  filename?: string;
56
+ /**
57
+ * The folder name.
58
+ * @example "playwright-report"
59
+ */
60
+ folderPath?: string;
46
61
  }
47
62
 
48
63
  interface Steps {
@@ -66,8 +81,9 @@ interface TestResultData {
66
81
  steps: Steps[];
67
82
  logs: string;
68
83
  screenshotPath?: string | null | undefined;
84
+ screenshots?: string[];
69
85
  filePath: string;
70
- projects: Set<string>;
86
+ filters: Set<string>;
71
87
  tracePath?: string;
72
88
  videoPath?: string;
73
89
  base64Image: boolean | undefined;
@@ -80,15 +96,16 @@ declare class OrtoniReport implements Reporter {
80
96
  private suiteName;
81
97
  private config;
82
98
  private projectSet;
83
- private tagsSet;
99
+ private folderPath;
84
100
  constructor(config?: OrtoniReportConfig);
85
101
  onBegin(config: FullConfig, suite: Suite): void;
86
102
  onTestBegin(test: TestCase, result: TestResult): void;
87
103
  onTestEnd(test: TestCase, result: TestResult): void;
88
104
  private attachFiles;
89
105
  onEnd(result: FullResult): void;
106
+ private registerPartial;
90
107
  private groupResults;
91
- generateHTML(filteredResults: TestResultData[], totalDuration: string): string;
108
+ generateHTML(filteredResults: TestResultData[], totalDuration: string, cssContent: string): string;
92
109
  }
93
110
 
94
111
  export { OrtoniReportConfig, OrtoniReport as default };
@@ -6623,12 +6623,15 @@ var OrtoniReport = class {
6623
6623
  this.results = [];
6624
6624
  this.groupedResults = {};
6625
6625
  this.projectSet = /* @__PURE__ */ new Set();
6626
- this.tagsSet = /* @__PURE__ */ new Set();
6627
6626
  this.config = config;
6627
+ this.folderPath = config.folderPath || "playwright-report";
6628
6628
  }
6629
6629
  onBegin(config, suite) {
6630
6630
  this.results = [];
6631
6631
  this.projectRoot = config.rootDir;
6632
+ if (!import_fs.default.existsSync(this.folderPath)) {
6633
+ import_fs.default.mkdirSync(this.folderPath, { recursive: true });
6634
+ }
6632
6635
  }
6633
6636
  onTestBegin(test, result) {
6634
6637
  }
@@ -6671,7 +6674,7 @@ var OrtoniReport = class {
6671
6674
  result.stdout.concat(result.stderr).map((log) => log).join("\n")
6672
6675
  ),
6673
6676
  filePath,
6674
- projects: this.projectSet,
6677
+ filters: this.projectSet,
6675
6678
  base64Image: this.config.base64Image
6676
6679
  };
6677
6680
  this.attachFiles(result, testResult);
@@ -6683,35 +6686,34 @@ var OrtoniReport = class {
6683
6686
  attachFiles(result, testResult) {
6684
6687
  if (result.attachments) {
6685
6688
  const { base64Image } = this.config;
6686
- const screenshot = result.attachments.find(
6687
- (attachment) => attachment.name === "screenshot"
6688
- );
6689
- if (screenshot && screenshot.path) {
6690
- try {
6691
- const screenshotContent = import_fs.default.readFileSync(
6692
- screenshot.path,
6693
- base64Image ? "base64" : void 0
6694
- );
6695
- testResult.screenshotPath = base64Image ? `data:image/png;base64,${screenshotContent}` : import_path2.default.resolve(screenshot.path);
6696
- } catch (error) {
6697
- console.error(
6698
- `OrtoniReport: Failed to read screenshot file: ${screenshot.path}`,
6699
- error
6700
- );
6689
+ testResult.screenshots = [];
6690
+ result.attachments.forEach((attachment) => {
6691
+ if (attachment.contentType === "image/png") {
6692
+ let screenshotPath = "";
6693
+ if (attachment.path) {
6694
+ try {
6695
+ const screenshotContent = import_fs.default.readFileSync(
6696
+ attachment.path,
6697
+ base64Image ? "base64" : void 0
6698
+ );
6699
+ screenshotPath = base64Image ? `data:image/png;base64,${screenshotContent}` : import_path2.default.resolve(attachment.path);
6700
+ } catch (error) {
6701
+ console.error(
6702
+ `OrtoniReport: Failed to read screenshot file: ${attachment.path}`,
6703
+ error
6704
+ );
6705
+ }
6706
+ } else if (attachment.body) {
6707
+ screenshotPath = `data:image/png;base64,${attachment.body.toString("base64")}`;
6708
+ }
6709
+ if (screenshotPath) {
6710
+ testResult.screenshots?.push(screenshotPath);
6711
+ }
6701
6712
  }
6702
- }
6703
- const tracePath = result.attachments.find(
6704
- (attachment) => attachment.name === "trace"
6705
- );
6706
- if (tracePath?.path) {
6707
- testResult.tracePath = import_path2.default.resolve(__dirname, tracePath.path);
6708
- }
6709
- const videoPath = result.attachments.find(
6710
- (attachment) => attachment.name === "video"
6711
- );
6712
- if (videoPath?.path) {
6713
- testResult.videoPath = import_path2.default.resolve(__dirname, videoPath.path);
6714
- }
6713
+ if (attachment.name === "video" && attachment.path) {
6714
+ testResult.videoPath = import_path2.default.resolve(__dirname, attachment.path);
6715
+ }
6716
+ });
6715
6717
  }
6716
6718
  }
6717
6719
  onEnd(result) {
@@ -6721,31 +6723,63 @@ var OrtoniReport = class {
6721
6723
  );
6722
6724
  const totalDuration = msToTime(result.duration);
6723
6725
  this.groupResults();
6726
+ import_handlebars.default.registerHelper("joinWithSpace", (array) => array.join(" "));
6724
6727
  import_handlebars.default.registerHelper("json", (context) => safeStringify(context));
6725
6728
  import_handlebars.default.registerHelper(
6726
6729
  "eq",
6727
6730
  (actualStatus, expectedStatus) => actualStatus === expectedStatus
6728
6731
  );
6729
- const outputFilename = ensureHtmlExtension(this.config.filename || "ortoni-report.html");
6730
- const html = this.generateHTML(filteredResults, totalDuration);
6731
- const outputPath = import_path2.default.resolve(process.cwd(), outputFilename);
6732
+ import_handlebars.default.registerHelper("gr", (count) => count > 0);
6733
+ const cssContent = import_fs.default.readFileSync(
6734
+ import_path2.default.resolve(__dirname, "style", "main.css"),
6735
+ "utf-8"
6736
+ );
6737
+ this.registerPartial("navbar");
6738
+ this.registerPartial("testStatus");
6739
+ this.registerPartial("testPanel");
6740
+ this.registerPartial("summaryCard");
6741
+ this.registerPartial("userInfo");
6742
+ const outputFilename = ensureHtmlExtension(
6743
+ this.config.filename || "ortoni-report.html"
6744
+ );
6745
+ if (!import_fs.default.existsSync(this.folderPath)) {
6746
+ import_fs.default.mkdirSync(this.folderPath, { recursive: true });
6747
+ }
6748
+ const html = this.generateHTML(filteredResults, totalDuration, cssContent);
6749
+ const outputPath = import_path2.default.join(process.cwd(), this.folderPath, outputFilename);
6732
6750
  import_fs.default.writeFileSync(outputPath, html);
6733
6751
  console.log(`Ortoni HTML report generated at ${outputPath}`);
6734
6752
  } catch (error) {
6735
6753
  console.error("OrtoniReport: Error generating report:", error);
6736
6754
  }
6737
6755
  }
6756
+ registerPartial(name) {
6757
+ import_handlebars.default.registerPartial(name, import_fs.default.readFileSync(
6758
+ import_path2.default.resolve(__dirname, "views", name + ".hbs"),
6759
+ "utf-8"
6760
+ ));
6761
+ }
6738
6762
  groupResults() {
6739
- this.groupedResults = this.results.reduce((acc, result, index) => {
6740
- const { filePath, suite, projectName } = result;
6741
- acc[filePath] = acc[filePath] || {};
6742
- acc[filePath][suite] = acc[filePath][suite] || {};
6743
- acc[filePath][suite][projectName] = acc[filePath][suite][projectName] || [];
6744
- acc[filePath][suite][projectName].push({ ...result, index });
6745
- return acc;
6746
- }, {});
6763
+ if (this.config.showProject) {
6764
+ this.groupedResults = this.results.reduce((acc, result, index) => {
6765
+ const { filePath, suite, projectName } = result;
6766
+ acc[filePath] = acc[filePath] || {};
6767
+ acc[filePath][suite] = acc[filePath][suite] || {};
6768
+ acc[filePath][suite][projectName] = acc[filePath][suite][projectName] || [];
6769
+ acc[filePath][suite][projectName].push({ ...result, index });
6770
+ return acc;
6771
+ }, {});
6772
+ } else {
6773
+ this.groupedResults = this.results.reduce((acc, result, index) => {
6774
+ const { filePath, suite } = result;
6775
+ acc[filePath] = acc[filePath] || {};
6776
+ acc[filePath][suite] = acc[filePath][suite] || [];
6777
+ acc[filePath][suite].push({ ...result, index });
6778
+ return acc;
6779
+ }, {});
6780
+ }
6747
6781
  }
6748
- generateHTML(filteredResults, totalDuration) {
6782
+ generateHTML(filteredResults, totalDuration, cssContent) {
6749
6783
  try {
6750
6784
  const totalTests = filteredResults.length;
6751
6785
  const passedTests = this.results.filter(
@@ -6757,11 +6791,15 @@ var OrtoniReport = class {
6757
6791
  ).length;
6758
6792
  const successRate = ((passedTests + flakyTests) / totalTests * 100).toFixed(2);
6759
6793
  const templateSource = import_fs.default.readFileSync(
6760
- import_path2.default.resolve(__dirname, "report-template.hbs"),
6794
+ import_path2.default.resolve(__dirname, "views", "main.hbs"),
6761
6795
  "utf-8"
6762
6796
  );
6763
6797
  const template = import_handlebars.default.compile(templateSource);
6764
6798
  const logo = this.config.logo ? import_path2.default.resolve(this.config.logo) : void 0;
6799
+ const allTags = /* @__PURE__ */ new Set();
6800
+ this.results.forEach((result) => {
6801
+ result.testTags.forEach((tag) => allTags.add(tag));
6802
+ });
6765
6803
  const data = {
6766
6804
  logo,
6767
6805
  totalDuration,
@@ -6780,9 +6818,12 @@ var OrtoniReport = class {
6780
6818
  preferredTheme: this.config.preferredTheme,
6781
6819
  successRate,
6782
6820
  lastRunDate: formatDate(/* @__PURE__ */ new Date()),
6783
- projects: this.projectSet
6821
+ projects: this.projectSet,
6822
+ allTags: Array.from(allTags),
6823
+ showProject: this.config.showProject || false,
6824
+ title: this.config.title || "Ortoni Playwright Test Report"
6784
6825
  };
6785
- return template(data);
6826
+ return template({ ...data, inlineCss: cssContent });
6786
6827
  } catch (error) {
6787
6828
  console.error("OrtoniReport: Error generating HTML:", error);
6788
6829
  return `<html><body><h1>Report generation failed</h1><pre>${error.stack}</pre></body></html>`;
@@ -6619,12 +6619,15 @@ var OrtoniReport = class {
6619
6619
  this.results = [];
6620
6620
  this.groupedResults = {};
6621
6621
  this.projectSet = /* @__PURE__ */ new Set();
6622
- this.tagsSet = /* @__PURE__ */ new Set();
6623
6622
  this.config = config;
6623
+ this.folderPath = config.folderPath || "playwright-report";
6624
6624
  }
6625
6625
  onBegin(config, suite) {
6626
6626
  this.results = [];
6627
6627
  this.projectRoot = config.rootDir;
6628
+ if (!fs.existsSync(this.folderPath)) {
6629
+ fs.mkdirSync(this.folderPath, { recursive: true });
6630
+ }
6628
6631
  }
6629
6632
  onTestBegin(test, result) {
6630
6633
  }
@@ -6667,7 +6670,7 @@ var OrtoniReport = class {
6667
6670
  result.stdout.concat(result.stderr).map((log) => log).join("\n")
6668
6671
  ),
6669
6672
  filePath,
6670
- projects: this.projectSet,
6673
+ filters: this.projectSet,
6671
6674
  base64Image: this.config.base64Image
6672
6675
  };
6673
6676
  this.attachFiles(result, testResult);
@@ -6679,35 +6682,34 @@ var OrtoniReport = class {
6679
6682
  attachFiles(result, testResult) {
6680
6683
  if (result.attachments) {
6681
6684
  const { base64Image } = this.config;
6682
- const screenshot = result.attachments.find(
6683
- (attachment) => attachment.name === "screenshot"
6684
- );
6685
- if (screenshot && screenshot.path) {
6686
- try {
6687
- const screenshotContent = fs.readFileSync(
6688
- screenshot.path,
6689
- base64Image ? "base64" : void 0
6690
- );
6691
- testResult.screenshotPath = base64Image ? `data:image/png;base64,${screenshotContent}` : path2.resolve(screenshot.path);
6692
- } catch (error) {
6693
- console.error(
6694
- `OrtoniReport: Failed to read screenshot file: ${screenshot.path}`,
6695
- error
6696
- );
6685
+ testResult.screenshots = [];
6686
+ result.attachments.forEach((attachment) => {
6687
+ if (attachment.contentType === "image/png") {
6688
+ let screenshotPath = "";
6689
+ if (attachment.path) {
6690
+ try {
6691
+ const screenshotContent = fs.readFileSync(
6692
+ attachment.path,
6693
+ base64Image ? "base64" : void 0
6694
+ );
6695
+ screenshotPath = base64Image ? `data:image/png;base64,${screenshotContent}` : path2.resolve(attachment.path);
6696
+ } catch (error) {
6697
+ console.error(
6698
+ `OrtoniReport: Failed to read screenshot file: ${attachment.path}`,
6699
+ error
6700
+ );
6701
+ }
6702
+ } else if (attachment.body) {
6703
+ screenshotPath = `data:image/png;base64,${attachment.body.toString("base64")}`;
6704
+ }
6705
+ if (screenshotPath) {
6706
+ testResult.screenshots?.push(screenshotPath);
6707
+ }
6697
6708
  }
6698
- }
6699
- const tracePath = result.attachments.find(
6700
- (attachment) => attachment.name === "trace"
6701
- );
6702
- if (tracePath?.path) {
6703
- testResult.tracePath = path2.resolve(__dirname, tracePath.path);
6704
- }
6705
- const videoPath = result.attachments.find(
6706
- (attachment) => attachment.name === "video"
6707
- );
6708
- if (videoPath?.path) {
6709
- testResult.videoPath = path2.resolve(__dirname, videoPath.path);
6710
- }
6709
+ if (attachment.name === "video" && attachment.path) {
6710
+ testResult.videoPath = path2.resolve(__dirname, attachment.path);
6711
+ }
6712
+ });
6711
6713
  }
6712
6714
  }
6713
6715
  onEnd(result) {
@@ -6717,31 +6719,63 @@ var OrtoniReport = class {
6717
6719
  );
6718
6720
  const totalDuration = msToTime(result.duration);
6719
6721
  this.groupResults();
6722
+ import_handlebars.default.registerHelper("joinWithSpace", (array) => array.join(" "));
6720
6723
  import_handlebars.default.registerHelper("json", (context) => safeStringify(context));
6721
6724
  import_handlebars.default.registerHelper(
6722
6725
  "eq",
6723
6726
  (actualStatus, expectedStatus) => actualStatus === expectedStatus
6724
6727
  );
6725
- const outputFilename = ensureHtmlExtension(this.config.filename || "ortoni-report.html");
6726
- const html = this.generateHTML(filteredResults, totalDuration);
6727
- const outputPath = path2.resolve(process.cwd(), outputFilename);
6728
+ import_handlebars.default.registerHelper("gr", (count) => count > 0);
6729
+ const cssContent = fs.readFileSync(
6730
+ path2.resolve(__dirname, "style", "main.css"),
6731
+ "utf-8"
6732
+ );
6733
+ this.registerPartial("navbar");
6734
+ this.registerPartial("testStatus");
6735
+ this.registerPartial("testPanel");
6736
+ this.registerPartial("summaryCard");
6737
+ this.registerPartial("userInfo");
6738
+ const outputFilename = ensureHtmlExtension(
6739
+ this.config.filename || "ortoni-report.html"
6740
+ );
6741
+ if (!fs.existsSync(this.folderPath)) {
6742
+ fs.mkdirSync(this.folderPath, { recursive: true });
6743
+ }
6744
+ const html = this.generateHTML(filteredResults, totalDuration, cssContent);
6745
+ const outputPath = path2.join(process.cwd(), this.folderPath, outputFilename);
6728
6746
  fs.writeFileSync(outputPath, html);
6729
6747
  console.log(`Ortoni HTML report generated at ${outputPath}`);
6730
6748
  } catch (error) {
6731
6749
  console.error("OrtoniReport: Error generating report:", error);
6732
6750
  }
6733
6751
  }
6752
+ registerPartial(name) {
6753
+ import_handlebars.default.registerPartial(name, fs.readFileSync(
6754
+ path2.resolve(__dirname, "views", name + ".hbs"),
6755
+ "utf-8"
6756
+ ));
6757
+ }
6734
6758
  groupResults() {
6735
- this.groupedResults = this.results.reduce((acc, result, index) => {
6736
- const { filePath, suite, projectName } = result;
6737
- acc[filePath] = acc[filePath] || {};
6738
- acc[filePath][suite] = acc[filePath][suite] || {};
6739
- acc[filePath][suite][projectName] = acc[filePath][suite][projectName] || [];
6740
- acc[filePath][suite][projectName].push({ ...result, index });
6741
- return acc;
6742
- }, {});
6759
+ if (this.config.showProject) {
6760
+ this.groupedResults = this.results.reduce((acc, result, index) => {
6761
+ const { filePath, suite, projectName } = result;
6762
+ acc[filePath] = acc[filePath] || {};
6763
+ acc[filePath][suite] = acc[filePath][suite] || {};
6764
+ acc[filePath][suite][projectName] = acc[filePath][suite][projectName] || [];
6765
+ acc[filePath][suite][projectName].push({ ...result, index });
6766
+ return acc;
6767
+ }, {});
6768
+ } else {
6769
+ this.groupedResults = this.results.reduce((acc, result, index) => {
6770
+ const { filePath, suite } = result;
6771
+ acc[filePath] = acc[filePath] || {};
6772
+ acc[filePath][suite] = acc[filePath][suite] || [];
6773
+ acc[filePath][suite].push({ ...result, index });
6774
+ return acc;
6775
+ }, {});
6776
+ }
6743
6777
  }
6744
- generateHTML(filteredResults, totalDuration) {
6778
+ generateHTML(filteredResults, totalDuration, cssContent) {
6745
6779
  try {
6746
6780
  const totalTests = filteredResults.length;
6747
6781
  const passedTests = this.results.filter(
@@ -6753,11 +6787,15 @@ var OrtoniReport = class {
6753
6787
  ).length;
6754
6788
  const successRate = ((passedTests + flakyTests) / totalTests * 100).toFixed(2);
6755
6789
  const templateSource = fs.readFileSync(
6756
- path2.resolve(__dirname, "report-template.hbs"),
6790
+ path2.resolve(__dirname, "views", "main.hbs"),
6757
6791
  "utf-8"
6758
6792
  );
6759
6793
  const template = import_handlebars.default.compile(templateSource);
6760
6794
  const logo = this.config.logo ? path2.resolve(this.config.logo) : void 0;
6795
+ const allTags = /* @__PURE__ */ new Set();
6796
+ this.results.forEach((result) => {
6797
+ result.testTags.forEach((tag) => allTags.add(tag));
6798
+ });
6761
6799
  const data = {
6762
6800
  logo,
6763
6801
  totalDuration,
@@ -6776,9 +6814,12 @@ var OrtoniReport = class {
6776
6814
  preferredTheme: this.config.preferredTheme,
6777
6815
  successRate,
6778
6816
  lastRunDate: formatDate(/* @__PURE__ */ new Date()),
6779
- projects: this.projectSet
6817
+ projects: this.projectSet,
6818
+ allTags: Array.from(allTags),
6819
+ showProject: this.config.showProject || false,
6820
+ title: this.config.title || "Ortoni Playwright Test Report"
6780
6821
  };
6781
- return template(data);
6822
+ return template({ ...data, inlineCss: cssContent });
6782
6823
  } catch (error) {
6783
6824
  console.error("OrtoniReport: Error generating HTML:", error);
6784
6825
  return `<html><body><h1>Report generation failed</h1><pre>${error.stack}</pre></body></html>`;