測試機
只講事實。若要了解這些事實背後的概念模型,請參閱 測試機與隔離環境;若要開啟一部機器,請按照 這份指南。
介面與後端
標題為「介面與後端」的章節| 介面 | 驅動 | 後端 | 值 | 受控對外連線 |
|---|---|---|---|---|
| 終端(exec) | 在 VM exec 通道上運作的一個 shell | microVM | smolvm |
是 |
| 瀏覽器 | 無介面 Chromium(CDP)、Firefox(WebDriver BiDi)或 WebKit(W3C WebDriver) | microVM | smolvm |
是 |
| 電腦 | X11 桌面:輸入經由虛擬機器管理程式,擷取經由客體代理 | QEMU | qemu-hvf(macOS)、qemu-kvm(Linux)、qemu-tcg(未加速的後備選項,永不自動選用) |
是 |
| Android | 經 ADB 驅動的 Android | Google 的模擬器 | android-emulator |
否 —— 模擬器自行管理其網絡 |
| iOS | 經 WebDriverAgent 驅動的 iOS Simulator | CoreSimulator | ios-simulator |
否 —— 網絡跟隨主機 Mac |
終端與 computer_use 介面共用 computer 這個介面值;終端以其 exec 映像標記
區分,且永不會被當作可操控的桌面重用(反之亦然)。一則對話會重用同一部終端
機器。
瀏覽器引擎
標題為「瀏覽器引擎」的章節| 引擎 | 值 | 協定 | 即時畫面 | 靜態畫面 |
|---|---|---|---|---|
| Chromium | chromium(預設) |
Chrome DevTools Protocol | 螢幕串流 —— 瀏覽器在頁面重繪時推送畫面 | JPEG |
| Firefox | firefox |
WebDriver BiDi | 輪詢式靜態畫面(BiDi 沒有螢幕串流),上限 6 fps | JPEG |
| WebKit | webkit |
W3C WebDriver(經典) | 輪詢式靜態畫面,由主機的 ffmpeg 轉碼 | PNG |
一部測試機終其一生只運行一款引擎。一則對話可以每個引擎各持有一部 測試機 —— 這正是並排比較得以成立的原因:把「Firefox(VM)」開在 「Chromium(VM)」旁邊就是兩部機器,關掉其中一部,另一部仍會繼續運行。
把 engine 傳給 rig.open 或 browser_use 工具。未知的名稱會被拒絕,而不是
套用預設值 —— 要求一款瀏覽器卻默默得到另一款,等於用錯誤的答案回答一個相容性
問題。
各引擎特有的限制,會如實說明,而不是繞過去:
- Firefox 沒有可繞過快取的重新載入(它直接拒絕 BiDi 的
ignoreCache), 因此強制重新載入就等同一般重新載入。 - WebKit 同樣沒有可繞過快取的重新載入,也沒有主控台饋送(輸出是在每次
導覽之後透過掛鉤
console.*擷取的,因此載入期間記錄的任何內容都會遺漏 —— 每一次主控台擷取都會如此說明),而且它的即時畫面需要主機上安裝ffmpeg。 - 兩者都不會經由其協定公開平台無障礙樹,因此
extract (a11y)傳回的是由 DOM 衍生的近似結果,並標示為近似。 - 兩者都無法接收真正的檔案拖放;不過檔案輸入仍然可以指向機器內部的路徑。
機器映像
標題為「機器映像」的章節| 介面 | 映像 | 取得方式 |
|---|---|---|
| 終端 | ubuntu:24.04,以摘要鎖定 |
首次開機時自動取得,經由機器本身受閘門管制的網絡 |
| 瀏覽器(Chromium、Firefox、WebKit) | debian:trixie-slim,以摘要鎖定,另加引擎本身的套件 |
首次開機時自動取得,然後快取為預熱包 |
| 電腦 | 一個 qcow2 桌面映像,內含 Chromium、Firefox、WebKit,以及終端所預熱的同一套 GitHub 託管執行器 CLI 基準 | 在設定 → 伺服器 → 測試機下匯入(在有發布的地方,下載會以校驗和鎖定) |
| Android | Google 的系統映像 | 經由 Android SDK |
| iOS | 一個 Xcode Simulator 執行環境,外加以摘要鎖定的 WebDriverAgent | 執行環境經由 Xcode;自動化橋接器則在設定 → 伺服器 → 測試機取得 |
每一種 microVM 首次開機之後,伺服器會在資料目錄下快取一份預先解壓的複本,
之後每一部機器都從它開機 —— 包括已裝好套件的預熱映像。終端會預熱一套
GitHub 託管執行器風格的 CLI 工具組(git、gcc、python3、jq、rsync、
封存工具,以及 apt 基準中其餘的部分;不含 Docker 或語言工具快取)。電腦的
qcow2 在建立映像時就把同一批 CLI 一併烘焙進去,與 XFCE 及三款瀏覽器並列。
每一款瀏覽器都會預熱自己的引擎。三款引擎共用同一個 Debian 基底,但永不共用
同一個包:快取以映像、引擎以及一個音訊修訂版本(audio-v1-<engine>)
作為索引鍵,因為為某一款引擎預熱的機器 —— 或在 PulseAudio 與 ffmpeg 尚未
成為工作負載一部分之前預熱的機器 —— 裡面都沒有其他引擎的東西。基於同樣
理由,終端的那個包以工具組修訂版本(tools-v1)作為索引鍵:在編譯器成為
映像一部分之前所預熱的包,絕不能拿去供應一部本應配備編譯器的機器。
沒有任何現成的自動化映像值得採用 —— Chromium 的
chromedp/headless-shell 在編譯時把 ALSA 與 PulseAudio 都排除掉,因此它
永遠無法發出聲音;唯一仍有人維護的 Firefox 映像,大小是 Debian 基底的
33 倍,卻仍然不附 socat;而有人維護的 WebKit 映像則完全不存在。因此每一款
引擎都在首次啟動時安裝(chromium、firefox-esr 或 webkit2gtk-driver),
並在過程中連上 Debian 封存站(deb.debian.org、security.debian.org、
cloudfront.debian.net)。Chromium 用的是發行版提供的二進位檔,以
chromium --headless=new 啟動。
各工作區的自訂映像
標題為「各工作區的自訂映像」的章節| 設定鍵 | 覆寫的項目 |
|---|---|
rigs.smolvm.execImage |
終端(VM)映像 |
rigs.smolvm.browserImage |
Chromium 瀏覽器(VM)映像 |
經由受管理員閘門管制的工作區設定存放區寫入(設定 → 伺服器 → 測試機 → 自訂映像(此工作區))。值必須是一個註冊表參照 —— 本機 路徑、封存檔與類似旗標的字串都會被拒絕,在欄位處拒絕一次,開機時再拒絕 一次。已儲存的值若無效,會被響亮地忽略,改為啟動預設值。映像本身的註冊表 (再加上 Docker Hub、GHCR、Quay、GitLab 與 GCR 已知的 blob CDN 夥伴) 會為了拉取而獲對外連線閘門放行。
終端映像合約 —— 必須有 bash;git、curl、socat 已預先安裝,
或是一個 Debian/Ubuntu apt 基底(隔離環境會在首次啟動時安裝這三項,再加
一套 GitHub 託管執行器風格的 CLI 工具組:編譯器、Python 3、jq、封存工具、
rsync。語言工具快取與 Docker 不會安裝)。
瀏覽器映像合約 —— 一個 Debian/Ubuntu apt 基底(或已安裝好的套件),
具備 bash 與 socat,再加上一個 chromium 二進位檔,由伺服器自行以
chromium --headless=new 啟動,並把 DevTools 放在客體回送位址 9223,
再轉送到客體連接埠 9222。首次啟動時會為音訊通道安裝 PulseAudio 與
ffmpeg。不要建置 FROM chromedp/headless-shell:上游的 headless.gn
在編譯時把 ALSA 與 PulseAudio 都排除掉,因此那個二進位檔無法發出聲音。
這項覆寫只適用於 Chromium 測試機 —— 它啟動的工作負載指令屬於
Chromium,交給 Firefox 或 WebKit 只會啟動一個根本不存在的二進位檔。
Firefox 或 WebKit 測試機會響亮地忽略它,並啟動自己固定的映像。
自動化連接埠 —— 每一部瀏覽器機器都只把一個主機回送連接埠轉送到客體
9222。在它背後:Chromium 的 DevTools 與 Firefox 的遠端代理位於客體回送
位址 9223,前面有一道 socat 轉送(兩者都無條件繫結回送位址);WebKit
的驅動程式接受一個 --host,自行繫結客體的網絡介面。Firefox 另外會以其
自身連接埠驗證 Host 標頭,因此用戶端送出的是客體端的 authority,而不是
它撥接時所用的那一個。
動作動詞
標題為「動作動詞」的章節每個動作都以全函數方式解析:格式錯誤的載荷會傳回一則指出欄位名稱的訊息, 永不拋出例外。觀察類動詞(標有 ○)在人類持有控制權時仍然允許;變更類動詞 則會被拒絕。
computer_use
標題為「computer_use」的章節screenshot○、cursor_position○、mouse_move、left_click、
right_click、middle_click、double_click、triple_click、
left_mouse_down、left_mouse_up、left_click_drag、scroll、key、
hold_key(0–60 秒)、type、wait○(0–60 秒)、set_display、
clipboard_read○、clipboard_write。
座標是客體目前模式下的客體像素;每一張螢幕截圖都會說明顯示尺寸。按鍵組合
使用 X11 的寫法(ctrl+s、alt+Tab、Return)。
clipboard_read 接受一個 selection:clipboard(ctrl+C 所填滿的內容,
預設值)、primary(X11 的選取即複製)或 xdnd(正在進行中的拖曳)。
三者之中,空結果都是正常答案。clipboard_write 只接受 text —— 影像與
檔案走檔案通道,因為動作的引數會寫進稽核日誌,而影像的位元組不該出現在
那裡。剪貼板內容會以不受信任資料的形式、外加圍欄後傳回。
browser_use
標題為「browser_use」的章節navigate(僅限 http/https)、reload(hard 可選)、stop_loading、
click(選擇器或座標)、fill、type、key(DOM 鍵名)、
scroll、mouse_move、drag、left_mouse_down、left_mouse_up、
extract○(a11y | dom | console)、screenshot○(full_page
可選)、set_viewport(最小 320×240)、history、wait_for○(選擇器,
0.1–30 秒)、clipboard_read○、clipboard_write。
擷取到的頁面內容會以不受信任資料的形式、外加圍欄後傳回。
瀏覽器只有一個剪貼板,因此 clipboard_read 不接受 selection。兩個剪貼板
動詞都需要安全上下文:以純 http:// 提供的頁面完全沒有剪貼板 API,
而這個動詞會如實說明,而不是回報一個空的剪貼板 —— 在那裡請改用 type,
它會直接在游標位置插入,不經剪貼板。
mobile_use
標題為「mobile_use」的章節tap、swipe(50–5000 毫秒)、type、key(別名 back、home、
recents、enter、delete、backspace、tab、escape、search、
menu、volume_up、volume_down、power,或任何 KEYCODE_*)、
screenshot○、ui_dump○、install_apk
(僅限測試機的工作樹與伺服器資料目錄)、start_app、
stop_app、clear_app_data、uninstall_app、open_url,以及 shell。
shell 接受一個 argv 清單,在 Android 內部執行,永不經由主機 shell;
它涵蓋 logcat、dumpsys 與 pm grant 之類的開發者診斷工具。
mobile_use 沒有剪貼板動詞。自 Android 10 起,只有取得焦點的應用程式可以
讀取剪貼板。
ios_use
標題為「ios_use」的章節tap、swipe(50–5000 毫秒)、type、key(enter、backspace、tab、
escape、方向鍵或一個可列印字元;可選的 command、
control、option 與 shift 修飾鍵)、home、lock、unlock、
screenshot○、ui_dump○、install_app(一個模擬器 .app 目錄,
僅限測試機的工作樹或伺服器資料目錄)、start_app、stop_app、
uninstall_app、open_url,以及 spawn。spawn 接受 argv,經由 simctl
在模擬器內部執行;它永不叫用主機 shell。
座標是 iOS 螢幕的點,不是螢幕截圖的像素。即時串流維持模擬器固定的 尺寸,以黑邊填補,而不是調整裝置大小。主機到模擬器、以及模擬器到主機的 文字剪貼板傳輸,採用與其他測試機相同的按使用者、按方向許可政策;iOS 的 剪貼板通道不接受檔案與影像。
每部測試機都會建立一個自己擁有的 CoreSimulator 裝置與 WebDriverAgent 工作階段。關閉它會停止 WebDriverAgent、關機並刪除該裝置,以及移除它的 註冊表項目。iOS 測試機只在配備 Xcode、已安裝 iOS 執行環境及固定版本自動化 橋接器的 macOS 伺服器上運行。它們不提供音訊、麥克風、檔案拖放、主機 shell,也不保證封閉網絡。
管理工具
標題為「管理工具」的章節| 工具 | 說明 |
|---|---|
rig_list |
列出工作區的測試機,連同介面、狀態、顯示與控制者 |
rig_close |
銷毀一部測試機並丟棄其磁碟 |
生命週期
標題為「生命週期」的章節| 階段 | 含義 |
|---|---|
provisioning |
正在開機;狀態詳情會原樣帶出目前的步驟 |
ready |
可觸及並接受動作 |
parked |
閒置停駐:vCPU 已停止,RAM 仍常駐;下一個動作會喚醒它 |
closing |
正在關機 |
closed |
已消失;該列會為稽核軌跡保留下來 |
failed |
已死亡或從未啟動;可資區分,以便介面提供重試 |
| 關閉原因 | 觸發時機 |
|---|---|
requested |
有人或代理要求 |
idleTimeout |
閒置超過閒置時限的 2 倍,或因記憶體而遭淘汰 |
ttlExpired |
硬性壽命耗盡 |
conversationEnded |
擁有它的對話已結束 |
workspaceGone |
工作區被刪除,或成員資格已失去 |
serverShutdown |
伺服器正在停止 |
backendFailure |
底下的虛擬機器管理程式死亡 |
預設值與上限
標題為「預設值與上限」的章節| 值 | 終端 | 瀏覽器 | 電腦 | 流動裝置 |
|---|---|---|---|---|
| 記憶體 | 512 MB | 2048 MB(Chromium);2560 MB(Firefox、WebKit) | 4096 MB | 4096 MB |
| vCPU | 2 | 2 | 4 | 4 |
| 硬性 TTL | 8 小時 | 2 小時 | 2 小時 | 2 小時 |
| 閒置時限 | 45 分鐘 | 15 分鐘 | 15 分鐘 | 15 分鐘 |
| 開機顯示 | — | 1280×800 | 1280×800 | 1080×1920 |
- 代理通道的靜態畫面上限為 1280×800;人類通道最多可協商至 2560×1600。
- 主機為所有測試機設定的常駐記憶體預算預設為 12 GB,以常駐 MB 計算 (已停駐的 VM 仍佔住它的 RAM)。淘汰採最久未使用優先,且永不帶走有人 正在操控、觀看,或開着終端的機器。
- 開啟中的終端或觀看通道會令測試機免於停駐與閒置回收;硬性 TTL 依然適用。
連接埠與開發網域
標題為「連接埠與開發網域」的章節| 連接埠 | 位置 | 保留用途 |
|---|---|---|
7911 |
終端客體 | 每個轉送都經由它乘載的連接埠多工器 |
9222, 9223 |
瀏覽器客體 | 自動化轉送與端點(WebKit 直接繫結 9222,不使用轉送) |
80, 443 |
瀏覽器客體回送 | 開發網域的 HTTP/HTTPS 通道 |
- 探索會在伺服器端每約 4 秒輪詢終端客體正在監聽的 TCP 連接埠。
- 每部測試機最多自動轉送 16 個連接埠;更多連接埠只會列在日誌中,不會 轉送。
- 主機端的轉送繫結回送位址,並優先使用客體本身的連接埠號; 在區域網絡分享會加上一個由作業系統指派的區域網絡連接埠。
- 開發網域必須符合
name.test或name.localhost(小寫,標籤最長 63 個字元)。一個網域對應一個連接埠。 - 開發網域的 HTTPS 使用一個伺服器本機的憑證頒發機構(EC P-256,位於
<dataDir>/rigs/tls/之下,金鑰權限為 0600,且永不進入任何客體),並為*.test/*.localhost簽發一張萬用字元葉憑證。主機上沒有openssl就沒有 HTTPS 通道;純 HTTP 仍然可以路由。
對外連線預設值
標題為「對外連線預設值」的章節| 介面 | 預設允許清單 |
|---|---|
| 終端 | 沙箱開發者基準(程式碼託管平台、套件註冊表、模型供應商)+ Ubuntu apt 鏡像站 + 工作樹自身的程式碼託管主機 |
| 瀏覽器 | usectrl.dev(產品網站)—— 再加上每一款引擎首次開機、安裝其套件期間的 Debian 封存站。首頁是本機的 file:// 文件,無需對外連線 |
| 電腦 | 空的 —— 一律拒絕 —— 直到有呼叫者授予主機 |
| 流動裝置 | 不強制執行(網絡由模擬器管理) |
每一個 microVM 另外還會取得其映像的註冊表主機,且僅限於拉取之用。憑證代理 的允許主機集合源自同一份允許清單:客體觸及不到的主機,不會獲簽發任何權杖。
剪貼板與檔案傳輸
標題為「剪貼板與檔案傳輸」的章節複製/貼上的兩個方向,以及把檔案拖入與拖出。這些功能開放給操控測試機的人; 代理則透過上述動詞取得剪貼板,並完全沒有檔案傳輸(它在隔離環境內本來就有 一個 shell)。
剪貼板跨越邊界是按使用者、按方向各自把關的。主機貼到測試機預設允許;測試機 複製到主機會先彈出詢問(可授予十分鐘權限,或在設定 → 伺服器 → 測試機 → 剪貼板存取下選擇永遠允許)。這兩個選擇互相獨立,而且永遠不是工作區設定。
| 通道 | 路由 | 承載的內容 |
|---|---|---|
| 剪貼板 | GET/POST /rig/clipboard/<rigId> |
文字、一張 PNG/JPEG(≤16 MB),以及剪貼板上那些檔案的客體路徑 |
| 檔案 | POST /rig/files/<rigId> |
最多 64 個檔案,每個 ≤256 MB,每次投放 ≤512 MB,可附帶一個客體像素的投放點 |
| 檔案 | GET /rig/files/<rigId>?p=<base64url path> |
一個檔案的位元組,向外讀出 |
- 是 HTTP,不是 RPC。 RPC socket 會在任何超過 256 KB 的入站訊框上 關閉連線 —— 這道 DoS 防護對 JSON 指令來說是對的,但對貼上的一張螢幕 截圖來說卻是致命的。位元組走的是測試機畫面與會議音訊早已使用的那一類 路由。
- 認證用的是已簽署的目標
rig-files:<workspaceId>/<rigId>,再加上 工作區成員資格 —— 它是獨立的目標,因此一個為了觀看某部機器而簽發的 URL,不能同時把檔案寫進該機器。 - 接管同樣適用。 寫入剪貼板與投放檔案屬於變更類操作,在別人持有控制權 時會被拒絕;讀取剪貼板與把檔案讀出則屬觀察類,不受此限。
- 投放落在何處:
~/Drops(桌面)、~/drops(終端 —— 在工作樹旁邊, 永不放在裡面,因此投放永遠不會以未追蹤變更的形式出現)、/tmp/cc-drops(瀏覽器)。目錄由伺服器挑選;呼叫者無法指定。現有檔案永不被覆寫 —— 名稱相撞時會以2-name.ext落地。 - 客體會被告知甚麼: 瀏覽器頁面會在請求的位置收到一個真實的拖放事件
(
Input.dispatchDragEvent);桌面則會在它的剪貼板上取得這些檔案及其 URI,因為沒有主機能在客體內沒有特權守護程序的情況下,向任意工具組合成 一次 XDND 拖曳。回覆會說明實際發生的是兩者中的哪一種。 - 拖出僅限桌面,並在拖曳進行期間讀取客體的
XdndSelection。若載荷與 按下那一刻客體手上已有的內容完全相同,就會被視為一次已完成拖曳所遺下的 過期選取,而不是新的選取 —— X 並沒有「拖曳已結束」這一步。 - 映像要求: 桌面映像必須是協定第 2 版或之後的版本(在剪貼板支援落地 之後建置)。較舊的映像會回應 404,而應用程式會叫你重新建置它,而不是 回報機器故障。
- 稽核日誌只記錄形狀,永不記錄內容:
clipboard_read、clipboard_write、drop_files與read_file的列只帶有數量、大小與 路徑。剪貼板正是憑證每次短暫停留數秒的地方,而其中的任何內容都不會被 記下。
護欄、事件與儲存
標題為「護欄、事件與儲存」的章節- 測試機工具會宣告
enclosureControl動作類別(使用類工具另外宣告networkEgress與processSpawn;mobile_use與ios_use還會宣告packageInstall)。rig_close宣告enclosureControl;rig_list不宣告任何類別。預設允許;唯讀模式一概拒絕;每個空間的自主程度旋鈕可以 把它降為先詢問。 - 領域事件:
RigClosedEvent(帶其關閉原因)、RigControlChanged(接管/交還)、RigReaped(閒置、TTL 或記憶體淘汰,並帶上正在操控的 代理)。開啟測試機不是事件。參見 領域事件。 - 儲存:每個工作區資料庫中的
rig_sessions與rig_action_log。動作日誌 會記錄每一個輸入事件,連同其主體與一個按測試機單調遞增的序號;輸入的 文字在寫入之前會被縮減為長度與雜湊值。 - 資料目錄:
rigs/images/(匯入的 qcow2 映像)、rigs/smolvm/(按測試機的 執行期狀態)、rigs/smolvm-packs/(預先解壓的機器包)、rigs/tls/(開發網域的憑證頒發機構)、rigs/run/(QEMU 覆寫層與種子)。 - 路由:即時畫面是空間或 PR 頁面裡的其中一個分頁;
/settings/server/rigs就是測試機設定畫面。