/** * Test Runners — Registry Surface * * Discovers installed test-runner packages, loads their suites into * sw.testing, and provides a dashboard to run and view results. */ (async function () { 'use strict'; var mount = document.getElementById('extension-mount'); if (!mount) return; mount.classList.add('ext-test-runners-root'); // ─── Boot SDK ───────────────────────────────────────────── try { var base = window.__BASE__ || ''; var ver = window.__VERSION__ || '0'; if (!window.preact) { var { h, render } = await import(base + '/js/sw/vendor/preact.module.js'); var hooksModule = await import(base + '/js/sw/vendor/hooks.module.js'); var htmModule = await import(base + '/js/sw/vendor/htm.module.js'); window.preact = { h, render }; window.hooks = hooksModule; window.html = htmModule.default.bind(h); } var sdk = await import(base + '/js/sw/sdk/index.js?v=' + ver); await sdk.boot(); } catch (e) { mount.innerHTML = '
SDK boot failed: ' + e.message + '
'; return; } var { h, render } = preact; var { useState, useEffect, useCallback } = hooks; // ─── Shell Topbar ───────────────────────────────────────── if (sw.shell && sw.shell.topbar) { sw.shell.topbar.setTitle('Test Runners'); } // ─── Status Icons ───────────────────────────────────────── var STATUS_ICON = { passed: '\u2713', failed: '\u2717', warned: '\u26A0', skipped: '\u2014' }; // ─── App Component ──────────────────────────────────────── function App() { var _s = useState(null); var runners = _s[0], setRunners = _s[1]; var _s2 = useState(null); var allPkgs = _s2[0], setAllPkgs = _s2[1]; var _s3 = useState(null); var results = _s3[0], setResults = _s3[1]; var _s4 = useState(false); var running = _s4[0], setRunning = _s4[1]; var _s5 = useState(null); var runningSuite = _s5[0], setRunningSuite = _s5[1]; var _s6 = useState(''); var error = _s6[0], setError = _s6[1]; var _s7 = useState(false); var loaded = _s7[0], setLoaded = _s7[1]; // Discover runners and load their scripts useEffect(function () { discoverAndLoad().catch(function (e) { setError('Failed to discover runners: ' + e.message); }); }, []); async function discoverAndLoad() { // Fetch all packages (admin endpoint) var pkgsResp = await sw.api.get('/api/v1/admin/packages'); var pkgs = pkgsResp.data || pkgsResp; setAllPkgs(pkgs); // Filter test-runner type var testRunners = pkgs.filter(function (p) { return p.type === 'test-runner' && p.enabled; }); setRunners(testRunners); // Clear any previously registered suites sw.testing.clear(); // Load each runner's main.js to register suites. // Runner main.js scripts are async IIFEs that load sub-modules sequentially, // so the script onload fires before suites are registered. We load all runner // scripts, then poll until suite count stabilizes. var surfaceBase = (window.__BASE__ || '') + '/surfaces/'; for (var i = 0; i < testRunners.length; i++) { var r = testRunners[i]; await loadRunnerScript(surfaceBase + r.id + '/js/main.js?v=' + (r.version || '0') + '.' + Date.now()); } // Wait for suites to stabilize (runners load sub-modules async) await waitForSuites(); setLoaded(true); console.log('[TestRunners] Loaded ' + testRunners.length + ' runners, ' + sw.testing.suites().length + ' suites'); } function loadRunnerScript(src) { return new Promise(function (resolve) { var s = document.createElement('script'); s.src = src; s.onload = resolve; s.onerror = function () { console.warn('[TestRunners] Failed to load: ' + src); resolve(); }; document.body.appendChild(s); }); } function waitForSuites() { return new Promise(function (resolve) { var lastCount = 0; var stableCount = 0; var interval = setInterval(function () { var count = sw.testing.suites().length; if (count === lastCount && count > 0) { stableCount++; if (stableCount >= 3) { clearInterval(interval); resolve(); } } else { stableCount = 0; lastCount = count; } }, 500); // Timeout after 30s regardless setTimeout(function () { clearInterval(interval); resolve(); }, 30000); }); } // Runner dependency declarations. // Stored here (not in manifests) to avoid the package system marking // runners as dormant due to install-order timing during bundled install. var RUNNER_REQUIRES = { 'notes-runner': ['notes'], 'chat-runner': ['chat', 'chat-core'], 'schedules-runner': ['schedules'], 'workflow-runner': ['content-approval'], 'renderer-runner': ['mermaid-renderer'] }; // Check requires for a runner function checkRequires(runner) { var requires = RUNNER_REQUIRES[runner.id] || runner.manifest?.requires || runner.requires || []; if (!requires.length || !allPkgs) return { met: requires, missing: [] }; var installedIds = allPkgs.filter(function (p) { return p.enabled; }).map(function (p) { return p.id; }); var met = [], missing = []; for (var i = 0; i < requires.length; i++) { if (installedIds.indexOf(requires[i]) !== -1) met.push(requires[i]); else missing.push(requires[i]); } return { met: met, missing: missing }; } // Run all suites // Post results to kernel API for CI consumption (fire-and-forget) function postResults(runnerId, data) { sw.api.post('/api/v1/admin/test-runners/results', { runner_id: runnerId || 'all', results: data }).catch(function (e) { console.warn('[TestRunners] Failed to store results:', e.message); }); } var runAll = useCallback(async function () { if (running) return; setRunning(true); setResults(null); setRunningSuite('all'); try { var r = await sw.testing.run(); setResults(r); postResults('all', r); } catch (e) { setError('Run failed: ' + e.message); } setRunning(false); setRunningSuite(null); }, [running]); // Run suites for a single runner var runRunner = useCallback(async function (runner) { if (running) return; setRunning(true); setRunningSuite(runner.id); // Map runner package ID to suite name prefix. // ICD/SDK runners use 'icd/'/'sdk/' prefixes. // Package runners use the package name they test: 'notes/', 'chat/', etc. var prefix; if (runner.id === 'icd-test-runner') prefix = 'icd/'; else if (runner.id === 'sdk-test-runner') prefix = 'sdk/'; else prefix = runner.id.replace(/-runner$/, '') + '/'; var suiteNames = sw.testing.suites().filter(function (n) { return n.indexOf(prefix) === 0; }); var allSuiteResults = []; var summary = { total: 0, passed: 0, failed: 0, warned: 0, skipped: 0 }; var start = performance.now(); for (var i = 0; i < suiteNames.length; i++) { var r = await sw.testing.run(suiteNames[i]); for (var j = 0; j < r.suites.length; j++) { allSuiteResults.push(r.suites[j]); for (var k = 0; k < r.suites[j].tests.length; k++) { var t = r.suites[j].tests[k]; summary.total++; if (t.status === 'passed') summary.passed++; else if (t.status === 'failed') summary.failed++; else if (t.status === 'warned') summary.warned++; else if (t.status === 'skipped') summary.skipped++; } } if (r.suites[0] && r.suites[0].status === 'skipped') summary.skipped++; } var runnerResults = { timestamp: new Date().toISOString(), duration_ms: Math.round(performance.now() - start), summary: summary, suites: allSuiteResults }; setResults(runnerResults); postResults(runner.id, runnerResults); setRunning(false); setRunningSuite(null); }, [running]); if (error) { return html`${error}
No test-runner packages installed.
Install packages with "type": "test-runner" to see them here.
${desc}
` : null} ${req.missing.length > 0 ? html`Missing required packages: ${req.missing.join(', ')}
` : req.met.length > 0 ? html`Requires: ${req.met.join(', ')}
` : null } ${runnerSuites.length > 0 ? html`<${ResultsPanel} suites=${runnerSuites} />` : null}