import React, { Suspense, useEffect, useState } from "react"; import type { SessionTimeline } from "./SessionPlayer"; const TestSource = React.lazy(() => import("./TestSource")); const TerminalCast = React.lazy(() => import("./TerminalCast")); const SessionPlayer = React.lazy(() => import("./SessionPlayer")); // --------------------------------------------------------------------------- // The matrix (scenario × target health) plus a per-run artifact page. The // test SOURCE is where correctness is reviewed; this site only answers "is // everything green" and hands you the debugging artifacts (Playwright trace, // session video, screenshots, failure output) for any run. // --------------------------------------------------------------------------- interface ManifestRun { scenario: string; target: string; slug: string; ok: boolean; durationMs?: number; endedAt?: number; } interface Manifest { generatedAt: number; runs: ManifestRun[]; skips: Array<{ scenario: string; target: string; missing: string[] }>; } interface RunResult { scenario: string; target: string; ok: boolean; startedAt: number; endedAt: number; durationMs: number; error?: string; artifacts: string[]; } const useRoute = () => { const [hash, setHash] = useState(window.location.hash); useEffect(() => { const onChange = () => setHash(window.location.hash); window.addEventListener("hashchange", onChange); return () => window.removeEventListener("hashchange", onChange); }, []); const parts = hash.replace(/^#\/?/, "").split("/").filter(Boolean); return parts.length >= 2 ? { target: parts[0], slug: parts[1] } : null; }; export const App = () => { const route = useRoute(); return route ? : ; }; // --------------------------------------------------------------------------- // Matrix // --------------------------------------------------------------------------- const Matrix = () => { const [manifest, setManifest] = useState(null); const [error, setError] = useState(null); useEffect(() => { fetch("manifest.json") .then((r) => r.json()) .then(setManifest) .catch((e) => setError(String(e))); }, []); if (error) return
failed to load manifest.json: {error}
; if (!manifest) return
loading…
; const targets = [...new Set(manifest.runs.map((r) => r.target))].sort(); const scenarios = [ ...new Set([...manifest.runs, ...manifest.skips].map((r) => r.scenario)), ].sort(); const runFor = (scenario: string, target: string) => manifest.runs .filter((r) => r.scenario === scenario && r.target === target) .sort((a, b) => (b.endedAt ?? 0) - (a.endedAt ?? 0))[0]; const skipFor = (scenario: string, target: string) => manifest.skips.find((s) => s.scenario === scenario && s.target === target); return (

Executor e2e — every scenario, on every deployment

Click a result for that run's artifacts (Playwright trace, video, screenshots, failure output). “—” = capability not on that target.

{targets.map((t) => ( ))} {scenarios.map((scenario) => ( {targets.map((target) => { const run = runFor(scenario, target); if (run) { return ( ); } return ( ); })} ))}
scenario{t}
{scenario} {run.ok ? "✓ passed" : "✗ FAILED"} {run.durationMs != null && ( {(run.durationMs / 1000).toFixed(1)}s )} {skipFor(scenario, target) ? "—" : "·"}

generated {new Date(manifest.generatedAt).toLocaleString()}

); }; // --------------------------------------------------------------------------- // Run page: status + error + artifacts. The trace opens in Playwright's own // viewer, SELF-HOSTED at /trace-viewer (copied out of playwright-core by // rebuild-viewer.ts). trace.playwright.dev would work on localhost but not // over tailscale: it's HTTPS, this server is HTTP, and browsers block the // mixed-content fetch of trace.zip. Same-origin avoids all of it. // --------------------------------------------------------------------------- // The suite's motel (local OTLP store, booted by the global setup on a // fixed port). Every run exports distributed traces there; the run page // links its harvested trace ids straight into motel's per-trace waterfall. const MOTEL_VIEWER = "http://127.0.0.1:4796"; interface RunTraceRef { id: string; at: number; url: string; } type RunTab = "session" | "browser" | "terminal" | "source"; const RunView = ({ target, slug }: { target: string; slug: string }) => { const base = `${target}/${slug}`; const [result, setResult] = useState(null); const [error, setError] = useState(null); const [tab, setTab] = useState(null); const [traces, setTraces] = useState([]); const [timeline, setTimeline] = useState(null); useEffect(() => { fetch(`${base}/result.json`) .then((r) => r.json()) .then(setResult) .catch((e) => setError(String(e))); fetch(`${base}/traces.json`) .then((r) => (r.ok ? r.json() : [])) .then(setTraces) .catch(() => setTraces([])); fetch(`${base}/timeline.json`) .then((r) => (r.ok ? r.json() : null)) .then(setTimeline) .catch(() => setTimeline(null)); }, [base]); if (error) return
failed to load run: {error}
; if (!result) return
loading…
; const has = (name: string) => result.artifacts.includes(name); const screenshots = result.artifacts.filter((a) => a.endsWith(".png")).sort(); const video = has("session.mp4") ? "session.mp4" : has("session.webm") ? "session.webm" : null; const film = has("film.mp4") ? "film.mp4" : null; const cast = has("terminal.cast") ? "terminal.cast" : null; const traceUrl = has("trace.zip") ? new URL( `trace-viewer/index.html?trace=${encodeURIComponent( new URL(`${base}/trace.zip`, window.location.href).toString(), )}`, window.location.href, ).toString() : null; // The live two-recording player needs both recordings AND a focus // timeline whose clocks are anchored. Anything less falls back to // film.mp4 (pre-rendered cuts) or the single recording. const playable = Boolean( cast && video && timeline && timeline.focus.length >= 2 && timeline.anchors.terminal !== undefined && timeline.anchors.browser !== undefined, ); const tabs: Array<{ id: RunTab; label: string; available: boolean }> = [ { id: "session", label: "▶ session", available: playable || Boolean(film) }, { id: "browser", label: "browser", available: Boolean(video) }, { id: "terminal", label: "terminal", available: Boolean(cast) }, { id: "source", label: " test source", available: has("test.ts") }, ]; const available = tabs.filter((entry) => entry.available); const active: RunTab | undefined = tab ?? available[0]?.id; return (

{result.ok ? "✓ PASSED" : "✗ FAILED"} · {result.scenario}

{result.target} · {(result.durationMs / 1000).toFixed(1)}s ·{" "} {new Date(result.endedAt).toLocaleString()}

{result.error &&
{result.error}
} {available.length > 1 && (
{available.map((entry) => ( ))}
)} {active === "session" && (playable && cast && video && timeline ? ( loading session player…

}>
) : ( film && (
); }; const labelOf = (file: string): string => file .replace(/\.png$/, "") .replace(/^\d+-/, "") .replace(/-/g, " ");