開發日誌 · 2026-07-13
一次開多台,只有幾支掛
之前試過一次開多台模擬器一起跑 E2E,想把總時間壓下來。結果不是整批爆——是某幾支測案掛,其他照過。
當下第一個反應不是「怎麼會掛」,是「為什麼只有這幾支掛」。這件事本身就是線索。整批全倒,通常是環境沒起來、連線爆掉那種;只掛幾支,幾乎都是某個共享資源被平行的行程互相踩,踩到的才掛,沒碰到的照跑。
所以我沒先去看斷言細節,先去找那幾支掛的共通點。翻出來一看,清一色都是「複製一段字串到剪貼簿,再讀回來精確斷言」的案子。
// 示意:複製再讀回來斷言
await copyButton.click()
expect(await clipboardText()).toBe('9876543210')
剪貼簿。方向對了。
我腦裡原本的印象是「模擬器跟 Mac 共用一份剪貼簿」。查下去才知道這句話是錯的:每台模擬器其實各自有獨立的 pasteboard。真正把它們綁在一起的,是 Simulator.app 一個預設開著的設定——Edit → Automatically Sync Pasteboard。開著的時候,每台 sim 的剪貼簿會雙向同步到 host Mac 那一份。大家都鏡射到 Mac 的同一份,於是「實質上」所有 sim 共用一份。
競態就浮出來了:
行程 A 在它的 sim 複製了 X,同步上 Mac,又被同步進行程 B 的 sim。A 回頭讀自己剛複製的東西,讀到的卻可能是 B 在這半秒內蓋上去的值。斷言當然掛。而完全不碰剪貼簿的案子,一支都不受影響——跟「只有那幾支掛」對得剛剛好。
解法有兩條:把 auto-sync 關掉,每台 sim 立刻回到各自獨立;或者根本不透過 Mac 那份,用帶 UDID 的 CLI 直接對某一台讀寫。
xcrun simctl pbcopy <UDID> # 寫進「這一台」的剪貼簿
xcrun simctl pbpaste <UDID> # 讀「這一台」的,不經 Mac
值得記的,其實是我一開始那句錯話。如果你相信「模擬器天生跟 Mac 共享剪貼簿」,這題會顯得無解——共享是天性,還能怎樣?但真相是「各自獨立,只是被預設同步鏡射在一起」,那就有兩個開關可以拆。錯的心智模型不會讓你的斷言掛,它讓你連哪裡有開關都找不到。
以為多開幾台就會更快
剪貼簿同步關掉、競態排除之後,回到本來的目標:用多台模擬器把總時間壓下來。
直覺是靠框架。WebdriverIO 支援多 capability,你很自然會想:把四台 sim 塞進 capabilities 陣列,不就自動分擔了嗎?
不會。這是我差點踩進去的坑。WDIO 多 capability 的語意是「矩陣」——同一批 spec,在每一台裝置上各跑一遍。那是設計給你做跨裝置、跨瀏覽器驗證用的,不是拿來分片加速的。你放四台,等於同樣的 spec 跑了四遍,只會更慢。
要真的分片,得自己來:把不相交的 spec 子集,手動塞進每台 capability 的 specs。而且單一 Appium server 同時驅動多個 XCUITest session,WDA 的 port、derivedDataPath 這些很容易互相打架。愈想在框架裡硬做,愈脆。
所以我沒走框架原生那條,改成一層薄薄的多進程 wrapper:偵測機器規格算出一個安全的並行度 N,把 spec 依檔案輪流切成 N 片,起 N 個各自獨立的行程,每個行程一台自己的 sim、一個自己的 Appium server、自己的 port。隔離最乾淨,也最不容易互踩——上一段那種踩法我已經受夠了。
N 這個數字,不能只看 CPU 核數。每開一台 sim,要養一個 node worker、一個 WDA、還有模擬器本體,吃 CPU 也吃 RAM。只照核數配,RAM 會先爆。所以我讓兩個天花板同時夾,取小的那個:
N = max(1, min(
floor(cores / 2), // 每台 sim ≈ worker + WDA + 模擬器本體
floor(freeRamGB / 4), // 每台約抓 4GB,保守估
parallelSpecCount, // 沒那麼多片,就別開那麼多台
HARD_CAP, // 硬上限,再高階也別把機器操死
))
行程之間就靠不同的 env 撐開,各綁各的:
SIM_UDID=<各自> APPIUM_PORT=4723+i WDA_LOCAL_PORT=8100+i wdio run --spec <該片>
(至於平行寫結果檔會不會互相蓋——那層前陣子就改成各寫各的了,檔名帶 pid,收工再合併,這次白撿,不重講。)
真正的關鍵判斷是第一步:看穿「多 capability」不是加速鍵。認清這點,後面才會往「多進程、各自隔離」的方向走。沒認清,就會在框架裡愈疊愈複雜,最後 spec 跑了四遍,還以為自己在平行。