stop path depends on setsid/ss, silently ineffective under Git Bash; see
local-workstation-quirks.md §5);
frontend is at `http://localhost:4444` and backend at `http://localhost:4096`
(see AGENTS.md Dev services section).
3.**Chromium is installed.**The headless backend drives an isolated
Chromium; if a tool reports a missing browser, run
`bunx playwright install chromium` once in any shell.
## Tool selection recipe
The Playwright MCP exposes many tools. Pick by *what you need to observe*,
not by familiarity:
The unified surface exposes focused read/navigation tools. Pick by *what you
need to observe*, not by familiarity. Cost discipline applies: the snapshot
answers structure/state questions, the screenshot only visual ones — take
both in one cycle only when a visual verdict is itself the deliverable:
| Need | Tool | Why |
| --- | --- | --- |
| Open a URL | `browser_navigate` | Always the first call. |
| Open a URL | `browser_open` | Always the first call (opens the user-visible browser pane / isolated browser). Pass `surface: "headless"` when the intent is headless — the user's pane is never touched (no confirmation bar, no pane binding) and later browser_* tools operate on the isolated headless copy; if already bound to the pane, `browser_pane_release` first. |
| See the page structure (roles, names) | `browser_snapshot` | Returns the accessibility tree — stable across CSS refactors. Prefer this over `browser_take_screenshot` when asserting "does element X exist / is it labelled Y?". |
| See the page *visually* | `browser_take_screenshot` | For layout, spacing, color, overflow. Attach the PNG to your report. |
| See the page *visually* | `browser_take_screenshot` | For layout, spacing, color, overflow. Attach the PNG to your report.**Vision-model guard (#screenshot-guard):** the PNG is pixels, not text — a session model without vision CANNOT read it. If YOU cannot see images, you must delegate reading via `Task(role: "Tool")` per the `image-interpret` skill (returns a structured text spec); never attempt to interpret the PNG yourself. Pixel questions → image-interpret; DOM/state questions → `browser_evaluate`. |
| Read console errors / warnings | `browser_console_messages` | Filter by level (`error`, `warn`). The first stop for any "page is blank" bug. |
| Click, type, hover, select | `browser_click`, `browser_type`, `browser_hover`, `browser_select_option` | Drive the flow. Always `browser_snapshot` first to get the `ref` for the target element. |
| Assert computed style / DOM state | `browser_evaluate` | Last resort. Snapshots cover 90%; reach for `evaluate` only when you need `getComputedStyle`, scroll positions, or arbitrary JS state. |
| Record a video / trace | `browser_start_tracing`, `browser_stop_tracing` | Heavy; use only when a bug is timing-sensitive and console+snapshot can't catch it. |
| Close the session | `browser_close` | Tear down when done; see the session cleanup section. |
## Pane-first recipe (prefer over headless)
@@ -109,8 +125,8 @@ or exceed Playwright for the hard cases:
| Need | Pane tool / pattern | Notes |
| --- | --- | --- |
| Open / bind pane | `browser_open` (default surface) | Never touch the user's pane from a headless-only intent — pass `surface: "headless"` or `browser_pane_release` first. |
| Structure + refs | `browser_snapshot` | Open shadow is pierced; cross-origin iframes expose `f{seq}/e{n}` synthetic refs — prefer those over attr ids when acting into child frames. |
| Act (click/type/…) | `browser_act` / `browser_act_batch` | Locators (`role=` / `text=` / …) walk open shadow. Cross-origin click/type route through CDP trusted events. |
| Structure + refs | `browser_snapshot` | Open shadow is pierced; closed shadow is CDP-merged (`closedShadow:true`, no `e<n>`). Cross-origin iframes expose `f{seq}/e{n}` synthetic refs — prefer those over attr ids when acting into child frames. Closed hosts inside stitched OOPIF subtrees carry `closedShadow:true` (CDP merge after stitch). |
| Act (click/type/…) | `browser_click` / `browser_type` / `browser_act_batch` | Locators (`role=` / `text=` / …) walk open shadow; after a JS miss, pane CDP pierces **closed** shadow for locate/wait/scroll (nested closed-in-open + same-origin iframe contentDocument + cross-origin OOPIF via Target.attachToTarget, including nested OOPIF-in-OOPIF host-offset chains; `DOM.focus` for type). `css=` stays light-DOM. Cross-origin click/type route through CDP trusted events. |
| Native dialogs | `browser_handle_dialog`**before** the act | Prefer `mode: "arm"` (or `act_batch.dialog_prearm`) so the pane parks the answer before the click that opens confirm/prompt — avoids the 1.5s auto-dismiss / widget-cancel race. Bind with `messageIncludes` when the page can raise multiple dialogs. Call `mode: "disarm"` if the trigger was cancelled. |
| Highlight / locator | `browser_highlight`, `browser_generate_locator` | Dual-leg: both pane and headless. Use highlight to confirm the ref before a write. |
| Failure recovery | re-snapshot + path candidates | Act/locator failures may return `pathCandidates` (frame/shadow path hints). Heal scores framePath so sibling iframes do not auto-heal across frames. |
@@ -124,28 +140,30 @@ on the pane.
Follow these steps in order. Do not skip the snapshot step: the `ref` it
returns is the stable handle every subsequent tool needs.
1.`browser_navigate` to the page where the bug lives.
1.`browser_open` the page where the bug lives.
2.`browser_console_messages` — record any errors *before* touching anything.
Many "interaction bugs" are actually load-time errors.
3.`browser_snapshot` — locate the target element; capture its `ref` and
accessible name.
4.Drive the interaction with `browser_click` / `browser_type` /
`browser_press_key` until the bug reproduces.
5. Re-read `browser_console_messages` and `browser_network_requests` —
capture the *delta* introduced by the interaction.
3.`browser_snapshot` — locate the target element; capture its accessible
name.
4.If the reproduction needs interaction beyond reading, use
`browser_evaluate` sparingly to probe state — the unified surface is
read-focused; drive deep flows via the e2e suites when needed.
5. Re-read `browser_console_messages` — capture the *delta* introduced by
the interaction / page state.
6.`browser_take_screenshot` (or a second `browser_snapshot`) to freeze the
broken state.
7. Report: URL, the minimal interaction sequence, console delta, network
delta, screenshot, and the suspected component path in
`packages/app/src/...`.
7. Report: URL, the observation sequence, console delta, screenshot, and the
the single mandatory sanitize boundary; the `.playwright-mcp/` layer
keeps its current semantics, additive not replaced) and cite the pack
directory. Evidence ladder: raw capture → `.playwright-mcp/` working
@@ -182,9 +200,9 @@ When a `verify` work item has an E2E or UI DoD entry:
| --- | --- |
| "The unit test already covers it." | Unit tests assert component logic, not what Chromium actually renders. Visual and focus bugs live in the gap. |
| "I can tell from the JSX that it's aligned." | Computed layout depends on parent containers, fonts, and viewport. Screenshot the real page. |
| "Browser automation is overkill for a small change." | A `browser_navigate` + `browser_take_screenshot`pair is ~2 seconds. The cost is in *not* looking. |
| "Browser automation is overkill for a small change." | A `browser_open` + `browser_snapshot`read is ~2 seconds — cheap. The cost is in *not* looking; add the screenshot only where the question is visual. |
| "I'll just run the whole e2e suite." | The suite answers "did anything break?" — it does not answer "does this specific change look right?". |
| "Snapshot is enough, skip the screenshot." | Accessibility trees hide overflow, clipping, and z-index bugs. Take both for visual changes. |
| "Snapshot is enough, skip the screenshot." | Depends on the claim. For a visual change it is backwards — accessibility trees hide overflow, clipping, and z-index bugs, so take the screenshot too. For a non-visual claim (presence, label, state) the snapshot alone IS the observation; don't pad it with pixels. |
## Boundaries
@@ -203,18 +221,12 @@ When a `verify` work item has an E2E or UI DoD entry:
## Session cleanup (mandatory)
A Playwright MCP browser is a real Chrome with a `/tmp` user-data-dir — its
mode follows `mcp.playwright.headless` (`headless-field-v1`, [org-internal #4393]): the repo
default `auto` spawns headed on desktop (win32/darwin; linux only with
DISPLAY/WAYLAND_DISPLAY set) and headless otherwise; an explicit `true`/`false`
or `OCTOPUS_MCP_HEADLESS=1|0` pins it. The mode is fixed per new session —
switching the config does not hot-switch a running browser. It
stays alive as long as its MCP server runs, and the MCP server stays alive as
long as its parent octopus backend runs. When a browser-debug session ends
without an explicit `browser_close`, OR the octopus session times out mid-task,
the Chrome + its MCP server are orphaned and accumulate across sessions —
burning CPU and leaving dead profile dirs (issue [org-internal #1315]); in headed mode the
orphan is a visible stray window.
The unified surface's isolated backend is a real Chrome with a `/tmp`
user-data-dir. It stays alive as long as its parent octopus backend runs.
When a browser-debug session ends without an explicit `browser_close`, OR the
octopus session times out mid-task, the Chrome is orphaned and accumulates
across sessions — burning CPU and leaving dead profile dirs (issue [org-internal #1315]); in
headed mode the orphan is a visible stray window.
**Iron rule: every browser-debug session MUST close its browser before ending.**
@@ -236,19 +248,16 @@ orphan is a visible stray window.
**Why this matters:** a single forgotten `browser_close` leaves a Chrome
subtree (8+ processes) running indefinitely. Over days this is hundreds of
orphaned processes and wasted CPU. The MCP server does not auto-close its
browser on disconnect — only an explicit `browser_close` (or process reaping)
releases it.
orphaned processes and wasted CPU. Only an explicit `browser_close` (or
process reaping) releases it.
## Screenshot output paths
The Playwright MCP server is launched WITHOUT `--output-dir` (see
`octopus.jsonc` → `mcp.playwright.command`): its default output location is
`.playwright-mcp/` in the process cwd (the worktree root), gitignored and
auto-evicted at 100 MB via `--output-max-size`. Auto-named output (no
`filename`) lands there — but an explicit `filename` can resolve against the
cwd and drop the PNG into the repo root, where the `/*.png` gitignore
backstop hides it from `git status` and nothing ever cleans it up.
Screenshots from `browser_take_screenshot` land in `.playwright-mcp/` in the
process cwd (the worktree root), gitignored. An explicit `filename` can
resolve against the cwd and drop the PNG into the repo root, where the
`/*.png` gitignore backstop hides it from `git status` and nothing ever
cleans it up.
**Iron rule: 截图文件名必须写进 `.playwright-mcp/`。** Every `filename`
passed to `browser_take_screenshot` (or any tool that writes a file) MUST
@@ -281,10 +290,10 @@ Two valid destinations for screenshots, by purpose:
octopus UI. The endpoint exists but has returned 500 on this instance;
verify before relying on it.
3. **Publish to the wiki evidence namespace `{slug}/verify/evidence/{name}`
via `wiki 读写 API(见 TERMINOLOGY)`** — last resort only, when no other channel
via git direct-push to `octopus.wiki.git` (`_shared/gitea-write-patterns.md` top-of-file, [org-internal #6180])** — last resort only, when no other channel
works AND the evidence must outlive the worktree (this is the same durable
channel `verify` mandates for DoD evidence entries — see
`core/skills/verify/SKILL.md` References). Keep it small:
`.octopus/skills/verify/SKILL.md` References). Keep it small:
≤5 images per PR, each ≤200KB (compress/downscale, prefer element
crops over full-page).
@@ -302,13 +311,19 @@ prefix is the real fix; rely on that, not on the backstop.
## References
-`core/skills/frontend/SKILL.md` — where UI changes are planned; invoke
-`.octopus/skills/image-interpret/SKILL.md` — REQUIRED when the session model
has no vision: `browser_take_screenshot` writes a PNG the driving model
cannot read. Delegate reading via `Task(role: "Tool")` (structured text spec
back); never self-interpret pixel content. Pixel questions (layout, color,
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.