import { expect, type Page, test } from "@playwright/test"; interface AuthStateResponse { authEnabled: boolean; formLoginEnabled: boolean; oidcEnabled: boolean; oidcProviderName: string; registrationEnabled: boolean; } async function getAuthState(page: Page): Promise { try { const response = await page.request.get("/api/auth/state"); if (!response.ok()) return null; return (await response.json()) as AuthStateResponse; } catch { return null; } } async function isAuthEnabled(page: Page): Promise { const state = await getAuthState(page); return state?.authEnabled !== false; } /** * Authentication E2E Tests * * Tests the login/register UI when not authenticated. * Uses empty storage state to simulate unauthenticated access. * * NOTE: This file intentionally imports `test` from @playwright/test * (not from fixtures) because auth tests use empty storageState and * must NOT have the auth-me caching interceptor. */ test.describe("Authentication", () => { test.use({ storageState: { cookies: [], origins: [] } }); test("should show login page for unauthenticated users", async ({ page }) => { test.skip(!(await isAuthEnabled(page)), "Auth is disabled in this environment"); await page.goto("/"); await expect(page.locator(".auth-container")).toBeVisible({ timeout: 15000 }); // Should have the app title await expect(page.locator(".auth-title")).toContainText("MedAssist-ng"); }); test("should have username and password fields", async ({ page }) => { test.skip(!(await isAuthEnabled(page)), "Auth is disabled in this environment"); await page.goto("/"); await expect(page.locator(".auth-container")).toBeVisible({ timeout: 15000 }); const usernameField = page.locator("#username"); const passwordField = page.locator("#password"); await expect(usernameField).toBeVisible(); await expect(usernameField).toBeEnabled(); await expect(passwordField).toBeVisible(); await expect(passwordField).toBeEnabled(); }); test("should have a submit button", async ({ page }) => { test.skip(!(await isAuthEnabled(page)), "Auth is disabled in this environment"); await page.goto("/"); await expect(page.locator(".auth-container")).toBeVisible({ timeout: 15000 }); const submitButton = page.locator('button.auth-submit[type="submit"]'); await expect(submitButton).toBeVisible(); await expect(submitButton).toBeEnabled(); }); test("should not navigate to dashboard without credentials", async ({ page }) => { test.skip(!(await isAuthEnabled(page)), "Auth is disabled in this environment"); await page.goto("/dashboard"); // Should NOT show the app header (redirected to login) await expect(page.locator("header.hero")).not.toBeVisible({ timeout: 10000 }); // Should show auth form instead await expect(page.locator(".auth-container")).toBeVisible(); }); test("should show error for invalid credentials", async ({ page }) => { test.skip(!(await isAuthEnabled(page)), "Auth is disabled in this environment"); await page.goto("/"); await expect(page.locator(".auth-container")).toBeVisible({ timeout: 15000 }); // Fill in invalid credentials await page.locator("#username").fill("nonexistent-user"); await page.locator("#password").fill("wrongpassword"); await page.locator('button.auth-submit[type="submit"]').click(); // Should show an error message await expect(page.locator(".auth-error")).toBeVisible({ timeout: 5000 }); }); test("should toggle between login and register forms", async ({ page }) => { test.skip(!(await isAuthEnabled(page)), "Auth is disabled in this environment"); await page.goto("/"); await expect(page.locator(".auth-container")).toBeVisible({ timeout: 15000 }); const toggleButton = page.locator("button.auth-link-btn"); test.skip( !(await toggleButton.isVisible().catch(() => false)), "Registration toggle is unavailable in this environment" ); // Check current subtitle text const subtitle = page.locator(".auth-subtitle"); const initialText = await subtitle.textContent(); // Click the toggle link (Create account / Already have an account) await toggleButton.click(); // Subtitle should change const newText = await subtitle.textContent(); expect(newText).not.toBe(initialText); }); test("should show SSO button when OIDC is enabled", async ({ page }) => { const state = await getAuthState(page); test.skip(!state?.authEnabled, "Auth is disabled in this environment"); test.skip(!state?.oidcEnabled, "OIDC is not enabled in this environment"); await page.goto("/"); await expect(page.locator(".auth-container")).toBeVisible({ timeout: 15000 }); const ssoButton = page.locator("button.sso-btn"); await expect(ssoButton).toBeVisible(); await expect(ssoButton).toContainText(state.oidcProviderName || "SSO"); }); test("should hide form login when formLoginEnabled is false", async ({ page }) => { const state = await getAuthState(page); test.skip(!state?.authEnabled, "Auth is disabled in this environment"); test.skip(state?.formLoginEnabled !== false, "Form login is enabled — cannot test hidden state"); await page.goto("/"); await expect(page.locator(".auth-container")).toBeVisible({ timeout: 15000 }); // Username/password fields should not be visible await expect(page.locator("#username")).not.toBeVisible(); await expect(page.locator("#password")).not.toBeVisible(); // SSO button should be the only login method await expect(page.locator("button.sso-btn")).toBeVisible(); }); test("should show both login methods when OIDC and form login are enabled", async ({ page }) => { const state = await getAuthState(page); test.skip(!state?.authEnabled, "Auth is disabled in this environment"); test.skip(!state?.oidcEnabled, "OIDC is not enabled"); test.skip(!state?.formLoginEnabled, "Form login is not enabled"); await page.goto("/"); await expect(page.locator(".auth-container")).toBeVisible({ timeout: 15000 }); // Both login methods visible await expect(page.locator("#username")).toBeVisible(); await expect(page.locator("#password")).toBeVisible(); await expect(page.locator("button.sso-btn")).toBeVisible(); }); });