// Covers Phase 7b's player-position relay end to end: a simulated mod connection sends a // player_positions roster over the real WS gateway, and a real browser tab — subscribed via // /ws/players/:serverId — is expected to render it as a map marker and update the online count. // Nothing here is seeded via SQL (unlike markers.spec.ts's terrain footprint) since there's // nothing to seed: the roster only exists once the WS protocol actually runs, which is exactly // the gap a headless unit test (ws-gateway.test.ts) can't close — it never proves a real browser // on the other end of players-gateway.ts's Redis pub/sub actually receives and renders it. import { test, expect, type Page } from "@playwright/test"; import { readE2eState, API_PORT } from "../config"; const state = readE2eState(); async function waitForMapReady(page: Page) { await page.goto("/"); await expect(page.getByTestId("players-toggle")).toBeVisible(); await page.locator("#map.leaflet-container").waitFor(); } /** * Opens a throwaway WS connection *inside the browser context* (not Node-side) — this page * already has other in-app sockets open (chat, players), but a raw WebSocket a test script opens * here is entirely independent of those; Chromium has no trouble holding several at once. Playing * the mod's own wire protocol (see ws-gateway.ts's doc comment) rather than reaching into * Redis/Postgres directly is the point: it's what proves the real WS handler path works, not just * whatever a lower-level test harness could fake. */ async function sendAsMod(page: Page, port: number, token: string, message: object) { await page.evaluate( ([port, token, message]) => { return new Promise((resolve, reject) => { const ws = new WebSocket(`ws://localhost:${port}/ws`); ws.onopen = () => ws.send(JSON.stringify({ type: "hello", token })); ws.onmessage = (ev) => { const parsed = JSON.parse(ev.data as string); if (parsed.type === "hello_ack" && parsed.ok) { ws.send(JSON.stringify(message)); setTimeout(() => { ws.close(); resolve(); }, 200); } else if (parsed.type === "hello_ack" && !parsed.ok) { reject(new Error(`hello rejected: ${parsed.error}`)); } }; ws.onerror = () => reject(new Error("mod ws connection failed")); }); }, [port, token, message] as const, ); } test("a player roster sent over the mod WS protocol shows up as a marker on the map", async ({ page }) => { await waitForMapReady(page); await expect(page.getByTestId("players-online-count")).toHaveText("0 online"); await sendAsMod(page, API_PORT, state.serverToken, { type: "player_positions", dimension: 0, players: [{ uuid: "e2e-player-1", username: "E2EPlayer", x: 5, y: 64, z: 5 }], }); await expect(page.getByTestId("players-online-count")).toHaveText("1 online"); await expect(page.locator(".leaflet-tooltip", { hasText: "E2EPlayer" })).toBeVisible(); }); test("unchecking 'show' hides player markers without dropping the roster count", async ({ page }) => { await waitForMapReady(page); await sendAsMod(page, API_PORT, state.serverToken, { type: "player_positions", dimension: 0, players: [{ uuid: "e2e-player-2", username: "ToggleTest", x: 1, y: 64, z: 1 }], }); await expect(page.getByTestId("players-online-count")).toHaveText("1 online"); await expect(page.locator(".leaflet-tooltip", { hasText: "ToggleTest" })).toBeVisible(); await page.getByTestId("players-toggle").uncheck(); await expect(page.locator(".leaflet-tooltip", { hasText: "ToggleTest" })).toHaveCount(0); // The roster itself is untouched by the visibility toggle — only the map layer is. await expect(page.getByTestId("players-online-count")).toHaveText("1 online"); }); test("an empty roster (everyone logged out) clears existing markers", async ({ page }) => { await waitForMapReady(page); await sendAsMod(page, API_PORT, state.serverToken, { type: "player_positions", dimension: 0, players: [{ uuid: "e2e-player-3", username: "LoggingOut", x: 2, y: 64, z: 2 }], }); await expect(page.locator(".leaflet-tooltip", { hasText: "LoggingOut" })).toBeVisible(); await sendAsMod(page, API_PORT, state.serverToken, { type: "player_positions", dimension: 0, players: [] }); await expect(page.getByTestId("players-online-count")).toHaveText("0 online"); await expect(page.locator(".leaflet-tooltip", { hasText: "LoggingOut" })).toHaveCount(0); });