Align the shared workflow skill with Octopus pane agents: prefer the
user-visible pane, pre-arm dialogs, use f{seq}/e{n} frame refs, and
dual-leg highlight/locator tools.
2026-11-02 18:45:37 +08:00
4 changed files with 167 additions and 83 deletions
stop path depends on setsid/ss, silently ineffective under Git Bash; see
stop path depends on setsid/ss, silently ineffective under Git Bash; see
local-workstation-quirks.md §5);
local-workstation-quirks.md §5);
frontend is at `http://localhost:4444` and backend at `http://localhost:4096`
frontend is at `http://localhost:4444` and backend at `http://localhost:4096`
(see AGENTS.md Dev services section).
(see AGENTS.md Dev services section).
3.**Chromium is installed.**First run of `@playwright/mcp` downloads it
3.**Chromium is installed.**The headless backend drives an isolated
automatically; if the tool reports a missing browser, run
Chromium; if a tool reports a missing browser, run
`bunx playwright install chromium` once in any shell.
`bunx playwright install chromium` once in any shell.
## Tool selection recipe
## Tool selection recipe
The Playwright MCP exposes many tools. Pick by *what you need to observe*,
The unified surface exposes focused read/navigation tools. Pick by *what you
not by familiarity:
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 |
| 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 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. |
| 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. |
| 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. |
| Close the session | `browser_close` | Tear down when done; see the session cleanup section. |
| 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. |
## Pane-first recipe (prefer over headless)
Default to the **user-visible browser pane** unless the task explicitly needs
an isolated headless Chromium (`surface: "headless"`). Pane agents now match
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; 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. |
Headless remains the right tool for parallel CI-style isolation and when the
user must not see the session. For interactive debug of `packages/app`, stay
on the pane.
## Standard recipe — reproduce a UI bug
## Standard recipe — reproduce a UI bug
Follow these steps in order. Do not skip the snapshot step: the `ref` it
Follow these steps in order. Do not skip the snapshot step: the `ref` it
returns is the stable handle every subsequent tool needs.
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.
2.`browser_console_messages` — record any errors *before* touching anything.
Many "interaction bugs" are actually load-time errors.
Many "interaction bugs" are actually load-time errors.
3.`browser_snapshot` — locate the target element; capture its `ref` and
3.`browser_snapshot` — locate the target element; capture its accessible
accessible name.
name.
4.Drive the interaction with `browser_click` / `browser_type` /
4.If the reproduction needs interaction beyond reading, use
`browser_press_key` until the bug reproduces.
`browser_evaluate` sparingly to probe state — the unified surface is
5. Re-read `browser_console_messages` and `browser_network_requests` —
read-focused; drive deep flows via the e2e suites when needed.
capture the *delta* introduced by the interaction.
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
6.`browser_take_screenshot` (or a second `browser_snapshot`) to freeze the
broken state.
broken state.
7. Report: URL, the minimal interaction sequence, console delta, network
7. Report: URL, the observation sequence, console delta, screenshot, and the
delta, screenshot, and the suspected component path in
the single mandatory sanitize boundary; the `.playwright-mcp/` layer
the single mandatory sanitize boundary; the `.playwright-mcp/` layer
keeps its current semantics, additive not replaced) and cite the pack
keeps its current semantics, additive not replaced) and cite the pack
directory. Evidence ladder: raw capture → `.playwright-mcp/` working
directory. Evidence ladder: raw capture → `.playwright-mcp/` working
@@ -163,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. |
| "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. |
| "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?". |
| "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
## Boundaries
@@ -184,18 +221,12 @@ When a `verify` work item has an E2E or UI DoD entry:
## Session cleanup (mandatory)
## Session cleanup (mandatory)
A Playwright MCP browser is a real Chrome with a `/tmp` user-data-dir — its
The unified surface's isolated backend is a real Chrome with a `/tmp`
mode follows `mcp.playwright.headless` (`headless-field-v1`, [org-internal #4393]): the repo
user-data-dir. It stays alive as long as its parent octopus backend runs.
default `auto` spawns headed on desktop (win32/darwin; linux only with
When a browser-debug session ends without an explicit `browser_close`, OR the
DISPLAY/WAYLAND_DISPLAY set) and headless otherwise; an explicit `true`/`false`
octopus session times out mid-task, the Chrome is orphaned and accumulates
or `OCTOPUS_MCP_HEADLESS=1|0` pins it. The mode is fixed per new session —
across sessions — burning CPU and leaving dead profile dirs (issue [org-internal #1315]); in
switching the config does not hot-switch a running browser. It
headed mode the orphan is a visible stray window.
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.
**Iron rule: every browser-debug session MUST close its browser before ending.**
**Iron rule: every browser-debug session MUST close its browser before ending.**
@@ -217,19 +248,16 @@ orphan is a visible stray window.
**Why this matters:** a single forgotten `browser_close` leaves a Chrome
**Why this matters:** a single forgotten `browser_close` leaves a Chrome
subtree (8+ processes) running indefinitely. Over days this is hundreds of
subtree (8+ processes) running indefinitely. Over days this is hundreds of
orphaned processes and wasted CPU. The MCP server does not auto-close its
orphaned processes and wasted CPU. Only an explicit `browser_close` (or
browser on disconnect — only an explicit `browser_close` (or process reaping)
process reaping) releases it.
releases it.
## Screenshot output paths
## Screenshot output paths
The Playwright MCP server is launched WITHOUT `--output-dir` (see
Screenshots from `browser_take_screenshot` land in `.playwright-mcp/` in the
`octopus.jsonc` → `mcp.playwright.command`): its default output location is
process cwd (the worktree root), gitignored. An explicit `filename` can
`.playwright-mcp/` in the process cwd (the worktree root), gitignored and
resolve against the cwd and drop the PNG into the repo root, where the
auto-evicted at 100 MB via `--output-max-size`. Auto-named output (no
`/*.png` gitignore backstop hides it from `git status` and nothing ever
`filename`) lands there — but an explicit `filename` can resolve against the
cleans it up.
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`
**Iron rule: 截图文件名必须写进 `.playwright-mcp/`。** Every `filename`
passed to `browser_take_screenshot` (or any tool that writes a file) MUST
passed to `browser_take_screenshot` (or any tool that writes a file) MUST
@@ -262,10 +290,10 @@ Two valid destinations for screenshots, by purpose:
octopus UI. The endpoint exists but has returned 500 on this instance;
octopus UI. The endpoint exists but has returned 500 on this instance;
verify before relying on it.
verify before relying on it.
3. **Publish to the wiki evidence namespace `{slug}/verify/evidence/{name}`
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
works AND the evidence must outlive the worktree (this is the same durable
channel `verify` mandates for DoD evidence entries — see
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
≤5 images per PR, each ≤200KB (compress/downscale, prefer element
crops over full-page).
crops over full-page).
@@ -283,13 +311,19 @@ prefix is the real fix; rely on that, not on the backstop.
## References
## 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.