#!/usr/bin/env npx zx /** * Test runner for Paseo CLI E2E tests * * Runs all test phases in sequence and reports results. * Each test is a separate .ts file that can also be run independently. */ import { $ } from "zx"; import { readdir, writeFile } from "fs/promises"; import { join, dirname } from "path"; import { fileURLToPath } from "url"; const __dirname = dirname(fileURLToPath(import.meta.url)); const args = process.argv.slice(2); const testEnvDefaults = { PASEO_LOCAL_SPEECH_AUTO_DOWNLOAD: process.env.PASEO_LOCAL_SPEECH_AUTO_DOWNLOAD ?? "0", PASEO_DICTATION_ENABLED: process.env.PASEO_DICTATION_ENABLED ?? "0", PASEO_VOICE_MODE_ENABLED: process.env.PASEO_VOICE_MODE_ENABLED ?? "0", }; let jsonOutputPath: string | null = null; for (let i = 0; i < args.length; i++) { const arg = args[i]; if (arg === "--json-output") { const value = args[i + 1]; if (!value) { throw new Error("--json-output requires a file path"); } jsonOutputPath = value; i++; continue; } } $.verbose = false; type Failure = { test: string; error: string }; async function runCommand(label: string, command: string): Promise { console.log(`\n${"โ”€".repeat(50)}`); console.log(`๐Ÿ”ง ${label}...`); console.log("โ”€".repeat(50)); const result = await $`bash -lc ${command}`.nothrow(); if (result.exitCode !== 0) { const error = result.stderr || result.stdout || `Exit code: ${result.exitCode}`; console.error(`\nโŒ ${label} failed`); console.error(error); throw new Error(error); } } async function writeJsonSummary({ passed, failed, failures, }: { passed: number; failed: number; failures: Failure[]; }) { if (!jsonOutputPath) { return; } await writeFile( jsonOutputPath, JSON.stringify( { suite: "cli-local", command: "npm run test:local --workspace=@getpaseo/cli", counts: { passed, failed, skipped: 0, }, skippedTests: [], failures: failures.map(({ test, error }) => ({ test, error: error.split("\n")[0] ?? "", })), }, null, 2, ) + "\n", ); } console.log("๐Ÿงช Paseo CLI E2E Test Runner\n"); console.log("=".repeat(50)); // Discover all test files const files = await readdir(__dirname); const testFiles = files.filter((f) => f.match(/^\d{2}-.*\.test\.ts$/)).sort(); if (testFiles.length === 0) { console.log("โŒ No test files found"); await writeJsonSummary({ passed: 0, failed: 0, failures: [] }); process.exit(1); } console.log(`Found ${testFiles.length} test file(s):\n`); for (const file of testFiles) { console.log(` - ${file}`); } console.log(); let passed = 0; let failed = 0; const failures: Failure[] = []; await runCommand("Building relay", "npm run build --workspace=@getpaseo/relay"); await runCommand("Building server", "npm run build --workspace=@getpaseo/server"); await runCommand("Building CLI", "npm run build --workspace=@getpaseo/cli"); for (const testFile of testFiles) { const testPath = join(__dirname, testFile); const testName = testFile.replace(/\.test\.ts$/, ""); console.log(`\n${"โ”€".repeat(50)}`); console.log(`๐Ÿ“‹ Running ${testName}...`); console.log("โ”€".repeat(50)); try { const result = await $`PASEO_LOCAL_SPEECH_AUTO_DOWNLOAD=${testEnvDefaults.PASEO_LOCAL_SPEECH_AUTO_DOWNLOAD} PASEO_DICTATION_ENABLED=${testEnvDefaults.PASEO_DICTATION_ENABLED} PASEO_VOICE_MODE_ENABLED=${testEnvDefaults.PASEO_VOICE_MODE_ENABLED} npx tsx ${testPath}`.nothrow(); if (result.exitCode === 0) { console.log(`\nโœ… ${testName} PASSED`); passed++; } else { console.log(`\nโŒ ${testName} FAILED (exit code: ${result.exitCode})`); if (result.stderr) { console.log("stderr:", result.stderr); } failed++; failures.push({ test: testName, error: result.stderr || `Exit code: ${result.exitCode}` }); break; } } catch (e) { const error = e instanceof Error ? e.message : String(e); console.log(`\nโŒ ${testName} FAILED`); console.log("Error:", error); failed++; failures.push({ test: testName, error }); break; } } // Summary console.log("\n" + "=".repeat(50)); console.log("๐Ÿ“Š Test Results"); console.log("=".repeat(50)); console.log(` โœ… Passed: ${passed}`); console.log(` โŒ Failed: ${failed}`); console.log(` ๐Ÿ“ Total: ${passed + failed}`); if (failures.length > 0) { console.log("\nโŒ Failed tests:"); for (const { test, error } of failures) { console.log(` - ${test}`); if (error) { console.log(` ${error.split("\n")[0]}`); } } } console.log(); await writeJsonSummary({ passed, failed, failures }); process.exit(failed > 0 ? 1 : 0);