sandbox_exec
在箱子裡跑一個 shell 命令:前景跑(有逾時),或背景跑(輸出寫到 log 檔)。前景命令執行時畫面有變,這一步會留截圖與錄影給人看。
| 輸入 | 輸出 |
|---|---|
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;往環境位址或 link 的連線接不通時有 privateEndpointErrors 與 privateEndpointHint;background 時有 bgId 與 logPath;帶 baseline 時有下面說的比較結果 |
note(必填):一句話說明這個命令要做什麼,用人讀的語言寫(工具說明裡會寫明是哪一種)。app 顯示它而不是指令原文,當作箱子目前在做的事。cwd相對於箱子的工作目錄/work,預設就是它。timeoutSec限制前景命令:預設 600,最多 3600。時間到了,命令會被SIGKILL砍掉,連同它在同一個 process group 裡起的程式(用&起的也算),回傳到那時為止印出的輸出,timedOut是true、signal是"SIGKILL"、exitCode是 -1。- 每個前景呼叫都有
execId(本機轉接器在送出呼叫前就選好)。結果回來之前呼叫就斷了的話(連線斷掉,或 client 不等了),命令不會停:它會跑到結束或timeoutSec用完,用sandbox_procs的wait、把bgId設成這個execId,就拿得到原本那次呼叫該回的東西(result:stdout、stderr、exitCode…),所以不要先重跑一次。轉接器的錯誤訊息會寫出execId與那個呼叫。最近 200 個前景結果會保留。用這個execId呼叫stop可以提早結束它。 - 輸出:
stdout與stderr各留最後 16 KB(tailBytes可以設少一點,1 到 16384)。印得更多時,前面會被截掉:truncated: true,outputUrl連到完整的輸出(兩個 stream 照印出的順序;一小時有效),outputBytes是它的大小,outputNote說明每個 stream 各留了多少。大部分是二進位的 stream(NUL 位元組、無效的 UTF-8)不會回傳:它會顯示[binary output: N bytes, not shown; …],完整輸出在outputUrl;文字裡零星的 NUL 位元組會以␀或[N NUL bytes]回來。輸出不是終端機,所以程式會印非互動的格式:node --test印的是 TAP(# fail 2、not ok 3 - name),不是終端機上那種ℹ fail行。 - 不是逾時造成的訊號會附
endReason:stopped by sandbox_procs stop、out-of-memory killer(箱子記憶體不夠了:少同時跑幾個吃重的工作,例如go build -p 2,或開大一點的箱子),或別的程序(kill、pkill)。 cmd是一個 JSON 字串。要寫檔案,用加引號的 heredoccat > path <<'EOF'…EOF:裡面什麼都不會展開,反斜線照原樣保留(不加引號的<<EOF會展開$與反引號)。長檔案、很多檔案,用sandbox_sync送,不要塞進cmd:超過 256 KB 的cmd會被拒絕,很長的cmd也可能讓 client 送出壞掉的參數,回來的是arguments: …。command也可以當作cmd的另一個名字。readOnly: true給只讀取的前景命令用(log、檔案、狀態):它不帶DISPLAY跑,什麼都不錄,有人透過takeoverUrl接手箱子、其他命令都得等的時候,它也照跑。它不是用來看或操作畫面的。不能和background一起用。- 前景呼叫在 shell 結束時就回,最多再等 3 秒讓還接著輸出的程序放手。用
&、nohup或setsid起、那時還在跑的程序就不管了:結果有leftoverChildren: true與一句hint,它們繼續跑、沒有東西追蹤它們,之後印的東西都會丟掉。如果先到timeoutSec,它們會跟整個 process group 一起被砍掉。要一直跑的東西(dev server、watcher、資料庫、模擬器)一律用自己的background: true呼叫起;sandbox_procs也只認得這種。 exitCode只看最後一個命令,所以失敗的測試接到tee、或後面還有一行,結果都是 0。strict: true會在命令前面加set -eo pipefail:exitCode就是第一個失敗的命令或管線那一段的,後面的也不會再跑。- 測試輸出很長時,讓結果只留重要的,不要讓無關的 log 把失敗淹掉,例如
go test ./... 2>&1 | grep -E '^(--- FAIL|FAIL|ok|panic)'(加strict: true或set -o pipefail,失敗才會反映在exitCode),或go test -json ./... | jq -r 'select(.Action=="fail") | .Package + " " + (.Test // "")'。超過 16 KB 的輸出從開頭截掉,完整的在outputUrl。 - 只有箱子要用的臨時測試(例如造假資料、對照 rollup 結果的 fixture)可以直接寫進箱子裡的副本、跑完就刪,本機的 repo 完全不碰:
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給 repo,不要給整個/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列出這些命令(已結束的也在內)、等其中一個結束,或連同它起的所有程序一起停掉;sandbox_status.running.background列出還在跑的,附logPath。要停就用bgId,不要用pkill -f或pgrep -f。命令是從腳本檔跑的,pattern 不會再打到跑你這次sandbox_exec的 shell,但命令列含有它的其他程序都會被比對到,別的背景工作也算。不是用 background 起的程序,用pkill -x <名稱>比對完整的程序名稱,或用kill <pid>。- 輸出寫到檔案或管線(而不是終端機)時,程式會先緩衝,所以 log 可能好幾分鐘都是空的,然後一次整批出現。每個命令都會設
PYTHONUNBUFFERED=1(除非你自己設了);管線裡用sed -u、grep --line-buffered或stdbuf -oL <command>,每一行一印出就會進 log。Node 的console.log寫到檔案時會立刻寫入。 readyPort(server 起來後會在127.0.0.1或::1接受連線的埠)與readyLog(正規表示式,RE2 語法,逐行比對 log,例如"Listening on|ready in")用來表示命令什麼時候準備好。sandbox_procs的wait帶until: "ready",只要其中一個成立就回,不必固定sleep;等的時候也可以再設這兩個。psbx-step <name> -- <command> [args...]執行較長腳本中的一個步驟,記下它的開始、結束碼與耗時;用那個bgId(或前景的execId)呼叫sandbox_procs,會把它們列成steps,即使 log 什麼都沒寫,也看得出哪個步驟正在跑、哪個失敗了。結束碼會原樣傳回(psbx-step build -- make && psbx-step test -- make test在第一個失敗處就停);管線要放進bash -c '…'裡。
- 輸出寫到檔案或管線(而不是終端機)時,程式會先緩衝,所以 log 可能好幾分鐘都是空的,然後一次整批出現。每個命令都會設
baseline: true把同一個命令接連跑兩次:先在cwd,再到<dest>-baseline裡的同一個位置;<dest>-baseline是sandbox_sync的baseline放在dest旁邊的較早的樹(cwd必須在這樣同步的dest裡)。它從兩份輸出挑出失敗的測試(go test的--- FAIL與FAIL <package>行、node --testTAP 的not ok、node、jest 與 vitest 的✖、✕、×行、pytest 的FAILED與ERROR),去掉耗時與測試編號,回傳onlyMine(只在有你的改動時失敗)、alreadyFailing(兩邊都失敗)、fixedByMine(只在改動前失敗)、counts、exitCode與baselineExitCode,以及mineLog與baselineLog:兩份完整輸出,在/work/.sbx/logs底下。沒認出任何測試名稱、但有一次執行以非零結束時,會寫在summary裡:這時請讀 log。不能和background一起用。- 專案自己的依賴不在映像裡。Python 專案先在箱子裡
pip install -r requirements.txt(以及專案其他的requirements*.txt)再跑測試;pip 裝到系統,break-system-packages已經設好。箱子裡任何地方的隨手 Node 腳本,沒有package.json也能直接 importplaywright、playwright-core與ws(import { chromium } from 'playwright',或require),不用npm install:它們和 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語系與中日韓字型。git 已經設好身分(ParallelSandbox Box,在/etc/gitconfig與 root 的~/.gitconfig)與safe.directory *,要 commit 的測試不用再設。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(場景網址的 host,含 key,當成網址本身保管)、交給箱子的 secrets,以及箱子的環境指定了 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列的程序也一樣),注入箱子的 secret、以及/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的連線字串。檔案本身不改:背景命令的 log 檔裡是真的值(經sandbox_exec讀出來時又會遮掉),sandbox_get與sandbox_pull拿到的檔案也是原樣。 - 前景命令跑的期間(包括命令結束後、拍截圖前的那 0.6 秒),箱子螢幕(
DISPLAY=:99,或接著的手機)上的畫面有變的話,這一步會自動留下一張截圖,通常還有一段錄影(例外見下面:有時只有截圖,有時什麼都不留):錄影從看到畫面變的那一刻開始(畫面每 1 秒才比一次,接著手機時每 2 秒,所以會比畫面真正變的時候晚一點),錄到拍下截圖為止;截圖在命令結束後 0.6 秒拍(這 0.6 秒內你又跑了下一個前景命令的話,就在那個命令開始前馬上拍)。畫面沒變就什麼都不留。人在 app 裡這個箱子的「截圖與錄影」看得到,每一步標著時間和你給的note。所以瀏覽器與 App 的測試請開著畫面在:99上跑(Playwright 的--headed、HEADED=1),人才看得到每一步測了什麼。每一種畫面留多久、箱子停掉之後誰還看得到,總表在收尾。細節與例外:- 怎麼算有變:命令開始前先記下畫面,之後每 1 秒(接著手機時每 2 秒)跟它比一次;游標閃爍不算變。命令結束後再等 0.6 秒(這 0.6 秒內下一個前景命令開始的話,只等到它開始的那一刻),到那時還沒看到變化的話,再比最後一次,有變就拍那張截圖。整段畫面都沒變(例如只跑
npm test)就什麼都不留,截圖也沒有。 - 不到 1 秒就結束的命令(例如用
xdotool點一下)來不及開始錄影:畫面在命令結束後 0.6 秒內變了,就只有那張截圖;更晚才變(例如點下去之後頁面要載入好幾秒)就什麼都不留。要留下點擊之後的畫面,在同一個命令裡多等幾秒,例如xdotool mousemove 400 300 click 1; sleep 2。接著手機時要等更久:開始前的畫面是命令開始之後才抓的(iPhone 抓一張要一兩秒),抓好之後每 2 秒才比一次,所以錄影最早也要命令開始 2 秒後才開始(iPhone 抓圖慢,還會更晚),點一下(artemis-adb tap、psbx-ios tap)的變化也可能被當成開始前的畫面,什麼都不留。點擊前後都多等幾秒,例如sleep 3; psbx-ios tap 200 400; sleep 3。 - 這一步本身要不要錄影,只在第一次看到畫面變的那一刻決定一次,之後不再改。第一次看到畫面變時,你正用
sandbox_shot錄影的話,命令期間畫面怎麼變,只錄在你用sandbox_shot開的那段錄影裡;這一步本身沒有影片(你那段錄影也不會算成這一步的),只留命令結束後拍的那張截圖,之後也不會補上影片。第一次看到畫面變時,你沒在錄的話,這一步本身照常錄影,也拍那張截圖。你用sandbox_shot開的錄影永遠不會併進這個命令的那一步,它的下場只有兩種:你自己用record: "stop"停掉的,會上傳,在 app 裡成為另外一步(kind是shot、detail是record的那一步);被自動停掉的,不會上傳,也不會成為任何一步,app 裡永遠看不到。所以錄影被自動停掉的話,那段錄影期間跑的前景命令,人只看得到各自那一步的截圖。ParallelSandbox 自動停掉你的錄影,最早也要等到箱子最後一次使用(會動到箱子的工具呼叫結束或喚醒,見箱子狀態)起算滿 1 小時,而且要等到沒有別的東西讓箱子醒著;跟錄了多久無關,前景命令跑的期間也不會自動停掉它。被自動停掉的 mp4 留在哪、怎麼拿、拿回來之後留多久,都寫在sandbox_shot講record的那一點。 - 一步最多錄 10 分鐘,從開始錄(第一次看到畫面變)的那一刻算起,
timeoutSec超過 600 的命令才會碰到(錄影最早在命令開始 1 秒後才開始,接著手機時更晚,所以 600 秒內結束的命令錄不滿 10 分鐘):錄滿 10 分鐘之後命令照樣跑,只是不再錄;錄好的 10 分鐘照樣留著,命令結束後照樣拍截圖。 background: true的命令不錄。- 這些截圖與錄影拍攝後保留 7 天。箱子停掉後,app「全部沙盒」的「已停止(7 天內)」仍能打開該箱子的截圖與錄影;箱子本身和
/work不能恢復。有設idleTimeoutMin時,上限是 10080 分鐘(7 天),詳見箱子狀態。 - 箱子停掉之後你還拿得到,但只能用 REST API,沒有對應的 MCP 工具:帶 API key 呼叫
GET https://api.parallelsandbox.com/v1/boxes/{id}/media,每一步拍下後 7 天內都拿得到(最多最新的 200 步,見 REST)。要人看每一步做了什麼,就用sandbox_review交件,別直接sandbox_stop(收尾)。
- 怎麼算有變:命令開始前先記下畫面,之後每 1 秒(接著手機時每 2 秒)跟它比一次;游標閃爍不算變。命令結束後再等 0.6 秒(這 0.6 秒內下一個前景命令開始的話,只等到它開始的那一刻),到那時還沒看到變化的話,再比最後一次,有變就拍那張截圖。整段畫面都沒變(例如只跑
- 箱子是 x86_64(amd64):跑在 Intel Xeon 主機上的 Debian 12。請拉或 build
linux/amd64的 image。只有 arm64 的 image(例如為 Graviton build 的,或在 Apple silicon 的 Mac 上沒帶--platform linux/amd64build 的)會失敗:exec format error。箱子裡沒有裝模擬:docker run --privileged --rm tonistiigi/binfmt --install arm64可以替那個箱子加上,但模擬的容器很慢(我們試的箱子上,同一段 CPU 迴圈花了約 6 倍時間),所以請改 build amd64,或 push 多架構的 image(docker buildx build --platform linux/amd64,linux/arm64)。要給別處用的 arm64 產物(Graviton、arm64 的 Lambda),只要不在箱子裡跑 arm64 的東西,不裝模擬也 build 得出來:CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build,或對RUN步驟都在 build 平台上跑的 Dockerfile(編譯的那個 stage 寫FROM --platform=$BUILDPLATFORM …,再COPY --from那個 stage)下docker buildx build --platform linux/arm64。arm64 stage 裡的RUN步驟沒有模擬會失敗,build 出來的 image 也不能在箱子裡跑(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):箱子會讓那個分頁維持這個 viewport,你繼續在裡面操作也不會變回去。沒登入的頁面用target: "url"。橫向螢幕用--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指向這個螢幕的 session bus,桌面通知(Electron 的Notification、notify-send)會出現在右上角。 - 螢幕只有這兩種大小。要看別的尺寸的網頁,用
sandbox_shot的target: "url"或"tab"加width、height。桌面 App 要例如 1440 × 900,自己開一個螢幕(以background: true跑Xvfb :100 -screen 0 1440x900x24),用DISPLAY=:100跑 App,再用ffmpeg -f x11grab -video_size 1440x900 -i :100 -frames:v 1 shot.png拍、sandbox_get拿回來:sandbox_shot與人看到的畫面都只有:99。 - 用角色與無障礙名稱找元素(
page.getByRole('button', { name: 'Save' }),icon 按鈕的aria-label也找得到),不要用畫面上的文字或位置:只有 icon 的按鈕沒有文字,選到好幾個元素的 locator 會撞上 Playwright 的 strict mode。 - 一個瀏覽器一次只驅動一個分頁。不在最前面的分頁拿不到畫格,對它
page.screenshot會逾時;同一個瀏覽器換分頁操作前先page.bringToFront()(CDP 是Page.bringToFront),同時跑的腳本各開一個瀏覽器,各用自己的--user-data-dir與除錯埠。 - 需要使用者手勢的動作(
window.open、剪貼簿、全螢幕)要真實輸入:Playwright 的click()、CDP 的Input.dispatchMouseEvent或xdotool click。經 CDPRuntime.evaluate送的element.click()不算使用者操作(除非呼叫帶userGesture: true),它開的彈出視窗會被擋掉,而且不報錯。 - 給自己的 CDP 腳本用的最小有畫面 Chromium:用
background: true跑chromium --no-sandbox --remote-debugging-port=9222 --user-data-dir=/tmp/chrome <網址>(以 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等於要一個兩倍螢幕大的視窗。 - 箱子沒有 GPU,WebGL 預設不能用(Chromium 的 log 有
WebGL1 blocklisted,網頁偵測不到 WebGL)。需要的頁面加--use-angle=swiftshader --enable-unsafe-swiftshader:軟體算繪,結果正確但慢。 - 語音輸入:
/opt/psbx/fake-mic/en.wav與/opt/psbx/fake-mic/zh.wav各是一句英文與中文的人聲。Chromium 帶--use-fake-ui-for-media-stream --use-fake-device-for-media-stream --use-file-for-fake-audio-capture=/opt/psbx/fake-mic/zh.wav開,getUserMedia拿到的麥克風就是這段錄音,一直循環,也不跳權限詢問。要別的句子用espeak-ng -v en-us -w /tmp/say.wav "..."(中文用-v cmn-latn-pinyin;直接用cmn會把拼音的聲調數字用英文唸出來)。 - 在模擬手機的頁面上滑動,送 CDP 的
Input.dispatchTouchEvent:touchStart、幾個touchMove、最後touchEnd。Input.synthesizeScrollGesture帶gestureSourceType: "touch"在箱子裡捲不動。 - 箱子的 Chromium 讓
localhost、127.0.0.1、*.localhost與*.parallelsandbox.com不用問就能用剪貼簿,開別種語言的頁面也不跳翻譯提示,不會蓋在截圖與錄影上(managed policy,/etc/chromium/policies/managed/psbx.json)。navigator.clipboard.readText()仍然要頁面有焦點,先把它帶到最前面。其他 origin 要先授權(Playwright:context.grantPermissions(['clipboard-read', 'clipboard-write']);CDP 的Browser.grantPermissions只在那條連線開著時有效);沒有授權時,Chromium 會在頁面上方跳出權限列,呼叫就一直等著。 - 要確認頁面實際用了哪個字型,問 Chromium:對那個元素用 CDP
CSS.getPlatformFontsForNode,會列出用到的字型與各畫了幾個字。依unicode-range分片的網路字型(多數 CJK 字型與 Google Fonts)用document.fonts.check()不可靠:已經用網路字型畫好的字也可能回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 裡 type 為service_worker的 target;要重啟就從chrome://extensions重新載入擴充功能,或關掉那個 target。下載不要動:Playwright 的acceptDownloads與 CDPBrowser.setDownloadBehavior設allow會在擴充功能的chrome.downloads.onDeterminingFilename之前就把下載處理掉,測這類行為要用預設行為。
- 螢幕是 390 × 844 直向,箱子用
- 箱子裡的程式連不到環境位址或 link 時,它只看得到連線被 reset 或關掉。這時結果會帶
privateEndpointErrors([{target, error, count}],這個命令期間的每一個失敗)與privateEndpointHint,寫著連接器或 ParallelSandbox 說的原因,例如the connector could not reach chatbot-dev.svc.local:8000: lookup chatbot-dev.svc.local: no such host(那個服務在他們網路裡沒有在跑的 task)或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也照樣拍得到畫面,要繼續改程式也可以開另一個箱子把程式同步過去。
所有工具與主題都列在工具參考。