/** * Live login helper — runs the real Keycloak OAuth flow in the current context. * * We use this instead of `storageState` because Supabase JS v2's async * `_recoverAndRefresh` doesn't reliably re-fire `onAuthStateChange`/INITIAL_SESSION * when the browser context is built from a saved localStorage snapshot. Doing the * full login for each test adds ~2–3 seconds but produces deterministic state. */ import type { Page } from '@playwright/test'; import type { USERS } from './users.js'; export async function loginAs(page: Page, user: (typeof USERS)[keyof typeof USERS]): Promise { await page.goto('/'); await page.waitForURL(/\/realms\/colectivo\/protocol\/openid-connect\/auth/, { timeout: 15_000 }); await page.fill('#username', user.email); await page.fill('#password', user.password); await page.click('#kc-login'); // Wait until we're redirected OFF the callback page. Origin depends on the // PUBLIC_APP_URL the stack is configured with (localhost for plain dev; LAN // IP like http://192.168.1.167:5173 when phones are in the loop). const appOrigin = new URL(process.env.PUBLIC_APP_URL ?? 'http://localhost:5173').origin; await page.waitForURL( (url) => url.origin === appOrigin && !url.pathname.startsWith('/auth/callback'), { timeout: 20_000 } ); await page.waitForFunction( () => Object.keys(window.localStorage).some( (k) => k.startsWith('sb-') && k.endsWith('-auth-token') ), null, { timeout: 15_000 } ); // Wait for `$currentCollective` to populate — any test that creates rows // needs this, because `handleCreate` silently returns when collective is // null. On localhost the round-trip was fast enough that races were rare, // but over LAN IP the extra latency exposes the race. We detect it by the // sidebar's collective button rendering the actual name (otherwise it // shows the app_name fallback "Colectivo"). await page.waitForFunction( (appName) => { const buttons = document.querySelectorAll('button'); for (const b of Array.from(buttons)) { const txt = b.textContent?.trim(); if (txt && txt !== appName && !txt.startsWith(appName)) { // Heuristic: any non-default button with emoji-prefixed name // (seed collective is "Casa García-López" with emoji "🏠"). if (/\p{Emoji}/u.test(txt)) return true; } } return false; }, 'Colectivo', { timeout: 10_000 } ); }