计划执行与检查节点
executing-plans
按既定实施计划完成工作,保持过程检查和反馈。
protocol-browser-anti-stall【效率与协作】Guardrail for Playwright CLI sessions: headed, named, isolated; prevents parallel collisions and recovers stalls without scripted shortcuts. Read before browser work or when automation freezes.
独立 GitHub 快照 · 自动分类 · 以原作者文档为准
Degree of freedom: LOW. Almost every step is exact. Only "why it
stalled" and the next hypothesis are [HIGH freedom].
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.
eval to clickObserve: checkout
click; URL still/cart; console hydration warning;POST /api/checkoutpending; session-s=qa-checkout. Interpret: SPA not ready + possibly a stale ref — not "the button is missing". Classify: attempt 2 —find/waitFora landmark, freshsnapshot, then click. Notrun-codeto submit. Stop at 4: still stuck → BLOCKER with console + requests + screenshot.
-s= — every call; never a shared session nameeval / run-code never click, type, or submitPW="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.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.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.
--headed on open.
If you cannot see the window, say so rather than proceeding blind.click, type, fill, select, hover, press, drag
exactly as a user would. Never chain a whole flow into one code snippet.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.*.spec.ts, run npx playwright test, or use
codegen. You are here to experience the flow, not automate past it.snapshot + screenshot + console + requests, plus the
dev-server terminal. Real pain surfaces on screen and in logs, not in an assertion.$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-all while another agent may be mid-test.--browser chrome|firefox|webkit|msedge, --device "iphone 15", or --mobile on open
when the task calls for it.After every open / goto / reload:
snapshot — confirm the URL changed and the page has content.sleep 2 → snapshot again.Never assume navigation succeeded without a snapshot to confirm it.
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.
SPAs (React, Next.js, Vue) fire load before hydration completes — never trust load events.
run-code + waitFor, or find).state: 'hidden' rather than sleeping.| 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.
When something is not working, gather evidence FIRST, then form a hypothesis:
console — JS errors, warnings (console error to filter by level)requests — pending/failed calls; request <n> / response-body <n> for detailsnapshot — the actual DOM state, not what you assumescreenshot --filename .playwright-mcp/<name>.png — visual stateOnly retry once you have a new hypothesis grounded in that evidence.
| 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.
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.
.playwright-mcp/ (gitignored):
screenshot --filename .playwright-mcp/home-390.png. Name by route + viewport/step..yml files to .playwright-cli/ in the working directory —
also gitignored, never committed.*.png / *.log into .playwright-mcp/ before ending the session.Each agent gets its own browser through session isolation:
# agent A # agent B (simultaneously, no conflict)
$PW -s=audit-ux open --headed … $PW -s=qa-checkout open --headed …
close/kill-all sessions you did not open.list shows every session with its status, profile, and headed flag — check it before assuming.tab-list, tab-new, tab-select,
tab-close); the fresh-refs rule applies after every tab switch.references/playwright-session-coordination.md.