playwright-interactive:用持久化 js_repl 做 UI 迭代調試

前言

讓 AI 編程助手改一版前端、再幫你「點一遍頁面看對不對」,聽起來合理,實際卻常卡在流程上:每次驗證都重新拉起瀏覽器、重新找頁面句柄;改完代碼後是該 reload 還是整進程重啓說不清;功能路徑走通了,卻沒單獨做視覺檢查,首屏裁切、對比度、動效中途狀態這類問題容易漏掉。和「一次性 CLI 跑通某個網頁流程」相比,本地 Web 或 Electron 應用的調試更需要同一會話裏反覆迭代

OpenAI 在精選技能庫中提供了 playwright-interactive Skill。它不走終端裏的一次性 playwright-cli 命令流,而是通過持久化的 js_repl 會話掛住 Playwright / Electron 句柄,讓 Agent 像開發者一樣:改代碼 → 刷新或重啓 → 功能 QA → 視覺 QA → 截圖留證。本文依據官方 SKILL.md、同倉庫的 playwright Skill,以及 Codex 相關 Issue 交叉覈實後整理。

這是什麼

playwright-interactive 是一個 Agent Skill(通用 SKILL.md 格式),收錄於 OpenAI 維護的 openai/skills 倉庫 skills/.curated/playwright-interactive 目錄。官方一句話定位是:

通過 js_repl 做持久化的瀏覽器與 Electron 交互,用於快速迭代的 UI 調試。

它解決的問題很具體:在本地調試 Web 或 Electron 應用時,跨多輪修改保持同一套 Playwright 句柄(browser / context / page,或 electronApp / appWindow),在不必每次重啓整套工具鏈的前提下,完成功能驗證與視覺 QA。

同倉庫還有面向 CLI 的 playwright Skill(playwright-cli:打開頁面、snapshot、按 ref 點擊)。二者互補:playwright 更適合「對某個 URL 跑一遍自動化」;playwright-interactive 更適合「對着正在開發的本地應用,像 REPL 一樣邊改邊驗」。

需要事先說明:openai/skills 倉庫 README 已標註 deprecated,後續示例轉向 OpenAI Plugins。更關鍵的是,該 Skill 強依賴的 js_repl 在當前 Codex 中已被標記爲 removed(社區 Issue 與 codex features list 輸出可交叉確認)。因此下文按官方 SKILL.md 如實介紹其設計與用法,並在注意事項中單獨寫清運行時前提——不要把它當作在所有當前 Codex 環境中都能開箱即用的能力

核心功能與亮點

覈實官方資料後,能力可以概括爲以下幾點。

1. 持久化 Playwright 會話,而不是每次冷啓動

js_repl 裏用 var 聲明頂層句柄(browsercontextpageelectronAppappWindow 等),後續 cell 直接複用。官方明確:把 js_repl_reset 當作恢復手段,不要當日常清理——重置內核會毀掉所有 Playwright 句柄。

2. 覆蓋桌面 Web、移動 Web、原生窗口與 Electron

  • 桌面 Web:默認顯式 viewport(如 1600×900),便於可復現截圖與斷點檢查。
  • 移動 Web:單獨的 mobileContext / mobilePage(如 390×844isMobile + hasTouch)。
  • 原生窗口模式:viewport: null,用於驗證真實窗口尺寸、系統 DPI、瀏覽器 chrome 相關行爲。
  • Electron:通過 Playwright 的 _electron.launch,按真實桌面窗口處理(noDefaultViewport)。

官方建議:例行迭代用顯式 viewport;需要最終環境相關簽收時,再單獨做一輪 native-window;模式切換視爲 context 重置,不要混用。

3. 先建 QA 清單,再分功能 QA 與視覺 QA

測試前要寫一份共享 QA inventory,來源包括:用戶需求、已實現的用戶可見行爲、最終回覆裏打算簽收的聲明。功能 QA 要求用真實用戶輸入(鍵盤、鼠標、點擊、觸摸等 Playwright API),page.evaluate / electronApp.evaluate 可以探查狀態,但不能算簽收輸入。視覺 QA 與功能 QA 分開,且用戶可見聲明必須在「該聲明應被感知的具體狀態」下檢查。

4. 改代碼後的 reload / relaunch 決策清晰

  • 僅渲染層改動:Web 用 reloadWebContexts(),Electron 用 appWindow.reload(...)
  • 主進程、preload 或啓動邏輯改動:關閉並重新 launch Electron。
  • 對進程歸屬或啓動代碼不確定時:寧可 relaunch,不要猜測。

