helpers.ts
1 /** 2 * Shared helpers for E2E tests. 3 * Runs the built opencli binary as a subprocess. 4 */ 5 6 import { execFile } from 'node:child_process'; 7 import { promisify } from 'node:util'; 8 import * as path from 'node:path'; 9 import { fileURLToPath } from 'node:url'; 10 11 const exec = promisify(execFile); 12 const __dirname = path.dirname(fileURLToPath(import.meta.url)); 13 const ROOT = path.resolve(__dirname, '../..'); 14 const MAIN = path.join(ROOT, 'dist', 'src', 'main.js'); 15 16 export interface CliResult { 17 stdout: string; 18 stderr: string; 19 code: number; 20 } 21 22 /** 23 * Run `opencli` as a child process with the given arguments. 24 * Without PLAYWRIGHT_MCP_EXTENSION_TOKEN, opencli auto-launches its own browser. 25 */ 26 export async function runCli( 27 args: string[], 28 opts: { timeout?: number; env?: Record<string, string> } = {}, 29 ): Promise<CliResult> { 30 const timeout = opts.timeout ?? 30_000; 31 try { 32 const runtime = process.env.OPENCLI_TEST_RUNTIME || 'node'; 33 const { stdout, stderr } = await exec(runtime, [MAIN, ...args], { 34 cwd: ROOT, 35 timeout, 36 env: { 37 ...process.env, 38 // Prevent chalk colors from polluting test assertions 39 FORCE_COLOR: '0', 40 NO_COLOR: '1', 41 ...opts.env, 42 }, 43 }); 44 return { stdout, stderr, code: 0 }; 45 } catch (err: any) { 46 return { 47 stdout: err.stdout ?? '', 48 stderr: err.stderr ?? '', 49 code: err.code ?? 1, 50 }; 51 } 52 } 53 54 /** 55 * Parse JSON output from a CLI command. 56 * Throws a descriptive error if parsing fails. 57 */ 58 export function parseJsonOutput(stdout: string): any { 59 try { 60 return JSON.parse(stdout.trim()); 61 } catch { 62 throw new Error(`Failed to parse CLI JSON output:\n${stdout.slice(0, 500)}`); 63 } 64 }