VOICED_SELFTEST_OUT runs the Diagnostics checks plus an issue, archive, export (searchable, flattened, re-render), cancel and archive-immutability flow through the real IPC, writes a JSON report and exits. Inert without the variable; doubles as the per-release gate on installed builds.
87 lines
3.1 KiB
TypeScript
87 lines
3.1 KiB
TypeScript
import { invoke } from "@tauri-apps/api/core";
|
|
import type { SelfTestReport } from "./lib/selfTest";
|
|
import type { StepResult } from "./lib/selfTestSteps";
|
|
|
|
/**
|
|
* Unattended self-test mode, enabled by the VOICED_SELFTEST_OUT environment variable (the backend
|
|
* returns the report path, or null in normal use). It runs the diagnostics and an end-to-end invoice
|
|
* flow in this very webview, writes a JSON report and makes the process exit with the verdict.
|
|
*/
|
|
|
|
const WATCHDOG_MS = 120_000;
|
|
|
|
export async function maybeRunSelfTest(): Promise<void> {
|
|
let reportPath: string | null = null;
|
|
try {
|
|
reportPath = await invoke<string | null>("selftest_config");
|
|
} catch {
|
|
return; // Not running inside Tauri (plain browser dev server).
|
|
}
|
|
if (!reportPath) return;
|
|
const path = reportPath;
|
|
|
|
const start = performance.now();
|
|
const startedAt = new Date().toISOString();
|
|
let diagnostics: SelfTestReport | null = null;
|
|
let steps: StepResult[] = [];
|
|
let renderMode: () => string = () => "unknown";
|
|
let finished = false;
|
|
|
|
const finish = async (ok: boolean, error?: string) => {
|
|
if (finished) return;
|
|
finished = true;
|
|
const body = {
|
|
ok,
|
|
...(error ? { error } : {}),
|
|
startedAt,
|
|
totalMs: Math.round(performance.now() - start),
|
|
userAgent: navigator.userAgent,
|
|
renderMode: renderMode(),
|
|
steps,
|
|
diagnostics,
|
|
};
|
|
try {
|
|
await invoke("selftest_report", { json: JSON.stringify(body, null, 2), ok });
|
|
} catch (e) {
|
|
console.error("self-test report could not be written", e);
|
|
}
|
|
};
|
|
|
|
const failWith = (what: string) => void finish(false, what);
|
|
const timer = window.setTimeout(() => {
|
|
if (finished) return;
|
|
void finish(false, "timeout");
|
|
}, WATCHDOG_MS);
|
|
window.addEventListener("error", (e) => failWith(`uncaught error: ${e.message}`));
|
|
window.addEventListener("unhandledrejection", (e) => failWith(`unhandled rejection: ${String(e.reason)}`));
|
|
|
|
try {
|
|
const [{ createStepLog }, { runSelfTest }, { browserSelfTestDeps }, { runE2eSteps }, { getRenderClient }] = await Promise.all([
|
|
import("./lib/selfTestSteps"),
|
|
import("./lib/selfTest"),
|
|
import("./lib/selfTestBrowser"),
|
|
import("./lib/selfTestE2e"),
|
|
import("./lib/renderClient"),
|
|
]);
|
|
renderMode = () => getRenderClient().mode;
|
|
const log = createStepLog();
|
|
steps = log.steps;
|
|
|
|
await log.run("diagnostics: render, rasterise, fingerprint, flatten", async () => {
|
|
const report = await runSelfTest(browserSelfTestDeps(), (r) => {
|
|
diagnostics = r;
|
|
});
|
|
diagnostics = report;
|
|
const failed = report.sections.flatMap((s) => s.checks.filter((c) => c.status === "fail").map((c) => `${s.title} / ${c.label}: ${c.detail ?? ""}`));
|
|
return { ok: report.passed, detail: failed.length ? failed.join("\n") : "all checks passed" };
|
|
});
|
|
await runE2eSteps(log, path);
|
|
|
|
window.clearTimeout(timer);
|
|
await finish(log.steps.every((s) => s.ok));
|
|
} catch (e) {
|
|
window.clearTimeout(timer);
|
|
await finish(false, e instanceof Error ? `${e.name}: ${e.message}\n${e.stack ?? ""}` : String(e));
|
|
}
|
|
}
|