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

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

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

小夜