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):
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.