---
name: Playwright
triggers:
  - playwright
  - browser automation
  - browse the page
  - web page
  - aria snapshot
  - browser snapshot
replaces_orchestrator: false
---

# Playwright Skill

Drive a real browser (navigate, click, type, read pages) via a long-running **Playwright MCP daemon** plus a stateless CLI called `pw`. The daemon holds the browser + session; `pw` connects per-call. When the operator says "use the playwright skill", follow the checklist below: make sure the daemon and CLI are up, then work the page **reading aria-snapshots, not screenshots**.

## Golden rules (read first)
- **NEVER save screenshots to disk.** Prefer `browser_snapshot` (accessibility/aria tree) for everything — reading, locating elements, verifying state. It is text, cheap, and actionable.
- **Screenshots only as a last resort** (e.g. a purely visual bug that the aria tree cannot express). If you truly must, do it once; do not write image files into the repo.
- **Credential/corporate contexts — MANDATORY: never use a raw `browser_snapshot` or `browser_evaluate` that could surface a value.** Any time you work with the operator's personal accounts, a corporate login, or any authenticated page, ALWAYS pass the global `--safe` flag (or `export PW_SAFE_MODE=1`) so every known vault value is scrubbed to `[REDACTED]` before it can reach your context — and use `pw redacted-snapshot` to read the page. A raw snapshot has leaked an email before; assume it will again. Note: redaction only covers values that live in SecureVault, so keep sensitive identifiers (email, username, password) in the vault.
- **Spend as few tokens as possible.** Snapshots can be huge. Navigate first, then take ONE snapshot; grep/filter for the part you need instead of re-dumping the whole tree. Don't snapshot in a loop.
- The daemon runs in its **own terminal window**, independent of the agent's shell — never launch it in the foreground of your own bash (it must outlive the command).

