diff --git a/frontend/e2e/workflows/1-login.spec.ts b/frontend/e2e/workflows/1-login.spec.ts new file mode 100644 index 00000000..0ad7b210 --- /dev/null +++ b/frontend/e2e/workflows/1-login.spec.ts @@ -0,0 +1,227 @@ +import { test, expect } from '@playwright/test'; +import * as auth from '../fixtures/auth'; +import * as assertions from '../utils/assertions'; +import { LDAP_USERS, LOCAL_USERS } from '../fixtures/test-data'; + +test.describe('Login Workflow', () => { + const BASE_URL = process.env.BASE_URL || 'http://localhost:3000'; + + test.beforeEach(async ({ page }) => { + // Navigate to login page + await page.goto(BASE_URL); + // Wait for identity check overlay + await page.waitForSelector('[data-testid="identity-check-overlay"]', { timeout: 10000 }); + }); + + test('should display identity check overlay on app load', async ({ page }) => { + const overlay = page.locator('[data-testid="identity-check-overlay"]'); + await expect(overlay).toBeVisible(); + + // Verify tabs are available + const ldapTab = page.locator('[data-testid="ldap-login-tab"]'); + const localTab = page.locator('[data-testid="local-login-tab"]'); + + await expect(ldapTab).toBeVisible(); + await expect(localTab).toBeVisible(); + }); + + test('should display login form with username and password fields', async ({ page }) => { + await assertions.assertLoginFormVisible(page); + }); + + test('should reject empty login credentials', async ({ page }) => { + const submitButton = page.locator('[data-testid="login-submit"]'); + await submitButton.click(); + + // Should show validation error + const errorMessage = page.locator('[data-testid="toast-error"]'); + await expect(errorMessage).toBeVisible({ timeout: 3000 }); + }); + + test('should reject invalid LDAP credentials', async ({ page }) => { + await auth.loginWithLdap(page, LDAP_USERS.invalid, BASE_URL); + + // Should show error message + const errorToast = page.locator('[data-testid="toast-error"]'); + await expect(errorToast).toBeVisible({ timeout: 5000 }); + + // Should still be on login page + await expect(page).toHaveURL(BASE_URL); + }); + + test('should successfully login with valid LDAP credentials', async ({ page }) => { + // Skip if LDAP is not configured in test environment + test.skip(process.env.SKIP_LDAP === 'true', 'LDAP not available in test environment'); + + await auth.loginWithLdap(page, LDAP_USERS.valid, BASE_URL); + + // Verify we're authenticated + await assertions.assertUserAuthenticated(page, BASE_URL); + + // Verify token is stored + const token = await auth.getAuthToken(page); + expect(token).toBeTruthy(); + }); + + test('should reject invalid local user credentials', async ({ page }) => { + // Switch to local user tab + const localTab = page.locator('[data-testid="local-login-tab"]'); + await localTab.click(); + + // Try to login with invalid credentials + await page.fill('[data-testid="local-username-input"]', 'invalid_user'); + await page.fill('[data-testid="local-password-input"]', 'wrong_password'); + await page.click('[data-testid="local-login-submit"]'); + + // Should show error + const errorToast = page.locator('[data-testid="toast-error"]'); + await expect(errorToast).toBeVisible({ timeout: 5000 }); + }); + + test('should successfully login with valid local user credentials', async ({ page }) => { + // Switch to local user tab + const localTab = page.locator('[data-testid="local-login-tab"]'); + await localTab.click(); + + // Login with valid credentials + await auth.loginWithLocalUser(page, LOCAL_USERS.admin, BASE_URL); + + // Verify authenticated + await assertions.assertUserAuthenticated(page, BASE_URL); + }); + + test('should show user identity after successful login', async ({ page }) => { + await auth.loginWithLocalUser(page, LOCAL_USERS.admin, BASE_URL); + + // Check that user info is displayed + const userDisplay = page.locator('[data-testid="user-display"]'); + await expect(userDisplay).toBeVisible(); + + // Verify username is shown + const username = await userDisplay.textContent(); + expect(username).toContain(LOCAL_USERS.admin.username); + }); + + test('should maintain session across page reloads', async ({ page }) => { + await auth.loginWithLocalUser(page, LOCAL_USERS.admin, BASE_URL); + await assertions.assertUserAuthenticated(page, BASE_URL); + + // Reload page + await page.reload(); + + // Should still be authenticated + const token = await auth.getAuthToken(page); + expect(token).toBeTruthy(); + + // Should not be on login page + await expect(page).not.toHaveURL(BASE_URL); + }); + + test('should support logout functionality', async ({ page }) => { + // Login first + await auth.loginWithLocalUser(page, LOCAL_USERS.admin, BASE_URL); + await assertions.assertUserAuthenticated(page, BASE_URL); + + // Logout + await auth.logout(page, BASE_URL); + + // Should be back on login page + const identityOverlay = page.locator('[data-testid="identity-check-overlay"]'); + await expect(identityOverlay).toBeVisible({ timeout: 5000 }); + + // Token should be cleared + const token = await auth.getAuthToken(page); + expect(token).toBeFalsy(); + }); + + test('should show admin button for admin users', async ({ page }) => { + await auth.loginWithLocalUser(page, LOCAL_USERS.admin, BASE_URL); + await assertions.assertUserAuthenticated(page, BASE_URL); + + // Admin button should be visible + const adminButton = page.locator('[data-testid="admin-button"]'); + await expect(adminButton).toBeVisible(); + }); + + test('should allow switching between LDAP and local login tabs', async ({ page }) => { + const ldapTab = page.locator('[data-testid="ldap-login-tab"]'); + const localTab = page.locator('[data-testid="local-login-tab"]'); + + // Start on LDAP tab + let ldapForm = page.locator('[data-testid="ldap-login-form"]'); + await expect(ldapForm).toBeVisible(); + + // Switch to local + await localTab.click(); + ldapForm = page.locator('[data-testid="ldap-login-form"]'); + await expect(ldapForm).not.toBeVisible(); + + // Switch back to LDAP + await ldapTab.click(); + ldapForm = page.locator('[data-testid="ldap-login-form"]'); + await expect(ldapForm).toBeVisible(); + }); + + test('should remember login preference on revisit', async ({ page, context }) => { + // Login with local user + await auth.loginWithLocalUser(page, LOCAL_USERS.admin, BASE_URL); + + // Close and reopen browser + await page.close(); + const newPage = await context.newPage(); + await newPage.goto(BASE_URL); + + // Should still be authenticated + const authenticated = await auth.isAuthenticated(newPage); + expect(authenticated).toBe(true); + + await newPage.close(); + }); + + test('should handle network errors gracefully', async ({ page }) => { + // Enable offline mode + await page.context().setOffline(true); + + // Try to login + const usernameInput = page.locator('[data-testid="username-input"]'); + const passwordInput = page.locator('[data-testid="password-input"]'); + const submitButton = page.locator('[data-testid="login-submit"]'); + + await usernameInput.fill(LOCAL_USERS.admin.username); + await passwordInput.fill(LOCAL_USERS.admin.password); + await submitButton.click(); + + // Should show network error + const errorToast = page.locator('[data-testid="toast-error"]'); + await expect(errorToast).toBeVisible({ timeout: 5000 }); + + // Re-enable network + await page.context().setOffline(false); + }); + + test('should display user list in local login tab', async ({ page }) => { + const localTab = page.locator('[data-testid="local-login-tab"]'); + await localTab.click(); + + // User list should be visible + const userList = page.locator('[data-testid="user-list"]'); + await expect(userList).toBeVisible(); + + // Should have at least one user + const userItems = page.locator('[data-testid="user-list-item"]'); + expect(await userItems.count()).toBeGreaterThan(0); + }); + + test('should auto-login when clicking user from list', async ({ page }) => { + const localTab = page.locator('[data-testid="local-login-tab"]'); + await localTab.click(); + + // Click first user in list + const firstUser = page.locator('[data-testid="user-list-item"]').first(); + await firstUser.click(); + + // Password input should be focused + const passwordInput = page.locator('[data-testid="local-password-input"]'); + await expect(passwordInput).toBeFocused(); + }); +});