Use this skill for service operations only. DO NOT use this skill for CLI implementation lifecycle work such as creating, testing, updating, troubleshooting, validating, removing, or documenting the CLI tool itself; delegate those tasks to cli-tool-expert. MANDATORY: Use this skill for ALL playwright-cli browser automation operations. DO NOT run playwright-cli commands without loading this skill first. Expert reference for the playwright-cli tool covering all commands, syntax, session management, element interaction model, and workflow patterns. Triggers: "playwright-cli", "playwright cli", "browser automation", "page snapshot", "browser session", "playwright click", "playwright fill", "playwright navigate", "open browser", "take screenshot with playwright".
Resources
2Install
npx skillscat add adbertram/cli-tools/playwright-cli Install via the SkillsCat registry.
Commands are flat — there are no grouped subcommands. Write playwright-cli snapshot, not playwright-cli page snapshot.
| Task | Command |
|---|---|
| Open browser to URL | playwright-cli open https://example.com |
| Navigate to URL | playwright-cli goto https://example.com |
| Take page snapshot | playwright-cli snapshot |
| Click element | playwright-cli click REF |
| Fill form field | playwright-cli fill REF "text" |
| Take screenshot | playwright-cli screenshot |
| List sessions | playwright-cli list |
| Press key | playwright-cli press Enter |
| Save auth state | playwright-cli state-save auth.json |
| List network requests | playwright-cli requests |
| </quick_start> |
There are no grouped forms like browser open, page snapshot, interact click, tab list, cookie get, network requests, devtools console, or data delete. Those will fail at parse time.
The following commands require a REF from a prior snapshot: click, dblclick, fill, hover, drag (two refs), select, check, uncheck, and optionally screenshot/eval. Always snapshot before interacting, and re-snapshot after any DOM-changing action — stale refs are invalid. After any submit or navigation, inspect the new snapshot and continue from the live page state; do not assume the next page or step.
Interaction commands can report a markdown ### Error block after the browser has already completed part of the action, such as a submit click that times out while the page still navigates. Treat ### Error in stdout as an action error, then verify the final page state with snapshot, URL, or title before deciding whether the workflow can continue from the new page.
Do not call require.resolve() on guessed private package subpaths. Node can
reject them with ERR_PACKAGE_PATH_NOT_EXPORTED. Resolve the real launcher,
then inspect its adjacent package metadata by file path.
launcher=$(command -v playwright-cli)
node -e 'const fs=require("node:fs"); const path=require("node:path"); const launcher=fs.realpathSync(process.argv[1]); const packageJson=path.join(path.dirname(launcher),"package.json"); if (!fs.existsSync(packageJson)) { console.error(`MISSING_PACKAGE_JSON:${packageJson}`); process.exit(1); } const pkg=JSON.parse(fs.readFileSync(packageJson,"utf8")); if (pkg.name !== "@playwright/cli") { console.error(`WRONG_PACKAGE_OWNER:${pkg.name}`); process.exit(1); } console.log(JSON.stringify({launcher,packageJson,name:pkg.name,version:pkg.version},null,2));' "$launcher"
- `fill REF "text"` — Replaces existing content instantly (like clearing + pasting). Requires a REF. Use for most form fields.
- `type "text"` — Types character by character into the currently focused element. **Takes only text, no REF.** Focus the target first (e.g., `click REF`) before `type`. Use when the field has autocomplete, live search, or key-by-key event handlers. Add `--submit` to press Enter after typing.
- For password/API-key/token fields, do not pass the real secret value as the `fill` text and do not embed it in `run-code`; command output includes the generated Playwright code. Instead, open the session with `PLAYWRIGHT_MCP_SECRETS_FILE` pointing to a dotenv file, then pass the secret key name to `fill` only, e.g. `playwright-cli -s=pp fill REF PAYPAL_PASS`. The CLI fills the secret value and redacts it from output as `PAYPAL_PASS`.
- **Only `fill` substitutes a secret key. `type` does not. Never pass a secret key name to `type`.** The CLI `fill` command maps to the MCP tool `browser_type`, which calls `lookupSecret`. The CLI `type` command maps to the MCP tool `browser_press_sequentially`, whose handler calls `page.keyboard.type(text)` and never calls `lookupSecret`. Only `browser_type` and `browser_fill_form` call `lookupSecret`. `playwright-cli -s=x type MY_KEY` types the literal characters `MY_KEY` into the page. The result is a failed login that looks like a wrong-password event. There is no CLI command that types a secret key by key. Verified in published `@playwright/cli` 0.1.18.
- **Every secret `fill` requires the post-fill length check.** See the `Secret Fill Verification` principle below.
- If `click REF` on a visible submit control is blocked by an overlay or intercepted pointer events, use keyboard submission from the focused field, such as `playwright-cli press Enter` or `playwright-cli fill REF "text" --submit`, before reaching for raw DOM submission.
`fill` can write the secret into a **different** input while it exits `0` and
prints the correct locator. Playwright's `locator.fill()` runs in two phases.
An injected page script focuses the target element and returns `needsinput`.
The driver then calls `page.keyboard.insertText(value)` out of process. The text
goes to the input that holds focus at that moment. A page that moves focus can
receive the secret in another input. The printed locator comes from ref
resolution, not from the final write. Exit status `0` is not proof. `click REF`
before `fill` does not prevent this.After every secret fill, read back the per-field value LENGTHS and confirm two facts:
- The intended field's value length equals the secret's length.
- No other field's value length changed.
Never read, print, or log a field value. Read value.length only.
Take a baseline before the fill, then check after the fill:
guard=/Users/adam/Dropbox/GitRepos/Agents/skills/global/browser-automation/scripts/verify-secret-fill.sh
"$guard" baseline pp /tmp/pw-base.json
playwright-cli -s=pp fill REF PAYPAL_PASS
"$guard" check pp /tmp/pp.env PAYPAL_PASS '#password' /tmp/pw-base.jsonThe guard prints PW_GUARD_OK and exits 0 only when both facts hold. On PW_GUARD_FAIL, do not submit the form. Clear the polluted field, resolve why the focus moved, and fill again. On a two-step login, submit the email step first so the password field genuinely accepts the focus. Do not set the value with eval or run-code; that puts the plaintext secret on the command line and skips the page's input events.
The inline equivalent of the length read:
playwright-cli -s=pp eval "() => Array.from(document.querySelectorAll('input,textarea')).map(el => ({ sel: el.id, type: el.type, len: el.value.length }))"A fill that puts the literal secret key name into the page is a different failure: the secrets file path was missing or unreadable. Stop, and rerun with the real readable PLAYWRIGHT_MCP_SECRETS_FILE path. Do not keep submitting the form.
When redirecting playwright-cli stdout to an artifact, especially forrun-code, do not run a bare command such as `playwright-cli ... run-code ...
"$out"
. Create the artifact parent, capture stderr to a sidecar file, preserve the producer status, and print explicit evidence on failure. On success, verify the stdout artifact is non-empty and inspect it for### Errorbefore treating the command as successful. If the command exits non-zero, print stdout as failure evidence becauserun-codewrites markdown### Error` output to stdout while stderr can be
empty.
If a validation wrapper prints per-case summaries to the parent stdout, capture
that wrapper stdout in its own log and assert summary markers against that log,
not against the per-case redirected producer stdout artifacts.
out=/path/to/workspace/dom.json
err=/path/to/workspace/dom.stderr
mkdir -p "$(dirname "$out")"
if playwright-cli -s=ata run-code 'async (page) => ({ title: await page.title() })' >"$out" 2>"$err"; then
if [ ! -s "$out" ]; then
printf 'PLAYWRIGHT_STDOUT_EMPTY:%s\n' "$out" >&2
exit 1
fi
rg -n -F -- '### Error' "$out"
rg_rc=$?
if [ "$rg_rc" -eq 0 ]; then
printf 'PLAYWRIGHT_MARKDOWN_ERROR:%s\n' "$out" >&2
exit 1
fi
[ "$rg_rc" -eq 1 ] || exit "$rg_rc"
else
rc=$?
printf 'PLAYWRIGHT_FAILED:%s rc=%s stdout=%s stderr=%s\n' "$out" "$rc" "$out" "$err" >&2
[ -s "$out" ] && sed -n '1,80p' "$out" >&2
[ -s "$err" ] && sed -n '1,80p' "$err" >&2
exit "$rc"
fiPer-command options vary and are documented in usage.json; examples:
listsupports--allcookie-listsupports--domain/--pathrequestssupports--static/--filter/--clearconsolesupports--clearand a positionalmin-level(info, warning, error, etc.)screenshotsupports--filename/--full-page
There are no generic --table, --limit, or --properties flags. Use only
the options listed for the exact command in usage.json.
Do not assume Node globals are available in run-code. In this environment, process is undefined, require(...) fails with ReferenceError: require is not defined, and dynamic import(...) can fail with ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING. For secrets, do not use process.env in run-code; open the session with PLAYWRIGHT_MCP_SECRETS_FILE and use a snapshot-ref fill command with the secret key name. Only fill substitutes a secret key. Never pass a secret key name to type, because type sends the literal characters. Verify every secret fill with the length check in the Secret Fill Verification principle.
After dropdown changes, verify the live DOM value with playwright-cli eval. If playwright-cli select REF VALUE reports success but the selected value is still empty, set the element value with eval and dispatch a bubbling change event, then verify again.