サンプルリポジトリ
https://github.com/parallel-sandbox 配下の公開リポジトリが 2 つあります。どちらもローカルで docker compose up で動き、プラットフォーム自体のテストにもこの 2 つを使っています。以下のツール呼び出しが、ボックスで動かす現在の方法です。
example-compose-app
https://github.com/parallel-sandbox/example-compose-app
ジョブをキューに入れる Node API(ポート 3000)と、それを処理する Go ワーカー(ポート 8080)を docker-compose.yml でつないだもの。e2e/ には、実際のブラウザでページを開き、ジョブを追加し、ワーカーが終えるのを待ってスクリーンショットを保存する Playwright テストがあります。
ボックス内で試せること:git clone、docker compose up --build、ボックス内で解決されるサービス名、仮想ディスプレイ上のヘッド付きブラウザ、sandbox_shot の録画、sandbox_get でのスクリーンショット取得、sandbox_review での人への引き渡し。
sandbox_start { "name": "example-compose-app e2e", "goal": "ボックスの画面の実ブラウザで example-compose-app の e2e テストを実行する。テストが通り、スクリーンショットと動画が保存されれば完了", "services": [{ "name": "api", "port": 3000, "web": true }, { "name": "worker", "port": 8080 }], "orientation": "landscape" }
sandbox_exec { "id": "<id>", "cmd": "git clone https://github.com/parallel-sandbox/example-compose-app.git", "note": "サンプルリポジトリを clone する" }
sandbox_exec { "id": "<id>", "cmd": "docker compose up -d --build --wait", "cwd": "example-compose-app", "timeoutSec": 600, "note": "api と worker をビルドして起動する" }
sandbox_exec { "id": "<id>", "cmd": "curl -s http://api:3000/api/health", "note": "名前で api に届くか確認する" }
sandbox_exec { "id": "<id>", "cmd": "npm ci && npx playwright install chromium", "cwd": "example-compose-app/e2e", "timeoutSec": 600, "note": "e2e テストの依存をインストールする" }
sandbox_shot { "id": "<id>", "record": "start" }
sandbox_exec { "id": "<id>", "cmd": "HEADED=1 BASE_URL=http://localhost:3000 npx playwright test", "cwd": "example-compose-app/e2e", "timeoutSec": 300, "note": "ボックスの画面で e2e テストを実行する" }
sandbox_shot { "id": "<id>", "record": "stop" }
sandbox_get { "id": "<id>", "path": "example-compose-app/e2e/screenshots/jobs.png" }
sandbox_review { "id": "<id>", "what": "ジョブ名を入力して Add job を押してください。数秒でステータスが pending から done に変わり、sha256 の結果が出ます。ボックスの画面で e2e テストを実行し、「スクリーンショットと録画」に録画を残しました。", "open": "web", "port": 3000 }
この流れは sandbox_stop ではなく sandbox_review で終わります。ボックスは人のアプリの「確認待ち」で待ち、人はそのカードからページを開き、「スクリーンショットと録画」でその録画と各ステップのスクリーンショットを見られます。ボックスを止めると、アプリではどれも見られなくなります(仕上げ)。
orientation: "landscape" でボックスの画面は 1280 × 800 になり、テストが開くデスクトップ向けのページが収まります。指定しなければ 390 × 844 の縦向きです(sandbox_start)。
api と worker は起動時からボックス内でそれぞれ専用のアドレスに解決され、api:3000 と worker:8080 は公開したポートに届きます。2 つのコンテナは compose ネットワークでも互いに届くので、配線は不要です。
もう一つの動かし方:api を変更し、ワーカーはステージングの https://staging.example.com ですでに動いている場合。externalBaseUrl を渡すと、両方のサービスが external で始まり、api:3000 と worker:8080 への HTTP はそこへ転送されます。api を box に切り替え、api だけを起動します(boxd はそのポートを占有しないので順番は問いません)。worker はパスそのままでステージングに届き続けます:
sandbox_start { "name": "変更した api とステージングの worker", "goal": "変更した api をステージングの worker に対してテストする。変更した api から追加したジョブをステージングの worker が最後まで処理すれば完了", "services": [{ "name": "api", "port": 3000 }, { "name": "worker", "port": 8080 }], "externalBaseUrl": "https://staging.example.com" }
sandbox_exec { "id": "<id>", "cmd": "git clone https://github.com/parallel-sandbox/example-compose-app.git", "note": "サンプルリポジトリを clone する" }
sandbox_wire { "id": "<id>", "service": "api", "mode": "box" }
sandbox_exec { "id": "<id>", "cmd": "docker compose up -d --build --wait api", "cwd": "example-compose-app", "timeoutSec": 600, "note": "api だけをビルドして起動する" }
example-web-with-logs
https://github.com/parallel-sandbox/example-web-with-logs
ブラウザログ SDK @parallelsandbox/log を付けた 1 枚の静的ページ。ボタンを押すと 3 段の関数呼び出しの奥で未捕捉のエラーを投げます。ページは esbuild でビルド(minify、ソースマップ付き)、nginx で配信、コンテナ起動時に環境変数から設定を書き出すので、プロジェクトの書き込みキーはイメージに入りません。
ボックス内で試せること:sandbox_start の secrets、ソースマップのアップロード、仮想ディスプレイ上のページ、logs_errors が src/app.js の行に解決されたスタック付きでエラーを返すこと。ログプロジェクトは、ページを開くオリジンを許可している必要があります。ここではボックス内の http://localhost:8080 です(ログ SDK を参照)。
sandbox_start { "name": "example-web-with-logs", "goal": "ページで投げたエラーがログに届くか確かめる。logs_errors がそのエラーを src/app.js に解決されたスタック付きで返せば完了", "services": [{ "name": "web", "port": 8080, "web": true }], "secrets": ["PSBX_LOG_WRITE_KEY"], "orientation": "landscape" }
sandbox_exec { "id": "<id>", "cmd": "git clone https://github.com/parallel-sandbox/example-web-with-logs.git", "note": "サンプルリポジトリを clone する" }
sandbox_exec { "id": "<id>", "cmd": "PSBX_RELEASE=$(git rev-parse --short HEAD) PSBX_LOG_PROJECT=<project id> PSBX_LOG_ENDPOINT=https://log.parallelsandbox.com docker compose up -d --build --wait", "cwd": "example-web-with-logs", "timeoutSec": 600, "note": "ログ設定付きでページをビルドして配信する" }
ソースマップはボックスではなく自分のマシンからアップロードします。API キーがボックスに入らないためです。同じコミットなので release id も同じです:
git clone https://github.com/parallel-sandbox/example-web-with-logs.git && cd example-web-with-logs
npm ci && PSBX_RELEASE=$(git rev-parse --short HEAD) npm run build
PSBX_API_KEY=<api key> PSBX_LOG_PROJECT=<project id> ./scripts/upload-sourcemaps.sh
スタックはエラーが届いた時点で解決されるので、クリックする前にソースマップをアップロードしてください。ボックスに戻って:
sandbox_exec { "id": "<id>", "cmd": "chromium --no-sandbox --kiosk --window-size=1280,800 --user-data-dir=/tmp/chrome http://localhost:8080/", "background": true, "note": "ボックスの画面でページを開く" }
sandbox_shot { "id": "<id>" }
sandbox_exec { "id": "<id>", "cmd": "xdotool mousemove 640 300 click 1", "note": "エラーを投げるボタンを押す" }
logs_errors { "project": "<project id>", "since": "10m" }
sandbox_stop { "id": "<id>" }
上の Chromium の引数とクリック位置 640 300 は、このボックスを起動したときの横向きの画面を前提にしています。縦向きのボックス(390 × 844、既定)では --window-size=1280,800 の代わりに --force-device-scale-factor=0.78 --window-size=500,1082 --window-position=0,0 で Chromium を開き、ボタンの位置はスクリーンショットから取ってください(sandbox_exec)。
localhost で開いたページにはボックス id が付きません(SDK はボックスの URL のホストから読む)。そのためこの問い合わせは boxId ではなく時間で絞っています。
テンプレートとして使う
どちらのリポジトリもごく普通の形です。ルートに docker-compose.yml、サービスごとにディレクトリと Dockerfile、テストは専用ディレクトリ。この形のリポジトリなら、ボックスでの動かし方は同じです。ParallelSandbox 固有なのは sandbox_start の goal と services 一覧(ボックスの目的、名前とポート)と、ブラウザエラーを集めたい場合のログ SDK だけです。