Infrastructure: - adapters/: base, tauri (port 9750), electrobun (port 9761 + PTY daemon) - helpers/: 120+ centralized selectors, reusable actions, custom assertions - wdio.shared.conf.js + stack-specific configs 18 unified specs (205 tests): splash(6) smoke(15) settings(19) terminal(14) agent(15) search(12) files(15) comms(10) tasks(10) theme(12) groups(12) keyboard(8) notifications(10) diagnostics(8) status-bar(12) context(9) worktree(8) llm-judged(10) Daemon: --stack tauri|electrobun|both flag Scripts: test:e2e:tauri, test:e2e:electrobun, test:e2e:both
108 lines
3.3 KiB
TypeScript
108 lines
3.3 KiB
TypeScript
#!/usr/bin/env tsx
|
|
// Agent Orchestrator E2E Test Daemon — CLI entry point
|
|
// Usage: tsx index.ts [--full] [--spec <pattern>] [--watch] [--agent] [--stack tauri|electrobun|both]
|
|
|
|
import { resolve, dirname } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { watch } from 'node:fs';
|
|
import { Dashboard } from './dashboard.ts';
|
|
import { runSpecs, discoverSpecs, specDisplayName, clearCache, type RunOptions, type StackTarget } from './runner.ts';
|
|
import { AgentBridge } from './agent-bridge.ts';
|
|
|
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
const SPECS_DIR = resolve(__dirname, '../specs');
|
|
|
|
// ── CLI args ──
|
|
const args = process.argv.slice(2);
|
|
const fullMode = args.includes('--full');
|
|
const watchMode = args.includes('--watch');
|
|
const agentMode = args.includes('--agent');
|
|
const specIdx = args.indexOf('--spec');
|
|
const specPattern = specIdx !== -1 ? args[specIdx + 1] : undefined;
|
|
const stackIdx = args.indexOf('--stack');
|
|
const stackTarget: StackTarget = stackIdx !== -1
|
|
? (args[stackIdx + 1] as StackTarget) ?? 'tauri'
|
|
: 'tauri';
|
|
|
|
// Validate stack target
|
|
if (!['tauri', 'electrobun', 'both'].includes(stackTarget)) {
|
|
console.error(`Invalid --stack value: ${stackTarget}. Use: tauri, electrobun, or both`);
|
|
process.exit(1);
|
|
}
|
|
|
|
console.log(`E2E Test Daemon — stack: ${stackTarget}`);
|
|
|
|
// ── Init ──
|
|
const dashboard = new Dashboard();
|
|
let bridge: AgentBridge | null = null;
|
|
let pendingRerun: RunOptions | null = null;
|
|
|
|
if (agentMode) {
|
|
bridge = new AgentBridge(dashboard);
|
|
bridge.onRerunRequest((opts) => { pendingRerun = opts; });
|
|
bridge.start();
|
|
}
|
|
|
|
// ── Run cycle ──
|
|
async function runCycle(opts: RunOptions = {}): Promise<void> {
|
|
const specs = discoverSpecs(opts.pattern ?? specPattern);
|
|
dashboard.setTests(specs.map((s) => ({ name: specDisplayName(s), specFile: s })));
|
|
dashboard.startRefresh();
|
|
bridge?.setRunning(true);
|
|
|
|
await runSpecs({
|
|
pattern: opts.pattern ?? specPattern,
|
|
full: opts.full ?? fullMode,
|
|
stack: opts.stack ?? stackTarget,
|
|
onResult: (r) => dashboard.updateTest(r.name, r.status, r.durationMs, r.error),
|
|
});
|
|
|
|
dashboard.markComplete();
|
|
dashboard.stopRefresh();
|
|
bridge?.setRunning(false);
|
|
}
|
|
|
|
function shutdown(poll?: ReturnType<typeof setInterval>, watcher?: ReturnType<typeof watch>): void {
|
|
watcher?.close();
|
|
if (poll) clearInterval(poll);
|
|
dashboard.stop();
|
|
bridge?.stop();
|
|
process.exit(0);
|
|
}
|
|
|
|
// ── Main ──
|
|
async function main(): Promise<void> {
|
|
if (fullMode) clearCache();
|
|
await runCycle();
|
|
|
|
if (watchMode || agentMode) {
|
|
const watcher = watchMode
|
|
? watch(SPECS_DIR, { recursive: false }, (_ev, f) => {
|
|
if (f?.endsWith('.test.ts')) pendingRerun = { pattern: specPattern, full: false };
|
|
})
|
|
: undefined;
|
|
|
|
const poll = setInterval(async () => {
|
|
if (pendingRerun) {
|
|
const opts = pendingRerun;
|
|
pendingRerun = null;
|
|
await runCycle(opts);
|
|
}
|
|
}, 1000);
|
|
|
|
process.on('SIGINT', () => shutdown(poll, watcher));
|
|
process.on('SIGTERM', () => shutdown(poll, watcher));
|
|
} else {
|
|
dashboard.stop();
|
|
bridge?.stop();
|
|
const hasFailed = dashboard.getTests().some((t) => t.status === 'failed');
|
|
process.exit(hasFailed ? 1 : 0);
|
|
}
|
|
}
|
|
|
|
main().catch((err) => {
|
|
console.error('Fatal error:', err);
|
|
dashboard.stop();
|
|
bridge?.stop();
|
|
process.exit(1);
|
|
});
|