# dsh-playwright-cli [![npm](https://img.shields.io/npm/v/dsh-playwright-cli)](https://www.npmjs.com/package/dsh-playwright-cli) [![license](https://img.shields.io/npm/l/dsh-playwright-cli)](LICENSE) 在 **DeepSeek Harness** 里驱动真实浏览器:一个模型可调用的工具 `playwright_cli` 包住 [`microsoft/playwright-cli`](https://github.com/microsoft/playwright-cli),外加一个随插件注册的 `playwright-cli` 技能,把命令词表教给模型。 playwright-cli 自己就推荐编码智能体走「CLI + 技能」而不是 MCP:命令短,页面快照落到文件里, 不用把整棵可访问性树或一大张工具 schema 塞进上下文。这个插件保留这套设计,只补上缺的两块: 一个能调用的工具,和一个按需加载的技能。 English: [README.md](README.md) ## 安装 **在应用里:** **Plugins → Plugin Manager → 安装 bundle → `dsh-playwright-cli`**。 **在 shell 里**(Harness CLI 在 PATH 上时): ```bash dsh plugin --profile <你的 profile> add dsh-playwright-cli ``` **让智能体装:** `plugin_manager`,`action: install_bundle`,`target: dsh-playwright-cli`。 > Windows 上 `dsh` 这个名字很容易被别的软件占用(PATH 冲突)。如果 `dsh plugin` 不是 Harness 的 CLI, > 直接用应用内的插件管理器。 机器上需要有 playwright-cli: ```bash npm install -g @playwright/cli@latest ``` 还需要一个浏览器 —— playwright-cli 自己不带。二选一:下载它自带的 Chromium(约 150 MB): ```bash playwright-cli install-browser chrome-for-testing ``` 或者直接复用系统里已有的浏览器,零下载: ```text playwright_cli open https://example.com --browser=msedge ``` ## 快速上手 ```text playwright_cli open https://playwright.dev --browser=msedge playwright_cli snapshot --depth=4 → [artifacts] C:\...\.playwright-cli\page-....yml (读这个文件拿 ref) playwright_cli click e15 playwright_cli fill e7 "user@example.com" playwright_cli screenshot --filename=shot.png playwright_cli close ``` 每次结果都带页面状态、本次产生的产物(绝对路径)和 CLI 退出码。`--raw` 只返回值本身, 例如 `--raw eval "document.title"`。 ## 工具参数 | 参数 | 必填 | 说明 | | --- | --- | --- | | `command` | 是 | 一条 playwright-cli 命令,不含 `playwright-cli` 前缀;含空格的参数加引号 | | `session` | 否 | 命名会话(`-s=`),用于隔离并行任务 | | `workdir` | 否 | 工作目录,产物写到其下 `.playwright-cli/`;默认会话目录 | | `timeoutMs` | 否 | 单次调用超时,默认取配置,上限 900000 | ## 配置 全部可选,写在 profile 的 `cordis.patch.yml` 里 `playwright-cli` 这条行下: | 字段 | 默认 | 说明 | | --- | --- | --- | | `executable` | 自动发现 | `playwright-cli.js` 绝对路径(用 `nodePath` 跑)或任意可执行文件 | | `nodePath` | PATH 里的 `node` | 跑 CLI 以及它再拉起的浏览器守护进程用的 Node | | `session` | 无 | 默认会话名 | | `timeoutMs` | `120000` | 单条命令上限 | | `disableUpdateNotifier` | `true` | 注入 `NO_UPDATE_NOTIFIER=1`(见下文 Node 24 那条) | | `env` | `{}` | 追加环境变量,例如 `PLAYWRIGHT_MCP_BROWSER: chrome` | ## 实现方式 - **不经过 shell**:命令行分词后按 argv 直接交给 `node playwright-cli.js …`,URL 里的 `&`、 路径里的空格、`--data="k=v"` 和 Windows 引号规则都不会破坏命令。 - **自动发现**:从会话目录逐级向上找本地 `node_modules/@playwright/cli` 或 `node_modules/playwright`, 再查全局 npm / pnpm / Homebrew 的常规位置;`executable` 可全部覆盖。 - **用真 Node,不用 Host 二进制**:桌面版 Host 是 Electron-as-node(`ELECTRON_RUN_AS_NODE=1`), `process.execPath` 是应用本体;playwright-cli 会用它再拉起浏览器守护进程,于是崩在 `too many arguments`。所以插件优先从 PATH 解析真正的 `node`。 - **退出码可信**:Node 24 上 playwright-cli 的更新检查会在命令成功之后把进程打崩(退出码变成 `0xC0000409`),默认已关掉更新提示。 - **产物即文件**:快照、截图、PDF、视频、trace、console 日志都解析成绝对路径列在 `[artifacts]` 下, 模型用 `read` / `read_image` 去取,不为内容付 token。 - **给提示,不猜**:首次最常见的两种失败(没装浏览器、浏览器没开)会多一行 `[hint]`,直接给出下一条命令。 ## 排障 | 现象 | 原因 | 处理 | | --- | --- | --- | | `playwright-cli is not installed or was not found` | 本地和全局都没有 | `npm i -g @playwright/cli@latest`,或配置 `executable` | | `Browser "chrome-for-testing" is not installed` | 没有下载浏览器 | `playwright_cli install-browser chrome-for-testing`,或 `--browser=msedge` | | `Chromium distribution 'chrome' is not found` | Chrome 不在默认路径 | `--browser=msedge` 或 `--browser=chrome` | | `too many arguments. Expected 1 argument but got 2` | 用 Electron 二进制跑了 CLI | 不要写 `nodePath`,让插件自己解析 `node` | | `The browser '' is not open` | 会话闲置退出或从没开过 | 重新 `open`(`--persistent` 可保留 profile) | | 百度出现 `百度安全验证` | 站点级反自动化验证码 | 不是插件问题:如实报告,换引擎 | | 快照只有页头页脚 | 结果在命令返回之后才渲染 | 再 `snapshot` 一次,或直接 `find "<文字>"` | ## 维护指南 ### 仓库结构 | 路径 | 负责什么 | 什么时候改 | | --- | --- | --- | | `index.js` | 工具 schema、进程启动、安装发现、产物归集、hint、UI 呈现 | CLI 参数/输出形状变化,或 DSH 的 `ToolDefinition` 变化 | | `skill.js` | 给模型看的技能正文(命令表、循环、排障) | 命令面变化 | | `cordis.patch.yml` | Loader 行、默认配置、字段注释 | 新增配置项 | | `locale/*.json`、`icon.svg` | 插件管理器里的标题、描述、图标 | 文案/品牌 | | `README.md`、`README.zh.md` | 用户 + 维护者文档 | 任何行为变化 | ### 适配新版 playwright-cli 1. `npm view @playwright/cli version`,然后 `npm i -g @playwright/cli@latest`。 2. `playwright-cli --help` —— 和 `skill.js` 里的命令表逐条对。**技能就是模型的合同**,先同步它。 3. 看 `open` / `attach` 的参数(`--browser`、`--device`、`--persistent`、`--profile`、`--config`)。 新参数通常只改文档,不动代码。 4. 看输出标记:`### Page`、`### Snapshot`、`[Snapshot](.playwright-cli/…)`、`### Events` 里的 `console-….log#L1-L6`。路径形状一变,就改 `collectArtifacts()` 里的 `MARKDOWN_LINK`、 `FILE_TOKEN` 和 `#L…` 截断。 5. 看会话选择:插件同时传 `-s=` 并设置 `PLAYWRIGHT_CLI_SESSION`。如果某个版本开始拒绝其中之一, 去掉 `execute()` 里设置环境变量那一行。 6. 看 `hintFor()` 匹配的报错文案:`is not installed`、`Chromium distribution 'chrome' is not found`、 `is not open, please run open first`。 7. 跑一遍下面的冒烟测试,再升级版本号、更新 CHANGELOG。 ### 输出 schema 是被强校验的 DSH 会用 `output.schema` 校验工具返回的 `value`,不匹配就整次调用失败并报 `returned invalid output`。凡是可能为 `null` 的字段都要写成 `oneOf: [{type:'string'}, {type:'null'}]`(0.1.0 就踩过 `signal: null` 这个坑)。 改返回值时,值和 schema 一起改。 ### 适配新版 DSH 1. `dsh --version`;只有确实依赖了新能力才抬高 `engines.dsh`。 2. 复查插件用到的四个服务。在活动会话里用 `cordis_inspect_query`:`platform: host`、 `provider: Service`、`input: {"service":"tools"}`(另外还有 `subprocess`、`skills`、 `workingDirectory`)。 3. 确认 `ctx.subprocess.resolveExecutable('node')` 还能解析;如果某个 Host 不再是 Electron-as-node, 退回 `process.execPath` 的兜底也照样能用。 4. 本插件故意**不声明 `Config`**(否则要引入 schemastery 依赖)。如果哪天应用内配置编辑强制要求 schema, 再加 `export const Config`。 5. 重装后确认两半都注册上了:工具出现在智能体工具表里,`playwright-cli` 出现在技能目录里。 ### 发版清单 语义化版本:**patch** = 文档/hint;**minor** = 新增命令或配置项(向后兼容); **major** = 改包名/行 id、删配置、改工具名。 ```bash npm pack --dry-run # 确认 index.js、skill.js、patch、locale、icon、文档、LICENSE 都在 git add -A && git commit -m "release: vX.Y.Z" git tag vX.Y.Z && git push --follow-tags npm publish # 在 CI 里发布时加 --provenance ``` 然后从干净状态验证: ```bash dsh plugin --profile remove dsh-playwright-cli dsh plugin --profile add dsh-playwright-cli ``` 再跑冒烟测试,最后把 `CHANGELOG.md` 补上。 ### 本地调试循环 profile 里装的是本目录的软链,但 Host 会缓存已加载的模块。改完 `index.js`、`skill.js` 或 `cordis.patch.yml` 之后,要卸载再重装这个 bundle(插件管理器开关,或用 `plugin_manager` 的 `remove_bundle` + `install_bundle`),只有重新加载才会生效。 ### 冒烟测试 | 命令 | 预期 | | --- | --- | | `--version` | 版本号,`exit=0` | | `list` | `(no browsers)` 或当前会话列表 | | `open https://example.com --browser=msedge` | 页面标题,`.playwright-cli/page-*.yml` 产物 | | `find "Example"` | 带上下文的匹配节点 | | `screenshot --filename=shot.png` | `shot.png` 出现在绝对路径 `[artifacts]` 里 | | `close` | `Browser '' closed` | 搜索引擎是最诚实的端到端检查:Bing/Google 无头可用;百度会对自动化返回验证码页,遇到就如实报告, 不要拿同一个 URL 反复重试。 ## 发布到 npm 与上架市场 1. 替换 `package.json` 里的占位符:`YOUR NAME`、`YOUR_GITHUB_USER`(三处 URL)。 2. `git init && git branch -M main`,在 GitHub 建库并推送。 3. `npm login`(开了 2FA 的账号需要一次性验证码),然后 `npm publish`。 4. 发布必须走公共 npm registry;`registry.npmmirror.com` 这类镜像是给**安装**用的,`npm publish` 不能走镜像。 5. 可选:用 GitHub Actions 带 `--provenance` 发布(需要 `id-token: write`),或在 npm 上给该仓库配置 trusted publishing。 ### 让插件出现在应用内市场 只要包在 npm 上,按包名直接安装就已经可用。应用内的**插件市场**(`dshmarket`)读的是一份策展目录 `https://awesome-dsh-plugin.com/plugins.json`,每次请求现取;条目包含 `name`、`description`、 `category`(至少一个)和 `source`(npm 包或仓库 URL)。收录是策展制,向该目录提交条目 (其仓库接受新插件的 PR/issue),示例: ```json { "name": "dsh-playwright-cli", "description": "microsoft/playwright-cli as a DSH tool plus a bundled skill.", "category": ["tool", "browser"], "source": { "kind": "npm", "pkg": "dsh-playwright-cli" } } ``` 用户也可以自己把这份目录地址加到 **Plugins → Discover → Index sources** 里。 ## 许可与致谢 插件本体 MIT。`skill.js` 的技能正文改编自 [`microsoft/playwright-cli`](https://github.com/microsoft/playwright-cli) 的 `SKILL.md` (Apache-2.0,Copyright Microsoft Corporation),详见 [LICENSE](LICENSE)。