본문으로 건너뛰기
cccskills
무료GitHub에 공개

uniffy-playwright

Use the project MCP infrastructure to inspect the running app from a coding agent. Covers the Playwright MCP servers for headless-browser automation against the dev UI - navigate, snapshot, evaluate JS, capture console + network, and debug runtime state without leaving the conversation. TRIGGER when: the user asks you to "open the app", "click around", "check the browser", "use playwright", "drive the UI", "screenshot the page", "inspect runtime state in the browser", "check the console", "verify a fix in the running app", or log in as a named user; the user offers "do you want to connect to mcp"; a bug needs in-browser reproduction and the dev stack is running; you need to read live YDoc / Redux / window state to confirm a hypothesis. SKIP when: the user is asking about MCP / Playwright in general (not the project's setup); the task is pure backend, code-only, or covered by reading tests/logs; the dev stack is not running and the user hasn't asked you to start it.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

uniffy-io/uniffy스타 68(GitHub 68 + cccskills 0)
연동 도구Playwright

실행 점수 미검증

설치 방법 보기

포함된 파일(1)

  • SKILL.md11.6 KB

SKILL.md(원문)

설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.

Uniffy MCP Infrastructure

The project ships Playwright MCP servers inside docker so supported coding agents can drive real Chrome sessions against the dev UI. This skill is the operating manual.

What runs

Two identical Playwright MCP containers - the second exists purely to give multi-user tests a separate browser (isolated cookie jar + localStorage):

ContainerMCP serverHost portPurpose
uniffy-mcp-playwrightplaywright8931Primary browser. Default for single-user work.
uniffy-mcp-playwright-bplaywright-b8932Second, fully isolated browser. Use as the second user in cross-session tests.

Both run the same baked image uniffy-mcp-playwright (.docker/dev/mcp-playwright.Dockerfile), with separate ports and persistent profile volumes. Claude Code uses the SSE endpoints from .mcp.json:

{ "mcpServers": {
    "playwright":   { "type": "sse", "url": "http://localhost:8931/sse" },
    "playwright-b": { "type": "sse", "url": "http://localhost:8932/sse" }
} }

Codex uses the Streamable HTTP endpoints from .codex/config.toml:

[mcp_servers.playwright]
url = "http://localhost:8931/mcp"

[mcp_servers.playwright-b]
url = "http://localhost:8932/mcp"

Both start under the dev profile (docker compose --profile dev up / ./manage.py stack up). Compose config: .docker/compose/dev-tools.yaml. The image bakes @playwright/mcp + chrome-for-testing + chromium-headless-shell so the first request after a cold start does not stall on a 100MB+ download. Separate pw-profile-a and pw-profile-b volumes keep the two browser identities isolated and persistent across container restarts.

After adding or starting a server, refresh MCP connections with /mcp when the host supports it, or restart the agent session. Tool namespace prefixes are host-generated; select tools from the playwright or playwright-b server by their browser_* names.

Reaching the dev UI

The browser runs inside the docker network. Its localhost is not the development host's localhost. Use the docker-internal hostname:

  • Frontend: http://host.docker.internal:5173
  • Backend API: http://host.docker.internal:8000

The Vite dev server's server.allowedHosts includes host.docker.internal (see src/ui/vite.config.ts). Other hostnames produce Blocked request. This host ... is not allowed. - if you see that, the URL is wrong.

Default credentials (dev only)

Every seeded user shares one password = $INITIAL_ADMIN_PASSWORD (default admin). All belong to the org "Uniffy" (the org card on /select-org).

EmailUsernameNameOrg roleNotes
admin@uniffy.ioadminSystem AdministratorOWNERalso is_system_admin
alice@uniffy.ioaliceAlice JohnsonMEMBEREngineering (member)
bob@uniffy.iobobBob SmithMEMBEREngineering (admin)
charlie@uniffy.iocharlieCharlie BrownMEMBEREngineering + Product (member of both)
diana@uniffy.iodianaDiana PrinceMEMBERProduct (admin)
eve@uniffy.ioeveEve MartinezMEMBERProduct (member)

Groups (org "Uniffy"): Engineering {alice, bob(admin), charlie} and Product {charlie, diana(admin), eve}. charlie is intentionally in both, to exercise multi-group permission resolution.

Source of truth: src/uniffy/db/seed.py (admin + org) and src/uniffy/db/seed_dev.py (the 5 test users + 2 groups; only seeded when ENVIRONMENT=development).

The auth form has two Sign in buttons (mode switcher + submit). To submit, target [data-testid="auth-submit-login"] directly.

Two-user (cross-session) testing

For anything that needs two users at once - sharing, permission grants, live cross-session updates, "create as A, confirm B sees it" - use the two containers as two browsers. They have separate cookie jars + localStorage, so the users never collide.

Preferred: two containers (one user per browser)

  • User A -> playwright server (port 8931). User B -> playwright-b server (port 8932).
  • Log each user in once in its own browser via the login recipe below. No tab juggling, no identity-swap trap - you may browser_navigate / reload freely in either browser.
  • Both browsers hit the same dev UI (http://host.docker.internal:5173) and each opens its own notification stream, so a server push fired by A's action arrives in B's browser independently.
  • If the playwright-b tools are missing, the -b container is not up or MCP has not reconnected - see "Operating the containers".

Browser isolation

Use separate browser contexts or the two containers for separate users. Auth rotation lives in uniffy:auth storage and synchronizes across tabs. A login as another user retires the first tab's session, so two tabs in one browser profile cannot represent independent users. Same-user multi-tab tests remain supported. Never print tokens or cache-key seeds in tool output.

Login recipe

The login <input> elements are typed-only via direct DOM events in newer React versions; the MCP browser_type selectors sometimes parse the textbox role oddly and throw. The reliable pattern is browser_evaluate to set values via the native value setter so React picks them up:

const email = document.querySelector('input[type="email"], input[autocomplete="email"]');
const pwd = document.querySelector('input[type="password"]');
const setVal = (el, v) => {
  const set = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;
  set.call(el, v);
  el.dispatchEvent(new Event('input', { bubbles: true }));
  el.dispatchEvent(new Event('change', { bubbles: true }));
};
setVal(email, 'admin@uniffy.io');
setVal(pwd, 'admin');

Then browser_click on [data-testid="auth-submit-login"], then click the workspace card on /select-org.

Core MCP tools

ToolWhen to use
browser_navigateStart of every session + page transitions.
browser_snapshotAccessibility tree for the current page. Use this before clicking - it gives ref=eXX ids you can target.
browser_click / browser_typeStandard interactions. Use target = ref=eXX from the snapshot, OR a CSS / data-testid selector.
browser_evaluateThe escape hatch. Run arbitrary JS in the page context. Reach window.__milkdownEditor, window.__milkdownEditorView, Redux store (window.store if exposed), CSS-only queries, Y.Doc internals.
browser_console_messagesPull console logs. Pass level: "error" + all: true to inspect everything since session start.
browser_network_requestsInspect XHR/WebSocket traffic. Filter with the filter regex param.
browser_take_screenshotVisual verification. Prefer browser_snapshot for action targeting (cheaper, structured).
browser_press_key / browser_wait_forKeyboard shortcuts, async waits. browser_wait_for accepts time (seconds), text, or textGone.

Inspecting Yjs / Milkdown state

The notes editor exposes globals for debugging:

  • window.__milkdownEditor - the Crepe-wrapped Milkdown editor
  • window.__milkdownEditorView - the underlying ProseMirror EditorView

To read live YDoc state (e.g. confirm Y.Text("markdown") is being mirrored from PM transactions):

const view = window.__milkdownEditorView;
for (const p of view.state.plugins) {
  const st = p.getState ? p.getState(view.state) : null;
  if (st?.binding?.doc) {
    const doc = st.binding.doc;
    const ytext = doc.share.get('markdown');
    const frag = doc.share.get('prosemirror');
    return {
      ytextLen: ytext?._length,
      ytextSample: ytext?.toString().slice(0, 200),
      fragmentLen: frag?.length,
    };
  }
}

The y-sync$ plugin holds the binding; its state.binding.doc is the live Y.Doc.

Operating the containers

Both services live in .docker/compose/dev-tools.yaml; add mcp-playwright-b to any command to act on the second browser (or omit the service name to act on both).

ActionCommand
Build / rebuild imagedocker compose build mcp-playwright (one image, shared by both)
Start bothdocker compose --profile dev up -d mcp-playwright mcp-playwright-b (or ./manage.py stack up -d)
Start only the 2nddocker compose --profile dev up -d mcp-playwright-b
Logsdocker logs uniffy-mcp-playwright-b --tail 100 -f
Restart (after image rebuild)docker compose restart mcp-playwright mcp-playwright-b
Tear downdocker compose --profile dev down mcp-playwright mcp-playwright-b

After editing the Dockerfile, rebuild and restart - running containers keep the old image until then. After starting or adding a server, reconnect MCP with /mcp or restart the agent session so the playwright-b tools become visible.

Selector gotchas

  • ref=eXX snapshot refs are NOT accepted by browser_click in this build - it throws browserBackend.callTool: Unknown engine "ref". Click via browser_evaluate instead: find the element (by text / data-testid / title) and call .click() on it. Treat the snapshot refs as read-only locators for finding things, not as click targets.
  • window.store is NOT exposed - reading Redux via window.store.getState() returns undefined. Read state from the DOM instead (e.g. tree section contents, header avatar initials), or from localStorage.getItem('persist:root') (note: that's the persisted snapshot, which can lag/diverge from a tab's live in-memory state - see two-user testing).
  • browser_type with target as the snapshot's textbox role description sometimes throws Unexpected token while parsing css selector. Fall back to browser_evaluate with the native value-setter pattern (the login recipe) - it's the reliable way to fill any React-controlled input.
  • Tree nodes in the notes sidebar are not always React-routable links - many fire via onClick on a div[data-node-id="..."]. Use browser_evaluate to click the chevron <button> child to expand a folder before clicking the leaf.
  • strict mode violation: resolved to N elements means the selector is ambiguous. Prefer data-testid attributes, or filter to the visible one (el.offsetParent !== null).

Workflow template

  1. browser_navigate to http://host.docker.internal:5173.
  2. browser_snapshot - confirm page state.
  3. If unauthenticated, run the login recipe above.
  4. Navigate to the failing surface (/notes/<id>, /chat/..., etc).
  5. browser_console_messages with level: "error", all: true to capture any uncaught errors.
  6. browser_evaluate for runtime state confirmation (YDoc, Redux, window globals).
  7. browser_take_screenshot only when the user asks for visual evidence - the accessibility tree is faster and cheaper.

리뷰

아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.

댓글

로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

보내면 바로 공개됩니다. 이메일 주소나 전화번호 같은 개인 정보는 쓰지 마세요. URL은 2개까지입니다.

0 / 1,000자

이 양식은 reCAPTCHA로 보호되며 Google 개인정보처리방침 및 서비스 약관이 적용됩니다.

같은 저장소의 스킬

commit

무료

Create a conventional commit for all uncommitted changes

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

uniffy-io/uniffy682026년 10월 23일 업데이트

Create a Product Requirements Document from the current conversation and save it under `.agents/local/plans/`. Use when the user asks for a PRD, product specification, or requirements document for an Uniffy feature.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

uniffy-io/uniffy682026년 10월 23일 업데이트

execute

무료

Execute or resume an implementation plan from `.agents/local/plans/`, keeping its companion backlog and validation evidence current. Use when the user asks to implement a saved plan or continue planned multi-phase work.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

uniffy-io/uniffy682026년 10월 23일 업데이트

Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

uniffy-io/uniffy682026년 10월 23일 업데이트

Audit the in-tree model catalog against each provider's live API (Anthropic, OpenAI, Google, OpenRouter, xAI) using the keys in .env. Surfaces new models the catalog is missing, catalog models a provider no longer returns, and price/context drift on the curated OpenRouter subset, so the catalog stays current. Works on both stacks (docker containers or host venv). Run periodically or when a provider ships new models / changes pricing.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

uniffy-io/uniffy682026년 10월 23일 업데이트

Create a comprehensive Uniffy feature plan in `.agents/local/plans/` through deep codebase analysis, applicable rules, verified patterns, risks, tests, and executable validation steps. Use when the user asks to plan a feature or prepare implementation work without writing product code.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

uniffy-io/uniffy682026년 10월 23일 업데이트

uniffy-io의 스킬 모두 보기

이 스킬의 문제 신고