| name | playwright-cli |
| description | 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". |
Expert reference for the `playwright-cli` browser automation CLI (npm package `@playwright/cli`, v0.1.18). Provides command syntax and workflow guidance for all 86 visible flat commands.
<quick_start>
playwright-cli [-s=<session>] <command> [arguments] [options]
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> | |
<essential_principles>
MANDATORY: Consult the adjacent usage.json at <cli-tools-root>/_repo/skills/<tool>-cli/usage.json before executing ANY playwright-cli command.
It contains every command, argument, and option with descriptions sourced directly from --help. Never guess at syntax. When in doubt, also run playwright-cli --help <command>.
The binary exposes only flat commands. The adjacent `usage.json` contains the
exact current command names, arguments, and options.
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 core interaction pattern for element-based commands:
1. **Snapshot** — `playwright-cli snapshot` captures the page and returns element references (REFs)
2. **Interact** — Use the REF in an interaction command: `playwright-cli click REF`
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.
- **Default session**: Commands operate on the most recent browser session automatically.
- **Named sessions**: Use `-s=` global option to target a specific session.
- **A named session is not a persistent profile**: The daemon defaults to an
isolated context. A config file can contain `browser.userDataDir`, but that
path is not used unless `open` also enables persistence. When the config owns
the profile and executable, use
`playwright-cli -s=svc open URL --config /absolute/path/cli.config.json --persistent`
and require the resulting status to report the configured path. A reported
`user-data-dir: ` is a failed profile launch. `--profile `
also implies persistence; do not give it a path that conflicts with the
config's `browser.userDataDir`.
- **Do not use implicit `default` for automation**: For portal login,
credential, and multi-step browser work, open and reuse a short named session
with `-s=` on every command. A bare command uses `default`, which
maps to `default.sock` under the workspace daemon hash; concurrent or stale
daemon startup can fail with `listen EADDRINUSE .../default.sock`. If this
happens, do not retry the bare command. Run `playwright-cli list --all`, target
the intended named session, or use `kill-all` only after preserving any needed
browser state.
- **Keep session names short on macOS**: The CLI embeds the session name directly in a Unix socket path under the system temp directory. Long names can exceed macOS socket path limits and fail with `listen EINVAL`. Use short lowercase names such as `rcenv`, `impact`, or `bf` instead of descriptive names like `codex-run-code-env-test`.
- **Recover after socket/runtime failures**: Errors such as `EADDRINUSE`,
`listen EINVAL`, daemon startup failure, or `The browser '' is not open,
please run open first` mean the CLI session state is not trustworthy. Stop
issuing element-ref commands like `click`, `fill`, or `press` against the old
refs. Preserve the exact error, run `playwright-cli list` to prove whether the
named session still exists, then either reopen the same short named session
with `playwright-cli -s= open ` and take a fresh
`snapshot`, or escalate to the next approved browser-control method with the
target URL and failure evidence. Never paste credentials into a session after
this failure until the browser has been reopened and the visible destination
has been verified.
- **List sessions**: `playwright-cli list` (add `--all` to include sessions from all workspaces).
- **Multiple sessions**: Open multiple browsers with `playwright-cli open`, target each with `-s=`.
- **Cleanup**: `playwright-cli close-all`, or `playwright-cli kill-all` for stale/zombie processes.
First prove the launcher identity with `command -v`, its resolved path, its
shebang, and the owning package metadata. The canonical launcher must resolve
to the npm package `@playwright/cli` at the version recorded in `usage.json`.
A Python launcher that imports `playwright_cli.main` is a different wrapper.
Its grouped `browser`, `page`, or `interact` commands do not define this
skill's contract. Repair the launcher or package install. Do not translate flat
commands for that wrapper.
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.json
The 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.
After CAPTCHA or anti-bot callbacks, do not treat a token or a single input's
`disabled === false` as proof that the submit button is enabled. Re-snapshot the
page and verify the actual submit control's state with `playwright-cli eval`,
including `disabled`, `aria-disabled`, class names, visibility, and the
accessible snapshot text. If the target control still has `aria-disabled="true"`
or a disabled class, do not force-click it and do not remove the disabled state
with `eval`; wait for the page's callback to complete, re-check required fields,
or use the site's normal keyboard submission only when the focused field is a
non-sensitive lookup/search field and the action is not a final payment,
medical, financial, credential, or account-changing submit. For payment,
medical, financial, credential, or final authorization flows, a Playwright
"not enabled" click failure is a stop-and-report condition unless the site
itself later enables the button through its normal UI.
The `category` values in `usage.json` mirror the help section names. They are
reference groups only. Always invoke the flat command name.
Commands return **markdown** by default. Use the global `--json` option for a
JSON response. Use `--raw` for only the result value. Large results, such as snapshots and network logs,
console logs) are written to files inside `.playwright-cli/` and the command
output references them by path. Read those files directly for structured data.
When using `--filename`, keep the command output visible and verify the
referenced file is non-empty before relying on it; modal dialogs and failed or
blocked states can still produce useful command output even when a redirected
file is empty. `snapshot --filename FILE` can also exit `0` and print a
Snapshot link while `FILE` is zero bytes if the page has not produced an
accessibility tree yet. Wait for a target-specific semantic locator, take a
fresh snapshot, and require `test -s FILE`; the link alone is not proof.
When redirecting playwright-cli stdout to an artifact, especially for
run-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' >&2
[ -s ] && sed -n >&2
Per-command options vary and are documented in usage.json; examples:
list supports --all
cookie-list supports --domain / --path
requests supports --static / --filter / --clear
console supports --clear and a positional min-level (info, warning, error, etc.)
screenshot supports --filename / --full-page
There are no generic --table, --limit, or --properties flags. Use only
the options listed for the exact command in usage.json.
`eval` executes JavaScript in the browser DOM context. It does not receive a
Playwright `page` object: use `() => ...` for page-global DOM code, or
`(element) => ...` with a REF when inspecting one snapshot element. Do not pass
`async (page) => page.locator(...)` to `eval`; `page` will be undefined. When a
probe needs Playwright APIs such as `page.locator`, `page.getByRole`, or
`page.title`, use `run-code` with `async (page) => { ... }` and inspect stdout
for `### Error`.
`run-code` expects a JavaScript function/callable expression invoked with the `page` object. Do not pass top-level statements such as `await page.title();` or `const title = await page.title();`; use `async (page) => { ... }` for multi-statement snippets. Runtime syntax errors can print a markdown `### Error` block while the process still exits `0`, so inspect stdout for `### Error` before treating a `run-code` result as successful.
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.
For payment or credential flows, minimize exploratory commands once sensitive pages are open. Before final submit, use `playwright-cli eval` to verify only sanitized values: merchant/payee, account/reference, amount, email, card last four, expiration, CVV length, billing ZIP/address summary, and required unchecked/checked flags. Never print full card numbers, CVV values, passwords, or tokens.
<reference_index>
usage.json — Flat command map with all arguments, options, defaults, and descriptions (sourced from --help).
references/workflow-patterns.md — Common automation workflows (login, form filling, scraping, state persistence, network mocking, recording).
</reference_index>
<success_criteria>
- Flat command name used (verified against
usage.json or playwright-cli --help <cmd>).
- Snapshot taken before any element-ref interaction command.
-s=<session> used when multiple sessions exist.
- Command stdout inspected for
### Error; when present, final page state is verified before continuing.
- Secret key names passed to
fill only, never to type.
- Every secret
fill followed by the per-field length check, and the form not submitted when the check fails.
</success_criteria>