odhin-reports-playwright 1.0.1

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 (81) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +35 -0
  3. package/lib/generate.d.ts +56 -0
  4. package/lib/generate.js +969 -0
  5. package/lib/help.d.ts +46 -0
  6. package/lib/help.js +503 -0
  7. package/lib/html/assets/css/bootstrap.min.css +1 -0
  8. package/lib/html/assets/css/dark-theme.css +55 -0
  9. package/lib/html/assets/css/dark-theme.min.css +1 -0
  10. package/lib/html/assets/css/img-comparison-slider.css +50 -0
  11. package/lib/html/assets/css/img-comparison-slider.min.css +1 -0
  12. package/lib/html/assets/css/jquery.dataTables.min.css +1 -0
  13. package/lib/html/assets/css/light-theme.css +53 -0
  14. package/lib/html/assets/css/light-theme.min.css +1 -0
  15. package/lib/html/assets/css/prism.css +163 -0
  16. package/lib/html/assets/css/prism.min.css +1 -0
  17. package/lib/html/assets/css/style-colors.css +349 -0
  18. package/lib/html/assets/css/style-colors.min.css +1 -0
  19. package/lib/html/assets/css/style.css +586 -0
  20. package/lib/html/assets/css/style.min.css +1 -0
  21. package/lib/html/assets/js/bootstrap.min.js +1 -0
  22. package/lib/html/assets/js/chart.min.js +1 -0
  23. package/lib/html/assets/js/customTheme.js +40 -0
  24. package/lib/html/assets/js/customTheme.min.js +1 -0
  25. package/lib/html/assets/js/darkTheme.js +34 -0
  26. package/lib/html/assets/js/darkTheme.min.js +1 -0
  27. package/lib/html/assets/js/dataTables.js +4 -0
  28. package/lib/html/assets/js/dataTables.min.js +1 -0
  29. package/lib/html/assets/js/img-comparison-slider.min.js +2 -0
  30. package/lib/html/assets/js/jquery-3.5.1.min.js +1 -0
  31. package/lib/html/assets/js/jquery.dataTables.min.js +1 -0
  32. package/lib/html/assets/js/lightTheme.js +34 -0
  33. package/lib/html/assets/js/lightTheme.min.js +1 -0
  34. package/lib/html/assets/js/prism.min.js +1 -0
  35. package/lib/html/assets/js/script.js +61 -0
  36. package/lib/html/assets/js/script.min.js +1 -0
  37. package/lib/html/base.html +182 -0
  38. package/lib/html/chart.html +21 -0
  39. package/lib/html/chartFile.html +33 -0
  40. package/lib/html/chartInfo.html +5 -0
  41. package/lib/html/chartJs.html +133 -0
  42. package/lib/html/chartProject.html +33 -0
  43. package/lib/html/chartStatus.html +87 -0
  44. package/lib/html/content.html +30 -0
  45. package/lib/html/runInfo.html +47 -0
  46. package/lib/html/summary.html +20 -0
  47. package/lib/html/summaryRow.html +10 -0
  48. package/lib/html/testCase.html +12 -0
  49. package/lib/html/testCaseDetails.html +125 -0
  50. package/lib/index.d.ts +12 -0
  51. package/lib/index.js +48 -0
  52. package/lib/server.mjs +8 -0
  53. package/lib/types/execFileInfo.types.d.ts +13 -0
  54. package/lib/types/execFileInfo.types.js +2 -0
  55. package/lib/types/execInfo.types.d.ts +15 -0
  56. package/lib/types/execInfo.types.js +2 -0
  57. package/lib/types/execOptions.types.d.ts +17 -0
  58. package/lib/types/execOptions.types.js +2 -0
  59. package/lib/types/execProjectInfo.types.d.ts +13 -0
  60. package/lib/types/execProjectInfo.types.js +2 -0
  61. package/lib/types/execTestCase.types.d.ts +14 -0
  62. package/lib/types/execTestCase.types.js +2 -0
  63. package/lib/types/execTestResult.types.d.ts +43 -0
  64. package/lib/types/execTestResult.types.js +2 -0
  65. package/lib/types/onEnd.types.d.ts +12 -0
  66. package/lib/types/onEnd.types.js +2 -0
  67. package/lib/types/onProject.types.d.ts +53 -0
  68. package/lib/types/onProject.types.js +2 -0
  69. package/lib/types/onStdIO.types.d.ts +11 -0
  70. package/lib/types/onStdIO.types.js +2 -0
  71. package/lib/types/onStepBegin.types.d.ts +22 -0
  72. package/lib/types/onStepBegin.types.js +2 -0
  73. package/lib/types/onStepEnd.types.d.ts +25 -0
  74. package/lib/types/onStepEnd.types.js +2 -0
  75. package/lib/types/onTestBegin.types.d.ts +15 -0
  76. package/lib/types/onTestBegin.types.js +2 -0
  77. package/lib/types/onTestEnd.types.d.ts +37 -0
  78. package/lib/types/onTestEnd.types.js +2 -0
  79. package/lib/types/testReport.types.d.ts +6 -0
  80. package/lib/types/testReport.types.js +2 -0
  81. package/package.json +67 -0
