Browser-session guardrail for Playwright CLI: use headed, named, isolated sessions; prevent parallel collisions and recover stalls without scripted shortcuts. Read before browser work or when automation freezes. Product QA behavior remains with the calling test/audit skill.
Scanned 9/11/2026
Install to Claude Code
npx -y skills add kensaurus/cursor-kenji --skill protocol-browser-anti-stall --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Protocol Browser Anti Stall?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/kensaurus-protocol-browser-anti-stall)More formats (shields.io, HTML) on the badges page.
---
name: protocol-browser-anti-stall
description: >
Browser-session guardrail for Playwright CLI: use headed, named,
isolated sessions; prevent parallel collisions and recover stalls
without scripted shortcuts. Read before browser work or when automation
freezes. Product QA behavior remains with the calling test/audit skill.
license: MIT
---
# Browser Anti-Stall Protocol (playwright-cli)
**Degree of freedom: LOW.** Almost every step is exact. Only "why it
stalled" and the next hypothesis are `[HIGH freedom]`.
**Apply these rules to EVERY browser action. No exceptions.**
This repo drives browsers with **`playwright-cli`**, not the Playwright MCP. The MCP exposes one
browser per server and a persistent profile can only be locked by one process at a time, so
parallel agents on the same repo fight over tabs and profile locks. The CLI gives every agent its
own isolated browser via `-s=<session>`, costs far fewer tokens (no tool schemas or verbose trees
loaded into context), and runs natively in parallel shells.
**Read `references/mcp-to-cli-map.md`** if you encounter old `browser_*` MCP tool calls — it maps
every tool to its CLI command. **Read `references/playwright-session-coordination.md`** before your
first command — session naming, persistent logins (incl. the Google/CDP block), and cleanup.
## How to reason
1. **Observe** — snapshot, screenshot, console, requests, and the session name
2. **Interpret** — stale ref, SPA hydration, pending request, or a real blocker
3. **Classify** — one new-hypothesis retry / 2s sleep-cycle / BLOCKER report
4. **Recover** — one real user action, then look; never `eval` to click
## Worked example
> **Observe:** checkout `click`; URL still `/cart`; console hydration warning; `POST /api/checkout` pending; session `-s=qa-checkout`.
> **Interpret:** SPA not ready + possibly a stale ref — not "the button is missing".
> **Classify:** attempt 2 — `find`/`waitFor` a landmark, fresh `snapshot`, then click. Not `run-code` to submit.
> **Stop at 4:** still stuck → BLOCKER with console + requests + screenshot.
## Self-critique before reporting
- **Headed + `-s=`** — every call; never a shared session name
- **Inspection-only eval** — `eval` / `run-code` never click, type, or submit
- **Evidence before retry** — max 4 attempts, then BLOCKER
- **Right owner** — product QA behavior stays with the calling test/audit skill
---
## Invocation — always this form [LOW freedom — run exactly]
```bash
PW="npx --yes @playwright/cli@latest" # portable; survives fnm/nvm version switches
$PW -s=<session> <command> [args]
```
- **`-s=<session>` is mandatory on every call.** Name it after your task or branch
(`-s=qa-checkout`, `-s=audit-ux-home`). Two agents must never share a session name.
- **Do not rely on a global `npm i -g` install.** Under `fnm`/`nvm` the global prefix is
per-shell and disappears; `npx` always resolves.
- `--json` / `--raw` are available when you need machine-readable output.
## 0. Manual & headed — never scripted (read first) [LOW freedom — run exactly]
You are driving a **real, visible browser** to feel what a user feels. A green script proves
nothing about UX — *see the screen* and *watch the logs*.
1. **Headed, always.** The CLI defaults to **headless** — you MUST pass `--headed` on `open`.
If you cannot see the window, say so rather than proceeding blind.
2. **One real action at a time.** `click`, `type`, `fill`, `select`, `hover`, `press`, `drag`
exactly as a user would. Never chain a whole flow into one code snippet.
3. **`eval` / `run-code` are inspection-only.** Use them ONLY to *read* state (DOM, computed
styles, storage, perf) or to wait for an element — never to click, type, navigate, or submit.
Driving the UI through code bypasses real events and hides the bug you are hunting.
4. **No test files, no runner.** Do not write `*.spec.ts`, run `npx playwright test`, or use
codegen. You are here to *experience* the flow, not automate past it.
5. **Look after every action.** Fresh `snapshot` + `screenshot` + `console` + `requests`, plus the
dev-server terminal. Real pain surfaces on screen and in logs, not in an assertion.
## 1. Session lifecycle [LOW freedom — run exactly]
```bash
$PW -s=qa-checkout open --headed http://localhost:3000 # start (once)
$PW -s=qa-checkout goto http://localhost:3000/cart # navigate within the session
$PW -s=qa-checkout snapshot # get refs
$PW -s=qa-checkout close # end YOUR session when done
$PW list # see all sessions (status, profile, headed)
$PW close-all # only when you own every session
$PW kill-all # last resort: stale/zombie processes
```
- `open` starts a browser; `goto` navigates an already-open one. Calling `open` twice on the same
session is wasteful — use `goto`.
- **Close only your own session.** Never `close-all` while another agent may be mid-test.
- Add `--browser chrome|firefox|webkit|msedge`, `--device "iphone 15"`, or `--mobile` on `open`
when the task calls for it.
## 2. Navigation guard [LOW freedom — run exactly]
After every `open` / `goto` / `reload`:
1. `snapshot` — confirm the URL changed and the page has content.
2. If blank or unchanged → `sleep 2` → `snapshot` again.
3. **Max 3 cycles (~6s).** Still not loaded → report a blocker (§8) and move on.
Never assume navigation succeeded without a snapshot to confirm it.
## 3. Waiting — there is no `wait` command [LOW freedom — run exactly]
Playwright **auto-waits** for actionability on `click`/`fill`/`select`, so most explicit waits are
unnecessary. When you genuinely must wait:
| Need | Do this |
|---|---|
| Fixed short pause | `sleep 2` in the shell — **never more than 3s per pause** |
| Wait for text/element | `run-code "async (page) => { await page.getByText('Dashboard').first().waitFor({ timeout: 5000 }); return 'ready'; }"` |
| Wait for something to disappear | `...waitFor({ state: 'hidden', timeout: 5000 })` |
| Poll for content | `find "<text>"` → if no match, `sleep 2` → retry (max 3) |
**Always set an explicit `timeout`** (milliseconds) in `waitFor` — the default 30s is far too long.
Use the incremental pattern instead of one long block:
```
sleep 2 → snapshot → check ↓ not ready
sleep 2 → snapshot → check ↓ not ready
sleep 2 → snapshot → check ↓ still not ready
STOP → report blocker with evidence
```
This handles cold starts, SPA hydration, and slow APIs without ever blocking blindly.
## 4. SPA-specific rules [LOW freedom — run exactly]
SPAs (React, Next.js, Vue) fire `load` before hydration completes — never trust load events.
- Wait for a **specific UI landmark** that proves the app rendered (`run-code` + `waitFor`, or `find`).
- If a spinner is showing, wait for it to reach `state: 'hidden'` rather than sleeping.
## 5. Anti-loop: max 4 attempts per goal [LOW freedom — run exactly]
| Attempt | Action |
|---|---|
| 1 | Try the action normally |
| 2 | Alternative approach — re-`snapshot` for a fresh ref, try a CSS selector instead, scroll into view, or `find` the element |
| 3 | Gather evidence: `console` + `requests` |
| 4 | **STOP.** Report what blocked progress, with evidence. |
Never repeat the exact same failing action without new evidence.
**Fresh refs after every state change.** Refs from a stale `snapshot` are invalid after any
navigate/click/fill/hover/key press. Re-`snapshot` before the next interaction. `click` also accepts
a unique CSS selector, which survives state changes better than a ref.
## 6. Evidence before retry [LOW freedom — run exactly]
When something is not working, gather evidence FIRST, then form a hypothesis:
1. `console` — JS errors, warnings (`console error` to filter by level)
2. `requests` — pending/failed calls; `request <n>` / `response-body <n>` for detail
3. `snapshot` — the actual DOM state, not what you assume
4. `screenshot --filename .playwright-mcp/<name>.png` — visual state
Only retry once you have a new hypothesis grounded in that evidence.
## 7. Timeout budget [LOW freedom — run exactly]
| Scope | Max time |
|---|---|
| Single interaction (click, fill, select) | 15 seconds |
| Navigation + verification | 30 seconds |
| Multi-page flow | 5 minutes |
| Full session | 15 minutes |
Exceeded? **Skip it** and log `[TIMEOUT] skipped: <step>`. One stuck step must not kill the session.
## 8. Blocker reporting format [LOW freedom — this shape]
```
BLOCKER:
- Session: [-s= name]
- Page: [current URL]
- Goal: [what I was trying to do]
- Blocked by: [what prevented it]
- Evidence: [console errors / failed requests / screenshot observation]
- Suggestion: [most likely next step or manual action needed]
```
Actionable information beats a silent freeze.
## 9. Artifacts [LOW freedom — run exactly]
- Screenshots, snapshots, and logs go under **`.playwright-mcp/`** (gitignored):
`screenshot --filename .playwright-mcp/home-390.png`. Name by route + viewport/step.
- The CLI also auto-writes snapshot `.yml` files to `.playwright-cli/` in the working directory —
also gitignored, never committed.
- Sweep any stray root-level `*.png` / `*.log` into `.playwright-mcp/` before ending the session.
## 10. Parallel agents [LOW freedom — run exactly]
Session isolation replaces the old tab-sharing etiquette — each agent gets its own browser:
```bash
# agent A # agent B (simultaneously, no conflict)
$PW -s=audit-ux open --headed … $PW -s=qa-checkout open --headed …
```
- Never reuse another agent's session name; never `close`/`kill-all` sessions you did not open.
- `list` shows every session with its status, profile, and headed flag — check it before assuming.
- Within one session, multiple tabs are still available (`tab-list`, `tab-new`, `tab-select`,
`tab-close`); the fresh-refs rule applies after every tab switch.
- Signed-in state is shared through **persistent profiles**, not shared tabs — see
`references/playwright-session-coordination.md`.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!