跳至內容

測試機

只講事實。若要了解這些事實背後的概念模型,請參閱 測試機與隔離環境;若要開啟一部機器,請按照 這份指南。

介面 驅動 後端 值 受控對外連線
終端(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,而不是 它撥接時所用的那一個。

每個動作都以全函數方式解析:格式錯誤的載荷會傳回一則指出欄位名稱的訊息, 永不拋出例外。觀察類動詞(標有 ○)在人類持有控制權時仍然允許;變更類動詞 則會被拒絕。

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 —— 影像與 檔案走檔案通道,因為動作的引數會寫進稽核日誌,而影像的位元組不該出現在 那裡。剪貼板內容會以不受信任資料的形式、外加圍欄後傳回。

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, 它會直接在游標位置插入,不經剪貼板。

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 起,只有取得焦點的應用程式可以 讀取剪貼板。

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 就是測試機設定畫面。