【免费下载链接】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)。
赞
【免费下载链接】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),仅供参考
网硕互联帮助中心






评论前必须登录!
注册