5. 截圖與模型座標對齊、視口適配檢查內建

若通過 codex.emitImage(...) 把截圖交給模型解讀,官方默認要求歸一化到 CSS 像素,避免 Retina / 高 DPI 下設備像素座標與 Playwright CSS 座標錯位。簽收前還要做 viewport fit:截圖爲主、數值檢查爲輔;出現裁切、遮擋、被擠出視口,即使 scroll 指標看起來正常,也視爲失敗。

安裝與啓用

Agent Skill 的通用形態是:一個目錄 + SKILL.md(本 Skill 另含 agents/assets/ 等可選資源)。各工具掃描路徑不同,以下爲可覈實的安裝方式。

在 Codex 中安裝 Skill 本體

curated 技能可用 Codex 內的 $skill-installer 按名稱安裝:

$skill-installer playwright-interactive

或指定 GitHub 目錄:

$skill-installer install https://github.com/openai/skills/tree/main/skills/.curated/playwright-interactive

安裝後通常位於 $CODEX_HOME/skills(默認 ~/.codex/skills)。若未自動出現,需重啓 Codex。

啓用 js_repl(Skill 前置條件)

官方 SKILL.md 要求本 Skill 必須啓用 js_repl。若缺失,在 ~/.codex/config.toml 中配置:

[features]
js_repl = true

也可啓動新會話時加:

codex --enable js_repl
# 等價於 -c features.js_repl=true

啓用後需新開 Codex 會話刷新工具列表。官方還寫明:在 js_repl + Playwright 沙箱支持完成前,需暫時關閉沙箱,例如:

codex --sandbox danger-full-access

或將 sandbox_mode 配成 danger-full-access

交叉覈實:在較新的 Codex CLI(社區報告如 0.128.0)中,codex features list 可能顯示 js_repl / js_repl_tools_onlyremoved。此時按文檔啓用也不會出現對應工具,Skill「按原文」無法工作。社區討論中更接近的替代運行時是 node_repl(及 Browser Use 文檔中的相關 MCP 表面),但官方 curated 的本 Skill 正文仍寫的是 js_repl,遷移 PR 是否合入需以倉庫現狀爲準。

在 Cursor / Claude Code 等工具中

通用 Agent Skills 標準下,可將該目錄放到工具約定路徑,例如:

  • Cursor:項目級 .cursor/skills/playwright-interactive/,或用戶級 ~/.cursor/skills/;亦兼容 .agents/skills/.codex/skills/ 等。
  • Claude Code:.claude/skills/playwright-interactive/~/.claude/skills/

注意:Skill 指令深度綁定 Codex 的 js_replcodex.emitImage 等運行時能力。把 SKILL.md 拷到 Cursor / Claude Code 後,Agent 仍可能缺少同等持久化 JS REPL,只能借鑑其中的 QA 清單、reload 決策與截圖規範,不能假設「複製目錄即可完整復現」。

一次性項目依賴安裝

在要調試的項目目錄執行(換工作區需重做):

test -f package.json || npm init -y
npm install playwright
# 僅 Web、需要 headed Chromium 或移動模擬時:
# npx playwright install chromium
# 僅 Electron,且當前工作區就是應用本身時:
# npm install --save-dev electron
node -e "import('playwright').then(() => console.log('playwright import ok')).catch((error) => { console.error(error); process.exit(1); })"

本地 Web 調試時,用持久 TTY 會話跑開發服務器(如 npm start),不要依賴短命後臺命令;page.goto 前確認端口已在監聽。

典型用法示例

以下片段均來自官方 SKILL.md,可按 cell 在 js_repl 中執行。

1. Bootstrap(只跑一次)

var chromium;
var electronLauncher;
var browser;
var context;
var page;
var mobileContext;
var mobilePage;
var electronApp;
var appWindow;

try {
  ({ chromium, _electron: electronLauncher } = await import("playwright"));
  console.log("Playwright loaded");
} catch (error) {
  throw new Error(
    `Could not load playwright from the current js_repl cwd. Run the setup commands from this workspace first. Original error: ${error}`
  );
}

綁定規則:共享句柄用 var,以便後續 cell 複用;句柄看起來過期時,將該綁定設爲 undefined 後重跑對應 cell,而不是到處加恢復邏輯。

2. 啓動或複用桌面 Web 會話

本地地址優先用 127.0.0.1,少用 localhost

var TARGET_URL = "http://127.0.0.1:3000";

if (page?.isClosed()) page = undefined;

await ensureWebBrowser();
context ??= await browser.newContext({
  viewport: { width: 1600, height: 900 },
});
page ??= await context.newPage();

