解決 Browser Evaluate 造成的頁面卡死:引入 CDP 重構方案
寫自動化指令碼最怕遇到一種情況:你明明只是想在頁面跑一段簡單的 JavaScript,結果因為那段 code 執行太久或意外卡住,導致後續所有的 click 或 type 動作全部跟著排隊卡死。
這通常是因為底層工具將所有指令序列化處理,一旦前面的 evaluate 沒結束,後面的指令就進不去。這種「一人卡關,全家報銷」的開發痛點,正是我們這次重構要解決的核心問題。
需要準備的東西
Section titled “需要準備的東西”在開始之前,請確保你的環境符合以下條件(依據源文件):
- 已開啟
browser.evaluateEnabled功能閘門 - 擁有可存取的 CDP (Chrome DevTools Protocol) endpoint
- 現有的 Playwright 基礎設施
Quick Start: 5 分鐘理解重構路徑
Section titled “Quick Start: 5 分鐘理解重構路徑”我們將棄用單一的 Playwright 指令隊列,改用 CDP 建立獨立的執行通道。
1. 建立統一的 Budget 管理
Section titled “1. 建立統一的 Budget 管理”首先,我們需要一個統一的預算機制,確保 timeoutMs 和 AbortSignal 在整個鏈路中行為一致。
// 範例:建立統一預算助手const budget = createBudget({ timeoutMs, signal });// 獲得一致的 signal, deadlineAtMs, 以及 remainingMs()2. 實作獨立的 CDP Evaluate 引擎
Section titled “2. 實作獨立的 CDP Evaluate 引擎”建立一個不走 Playwright 隊列的執行路徑。這會開啟一個獨立的 WebSocket 連接。
// src/browser/cdp-evaluate.ts 核心邏輯// 1. 連接到 browser-level CDP socket// 2. 使用 Target.attachToTarget 獲取 sessionId// 3. 執行 Runtime.evaluate 或 Runtime.callFunctionOn3. 擴展 Ref 標記以支援 CDP
Section titled “3. 擴展 Ref 標記以支援 CDP”為了讓 CDP 能精準選取元素,我們在 Snapshot 階段就把 backendDOMNodeId 存起來。
// 擴展後的 Ref 結構{ role: string; name: string; nth: number; backendDOMNodeId?: number; // 新增:用於 CDP 定位}4. 路由分流策略
Section titled “4. 路由分流策略”更新 act:evaluate 的處理邏輯:
- 如果沒有
ref或ref包含backendDOMNodeId:直接走 CDP 路徑,速度快且不阻塞。 - 如果無法對應到 ID:回退到 Playwright 路徑(保留現有的安全網)。
以下是根據重構計劃預見的常見問題與對策:
問題:CDP 元素對應(Mapping)失敗怎麼辦?
解決方案:這套機制是 best-effort 的。如果無法將 (role, name, nth) 對應到 backendDOMNodeId,系統會自動回退到原本的 Playwright 執行路徑,雖然會有阻塞風險,但能確保功能不中斷。
問題:執行 JavaScript 逾時會影響頁面嗎?
解決方案:當觸發逾時或中止時,我們會發送 Runtime.terminateExecution。這雖然可能產生副作用,但能有效釋放資源。這是為了防止分頁永久卡死的必要權衡。
問題:如果環境不支援 CDP Attach 怎麼辦? 解決方案:我們會保留現有的最後手段(Last Resort)復原路徑,包括斷開 Playwright 連線來強行恢復分頁狀態。
想要針對你的專案環境進行更具體的設定嗎?歡迎諮詢 AI Setup Assistant。
- 查看
Accessibility.getFullAXTree相關文件 - 了解
Runtime.terminateExecution的執行細節 - 閱讀 PR #13498 關於安全網的初步實作
- 探索
BrowserActRequest的完整 API 規範
OpenClaw Expert
還是卡住了?
如果這篇文件沒有解決你的情境,直接問 OpenClaw Expert,拿到可執行步驟。