package/lib/help.d.ts ADDED
@@ -0,0 +1,46 @@
1
+ import { ExecOptions } from "./types/execOptions.types";
2
+ import { TestStep } from "./types/execTestResult.types";
3
+ declare class Help {
4
+ private execOptions;
5
+ private ansiRegex;
6
+ private ansiColors;
7
+ private errorCodeLinesToPrint;
8
+ private stepCodeLinesToPrint;
9
+ private freePort;
10
+ showError: boolean;
11
+ constructor(execOptions: ExecOptions);
12
+ convertMsToTime(duration: number): string;
13
+ htmlEncode(input: any): any;
14
+ getRandomColor(): string;
15
+ stripAnsi(str: string): string;
16
+ escapeHTML(text: string): string;
17
+ ansi2htmlMarkup(text: string): string;
18
+ printStep(id: string, count: number, testStep: TestStep, space: number): string;
19
+ processLineByLine(filename: string, lineNumber: number, lines: number): string;
20
+ printErrorsTabButton(modalId: string): string;
21
+ printErrors(errors: string, errorCode: string, modalId: string): string;
22
+ printErrorCode(errorFile: string): string;
23
+ printCode(filename: string, fileLine: number, lines: number): string;
24
+ printScreenshotComparison(id: string, attachmentExpected: any, attachmentActual: any, attachmentDiff: any): string;
25
+ printScreenshot(id: string, attachment: any): string;
26
+ printImage(id: string, attachment: any): string;
27
+ printScreenshotsTabButton(modalId: string): string;
28
+ printScreenshots(screenshots: string, modalId: string): string;
29
+ printVideo(id: string, attachment: any): string;
30
+ printVideosTabButton(modalId: string): string;
31
+ printVideos(videos: string, modalId: string): string;
32
+ printStdoutTabButton(modalId: string): string;
33
+ printStdout(id: string, stdout: string): string;
34
+ printStderrTabButton(modalId: string): string;
35
+ printStderr(id: string, stderr: string): string;
36
+ printTraceTabButton(modalId: string): string;
37
+ printTrace(id: string, attachment: any): string;
38
+ convertBase64(fileToConvert: string): string;
39
+ getShortFilePath(filepath: string, testFolder: string): string;
40
+ createFile(filepath: string, content: string): void;
41
+ updateHtml(html: string, tag: string, content: string, contentType: string, filename: string): string;
42
+ base64ToFile(base64data: string, filePath: string, index?: number): any;
43
+ getPortFree(): Promise<string>;
44
+ isValidURL(url: string): boolean;
45
+ }
46
+ export default Help;
package/lib/help.js ADDED
@@ -0,0 +1,503 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const ansi_to_html_1 = __importDefault(require("ansi-to-html"));
7
+ const path_1 = __importDefault(require("path"));
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const fs_extra_1 = __importDefault(require("fs-extra"));
10
+ const portfinder_1 = __importDefault(require("portfinder"));
11
+ class Help {
12
+ constructor(execOptions) {
13
+ this.ansiRegex = new RegExp("[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))", "g");
14
+ this.ansiColors = {
15
+ 0: "#000",
16
+ 1: "#d85a51",
17
+ 2: "#0C0",
18
+ 3: "#d68043",
19
+ 4: "#00C",
20
+ 5: "#C0C",
21
+ 6: "#0CC",
22
+ 7: "#CCC",
23
+ 8: "#555",
24
+ 9: "#F55",
25
+ 10: "#5F5",
26
+ 11: "#FF5",
27
+ 12: "#55F",
28
+ 13: "#F5F",
29
+ 14: "#5FF",
30
+ 15: "#FFF",
31
+ };
32
+ this.errorCodeLinesToPrint = 4;
33
+ this.stepCodeLinesToPrint = 1;
34
+ this.freePort = 0;
35
+ this.showError = true;
36
+ this.execOptions = execOptions;
37
+ }
38
+ convertMsToTime(duration) {
39
+ let milliseconds = duration;
40
+ let seconds = Math.floor(milliseconds / 1000);
41
+ let minutes = Math.floor(seconds / 60);
42
+ let hours = Math.floor(minutes / 60);
43
+ milliseconds = milliseconds % 1000;
44
+ seconds = seconds % 60;
45
+ minutes = minutes % 60;
46
+ hours = hours % 24;
47
+ let out = "";
48
+ out += hours.toString() + "h ";
49
+ out += minutes.toString() + "m ";
50
+ out += seconds.toString() + "s ";
51
+ out += milliseconds.toString() + "ms ";
52
+ return out;
53
+ }
54
+ htmlEncode(input) {
55
+ return input
56
+ .replace(/&/g, "&amp;")
57
+ .replace(/</g, "&lt;")
58
+ .replace(/>/g, "&gt;");
59
+ }
60
+ getRandomColor() {
61
+ var length = 6;
62
+ var chars = "0123456789ABCDEF";
63
+ var hex = "#";
64
+ while (length--)
65
+ hex += chars[(Math.random() * 16) | 0];
66
+ return hex;
67
+ }
68
+ stripAnsi(str) {
69
+ return str.replace(this.ansiRegex, "");
70
+ }
71
+ escapeHTML(text) {
72
+ return text.replace(/[&"<>]/g, (c) => ({ "&": "&amp;", '"': "&quot;", "<": "&lt;", ">": "&gt;" }[c]));
73
+ }
74
+ ansi2htmlMarkup(text) {
75
+ const config = {
76
+ //bg: "var(--vscode-panel-background)",
77
+ //fg: "var(--vscode-foreground)",
78
+ colors: this.ansiColors,
79
+ };
80
+ return new ansi_to_html_1.default(config).toHtml(this.escapeHTML(text));
81
+ }
82
+ printStep(id, count, testStep, space) {
83
+ let icon = "";
84
+ let statusClass = "";
85
+ let title = "";
86
+ let body = "";
87
+ if (testStep.error !== undefined) {
88
+ icon = `<span class="step-error"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-x" viewBox="0 0 16 16"><path d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708"/></svg></span>`;
89
+ statusClass = "step-error-hover";
90
+ }
91
+ else {
92
+ icon = `<span class="step-ok"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-check" viewBox="0 0 16 16"><path d="M10.97 4.97a.75.75 0 0 1 1.07 1.05l-3.99 4.99a.75.75 0 0 1-1.08.02L4.324 8.384a.75.75 0 1 1 1.06-1.06l2.094 2.093 3.473-4.425z"/></svg></span>`;
93
+ statusClass = "step-ok-hover";
94
+ }
95
+ if (testStep.location !== undefined) {
96
+ title = `<p class="stepLine" style="padding-left: ${space}px">${icon +
97
+ " " +
98
+ testStep.title +
99
+ " | <span class='fst-italic'>" +
100
+ testStep.location.file.replace(path_1.default.resolve("./") + "/", "") +
101
+ ":" +
102
+ testStep.location.line}</span><label class="stepLineDuration">${this.convertMsToTime(testStep.duration)}</label></p>`;
103
+ let code = this.printCode(testStep.location.file, testStep.location.line, this.stepCodeLinesToPrint);
104
+ let startLine = testStep.location.line - this.stepCodeLinesToPrint <= 0
105
+ ? 1
106
+ : testStep.location.line - this.stepCodeLinesToPrint;
107
+ body += `<pre style="margin-left: ${space + 20}px;" class="line-numbers" data-start="${startLine}" >${code}</pre>`;
108
+ }
109
+ else {
110
+ title = `<p class="stepLine" style="padding-left: ${space}px">${icon + " " + testStep.title}<label class="stepLineDuration">${this.convertMsToTime(testStep.duration)}</label></p>`;
111
+ }
112
+ if (testStep.steps !== undefined) {
113
+ testStep.steps.forEach((step, index) => {
114
+ body += this.printStep(id, count + 10000 + index, step, space + 40);
115
+ });
116
+ }
117
+ if (body !== "") {
118
+ return `<div class="accordion accordion-flush " id="${id}-${count}-accordionFlush">
119
+ <div class="accordion-item">
120
+ <h2 class="accordion-header ${statusClass}">
121
+ <button class="accordion-button accordion-steps collapsed ${statusClass}" type="button" data-bs-toggle="collapse" data-bs-target="#flush-collapse-${id}-${count}" aria-expanded="false" aria-controls="flush-collapse-${id}-${count}">
122
+ ${title}
123
+ </button>
124
+ </h2>
125
+ <div id="flush-collapse-${id}-${count}" class="accordion-collapse collapse" data-bs-parent="#${id}-${count}-accordionFlush">
126
+ <div class="accordion-body accordion-body-steps">
127
+ ${body}
128
+ </div>
129
+ </div>
130
+ </div>
131
+ </div>`;
132
+ }
133
+ else {
134
+ return `<div class="accordion accordion-flush " id="${id}-${count}-accordionFlush">
135
+ <div class="accordion-item">
136
+ <h2 class="accordion-header ${statusClass}">
137
+ <button class="accordion-steps-no-link ${statusClass}" type="button" >
138
+ ${title}
139
+ </button>
140
+ </h2>
141
+ </div>
142
+ </div>`;
143
+ }
144
+ }
145
+ processLineByLine(filename, lineNumber, lines) {
146
+ let result = "";
147
+ try {
148
+ let codeLines = fs_1.default.readFileSync(filename, "utf-8").split(/\r?\n/);
149
+ codeLines.forEach(async (line, index) => {
150
+ let indx = index + 1;
151
+ if (indx >= lineNumber - lines && indx <= lineNumber + lines) {
152
+ result += line + "\n";
153
+ }
154
+ });
155
+ return result;
156
+ }
157
+ catch (error) {
158
+ return "";
159
+ }
160
+ }
161
+ printErrorsTabButton(modalId) {
162
+ return `<button class="result-tablinks-${modalId}" onclick="openResultTab(event, 'TabErrors-${modalId}', '${modalId}')">Errors</button>`;
163
+ }
164
+ printErrors(errors, errorCode, modalId) {
165
+ return `<div id="TabErrors-${modalId}" style="display: none" class="result-tabcontent result-tabcontent-${modalId}">
166
+ <pre class="container-fluid text-left pre">${this.ansi2htmlMarkup(errors)}</pre>
167
+ ${errorCode}
168
+ </div>`;
169
+ }
170
+ printErrorCode(errorFile) {
171
+ let delimiter = ":";
172
+ let start = -2;
173
+ let errorFileLine = errorFile.split(delimiter).slice(start)[0];
174
+ let errorFilename = errorFile
175
+ .split(delimiter)
176
+ .slice(0, start)
177
+ .join(delimiter);
178
+ let startLine = Number(errorFileLine) - this.errorCodeLinesToPrint <= 0
179
+ ? 1
180
+ : Number(errorFileLine) - this.errorCodeLinesToPrint;
181
+ return `<pre class="line-numbers" data-start="${startLine}" >${this.printCode(errorFilename, Number(errorFileLine), this.errorCodeLinesToPrint)}</pre>`;
182
+ }
183
+ printCode(filename, fileLine, lines) {
184
+ return (`<code class="language-typescript">` +
185
+ this.ansi2htmlMarkup(this.processLineByLine(filename, fileLine, lines)) +
186
+ `</code>`);
187
+ }
188
+ printScreenshotComparison(id, attachmentExpected, attachmentActual, attachmentDiff) {
189
+ let output = "";
190
+ let base64ExpectedImage = this.convertBase64(attachmentExpected.path);
191
+ let base64ActualImage = this.convertBase64(attachmentActual.path);
192
+ let base64DiffImage = this.convertBase64(attachmentDiff.path);
193
+ let expectedFileType = attachmentExpected.contentType?.split("/").pop();
194
+ let actualFileType = attachmentActual.contentType?.split("/").pop();
195
+ let diffFileType = attachmentDiff.contentType?.split("/").pop();
196
+ attachmentExpected.name =
197
+ attachmentExpected.name.slice(-4) === "." + expectedFileType
198
+ ? attachmentExpected.name.slice(0, -4)
199
+ : attachmentExpected.name;
200
+ attachmentActual.name =
201
+ attachmentActual.name.slice(-4) === "." + actualFileType
202
+ ? attachmentActual.name.slice(0, -4)
203
+ : attachmentActual.name;
204
+ attachmentDiff.name =
205
+ attachmentDiff.name.slice(-4) === "." + diffFileType
206
+ ? attachmentDiff.name.slice(0, -4)
207
+ : attachmentDiff.name;
208
+ if (base64ExpectedImage !== "" &&
209
+ base64ActualImage !== "" &&
210
+ base64DiffImage !== "") {
211
+ if (this.execOptions.embedAttachments === true) {
212
+ output = `
213
+ <div class="row ms-3 me-3"><div class="col text-center">
214
+ <img-comparison-slider tabindex="0" class="rendered odhin-border coloured-slider">
215
+ <figure slot="first" class="before">
216
+ <img src="data:${attachmentExpected.contentType};base64,${base64ExpectedImage}" class="control-attachment-size">
217
+ <figcaption>Expected</figcaption>
218
+ </figure>
219
+ <figure slot="second" class="after">
220
+ <img src="data:${attachmentActual.contentType};base64,${base64ActualImage}" class="control-attachment-size">
221
+ <figcaption>Actual</figcaption>
222
+ </figure>
223
+ </img-comparison-slider>
224
+ </div></div>
225
+
226
+ <div class="row ms-3 me-3">
227
+ <div class="col text-end odhin-attachments-label">${attachmentExpected.name + "." + expectedFileType}</div>
228
+ <div class="col text-start odhin-attachments-buttons-card"><a class="download-btn" target="_Blank" download="${attachmentExpected.name + "." + expectedFileType}" href="data:${attachmentExpected.contentType};base64,${base64ExpectedImage}">Download Expected <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-box-arrow-down" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M3.5 10a.5.5 0 0 1-.5-.5v-8a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 .5.5v8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 0 0 1h2A1.5 1.5 0 0 0 14 9.5v-8A1.5 1.5 0 0 0 12.5 0h-9A1.5 1.5 0 0 0 2 1.5v8A1.5 1.5 0 0 0 3.5 11h2a.5.5 0 0 0 0-1z"/><path fill-rule="evenodd" d="M7.646 15.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 14.293V5.5a.5.5 0 0 0-1 0v8.793l-2.146-2.147a.5.5 0 0 0-.708.708z"/></svg></a></div>
229
+ </div>
230
+
231
+ <div class="row ms-3 me-3">
232
+ <div class="col text-end odhin-attachments-label">${attachmentActual.name + "." + actualFileType}</div>
233
+ <div class="col text-start odhin-attachments-buttons-card"><a class="download-btn" target="_Blank" download="${attachmentActual.name + "." + actualFileType}" href="data:${attachmentActual.contentType};base64,${base64ActualImage}">Download Actual <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-box-arrow-down" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M3.5 10a.5.5 0 0 1-.5-.5v-8a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 .5.5v8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 0 0 1h2A1.5 1.5 0 0 0 14 9.5v-8A1.5 1.5 0 0 0 12.5 0h-9A1.5 1.5 0 0 0 2 1.5v8A1.5 1.5 0 0 0 3.5 11h2a.5.5 0 0 0 0-1z"/><path fill-rule="evenodd" d="M7.646 15.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 14.293V5.5a.5.5 0 0 0-1 0v8.793l-2.146-2.147a.5.5 0 0 0-.708.708z"/></svg></a></div>
234
+ </div>
235
+
236
+ <div class="row ms-3 me-3 mb-5 odhin-thin-border-bottom">
237
+ <div class="col text-end odhin-attachments-label">${attachmentDiff.name + "." + actualFileType}</div>
238
+ <div class="col text-start odhin-attachments-buttons-card"><a class="download-btn" target="_Blank" download="${attachmentDiff.name + "." + actualFileType}" href="data:${attachmentDiff.contentType};base64,${base64DiffImage}">Download Diff <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-box-arrow-down" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M3.5 10a.5.5 0 0 1-.5-.5v-8a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 .5.5v8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 0 0 1h2A1.5 1.5 0 0 0 14 9.5v-8A1.5 1.5 0 0 0 12.5 0h-9A1.5 1.5 0 0 0 2 1.5v8A1.5 1.5 0 0 0 3.5 11h2a.5.5 0 0 0 0-1z"/><path fill-rule="evenodd" d="M7.646 15.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 14.293V5.5a.5.5 0 0 0-1 0v8.793l-2.146-2.147a.5.5 0 0 0-.708.708z"/></svg></a></div>
239
+ </div>`;
240
+ }
241
+ else {
242
+ let finalExpectedFilename = this.base64ToFile(base64ExpectedImage, this.execOptions.outputFolder +
243
+ "/screenshots/" +
244
+ id +
245
+ "/" +
246
+ attachmentExpected.name +
247
+ "." +
248
+ expectedFileType);
249
+ let finalActualFilename = this.base64ToFile(base64ActualImage, this.execOptions.outputFolder +
250
+ "/screenshots/" +
251
+ id +
252
+ "/" +
253
+ attachmentActual.name +
254
+ "." +
255
+ actualFileType);
256
+ let finalDiffFilename = this.base64ToFile(base64DiffImage, this.execOptions.outputFolder +
257
+ "/screenshots/" +
258
+ id +
259
+ "/" +
260
+ attachmentDiff.name +
261
+ "." +
262
+ diffFileType);
263
+ output = `
264
+ <div class="row ms-3 me-3"><div class="col text-center">
265
+ <img-comparison-slider tabindex="0" class="rendered odhin-thin-border coloured-slider">
266
+ <figure slot="first" class="before">
267
+ <img src="screenshots/${id + "/" + finalExpectedFilename}" class="control-attachment-size">
268
+ <figcaption>Expected</figcaption>
269
+ </figure>
270
+ <figure slot="second" class="after">
271
+ <img src="screenshots/${id + "/" + finalActualFilename}" class="control-attachment-size">
272
+ <figcaption>Actual</figcaption>
273
+ </figure>
274
+ </img-comparison-slider>
275
+ </div></div>
276
+
277
+ <div class="row ms-3 me-3">
278
+ <div class="col text-end odhin-attachments-label">${finalExpectedFilename}</div>
279
+ <div class="col text-start odhin-attachments-buttons-card"><a class="download-btn" target="_Blank" download="${finalExpectedFilename}" href="screenshots/${id + "/" + finalExpectedFilename}">Download Expected <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-box-arrow-down" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M3.5 10a.5.5 0 0 1-.5-.5v-8a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 .5.5v8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 0 0 1h2A1.5 1.5 0 0 0 14 9.5v-8A1.5 1.5 0 0 0 12.5 0h-9A1.5 1.5 0 0 0 2 1.5v8A1.5 1.5 0 0 0 3.5 11h2a.5.5 0 0 0 0-1z"/><path fill-rule="evenodd" d="M7.646 15.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 14.293V5.5a.5.5 0 0 0-1 0v8.793l-2.146-2.147a.5.5 0 0 0-.708.708z"/></svg></a></div>
280
+ </div>
281
+
282
+ <div class="row ms-3 me-3">
283
+ <div class="col text-end odhin-attachments-label">${finalActualFilename}</div>
284
+ <div class="col text-start odhin-attachments-buttons-card"><a class="download-btn" target="_Blank" download="${finalActualFilename}" href="screenshots/${id + "/" + finalActualFilename}">Download Actual <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-box-arrow-down" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M3.5 10a.5.5 0 0 1-.5-.5v-8a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 .5.5v8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 0 0 1h2A1.5 1.5 0 0 0 14 9.5v-8A1.5 1.5 0 0 0 12.5 0h-9A1.5 1.5 0 0 0 2 1.5v8A1.5 1.5 0 0 0 3.5 11h2a.5.5 0 0 0 0-1z"/><path fill-rule="evenodd" d="M7.646 15.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 14.293V5.5a.5.5 0 0 0-1 0v8.793l-2.146-2.147a.5.5 0 0 0-.708.708z"/></svg></a></div>
285
+ </div>
286
+
287
+ <div class="row ms-3 me-3 mb-5 odhin-thin-border-bottom">
288
+ <div class="col text-end odhin-attachments-label">${finalDiffFilename}</div>
289
+ <div class="col text-start odhin-attachments-buttons-card"><a class="download-btn" target="_Blank" download="${finalDiffFilename}" href="screenshots/${id + "/" + finalDiffFilename}">Download Diff <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-box-arrow-down" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M3.5 10a.5.5 0 0 1-.5-.5v-8a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 .5.5v8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 0 0 1h2A1.5 1.5 0 0 0 14 9.5v-8A1.5 1.5 0 0 0 12.5 0h-9A1.5 1.5 0 0 0 2 1.5v8A1.5 1.5 0 0 0 3.5 11h2a.5.5 0 0 0 0-1z"/><path fill-rule="evenodd" d="M7.646 15.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 14.293V5.5a.5.5 0 0 0-1 0v8.793l-2.146-2.147a.5.5 0 0 0-.708.708z"/></svg></a></div>
290
+ </div>`;
291
+ }
292
+ }
293
+ return output;
294
+ }
295
+ printScreenshot(id, attachment) {
296
+ let output = "";
297
+ let base64Image = this.convertBase64(attachment.path);
298
+ let screenshotFilename = attachment.path
299
+ .replace("\\", "/")
300
+ .split("/")
301
+ .pop();
302
+ if (base64Image !== "") {
303
+ if (this.execOptions.embedAttachments === true) {
304
+ output = `
305
+ <div class="row ms-3 me-3"><div class="col text-center"><img src="data:${attachment.contentType};base64,${base64Image}" class="img-fluid mt-3 odhin-thin-border control-attachment-size" alt="Screenshot"></div></div><div class="row ms-3 me-3 mb-5 odhin-thin-border-bottom"><div class="col text-end odhin-attachments-label">${screenshotFilename}</div><div class="col text-left odhin-attachments-buttons-card"><a class="download-btn" target="_Blank" download="${attachment.name}" href="data:${attachment.contentType};base64,${base64Image}">Download <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-box-arrow-down" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M3.5 10a.5.5 0 0 1-.5-.5v-8a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 .5.5v8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 0 0 1h2A1.5 1.5 0 0 0 14 9.5v-8A1.5 1.5 0 0 0 12.5 0h-9A1.5 1.5 0 0 0 2 1.5v8A1.5 1.5 0 0 0 3.5 11h2a.5.5 0 0 0 0-1z"/><path fill-rule="evenodd" d="M7.646 15.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 14.293V5.5a.5.5 0 0 0-1 0v8.793l-2.146-2.147a.5.5 0 0 0-.708.708z"/></svg></a></div></div>`;
306
+ }
307
+ else {
308
+ let finalFilename = this.base64ToFile(base64Image, this.execOptions.outputFolder +
309
+ "/screenshots/" +
310
+ id +
311
+ "/" +
312
+ screenshotFilename);
313
+ output = `
314
+ <div class="row ms-3 me-3"><div class="col text-center"><img src="screenshots/${id + "/" + finalFilename}" class="img-fluid mt-3 odhin-thin-border control-attachment-size" alt="Screenshot"></div></div>
315
+ <div class="row ms-3 me-3 mb-5 odhin-thin-border-bottom"><div class="col text-end odhin-attachments-label">${screenshotFilename}</div>
316
+ <div class="col text-left odhin-attachments-buttons-card"><a class="download-btn" target="_Blank" download="${finalFilename}" href="screenshots/${id + "/" + finalFilename}">Download <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-box-arrow-down" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M3.5 10a.5.5 0 0 1-.5-.5v-8a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 .5.5v8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 0 0 1h2A1.5 1.5 0 0 0 14 9.5v-8A1.5 1.5 0 0 0 12.5 0h-9A1.5 1.5 0 0 0 2 1.5v8A1.5 1.5 0 0 0 3.5 11h2a.5.5 0 0 0 0-1z"/><path fill-rule="evenodd" d="M7.646 15.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 14.293V5.5a.5.5 0 0 0-1 0v8.793l-2.146-2.147a.5.5 0 0 0-.708.708z"/></svg></a></div></div>`;
317
+ }
318
+ }
319
+ return output;
320
+ }
321
+ printImage(id, attachment) {
322
+ let output = "";
323
+ let base64Image = Buffer.from(attachment.body).toString("base64");
324
+ let fileType = attachment.contentType?.split("/").pop();
325
+ if (base64Image !== "") {
326
+ if (this.execOptions.embedAttachments === true) {
327
+ output = `
328
+ <div class="row ms-3 me-3"><div class="col text-center"><img src="data:${attachment.contentType};base64,${base64Image}" class="img-fluid mt-3 odhin-thin-border control-attachment-size" alt="Screenshot"></div></div>
329
+ <div class="row ms-3 me-3 mb-5 odhin-thin-border-bottom"><div class="col text-end odhin-attachments-label">${attachment.name + "." + fileType}</div><div class="col text-left odhin-attachments-buttons-card"><a class="download-btn" target="_Blank" download="${attachment.name + "." + fileType}" href="data:${attachment.contentType};base64,${base64Image}">Download <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-box-arrow-down" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M3.5 10a.5.5 0 0 1-.5-.5v-8a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 .5.5v8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 0 0 1h2A1.5 1.5 0 0 0 14 9.5v-8A1.5 1.5 0 0 0 12.5 0h-9A1.5 1.5 0 0 0 2 1.5v8A1.5 1.5 0 0 0 3.5 11h2a.5.5 0 0 0 0-1z"/><path fill-rule="evenodd" d="M7.646 15.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 14.293V5.5a.5.5 0 0 0-1 0v8.793l-2.146-2.147a.5.5 0 0 0-.708.708z"/></svg></a></div></div>`;
330
+ }
331
+ else {
332
+ let finalFilename = this.base64ToFile(base64Image, this.execOptions.outputFolder +
333
+ "/screenshots/" +
334
+ id +
335
+ "/" +
336
+ attachment.name +
337
+ "." +
338
+ fileType);
339
+ output = `
340
+ <div class="row ms-3 me-3"><div class="col text-center"><img src="screenshots/${id + "/" + finalFilename}" class="img-fluid mt-3 odhin-thin-border control-attachment-size" alt="Screenshot"></div></div>
341
+ <div class="row ms-3 me-3 mb-5 odhin-thin-border-bottom"><div class="col text-end odhin-attachments-label">${attachment.name + "." + fileType}</div>
342
+ <div class="col text-left odhin-attachments-buttons-card"><a class="download-btn" target="_Blank" download="${finalFilename}" href="screenshots/${id + "/" + finalFilename}">Download <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-box-arrow-down" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M3.5 10a.5.5 0 0 1-.5-.5v-8a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 .5.5v8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 0 0 1h2A1.5 1.5 0 0 0 14 9.5v-8A1.5 1.5 0 0 0 12.5 0h-9A1.5 1.5 0 0 0 2 1.5v8A1.5 1.5 0 0 0 3.5 11h2a.5.5 0 0 0 0-1z"/><path fill-rule="evenodd" d="M7.646 15.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 14.293V5.5a.5.5 0 0 0-1 0v8.793l-2.146-2.147a.5.5 0 0 0-.708.708z"/></svg></a></div></div>`;
343
+ }
344
+ }
345
+ return output;
346
+ }
347
+ printScreenshotsTabButton(modalId) {
348
+ return `<button class="result-tablinks-${modalId}" onclick="openResultTab(event, 'TabScreenshots-${modalId}', '${modalId}')">Screenshots</button>`;
349
+ }
350
+ printScreenshots(screenshots, modalId) {
351
+ return `<div id="TabScreenshots-${modalId}" style="display: none" class="result-tabcontent result-tabcontent-${modalId}">${screenshots}</div>`;
352
+ }
353
+ printVideo(id, attachment) {
354
+ let output = "";
355
+ let base64Video = this.convertBase64(attachment.path);
356
+ let videoFilename = attachment.path.replace("\\", "/").split("/").pop();
357
+ if (base64Video !== "") {
358
+ if (this.execOptions.embedAttachments === true) {
359
+ output = `
360
+ <div class="row ms-3 me-3"><div class="col text-center"><video controls playsinline autoplay muted loop src="data:${attachment.contentType};base64,${base64Video}" class="object-fit-contain mt-3 odhin-thin-border control-attachment-size" autoplay></video></div></div>
361
+ <div class="row ms-3 me-3 mb-5 odhin-thin-border-bottom"><div class="col text-end odhin-attachments-label">${videoFilename}</div>
362
+ <div class="col text-left odhin-attachments-buttons-card"><a class="download-btn" target="_Blank" download="${videoFilename}" href="data:${attachment.contentType};base64,${base64Video}">Download <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-box-arrow-down" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M3.5 10a.5.5 0 0 1-.5-.5v-8a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 .5.5v8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 0 0 1h2A1.5 1.5 0 0 0 14 9.5v-8A1.5 1.5 0 0 0 12.5 0h-9A1.5 1.5 0 0 0 2 1.5v8A1.5 1.5 0 0 0 3.5 11h2a.5.5 0 0 0 0-1z"/><path fill-rule="evenodd" d="M7.646 15.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 14.293V5.5a.5.5 0 0 0-1 0v8.793l-2.146-2.147a.5.5 0 0 0-.708.708z"/></svg></a></div></div>`;
363
+ }
364
+ else {
365
+ let finalFilename = this.base64ToFile(base64Video, this.execOptions.outputFolder + "/videos/" + id + "/" + videoFilename);
366
+ output = `
367
+ <div class="row ms-3 me-3"><div class="col text-center"><video controls playsinline autoplay muted loop src="videos/${id + "/" + finalFilename}" class="object-fit-contain mt-3 odhin-thin-border control-attachment-size" autoplay></video></div></div>
368
+ <div class="row ms-3 me-3 mb-5 odhin-thin-border-bottom"><div class="col text-end odhin-attachments-label">${videoFilename}</div>
369
+ <div class="col text-left odhin-attachments-buttons-card"><a class="download-btn" target="_Blank" download="${finalFilename}" href="videos/${id + "/" + finalFilename}">Download <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-box-arrow-down" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M3.5 10a.5.5 0 0 1-.5-.5v-8a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 .5.5v8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 0 0 1h2A1.5 1.5 0 0 0 14 9.5v-8A1.5 1.5 0 0 0 12.5 0h-9A1.5 1.5 0 0 0 2 1.5v8A1.5 1.5 0 0 0 3.5 11h2a.5.5 0 0 0 0-1z"/><path fill-rule="evenodd" d="M7.646 15.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 14.293V5.5a.5.5 0 0 0-1 0v8.793l-2.146-2.147a.5.5 0 0 0-.708.708z"/></svg></a></div></div>`;
370
+ }
371
+ }
372
+ return output;
373
+ }
374
+ printVideosTabButton(modalId) {
375
+ return `<button class="result-tablinks-${modalId}" onclick="openResultTab(event, 'TabVideos-${modalId}', '${modalId}')">Videos</button>`;
376
+ }
377
+ printVideos(videos, modalId) {
378
+ return `<div id="TabVideos-${modalId}" style="display: none" class="result-tabcontent result-tabcontent-${modalId}">${videos}</div>`;
379
+ }
380
+ printStdoutTabButton(modalId) {
381
+ return `<button class="result-tablinks-${modalId}" onclick="openResultTab(event, 'TabStdout-${modalId}', '${modalId}')">Stdout</button>`;
382
+ }
383
+ printStdout(id, stdout) {
384
+ return `<div id="TabStdout-${id}" style="display: none" class="result-tabcontent result-tabcontent-${id}">
385
+ <div class="row ps-3 pe-3"><div class="col"><pre class="pre"><code>${this.htmlEncode(stdout.replaceAll('"', "'").replaceAll("\n,", "\n"))}</code></pre></div></div><div class="row ms-3 me-3"><div class="col text-center odhin-attachments-buttons-card-no-bottom"><a class="download-btn" target="_Blank" download="stdout.txt" href="data:text/html,${this.htmlEncode(stdout.replaceAll('"', "'").replaceAll("\n,", "\n"))}">Download Stdout <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-box-arrow-down" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M3.5 10a.5.5 0 0 1-.5-.5v-8a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 .5.5v8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 0 0 1h2A1.5 1.5 0 0 0 14 9.5v-8A1.5 1.5 0 0 0 12.5 0h-9A1.5 1.5 0 0 0 2 1.5v8A1.5 1.5 0 0 0 3.5 11h2a.5.5 0 0 0 0-1z"/><path fill-rule="evenodd" d="M7.646 15.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 14.293V5.5a.5.5 0 0 0-1 0v8.793l-2.146-2.147a.5.5 0 0 0-.708.708z"/></svg></a></div>
386
+ </div></div>`;
387
+ }
388
+ printStderrTabButton(modalId) {
389
+ return `<button class="result-tablinks-${modalId}" onclick="openResultTab(event, 'TabStderr-${modalId}', '${modalId}')">Stderr</button>`;
390
+ }
391
+ printStderr(id, stderr) {
392
+ return `<div id="TabStderr-${id}" style="display: none" class="result-tabcontent result-tabcontent-${id}">
393
+ <div class="row ps-3 pe-3"><div class="col"><pre class="pre"><code>${this.htmlEncode(stderr.replaceAll('"', "'").replaceAll("\n,", "\n"))}</code></pre></div></div><div class="row ms-3 me-3"><div class="col text-center odhin-attachments-buttons-card-no-bottom"><a class="download-btn" target="_Blank" download="stderr.txt" href="data:text/html,${this.htmlEncode(stderr.replaceAll('"', "'").replaceAll("\n,", "\n"))}">Download Stderr <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-box-arrow-down" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M3.5 10a.5.5 0 0 1-.5-.5v-8a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 .5.5v8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 0 0 1h2A1.5 1.5 0 0 0 14 9.5v-8A1.5 1.5 0 0 0 12.5 0h-9A1.5 1.5 0 0 0 2 1.5v8A1.5 1.5 0 0 0 3.5 11h2a.5.5 0 0 0 0-1z"/><path fill-rule="evenodd" d="M7.646 15.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 14.293V5.5a.5.5 0 0 0-1 0v8.793l-2.146-2.147a.5.5 0 0 0-.708.708z"/></svg></a></div>
394
+ </div></div>`;
395
+ }
396
+ printTraceTabButton(modalId) {
397
+ return `<button class="result-tablinks-${modalId}" onclick="openResultTab(event, 'TabTrace-${modalId}', '${modalId}')">Trace</button>`;
398
+ }
399
+ printTrace(id, attachment) {
400
+ let output = "";
401
+ let base64trace = this.convertBase64(attachment.path);
402
+ let fileType = attachment.contentType?.split("/").pop();
403
+ if (base64trace !== "") {
404
+ if (this.execOptions.embedAttachments === true) {
405
+ output = `
406
+ <div class="row ms-3 me-3 mb-5"><div class="col text-center odhin-attachments-buttons-card-no-bottom"><a class="download-btn" target="_Blank" download="${attachment.name}" href="data:${attachment.contentType};base64,${base64trace}">Download Trace <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-box-arrow-down" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M3.5 10a.5.5 0 0 1-.5-.5v-8a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 .5.5v8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 0 0 1h2A1.5 1.5 0 0 0 14 9.5v-8A1.5 1.5 0 0 0 12.5 0h-9A1.5 1.5 0 0 0 2 1.5v8A1.5 1.5 0 0 0 3.5 11h2a.5.5 0 0 0 0-1z"/><path fill-rule="evenodd" d="M7.646 15.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 14.293V5.5a.5.5 0 0 0-1 0v8.793l-2.146-2.147a.5.5 0 0 0-.708.708z"/></svg></a></div></div>`;
407
+ }
408
+ else {
409
+ let finalFilename = this.base64ToFile(base64trace, this.execOptions.outputFolder +
410
+ "/traces/" +
411
+ id +
412
+ "/" +
413
+ attachment.name +
414
+ "." +
415
+ fileType);
416
+ output = `
417
+ <div class="row ms-3 me-3 mb-1"><div class="col text-center odhin-attachments-buttons-card-no-bottom"><a class="download-btn" target="_Blank" download="${finalFilename}" href="traces/${id + "/" + finalFilename}">Download Trace <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-box-arrow-down" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M3.5 10a.5.5 0 0 1-.5-.5v-8a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 .5.5v8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 0 0 1h2A1.5 1.5 0 0 0 14 9.5v-8A1.5 1.5 0 0 0 12.5 0h-9A1.5 1.5 0 0 0 2 1.5v8A1.5 1.5 0 0 0 3.5 11h2a.5.5 0 0 0 0-1z"/><path fill-rule="evenodd" d="M7.646 15.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 14.293V5.5a.5.5 0 0 0-1 0v8.793l-2.146-2.147a.5.5 0 0 0-.708.708z"/></svg></a></div></div>`;
418
+ }
419
+ }
420
+ return `<div id="TabTrace-${id}" style="display: none" class="result-tabcontent result-tabcontent-${id}">${output}</div>`;
421
+ }
422
+ convertBase64(fileToConvert) {
423
+ try {
424
+ return fs_1.default.readFileSync(fileToConvert, { encoding: "base64" });
425
+ }
426
+ catch (err) {
427
+ return "";
428
+ }
429
+ }
430
+ getShortFilePath(filepath, testFolder) {
431
+ if (filepath.includes("/" + testFolder + "/")) {
432
+ return filepath.split("/" + testFolder + "/")[1];
433
+ }
434
+ else if (filepath.includes("\\" + testFolder + "\\")) {
435
+ return filepath.split("\\" + testFolder + "\\")[1];
436
+ }
437
+ else {
438
+ return filepath;
439
+ }
440
+ }
441
+ createFile(filepath, content) {
442
+ try {
443
+ fs_extra_1.default.outputFileSync(filepath, content);
444
+ }
445
+ catch (err) {
446
+ if (this.showError === true) {
447
+ console.error("Error creating file: " + filepath);
448
+ }
449
+ }
450
+ }
451
+ updateHtml(html, tag, content, contentType, filename) {
452
+ let output = "";
453
+ if (this.execOptions.embedAssets === true) {
454
+ if (contentType === "css") {
455
+ output = html.replace(tag, "<style>" + content + "</style>");
456
+ }
457
+ else if (contentType === "js") {
458
+ output = html.replace(tag, "<script type='text/javascript'>" + content + "</script>");
459
+ }
460
+ }
461
+ else {
462
+ if (contentType === "css") {
463
+ this.createFile(this.execOptions.outputFolder + "/css/" + filename, content);
464
+ output = html.replace(tag, "<link rel='stylesheet' href='css/" + filename + "' >");
465
+ }
466
+ else if (contentType === "js") {
467
+ this.createFile(this.execOptions.outputFolder + "/js/" + filename, content);
468
+ output = html.replace(tag, "<script type='text/javascript' src='js/" + filename + "' ></script>");
469
+ }
470
+ }
471
+ return output;
472
+ }
473
+ base64ToFile(base64data, filePath, index) {
474
+ if (fs_1.default.existsSync(filePath)) {
475
+ let newIndex = index !== undefined && index > 0 ? index + 1 : 1;
476
+ let filename = filePath.split("/").pop();
477
+ let splitName = filename.split(".");
478
+ let newFilePath = filePath.replaceAll(filename, splitName[0] + "_" + newIndex + "." + splitName[1]);
479
+ return this.base64ToFile(base64data, newFilePath, newIndex);
480
+ }
481
+ else {
482
+ let buf = Buffer.from(base64data, "base64");
483
+ fs_extra_1.default.outputFileSync(filePath, buf);
484
+ return filePath.split("/").pop()?.toString();
485
+ }
486
+ }
487
+ async getPortFree() {
488
+ await portfinder_1.default
489
+ .getPortPromise()
490
+ .then((port) => {
491
+ this.freePort = port;
492
+ })
493
+ .catch((err) => {
494
+ throw err;
495
+ });
496
+ return this.freePort.toString();
497
+ }
498
+ isValidURL(url) {
499
+ const urlPattern = /^(https?|ftp):\/\/[^\s/$.?#].[^\s]*$/i;
500
+ return urlPattern.test(url);
501
+ }
502
+ }
503
+ exports.default = Help;