/ tests / e2e / helpers.ts
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  }