await page.goto(TARGET_URL, { waitUntil: "domcontentloaded" });
console.log("Loaded:", await page.title());

其中 ensureWebBrowser 等 helper 在官方 Bootstrap 後的 Shared web helpers 一節給出:瀏覽器斷開則清空並重 launch(默認 headless: false)。

3. 迭代中的刷新與 Electron 重啓

Web 渲染層刷新:

await reloadWebContexts();

Electron 僅渲染層:

await appWindow.reload({ waitUntil: "domcontentloaded" });
console.log("Reloaded Electron window");

主進程 / preload / 啓動相關改動後重啓 Electron:

await electronApp.close().catch(() => {});
electronApp = undefined;
appWindow = undefined;

electronApp = await electronLauncher.launch({
  args: [ELECTRON_ENTRY],
});

appWindow = await electronApp.firstWindow();
console.log("Relaunched Electron window:", await appWindow.title());

4. 任務真正結束時再 Cleanup

退出 Codex、關終端或丟失 js_repl 不會隱式執行 browser.close() / electronApp.close()。Electron 尤其可能殘留進程。官方 cleanup 示例(節選邏輯):

if (electronApp) {
  await electronApp.close().catch(() => {});
}
if (mobileContext) {
  await mobileContext.close().catch(() => {});
}
if (context) {
  await context.close().catch(() => {});
}
if (browser) {
  await browser.close().catch(() => {});
}

browser = context = page = undefined;
mobileContext = mobilePage = undefined;
electronApp = appWindow = undefined;

console.log("Playwright session closed");

若馬上退出 Codex,應先跑 cleanup,看到 "Playwright session closed" 再退出。

5. 在對話裏怎麼用

可以明確要求 Agent 按該 Skill 工作,例如:

使用 playwright-interactive:對本地 http://127.0.0.1:3000 做一輪 UI 調試。
先寫 QA inventorybootstrap js_repl,保持同一 page 句柄;
我改完代碼後只 reload,分別做功能 QA 與視覺 QA,並做 viewport fit 檢查。

把目標 URL、是 Web 還是 Electron、是否需要移動端/原生窗口簽收寫清楚,Agent 才應按官方 core workflow 執行,而不是退回到一次性 CLI 腳本。

適用場景與注意事項

適合誰、什麼場景:

  • 本地 Web 或 Electron 應用的多輪 UI 迭代調試
  • 需要 Agent 在同一瀏覽器/窗口會話裏連續操作、截圖、對照聲明做簽收;
  • 既要端到端功能路徑,又要單獨的視覺 QA、首屏/最小視口適配檢查;
  • playwright(CLI)Skill 搭配:探索外網或一次性流程用 CLI,貼着本地工程反覆改 UI 用 interactive。

限制與坑(官方 + 交叉覈實):

  1. 運行時依賴 js_repl:當前 Codex 若已移除該特性,按 SKILL.md 原文無法啓用;安裝 Skill 目錄不等於能力可用。
  2. 臨時要求關閉沙箱:官方寫明需 --sandbox danger-full-access,有安全與權限含義,勿在不信任的倉庫上盲目開啓。
  3. js_repl_reset 會毀掉句柄:只在內核真正卡住時用。
  4. 常見失敗:找不到 playwright 模塊(未在當前 cwd 安裝);缺 Chromium 可執行文件(需 npx playwright install chromium);net::ERR_CONNECTION_REFUSED(dev server 未在持久會話中運行);Electron 下不要用 context().newPage() 當 scratch page(官方明確不支持該路徑)。
  5. 倉庫狀態openai/skills 已 deprecated;長期應關注 OpenAI Plugins 與 Codex Skills 文檔 的替代方案。

小結

playwright-interactive 把「持久化瀏覽器/Electron 句柄 + 共享 QA 清單 + 功能/視覺分軌簽收 + reload/relaunch 決策」固化成 Agent 可執行的工作流,目標是讓 UI 調試像開發者坐在 headed 瀏覽器前一樣可迭代,而不是每次從冷啓動腳本重來。它與 CLI 向的 playwright Skill 形成互補,但強依賴 js_repl 等運行時;在當前 Codex 中啓用前,務必先確認本機是否仍暴露該工具。

官方目錄:https://github.com/openai/skills/tree/main/skills/.curated/playwright-interactive
相關對照:https://github.com/openai/skills/tree/main/skills/.curated/playwright

羽毛球分组比赛记分
小程序二维码

欢迎使用《羽毛球分组比赛记分》微信小程序

小夜