云计算百科
云计算领域专业知识百科平台

Kiln 实战指南:用 playwright-cli 驱动 UI,从沙箱服务器到 Locator 陷阱排查

  • 人工智能
  • AI 应用
  • AI 评测
  • RAG
  • 微调
  • 提示工程
  • 开发工具
  • 桌面应用

【免费下载链接】Kiln

Build, Evaluate, and Optimize AI Systems. Includes evals, RAG, agents, fine-tuning, synthetic data generation, dataset management, MCP, and more.

项目地址:
https://gitcode.com/gh_mirrors/ki/Kiln

点击查看 免费下载

本文围绕 Kiln 仓库中的 driving_the_ui.md 展开,介绍如何用 playwright-cli 这一"把浏览器当 shell 命令驱动"的工具来验证自己的 UI 改动:包括如何用 playwright_server.sh 起一个与 e2e 套件端口隔离的持久沙箱服务器、snapshot/find/click 的核心命令面、截图前必须等待页面"稳定"的原因,以及 Kiln 界面中重名标签、折叠面板等几类典型 locator 陷阱的排查方法。读完本文,你可以独立操作 Kiln 的 Web UI 完成"改代码 → 起沙箱 → 驱动浏览器 → 截图验证"的完整闭环,并理解仓库中对应的组件源码是如何造成这些陷阱的。

playwright-cli 的定位:浏览器即 shell 命令

playwright-cli 的设计是把浏览器作为一个 shell 命令来使用:一个持久会话,每条命令都作用在这个会话上。Kiln 团队明确它的用途边界——用它来"检查自己正在改的 UI",而不是当作测试框架。真正的端到端测试套件是 npm run tests:e2e(位于 app/web_ui/tests/e2e),由 playwright.config.ts 自行拉起并管理四个服务器;playwright-cli 面向的是开发过程中随时想看一眼真实界面的场景。两者共享同一份浏览器安装,但职责完全不同。

完整命令列表可以通过 playwright-cli –help 获得(Claude Code 环境中有同名 skill 内置了这份清单)。

先起服务器:playwright_server.sh 沙箱

playwright-cli 需要一个真正在跑的 app 来指向,所以第一步永远是起服务器:

.agents/scripts/playwright_server.sh start # 打印 http://localhost:6544

这个脚本支持 start / stop / status / reset / snapshot 五个子命令(-h/–help 可打印完整说明)。理解它的几个关键设计,能解释后面很多行为:

  • 端口刻意错开 e2e 套件。沙箱前端/后端默认跑在 6544/6545 端口(playwright_server.sh 第 27–28 行),而 e2e 套件占用 6534–6537(见 ports.ts:6534 前端、6535 后端、6536 mock provider、6537 mock kiln server)。之所以分开,是因为套件设置了 reuseExistingServer: false(playwright.config.ts 中多处出现),两套服务器若抢同一端口,后启动的一方会直接失败。端口可通过 KILN_DEV_FRONTEND_PORT / KILN_DEV_BACKEND_PORT 覆盖。
  • 数据隔离在专用沙箱目录。沙箱数据默认存放在 app/web_ui/.agent_dev_home(可用 KILN_DEV_HOME 覆盖)。脚本启动后端时通过 env HOME="$RUN_DIR" 把 HOME 指过去——Kiln 的项目数据读写都挂在 $HOME 下,所以一个隔离的 HOME 就能保证你在浏览器里点到的任何东西都不会碰真实的 Kiln 项目。脚本还内置 guard_not_real_home 防护:若 KILN_DEV_HOME 解析后恰好是你的真实 home,会直接拒绝,因为 reset 会删掉整个目录。
  • 首次 start 自动种入 fixture 项目。沙箱首次启动时会把已提交到仓库的 .agents/playwright_project 拷进沙箱(cp -R 加 .playwright_seed 戳文件保证只种一次),这样各个页面有真实数据可渲染,而不是 onboarding 向导。这个 fixture 是一个"客服工单分诊"项目,包含两个任务、20 条 runs、eval、skills、Docs & Search 等数据——详见 seeded_project.md。种完后脚本还会调用后端 /api/projects 断言项目确实被加载(verify_seed_loaded,第 394 行起):如果加载失败,应用会零项目起步并把你重定向到 /setup,与"没种过种子"在视觉上完全一样,是极难从外部诊断的问题,所以脚本宁可打印明确警告也不让 hint 静默出错。
  • 可选接入真实模型 provider。如果环境变量 OPENROUTER_QA_KEY 已设置,种沙箱时会把它写进沙箱的 settings.yaml,应用启动即带有已连接的 OpenRouter,可以发起真实模型调用;start 会在存在时明确报告。变量名刻意不叫 OPENROUTER_API_KEY——后者是 pytest –runpaid 等付费测试读取的变量,共用一个名字会让"没人在场"的脚本也开始花钱。
  • reset 与 snapshot 构成 fixture 的更新回路。reset 停止、删除沙箱、重新种一份干净的;snapshot 则把当前沙箱里的项目反向捕获回 .agents/playwright_project,让未来的会话从你构建好的初始状态起步。snapshot 只接受 Kiln Projects 下恰好一个项目(多个会要求你先删到只剩一个),并会清除 .git/.DS_Store 后打印 git diff 提示人工复核。

