import { chromium } from 'playwright'; import { env } from './env'; const store: Map = new Map(); function setToken(key: string, token: string, expiresIn?: number) { const expiresAt = expiresIn ? Date.now() + expiresIn * 1000 - 5000 : undefined; store.set(key, { token, expiresAt }); } function getTokenFromStore(key: string) { const entry = store.get(key); if (!entry) return null; if (entry.expiresAt && Date.now() > entry.expiresAt) { store.delete(key); return null; } return entry.token; } async function loginViaRaylab(username: string, password: string) { const base = env.BASE_URL || 'http://localhost:3000'; const browser = await chromium.launch({ headless: Boolean(process.env.PW_HEADLESS || '1') }); const context = await browser.newContext(); const page = await context.newPage(); // Navigate to RayLab - expects redirect to identity provider await page.goto(base, { waitUntil: 'networkidle' }); // Wait for a login form to appear on identity provider try { await page.waitForSelector('input[type="text"], input[type="email"], input[name="username"]', { timeout: 10000 }); // fill username const usernameInput = await page.$('input[name="username"]') || await page.$('input[type="email"]') || await page.$('input[type="text"]'); if (usernameInput) await usernameInput.fill(username); // fill password const passwordInput = await page.$('input[type="password"], input[name="password"]'); if (passwordInput) await passwordInput.fill(password); // try to submit const submitButton = await page.$('button[type="submit"], input[type="submit"]'); if (submitButton) { await Promise.all([page.waitForNavigation({ waitUntil: 'networkidle', timeout: 20000 }), submitButton.click()]); } } catch (e) { // If no login form found, we may already be at callback } // Wait for callback to be done and cookie to be set await page.waitForTimeout(1000); const cookies = await context.cookies(); const jwtCookie = cookies.find(c => c.name === 'raylab_jwt'); const refreshCookie = cookies.find(c => c.name === 'raylab_refresh'); const result: any = {}; if (jwtCookie) result.accessToken = jwtCookie.value; if (refreshCookie) result.refreshToken = refreshCookie.value; await context.close(); await browser.close(); if (!result.accessToken) throw new Error('Login failed: no session cookie set'); return result; } export async function getToken(role: 'owner' | 'admin' | 'employee' = 'admin') { const cacheKey = `role:${role}`; const cached = getTokenFromStore(cacheKey); if (cached) return cached; const username = role === 'owner' ? env.OWNER_USERNAME : role === 'admin' ? env.ADMIN_USERNAME : env.EMPLOYEE_USERNAME; const password = role === 'owner' ? env.OWNER_PASSWORD : role === 'admin' ? env.ADMIN_PASSWORD : env.EMPLOYEE_PASSWORD; if (!username || !password) throw new Error('Missing credentials in .env for role: ' + role); const data = await loginViaRaylab(username, password); const token = data.accessToken; // expiresIn not available from cookie - default to 1 hour const expiresIn = Number(process.env.TEST_TOKEN_EXPIRES_IN || 3600); setToken(cacheKey, token, expiresIn); return token; } export async function getRawToken(username: string, password: string) { const data = await loginViaRaylab(username, password); return data.accessToken; } export default { getToken, getRawToken };