(() => { "use strict"; const MAX_RENDERED_RESULTS = 2_000; const MAX_DETAIL_CHARS = 24_000; const MAX_HIGHLIGHTED_RAW_CHARS = 4 * 1024 * 1024; const rawSources = new WeakMap(); function escapeHtml(value) { return String(value ?? "") .replaceAll("&", "&") .replaceAll("<", "<") .replaceAll(">", ">") .replaceAll('"', """) .replaceAll("'", "'"); } function icon(name) { const safeName = /^[a-z-]+$/.test(name) ? name : "file"; return ``; } function formatDate(value) { const date = new Date(value); return Number.isNaN(date.getTime()) ? "" : new Intl.DateTimeFormat(undefined, { dateStyle: "medium", timeStyle: "short", }).format(date); } function elements(parent, localName) { if (!parent?.getElementsByTagName) return []; return [...parent.getElementsByTagName("*")].filter( (element) => element.localName === localName, ); } function firstElement(parent, localName) { return elements(parent, localName)[0] ?? null; } function directChild(parent, localName) { return ( [...(parent?.children ?? [])].find( (element) => element.localName === localName, ) ?? null ); } function elementText(element) { return String(element?.textContent ?? "").trim(); } function attribute(element, name) { return String(element?.getAttribute(name) ?? "").trim(); } function outcomeCategory(outcome) { const normalized = String(outcome ?? "") .toLocaleLowerCase() .replace(/[^a-z]/g, ""); if (normalized === "passed") return "passed"; if ( [ "failed", "error", "timeout", "aborted", "passedbutrunaborted", "disconnected", ].includes(normalized) ) { return "failed"; } if ( ["notexecuted", "notrunnable", "inconclusive", "pending"].includes( normalized, ) ) { return "skipped"; } return "other"; } function outcomeIcon(category) { if (category === "passed") return "check-circle"; if (category === "failed") return "x-circle"; if (category === "skipped") return "clock"; return "workflow"; } const KNOWN_TRX_OUTCOMES = new Set([ "passed", "failed", "completed", "error", "warning", "inconclusive", "aborted", "timeout", "inprogress", "notexecuted", ]); function summarizedRunOutcome(report) { const normalized = String(report.outcome ?? "").toLowerCase(); if (!KNOWN_TRX_OUTCOMES.has(normalized) || normalized === "completed") { if (report.summaryCounts.failed > 0) return "Failed"; if (report.summaryCounts.passed > 0) return "Passed"; if (report.summaryCounts.skipped > 0) return "Skipped"; } return report.outcome; } function parseDuration(value) { const duration = String(value ?? "").trim(); if (!duration) return null; const clock = duration.match( /^(?:(\d+)\.)?(\d{1,2}):(\d{2}):(\d{2}(?:\.\d+)?)$/, ); if (clock) { const [, days = "0", hours, minutes, seconds] = clock; return ( Number(days) * 86_400_000 + Number(hours) * 3_600_000 + Number(minutes) * 60_000 + Number(seconds) * 1_000 ); } const iso = duration.match( /^P(?:(\d+(?:\.\d+)?)D)?(?:T(?:(\d+(?:\.\d+)?)H)?(?:(\d+(?:\.\d+)?)M)?(?:(\d+(?:\.\d+)?)S)?)?$/i, ); if (!iso) return null; const [, days = "0", hours = "0", minutes = "0", seconds = "0"] = iso; return ( Number(days) * 86_400_000 + Number(hours) * 3_600_000 + Number(minutes) * 60_000 + Number(seconds) * 1_000 ); } function formatDuration(durationMs) { if (!Number.isFinite(durationMs)) return ""; if (durationMs < 1_000) { return `${Math.max(0, Math.round(durationMs))} ms`; } if (durationMs < 60_000) { const seconds = durationMs / 1_000; return `${seconds .toFixed(seconds < 10 ? 2 : 1) .replace(/\.0+$|(\.\d*[1-9])0+$/, "$1")} s`; } if (durationMs < 3_600_000) { const minutes = Math.floor(durationMs / 60_000); const seconds = Math.floor((durationMs % 60_000) / 1_000); return `${minutes}m ${seconds}s`; } const hours = Math.floor(durationMs / 3_600_000); const minutes = Math.floor((durationMs % 3_600_000) / 60_000); return `${hours}h ${minutes}m`; } function parseReport(value) { const source = String(value ?? ""); if (/ element.localName?.toLocaleLowerCase() === "parsererror", ); if (parserError) { const detail = elementText(parserError).replace(/\s+/g, " ").slice(0, 240); throw new Error(detail || "The XML document is malformed."); } const root = documentNode.documentElement; if (root?.localName !== "TestRun") { throw new Error( "The XML document does not contain a TRX TestRun root element.", ); } const definitions = new Map(); for (const unitTest of elements(root, "UnitTest")) { const testId = attribute(unitTest, "id").toLocaleLowerCase(); if (!testId) continue; const method = firstElement(unitTest, "TestMethod"); definitions.set(testId, { name: attribute(unitTest, "name"), className: attribute(method, "className"), methodName: attribute(method, "name"), codeBase: attribute(method, "codeBase"), adapterTypeName: attribute(method, "adapterTypeName"), }); } const testLists = new Map(); for (const testList of elements(root, "TestList")) { const id = attribute(testList, "id").toLocaleLowerCase(); if (id) testLists.set(id, attribute(testList, "name")); } const results = []; const resultsElement = directChild(root, "Results"); const isResultElement = (element) => element.localName?.endsWith("TestResult") || element.localName === "TestResultAggregation"; const readResult = (element, depth) => { const testId = attribute(element, "testId").toLocaleLowerCase(); const definition = definitions.get(testId) ?? {}; const output = directChild(element, "Output"); const errorInfo = firstElement(output, "ErrorInfo"); const outcome = attribute(element, "outcome") || "Unknown"; const testListId = attribute(element, "testListId").toLocaleLowerCase(); const durationValue = attribute(element, "duration"); const resultFiles = elements(output, "ResultFile") .map((file) => attribute(file, "path")) .filter(Boolean); const properties = elements(output, "Property") .map((property) => { const key = elementText(directChild(property, "Key")) || attribute(property, "name"); const propertyValue = elementText(directChild(property, "Value")) || attribute(property, "value"); return key ? { key, value: propertyValue } : null; }) .filter(Boolean); return { depth, outcome, category: outcomeCategory(outcome), testName: attribute(element, "testName") || definition.name || definition.methodName || "Unnamed test", className: definition.className || attribute(element, "className"), methodName: definition.methodName, codeBase: definition.codeBase, adapterTypeName: definition.adapterTypeName, computerName: attribute(element, "computerName"), startTime: attribute(element, "startTime"), endTime: attribute(element, "endTime"), durationMs: parseDuration(durationValue), executionId: attribute(element, "executionId"), testId: attribute(element, "testId"), testListName: testLists.get(testListId) ?? "", dataRowInfo: attribute(element, "dataRowInfo"), message: elementText(firstElement(errorInfo, "Message")), stackTrace: elementText(firstElement(errorInfo, "StackTrace")), stdout: elementText(firstElement(output, "StdOut")), stderr: elementText(firstElement(output, "StdErr")), debugTrace: elementText(firstElement(output, "DebugTrace")), resultFiles, properties, }; }; const visitResults = (parent, depth = 0) => { for (const element of parent?.children ?? []) { if (!isResultElement(element)) continue; results.push(readResult(element, depth)); const innerResults = directChild(element, "InnerResults"); if (innerResults) visitResults(innerResults, depth + 1); } }; visitResults(resultsElement); const countersElement = firstElement(root, "Counters"); const counters = {}; for (const counterAttribute of countersElement?.attributes ?? []) { const number = Number(counterAttribute.value); if (Number.isFinite(number) && number >= 0) { counters[counterAttribute.name] = number; } } const resultCounts = results.reduce( (counts, result) => { counts[result.category] += 1; return counts; }, { passed: 0, failed: 0, skipped: 0, other: 0 }, ); const sumCounters = (names, fallback) => { const values = names .filter((name) => Number.isFinite(counters[name])) .map((name) => counters[name]); return values.length > 0 ? values.reduce((total, number) => total + number, 0) : fallback; }; const times = firstElement(root, "Times"); const startTime = attribute(times, "start"); const finishTime = attribute(times, "finish"); const startTimestamp = new Date(startTime).getTime(); const finishTimestamp = new Date(finishTime).getTime(); const durationMs = Number.isFinite(startTimestamp) && Number.isFinite(finishTimestamp) && finishTimestamp >= startTimestamp ? finishTimestamp - startTimestamp : null; const summaryElement = firstElement(root, "ResultSummary"); return { name: attribute(root, "name") || "Test run", runId: attribute(root, "id"), user: attribute(root, "runUser"), computerName: attribute(root, "computerName"), outcome: attribute(summaryElement, "outcome") || "Unknown", startTime, finishTime, durationMs, resultCounts, summaryCounts: { total: Number.isFinite(counters.total) ? counters.total : results.length, passed: sumCounters(["passed"], resultCounts.passed), failed: sumCounters( [ "failed", "error", "timeout", "aborted", "passedButRunAborted", "disconnected", ], resultCounts.failed, ), skipped: sumCounters( ["notExecuted", "notRunnable", "inconclusive", "pending"], resultCounts.skipped, ), }, results, }; } function renderMetadataItem(label, value, { mono = false } = {}) { if (!value) return ""; return `
`; } function truncateDetail(value) { const text = String(value ?? ""); if (text.length <= MAX_DETAIL_CHARS) { return { text, truncated: false }; } return { text: text.slice(0, MAX_DETAIL_CHARS), truncated: true, }; } function renderDetailBlock(label, value) { if (!value) return ""; const detail = truncateDetail(value); return `${escapeHtml(detail.text)}
${
detail.truncated
? `Output truncated after ${MAX_DETAIL_CHARS.toLocaleString()} characters. View the raw XML for the full value.
` : "" }No additional details were recorded for this result.
'}${escapeHtml(message)}
${highlighted}
Try another search or outcome filter.