import { discoverReportCatalog } from "./report-catalog.js"; import path from "node:path"; import { mkdir } from "node:fs/promises"; import { chromium } from "@playwright/test"; import { runnerMatrix } from "./catalog.js"; import type { RunnerE2ECampaign } from "./types.js"; const MAX_PUBLIC_EXECUTION_DURATION_MS = 24 * 60 * 60 * 1_000; function html(value: unknown) { return String(value ?? "") .replaceAll("&", "&") .replaceAll("<", "<") .replaceAll(">", ">") .replaceAll('"', """) .replaceAll("'", "'"); } function durationLabel(durationMs: number) { const seconds = Math.round(durationMs / 1_000); if (seconds < 60) return `${seconds}s`; const minutes = Math.floor(seconds / 60); if (minutes < 60) return `${minutes}m ${seconds % 60}s`; return `${Math.floor(minutes / 60)}h ${minutes % 60}m`; } export function renderPublicCampaignSummary(campaign: RunnerE2ECampaign) { const catalog = discoverReportCatalog({ catalog: runnerMatrix, expected: campaign.expected, results: campaign.results, }); const catalogById = new Map( catalog.map((execution) => [execution.id, execution]), ); const expectedIds = [ ...new Set( campaign.expected.filter((executionId) => catalogById.has(executionId)), ), ]; const expectedIdSet = new Set(expectedIds); const resultById = new Map( campaign.results .filter((result) => expectedIdSet.has(result.executionId)) .map((result) => [result.executionId, result]), ); const isPassed = (executionId: string) => { const result = resultById.get(executionId); return result?.status === "passed" && result.cleanup === "passed"; }; const passed = expectedIds.filter(isPassed).length; const durationMs = [...resultById.values()].reduce( (total, result) => total + (Number.isFinite(result.durationMs) && result.durationMs >= 0 ? Math.min(result.durationMs, MAX_PUBLIC_EXECUTION_DURATION_MS) : 0), 0, ); const suites = [ ...new Map( catalog.map((execution) => [execution.suite.id, execution.suite.label]), ), ].map(([suiteId, label]) => { const selectedIds = expectedIds.filter( (executionId) => catalogById.get(executionId)?.suite.id === suiteId, ); return { label, selected: selectedIds.length, passed: selectedIds.filter(isPassed).length, }; }); const rows = suites .filter((suite) => suite.selected > 0) .map( (suite) => `
${html(suite.label)} ${suite.passed}/${suite.selected} ${suite.passed === suite.selected ? "Passed" : "Needs attention"}
`, ) .join(""); return `
PPaperclip
Trusted history publication

Runner full-stack E2E

Campaign summary

${passed}/${expectedIds.length}Selected executions passed
${expectedIds.length - passed}Failed or incomplete
${html(durationLabel(durationMs))}Total test time
${rows}
`; } export async function writePublicCampaignSummaryImage( campaign: RunnerE2ECampaign, output: string, ) { await mkdir(path.dirname(output), { recursive: true }); const browser = await chromium.launch({ headless: true, env: { LANG: "C.UTF-8", PATH: process.env.PATH ?? "", TMPDIR: process.env.RUNNER_TEMP ?? "/tmp", }, }); try { const context = await browser.newContext({ viewport: { width: 1400, height: 900 }, deviceScaleFactor: 1, }); await context.setOffline(true); await context.route("**/*", (route) => route.abort()); const page = await context.newPage(); await page.setContent(renderPublicCampaignSummary(campaign), { waitUntil: "domcontentloaded", }); await page.screenshot({ path: output, type: "png", fullPage: true, animations: "disabled", }); await context.close(); } finally { await browser.close(); } }