Add unattended end-to-end self-test mode for the production webview
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.
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
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));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user