启动成功后,脚本会直接打印接下来要执行的三条命令(含已填好的 seeded id),见下一节。

落地到带数据的页面:ui_state 三命令序列

Kiln 的布局在没有 localStorage 中的 ui_state 时,无论你要访问什么 URL 都会重定向到任务选择器。这不是服务器没起好,而是浏览器状态缺失。源码上,ui_state 是一个 localStorage store,结构定义在 stores.ts:

export type UIState = {
current_project_id: string | null
current_task_id: string | null
selected_model: string | null
}

export const ui_state = localStorageStore("ui_state", default_ui_state)

start 打印的命令序列就是为穿过这道门而设计的,三条必须按顺序全部执行:

playwright-cli open http://localhost:6544
playwright-cli localstorage-set ui_state \\
'{"current_project_id":"<id>","current_task_id":"<id>","selected_model":null}'
playwright-cli goto http://localhost:6544

顺序之所以死板,是因为 localstorage-set 在没有打开的浏览器时会直接失败,而对已打开的浏览器再跑一次 open 会开一个全新 context、把刚写进去的 storage 丢掉。最后一步要用 goto 而不是 reload:写 storage 后页面其实已经坐在被重定向到的任务选择器上,reload 只会留在原地。selected_model 在有 key 的沙箱中会被预填为 openrouter/gpt_5_6_luna(脚本中的 SEED_MODEL),这样 Run 页面打开即选中便宜模型,省去每次在模型下拉框里 find-open-find-click 的开销。

穿过这道门之后,goto 任意深链接都可以,例如 /dataset/<project_id>/<task_id>、/generate/<project_id>/<task_id>。

核心命令面:open / snapshot / find / click / screenshot

基本驱动循环如下:

playwright-cli open http://localhost:6544 # 启动浏览器并导航
playwright-cli snapshot # 以无障碍树形式输出页面
playwright-cli find "Generate Eval Data" # 大页面上做搜索而不是通读
playwright-cli click e44 # ref(如 e44)来自 snapshot
playwright-cli screenshot –filename=/tmp/ui.png
playwright-cli console # 控制台消息
playwright-cli requests # 网络活动
playwright-cli close

其中 snapshot 是默认首选:它给出页面的结构与文本,比图片更便宜,也更接近 e2e 定位器真正匹配的内容。当问题是视觉性的——间距、对齐、颜色、元素是否重叠——才去截图,然后读回这张 PNG。find 则是大页面上的高效替代:与其读整棵 snapshot 树,不如直接按文本搜索,它会一并报告匹配元素的真实 role 和 ref。

浏览器配置缺失时的报错与修复

如果 open 报 Chromium distribution chrome was not found,说明全局配置缺失——没有它,playwright-cli 会尝试启动品牌版 Google Chrome,而容器里通常没有。一次性绕过是加 –browser=chromium;正式修复是 setup_env.sh –add-playwright。

