Automated testing pipeline

Last updated Sep 30, 2026 Note

This page documents Sandbox SDK 0.x for existing applications. For new applications, refer to Run tests from a Git repository. To move an existing application to @cloudflare/sandbox 1.0, refer to Migrate from Sandbox SDK 0.x.

Build a testing pipeline that clones Git repositories, installs dependencies, runs tests, and reports results.

Time to complete: 25 minutes

Prerequisites

  1. Sign up for a Cloudflare account ↗︎.
  2. Install Node.js ↗︎.

<details>

<summary>

Node.js version manager

</summary>

Use <a href="https://viteplus.dev/guide/env">Vite+ (<code>vp env</code>) ↗︎</a> to install and manage Node.js versions.

For supported Node.js versions, refer to the <a href="https://developers.cloudflare.com/workers/wrangler/install-and-update/#install-wrangler">Wrangler system requirements</a>.

</details>

You'll also need a GitHub repository with tests (public or private with access token).

1. Create your project

npmyarnpnpm

npm create cloudflare@latest -- test-pipeline --template=cloudflare/sandbox-sdk/examples/minimal#v0
yarn create cloudflare test-pipeline --template=cloudflare/sandbox-sdk/examples/minimal#v0
pnpm create cloudflare@latest test-pipeline --template=cloudflare/sandbox-sdk/examples/minimal#v0
cd test-pipeline

2. Build the pipeline

Replace src/index.ts:

import { getSandbox, proxyToSandbox, parseSSEStream, type Sandbox, type ExecEvent } from '@cloudflare/sandbox';

export { Sandbox } from '@cloudflare/sandbox';

interface Env {
    Sandbox: DurableObjectNamespace<Sandbox>;
    GITHUB_TOKEN?: string;
}

export default {
    async fetch(request: Request, env: Env): Promise<Response> {
        const proxyResponse = await proxyToSandbox(request, env);
        if (proxyResponse) return proxyResponse;

        if (request.method !== 'POST') {
            return new Response('POST { "repoUrl": "https://github.com/owner/repo", "branch": "main" }');
        }

        try {
            const { repoUrl, branch } = await request.json();

            if (!repoUrl) {
                return Response.json({ error: 'repoUrl required' }, { status: 400 });
            }

            const sandbox = getSandbox(env.Sandbox, `test-${Date.now()}`);

            try {
                // Clone repository
                console.log('Cloning repository...');
                let cloneUrl = repoUrl;

                if (env.GITHUB_TOKEN && cloneUrl.includes('github.com')) {
                    cloneUrl = cloneUrl.replace('https://', `https://${env.GITHUB_TOKEN}@`);
                }

                await sandbox.gitCheckout(cloneUrl, {
                    ...(branch && { branch }),
                    depth: 1,
                    targetDir: 'repo'
                });
                console.log('Repository cloned');

                // Detect project type
                const projectType = await detectProjectType(sandbox);
                console.log(`Detected ${projectType} project`);

                // Install dependencies
                const installCmd = getInstallCommand(projectType);
                if (installCmd) {
                    console.log('Installing dependencies...');
                    const installStream = await sandbox.execStream(`cd /workspace/repo && ${installCmd}`);

                    let installExitCode = 0;
                    for await (const event of parseSSEStream<ExecEvent>(installStream)) {
                        if (event.type === 'stdout' || event.type === 'stderr') {
                            console.log(event.data);
                        } else if (event.type === 'complete') {
                            installExitCode = event.exitCode;
                        }
                    }

                    if (installExitCode !== 0) {
                        return Response.json({
                            success: false,
                            error: 'Install failed',
                            exitCode: installExitCode
                        });
                    }
                    console.log('Dependencies installed');
                }

                // Run tests
                console.log('Running tests...');
                const testCmd = getTestCommand(projectType);
                const testStream = await sandbox.execStream(`cd /workspace/repo && ${testCmd}`);

                let testExitCode = 0;
                for await (const event of parseSSEStream<ExecEvent>(testStream)) {
                    if (event.type === 'stdout' || event.type === 'stderr') {
                        console.log(event.data);
                    } else if (event.type === 'complete') {
                        testExitCode = event.exitCode;
                    }
                }
                console.log(`Tests completed with exit code ${testExitCode}`);

                return Response.json({
                    success: testExitCode === 0,
                    exitCode: testExitCode,
                    projectType,
                    message: testExitCode === 0 ? 'All tests passed' : 'Tests failed'
                });

            } finally {
                await sandbox.destroy();
            }

        } catch (error: any) {
            return Response.json({ error: error.message }, { status: 500 });
        }
    },
};

async function detectProjectType(sandbox: any): Promise<string> {
    try {
        await sandbox.readFile('/workspace/repo/package.json');
        return 'nodejs';
    } catch {}

    try {
        await sandbox.readFile('/workspace/repo/requirements.txt');
        return 'python';
    } catch {}

    try {
        await sandbox.readFile('/workspace/repo/go.mod');
        return 'go';
    } catch {}

    return 'unknown';
}

function getInstallCommand(projectType: string): string {
    switch (projectType) {
        case 'nodejs': return 'npm install';
        case 'python': return 'pip install -r requirements.txt || pip install -e .';
        case 'go': return 'go mod download';
        default: return '';
    }
}

function getTestCommand(projectType: string): string {
    switch (projectType) {
        case 'nodejs': return 'npm test';
        case 'python': return 'python -m pytest || python -m unittest discover';
        case 'go': return 'go test ./...';
        default: return 'echo "Unknown project type"';
    }
}

3. Test locally

Start the dev server:

npm run dev

Test with a repository:

curl -X POST http://localhost:8787 \
  -H "Content-Type: application/json" \
  -d '{
    "repoUrl": "https://github.com/cloudflare/sandbox-sdk"
  }'

You will see progress logs in the wrangler console, and receive a JSON response:

{
  "success": true,
  "exitCode": 0,
  "projectType": "nodejs",
  "message": "All tests passed"
}

4. Deploy

npx wrangler deploy

For private repositories, set your GitHub token:

npx wrangler secret put GITHUB_TOKEN

What you built

An automated testing pipeline that:

  • Clones Git repositories
  • Detects project type (Node.js, Python, Go)
  • Installs dependencies automatically
  • Runs tests and reports results

Next steps

Was this helpful?

YesNo

On this page

Automated testing pipeline - 图1Docs

{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/sandbox/sdk/tutorials/automated-testing-pipeline/#page","headline":"Automated testing pipeline","description":"Build a Sandbox SDK 0.x testing pipeline that clones Git repositories, installs dependencies, runs tests, and reports results.","url":"https://developers.cloudflare.com/sandbox/sdk/tutorials/automated-testing-pipeline/","inLanguage":"en","image":"https://developers.cloudflare.com/sandbox/sdk/tutorials/automated-testing-pipeline/og.png?v=8bc6b418e93deff4","dateModified":"2026-09-30","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}


description: Clone repositories, analyze code with Claude, and post review comments to GitHub pull requests with the Sandbox SDK 0.x. title: Build a code review bot image: https://developers.cloudflare.com/sandbox/sdk/tutorials/code-review-bot/og.png?v=ec301db0f450ff19


Skip to content

Documentation Index
Fetch the complete documentation index at: https://developers.cloudflare.com/sandbox/llms.txt
Use this file to discover all available pages before exploring further.