Skip to main content
Spawn the decipher-qa CLI as a child process and parse its final result block. Below is a copy-pasteable reference implementation.
This is boilerplate — a thin wrapper around the CLI to get you started. Fork it, rename the functions, add retry logic, wire it into your test framework, emit your own telemetry — whatever fits your pipeline. The CLI is the contract; the wrapper is just convenience.

The Runner

The CLI prints a final === RESULT === block with the run summary as JSON. The runner spawns the process, streams its output, and parses that block on exit. decipher-qa-runner.ts
import { spawn } from "child_process";

export type TestRunStatus = "passed" | "failed" | "failed_internal";

export interface RunTestResult {
  status: TestRunStatus;
  duration: number; // milliseconds
  runId: number;
  runUrl: string;
}

export interface RunOptions {
  testId: number;
  cdpUrl?: string;
  headful?: boolean;
  origin?: string;
  stream?: boolean; // default true — pipe CLI output to your terminal
}

export function runDecipherQaTest(opts: RunOptions): Promise<RunTestResult> {
  const args = ["test", "run-cdp", "--testId", String(opts.testId)];
  if (opts.cdpUrl) args.push("--cdp", opts.cdpUrl);
  if (opts.headful) args.push("--headful");
  if (opts.origin) args.push("--origin", opts.origin);

  const child = spawn("decipher-qa", args, {
    stdio: ["ignore", "pipe", "pipe"],
  });

  const streaming = opts.stream !== false;
  let stdout = "";

  child.stdout!.on("data", (chunk: Buffer) => {
    if (streaming) process.stdout.write(chunk);
    stdout += chunk.toString("utf8");
  });
  child.stderr!.on("data", (chunk: Buffer) => {
    if (streaming) process.stderr.write(chunk);
  });

  return new Promise((resolve, reject) => {
    child.on("error", reject);
    child.on("close", () => {
      const clean = stdout.replace(/\x1b\[[0-9;]*m/g, "");
      const marker = clean.lastIndexOf("=== RESULT ===");
      if (marker === -1) {
        return reject(new Error("decipher-qa exited without a result block"));
      }
      try {
        const json = clean.slice(marker + "=== RESULT ===".length).trim();
        resolve(JSON.parse(json) as RunTestResult);
      } catch (err) {
        reject(new Error(`Failed to parse decipher-qa result: ${(err as Error).message}`));
      }
    });
  });
}

Example Script

Here’s a self-contained script that runs test 1522 against a local headful Chromium and logs the result. Swap in your own testId and options:
import { runDecipherQaTest } from "./decipher-qa-runner";

async function main() {
  const result = await runDecipherQaTest({
    testId: 1522,
    headful: true,
    origin: "https://staging.myapp.com",
    stream: true,
  });

  console.log("\n=== RESULT ===");
  console.log(JSON.stringify(result, null, 2));

  if (result.status !== "passed") {
    console.error(`Test failed: ${result.error ?? "unknown error"}`);
    process.exit(1);
  }
}

main().catch((err) => {
  console.error(err);
  process.exit(1);
});

Usage

Run it with tsx (or compile and run with node):
npx tsx run-test.ts
You’ll get a streaming view of the run followed by a JSON summary:
{
  "status": "passed",
  "duration": 127000,
  "runId": 48291,
  "runUrl": "https://app.getdecipher.com/tests/1522/runs/48291"
}
The process exits 0 on pass and 1 on fail, so it slots into any CI pipeline that already expects conventional exit codes.
Need help? Contact our support team.