这份配置在 ~/.playwright/cli.config.json——是 playwright-cli 的全局配置而非仓库内文件,因为"装了哪个浏览器"是关于机器的事实而非关于代码的事实。两个 setup 脚本都只在文件不存在时写入,你自己的修改会被保留。正是这个全局属性,让上述命令在任意目录下都能跑,而不限于仓库根目录。

截图:永远不要相信第一帧

在 open 或 goto 之后立刻截图,得到的非常经常是纯白画面,而且输出里不会有任何提示。此时 DOM 其实已经完整——snapshot 能返回整个页面,document.readyState 也是 "complete"——但 Chromium 还没绘制(paint)出来。因此 snapshot 能跑通不能当作"页面已渲染"的门禁。

这件事之所以关键,是因为"白屏"在正常运行中是"应用没启动起来"的典型信号;一个假白屏会让你把好好的应用报成坏的。正确做法是先让页面稳定再截图:

playwright-cli run-code "async page => await page.waitForLoadState('networkidle')"
playwright-cli screenshot –filename=/tmp/ui.png

networkidle 是这里的正确默认:Kiln 的页面在 hydrate 之后还会向 API 发请求取数,networkidle 同时等到了数据与绘制。如果你只需要等到绘制、不关心数据,两个动画帧就够了:

playwright-cli run-code "async page => await page.evaluate(() => new Promise(r => requestAnimationFrame(() => requestAnimationFrame(r))))"

让 find 告诉你 role:别猜元素的语义角色

不要根据元素"看起来像什么"去猜它的 role。Kiln 用 button 样式装饰了大量 link,于是 getByRole("button", { name: "Get Started" }) 会一无所获,而 find "Get Started" 会返回 link "Get Started" [ref=e9]——一次调用同时拿到 ref 和真实 role。正确姿势是先 find,再按它报告的内容写 locator。

更进一步:直接点击 find/snapshot 给出的 ref。ref 在构造上就是单一元素,天然不可能触发 strict mode 的"解析到多个元素"歧义——这在下面一节会看到有多重要。

一个标签对应两个元素:strict mode 与 /dev/null 陷阱

Kiln 会为某个菜单项和它打开的对话框里的提交按钮复用同一个标签。以 Dataset 页面的批量标签工具栏为例:Add Tags 既是已打开下拉菜单里的一项,又是随之弹出的对话框内部那个处于禁用态的提交按钮。对应的组件源码见 table_toolbar.svelte:一个 DaisyUI dropdown,触发器是 role="button" 的 div,dropdown-content 里并排放着 Add Tags 与 Remove Tags 两个按钮。

所以这样一条命令:

playwright-cli click "getByRole('button', { name: 'Add Tags', exact: true })"

会失败并报 strict mode violation: … resolved to 2 elements,错误信息会列出两个匹配以及各自的消歧 locator,而对话框根本没有打开。

playwright-cli 把这类失败打印在 stdout 上并以退出码 1 结束。 真正把它藏起来的因此是 >/dev/null——包括那种为了转录整洁而随手给每条命令都加上的 >/dev/null 2>&1。实测对照:

重定向方式你能看到什么
无重定向,或 2>/dev/null 完整的 strict-mode 错误
1>/dev/null,或 >/dev/null 2>&1 什么都没有

所有情况下退出码都是 1。如果非要丢弃输出,就去检查 $?——一个"看起来什么都没点中"的失败点击,就是两行的 locator 修复拖成一下午错误猜测的典型起点。

定位器本身的修复方案是先 find、点 ref:

playwright-cli click "div.dropdown [role=button]" # 打开菜单
playwright-cli find "Add Tags" # 3 个匹配,其中恰好 1 个带 ref
playwright-cli click f1e369 # 打开对话框

find 对该标签报告三个匹配,其中恰好一个带 ref:就是那个菜单项。范围限定(scoping)也可以,只要范围足够窄到唯一,比如 ".dropdown-content button >> nth=0";而光秃秃的 ".dropdown-content button" 本身就是 strict-mode 违规,因为菜单里同时装着 Add Tags 和 Remove Tags。

