# CI Test Runner
URL: /docs/cookbooks/ci-test-runner

Run a repo's test suite in a disposable sandbox and turn the output into a structured pass/fail report.



<CookbookMeta difficulty="Beginner" time="~5 min" primitives="[&#x22;exec&#x22;, &#x22;strict: false&#x22;]" />

Run a repo's test suite in a disposable, CI-style sandbox and turn the raw output into a
structured pass/fail report — the building block for a PR check, a bot, or an agent that needs to
know "did the tests pass" without scraping a log.

## Setup [#setup]

<Steps>
  <Step>
    Start OpenSandbox in Docker (one-time setup):

    ```bash
    bunx alineo-cli init
    ```
  </Step>
</Steps>

## Run it [#run-it]

<CookbookPlayground
  cwd="cookbooks/ci-test-runner"
  repoHref="https://github.com/DrejT/alineo/tree/main/cookbooks/ci-test-runner"
  commands="[
  { command: &#x22;bun install&#x22; },
  { command: &#x22;bun start&#x22; },
]"
  output="`Scaffolding calc.py + test_calc.py in the sandbox...
Installing pytest...
Running pytest...

{
&#x22;status&#x22;: &#x22;failed&#x22;,
&#x22;summary&#x22;: &#x22;1 passed, 1 failed&#x22;,
&#x22;output&#x22;: &#x22;FAILED test_calc.py::test_subtract - assert 1 == 2&#x22;
}

process.exitCode = 1`"
/>

## What it does [#what-it-does]

<Steps>
  <Step>
    Scaffolds a tiny Python project (`calc.py` + `test_calc.py`) directly in the sandbox — one test
    deliberately fails, so you see a real failure report.
  </Step>

  <Step>
    Installs `pytest`.
  </Step>

  <Step>
    Runs the suite with `strict: false` so a non-zero exit is data, not a thrown error.
  </Step>

  <Step>
    Prints a structured report (`status`, `summary`, and the full output on failure) and sets
    `process.exitCode` accordingly — the same shape a CI step or bot would check.
  </Step>
</Steps>

<Callout title="Point this at a real repository">
  Swap the "scaffold a project" step for a real clone:

  ```ts
  await sb.exec(`git clone --depth 1 ${repoUrl} /workspace`);
  ```
</Callout>

<Callout type="warn" title="Server proxy">
  All examples default to `useServerProxy: true` — traffic routes through the OpenSandbox server so
  Docker bridge IPs don't need to be reachable directly. Set `USE_SERVER_PROXY=false` to disable
  (e.g. when using `uvx opensandbox-server` on the host).
</Callout>
