Kế hoạch Refactor Browser Evaluate CDP
Ngữ cảnh
act:evaluate thực thi JavaScript do người dùng cung cấp trong trang. Ngày nay nó chạy qua Playwright
(page.evaluate hoặc locator.evaluate). Playwright tuần tự hóa lệnh CDP mỗi trang, vì vậy một
evaluate bị kẹt hoặc chạy lâu có thể chặn hàng đợi lệnh trang và làm cho mọi hành động sau
trên tab đó trông "bị kẹt".
PR #13498 thêm lưới an toàn thực tế (evaluate có giới hạn, lan truyền abort và khôi phục best-effort). Tài liệu này mô tả một refactor lớn hơn làm cho act:evaluate vốn dĩ
cô lập khỏi Playwright để một evaluate bị kẹt không thể làm kẹt các hoạt động Playwright bình thường.
Mục tiêu
act:evaluatekhông thể chặn vĩnh viễn các hành động browser sau trên cùng tab.- Timeout là nguồn chân lý duy nhất end to end để người gọi có thể dựa vào ngân sách.
- Abort và timeout được xử lý theo cùng cách qua HTTP và dispatch trong process.
- Nhắm mục tiêu element cho evaluate được hỗ trợ mà không chuyển mọi thứ khỏi Playwright.
- Duy trì khả năng tương thích ngược cho người gọi và payload hiện có.
Ngoài phạm vi
- Thay thế tất cả hành động browser (click, type, wait, v.v.) bằng triển khai CDP.
- Xóa lưới an toàn hiện có được giới thiệu trong PR #13498 (nó vẫn là dự phòng hữu ích).
- Giới thiệu khả năng không an toàn mới ngoài cổng
browser.evaluateEnabledhiện có. - Thêm cô lập process (worker process/thread) cho evaluate. Nếu chúng ta vẫn thấy trạng thái kẹt khó khôi phục sau refactor này, đó là ý tưởng follow-up.
Kiến trúc hiện tại (Tại sao nó bị kẹt)
Ở mức cao:
- Người gọi gửi
act:evaluateđến dịch vụ điều khiển browser. - Handler route gọi vào Playwright để thực thi JavaScript.
- Playwright tuần tự hóa lệnh trang, vì vậy một evaluate không bao giờ kết thúc chặn hàng đợi.
- Hàng đợi bị kẹt có nghĩa là các hoạt động click/type/wait sau trên tab có thể trông bị treo.
Kiến trúc đề xuất
1. Lan truyền deadline
Giới thiệu một khái niệm ngân sách duy nhất và rút ra mọi thứ từ nó:
- Người gọi đặt
timeoutMs(hoặc một deadline trong tương lai). - Timeout yêu cầu bên ngoài, logic handler route và ngân sách thực thi bên trong trang tất cả đều sử dụng cùng ngân sách, với khoảng trống nhỏ khi cần cho overhead tuần tự hóa.
- Abort được lan truyền như một
AbortSignalở mọi nơi để hủy nhất quán.
Hướng triển khai:
- Thêm helper nhỏ (ví dụ:
createBudget({ timeoutMs, signal })) trả về:signal: AbortSignal được liên kếtdeadlineAtMs: deadline tuyệt đốiremainingMs(): ngân sách còn lại cho hoạt động con
- Sử dụng helper này trong:
src/browser/client-fetch.ts(HTTP và dispatch trong process)src/node-host/runner.ts(đường dẫn proxy)- Triển khai hành động browser (Playwright và CDP)
2. Engine Evaluate riêng (Đường dẫn CDP)
Thêm triển khai evaluate dựa trên CDP không chia sẻ hàng đợi lệnh per page của Playwright. Thuộc tính chính là transport evaluate là kết nối WebSocket riêng và phiên CDP riêng được đính kèm vào mục tiêu.
Hướng triển khai:
- Module mới, ví dụ:
src/browser/cdp-evaluate.ts, mà:- Kết nối đến endpoint CDP được cấu hình (socket cấp browser).
- Sử dụng
Target.attachToTarget({ targetId, flatten: true })để lấysessionId. - Chạy:
Runtime.evaluatecho evaluate cấp trang, hoặcDOM.resolveNodecộngRuntime.callFunctionOncho evaluate element.
- Khi timeout hoặc abort:
- Gửi
Runtime.terminateExecutionbest-effort cho phiên. - Đóng WebSocket và trả về lỗi rõ ràng.
- Gửi
Lưu ý:
- Điều này vẫn thực thi JavaScript trong trang, vì vậy termination có thể có tác dụng phụ. Lợi ích là nó không làm kẹt hàng đợi Playwright và nó có thể hủy ở lớp transport bằng cách kill phiên CDP.
3. Câu chuyện Ref (Nhắm mục tiêu element mà không cần refactor đầy đủ)
Phần khó là nhắm mục tiêu element. CDP cần handle DOM hoặc backendDOMNodeId, trong khi
ngày nay hầu hết hành động browser sử dụng locator Playwright dựa trên ref từ snapshot.
Cách tiếp cận được khuyến nghị: giữ ref hiện có, nhưng đính kèm id có thể giải quyết CDP tùy chọn.
3.1 Mở rộng thông tin Ref được lưu trữ
Mở rộng metadata ref role được lưu trữ để tùy chọn bao gồm id CDP:
- Ngày nay:
{ role, name, nth } - Đề xuất:
{ role, name, nth, backendDOMNodeId?: number }
Điều này giữ tất cả hành động dựa trên Playwright hiện có hoạt động và cho phép evaluate CDP chấp nhận
cùng giá trị ref khi backendDOMNodeId có sẵn.
3.2 Điền backendDOMNodeId khi Snapshot
Khi tạo snapshot role:
- Tạo bản đồ ref role hiện có như ngày nay (role, name, nth).
- Lấy cây AX qua CDP (
Accessibility.getFullAXTree) và tính bản đồ song song của(role, name, nth) -> backendDOMNodeIdsử dụng cùng quy tắc xử lý trùng lặp. - Hợp nhất id trở lại thông tin ref được lưu trữ cho tab hiện tại.
Nếu ánh xạ thất bại cho một ref, để backendDOMNodeId undefined. Điều này làm cho tính năng
best-effort và an toàn để triển khai.
3.3 Hành vi Evaluate với Ref
Trong act:evaluate:
- Nếu
refcó mặt và cóbackendDOMNodeId, chạy evaluate element qua CDP. - Nếu
refcó mặt nhưng không cóbackendDOMNodeId, quay về đường dẫn Playwright (với lưới an toàn).
Lối thoát tùy chọn:
- Mở rộng shape yêu cầu để chấp nhận
backendDOMNodeIdtrực tiếp cho người gọi nâng cao (và cho debug), trong khi giữreflà giao diện chính.
4. Giữ đường dẫn khôi phục cuối cùng
Ngay cả với evaluate CDP, có những cách khác để làm kẹt tab hoặc kết nối. Giữ cơ chế khôi phục hiện có (terminate execution + ngắt kết nối Playwright) như phương án cuối cùng cho:
- người gọi cũ
- môi trường nơi CDP attach bị chặn
- trường hợp biên Playwright không mong đợi
Kế hoạch triển khai (Iteration đơn)
Deliverable
- Engine evaluate dựa trên CDP chạy bên ngoài hàng đợi lệnh per-page Playwright.
- Ngân sách timeout/abort end-to-end duy nhất được sử dụng nhất quán bởi người gọi và handler.
- Metadata ref có thể tùy chọn mang
backendDOMNodeIdcho evaluate element. act:evaluateưu tiên engine CDP khi có thể và quay về Playwright khi không.- Test chứng minh một evaluate bị kẹt không làm kẹt các hành động sau.
- Log/metric làm cho thất bại và dự phòng hiển thị.
Checklist triển khai
- Thêm helper "budget" được chia sẻ để liên kết
timeoutMs+ upstreamAbortSignalthành:- một
AbortSignalduy nhất - một deadline tuyệt đối
- helper
remainingMs()cho hoạt động downstream
- một
- Cập nhật tất cả đường dẫn người gọi để sử dụng helper đó để
timeoutMscó nghĩa giống mọi nơi:src/browser/client-fetch.ts(HTTP và dispatch trong process)src/node-host/runner.ts(đường dẫn node proxy)- Wrapper CLI gọi
/act(thêm--timeout-msvàobrowser evaluate)
- Triển khai
src/browser/cdp-evaluate.ts:- kết nối đến socket CDP cấp browser
Target.attachToTargetđể lấysessionId- chạy
Runtime.evaluatecho evaluate trang - chạy
DOM.resolveNode+Runtime.callFunctionOncho evaluate element - khi timeout/abort: best-effort
Runtime.terminateExecutionsau đó đóng socket
- Mở rộng metadata ref role được lưu trữ để tùy chọn bao gồm
backendDOMNodeId:- giữ hành vi
{ role, name, nth }hiện có cho hành động Playwright - thêm
backendDOMNodeId?: numbercho nhắm mục tiêu element CDP
- giữ hành vi
- Điền
backendDOMNodeIdtrong quá trình tạo snapshot (best-effort):- lấy cây AX qua CDP (
Accessibility.getFullAXTree) - tính
(role, name, nth) -> backendDOMNodeIdvà hợp nhất vào bản đồ ref được lưu trữ - nếu ánh xạ không rõ ràng hoặc thiếu, để id undefined
- lấy cây AX qua CDP (
- Cập nhật routing
act:evaluate:- nếu không có
ref: luôn sử dụng evaluate CDP - nếu
refgiải quyết thànhbackendDOMNodeId: sử dụng evaluate element CDP - nếu không: quay về evaluate Playwright (vẫn có giới hạn và có thể abort)
- nếu không có
- Giữ đường dẫn khôi phục "phương án cuối cùng" hiện có như dự phòng, không phải đường dẫn mặc định.
- Thêm test:
- evaluate bị kẹt timeout trong ngân sách và click/type tiếp theo thành công
- abort hủy evaluate (ngắt kết nối client hoặc timeout) và mở khóa các hành động tiếp theo
- thất bại ánh xạ quay về Playwright một cách sạch sẽ
- Thêm khả năng quan sát:
- thời lượng evaluate và counter timeout
- sử dụng terminateExecution
- tỷ lệ dự phòng (CDP -> Playwright) và lý do
Tiêu chí chấp nhận
- Một
act:evaluatecố tình bị treo trả về trong ngân sách người gọi và không làm kẹt tab cho các hành động sau. timeoutMshoạt động nhất quán qua CLI, công cụ agent, node proxy và lệnh gọi trong process.- Nếu
refcó thể được ánh xạ thànhbackendDOMNodeId, evaluate element sử dụng CDP; nếu không đường dẫn dự phòng vẫn có giới hạn và có thể khôi phục.
Kế hoạch test
- Test đơn vị:
- Logic khớp
(role, name, nth)giữa ref role và node cây AX. - Hành vi helper budget (khoảng trống, toán thời gian còn lại).
- Logic khớp
- Test tích hợp:
- Timeout evaluate CDP trả về trong ngân sách và không chặn hành động tiếp theo.
- Abort hủy evaluate và kích hoạt termination best-effort.
- Test hợp đồng:
- Đảm bảo
BrowserActRequestvàBrowserActResponsevẫn tương thích.
- Đảm bảo
Rủi ro và giảm thiểu
- Ánh xạ không hoàn hảo:
- Giảm thiểu: ánh xạ best-effort, dự phòng sang evaluate Playwright và thêm công cụ debug.
Runtime.terminateExecutioncó tác dụng phụ:- Giảm thiểu: chỉ sử dụng khi timeout/abort và ghi lại hành vi trong lỗi.
- Overhead bổ sung:
- Giảm thiểu: chỉ lấy cây AX khi snapshot được yêu cầu, cache per target và giữ phiên CDP ngắn hạn.
- Hạn chế relay extension:
- Giảm thiểu: sử dụng API attach cấp browser khi socket per page không có sẵn và giữ đường dẫn Playwright hiện tại như dự phòng.
Câu hỏi mở
- Engine mới nên có thể cấu hình như
playwright,cdphayauto? - Chúng ta có muốn hiển thị định dạng "nodeRef" mới cho người dùng nâng cao hay giữ chỉ
ref? - Snapshot frame và snapshot scoped selector nên tham gia vào ánh xạ AX như thế nào?