几个值得注意的边界行为:

  • 菜单在命令之间保持打开状态。 playwright-cli 维持单一持久会话、触发器保持焦点,所以一条命令打开的 DaisyUI dropdown 对下一条命令来说仍然开着、可见、仍在无障碍树里——上面那段序列就是三个普通的独立进程。run-code 里"绕过页面直接 click"是最后手段:它绕过了 Playwright 的 actionability 检查,与"像用户一样驱动 UI"背道而驰。
  • 要先打开菜单再点菜单项。 菜单关闭时,菜单项是 visibility: hidden,此时同样的 locator 恰好解析到唯一元素——对话框里那个禁用提交按钮——于是你得到一个完全不同的失败:5 秒超时后报 element is not enabled。同样的标签、同样的命令,诊断却毫不相干。
  • 工具栏上并非每个控件都是菜单。 只有标签控件是 dropdown;它旁边的删除按钮单击即直接打开 modal(对应 table_toolbar.svelte 中独立于 dropdown 的删除按钮)。

折叠的 "Advanced Options" 会把字段藏进 find 的盲区

Kiln 的 Collapse 组件就是一个 checkbox 加一个隐藏面板,源码见 collapse.svelte:DaisyUI 的 collapse 结构,<input type="checkbox" bind:checked={open}> 控制 .collapse-content 的展开。闭合状态下面板里的字段在 DOM 中存在但 visibility: hidden——snapshot 和 find 因此完全不会报告它们。

推论:find "Extractor Name" 返回空意味着该区段是折叠的,不代表字段不存在。用 checkbox 而不是标签文本来确认状态:

playwright-cli eval "() => { const d=[…document.querySelectorAll('dialog')].filter(x=>x.open)[0]; return d.querySelector('input[type=checkbox]').checked }"

因为每次点击都会切换 checkbox,一次"看起来没生效"的点击紧跟第二次点击,会把状态原样带回起点。更稳的做法是点击打开的对话框内部的 checkbox——"dialog[open] .collapse input[type=checkbox]"——它无歧义;而标签的 ref 在周围表单重渲染之后可能已经失效。

小结:可核验的关键路径

主题依据位置
沙箱服务器脚本、端口与数据隔离 .agents/scripts/playwright_server.sh
命令面与本文各陷阱的原始描述 .agents/skills/playwright/references/driving_the_ui.md
种子 fixture 内容与 provider 说明 .agents/skills/playwright/references/seeded_project.md
ui_state 结构与 localStorage 持久化 stores.ts
Add Tags/Remove Tags 工具栏组件 table_toolbar.svelte
Collapse 组件(checkbox + 隐藏面板) collapse.svelte
e2e 套件端口与隔离配置 ports.ts、playwright.config.ts

这套方法的经验密度很高:snapshot 先行、find 定 role、点 ref 消除歧义、networkidle 再截图、失败信息只走 stdout 所以别用 >/dev/null 2>&1 吞掉它——每一条都对应 Kiln 源码里一个真实存在的 UI 结构或状态机,照着 SKILL.md 的 reference 索引可以进一步延伸到 e2e 套件的运行与调试(e2e_suite.md)和 fixture 的扩展(extending_the_fixture.md)。

赞

分享

  • 人工智能
  • AI 应用
  • AI 评测
  • RAG
  • 微调
  • 提示工程
  • 开发工具
  • 桌面应用

【免费下载链接】Kiln

Build, Evaluate, and Optimize AI Systems. Includes evals, RAG, agents, fine-tuning, synthetic data generation, dataset management, MCP, and more.

项目地址:
https://gitcode.com/gh_mirrors/ki/Kiln

点击查看 免费下载

上一篇:
Mac Mouse Fix:让第三方鼠标在macOS上性能提升200%的驱动增强工具

下一篇:
CANN/asc-devkit SIMT-API半精度余弦函数

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

赞(0)
未经允许不得转载:网硕互联帮助中心 » Kiln 实战指南:用 playwright-cli 驱动 UI,从沙箱服务器到 Locator 陷阱排查
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!