sandbox_exec
ボックスでシェルコマンドを 1 つ実行します。タイムアウト付きのフォアグラウンドか、ログファイルに出力するバックグラウンドで動かします。画面が変わると、フォアグラウンドのステップはスクリーンショットと録画を人のために残します。
| 入力 | 出力 |
|---|---|
id、cmd、note、cwd、timeoutSec、background、strict、user(root または tester)、readOnly、tailBytes、readyPort、readyLog、baseline |
stdout、stderr、exitCode、truncated、durationMs、timedOut、signal、boxTime、note、execId。出力が切り詰められたとき(またはバイナリだったとき)は outputUrl、outputBytes、outputNote、タイムアウト以外のシグナルで終わったときは endReason、コマンドが動いたままのプロセスを残したときは leftoverChildren と hint、環境のアドレスやリンクへの接続が失敗したときは privateEndpointErrors と privateEndpointHint、background なら bgId と logPath、baseline なら下記の比較 |
note(必須):このコマンドの目的を、人が読む言語(ツールの説明に書かれています)で一文で書きます。アプリはコマンドそのものではなくこれを、ボックスが今していることとして表示します。cwdはボックスの作業ディレクトリ/workからの相対パスで、既定はそこです。timeoutSecはフォアグラウンドコマンドを制限します。既定 600、最大 3600。時間切れになると、コマンドは同じプロセスグループで起動したプロセス(&で起動したものも含む)ごとSIGKILLで終了させられ、それまでに出力された内容が、timedOut: true、signal: "SIGKILL"、exitCode-1 とともに返ります。- フォアグラウンドの呼び出しにはすべて
execIdがあります(ローカルアダプターが呼び出しを送る前に決めます)。結果が返る前に呼び出しが切れても(接続が切れた、クライアントが待つのをやめたなど)、コマンドは止まりません。終わるかtimeoutSecに達するまで動き続け、sandbox_procsのwaitをbgIdにそのexecIdを指定して呼ぶと、その呼び出しが返すはずだったもの(result:stdout、stderr、exitCode…)が返ります。ですから、先にもう一度実行しないでください。アダプターのエラーにはexecIdとその呼び出しが書かれています。直近 200 件のフォアグラウンドの結果が保存されます。execIdを指定したstopで早めに終わらせることもできます。 - 出力:
stdoutとstderrはそれぞれ最後の 16 KB を残します(tailBytesでそれより少なく指定できます。1〜16384)。それより多く出力されたときは先頭が切られます。truncated: trueになり、outputUrlは完全な出力(両方のストリームを出力された順に。1 時間有効)へのリンク、outputBytesはそのサイズ、outputNoteは各ストリームをどれだけ残したかです。ほとんどがバイナリのストリーム(NUL バイト、不正な UTF-8)は返されず、[binary output: N bytes, not shown; …]となり、完全な出力はoutputUrlの先にあります。テキスト中の少しの NUL バイトは␀か[N NUL bytes]として返ります。出力先は端末ではないので、プログラムは非対話用の形式で出力します。node --testは端末でのℹ failの行ではなく、TAP(# fail 2、not ok 3 - name)を出力します。 - タイムアウト以外のシグナルには
endReasonが付きます:stopped by sandbox_procs stop、OOM killer(ボックスのメモリが足りなくなった。go build -p 2のように重い処理を同時に動かす数を減らすか、もっと大きいボックスを起動してください)、またはほかのプロセス(kill、pkill)です。 cmdは 1 つの JSON 文字列です。ファイルを書くには、引用符付きのヒアドキュメントcat > path <<'EOF'…EOFを使ってください。中身は何も展開されず、バックスラッシュも書いたとおりに残ります(引用符なしの<<EOFは$とバッククォートを展開します)。長いファイルや多数のファイルは、cmdに入れずにsandbox_syncで送ってください。256 KB を超えるcmdは拒否され、非常に長いものはクライアントが壊れた引数を送る原因になり、arguments: …として返ってきます。cmdの別名としてcommandも受け付けます。readOnly: trueは、読むだけのフォアグラウンドのコマンド(ログ、ファイル、状態)のためのものです。DISPLAYなしで動き、何も記録されず、人がtakeoverUrlでボックスを引き継いでいてほかのコマンドが待たされる間も実行されます。画面を見たり操作したりするためのものではありません。backgroundとは併用できません。- フォアグラウンドの呼び出しはシェルが終わった時点で戻り、出力をまだ握っているものはあと最大 3 秒しか待ちません。
&、nohup、setsidで起動し、そのとき動いているプロセスはそのままにされます。結果にleftoverChildren: trueとhintが付き、プロセスは動き続けますが、何も追跡せず、その後の出力は失われます。先にtimeoutSecに達したときは、プロセスグループごと終了させられます。動かし続けるもの(dev server、watcher、データベース、エミュレーター)は、それぞれbackground: trueの呼び出しで起動してください。sandbox_procsが扱えるのもそれだけです。 exitCodeは最後のコマンドのものだけなので、失敗したテストをteeにパイプしたり、後ろに別の行があったりすると 0 になります。strict: trueはコマンドの前にset -eo pipefailを付けます。exitCodeは最初に失敗したコマンドかパイプラインの段のものになり、残りは実行されません。- テストの出力が長いときは、関係ないログに失敗が埋もれないよう、結果に要るものだけ残します。たとえば
go test ./... 2>&1 | grep -E '^(--- FAIL|FAIL|ok|panic)'(失敗がexitCodeに出るようstrict: trueかset -o pipefailを付けます)や、go test -json ./... | jq -r 'select(.Action=="fail") | .Package + " " + (.Test // "")'。16 KB を超える出力は先頭から切られ、全体はoutputUrlにあります。 - ボックスだけで使う使い捨てのテスト(偽のデータを作って集計結果を照合する fixture など)は、ボックス内のコピーに直接書いて、実行後に消せます。ローカルのリポジトリには一切触れません:
cat > /work/<repo>/internal/rollup/rollup_sbx_test.go <<'EOF' … EOFのあとgo test ./internal/rollup -run Sbx、同じコマンドでファイルをrmします。正式なテストにするときだけsandbox_pullで持ち帰ります。 user: "tester"はコマンドを root 以外のユーザーで動かします(uid 1000 が空いていれば 1000、dockerグループに所属)。専用のHOME、TMPDIR(/tmp/u<uid>、0700)、GOPATH、npm キャッシュを持ち、最初に使ったときに作られます。uid を切り替えるプログラム(setpriv、会員ごとの uid)や root では動かないプログラムのテストに使ってください。root で動かすと、別の uid に切り替えた子プロセスが root の作った 0700 の一時ディレクトリに入れず、それだけでテストが大量に失敗します。そうしたコマンドの前に毎回、cwd以下の root 所有のファイル(/work/.sbxを除く)をtesterに渡して書き込めるようにするので、cwdには/work全体ではなくリポジトリを指定してください。既定はrootです。/tmp自体はすべての uid で共有です。テストが別の uid で CLI を動かすと、その uid 用の/tmp/claude-<uid>などが先に作られ、あとでその uid で動かした CLI が使用を拒みます(Temp directory /tmp/claude-1001 is owned by uid 97884)。uid ごとに一時ディレクトリを分け(TMPDIR。Claude Code はCLAUDE_CODE_TMPDIR)、すでにぶつかった場合はそのディレクトリを消してください。background: trueはすぐにbgIdとlogPathを返します。プロセスは呼び出し後も動き続け、出力はそのファイル/work/.sbx/logs/<bgId>.logに書かれるので、別のsandbox_execで読みます。sandbox_procsは、こうしたコマンドを終わったものも含めて一覧し、1 つの終了を待ち、起動したプロセスごとまとめて止めます。sandbox_status.running.backgroundには動作中のものがlogPath付きで並びます。止めるときはpkill -fやpgrep -fではなくbgIdを使ってください。コマンドはスクリプトファイルから動くので、パターンが自分のsandbox_execを動かしているシェルに一致することはもうありませんが、コマンドラインにそれを含むほかのプロセスにはすべて一致し、ほかのバックグラウンドジョブも含まれます。バックグラウンドで起動していないプロセスには、プロセス名に完全一致するpkill -x <名前>かkill <pid>を使ってください。- プログラムは、端末ではなくファイルやパイプに書く出力をバッファーするので、ログが何分も空のままで、あるとき一度に埋まることがあります。
PYTHONUNBUFFERED=1はすべてのコマンドに設定されます(自分で設定した場合を除く)。パイプラインではsed -u、grep --line-buffered、stdbuf -oL <command>を使うと、各行が出力されたときにログに届きます。Node のconsole.logはファイルへもすぐに書かれます。 readyPort(サーバーが起動すると127.0.0.1か::1で接続を受け付けるポート)とreadyLog(ログの各行と照合する正規表現。RE2 構文で、"Listening on|ready in"など)は、コマンドの準備ができたことを示す条件です。sandbox_procsのwaitをuntil: "ready"で呼ぶと、固定のsleepの代わりに、どちらかが成り立った時点で戻ります。待つときにこれらを指定することもできます。psbx-step <name> -- <command> [args...]は長いスクリプトの 1 ステップを実行し、その開始、終了コード、所要時間を記録します。そのbgId(またはフォアグラウンドのexecId)でsandbox_procsを呼ぶとstepsとして並ぶので、ログに何も出ていなくても、どのステップが実行中で、どれが失敗したかがわかります。終了コードはそのまま返ります(psbx-step build -- make && psbx-step test -- make testは最初の失敗で止まります)。パイプラインはbash -c '…'の中に入れてください。
- プログラムは、端末ではなくファイルやパイプに書く出力をバッファーするので、ログが何分も空のままで、あるとき一度に埋まることがあります。
baseline: trueは同じコマンドを 2 回、順番に実行します。まずcwdで、次に<dest>-baselineの同じ場所で実行します。<dest>-baselineはsandbox_syncのbaselineがdestの隣に置いた以前のツリーです(cwdはそうして同期したdestの中でなければなりません)。両方の出力から失敗したテストを拾い出し(go testの--- FAILとFAIL <package>の行、node --testの TAP のnot ok、node、jest、vitest の✖、✕、×の行、pytest のFAILEDとERROR)、所要時間とテスト番号を除いて、onlyMine(あなたの変更があるときだけ失敗)、alreadyFailing(両方で失敗)、fixedByMine(変更前だけ失敗)、counts、exitCodeとbaselineExitCode、そして両方の完全な出力を/work/.sbx/logsに置いたmineLogとbaselineLogを返します。名前を 1 つも認識できなかったのに 0 以外で終了した実行があれば、summaryにそう書かれます。そのときはログを読んでください。backgroundとは併用できません。- プロジェクト自身の依存はイメージに入っていません。Python のプロジェクトは、テストの前にボックスで
pip install -r requirements.txt(とプロジェクトのほかのrequirements*.txt)を実行してください。pip はシステム全体に入れ、break-system-packagesは設定済みです。ボックス内のどこにある使い捨ての Node スクリプトでも、package.jsonなしでnpm installせずにplaywright、playwright-core、wsを import できます(import { chromium } from 'playwright'、またはrequire)。それらと Playwright の Chromium、Firefox、WebKit はボックス全体に入っています。これらのブラウザーは Playwright 1.56.0 用です。別のバージョンの@playwright/testを固定したプロジェクトはExecutable doesn't exist at /usr/local/share/playwright/…で失敗するので、先にプロジェクトでnpx playwright install chromiumを実行するか(約 20 秒、共有のブラウザーディレクトリに入ります)、executablePath: '/usr/bin/chromium'で起動してください。別のメジャーバージョンの Node はnpx -y node@20 script.js(またはnode@24、初回約 5 秒)で動きます。イメージには ripgrep(rg)、zip、xxd、bats、bwrap、xclip、python(Python 3.11。Pillow、numpy、boto3、fonttools、brotli 入り。3.12 と 3.13 はtoolchainsで選択)、ImageMagick(convert、montage)、en_US.UTF-8ロケール、CJK フォントもあります。git には ID(ParallelSandbox Box。/etc/gitconfigと root の~/.gitconfig)とsafe.directory *が設定済みなので、コミットするテストに準備は要りません。nprocとfree -gがこのボックス自身の CPU とメモリを示します。ボックス内に cgroup の割り当てはありません(/sys/fs/cgroup/cpu.maxはありません)。ボックスの IBus 入力メソッドはIBUS_ADDRESSですべてのコマンドに設定されているため、自分でibus-daemonを起動するテストはenv -u IBUS_ADDRESSが必要です。イメージに何があるかは/etc/sbx/manifest.jsonに並んでいます。 - コマンドは root として
bash -lで動きます。環境にはすでにDISPLAY=:99(仮想ディスプレイ。起動したものはsandbox_shotやtakeoverUrlで見えます)、SBX_BOX_ID(ボックス id)、SBX_SCENE_HOST(シーン URL のホスト、key を含むので URL そのものとして扱う)、ボックスに渡したシークレット、そしてボックスの環境が AWS ロールを指定していればAWS_CONTAINER_CREDENTIALS_FULL_URIとAWS_REGIONがあります。コンテナには渡さない限り入りません(docker run -e SBX_BOX_ID ...)。各コマンドにはPYTHONUNBUFFERED=1、PSBX_JOB_ID(そのbgIdまたはexecId)、PSBX_STEPS(psbx-stepの記録先)も渡されます。 - 返ってくるものの中ではシークレットの値が隠されます。
stdout、stderr、outputUrlの完全な出力(sandbox_procsのcmdとlogTail、sandbox_statusが並べるプロセスも同じ)では、ボックスに注入したシークレットと/work/.sbx/envの設定の値のうち 8 文字以上のものが****に置き換わります。複数行の値(PEM 鍵など)は行ごとです。名前からアドレスやモードとわかる設定はデバッグのために見えたままです。HOST、PORT、ENV、TZ、LANG、STAGEと、_HOST、_HOSTNAME、_PORT、_REGION、_ENV、_ENVIRONMENT、_STAGE、_MODE、_LEVEL、_PATH、_DIR、_NAME、_TIMEZONE、_LOCALEで終わる名前です。ただし名前にSECRET、TOKEN、PASSWORD、PASSWD、PWD、KEY、CREDENTIAL、AUTH、PRIVATE、SIGNATURE、SALT、COOKIE、SESSION、DSNも含まれていれば隠されます。8 文字未満の値(true、3000、dev)や、それ以外のもの(psbx-testdbの接続文字列など)は隠されません。ファイルは変わりません。バックグラウンドコマンドのログファイルには本当の値があり(sandbox_execで読むとまた隠されます)、sandbox_getとsandbox_pullもファイルをそのまま返します。 - フォアグラウンドのコマンドの実行中(終了後、スクリーンショットを撮るまでの 0.6 秒も含みます)にボックスの画面(
DISPLAY=:99、または接続したスマホ)が変わると、そのステップのスクリーンショット 1 枚と、たいていは録画も自動で残ります(例外は下記:スクリーンショットだけのことも、何も残らないこともあります):録画は画面の変化を見つけた時点から始まり(画面を比べるのは 1 秒ごと、スマホ接続中は 2 秒ごとなので、実際に変わった時点より少し遅れます)、スクリーンショットを撮るまで続きます。スクリーンショットはコマンドが終わってから 0.6 秒後に撮られます(その 0.6 秒のうちに次のフォアグラウンドのコマンドを実行すると、そのコマンドが始まる前にすぐ撮られます)。画面が変わらなければ何も残りません。人はアプリでそのボックスの「スクリーンショットと録画」から見られ、各ステップには時刻とあなたのnoteが付きます。ブラウザやアプリのテストは:99上で headed で動かしてください(Playwright の--headed、HEADED=1)。そうすれば各ステップで何をテストしたかが人に見えます。画面の記録がそれぞれいつまで残り、ボックスを止めたあと誰が見られるかは、仕上げの表にまとめてあります。詳細と例外:- 変化の判定:コマンドの開始前に画面を記録し、そのあと 1 秒ごと(スマホ接続中は 2 秒ごと)にそれと比べます。カーソルの点滅は変化に数えません。コマンドが終わったあと 0.6 秒待ち(次のフォアグラウンドのコマンドがそれより早く始まれば、そこで待つのをやめます)、それまでに変化が見つかっていなければ最後にもう一度比べて、変わっていればスクリーンショットを撮ります。コマンドの間に画面が変わらなければ(ただの
npm testなど)何も残らず、スクリーンショットもありません。 - 1 秒たたずに終わるコマンド(
xdotoolでのクリック 1 回など)は、録画が始まる前に終わります。終了後 0.6 秒以内に画面が変われば、そのスクリーンショットだけが残ります。それより遅く変わると(クリックのあとページの読み込みに数秒かかるなど)何も残りません。クリックの結果を残すには、同じコマンドの中で数秒待ってください。例:xdotool mousemove 400 300 click 1; sleep 2。スマホ接続中はもっと長く待ってください:コマンド開始前の画面はコマンドが始まってから取得され(iPhone は 1 枚に 1〜2 秒かかります)、取得できてから 2 秒ごとに比べるので、録画が始まるのは早くてもコマンド開始の 2 秒後です(iPhone は取得が遅いので、さらに遅くなります)。タップ(artemis-adb tap、psbx-ios tap)による変化が開始前の画面として扱われ、何も残らないこともあります。タップの前後で数秒待ってください。例:sleep 3; psbx-ios tap 200 400; sleep 3。 - そのステップ自体が動画を撮るかどうかは、画面の変化を最初に見つけた瞬間に一度だけ決まり、あとで変わることはありません。画面の変化を最初に見つけたときにあなたが
sandbox_shotで録画していると、コマンド中の画面の変化は、あなたがsandbox_shotで始めた録画にだけ入ります。そのステップ自体には動画がなく(あなたの録画がそのステップの動画になることもありません)、コマンドの終了後に撮るスクリーンショットだけが残ります。あとから動画が足されることもありません。画面の変化を最初に見つけたときに録画していなければ、ステップはいつもどおり独自の動画を撮り、スクリーンショットも撮ります。あなたがsandbox_shotで始めた録画がそのコマンドのステップに入ることはなく、行き先は 2 通りだけです:あなたがrecord: "stop"で止めたものはアップロードされ、アプリに別のステップ(kindがshot、detailがrecordのステップ)として出ます。自動で止められたものはアップロードされず、どのステップにもならず、アプリには出ません。その場合、録画中に実行したフォアグラウンドのコマンドについて、人に見えるのはそれぞれのステップのスクリーンショットだけです。ParallelSandbox があなたの録画を自動で止めるのは、早くても、ボックスが最後に使われてから(ボックスに作用するツール呼び出しの終了か復帰。ボックスの状態を参照)まる 1 時間たったあとで、しかもほかに何もボックスを起こしていないときだけです。録画の長さとは関係なく、フォアグラウンドのコマンドの実行中は止めません。自動で止められた mp4 がどこに残り、どう取り出し、取り出したものがいつまで残るかは、sandbox_shotのrecordの項目にあります。 - 1 ステップの録画は最長 10 分で、録画が始まった時点(画面の変化を最初に見つけた時点)から数えます。
timeoutSecが 600 を超えるコマンドでだけ起こります(録画が始まるのは早くてもコマンド開始の 1 秒後、スマホ接続中はもっと後なので、600 秒以内に終わるコマンドが 10 分録り切ることはありません)。コマンドはそのまま動き続け、録画だけが止まります。録れた 10 分は残り、コマンドの終了後のスクリーンショットも撮られます。 background: trueのコマンドは録画されません。- これらのスクリーンショットと録画は撮影後 7 日間保存されます。ボックスを停止したあとも、アプリの「すべて」→「停止済み(過去 7 日)」から期限内の画像と動画を開けます。ボックスと
/workは復元できません。idleTimeoutMinの上限は 10080 分(7 日)です(ボックスの状態)。 - ボックスを止めたあとも、あなたは取得できます。ただし REST API だけで、対応する MCP ツールはありません:API キーを付けて
GET https://api.parallelsandbox.com/v1/boxes/{id}/mediaを呼ぶと、各ステップを撮られてから 7 日間取得できます(新しい 200 件まで。REST を参照)。各ステップで何をしたかを人に見てもらうときは、sandbox_stopで止めずにsandbox_reviewで渡してください(仕上げ)。
- 変化の判定:コマンドの開始前に画面を記録し、そのあと 1 秒ごと(スマホ接続中は 2 秒ごと)にそれと比べます。カーソルの点滅は変化に数えません。コマンドが終わったあと 0.6 秒待ち(次のフォアグラウンドのコマンドがそれより早く始まれば、そこで待つのをやめます)、それまでに変化が見つかっていなければ最後にもう一度比べて、変わっていればスクリーンショットを撮ります。コマンドの間に画面が変わらなければ(ただの
- ボックスは x86_64(amd64)で、Intel Xeon のホスト上の Debian 12 です。
linux/amd64のイメージを pull またはビルドしてください。arm64 だけのイメージ(Graviton 向けにビルドしたもの、Apple silicon の Mac で--platform linux/amd64なしにビルドしたものなど)はexec format errorで失敗します。エミュレーションは入っていません。docker run --privileged --rm tonistiigi/binfmt --install arm64でそのボックスに追加できますが、エミュレーションのコンテナは遅い(試したボックスでは同じ CPU ループに約 6 倍の時間)ので、amd64 でビルドし直すか、マルチアーキテクチャのイメージを push してください(docker buildx build --platform linux/amd64,linux/arm64)。ほかの場所で使う arm64 の成果物(Graviton、arm64 の Lambda)は、ボックスで arm64 のものを動かさない限り、エミュレーションなしでビルドできます。CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build、またはRUNがすべてビルドプラットフォームで動く Dockerfile(コンパイルするステージをFROM --platform=$BUILDPLATFORM …にして、そのステージからCOPY --from)をdocker buildx build --platform linux/arm64でビルドします。arm64 のステージのRUNはエミュレーションなしでは失敗し、できたイメージはボックスでは動きません(exec format error)。ボックスは Debian 12、glibc 2.36 です。ボックスで cgo を使ってビルドした Go のバイナリ(C コンパイラがあれば既定でそうなり、ボックスにはあります)はこの glibc を必要とするので、古い実行用イメージに入れるとGLIBC_2.34 not foundで失敗します。CGO_ENABLED=0でビルドするか、プログラムを動かすのと同じベースイメージの中でビルドしてください。 - ボックスの画面のブラウザ:
- 画面は 390 × 844 の縦向きで、
orientation: "landscape"で起動したボックスだけが 1280 × 800 です(sandbox_start)。Chromium のウィンドウは最小 500 ピクセル幅なので、縦向きの画面では--force-device-scale-factor=0.78 --window-size=500,1082 --window-position=0,0で開くとちょうど画面を埋めます。background: trueでchromium --no-sandbox --kiosk --force-device-scale-factor=0.78 --window-size=500,1082 --window-position=0,0 --remote-debugging-port=9222 --user-data-dir=/tmp/chrome http://localhost:3000/を実行します。このときページの CSS 幅は 500 です。ログインしたままのそのページをちょうどスマホ幅で見るには、sandbox_shotをtarget: "tab"とwidthで呼んでください(--remote-debugging-portが必要です)。ボックスがそのタブのビューポートを保つので、操作を続けても元に戻りません。ログインしていないページならtarget: "url"です。横向きの画面では、この 3 つの代わりに--window-size=1280,800を使います。コマンドは root で動くので、Chromium には--no-sandboxが必要です。 - 固定の
sleepではなく条件を待ってください。timeout 60 xdotool search --sync --onlyvisible --class chromiumはウィンドウが出たところで戻り、--remote-debugging-port=9222付きで起動した Chromium はcurl -s http://127.0.0.1:9222/json/versionが応答すれば CDP で使えます。sandbox_shotも自分で待てます(waitFor、stableMs。target: "tab"は起動中のブラウザを最大 45 秒待ちます)。 - ウィンドウは openbox が管理します。
xdotool windowactivateとgetactivewindowが使え、新しいウィンドウはキーボードフォーカスを受け取り、ダイアログ(Electron のdialog.showMessageBox、GTK)はタイトルバー付きで中央に開きます。ほかのウィンドウには枠を付けないので、0,0 から画面と同じ大きさで開いたウィンドウはこれまでどおり画面をちょうど埋めます。openbox 自身のショートカットはなく、すべてのキーがアプリに届きます。マウスポインターは普段は右下の隅にあって画面に写りません。hover を試すときは自分で動かしてください。DBUS_SESSION_BUS_ADDRESSがこの画面のセッションバスを指しているので、デスクトップ通知(Electron のNotification、notify-send)は右上に出ます。 - 画面の大きさはこの 2 種類だけです。別のサイズで Web ページを見るには、
sandbox_shotをtarget: "url"か"tab"とwidth、heightで使います。デスクトップアプリをたとえば 1440 × 900 で動かすには、自分で画面を開き(Xvfb :100 -screen 0 1440x900x24をbackground: trueで)、DISPLAY=:100でアプリを動かし、ffmpeg -f x11grab -video_size 1440x900 -i :100 -frames:v 1 shot.pngで撮ってsandbox_getで取り出してください。sandbox_shotと人が見る画面は:99だけです。 - 要素はロールとアクセシブルネームで探してください(
page.getByRole('button', { name: 'Save' })はアイコンボタンのaria-labelにも一致します)。表示テキストや位置では探さないでください。アイコンだけのボタンにはテキストがなく、複数の要素に一致するロケーターは Playwright の strict mode で失敗します。 - 1 つのブラウザで動かすページは一度に 1 つです。前面にないタブにはフレームが来ないので、そのタブへの
page.screenshotはタイムアウトします。同じブラウザの別のタブで作業する前にpage.bringToFront()(CDP ではPage.bringToFront)を呼び、同時に動かすスクリプトにはそれぞれ専用の--user-data-dirとデバッグポートでブラウザを 1 つずつ用意してください。 - ユーザー操作が必要なもの(
window.open、クリップボード、全画面)には本物の入力が要ります。Playwright のclick()、CDP のInput.dispatchMouseEvent、xdotool clickです。CDP のRuntime.evaluateで送ったelement.click()は、呼び出しにuserGesture: trueを付けない限りユーザー操作にならず、開こうとしたポップアップはエラーなしでブロックされます。 - 自分の CDP スクリプト用の最小のヘッドあり Chromium:
chromium --no-sandbox --remote-debugging-port=9222 --user-data-dir=/tmp/chrome <URL>をbackground: trueで起動し(root で動くので--no-sandboxがないとすぐ終了します)、until curl -sf http://127.0.0.1:9222/json/version >/dev/null; do sleep 0.2; doneで起動を待ってから接続します。Playwright のchromium.connectOverCDP('http://127.0.0.1:9222')か、/json/versionが返すwebSocketDebuggerUrlを使います。 - ウィンドウはプログラムが終わるまで残るので、
background: trueで開いてsandbox_procsのstopで閉じます。sandbox_shotのtarget: "window"は画面に残っているものをwindowsに列挙します(どれでもxdotool windowkill <id>で閉じられます)。 --force-device-scale-factorを付けると--window-sizeは拡大前のポイント数になり、端数は切り捨てられます。2 倍で 1280 × 800 の画面を埋めるには--window-size=641,401です。640,400だと右と下に 1 ピクセルの黒い縁が残り、1280,800だと画面の 2 倍の大きさのウィンドウを求めることになります。- ボックスには GPU がないので、WebGL は既定で使えません(Chromium のログに
WebGL1 blocklistedと出て、ページからは WebGL が見えません)。必要なページでは--use-angle=swiftshader --enable-unsafe-swiftshaderを付けます。ソフトウェア描画なので結果は正しいものの遅くなります。 - 音声入力:
/opt/psbx/fake-mic/en.wavと/opt/psbx/fake-mic/zh.wavは英語と中国語(北京語)の音声を 1 文ずつ収めています。Chromium を--use-fake-ui-for-media-stream --use-fake-device-for-media-stream --use-file-for-fake-audio-capture=/opt/psbx/fake-mic/en.wav付きで起動すると、getUserMediaはその録音をマイクとして受け取ります(ループ再生、権限の確認なし)。ほかの文はespeak-ng -v en-us -w /tmp/say.wav "..."で作れます(中国語は-v cmn-latn-pinyin。cmnのままだとピンインの声調の数字を英語で読みます)。 - スマホを模したページでスワイプするには、CDP の
Input.dispatchTouchEventをtouchStart、いくつかのtouchMove、touchEndの順に送ります。gestureSourceType: "touch"のInput.synthesizeScrollGestureはボックスではスクロールしません。 - ボックスの Chromium は
localhost、127.0.0.1、*.localhost、*.parallelsandbox.comに確認なしでクリップボードを使わせ、別の言語のページでも翻訳の案内を出さないので、スクリーンショットや録画に重なりません(managed policy、/etc/chromium/policies/managed/psbx.json)。navigator.clipboard.readText()には今もフォーカスのあるページが必要なので、先に前面に出してください。ほかのオリジンでは権限を与える必要があります(Playwright:context.grantPermissions(['clipboard-read', 'clipboard-write'])。CDP のBrowser.grantPermissionsはその接続が開いている間だけ有効です)。権限がないと、Chromium がページの上に権限バーを出し、呼び出しは待ったままになります。 - ページが実際にどのフォントで描かれているかは Chromium に聞きます。要素に対して CDP
CSS.getPlatformFontsForNodeを呼ぶと、使われたフォントとそれぞれが描いたグリフ数が返ります。unicode-rangeで分割された Web フォント(多くの CJK フォントや Google Fonts)ではdocument.fonts.check()は当てになりません。Web フォントで描画済みの文字でもfalseを返すことがあります。 - Chrome 拡張機能(Manifest V3)は
--load-extension=/work/<ext> --disable-extensions-except=/work/<ext>と専用の--user-data-dirで読み込みます。Playwright ではchromium.launchPersistentContextにこれらのargsとheadless: false(DISPLAY=:99上)を渡します。chrome.runtime.reload()などの開発者向け操作には、そのプロファイルでデベロッパーモードをオンにしておく必要があります(chrome://extensions)。オフだと拡張機能が理由 16777216 で無効になります。service worker は CDP の typeservice_workerのターゲットとして見え、再起動はchrome://extensionsから拡張機能を再読み込みするか、そのターゲットを止めます。ダウンロードの設定は触らないでください。Playwright のacceptDownloadsや CDPBrowser.setDownloadBehaviorのallowは、拡張機能のchrome.downloads.onDeterminingFilenameより先にダウンロードを処理してしまうので、そうした動作は既定のままでテストします。
- 画面は 390 × 844 の縦向きで、
- ボックス内のプログラムが環境のアドレスやリンクに届かないとき、プログラムから見えるのは接続がリセットされたか閉じられたことだけです。このとき結果に
privateEndpointErrors([{target, error, count}]、このコマンドの間のすべての失敗)とprivateEndpointHintが付き、コネクタや ParallelSandbox が返した理由が入ります。たとえばthe connector could not reach chatbot-dev.svc.local:8000: lookup chatbot-dev.svc.local: no such host(そのサービスは人のネットワークで動いているタスクがない)やthe connector "office" of this environment is offlineです。これはボックスの向こう側の問題で、コードを変えても直りません(環境)。環境のアドレスでも宣言した名前でもない名前は公開 DNS で引かれます。公開ドメインらしい名前でERR_NAME_NOT_RESOLVEDやNXDOMAINが出たら公開 DNS にレコードがないということなので、ボックスを疑う前にボックスでdig <名前>を確かめてください。 - 人が
takeoverUrl経由で接続している間(またはボックスの画面でboxの確認を試している間)、sandbox_execは実行せずにエラー(HTTP 423)を返し(readOnlyのコマンドは除く)、人がいつから使っているか、なぜか、遅くともいつ戻るかを伝えます。待って再試行してください。その間もsandbox_getとsandbox_procsは使え、sandbox_shotで画面も撮れます。コードの作業を続けるなら別のボックスを起動して同期することもできます。
すべてのツールとトピックはツールリファレンスに並んでいます。