Files
huajishe-tts/apps/web/e2e/drag-test-multiplayer.spec.ts
e2hang d46dc54808 feat(multiplayer): room list UI + E2E/unit/Docker fixes
Adds the multiplayer functionality the user requested:

- LobbyRoomList UI component (create / list / join via boardgame.io's
  built-in Lobby REST API). Credentials are server-issued; no more hardcoded
  p0/p1 in OnlineConfig.
- LobbyClient re-export wrapper in @tts-like/engine (typed factory
  createLobbyClient).
- CORS origin default: dev-friendly regex list (boardgame.io's
  isOriginAllowed treats literal '*' as exact-match, which silently blocks
  everything; we now use RegExp matching localhost + Tauri schemes).
- War shuffle accepts an injected random fn so unit tests are deterministic;
  fixed a tie-handling bug where piles were cleared without redistributing
  cards (now piles stay on tie for a "war" round).
- Engine unit tests (vitest + Local master for 2-client sync):
    packages/engine/src/games/{drag-test,war,mill}.test.ts  (34 cases)
- Server REST tests (vitest + supertest, port 0 random):
    packages/server/src/server.test.ts                        (9 cases)
- Playwright E2E (apps/web/e2e/*): 3 specs covering create → join →
  sync for War / DragTest / Mill across two browser contexts.
- Dockerfile fix: pnpm deploy --prod leaves boardgame.io as a symlink to
  .pnpm/boardgame.io@…; copy -rL materialises the dist so the runtime
  image can resolve boardgame.io/server.
- Root scripts: dev:server, test:e2e, verify (ts + test + e2e).
2026-08-23 15:37:58 +08:00

61 lines
2.3 KiB
TypeScript

/**
* DragTest — 联机 E2E
*
* 流程:
* - Tab 0 创建并 join P0
* - Tab 1 刷新后 join P1
* - 验证双方都看到 5 张牌在 hand
*/
import { test, expect, Browser } from '@playwright/test';
async function setupOnlineGame(browser: Browser, gameKey: string) {
const ctx = await browser.newContext();
const page = await ctx.newPage();
await page.goto('/');
await page.getByTestId('game-select').selectOption(gameKey);
await page.getByTestId('mode-online').click();
await expect(page.getByTestId('lobby-room-list')).toBeVisible();
return page;
}
async function joinAsFirstPlayer(page: any, playerSlot: '0' | '1') {
if (playerSlot === '0') {
await page.getByTestId('lobby-create-room').click();
} else {
// Tab 1: 等房间列表里出现 room
await expect(async () => {
await page.getByTestId('lobby-refresh').click();
const roomChips = await page.locator('[data-testid^="lobby-room-"]').all();
const ids = await Promise.all(roomChips.map((c: any) => c.getAttribute('data-testid')));
const real = ids.filter((id: string | null) =>
id && !id.endsWith('-list') && !id.endsWith('-refresh') && !id.endsWith('-room'),
);
expect(real.length).toBeGreaterThan(0);
}).toPass({ timeout: 10_000 });
const rooms = page.locator('[data-testid^="lobby-room-"]:not([data-testid="lobby-room-list"])');
const count = await rooms.count();
expect(count).toBeGreaterThan(0);
const firstRoom = rooms.first();
const matchIDAttr = await firstRoom.getAttribute('data-testid');
const matchID = matchIDAttr!.replace(/^lobby-room-/, '');
await page.getByTestId(`lobby-join-1-${matchID}`).click();
}
await expect(page.getByTestId('online-need-room')).not.toBeVisible({ timeout: 15_000 });
}
test.describe('DragTest — 2-player multiplayer E2E', () => {
test('both clients see the same 5-card hand initially', async ({ browser }) => {
const p0 = await setupOnlineGame(browser, 'drag-test');
const p1 = await setupOnlineGame(browser, 'drag-test');
await joinAsFirstPlayer(p0, '0');
await joinAsFirstPlayer(p1, '1');
// 双方都看到 hand zone 含 5 张牌
await expect(p0.getByTestId('zone-hand')).toHaveAttribute('data-count', '5');
await expect(p1.getByTestId('zone-hand')).toHaveAttribute('data-count', '5');
});
});