원클릭으로
new-e2e-spec
Add a new Playwright E2E spec for a night role, flow, or feature. Use when: adding an e2e test, new playwright spec.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Add a new Playwright E2E spec for a night role, flow, or feature. Use when: adding an e2e test, new playwright spec.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Run full quality pipeline, auto-fix lint/format issues, then commit and push. Use when: quality fix commit push, run quality, fix lint, fix format, quality commit push.
Structured debugging workflow: diagnose bugs/abnormal behavior, locate root cause, verify fix. Use when: debugging, bug, issue investigation, error, crash, not working, not rendering, timeout, disconnect.
Generate delegation prompts for specialist agents. Use when delegating tasks, writing expert prompts, or finding a specialist workflow.
Execute a minor release end-to-end: collect changes, write announcement, bump version, commit, tag, push. Use when: minor-release, minor release, new version, release minor.
Add a new preset board to PRESET_TEMPLATES. Use when: adding a board, creating a template, new preset.
Add a new werewolf role end-to-end: spec, night step, resolver, audio, badge, config, tests. Use when: adding a role, creating a character, new role SOP.
| name | new-e2e-spec |
| description | Add a new Playwright E2E spec for a night role, flow, or feature. Use when: adding an e2e test, new playwright spec. |
| argument-hint | Test target description (e.g., guard protection prevents death, 6-player magician swap) |
输出语言:执行本 skill 过程中,所有面向用户的输出(进度报告、询问、完成通知、错误提示)一律使用中文。
End-to-end addition of a Playwright E2E test, from defining the test target to local pass.
e2e/
├── fixtures/app.fixture.ts ← test.extend + createPlayerContexts + closeAll
├── pages/ ← Page Objects (class-based)
│ ├── HomePage.ts
│ ├── RoomPage.ts
│ ├── ConfigPage.ts
│ ├── NightFlowPage.ts
│ └── BoardPickerPage.ts
├── helpers/
│ ├── night-setup.ts ← withSetup() harness (creates game, runs body, cleans up)
│ ├── night-driver.ts ← Role-aware night actions (click seat, wolf vote, reveals)
│ ├── multi-player.ts ← setupNPlayerGame / setupNPlayerGameWithRoles
│ ├── waits.ts ← ensureConnected, waitForRoomScreenReady
│ ├── home.ts ← ensureAnonLogin, registerAutoDismissers
│ ├── ui.ts ← gotoWithRetry, clickIfVisible, screenshotOnFail
│ └── diagnostics.ts ← DiagnosticData logger per player
└── specs/ ← Test files (grouped by topic)
| Field | Description | Example |
|---|---|---|
| Test category | night-role / flow / feature | night-role |
| Involved roles | Which roles participate | seer, wolf, villager |
| Player count | Minimum players to cover scenario | 3 |
| Expected result | What to assert | reveal shows "好人" |
| Spec filename | Which file to add to or create | night-roles-check.spec.ts |
Night-role tests (most common): Use withSetup + night-driver.
import { expect, test } from '@playwright/test';
import { /* helpers */ } from '../helpers/night-driver';
import { withSetup } from '../helpers/night-setup';
test('description', async ({ browser }) => {
await withSetup(
browser,
{
playerCount: N,
configure: async (c) => c.configureCustomTemplate({ wolves: M, gods: [...], villagers: K }),
},
async ({ pages, roleMap }) => {
// Drive night actions + assert results
},
);
});
Non-night tests (room lifecycle, config, etc.): Use app fixture.
import { test } from '../fixtures/app.fixture';
test('description', async ({ app: { page, diag } }) => {
// Already logged in and on home screen
});
page.waitForTimeout(N) (only exception: ≤300ms inside polling loops).isVisible({ timeout: N }) (Playwright ignores this param)console.log (use test.step() + testInfo.attach())test.setTimeout(180_000)ensureConnected(page) to ensure WebSocket connection is stablewaitForRoomScreenReady(page) to wait for room to load| Helper | Purpose |
|---|---|
findRolePageIndex(map, name) | Find page index by role name |
findAllRolePageIndices(map, name) | Find all pages for a role |
waitForRoleTurn(page, keywords, allPages) | Wait for role's action turn |
clickSeatAndConfirm(page, seat) | Click seat + confirm |
driveWolfVote(pages, wolfIndices, seat) | Wolf vote |
waitForNightEnd(pages) | Wait for dawn |
readAlertText(page) | Read alert dialog text |
dismissAlert(page) | Dismiss alert dialog |
viewLastNightInfo(page) | View last night info |
clickBottomButton(page, text) | Click bottom button |
Roles MUST be driven in the order defined by NIGHT_STEP_ORDER_INTERNAL in packages/game-engine/src/models/roles/spec/plan.ts.
waitForRoleTurn calls tryClickAdvanceButton(includeSkip=true) to advance OTHER pages; wrong order will cause the target role to be auto-skipped.
Check order: grep -n '' packages/game-engine/src/models/roles/spec/plan.ts
Common order reference (position):
crowCurse (15) → wolfKill (16) → hiddenWolfReveal (18) → seerCheck (24)guardProtect (14) → wolfKill (16) → witchSave/witchPoison (20/21)Different actionKind steps have different UI flows. You must drive each according to its pattern:
chooseSeat steps (seer, crow, wolf, etc. choosing targets):
// 1. waitForRoleTurn detects keywords
const turn = await waitForRoleTurn(page, ['查验', '选择'], allPages, 120);
// 2. clickSeatAndConfirm internally dismisses alert then clicks seat
await clickSeatAndConfirm(page, targetSeat);
// 3. Read result alert (if any)
const reveal = await readAlertText(page);
await dismissAlert(page);
Reference: night-roles-check.spec.ts
chooseSeat + skip (not using ability):
const turn = await waitForRoleTurn(page, ['诅咒', '选择'], allPages, 120);
// Must dismiss the initial prompt alert first!
await dismissAlert(page);
await clickBottomButton(page, '不用技能');
// Some roles show a confirmation after skip
await dismissAlert(page);
Reference: night-roles-block.spec.ts seer/guard skip test
confirm steps (hiddenWolf viewing companions, avenger viewing faction, etc.):
const turn = await waitForRoleTurn(page, ['查看', '同伴'], allPages, 120);
// 1. Dismiss initial prompt alert (promptExecutor's showRoleActionPrompt)
await dismissAlert(page);
// 2. Click bottom button to trigger the actual action
await clickBottomButton(page, '查看同伴');
// 3. Read result alert
const reveal = await readAlertText(page);
await dismissAlert(page);
Reference: night-roles-hidden-wolf-crow.spec.ts, night-roles-kill.spec.ts (avenger)
wolf kill (dedicated helper):
const wolfTurn = await waitForRoleTurn(pages[wolfIdx]!, ['袭击', '选择'], allPages, 120);
await driveWolfVote(pages, wolfIndices, targetSeat);
configure: async (c) =>
c.configureCustomTemplate({
wolves: 1,
goodRoles: ['seer', 'witch'],
villagers: 2,
// wolfRoles: ['hiddenWolf'] // Wolf faction special roles
// specialRoles: ['thief'] // Third-party roles
});
Run through the core principles checklist 🔍 for all changes made:
as any / unnecessary ?.? (Principle 3)pnpm exec playwright test e2e/specs/<file> --reporter=listtest-results/) to diagnosenight-roles-<category>.spec.ts
<feature>.spec.ts (e.g., seating.spec.ts, reconnect.spec.ts)waitForRoleTurn calls tryClickAdvanceButton(includeSkip=true) for OTHER pages, which clicks "不用技能". If you drive a later role first, earlier roles will be auto-skipped during the wait.
❌ Wrong (crow acts before wolf, driving wolf first causes crow to be skipped):
await waitForRoleTurn(wolfPage, ['袭击'], pages);
await driveWolfVote(...);
await waitForRoleTurn(crowPage, ['诅咒'], pages); // Never detected: already auto-skipped
✅ Correct:
await waitForRoleTurn(crowPage, ['诅咒'], pages);
await clickSeatAndConfirm(crowPage, target);
await waitForRoleTurn(wolfPage, ['袭击'], pages);
await driveWolfVote(...);
All chooseSeat and confirm steps show a prompt alert on entry ("知道了" button).
clickSeatAndConfirm handles this internally (auto dismissAlert).clickBottomButton does NOT auto dismiss (only dismisses during retry).await dismissAlert(page) before clickBottomButton.roleMap.get(idx)!.seat — 0-based seat indexformatSeat(seat) = ${seat+1}号)