dsh-playwright-cli
Drive a real browser from DeepSeek Harness: one tool, playwright_cli, wraps
microsoft/playwright-cli, plus a bundled
playwright-cli skill that teaches the model the command vocabulary.
playwright-cli itself recommends CLI + skills over MCP for coding agents: commands are short, and the page snapshot goes to a file instead of a giant tool schema or accessibility tree. This plugin keeps that design and only adds the two missing pieces — a tool the model can call, and a skill that is discovered and loaded on demand.
中文说明见 README.zh.md。
Install
In the app: Plugins → Plugin Manager → install bundle → dsh-playwright-cli.
From a shell, when the Harness CLI is on your PATH:
dsh plugin --profile <your-profile> add dsh-playwright-cli
From an agent: plugin_manager with action: install_bundle, target: dsh-playwright-cli.
PATH collisions are common on Windows (
dshmay belong to another product). Ifdsh pluginis not the Harness CLI, use the in-app Plugin Manager.
playwright-cli must be available on the machine:
npm install -g @playwright/cli@latest
And a browser has to exist — playwright-cli ships none. Either download its Chromium once:
playwright-cli install-browser chrome-for-testing
or reuse a browser that is already installed, which needs no download at all:
playwright_cli open https://example.com --browser=msedge
Quick start
playwright_cli open https://playwright.dev --browser=msedge
playwright_cli snapshot --depth=4
→ [artifacts] C:\...\.playwright-cli\page-....yml (read this file to get refs)
playwright_cli click e15
playwright_cli fill e7 "user@example.com"
playwright_cli screenshot --filename=shot.png
playwright_cli close
Every result carries the page status, the artifacts it produced (absolute paths), and the CLI's exit
code. --raw returns just the value, e.g. --raw eval "document.title".
Tool parameters
| Parameter | Required | Meaning |
|---|---|---|
command |
yes | One playwright-cli command without the playwright-cli prefix. Quote arguments containing spaces. |
session |
no | Named browser session (-s=<name>), for isolating parallel tasks. |
workdir |
no | Working directory; artifacts land in <workdir>/.playwright-cli/. Defaults to the session directory. |
timeoutMs |
no | Per-call wall clock limit (default from config, capped at 900000). |
Configuration
Every field is optional and lives in the profile's cordis.patch.yml under the playwright-cli row:
| Field | Default | Meaning |
|---|---|---|
executable |
auto-discovered | Absolute path to playwright-cli.js (run with nodePath) or to a real executable |
nodePath |
node from PATH |
Node used for the CLI and the browser daemon it spawns |
session |
none | Default session name |
timeoutMs |
120000 |
Per-command limit |
disableUpdateNotifier |
true |
Sets NO_UPDATE_NOTIFIER=1 (see the Node 24 note below) |
env |
{} |
Extra environment, e.g. PLAYWRIGHT_MCP_BROWSER: chrome |
How it works
- No shell. The command line is tokenized and passed as an argv vector to
node playwright-cli.js …, so&in URLs, spaces in paths,--data="k=v"and Windows quoting cannot corrupt a command. - Discovery. It walks up from the session directory looking for a local
node_modules/@playwright/cliornode_modules/playwright, then checks the usual global npm, pnpm and Homebrew locations.executableoverrides all of it. - Real Node, not the Host binary. The desktop Host runs as Electron-as-node
(
ELECTRON_RUN_AS_NODE=1), soprocess.execPathis the app binary. playwright-cli re-spawns its browser daemon with that path and dies withtoo many arguments; the plugin therefore resolves a realnodefrom PATH first. - Exit codes are honest. playwright-cli's update notifier crashes the process after a successful
command on Node 24 (exit code becomes
0xC0000409); the notifier is disabled by default. - Artifacts are files. Snapshots, screenshots, PDFs, videos, traces and console logs are resolved
to absolute paths and listed under
[artifacts], so the model reads them withread/read_imageinstead of paying for them in the result. - Hints, not guesses. The two first-run failures (no browser installed, browser not started) get
an extra
[hint]line with the exact next command.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
playwright-cli is not installed or was not found |
no local/global install | npm i -g @playwright/cli@latest, or set executable |
Browser "chrome-for-testing" is not installed |
no bundled browser | playwright_cli install-browser chrome-for-testing, or --browser=msedge |
Chromium distribution 'chrome' is not found |
Chrome not in its default path | --browser=msedge or --browser=chrome |
too many arguments. Expected 1 argument but got 2 |
CLI launched with the Electron binary | leave nodePath unset so the plugin resolves node |
The browser '<name>' is not open |
session idle-closed or never opened | open again (add --persistent to keep the profile) |
百度安全验证 page on Baidu |
site-level anti-automation captcha | not a plugin bug: report the wall, try another engine |
| snapshot only has header/footer | results rendered after the command returned | re-run snapshot, or use find "<text>" |
Maintaining this plugin
Repository layout
| Path | Owns | Update it when |
|---|---|---|
index.js |
Tool schema, spawn, discovery, artifacts, hints, presenters | CLI flags/output shape change, or DSH's ToolDefinition changes |
skill.js |
Model-facing skill text (command table, loop, triage) | the command surface changes |
cordis.patch.yml |
Loader row + default config + field comments | you add a config key |
locale/*.json, icon.svg |
Plugin Manager title, description, icon | wording/branding |
README.md, README.zh.md |
user + maintainer docs | any behavior change |
Adapting to a new playwright-cli release
npm view @playwright/cli version, thennpm i -g @playwright/cli@latest.playwright-cli --help— compare the command list with the table inskill.js. The skill is the model's contract, so keep it in sync before anything else.- Check
open/attachflags (--browser,--device,--persistent,--profile,--config). New flags normally need docs only, no code. - Check the output markers:
### Page,### Snapshot,[Snapshot](.playwright-cli/…),### Eventswithconsole-….log#L1-L6. If a path shape changes, adjustMARKDOWN_LINK,FILE_TOKENand the#L…stripping incollectArtifacts(). - Check session selection: the plugin passes
-s=<name>and setsPLAYWRIGHT_CLI_SESSION. If a release starts rejecting one of them, drop the env assignment inexecute(). - Check the error strings matched by
hintFor():is not installed,Chromium distribution 'chrome' is not found,is not open, please run open first. - Run the smoke test below, then bump the version and the changelog.
The output schema is enforced
DSH validates the tool's returned value against output.schema; a mismatch fails the whole call with
returned invalid output. Declare every nullable field as
oneOf: [{type:'string'}, {type:'null'}] (this bit us once with signal: null), and build the
returned object and the schema together.
Adapting to a new DSH release
dsh --version; only raiseengines.dshwhen you depend on something new.- Re-check the four services the plugin uses. In a live session:
cordis_inspect_querywithplatform: host,provider: Service,input: {"service":"tools"}(alsosubprocess,skills,workingDirectory). - Confirm
ctx.subprocess.resolveExecutable('node')still resolves; if a Host stops running as Electron-as-node, the fallback toprocess.execPathkeeps working either way. - This plugin intentionally ships no
Configschema (it would add a schemastery dependency). If in-app config editing starts requiring one, addexport const Config. - Reinstall and confirm both halves registered: the tool in the agent's tool list and
playwright-cliin the skill catalog.
Release checklist
Semver: patch = docs/hints, minor = new commands or config keys (backward compatible), major = renamed package/row, removed config, renamed tool.
npm pack --dry-run # confirm index.js, skill.js, patch, locale, icon, docs, LICENSE
git add -A && git commit -m "release: vX.Y.Z"
git tag vX.Y.Z && git push --follow-tags
npm publish # add --provenance when publishing from CI
Then verify from a clean state:
dsh plugin --profile <profile> remove dsh-playwright-cli
dsh plugin --profile <profile> add dsh-playwright-cli
and run the smoke test. Keep CHANGELOG.md current.
Local development loop
The profile links this directory, but the Host caches the loaded module. After editing index.js,
skill.js or cordis.patch.yml, remove and re-install the bundle (Plugin Manager toggle, or
remove_bundle + install_bundle through plugin_manager); only a reload picks up new code.
Smoke test
| Command | Expected |
|---|---|
--version |
version line, exit=0 |
list |
(no browsers) or the open sessions |
open https://example.com --browser=msedge |
page title, .playwright-cli/page-*.yml artifact |
find "Example" |
matching nodes with context |
screenshot --filename=shot.png |
shot.png under absolute [artifacts] |
close |
Browser '<name>' closed |
Search-engine smoke tests are the honest end-to-end check: Bing/Google work headless; Baidu returns a captcha page to automation and should be reported, not retried.
Publishing
- Replace the placeholders in
package.json:YOUR NAME,YOUR_GITHUB_USER(three URLs). git init && git branch -M main, create the GitHub repository, push.npm login(2FA accounts need an OTP), thennpm publish.- Publish must go to the public npm registry; mirrors such as
registry.npmmirror.comare for installing, not fornpm publish. - Optional: publish from GitHub Actions with
--provenance(id-token: write), or configure npm trusted publishing for the repository.
Getting listed in the in-app market
Direct install by package name works as soon as the package is on npm. The Plugin Market
(dshmarket) reads a curated catalog, https://awesome-dsh-plugin.com/plugins.json, fetched fresh on
every request; entries carry name, description, category (at least one), and a source
(npm package or URL/repo). Listing is curated — submit an entry to that catalog (its repository
accepts PRs/issues for new plugins) with:
{
"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" }
}
Users can also add that catalog URL manually under Plugins → Discover → Index sources.
License and credits
MIT for this plugin. The skill text in skill.js is adapted from
microsoft/playwright-cli's SKILL.md
(Apache-2.0, Copyright Microsoft Corporation); see LICENSE.
No comments yet. Be the first to write one.