## Endpoint (important)
Always connect to `http://localhost:8931/mcp`.
- Not `127.0.0.1` and not `[::1]`: the daemon binds IPv6 `::1` and rejects any Host header other than `localhost:8931`.
- Set it once per shell: `export PW_MCP_URL="http://localhost:8931/mcp"` (the CLI's built-in default is `127.0.0.1`, which will NOT reach the daemon).

## Step 1 — Is the CLI (`pw`) installed?
Check: `pw help`. If "command not found", install it. **Prefer npm; fall back to local source only if npm fails.**

```bash
# 1) Primary — install the published package from npm:
npm i -g playwright-mcp-cli
pw help                               # confirm it's on PATH

# 2) Fallback (npm registry unreachable / install fails) — build from local source.
#    The repo is already checked out at C:\src\playwright-mcp-cli on this machine:
cd /c/src/playwright-mcp-cli && npm ci && npm run build && npm link
#    If the checkout is missing, clone it first:
#      git clone https://github.com/andriyshevchenko/playwright-mcp-cli
#    Or skip linking and run directly: node /c/src/playwright-mcp-cli/dist/cli.js <args>
```

## Step 2 — Is the daemon up?
Check: `export PW_MCP_URL="http://localhost:8931/mcp" && pw list` — if it prints tools, the daemon is up; skip to Step 3.

If it fails to connect, launch it in a **separate, persistent window**. On Windows launch via `cmd`, NOT PowerShell (the Volta-shimmed `npx` under PowerShell is broken — `npm-prefix.js not found`):
```bash
# Windows (bash tool) — detached cmd window that stays open:
pwsh -c "\$udd=\"\$env:LOCALAPPDATA\Microsoft\Edge\User Data\"; Start-Process cmd -ArgumentList '/k',(\"npx -y @playwright/mcp@latest --port 8931 --browser=msedge --user-data-dir \`\"\$udd\`\" --shared-browser-context\")"
```
- `--user-data-dir <Edge User Data>` reuses the operator's logged-in Edge profile (so sites like LinkedIn are already authenticated). **The real Edge must be fully closed first** — Playwright locks the profile dir. Confirm with the operator before killing his browser; he will lose open tabs.
- Omit `--user-data-dir` for a clean, unauthenticated profile.
- `--shared-browser-context` keeps ONE context so page state survives across separate stateless `pw` calls.
- Give it ~15s to cold-start; first run downloads the package. Verify with `pw list`.

## Step 3 — Use the CLI
```bash
export PW_MCP_URL="http://localhost:8931/mcp"
pw list                                   # discover available tools
pw <tool> --key value                     # values auto-parse as number/bool/string
pw <tool> --json '{"key":"value"}'        # merge raw JSON args
pw <tool> --key                           # boolean flag => { key: true }
```
- `--url` and `--out` are **reserved global options** (endpoint / output path). A tool argument literally named `url` (e.g. `browser_navigate`) MUST go through `--json`:
  ```bash
  pw browser_navigate --json '{"url":"https://example.com"}'
  ```
- Read the page: `pw browser_snapshot` (aria tree — your default). Filter its output for what you need.
- On any authenticated / corporate / personal-account page, add `--safe` (or `export PW_SAFE_MODE=1`) to EVERY call so vault values are auto-scrubbed from the output.

## Typical flow
```bash
export PW_MCP_URL="http://localhost:8931/mcp"
pw browser_navigate --json '{"url":"https://www.linkedin.com/feed/"}'
pw browser_snapshot | grep -i "<what you're looking for>"
pw browser_click --json '{"ref":"eNN"}'   # refs come from the snapshot
```
The shared context means each `pw` call is a fresh connection but the same browser page — navigate in one call, act/read in the next.

## Step 4 — Secure authentication (log in without seeing secrets)
`pw` can fill login forms with credentials pulled straight from the **SecureVault OS keychain** — the raw values are injected into the page and **never printed to your stdout**. Use this instead of asking the operator for passwords or typing them yourself.

Discover what's available (no daemon needed for these two):
```bash
pw vault-secrets                 # secret titles, e.g. ACME_EMAIL [email]
pw vault-profiles                # named profiles + their envVar -> secret mappings
```
Fill / type a single secret into the current page:
```bash
pw secure-fill --secret "ACME_EMAIL" --selector "input[type=email]"
pw secure-type --secret "ACME_PASSWORD" --selector "input[type=password]" --enter
```
Navigate to a URL stored in the vault (the address is never printed — use it when the target URL itself is sensitive, e.g. an internal portal with a token/tenant in the path):
```bash
pw secure-navigate --secret "ACME_PORTAL_URL"
pw secure-navigate --profile "Acme" --envVar "URL"   # --envVar defaults to URL
```
Run a whole multi-step login from a profile:
```bash
pw secure-auth --profile "Acme" --json '{"steps":[
  {"selector":"input[type=email]","envVar":"EMAIL","action":"fill","pressEnterAfter":true,"waitMs":3000},
  {"selector":"input[type=password]","envVar":"PASSWORD","action":"type","pressEnterAfter":true}
]}'
```
- Injection uses `document.execCommand('insertText')`, so it works with React/Angular/Vue fields that ignore direct `.value` sets.
- `secure-fill` = clears then inserts (best for email/text fields); `secure-type` = same insert but framed as keystrokes and supports `--enter`.
- `secure-navigate` = resolves a URL from the vault and opens it via `browser_navigate`; pass `--secret <title>` OR `--profile <name> [--envVar URL]`, not both.
- To read a post-login page safely use `pw redacted-snapshot` — it snapshots the aria tree with every vault value replaced by `[REDACTED]`.
- If a site is behind SSO with **device trust** (e.g. Duo on a `--user-data-dir` Edge profile), the OTP/2FA step is often skipped automatically. Use a clean profile (omit `--user-data-dir`) to force the OTP challenge.

### Keeping the session alive during login
The daemon **idle-resets the page to `about:blank` if no client connects for ~5–10s**. During a multi-step login, don't leave long gaps between `pw` calls — chain them tightly, or poll every ~3s (e.g. `pw browser_evaluate --function "() => location.pathname"`) to keep the page warm. `secure-auth` handles its own steps in one connection, so prefer it for the full flow.
