odhin-reports-playwright
Version:
Odhin Reports for Playwright
507 lines (500 loc) • 35.2 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const ansi_to_html_1 = __importDefault(require("ansi-to-html"));
const path_1 = __importDefault(require("path"));
const fs_1 = __importDefault(require("fs"));
const fs_extra_1 = __importDefault(require("fs-extra"));
const portfinder_1 = __importDefault(require("portfinder"));
class Help {
constructor(execOptions) {
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");
this.ansiColors = {
0: "#000",
1: "#d85a51",
2: "#0C0",
3: "#d68043",
4: "#00C",
5: "#C0C",
6: "#0CC",
7: "#CCC",
8: "#555",
9: "#F55",
10: "#5F5",
11: "#FF5",
12: "#55F",
13: "#F5F",
14: "#5FF",
15: "#FFF",
};
this.errorCodeLinesToPrint = 4;
this.stepCodeLinesToPrint = 1;
this.freePort = 0;
this.showError = true;
this.execOptions = execOptions;
}
convertMsToTime(duration) {
let milliseconds = duration;
let seconds = Math.floor(milliseconds / 1000);
let minutes = Math.floor(seconds / 60);
let hours = Math.floor(minutes / 60);
milliseconds = milliseconds % 1000;
seconds = seconds % 60;
minutes = minutes % 60;
hours = hours % 24;
let out = "";
out += hours.toString() + "h ";
out += minutes.toString() + "m ";
out += seconds.toString() + "s ";
out += milliseconds.toString() + "ms ";
return out;
}
htmlEncode(input) {
return input
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">");
}
getRandomColor() {
var length = 6;
var chars = "0123456789ABCDEF";
var hex = "#";
while (length--)
hex += chars[(Math.random() * 16) | 0];
return hex;
}
stripAnsi(str) {
return str.replace(this.ansiRegex, "");
}
escapeHTML(text) {
return text.replace(/[&"<>]/g, (c) => ({ "&": "&", '"': """, "<": "<", ">": ">" }[c]));
}
ansi2htmlMarkup(text) {
const config = {
//bg: "var(--vscode-panel-background)",
//fg: "var(--vscode-foreground)",
colors: this.ansiColors,
};
return new ansi_to_html_1.default(config).toHtml(this.escapeHTML(text));
}
printStep(id, count, testStep, space) {
let icon = "";
let statusClass = "";
let title = "";
let body = "";
if (testStep.error !== undefined) {
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>`;
statusClass = "step-error-hover";
}
else {
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>`;
statusClass = "step-ok-hover";
}
if (testStep.location !== undefined) {
title = `<p class="stepLine" style="padding-left: ${space}px">${icon +
" " +
testStep.title +
" | <span class='fst-italic'>" +
testStep.location.file.replace(path_1.default.resolve("./") + "/", "") +
":" +
testStep.location.line}</span><label class="stepLineDuration">${this.convertMsToTime(testStep.duration)}</label></p>`;
let code = this.printCode(testStep.location.file, testStep.location.line, this.stepCodeLinesToPrint);
let startLine = testStep.location.line - this.stepCodeLinesToPrint <= 0
? 1
: testStep.location.line - this.stepCodeLinesToPrint;
body += `<pre style="margin-left: ${space + 20}px;" class="line-numbers" data-start="${startLine}" >${code}</pre>`;
}
else {
title = `<p class="stepLine" style="padding-left: ${space}px">${icon + " " + testStep.title}<label class="stepLineDuration">${this.convertMsToTime(testStep.duration)}</label></p>`;
}
if (testStep.steps !== undefined) {
testStep.steps.forEach((step, index) => {
body += this.printStep(id, count + 10000 + index, step, space + 40);
});
}
if (body !== "") {
return `<div class="accordion accordion-flush " id="${id}-${count}-accordionFlush">
<div class="accordion-item">
<h2 class="accordion-header ${statusClass}">
<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}">
${title}
</button>
</h2>
<div id="flush-collapse-${id}-${count}" class="accordion-collapse collapse" data-bs-parent="#${id}-${count}-accordionFlush">
<div class="accordion-body accordion-body-steps">
${body}
</div>
</div>
</div>
</div>`;
}
else {
return `<div class="accordion accordion-flush " id="${id}-${count}-accordionFlush">
<div class="accordion-item">
<h2 class="accordion-header ${statusClass}">
<button class="accordion-steps-no-link ${statusClass}" type="button" >
${title}
</button>
</h2>
</div>
</div>`;
}
}
processLineByLine(filename, lineNumber, lines) {
let result = "";
try {
let codeLines = fs_1.default.readFileSync(filename, "utf-8").split(/\r?\n/);
codeLines.forEach(async (line, index) => {
let indx = index + 1;
if (indx >= lineNumber - lines && indx <= lineNumber + lines) {
result += line + "\n";
}
});
return result;
}
catch (error) {
return "";
}
}
printErrorsTabButton(modalId) {
return `<button class="result-tablinks-${modalId}" onclick="openResultTab(event, 'TabErrors-${modalId}', '${modalId}')">Errors</button>`;
}
printErrors(errors, errorCode, modalId) {
return `<div id="TabErrors-${modalId}" style="display: none" class="result-tabcontent result-tabcontent-${modalId}">
<pre class="container-fluid text-left pre">${this.ansi2htmlMarkup(errors)}</pre>
${errorCode}
</div>`;
}
printErrorCode(errorFile) {
let delimiter = ":";
let start = -2;
let errorFileLine = errorFile.split(delimiter).slice(start)[0];
let errorFilename = errorFile
.split(delimiter)
.slice(0, start)
.join(delimiter);
let startLine = Number(errorFileLine) - this.errorCodeLinesToPrint <= 0
? 1
: Number(errorFileLine) - this.errorCodeLinesToPrint;
return `<pre class="line-numbers" data-start="${startLine}" >${this.printCode(errorFilename, Number(errorFileLine), this.errorCodeLinesToPrint)}</pre>`;
}
printCode(filename, fileLine, lines) {
return (`<code class="language-typescript">` +
this.ansi2htmlMarkup(this.processLineByLine(filename, fileLine, lines)) +
`</code>`);
}
printScreenshotComparison(id, attachmentExpected, attachmentActual, attachmentDiff) {
let output = "";
let base64ExpectedImage = this.convertBase64(attachmentExpected.path);
let base64ActualImage = this.convertBase64(attachmentActual.path);
let base64DiffImage = this.convertBase64(attachmentDiff.path);
let expectedFileType = attachmentExpected.contentType?.split("/").pop();
let actualFileType = attachmentActual.contentType?.split("/").pop();
let diffFileType = attachmentDiff.contentType?.split("/").pop();
attachmentExpected.name =
attachmentExpected.name.slice(-4) === "." + expectedFileType
? attachmentExpected.name.slice(0, -4)
: attachmentExpected.name;
attachmentActual.name =
attachmentActual.name.slice(-4) === "." + actualFileType
? attachmentActual.name.slice(0, -4)
: attachmentActual.name;
attachmentDiff.name =
attachmentDiff.name.slice(-4) === "." + diffFileType
? attachmentDiff.name.slice(0, -4)
: attachmentDiff.name;
if (base64ExpectedImage !== "" &&
base64ActualImage !== "" &&
base64DiffImage !== "") {
if (this.execOptions.embedAttachments === true) {
output = `
<div class="row ms-3 me-3"><div class="col text-center">
<img-comparison-slider tabindex="0" class="rendered odhin-border coloured-slider">
<figure slot="first" class="before">
<img src="data:${attachmentExpected.contentType};base64,${base64ExpectedImage}" class="control-attachment-size">
<figcaption>Expected</figcaption>
</figure>
<figure slot="second" class="after">
<img src="data:${attachmentActual.contentType};base64,${base64ActualImage}" class="control-attachment-size">
<figcaption>Actual</figcaption>
</figure>
</img-comparison-slider>
</div></div>
<div class="row ms-3 me-3">
<div class="col text-end odhin-attachments-label">${attachmentExpected.name + "." + expectedFileType}</div>
<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>
</div>
<div class="row ms-3 me-3">
<div class="col text-end odhin-attachments-label">${attachmentActual.name + "." + actualFileType}</div>
<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>
</div>
<div class="row ms-3 me-3 mb-5 odhin-thin-border-bottom">
<div class="col text-end odhin-attachments-label">${attachmentDiff.name + "." + actualFileType}</div>
<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>
</div>`;
}
else {
let finalExpectedFilename = this.base64ToFile(base64ExpectedImage, this.execOptions.outputFolder +
"/screenshots/" +
id +
"/" +
attachmentExpected.name +
"." +
expectedFileType);
let finalActualFilename = this.base64ToFile(base64ActualImage, this.execOptions.outputFolder +
"/screenshots/" +
id +
"/" +
attachmentActual.name +
"." +
actualFileType);
let finalDiffFilename = this.base64ToFile(base64DiffImage, this.execOptions.outputFolder +
"/screenshots/" +
id +
"/" +
attachmentDiff.name +
"." +
diffFileType);
output = `
<div class="row ms-3 me-3"><div class="col text-center">
<img-comparison-slider tabindex="0" class="rendered odhin-thin-border coloured-slider">
<figure slot="first" class="before">
<img src="screenshots/${id + "/" + finalExpectedFilename}" class="control-attachment-size">
<figcaption>Expected</figcaption>
</figure>
<figure slot="second" class="after">
<img src="screenshots/${id + "/" + finalActualFilename}" class="control-attachment-size">
<figcaption>Actual</figcaption>
</figure>
</img-comparison-slider>
</div></div>
<div class="row ms-3 me-3">
<div class="col text-end odhin-attachments-label">${finalExpectedFilename}</div>
<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>
</div>
<div class="row ms-3 me-3">
<div class="col text-end odhin-attachments-label">${finalActualFilename}</div>
<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>
</div>
<div class="row ms-3 me-3 mb-5 odhin-thin-border-bottom">
<div class="col text-end odhin-attachments-label">${finalDiffFilename}</div>
<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>
</div>`;
}
}
return output;
}
printScreenshot(id, attachment) {
let output = "";
let base64Image = this.convertBase64(attachment.path);
let screenshotFilename = attachment.path
.replace("\\", "/")
.split("/")
.pop();
if (base64Image !== "") {
if (this.execOptions.embedAttachments === true) {
output = `
<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>`;
}
else {
let finalFilename = this.base64ToFile(base64Image, this.execOptions.outputFolder +
"/screenshots/" +
id +
"/" +
screenshotFilename);
output = `
<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>
<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="${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>`;
}
}
return output;
}
printImage(id, attachment) {
let output = "";
let base64Image = Buffer.from(attachment.body).toString("base64");
let fileType = attachment.contentType?.split("/").pop();
if (base64Image !== "") {
if (this.execOptions.embedAttachments === true) {
output = `
<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">${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>`;
}
else {
let finalFilename = this.base64ToFile(base64Image, this.execOptions.outputFolder +
"/screenshots/" +
id +
"/" +
attachment.name +
"." +
fileType);
output = `
<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>
<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="${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>`;
}
}
return output;
}
printScreenshotsTabButton(modalId) {
return `<button class="result-tablinks-${modalId}" onclick="openResultTab(event, 'TabScreenshots-${modalId}', '${modalId}')">Screenshots</button>`;
}
printScreenshots(screenshots, modalId) {
return `<div id="TabScreenshots-${modalId}" style="display: none" class="result-tabcontent result-tabcontent-${modalId}">${screenshots}</div>`;
}
printVideo(id, attachment) {
let output = "";
let base64Video = this.convertBase64(attachment.path);
let videoFilename = attachment.path.replace("\\", "/").split("/").pop();
if (base64Video !== "") {
if (this.execOptions.embedAttachments === true) {
output = `
<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>
<div class="row ms-3 me-3 mb-5 odhin-thin-border-bottom"><div class="col text-end odhin-attachments-label">${videoFilename}</div>
<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>`;
}
else {
let finalFilename = this.base64ToFile(base64Video, this.execOptions.outputFolder + "/videos/" + id + "/" + videoFilename);
output = `
<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>
<div class="row ms-3 me-3 mb-5 odhin-thin-border-bottom"><div class="col text-end odhin-attachments-label">${videoFilename}</div>
<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>`;
}
}
return output;
}
printVideosTabButton(modalId) {
return `<button class="result-tablinks-${modalId}" onclick="openResultTab(event, 'TabVideos-${modalId}', '${modalId}')">Videos</button>`;
}
printVideos(videos, modalId) {
return `<div id="TabVideos-${modalId}" style="display: none" class="result-tabcontent result-tabcontent-${modalId}">${videos}</div>`;
}
printStdoutTabButton(modalId) {
return `<button class="result-tablinks-${modalId}" onclick="openResultTab(event, 'TabStdout-${modalId}', '${modalId}')">Stdout</button>`;
}
printStdout(id, stdout) {
return `<div id="TabStdout-${id}" style="display: none" class="result-tabcontent result-tabcontent-${id}">
<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>
</div></div>`;
}
printStderrTabButton(modalId) {
return `<button class="result-tablinks-${modalId}" onclick="openResultTab(event, 'TabStderr-${modalId}', '${modalId}')">Stderr</button>`;
}
printStderr(id, stderr) {
return `<div id="TabStderr-${id}" style="display: none" class="result-tabcontent result-tabcontent-${id}">
<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>
</div></div>`;
}
printTraceTabButton(modalId) {
return `<button class="result-tablinks-${modalId}" onclick="openResultTab(event, 'TabTrace-${modalId}', '${modalId}')">Trace</button>`;
}
printTrace(id, attachment) {
let output = "";
let base64trace = this.convertBase64(attachment.path);
let fileType = attachment.contentType?.split("/").pop();
if (base64trace !== "") {
if (this.execOptions.embedAttachments === true) {
output = `
<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>`;
}
else {
let finalFilename = this.base64ToFile(base64trace, this.execOptions.outputFolder +
"/traces/" +
id +
"/" +
attachment.name +
"." +
fileType);
output = `
<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>`;
}
}
return `<div id="TabTrace-${id}" style="display: none" class="result-tabcontent result-tabcontent-${id}">${output}</div>`;
}
convertBase64(fileToConvert) {
try {
return fs_1.default.readFileSync(fileToConvert, { encoding: "base64" });
}
catch (err) {
return "";
}
}
getShortFilePath(filepath, testFolder) {
if (filepath.includes("/" + testFolder + "/")) {
return filepath.split("/" + testFolder + "/")[1];
}
else if (filepath.includes("\\" + testFolder + "\\")) {
return filepath.split("\\" + testFolder + "\\")[1];
}
else {
return filepath;
}
}
createFile(filepath, content) {
try {
fs_extra_1.default.outputFileSync(filepath, content);
}
catch (err) {
if (this.showError === true) {
console.error("Error creating file: " + filepath);
}
}
}
updateHtml(html, tag, content, contentType, filename) {
let output = "";
if (this.execOptions.embedAssets === true) {
if (contentType === "css") {
output = html.replace(tag, "<style>" + content + "</style>");
}
else if (contentType === "js") {
output = html.replace(tag, "<script type='text/javascript'>" + content + "</script>");
}
}
else {
if (contentType === "css" && content !== "") {
this.createFile(this.execOptions.outputFolder + "/css/" + filename, content);
output = html.replace(tag, "<link rel='stylesheet' href='css/" + filename + "' >");
}
else if (contentType === "js" && content !== "") {
this.createFile(this.execOptions.outputFolder + "/js/" + filename, content);
output = html.replace(tag, "<script type='text/javascript' src='js/" + filename + "' ></script>");
}
else {
output = html.replace(tag, "");
}
}
return output;
}
base64ToFile(base64data, filePath, index) {
if (fs_1.default.existsSync(filePath)) {
let newIndex = index !== undefined && index > 0 ? index + 1 : 1;
let filename = filePath.split("/").pop();
let splitName = filename.split(".");
let newFilePath = filePath.replaceAll(filename, splitName[0] + "_" + newIndex + "." + splitName[1]);
return this.base64ToFile(base64data, newFilePath, newIndex);
}
else {
let buf = Buffer.from(base64data, "base64");
fs_extra_1.default.outputFileSync(filePath, buf);
return filePath.split("/").pop()?.toString();
}
}
async getPortFree() {
await portfinder_1.default
.getPortPromise()
.then((port) => {
this.freePort = port;
})
.catch((err) => {
throw err;
});
return this.freePort.toString();
}
isValidURL(url) {
const urlPattern = /^(https?|ftp):\/\/[^\s/$.?#].[^\s]*$/i;
return urlPattern.test(url);
}
}
exports.default = Help;