Files
ttrpg_manager/e2e-realtime/seats.spec.ts
T
NilsBriggen 9b5c63f23c P15a: hosting a shared session requires a signed-in GM
- The Host control now checks cloudUsername(): when the GM isn't signed in it shows
  "🔒 Sign in to host" (→ Settings) instead of "📡 Host". Players still join as guests.
- Realtime e2e: the GM marks itself signed in before hosting.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 17:35:54 +02:00

53 lines
2.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { test, expect, type BrowserContext } from '@playwright/test';
async function fresh(ctx: BrowserContext) {
const page = await ctx.newPage();
await page.goto('/');
await page.evaluate(() => { indexedDB.deleteDatabase('ttrpg-manager'); localStorage.clear(); });
await page.reload();
return page;
}
test('a player claims a seat and manages their character; edits round-trip to the GM', async ({ browser }) => {
const gmCtx = await browser.newContext();
const playerCtx = await browser.newContext();
// GM: sample campaign + host (hosting requires a signed-in GM).
const gm = await fresh(gmCtx);
await gm.evaluate(() => localStorage.setItem('ttrpg-cloud-user', 'GM'));
gm.on('dialog', (d) => d.dismiss()); // optional-password prompt
await gm.getByLabel('Settings').click();
await gm.getByRole('button', { name: 'Load sample campaign' }).click();
await expect(gm.getByRole('heading', { name: 'Sample: Lost Mine' })).toBeVisible();
await gm.getByRole('button', { name: '📡 Host' }).click();
const code = (await gm.getByTestId('join-code').textContent())?.replace(/[^A-Z0-9]/g, '') ?? '';
expect(code).toHaveLength(6);
// Player: join → seat-claim screen → claim Lia.
const player = await playerCtx.newPage();
await player.goto(`/play?room=${code}`);
await expect(player.getByRole('heading', { name: 'Which character is yours?' })).toBeVisible();
await player.locator('div.rounded-lg', { hasText: 'Lia the Brave' }).getByRole('button', { name: 'This is me' }).click();
// GM: approve the seat request.
await gm.getByTestId('seat-requests').click();
await gm.getByRole('button', { name: 'Grant', exact: true }).click();
// Player: now drives an interactive sheet.
await expect(player.getByTestId('my-character')).toBeVisible();
const hp = await player.getByTestId('my-hp').textContent();
const cur = Number((hp ?? '0/0').split('/')[0]);
expect(cur).toBeGreaterThan(5);
// Spend HP → optimistic update on the panel…
await player.getByRole('button', { name: '5' }).click();
await expect(player.getByTestId('my-hp')).toHaveText(new RegExp(`^${cur - 5}/`));
// …and it round-trips: GM persists it and re-broadcasts to the party board.
const party = player.locator('section').filter({ has: player.getByRole('heading', { name: 'Party' }) });
await expect(party.getByText(`${cur - 5}/`)).toBeVisible({ timeout: 8000 });
await gmCtx.close();
await playerCtx.close();
});