diff --git a/.coverage b/.coverage deleted file mode 100644 index cf173c48..00000000 Binary files a/.coverage and /dev/null differ diff --git a/.gitignore b/.gitignore index 7cf4f262..e6d9e4f4 100644 --- a/.gitignore +++ b/.gitignore @@ -95,6 +95,60 @@ aInventory-PROD*.zip *.cert +# ── IDE & Editor Configuration ─────────────────────────────── +.vscode/ +.idea/ +*.swp +*.swo +*~ +.sublime-text/ +.eclipse/ + +# ── Test Coverage & Reports ────────────────────────────────── +# Python coverage +.coverage +.coverage.* +htmlcov/ +backend/.mypy_cache/ + +# Frontend test artifacts (Playwright, Vitest, coverage) +frontend/coverage/ +frontend/playwright-report/ +frontend/test-results/ +frontend/.vitest/ + +# Python type checking cache +**/.mypy_cache/ +**/.dmypy.json +**/.pyre/ + +# ── Build Artifacts & Cache ────────────────────────────────── +# TypeScript build info cache +**/*.tsbuildinfo +tsconfig.tsbuildinfo + +# Vitest/Jest +.vitest/ + +# ── Development Environment Files ──────────────────────────── +# Local environment overrides (not tracked, but prevent accidental commits) +.env.local +.env.test +.env.development +.env.staging +backend/.env.local +backend/.env.test + +# ── Git & Patch Artifacts ──────────────────────────────────── +*.orig +*.rej +*.patch~ + +# ── Test Images & Temporary Assets ────────────────────────── +# Test images uploaded during development +_images.tests/ +_images/ + # ── Local AI Tooling & Persistent Paths ────────────────────── .tool_paths .git_path diff --git a/frontend/playwright-report/data/0b9e37cd5ecf83154fb9b300824d32ab6910d760.png b/frontend/playwright-report/data/0b9e37cd5ecf83154fb9b300824d32ab6910d760.png deleted file mode 100644 index 98a7abd7..00000000 Binary files a/frontend/playwright-report/data/0b9e37cd5ecf83154fb9b300824d32ab6910d760.png and /dev/null differ diff --git a/frontend/playwright-report/data/11afb044f69e204fbe127fa1dbadf98b251de8bc.md b/frontend/playwright-report/data/11afb044f69e204fbe127fa1dbadf98b251de8bc.md deleted file mode 100644 index b1518080..00000000 --- a/frontend/playwright-report/data/11afb044f69e204fbe127fa1dbadf98b251de8bc.md +++ /dev/null @@ -1,224 +0,0 @@ -# Instructions - -- Following Playwright test failed. -- Explain why, be concise, respect Playwright best practices. -- Provide a snippet of code with the fix, if possible. - -# Test info - -- Name: 1-login.spec.ts >> Login Workflow >> should remember login preference on revisit -- Location: e2e/workflows/1-login.spec.ts:165:7 - -# Error details - -``` -Test timeout of 30000ms exceeded. -``` - -``` -Error: page.fill: Test timeout of 30000ms exceeded. -Call log: - - waiting for locator('[data-testid="local-username-input"]') - -``` - -# Page snapshot - -```yaml -- generic [active] [ref=e1]: - - generic [ref=e3]: - - generic [ref=e4]: - - img [ref=e6] - - heading "Identity Check" [level=2] [ref=e9] - - paragraph [ref=e10]: Select operator profile or use direct login - - generic [ref=e11]: - - button "newuser user" [ref=e12] [cursor=pointer]: - - generic [ref=e13]: - - img [ref=e15] - - generic [ref=e18]: - - paragraph [ref=e19]: newuser - - paragraph [ref=e20]: user - - img [ref=e21] - - generic [ref=e23]: - - button "Enterprise" [ref=e24] [cursor=pointer]: - - img [ref=e25] - - text: Enterprise - - button "Manual Login" [ref=e28] [cursor=pointer]: - - img [ref=e29] - - text: Manual Login - - button "Open Next.js Dev Tools" [ref=e37] [cursor=pointer]: - - img [ref=e38] - - alert [ref=e41] -``` - -# Test source - -```ts - 1 | import { Page, BrowserContext } from '@playwright/test'; - 2 | - 3 | /** - 4 | * Authentication fixture for E2E tests - 5 | * Handles login flows and session management - 6 | */ - 7 | - 8 | export interface LoginCredentials { - 9 | username: string; - 10 | password: string; - 11 | } - 12 | - 13 | export interface AuthSession { - 14 | token: string; - 15 | userId: string; - 16 | email: string; - 17 | isAdmin: boolean; - 18 | } - 19 | - 20 | /** - 21 | * Perform LDAP authentication - 22 | */ - 23 | export async function loginWithLdap( - 24 | page: Page, - 25 | credentials: LoginCredentials, - 26 | baseUrl: string = 'http://localhost' - 27 | ): Promise { - 28 | await page.goto(`${baseUrl}`); - 29 | - 30 | // Wait for login form to appear - 31 | await page.waitForSelector('[data-testid="login-form"]', { timeout: 5000 }); - 32 | - 33 | // Fill in username - 34 | await page.fill('[data-testid="username-input"]', credentials.username); - 35 | - 36 | // Fill in password - 37 | await page.fill('[data-testid="password-input"]', credentials.password); - 38 | - 39 | // Click login button - 40 | await page.click('[data-testid="login-submit"]'); - 41 | - 42 | // Wait for navigation to inventory page - 43 | await page.waitForURL(`${baseUrl}/*`, { waitUntil: 'networkidle' }); - 44 | } - 45 | - 46 | /** - 47 | * Perform local user authentication - 48 | */ - 49 | export async function loginWithLocalUser( - 50 | page: Page, - 51 | credentials: LoginCredentials, - 52 | baseUrl: string = 'http://localhost' - 53 | ): Promise { - 54 | await page.goto(`${baseUrl}`); - 55 | - 56 | // Wait for identity check overlay - 57 | await page.waitForSelector('[data-testid="identity-check-overlay"]', { timeout: 5000 }); - 58 | - 59 | // Click "Local User" tab if it exists - 60 | const localUserTab = page.locator('[data-testid="local-user-tab"]'); - 61 | if (await localUserTab.isVisible()) { - 62 | await localUserTab.click(); - 63 | } - 64 | - 65 | // Fill in username -> 66 | await page.fill('[data-testid="local-username-input"]', credentials.username); - | ^ Error: page.fill: Test timeout of 30000ms exceeded. - 67 | - 68 | // Fill in password - 69 | await page.fill('[data-testid="local-password-input"]', credentials.password); - 70 | - 71 | // Click login button - 72 | await page.click('[data-testid="local-login-submit"]'); - 73 | - 74 | // Wait for navigation to inventory page - 75 | await page.waitForURL(`${baseUrl}/*`, { waitUntil: 'networkidle' }); - 76 | } - 77 | - 78 | /** - 79 | * Logout from the application - 80 | */ - 81 | export async function logout(page: Page, baseUrl: string = 'http://localhost'): Promise { - 82 | // Click logout button or menu - 83 | const logoutButton = page.locator('[data-testid="logout-button"]'); - 84 | if (await logoutButton.isVisible()) { - 85 | await logoutButton.click(); - 86 | } else { - 87 | // Try finding it in menu - 88 | const menu = page.locator('[data-testid="user-menu"]'); - 89 | if (await menu.isVisible()) { - 90 | await menu.click(); - 91 | await page.click('[data-testid="logout-menu-item"]'); - 92 | } - 93 | } - 94 | - 95 | // Wait for redirect to login - 96 | await page.waitForURL(`${baseUrl}/*`, { timeout: 5000 }); - 97 | } - 98 | - 99 | /** - 100 | * Get authentication token from local storage - 101 | */ - 102 | export async function getAuthToken(page: Page): Promise { - 103 | const token = await page.evaluate(() => { - 104 | return localStorage.getItem('auth_token') || sessionStorage.getItem('auth_token'); - 105 | }); - 106 | return token; - 107 | } - 108 | - 109 | /** - 110 | * Set authentication token in local storage - 111 | */ - 112 | export async function setAuthToken(page: Page, token: string): Promise { - 113 | await page.evaluate((t) => { - 114 | localStorage.setItem('auth_token', t); - 115 | }, token); - 116 | } - 117 | - 118 | /** - 119 | * Clear authentication data - 120 | */ - 121 | export async function clearAuth(page: Page): Promise { - 122 | await page.evaluate(() => { - 123 | localStorage.removeItem('auth_token'); - 124 | sessionStorage.removeItem('auth_token'); - 125 | localStorage.removeItem('user_data'); - 126 | sessionStorage.removeItem('user_data'); - 127 | }); - 128 | } - 129 | - 130 | /** - 131 | * Check if user is authenticated - 132 | */ - 133 | export async function isAuthenticated(page: Page): Promise { - 134 | const token = await getAuthToken(page); - 135 | return !!token; - 136 | } - 137 | - 138 | /** - 139 | * Get current user info from page - 140 | */ - 141 | export async function getCurrentUser(page: Page): Promise<{ username: string; email: string } | null> { - 142 | try { - 143 | const userData = await page.evaluate(() => { - 144 | const stored = localStorage.getItem('user_data') || sessionStorage.getItem('user_data'); - 145 | return stored ? JSON.parse(stored) : null; - 146 | }); - 147 | return userData; - 148 | } catch { - 149 | return null; - 150 | } - 151 | } - 152 | - 153 | /** - 154 | * Wait for authentication to complete - 155 | */ - 156 | export async function waitForAuth(page: Page, timeout: number = 5000): Promise { - 157 | // Wait for either: - 158 | // 1. Token to be in storage - 159 | // 2. Navigation to happen - 160 | // 3. Auth overlay to disappear - 161 | await page.waitForFunction( - 162 | () => { - 163 | const token = - 164 | typeof window !== 'undefined' - 165 | ? localStorage.getItem('auth_token') || sessionStorage.getItem('auth_token') - 166 | : null; -``` \ No newline at end of file diff --git a/frontend/playwright-report/data/18de4149d79572f42f059e1c21d2a1b03b0e0a62.md b/frontend/playwright-report/data/18de4149d79572f42f059e1c21d2a1b03b0e0a62.md deleted file mode 100644 index 2a30e17a..00000000 --- a/frontend/playwright-report/data/18de4149d79572f42f059e1c21d2a1b03b0e0a62.md +++ /dev/null @@ -1,224 +0,0 @@ -# Instructions - -- Following Playwright test failed. -- Explain why, be concise, respect Playwright best practices. -- Provide a snippet of code with the fix, if possible. - -# Test info - -- Name: 1-login.spec.ts >> Login Workflow >> should support logout functionality -- Location: e2e/workflows/1-login.spec.ts:120:7 - -# Error details - -``` -Test timeout of 30000ms exceeded. -``` - -``` -Error: page.fill: Test timeout of 30000ms exceeded. -Call log: - - waiting for locator('[data-testid="local-username-input"]') - -``` - -# Page snapshot - -```yaml -- generic [active] [ref=e1]: - - generic [ref=e3]: - - generic [ref=e4]: - - img [ref=e6] - - heading "Identity Check" [level=2] [ref=e9] - - paragraph [ref=e10]: Select operator profile or use direct login - - generic [ref=e11]: - - button "newuser user" [ref=e12] [cursor=pointer]: - - generic [ref=e13]: - - img [ref=e15] - - generic [ref=e18]: - - paragraph [ref=e19]: newuser - - paragraph [ref=e20]: user - - img [ref=e21] - - generic [ref=e23]: - - button "Enterprise" [ref=e24] [cursor=pointer]: - - img [ref=e25] - - text: Enterprise - - button "Manual Login" [ref=e28] [cursor=pointer]: - - img [ref=e29] - - text: Manual Login - - button "Open Next.js Dev Tools" [ref=e37] [cursor=pointer]: - - img [ref=e38] - - alert [ref=e41] -``` - -# Test source - -```ts - 1 | import { Page, BrowserContext } from '@playwright/test'; - 2 | - 3 | /** - 4 | * Authentication fixture for E2E tests - 5 | * Handles login flows and session management - 6 | */ - 7 | - 8 | export interface LoginCredentials { - 9 | username: string; - 10 | password: string; - 11 | } - 12 | - 13 | export interface AuthSession { - 14 | token: string; - 15 | userId: string; - 16 | email: string; - 17 | isAdmin: boolean; - 18 | } - 19 | - 20 | /** - 21 | * Perform LDAP authentication - 22 | */ - 23 | export async function loginWithLdap( - 24 | page: Page, - 25 | credentials: LoginCredentials, - 26 | baseUrl: string = 'http://localhost' - 27 | ): Promise { - 28 | await page.goto(`${baseUrl}`); - 29 | - 30 | // Wait for login form to appear - 31 | await page.waitForSelector('[data-testid="login-form"]', { timeout: 5000 }); - 32 | - 33 | // Fill in username - 34 | await page.fill('[data-testid="username-input"]', credentials.username); - 35 | - 36 | // Fill in password - 37 | await page.fill('[data-testid="password-input"]', credentials.password); - 38 | - 39 | // Click login button - 40 | await page.click('[data-testid="login-submit"]'); - 41 | - 42 | // Wait for navigation to inventory page - 43 | await page.waitForURL(`${baseUrl}/*`, { waitUntil: 'networkidle' }); - 44 | } - 45 | - 46 | /** - 47 | * Perform local user authentication - 48 | */ - 49 | export async function loginWithLocalUser( - 50 | page: Page, - 51 | credentials: LoginCredentials, - 52 | baseUrl: string = 'http://localhost' - 53 | ): Promise { - 54 | await page.goto(`${baseUrl}`); - 55 | - 56 | // Wait for identity check overlay - 57 | await page.waitForSelector('[data-testid="identity-check-overlay"]', { timeout: 5000 }); - 58 | - 59 | // Click "Local User" tab if it exists - 60 | const localUserTab = page.locator('[data-testid="local-user-tab"]'); - 61 | if (await localUserTab.isVisible()) { - 62 | await localUserTab.click(); - 63 | } - 64 | - 65 | // Fill in username -> 66 | await page.fill('[data-testid="local-username-input"]', credentials.username); - | ^ Error: page.fill: Test timeout of 30000ms exceeded. - 67 | - 68 | // Fill in password - 69 | await page.fill('[data-testid="local-password-input"]', credentials.password); - 70 | - 71 | // Click login button - 72 | await page.click('[data-testid="local-login-submit"]'); - 73 | - 74 | // Wait for navigation to inventory page - 75 | await page.waitForURL(`${baseUrl}/*`, { waitUntil: 'networkidle' }); - 76 | } - 77 | - 78 | /** - 79 | * Logout from the application - 80 | */ - 81 | export async function logout(page: Page, baseUrl: string = 'http://localhost'): Promise { - 82 | // Click logout button or menu - 83 | const logoutButton = page.locator('[data-testid="logout-button"]'); - 84 | if (await logoutButton.isVisible()) { - 85 | await logoutButton.click(); - 86 | } else { - 87 | // Try finding it in menu - 88 | const menu = page.locator('[data-testid="user-menu"]'); - 89 | if (await menu.isVisible()) { - 90 | await menu.click(); - 91 | await page.click('[data-testid="logout-menu-item"]'); - 92 | } - 93 | } - 94 | - 95 | // Wait for redirect to login - 96 | await page.waitForURL(`${baseUrl}/*`, { timeout: 5000 }); - 97 | } - 98 | - 99 | /** - 100 | * Get authentication token from local storage - 101 | */ - 102 | export async function getAuthToken(page: Page): Promise { - 103 | const token = await page.evaluate(() => { - 104 | return localStorage.getItem('auth_token') || sessionStorage.getItem('auth_token'); - 105 | }); - 106 | return token; - 107 | } - 108 | - 109 | /** - 110 | * Set authentication token in local storage - 111 | */ - 112 | export async function setAuthToken(page: Page, token: string): Promise { - 113 | await page.evaluate((t) => { - 114 | localStorage.setItem('auth_token', t); - 115 | }, token); - 116 | } - 117 | - 118 | /** - 119 | * Clear authentication data - 120 | */ - 121 | export async function clearAuth(page: Page): Promise { - 122 | await page.evaluate(() => { - 123 | localStorage.removeItem('auth_token'); - 124 | sessionStorage.removeItem('auth_token'); - 125 | localStorage.removeItem('user_data'); - 126 | sessionStorage.removeItem('user_data'); - 127 | }); - 128 | } - 129 | - 130 | /** - 131 | * Check if user is authenticated - 132 | */ - 133 | export async function isAuthenticated(page: Page): Promise { - 134 | const token = await getAuthToken(page); - 135 | return !!token; - 136 | } - 137 | - 138 | /** - 139 | * Get current user info from page - 140 | */ - 141 | export async function getCurrentUser(page: Page): Promise<{ username: string; email: string } | null> { - 142 | try { - 143 | const userData = await page.evaluate(() => { - 144 | const stored = localStorage.getItem('user_data') || sessionStorage.getItem('user_data'); - 145 | return stored ? JSON.parse(stored) : null; - 146 | }); - 147 | return userData; - 148 | } catch { - 149 | return null; - 150 | } - 151 | } - 152 | - 153 | /** - 154 | * Wait for authentication to complete - 155 | */ - 156 | export async function waitForAuth(page: Page, timeout: number = 5000): Promise { - 157 | // Wait for either: - 158 | // 1. Token to be in storage - 159 | // 2. Navigation to happen - 160 | // 3. Auth overlay to disappear - 161 | await page.waitForFunction( - 162 | () => { - 163 | const token = - 164 | typeof window !== 'undefined' - 165 | ? localStorage.getItem('auth_token') || sessionStorage.getItem('auth_token') - 166 | : null; -``` \ No newline at end of file diff --git a/frontend/playwright-report/data/25c086a4cde2673fed46f9d88609959f3059fc36.md b/frontend/playwright-report/data/25c086a4cde2673fed46f9d88609959f3059fc36.md deleted file mode 100644 index 6b2076cd..00000000 --- a/frontend/playwright-report/data/25c086a4cde2673fed46f9d88609959f3059fc36.md +++ /dev/null @@ -1,192 +0,0 @@ -# Instructions - -- Following Playwright test failed. -- Explain why, be concise, respect Playwright best practices. -- Provide a snippet of code with the fix, if possible. - -# Test info - -- Name: 1-login.spec.ts >> Login Workflow >> should reject empty login credentials -- Location: e2e/workflows/1-login.spec.ts:32:7 - -# Error details - -``` -Test timeout of 30000ms exceeded. -``` - -``` -Error: locator.click: Test timeout of 30000ms exceeded. -Call log: - - waiting for locator('[data-testid="login-submit"]') - -``` - -# Page snapshot - -```yaml -- generic [active] [ref=e1]: - - generic [ref=e3]: - - generic [ref=e4]: - - img [ref=e6] - - heading "Identity Check" [level=2] [ref=e9] - - paragraph [ref=e10]: Select operator profile or use direct login - - generic [ref=e11]: - - button "newuser user" [ref=e12] [cursor=pointer]: - - generic [ref=e13]: - - img [ref=e15] - - generic [ref=e18]: - - paragraph [ref=e19]: newuser - - paragraph [ref=e20]: user - - img [ref=e21] - - generic [ref=e23]: - - button "Enterprise" [ref=e24] [cursor=pointer]: - - img [ref=e25] - - text: Enterprise - - button "Manual Login" [ref=e28] [cursor=pointer]: - - img [ref=e29] - - text: Manual Login - - button "Open Next.js Dev Tools" [ref=e37] [cursor=pointer]: - - img [ref=e38] - - alert [ref=e41] -``` - -# Test source - -```ts - 1 | import { test, expect } from '@playwright/test'; - 2 | import * as auth from '../fixtures/auth'; - 3 | import * as assertions from '../utils/assertions'; - 4 | import { LDAP_USERS, LOCAL_USERS } from '../fixtures/test-data'; - 5 | - 6 | test.describe('Login Workflow', () => { - 7 | const BASE_URL = process.env.BASE_URL || 'http://localhost:8917'; - 8 | - 9 | test.beforeEach(async ({ page }) => { - 10 | // Navigate to login page - 11 | await page.goto(BASE_URL); - 12 | // Wait for identity check overlay - 13 | await page.waitForSelector('[data-testid="identity-check-overlay"]', { timeout: 10000 }); - 14 | }); - 15 | - 16 | test('should display identity check overlay on app load', async ({ page }) => { - 17 | const overlay = page.locator('[data-testid="identity-check-overlay"]'); - 18 | await expect(overlay).toBeVisible(); - 19 | - 20 | // Verify tabs are available - 21 | const ldapTab = page.locator('[data-testid="ldap-login-tab"]'); - 22 | const localTab = page.locator('[data-testid="local-login-tab"]'); - 23 | - 24 | await expect(ldapTab).toBeVisible(); - 25 | await expect(localTab).toBeVisible(); - 26 | }); - 27 | - 28 | test('should display login form with username and password fields', async ({ page }) => { - 29 | await assertions.assertLoginFormVisible(page); - 30 | }); - 31 | - 32 | test('should reject empty login credentials', async ({ page }) => { - 33 | const submitButton = page.locator('[data-testid="login-submit"]'); -> 34 | await submitButton.click(); - | ^ Error: locator.click: Test timeout of 30000ms exceeded. - 35 | - 36 | // Should show validation error - 37 | const errorMessage = page.locator('[data-testid="toast-error"]'); - 38 | await expect(errorMessage).toBeVisible({ timeout: 3000 }); - 39 | }); - 40 | - 41 | test('should reject invalid LDAP credentials', async ({ page }) => { - 42 | await auth.loginWithLdap(page, LDAP_USERS.invalid, BASE_URL); - 43 | - 44 | // Should show error message - 45 | const errorToast = page.locator('[data-testid="toast-error"]'); - 46 | await expect(errorToast).toBeVisible({ timeout: 5000 }); - 47 | - 48 | // Should still be on login page - 49 | await expect(page).toHaveURL(BASE_URL); - 50 | }); - 51 | - 52 | test('should successfully login with valid LDAP credentials', async ({ page }) => { - 53 | // Skip if LDAP is not configured in test environment - 54 | test.skip(process.env.SKIP_LDAP === 'true', 'LDAP not available in test environment'); - 55 | - 56 | await auth.loginWithLdap(page, LDAP_USERS.valid, BASE_URL); - 57 | - 58 | // Verify we're authenticated - 59 | await assertions.assertUserAuthenticated(page, BASE_URL); - 60 | - 61 | // Verify token is stored - 62 | const token = await auth.getAuthToken(page); - 63 | expect(token).toBeTruthy(); - 64 | }); - 65 | - 66 | test('should reject invalid local user credentials', async ({ page }) => { - 67 | // Switch to local user tab - 68 | const localTab = page.locator('[data-testid="local-login-tab"]'); - 69 | await localTab.click(); - 70 | - 71 | // Try to login with invalid credentials - 72 | await page.fill('[data-testid="local-username-input"]', 'invalid_user'); - 73 | await page.fill('[data-testid="local-password-input"]', 'wrong_password'); - 74 | await page.click('[data-testid="local-login-submit"]'); - 75 | - 76 | // Should show error - 77 | const errorToast = page.locator('[data-testid="toast-error"]'); - 78 | await expect(errorToast).toBeVisible({ timeout: 5000 }); - 79 | }); - 80 | - 81 | test('should successfully login with valid local user credentials', async ({ page }) => { - 82 | // Switch to local user tab - 83 | const localTab = page.locator('[data-testid="local-login-tab"]'); - 84 | await localTab.click(); - 85 | - 86 | // Login with valid credentials - 87 | await auth.loginWithLocalUser(page, LOCAL_USERS.admin, BASE_URL); - 88 | - 89 | // Verify authenticated - 90 | await assertions.assertUserAuthenticated(page, BASE_URL); - 91 | }); - 92 | - 93 | test('should show user identity after successful login', async ({ page }) => { - 94 | await auth.loginWithLocalUser(page, LOCAL_USERS.admin, BASE_URL); - 95 | - 96 | // Check that user info is displayed - 97 | const userDisplay = page.locator('[data-testid="user-display"]'); - 98 | await expect(userDisplay).toBeVisible(); - 99 | - 100 | // Verify username is shown - 101 | const username = await userDisplay.textContent(); - 102 | expect(username).toContain(LOCAL_USERS.admin.username); - 103 | }); - 104 | - 105 | test('should maintain session across page reloads', async ({ page }) => { - 106 | await auth.loginWithLocalUser(page, LOCAL_USERS.admin, BASE_URL); - 107 | await assertions.assertUserAuthenticated(page, BASE_URL); - 108 | - 109 | // Reload page - 110 | await page.reload(); - 111 | - 112 | // Should still be authenticated - 113 | const token = await auth.getAuthToken(page); - 114 | expect(token).toBeTruthy(); - 115 | - 116 | // Should not be on login page - 117 | await expect(page).not.toHaveURL(BASE_URL); - 118 | }); - 119 | - 120 | test('should support logout functionality', async ({ page }) => { - 121 | // Login first - 122 | await auth.loginWithLocalUser(page, LOCAL_USERS.admin, BASE_URL); - 123 | await assertions.assertUserAuthenticated(page, BASE_URL); - 124 | - 125 | // Logout - 126 | await auth.logout(page, BASE_URL); - 127 | - 128 | // Should be back on login page - 129 | const identityOverlay = page.locator('[data-testid="identity-check-overlay"]'); - 130 | await expect(identityOverlay).toBeVisible({ timeout: 5000 }); - 131 | - 132 | // Token should be cleared - 133 | const token = await auth.getAuthToken(page); - 134 | expect(token).toBeFalsy(); -``` \ No newline at end of file diff --git a/frontend/playwright-report/data/328efe50859839024d2ccf522575379c83e61131.md b/frontend/playwright-report/data/328efe50859839024d2ccf522575379c83e61131.md deleted file mode 100644 index 952ef37c..00000000 --- a/frontend/playwright-report/data/328efe50859839024d2ccf522575379c83e61131.md +++ /dev/null @@ -1,224 +0,0 @@ -# Instructions - -- Following Playwright test failed. -- Explain why, be concise, respect Playwright best practices. -- Provide a snippet of code with the fix, if possible. - -# Test info - -- Name: 1-login.spec.ts >> Login Workflow >> should successfully login with valid local user credentials -- Location: e2e/workflows/1-login.spec.ts:81:7 - -# Error details - -``` -Test timeout of 30000ms exceeded. -``` - -``` -Error: page.fill: Test timeout of 30000ms exceeded. -Call log: - - waiting for locator('[data-testid="local-username-input"]') - -``` - -# Page snapshot - -```yaml -- generic [active] [ref=e1]: - - generic [ref=e3]: - - generic [ref=e4]: - - img [ref=e6] - - heading "Identity Check" [level=2] [ref=e9] - - paragraph [ref=e10]: Select operator profile or use direct login - - generic [ref=e11]: - - button "newuser user" [ref=e12] [cursor=pointer]: - - generic [ref=e13]: - - img [ref=e15] - - generic [ref=e18]: - - paragraph [ref=e19]: newuser - - paragraph [ref=e20]: user - - img [ref=e21] - - generic [ref=e23]: - - button "Enterprise" [ref=e24] [cursor=pointer]: - - img [ref=e25] - - text: Enterprise - - button "Manual Login" [ref=e28] [cursor=pointer]: - - img [ref=e29] - - text: Manual Login - - button "Open Next.js Dev Tools" [ref=e37] [cursor=pointer]: - - img [ref=e38] - - alert [ref=e41] -``` - -# Test source - -```ts - 1 | import { Page, BrowserContext } from '@playwright/test'; - 2 | - 3 | /** - 4 | * Authentication fixture for E2E tests - 5 | * Handles login flows and session management - 6 | */ - 7 | - 8 | export interface LoginCredentials { - 9 | username: string; - 10 | password: string; - 11 | } - 12 | - 13 | export interface AuthSession { - 14 | token: string; - 15 | userId: string; - 16 | email: string; - 17 | isAdmin: boolean; - 18 | } - 19 | - 20 | /** - 21 | * Perform LDAP authentication - 22 | */ - 23 | export async function loginWithLdap( - 24 | page: Page, - 25 | credentials: LoginCredentials, - 26 | baseUrl: string = 'http://localhost' - 27 | ): Promise { - 28 | await page.goto(`${baseUrl}`); - 29 | - 30 | // Wait for login form to appear - 31 | await page.waitForSelector('[data-testid="login-form"]', { timeout: 5000 }); - 32 | - 33 | // Fill in username - 34 | await page.fill('[data-testid="username-input"]', credentials.username); - 35 | - 36 | // Fill in password - 37 | await page.fill('[data-testid="password-input"]', credentials.password); - 38 | - 39 | // Click login button - 40 | await page.click('[data-testid="login-submit"]'); - 41 | - 42 | // Wait for navigation to inventory page - 43 | await page.waitForURL(`${baseUrl}/*`, { waitUntil: 'networkidle' }); - 44 | } - 45 | - 46 | /** - 47 | * Perform local user authentication - 48 | */ - 49 | export async function loginWithLocalUser( - 50 | page: Page, - 51 | credentials: LoginCredentials, - 52 | baseUrl: string = 'http://localhost' - 53 | ): Promise { - 54 | await page.goto(`${baseUrl}`); - 55 | - 56 | // Wait for identity check overlay - 57 | await page.waitForSelector('[data-testid="identity-check-overlay"]', { timeout: 5000 }); - 58 | - 59 | // Click "Local User" tab if it exists - 60 | const localUserTab = page.locator('[data-testid="local-user-tab"]'); - 61 | if (await localUserTab.isVisible()) { - 62 | await localUserTab.click(); - 63 | } - 64 | - 65 | // Fill in username -> 66 | await page.fill('[data-testid="local-username-input"]', credentials.username); - | ^ Error: page.fill: Test timeout of 30000ms exceeded. - 67 | - 68 | // Fill in password - 69 | await page.fill('[data-testid="local-password-input"]', credentials.password); - 70 | - 71 | // Click login button - 72 | await page.click('[data-testid="local-login-submit"]'); - 73 | - 74 | // Wait for navigation to inventory page - 75 | await page.waitForURL(`${baseUrl}/*`, { waitUntil: 'networkidle' }); - 76 | } - 77 | - 78 | /** - 79 | * Logout from the application - 80 | */ - 81 | export async function logout(page: Page, baseUrl: string = 'http://localhost'): Promise { - 82 | // Click logout button or menu - 83 | const logoutButton = page.locator('[data-testid="logout-button"]'); - 84 | if (await logoutButton.isVisible()) { - 85 | await logoutButton.click(); - 86 | } else { - 87 | // Try finding it in menu - 88 | const menu = page.locator('[data-testid="user-menu"]'); - 89 | if (await menu.isVisible()) { - 90 | await menu.click(); - 91 | await page.click('[data-testid="logout-menu-item"]'); - 92 | } - 93 | } - 94 | - 95 | // Wait for redirect to login - 96 | await page.waitForURL(`${baseUrl}/*`, { timeout: 5000 }); - 97 | } - 98 | - 99 | /** - 100 | * Get authentication token from local storage - 101 | */ - 102 | export async function getAuthToken(page: Page): Promise { - 103 | const token = await page.evaluate(() => { - 104 | return localStorage.getItem('auth_token') || sessionStorage.getItem('auth_token'); - 105 | }); - 106 | return token; - 107 | } - 108 | - 109 | /** - 110 | * Set authentication token in local storage - 111 | */ - 112 | export async function setAuthToken(page: Page, token: string): Promise { - 113 | await page.evaluate((t) => { - 114 | localStorage.setItem('auth_token', t); - 115 | }, token); - 116 | } - 117 | - 118 | /** - 119 | * Clear authentication data - 120 | */ - 121 | export async function clearAuth(page: Page): Promise { - 122 | await page.evaluate(() => { - 123 | localStorage.removeItem('auth_token'); - 124 | sessionStorage.removeItem('auth_token'); - 125 | localStorage.removeItem('user_data'); - 126 | sessionStorage.removeItem('user_data'); - 127 | }); - 128 | } - 129 | - 130 | /** - 131 | * Check if user is authenticated - 132 | */ - 133 | export async function isAuthenticated(page: Page): Promise { - 134 | const token = await getAuthToken(page); - 135 | return !!token; - 136 | } - 137 | - 138 | /** - 139 | * Get current user info from page - 140 | */ - 141 | export async function getCurrentUser(page: Page): Promise<{ username: string; email: string } | null> { - 142 | try { - 143 | const userData = await page.evaluate(() => { - 144 | const stored = localStorage.getItem('user_data') || sessionStorage.getItem('user_data'); - 145 | return stored ? JSON.parse(stored) : null; - 146 | }); - 147 | return userData; - 148 | } catch { - 149 | return null; - 150 | } - 151 | } - 152 | - 153 | /** - 154 | * Wait for authentication to complete - 155 | */ - 156 | export async function waitForAuth(page: Page, timeout: number = 5000): Promise { - 157 | // Wait for either: - 158 | // 1. Token to be in storage - 159 | // 2. Navigation to happen - 160 | // 3. Auth overlay to disappear - 161 | await page.waitForFunction( - 162 | () => { - 163 | const token = - 164 | typeof window !== 'undefined' - 165 | ? localStorage.getItem('auth_token') || sessionStorage.getItem('auth_token') - 166 | : null; -``` \ No newline at end of file diff --git a/frontend/playwright-report/data/3bd536c2f96e6887e8dcdf903496bf540c20b16c.png b/frontend/playwright-report/data/3bd536c2f96e6887e8dcdf903496bf540c20b16c.png deleted file mode 100644 index a0f367fa..00000000 Binary files a/frontend/playwright-report/data/3bd536c2f96e6887e8dcdf903496bf540c20b16c.png and /dev/null differ diff --git a/frontend/playwright-report/data/877704f08187bb24583d69722bc9166fbc34100e.png b/frontend/playwright-report/data/877704f08187bb24583d69722bc9166fbc34100e.png deleted file mode 100644 index b0b1cec4..00000000 Binary files a/frontend/playwright-report/data/877704f08187bb24583d69722bc9166fbc34100e.png and /dev/null differ diff --git a/frontend/playwright-report/data/acd4ce422581ae27636b69d2084b159f0becc1c5.png b/frontend/playwright-report/data/acd4ce422581ae27636b69d2084b159f0becc1c5.png deleted file mode 100644 index 9449d71f..00000000 Binary files a/frontend/playwright-report/data/acd4ce422581ae27636b69d2084b159f0becc1c5.png and /dev/null differ diff --git a/frontend/playwright-report/data/b55707639177d53bca85afb8b9268b02973aeb9c.md b/frontend/playwright-report/data/b55707639177d53bca85afb8b9268b02973aeb9c.md deleted file mode 100644 index d14eef79..00000000 --- a/frontend/playwright-report/data/b55707639177d53bca85afb8b9268b02973aeb9c.md +++ /dev/null @@ -1,233 +0,0 @@ -# Instructions - -- Following Playwright test failed. -- Explain why, be concise, respect Playwright best practices. -- Provide a snippet of code with the fix, if possible. - -# Test info - -- Name: 1-login.spec.ts >> Login Workflow >> should reject invalid local user credentials -- Location: e2e/workflows/1-login.spec.ts:66:7 - -# Error details - -``` -Test timeout of 30000ms exceeded. -``` - -``` -Error: page.fill: Test timeout of 30000ms exceeded. -Call log: - - waiting for locator('[data-testid="local-username-input"]') - -``` - -# Page snapshot - -```yaml -- generic [ref=e1]: - - generic [ref=e3]: - - generic [ref=e4]: - - img [ref=e6] - - heading "Identity Check" [level=2] [ref=e9] - - paragraph [ref=e10]: Select operator profile or use direct login - - generic [ref=e12]: - - generic [ref=e13]: - - button [ref=e14] [cursor=pointer]: - - img [ref=e15] - - generic [ref=e18]: - - paragraph [ref=e19]: Logging in as - - paragraph [ref=e20]: Manual Input - - generic [ref=e21]: - - text: Username - - generic [ref=e22]: - - img [ref=e23] - - textbox "Admin" [ref=e26] - - generic [ref=e27]: - - text: Password - - generic [ref=e28]: - - img [ref=e29] - - textbox "Enter password" [active] [ref=e32] - - button "Verify Identity" [ref=e33] [cursor=pointer] - - button "Open Next.js Dev Tools" [ref=e39] [cursor=pointer]: - - img [ref=e40] - - alert [ref=e43] -``` - -# Test source - -```ts - 1 | import { test, expect } from '@playwright/test'; - 2 | import * as auth from '../fixtures/auth'; - 3 | import * as assertions from '../utils/assertions'; - 4 | import { LDAP_USERS, LOCAL_USERS } from '../fixtures/test-data'; - 5 | - 6 | test.describe('Login Workflow', () => { - 7 | const BASE_URL = process.env.BASE_URL || 'http://localhost:8917'; - 8 | - 9 | test.beforeEach(async ({ page }) => { - 10 | // Navigate to login page - 11 | await page.goto(BASE_URL); - 12 | // Wait for identity check overlay - 13 | await page.waitForSelector('[data-testid="identity-check-overlay"]', { timeout: 10000 }); - 14 | }); - 15 | - 16 | test('should display identity check overlay on app load', async ({ page }) => { - 17 | const overlay = page.locator('[data-testid="identity-check-overlay"]'); - 18 | await expect(overlay).toBeVisible(); - 19 | - 20 | // Verify tabs are available - 21 | const ldapTab = page.locator('[data-testid="ldap-login-tab"]'); - 22 | const localTab = page.locator('[data-testid="local-login-tab"]'); - 23 | - 24 | await expect(ldapTab).toBeVisible(); - 25 | await expect(localTab).toBeVisible(); - 26 | }); - 27 | - 28 | test('should display login form with username and password fields', async ({ page }) => { - 29 | await assertions.assertLoginFormVisible(page); - 30 | }); - 31 | - 32 | test('should reject empty login credentials', async ({ page }) => { - 33 | const submitButton = page.locator('[data-testid="login-submit"]'); - 34 | await submitButton.click(); - 35 | - 36 | // Should show validation error - 37 | const errorMessage = page.locator('[data-testid="toast-error"]'); - 38 | await expect(errorMessage).toBeVisible({ timeout: 3000 }); - 39 | }); - 40 | - 41 | test('should reject invalid LDAP credentials', async ({ page }) => { - 42 | await auth.loginWithLdap(page, LDAP_USERS.invalid, BASE_URL); - 43 | - 44 | // Should show error message - 45 | const errorToast = page.locator('[data-testid="toast-error"]'); - 46 | await expect(errorToast).toBeVisible({ timeout: 5000 }); - 47 | - 48 | // Should still be on login page - 49 | await expect(page).toHaveURL(BASE_URL); - 50 | }); - 51 | - 52 | test('should successfully login with valid LDAP credentials', async ({ page }) => { - 53 | // Skip if LDAP is not configured in test environment - 54 | test.skip(process.env.SKIP_LDAP === 'true', 'LDAP not available in test environment'); - 55 | - 56 | await auth.loginWithLdap(page, LDAP_USERS.valid, BASE_URL); - 57 | - 58 | // Verify we're authenticated - 59 | await assertions.assertUserAuthenticated(page, BASE_URL); - 60 | - 61 | // Verify token is stored - 62 | const token = await auth.getAuthToken(page); - 63 | expect(token).toBeTruthy(); - 64 | }); - 65 | - 66 | test('should reject invalid local user credentials', async ({ page }) => { - 67 | // Switch to local user tab - 68 | const localTab = page.locator('[data-testid="local-login-tab"]'); - 69 | await localTab.click(); - 70 | - 71 | // Try to login with invalid credentials -> 72 | await page.fill('[data-testid="local-username-input"]', 'invalid_user'); - | ^ Error: page.fill: Test timeout of 30000ms exceeded. - 73 | await page.fill('[data-testid="local-password-input"]', 'wrong_password'); - 74 | await page.click('[data-testid="local-login-submit"]'); - 75 | - 76 | // Should show error - 77 | const errorToast = page.locator('[data-testid="toast-error"]'); - 78 | await expect(errorToast).toBeVisible({ timeout: 5000 }); - 79 | }); - 80 | - 81 | test('should successfully login with valid local user credentials', async ({ page }) => { - 82 | // Switch to local user tab - 83 | const localTab = page.locator('[data-testid="local-login-tab"]'); - 84 | await localTab.click(); - 85 | - 86 | // Login with valid credentials - 87 | await auth.loginWithLocalUser(page, LOCAL_USERS.admin, BASE_URL); - 88 | - 89 | // Verify authenticated - 90 | await assertions.assertUserAuthenticated(page, BASE_URL); - 91 | }); - 92 | - 93 | test('should show user identity after successful login', async ({ page }) => { - 94 | await auth.loginWithLocalUser(page, LOCAL_USERS.admin, BASE_URL); - 95 | - 96 | // Check that user info is displayed - 97 | const userDisplay = page.locator('[data-testid="user-display"]'); - 98 | await expect(userDisplay).toBeVisible(); - 99 | - 100 | // Verify username is shown - 101 | const username = await userDisplay.textContent(); - 102 | expect(username).toContain(LOCAL_USERS.admin.username); - 103 | }); - 104 | - 105 | test('should maintain session across page reloads', async ({ page }) => { - 106 | await auth.loginWithLocalUser(page, LOCAL_USERS.admin, BASE_URL); - 107 | await assertions.assertUserAuthenticated(page, BASE_URL); - 108 | - 109 | // Reload page - 110 | await page.reload(); - 111 | - 112 | // Should still be authenticated - 113 | const token = await auth.getAuthToken(page); - 114 | expect(token).toBeTruthy(); - 115 | - 116 | // Should not be on login page - 117 | await expect(page).not.toHaveURL(BASE_URL); - 118 | }); - 119 | - 120 | test('should support logout functionality', async ({ page }) => { - 121 | // Login first - 122 | await auth.loginWithLocalUser(page, LOCAL_USERS.admin, BASE_URL); - 123 | await assertions.assertUserAuthenticated(page, BASE_URL); - 124 | - 125 | // Logout - 126 | await auth.logout(page, BASE_URL); - 127 | - 128 | // Should be back on login page - 129 | const identityOverlay = page.locator('[data-testid="identity-check-overlay"]'); - 130 | await expect(identityOverlay).toBeVisible({ timeout: 5000 }); - 131 | - 132 | // Token should be cleared - 133 | const token = await auth.getAuthToken(page); - 134 | expect(token).toBeFalsy(); - 135 | }); - 136 | - 137 | test('should show admin button for admin users', async ({ page }) => { - 138 | await auth.loginWithLocalUser(page, LOCAL_USERS.admin, BASE_URL); - 139 | await assertions.assertUserAuthenticated(page, BASE_URL); - 140 | - 141 | // Admin button should be visible - 142 | const adminButton = page.locator('[data-testid="admin-button"]'); - 143 | await expect(adminButton).toBeVisible(); - 144 | }); - 145 | - 146 | test('should allow switching between LDAP and local login tabs', async ({ page }) => { - 147 | const ldapTab = page.locator('[data-testid="ldap-login-tab"]'); - 148 | const localTab = page.locator('[data-testid="local-login-tab"]'); - 149 | - 150 | // Start on LDAP tab - 151 | let ldapForm = page.locator('[data-testid="ldap-login-form"]'); - 152 | await expect(ldapForm).toBeVisible(); - 153 | - 154 | // Switch to local - 155 | await localTab.click(); - 156 | ldapForm = page.locator('[data-testid="ldap-login-form"]'); - 157 | await expect(ldapForm).not.toBeVisible(); - 158 | - 159 | // Switch back to LDAP - 160 | await ldapTab.click(); - 161 | ldapForm = page.locator('[data-testid="ldap-login-form"]'); - 162 | await expect(ldapForm).toBeVisible(); - 163 | }); - 164 | - 165 | test('should remember login preference on revisit', async ({ page, context }) => { - 166 | // Login with local user - 167 | await auth.loginWithLocalUser(page, LOCAL_USERS.admin, BASE_URL); - 168 | - 169 | // Close and reopen browser - 170 | await page.close(); - 171 | const newPage = await context.newPage(); - 172 | await newPage.goto(BASE_URL); -``` \ No newline at end of file diff --git a/frontend/playwright-report/data/bd8f14932ab704c3c314dba27be5ca5a190c6e1e.md b/frontend/playwright-report/data/bd8f14932ab704c3c314dba27be5ca5a190c6e1e.md deleted file mode 100644 index fe107665..00000000 --- a/frontend/playwright-report/data/bd8f14932ab704c3c314dba27be5ca5a190c6e1e.md +++ /dev/null @@ -1,187 +0,0 @@ -# Instructions - -- Following Playwright test failed. -- Explain why, be concise, respect Playwright best practices. -- Provide a snippet of code with the fix, if possible. - -# Test info - -- Name: 1-login.spec.ts >> Login Workflow >> should reject invalid LDAP credentials -- Location: e2e/workflows/1-login.spec.ts:41:7 - -# Error details - -``` -TimeoutError: page.waitForSelector: Timeout 5000ms exceeded. -Call log: - - waiting for locator('[data-testid="login-form"]') to be visible - - waiting for" http://localhost:8917/login" navigation to finish... - - navigated to "http://localhost:8917/login" - -``` - -# Page snapshot - -```yaml -- generic [active] [ref=e1]: - - generic [ref=e3]: - - generic [ref=e4]: - - img [ref=e6] - - heading "Identity Check" [level=2] [ref=e9] - - paragraph [ref=e10]: Select operator profile or use direct login - - generic [ref=e11]: - - button "newuser user" [ref=e12] [cursor=pointer]: - - generic [ref=e13]: - - img [ref=e15] - - generic [ref=e18]: - - paragraph [ref=e19]: newuser - - paragraph [ref=e20]: user - - img [ref=e21] - - generic [ref=e23]: - - button "Enterprise" [ref=e24] [cursor=pointer]: - - img [ref=e25] - - text: Enterprise - - button "Manual Login" [ref=e28] [cursor=pointer]: - - img [ref=e29] - - text: Manual Login - - button "Open Next.js Dev Tools" [ref=e37] [cursor=pointer]: - - img [ref=e38] - - alert [ref=e41] -``` - -# Test source - -```ts - 1 | import { Page, BrowserContext } from '@playwright/test'; - 2 | - 3 | /** - 4 | * Authentication fixture for E2E tests - 5 | * Handles login flows and session management - 6 | */ - 7 | - 8 | export interface LoginCredentials { - 9 | username: string; - 10 | password: string; - 11 | } - 12 | - 13 | export interface AuthSession { - 14 | token: string; - 15 | userId: string; - 16 | email: string; - 17 | isAdmin: boolean; - 18 | } - 19 | - 20 | /** - 21 | * Perform LDAP authentication - 22 | */ - 23 | export async function loginWithLdap( - 24 | page: Page, - 25 | credentials: LoginCredentials, - 26 | baseUrl: string = 'http://localhost' - 27 | ): Promise { - 28 | await page.goto(`${baseUrl}`); - 29 | - 30 | // Wait for login form to appear -> 31 | await page.waitForSelector('[data-testid="login-form"]', { timeout: 5000 }); - | ^ TimeoutError: page.waitForSelector: Timeout 5000ms exceeded. - 32 | - 33 | // Fill in username - 34 | await page.fill('[data-testid="username-input"]', credentials.username); - 35 | - 36 | // Fill in password - 37 | await page.fill('[data-testid="password-input"]', credentials.password); - 38 | - 39 | // Click login button - 40 | await page.click('[data-testid="login-submit"]'); - 41 | - 42 | // Wait for navigation to inventory page - 43 | await page.waitForURL(`${baseUrl}/*`, { waitUntil: 'networkidle' }); - 44 | } - 45 | - 46 | /** - 47 | * Perform local user authentication - 48 | */ - 49 | export async function loginWithLocalUser( - 50 | page: Page, - 51 | credentials: LoginCredentials, - 52 | baseUrl: string = 'http://localhost' - 53 | ): Promise { - 54 | await page.goto(`${baseUrl}`); - 55 | - 56 | // Wait for identity check overlay - 57 | await page.waitForSelector('[data-testid="identity-check-overlay"]', { timeout: 5000 }); - 58 | - 59 | // Click "Local User" tab if it exists - 60 | const localUserTab = page.locator('[data-testid="local-user-tab"]'); - 61 | if (await localUserTab.isVisible()) { - 62 | await localUserTab.click(); - 63 | } - 64 | - 65 | // Fill in username - 66 | await page.fill('[data-testid="local-username-input"]', credentials.username); - 67 | - 68 | // Fill in password - 69 | await page.fill('[data-testid="local-password-input"]', credentials.password); - 70 | - 71 | // Click login button - 72 | await page.click('[data-testid="local-login-submit"]'); - 73 | - 74 | // Wait for navigation to inventory page - 75 | await page.waitForURL(`${baseUrl}/*`, { waitUntil: 'networkidle' }); - 76 | } - 77 | - 78 | /** - 79 | * Logout from the application - 80 | */ - 81 | export async function logout(page: Page, baseUrl: string = 'http://localhost'): Promise { - 82 | // Click logout button or menu - 83 | const logoutButton = page.locator('[data-testid="logout-button"]'); - 84 | if (await logoutButton.isVisible()) { - 85 | await logoutButton.click(); - 86 | } else { - 87 | // Try finding it in menu - 88 | const menu = page.locator('[data-testid="user-menu"]'); - 89 | if (await menu.isVisible()) { - 90 | await menu.click(); - 91 | await page.click('[data-testid="logout-menu-item"]'); - 92 | } - 93 | } - 94 | - 95 | // Wait for redirect to login - 96 | await page.waitForURL(`${baseUrl}/*`, { timeout: 5000 }); - 97 | } - 98 | - 99 | /** - 100 | * Get authentication token from local storage - 101 | */ - 102 | export async function getAuthToken(page: Page): Promise { - 103 | const token = await page.evaluate(() => { - 104 | return localStorage.getItem('auth_token') || sessionStorage.getItem('auth_token'); - 105 | }); - 106 | return token; - 107 | } - 108 | - 109 | /** - 110 | * Set authentication token in local storage - 111 | */ - 112 | export async function setAuthToken(page: Page, token: string): Promise { - 113 | await page.evaluate((t) => { - 114 | localStorage.setItem('auth_token', t); - 115 | }, token); - 116 | } - 117 | - 118 | /** - 119 | * Clear authentication data - 120 | */ - 121 | export async function clearAuth(page: Page): Promise { - 122 | await page.evaluate(() => { - 123 | localStorage.removeItem('auth_token'); - 124 | sessionStorage.removeItem('auth_token'); - 125 | localStorage.removeItem('user_data'); - 126 | sessionStorage.removeItem('user_data'); - 127 | }); - 128 | } - 129 | - 130 | /** - 131 | * Check if user is authenticated -``` \ No newline at end of file diff --git a/frontend/playwright-report/data/c49d3316f329d7283ba0d5b666781aac30b68caa.md b/frontend/playwright-report/data/c49d3316f329d7283ba0d5b666781aac30b68caa.md deleted file mode 100644 index abff9b1c..00000000 --- a/frontend/playwright-report/data/c49d3316f329d7283ba0d5b666781aac30b68caa.md +++ /dev/null @@ -1,213 +0,0 @@ -# Instructions - -- Following Playwright test failed. -- Explain why, be concise, respect Playwright best practices. -- Provide a snippet of code with the fix, if possible. - -# Test info - -- Name: 1-login.spec.ts >> Login Workflow >> should display login form with username and password fields -- Location: e2e/workflows/1-login.spec.ts:28:7 - -# Error details - -``` -Error: expect(locator).toBeVisible() failed - -Locator: locator('[data-testid="login-form"]') -Expected: visible -Timeout: 5000ms -Error: element(s) not found - -Call log: - - Expect "toBeVisible" with timeout 5000ms - - waiting for locator('[data-testid="login-form"]') - -``` - -# Page snapshot - -```yaml -- generic [active] [ref=e1]: - - generic [ref=e3]: - - generic [ref=e4]: - - img [ref=e6] - - heading "Identity Check" [level=2] [ref=e9] - - paragraph [ref=e10]: Select operator profile or use direct login - - generic [ref=e11]: - - button "newuser user" [ref=e12] [cursor=pointer]: - - generic [ref=e13]: - - img [ref=e15] - - generic [ref=e18]: - - paragraph [ref=e19]: newuser - - paragraph [ref=e20]: user - - img [ref=e21] - - generic [ref=e23]: - - button "Enterprise" [ref=e24] [cursor=pointer]: - - img [ref=e25] - - text: Enterprise - - button "Manual Login" [ref=e28] [cursor=pointer]: - - img [ref=e29] - - text: Manual Login - - button "Open Next.js Dev Tools" [ref=e37] [cursor=pointer]: - - img [ref=e38] - - alert [ref=e41] -``` - -# Test source - -```ts - 1 | import { expect, Page, Locator } from '@playwright/test'; - 2 | - 3 | /** - 4 | * Custom Playwright assertions for E2E tests - 5 | * Provides domain-specific matchers for inventory operations - 6 | */ - 7 | - 8 | /** - 9 | * Assert that an inventory item is visible with expected data - 10 | */ - 11 | export async function assertItemVisible( - 12 | page: Page, - 13 | itemName: string, - 14 | expectedData?: { quantity?: number; category?: string } - 15 | ): Promise { - 16 | const itemRow = page.locator(`[data-testid="item-row"][data-item-name="${itemName}"]`); - 17 | await expect(itemRow).toBeVisible(); - 18 | - 19 | if (expectedData?.quantity !== undefined) { - 20 | const quantityCell = itemRow.locator('[data-testid="quantity-cell"]'); - 21 | await expect(quantityCell).toContainText(expectedData.quantity.toString()); - 22 | } - 23 | - 24 | if (expectedData?.category !== undefined) { - 25 | const categoryCell = itemRow.locator('[data-testid="category-cell"]'); - 26 | await expect(categoryCell).toContainText(expectedData.category); - 27 | } - 28 | } - 29 | - 30 | /** - 31 | * Assert that a barcode scan was successful - 32 | */ - 33 | export async function assertScanSuccess( - 34 | page: Page, - 35 | expectedItemName: string, - 36 | expectedQuantityChange: number - 37 | ): Promise { - 38 | // Check for success toast notification - 39 | const successToast = page.locator('[data-testid="toast-success"]'); - 40 | await expect(successToast).toBeVisible({ timeout: 3000 }); - 41 | - 42 | // Verify item quantity was updated - 43 | const itemRow = page.locator(`[data-testid="item-row"][data-item-name="${expectedItemName}"]`); - 44 | await expect(itemRow).toBeVisible(); - 45 | } - 46 | - 47 | /** - 48 | * Assert that login form is displayed - 49 | */ - 50 | export async function assertLoginFormVisible(page: Page): Promise { - 51 | const loginForm = page.locator('[data-testid="login-form"]'); -> 52 | await expect(loginForm).toBeVisible(); - | ^ Error: expect(locator).toBeVisible() failed - 53 | - 54 | const usernameInput = page.locator('[data-testid="username-input"]'); - 55 | const passwordInput = page.locator('[data-testid="password-input"]'); - 56 | const submitButton = page.locator('[data-testid="login-submit"]'); - 57 | - 58 | await expect(usernameInput).toBeVisible(); - 59 | await expect(passwordInput).toBeVisible(); - 60 | await expect(submitButton).toBeVisible(); - 61 | } - 62 | - 63 | /** - 64 | * Assert that user is authenticated (on inventory page) - 65 | */ - 66 | export async function assertUserAuthenticated(page: Page, baseUrl: string = 'http://localhost'): Promise { - 67 | // Check URL is on an authenticated page - 68 | await expect(page).toHaveURL(/inventory|dashboard|scanner/); - 69 | - 70 | // Verify logout button is visible - 71 | const logoutButton = page.locator('[data-testid="logout-button"]'); - 72 | await expect(logoutButton).toBeVisible(); - 73 | } - 74 | - 75 | /** - 76 | * Assert that admin dashboard is visible - 77 | */ - 78 | export async function assertAdminDashboardVisible(page: Page): Promise { - 79 | const adminOverlay = page.locator('[data-testid="admin-overlay"]'); - 80 | await expect(adminOverlay).toBeVisible(); - 81 | - 82 | // Check for tabs - 83 | const identityTab = page.locator('[data-testid="admin-tab-identity"]'); - 84 | const databaseTab = page.locator('[data-testid="admin-tab-database"]'); - 85 | - 86 | await expect(identityTab).toBeVisible(); - 87 | await expect(databaseTab).toBeVisible(); - 88 | } - 89 | - 90 | /** - 91 | * Assert that stock adjustment form is visible - 92 | */ - 93 | export async function assertStockAdjustmentVisible(page: Page, itemName: string): Promise { - 94 | const adjustmentForm = page.locator('[data-testid="stock-adjustment-form"]'); - 95 | await expect(adjustmentForm).toBeVisible(); - 96 | - 97 | const itemNameDisplay = page.locator(`[data-testid="adjustment-item-name"]`); - 98 | await expect(itemNameDisplay).toContainText(itemName); - 99 | - 100 | const quantityInput = page.locator('[data-testid="adjustment-quantity-input"]'); - 101 | await expect(quantityInput).toBeVisible(); - 102 | } - 103 | - 104 | /** - 105 | * Assert that AI extraction is in progress - 106 | */ - 107 | export async function assertAiExtractionInProgress(page: Page): Promise { - 108 | const extractionOverlay = page.locator('[data-testid="ai-extraction-overlay"]'); - 109 | await expect(extractionOverlay).toBeVisible(); - 110 | - 111 | const loadingSpinner = page.locator('[data-testid="extraction-loading"]'); - 112 | await expect(loadingSpinner).toBeVisible(); - 113 | } - 114 | - 115 | /** - 116 | * Assert that AI extraction results are displayed - 117 | */ - 118 | export async function assertAiExtractionResults( - 119 | page: Page, - 120 | expectedFields: { name?: string; partNumber?: string; quantity?: string } - 121 | ): Promise { - 122 | const resultsForm = page.locator('[data-testid="extraction-results-form"]'); - 123 | await expect(resultsForm).toBeVisible(); - 124 | - 125 | if (expectedFields.name) { - 126 | const nameInput = resultsForm.locator('[data-testid="extracted-name"]'); - 127 | await expect(nameInput).toHaveValue(expectedFields.name); - 128 | } - 129 | - 130 | if (expectedFields.partNumber) { - 131 | const pnInput = resultsForm.locator('[data-testid="extracted-part-number"]'); - 132 | await expect(pnInput).toHaveValue(expectedFields.partNumber); - 133 | } - 134 | - 135 | if (expectedFields.quantity) { - 136 | const quantityInput = resultsForm.locator('[data-testid="extracted-quantity"]'); - 137 | await expect(quantityInput).toHaveValue(expectedFields.quantity); - 138 | } - 139 | } - 140 | - 141 | /** - 142 | * Assert that offline sync is pending - 143 | */ - 144 | export async function assertOfflineSyncPending(page: Page): Promise { - 145 | const syncIndicator = page.locator('[data-testid="offline-sync-indicator"]'); - 146 | await expect(syncIndicator).toBeVisible(); - 147 | - 148 | const pendingBadge = page.locator('[data-testid="sync-pending-badge"]'); - 149 | await expect(pendingBadge).toBeVisible(); - 150 | } - 151 | - 152 | /** -``` \ No newline at end of file diff --git a/frontend/playwright-report/data/c598dfc727318850f1aaaa533df9f623f04844a4.md b/frontend/playwright-report/data/c598dfc727318850f1aaaa533df9f623f04844a4.md deleted file mode 100644 index 654868e4..00000000 --- a/frontend/playwright-report/data/c598dfc727318850f1aaaa533df9f623f04844a4.md +++ /dev/null @@ -1,238 +0,0 @@ -# Instructions - -- Following Playwright test failed. -- Explain why, be concise, respect Playwright best practices. -- Provide a snippet of code with the fix, if possible. - -# Test info - -- Name: 1-login.spec.ts >> Login Workflow >> should allow switching between LDAP and local login tabs -- Location: e2e/workflows/1-login.spec.ts:146:7 - -# Error details - -``` -Error: expect(locator).toBeVisible() failed - -Locator: locator('[data-testid="ldap-login-form"]') -Expected: visible -Timeout: 5000ms -Error: element(s) not found - -Call log: - - Expect "toBeVisible" with timeout 5000ms - - waiting for locator('[data-testid="ldap-login-form"]') - -``` - -# Page snapshot - -```yaml -- generic [active] [ref=e1]: - - generic [ref=e3]: - - generic [ref=e4]: - - img [ref=e6] - - heading "Identity Check" [level=2] [ref=e9] - - paragraph [ref=e10]: Select operator profile or use direct login - - generic [ref=e11]: - - button "newuser user" [ref=e12] [cursor=pointer]: - - generic [ref=e13]: - - img [ref=e15] - - generic [ref=e18]: - - paragraph [ref=e19]: newuser - - paragraph [ref=e20]: user - - img [ref=e21] - - generic [ref=e23]: - - button "Enterprise" [ref=e24] [cursor=pointer]: - - img [ref=e25] - - text: Enterprise - - button "Manual Login" [ref=e28] [cursor=pointer]: - - img [ref=e29] - - text: Manual Login - - button "Open Next.js Dev Tools" [ref=e37] [cursor=pointer]: - - img [ref=e38] - - alert [ref=e41] -``` - -# Test source - -```ts - 52 | test('should successfully login with valid LDAP credentials', async ({ page }) => { - 53 | // Skip if LDAP is not configured in test environment - 54 | test.skip(process.env.SKIP_LDAP === 'true', 'LDAP not available in test environment'); - 55 | - 56 | await auth.loginWithLdap(page, LDAP_USERS.valid, BASE_URL); - 57 | - 58 | // Verify we're authenticated - 59 | await assertions.assertUserAuthenticated(page, BASE_URL); - 60 | - 61 | // Verify token is stored - 62 | const token = await auth.getAuthToken(page); - 63 | expect(token).toBeTruthy(); - 64 | }); - 65 | - 66 | test('should reject invalid local user credentials', async ({ page }) => { - 67 | // Switch to local user tab - 68 | const localTab = page.locator('[data-testid="local-login-tab"]'); - 69 | await localTab.click(); - 70 | - 71 | // Try to login with invalid credentials - 72 | await page.fill('[data-testid="local-username-input"]', 'invalid_user'); - 73 | await page.fill('[data-testid="local-password-input"]', 'wrong_password'); - 74 | await page.click('[data-testid="local-login-submit"]'); - 75 | - 76 | // Should show error - 77 | const errorToast = page.locator('[data-testid="toast-error"]'); - 78 | await expect(errorToast).toBeVisible({ timeout: 5000 }); - 79 | }); - 80 | - 81 | test('should successfully login with valid local user credentials', async ({ page }) => { - 82 | // Switch to local user tab - 83 | const localTab = page.locator('[data-testid="local-login-tab"]'); - 84 | await localTab.click(); - 85 | - 86 | // Login with valid credentials - 87 | await auth.loginWithLocalUser(page, LOCAL_USERS.admin, BASE_URL); - 88 | - 89 | // Verify authenticated - 90 | await assertions.assertUserAuthenticated(page, BASE_URL); - 91 | }); - 92 | - 93 | test('should show user identity after successful login', async ({ page }) => { - 94 | await auth.loginWithLocalUser(page, LOCAL_USERS.admin, BASE_URL); - 95 | - 96 | // Check that user info is displayed - 97 | const userDisplay = page.locator('[data-testid="user-display"]'); - 98 | await expect(userDisplay).toBeVisible(); - 99 | - 100 | // Verify username is shown - 101 | const username = await userDisplay.textContent(); - 102 | expect(username).toContain(LOCAL_USERS.admin.username); - 103 | }); - 104 | - 105 | test('should maintain session across page reloads', async ({ page }) => { - 106 | await auth.loginWithLocalUser(page, LOCAL_USERS.admin, BASE_URL); - 107 | await assertions.assertUserAuthenticated(page, BASE_URL); - 108 | - 109 | // Reload page - 110 | await page.reload(); - 111 | - 112 | // Should still be authenticated - 113 | const token = await auth.getAuthToken(page); - 114 | expect(token).toBeTruthy(); - 115 | - 116 | // Should not be on login page - 117 | await expect(page).not.toHaveURL(BASE_URL); - 118 | }); - 119 | - 120 | test('should support logout functionality', async ({ page }) => { - 121 | // Login first - 122 | await auth.loginWithLocalUser(page, LOCAL_USERS.admin, BASE_URL); - 123 | await assertions.assertUserAuthenticated(page, BASE_URL); - 124 | - 125 | // Logout - 126 | await auth.logout(page, BASE_URL); - 127 | - 128 | // Should be back on login page - 129 | const identityOverlay = page.locator('[data-testid="identity-check-overlay"]'); - 130 | await expect(identityOverlay).toBeVisible({ timeout: 5000 }); - 131 | - 132 | // Token should be cleared - 133 | const token = await auth.getAuthToken(page); - 134 | expect(token).toBeFalsy(); - 135 | }); - 136 | - 137 | test('should show admin button for admin users', async ({ page }) => { - 138 | await auth.loginWithLocalUser(page, LOCAL_USERS.admin, BASE_URL); - 139 | await assertions.assertUserAuthenticated(page, BASE_URL); - 140 | - 141 | // Admin button should be visible - 142 | const adminButton = page.locator('[data-testid="admin-button"]'); - 143 | await expect(adminButton).toBeVisible(); - 144 | }); - 145 | - 146 | test('should allow switching between LDAP and local login tabs', async ({ page }) => { - 147 | const ldapTab = page.locator('[data-testid="ldap-login-tab"]'); - 148 | const localTab = page.locator('[data-testid="local-login-tab"]'); - 149 | - 150 | // Start on LDAP tab - 151 | let ldapForm = page.locator('[data-testid="ldap-login-form"]'); -> 152 | await expect(ldapForm).toBeVisible(); - | ^ Error: expect(locator).toBeVisible() failed - 153 | - 154 | // Switch to local - 155 | await localTab.click(); - 156 | ldapForm = page.locator('[data-testid="ldap-login-form"]'); - 157 | await expect(ldapForm).not.toBeVisible(); - 158 | - 159 | // Switch back to LDAP - 160 | await ldapTab.click(); - 161 | ldapForm = page.locator('[data-testid="ldap-login-form"]'); - 162 | await expect(ldapForm).toBeVisible(); - 163 | }); - 164 | - 165 | test('should remember login preference on revisit', async ({ page, context }) => { - 166 | // Login with local user - 167 | await auth.loginWithLocalUser(page, LOCAL_USERS.admin, BASE_URL); - 168 | - 169 | // Close and reopen browser - 170 | await page.close(); - 171 | const newPage = await context.newPage(); - 172 | await newPage.goto(BASE_URL); - 173 | - 174 | // Should still be authenticated - 175 | const authenticated = await auth.isAuthenticated(newPage); - 176 | expect(authenticated).toBe(true); - 177 | - 178 | await newPage.close(); - 179 | }); - 180 | - 181 | test('should handle network errors gracefully', async ({ page }) => { - 182 | // Enable offline mode - 183 | await page.context().setOffline(true); - 184 | - 185 | // Try to login - 186 | const usernameInput = page.locator('[data-testid="username-input"]'); - 187 | const passwordInput = page.locator('[data-testid="password-input"]'); - 188 | const submitButton = page.locator('[data-testid="login-submit"]'); - 189 | - 190 | await usernameInput.fill(LOCAL_USERS.admin.username); - 191 | await passwordInput.fill(LOCAL_USERS.admin.password); - 192 | await submitButton.click(); - 193 | - 194 | // Should show network error - 195 | const errorToast = page.locator('[data-testid="toast-error"]'); - 196 | await expect(errorToast).toBeVisible({ timeout: 5000 }); - 197 | - 198 | // Re-enable network - 199 | await page.context().setOffline(false); - 200 | }); - 201 | - 202 | test('should display user list in local login tab', async ({ page }) => { - 203 | const localTab = page.locator('[data-testid="local-login-tab"]'); - 204 | await localTab.click(); - 205 | - 206 | // User list should be visible - 207 | const userList = page.locator('[data-testid="user-list"]'); - 208 | await expect(userList).toBeVisible(); - 209 | - 210 | // Should have at least one user - 211 | const userItems = page.locator('[data-testid="user-list-item"]'); - 212 | expect(await userItems.count()).toBeGreaterThan(0); - 213 | }); - 214 | - 215 | test('should auto-login when clicking user from list', async ({ page }) => { - 216 | const localTab = page.locator('[data-testid="local-login-tab"]'); - 217 | await localTab.click(); - 218 | - 219 | // Click first user in list - 220 | const firstUser = page.locator('[data-testid="user-list-item"]').first(); - 221 | await firstUser.click(); - 222 | - 223 | // Password input should be focused - 224 | const passwordInput = page.locator('[data-testid="local-password-input"]'); - 225 | await expect(passwordInput).toBeFocused(); - 226 | }); - 227 | }); - 228 | -``` \ No newline at end of file diff --git a/frontend/playwright-report/data/ce82bdf77eafd95f45c92adac93d33895723c22a.md b/frontend/playwright-report/data/ce82bdf77eafd95f45c92adac93d33895723c22a.md deleted file mode 100644 index ec531fc2..00000000 --- a/frontend/playwright-report/data/ce82bdf77eafd95f45c92adac93d33895723c22a.md +++ /dev/null @@ -1,224 +0,0 @@ -# Instructions - -- Following Playwright test failed. -- Explain why, be concise, respect Playwright best practices. -- Provide a snippet of code with the fix, if possible. - -# Test info - -- Name: 1-login.spec.ts >> Login Workflow >> should show user identity after successful login -- Location: e2e/workflows/1-login.spec.ts:93:7 - -# Error details - -``` -Test timeout of 30000ms exceeded. -``` - -``` -Error: page.fill: Test timeout of 30000ms exceeded. -Call log: - - waiting for locator('[data-testid="local-username-input"]') - -``` - -# Page snapshot - -```yaml -- generic [active] [ref=e1]: - - generic [ref=e3]: - - generic [ref=e4]: - - img [ref=e6] - - heading "Identity Check" [level=2] [ref=e9] - - paragraph [ref=e10]: Select operator profile or use direct login - - generic [ref=e11]: - - button "newuser user" [ref=e12] [cursor=pointer]: - - generic [ref=e13]: - - img [ref=e15] - - generic [ref=e18]: - - paragraph [ref=e19]: newuser - - paragraph [ref=e20]: user - - img [ref=e21] - - generic [ref=e23]: - - button "Enterprise" [ref=e24] [cursor=pointer]: - - img [ref=e25] - - text: Enterprise - - button "Manual Login" [ref=e28] [cursor=pointer]: - - img [ref=e29] - - text: Manual Login - - button "Open Next.js Dev Tools" [ref=e37] [cursor=pointer]: - - img [ref=e38] - - alert [ref=e41] -``` - -# Test source - -```ts - 1 | import { Page, BrowserContext } from '@playwright/test'; - 2 | - 3 | /** - 4 | * Authentication fixture for E2E tests - 5 | * Handles login flows and session management - 6 | */ - 7 | - 8 | export interface LoginCredentials { - 9 | username: string; - 10 | password: string; - 11 | } - 12 | - 13 | export interface AuthSession { - 14 | token: string; - 15 | userId: string; - 16 | email: string; - 17 | isAdmin: boolean; - 18 | } - 19 | - 20 | /** - 21 | * Perform LDAP authentication - 22 | */ - 23 | export async function loginWithLdap( - 24 | page: Page, - 25 | credentials: LoginCredentials, - 26 | baseUrl: string = 'http://localhost' - 27 | ): Promise { - 28 | await page.goto(`${baseUrl}`); - 29 | - 30 | // Wait for login form to appear - 31 | await page.waitForSelector('[data-testid="login-form"]', { timeout: 5000 }); - 32 | - 33 | // Fill in username - 34 | await page.fill('[data-testid="username-input"]', credentials.username); - 35 | - 36 | // Fill in password - 37 | await page.fill('[data-testid="password-input"]', credentials.password); - 38 | - 39 | // Click login button - 40 | await page.click('[data-testid="login-submit"]'); - 41 | - 42 | // Wait for navigation to inventory page - 43 | await page.waitForURL(`${baseUrl}/*`, { waitUntil: 'networkidle' }); - 44 | } - 45 | - 46 | /** - 47 | * Perform local user authentication - 48 | */ - 49 | export async function loginWithLocalUser( - 50 | page: Page, - 51 | credentials: LoginCredentials, - 52 | baseUrl: string = 'http://localhost' - 53 | ): Promise { - 54 | await page.goto(`${baseUrl}`); - 55 | - 56 | // Wait for identity check overlay - 57 | await page.waitForSelector('[data-testid="identity-check-overlay"]', { timeout: 5000 }); - 58 | - 59 | // Click "Local User" tab if it exists - 60 | const localUserTab = page.locator('[data-testid="local-user-tab"]'); - 61 | if (await localUserTab.isVisible()) { - 62 | await localUserTab.click(); - 63 | } - 64 | - 65 | // Fill in username -> 66 | await page.fill('[data-testid="local-username-input"]', credentials.username); - | ^ Error: page.fill: Test timeout of 30000ms exceeded. - 67 | - 68 | // Fill in password - 69 | await page.fill('[data-testid="local-password-input"]', credentials.password); - 70 | - 71 | // Click login button - 72 | await page.click('[data-testid="local-login-submit"]'); - 73 | - 74 | // Wait for navigation to inventory page - 75 | await page.waitForURL(`${baseUrl}/*`, { waitUntil: 'networkidle' }); - 76 | } - 77 | - 78 | /** - 79 | * Logout from the application - 80 | */ - 81 | export async function logout(page: Page, baseUrl: string = 'http://localhost'): Promise { - 82 | // Click logout button or menu - 83 | const logoutButton = page.locator('[data-testid="logout-button"]'); - 84 | if (await logoutButton.isVisible()) { - 85 | await logoutButton.click(); - 86 | } else { - 87 | // Try finding it in menu - 88 | const menu = page.locator('[data-testid="user-menu"]'); - 89 | if (await menu.isVisible()) { - 90 | await menu.click(); - 91 | await page.click('[data-testid="logout-menu-item"]'); - 92 | } - 93 | } - 94 | - 95 | // Wait for redirect to login - 96 | await page.waitForURL(`${baseUrl}/*`, { timeout: 5000 }); - 97 | } - 98 | - 99 | /** - 100 | * Get authentication token from local storage - 101 | */ - 102 | export async function getAuthToken(page: Page): Promise { - 103 | const token = await page.evaluate(() => { - 104 | return localStorage.getItem('auth_token') || sessionStorage.getItem('auth_token'); - 105 | }); - 106 | return token; - 107 | } - 108 | - 109 | /** - 110 | * Set authentication token in local storage - 111 | */ - 112 | export async function setAuthToken(page: Page, token: string): Promise { - 113 | await page.evaluate((t) => { - 114 | localStorage.setItem('auth_token', t); - 115 | }, token); - 116 | } - 117 | - 118 | /** - 119 | * Clear authentication data - 120 | */ - 121 | export async function clearAuth(page: Page): Promise { - 122 | await page.evaluate(() => { - 123 | localStorage.removeItem('auth_token'); - 124 | sessionStorage.removeItem('auth_token'); - 125 | localStorage.removeItem('user_data'); - 126 | sessionStorage.removeItem('user_data'); - 127 | }); - 128 | } - 129 | - 130 | /** - 131 | * Check if user is authenticated - 132 | */ - 133 | export async function isAuthenticated(page: Page): Promise { - 134 | const token = await getAuthToken(page); - 135 | return !!token; - 136 | } - 137 | - 138 | /** - 139 | * Get current user info from page - 140 | */ - 141 | export async function getCurrentUser(page: Page): Promise<{ username: string; email: string } | null> { - 142 | try { - 143 | const userData = await page.evaluate(() => { - 144 | const stored = localStorage.getItem('user_data') || sessionStorage.getItem('user_data'); - 145 | return stored ? JSON.parse(stored) : null; - 146 | }); - 147 | return userData; - 148 | } catch { - 149 | return null; - 150 | } - 151 | } - 152 | - 153 | /** - 154 | * Wait for authentication to complete - 155 | */ - 156 | export async function waitForAuth(page: Page, timeout: number = 5000): Promise { - 157 | // Wait for either: - 158 | // 1. Token to be in storage - 159 | // 2. Navigation to happen - 160 | // 3. Auth overlay to disappear - 161 | await page.waitForFunction( - 162 | () => { - 163 | const token = - 164 | typeof window !== 'undefined' - 165 | ? localStorage.getItem('auth_token') || sessionStorage.getItem('auth_token') - 166 | : null; -``` \ No newline at end of file diff --git a/frontend/playwright-report/data/df63ed0aadc766e5052f30f39c06940999f006f7.md b/frontend/playwright-report/data/df63ed0aadc766e5052f30f39c06940999f006f7.md deleted file mode 100644 index b629c616..00000000 --- a/frontend/playwright-report/data/df63ed0aadc766e5052f30f39c06940999f006f7.md +++ /dev/null @@ -1,197 +0,0 @@ -# Instructions - -- Following Playwright test failed. -- Explain why, be concise, respect Playwright best practices. -- Provide a snippet of code with the fix, if possible. - -# Test info - -- Name: 1-login.spec.ts >> Login Workflow >> should handle network errors gracefully -- Location: e2e/workflows/1-login.spec.ts:181:7 - -# Error details - -``` -Test timeout of 30000ms exceeded. -``` - -``` -Error: locator.fill: Test timeout of 30000ms exceeded. -Call log: - - waiting for locator('[data-testid="username-input"]') - -``` - -# Page snapshot - -```yaml -- generic [active] [ref=e1]: - - generic [ref=e3]: - - generic [ref=e4]: - - img [ref=e6] - - heading "Identity Check" [level=2] [ref=e9] - - paragraph [ref=e10]: Select operator profile or use direct login - - generic [ref=e11]: - - button "newuser user" [ref=e12] [cursor=pointer]: - - generic [ref=e13]: - - img [ref=e15] - - generic [ref=e18]: - - paragraph [ref=e19]: newuser - - paragraph [ref=e20]: user - - img [ref=e21] - - generic [ref=e23]: - - button "Enterprise" [ref=e24] [cursor=pointer]: - - img [ref=e25] - - text: Enterprise - - button "Manual Login" [ref=e28] [cursor=pointer]: - - img [ref=e29] - - text: Manual Login - - button "Open Next.js Dev Tools" [ref=e37] [cursor=pointer]: - - img [ref=e38] - - alert [ref=e41] -``` - -# Test source - -```ts - 90 | await assertions.assertUserAuthenticated(page, BASE_URL); - 91 | }); - 92 | - 93 | test('should show user identity after successful login', async ({ page }) => { - 94 | await auth.loginWithLocalUser(page, LOCAL_USERS.admin, BASE_URL); - 95 | - 96 | // Check that user info is displayed - 97 | const userDisplay = page.locator('[data-testid="user-display"]'); - 98 | await expect(userDisplay).toBeVisible(); - 99 | - 100 | // Verify username is shown - 101 | const username = await userDisplay.textContent(); - 102 | expect(username).toContain(LOCAL_USERS.admin.username); - 103 | }); - 104 | - 105 | test('should maintain session across page reloads', async ({ page }) => { - 106 | await auth.loginWithLocalUser(page, LOCAL_USERS.admin, BASE_URL); - 107 | await assertions.assertUserAuthenticated(page, BASE_URL); - 108 | - 109 | // Reload page - 110 | await page.reload(); - 111 | - 112 | // Should still be authenticated - 113 | const token = await auth.getAuthToken(page); - 114 | expect(token).toBeTruthy(); - 115 | - 116 | // Should not be on login page - 117 | await expect(page).not.toHaveURL(BASE_URL); - 118 | }); - 119 | - 120 | test('should support logout functionality', async ({ page }) => { - 121 | // Login first - 122 | await auth.loginWithLocalUser(page, LOCAL_USERS.admin, BASE_URL); - 123 | await assertions.assertUserAuthenticated(page, BASE_URL); - 124 | - 125 | // Logout - 126 | await auth.logout(page, BASE_URL); - 127 | - 128 | // Should be back on login page - 129 | const identityOverlay = page.locator('[data-testid="identity-check-overlay"]'); - 130 | await expect(identityOverlay).toBeVisible({ timeout: 5000 }); - 131 | - 132 | // Token should be cleared - 133 | const token = await auth.getAuthToken(page); - 134 | expect(token).toBeFalsy(); - 135 | }); - 136 | - 137 | test('should show admin button for admin users', async ({ page }) => { - 138 | await auth.loginWithLocalUser(page, LOCAL_USERS.admin, BASE_URL); - 139 | await assertions.assertUserAuthenticated(page, BASE_URL); - 140 | - 141 | // Admin button should be visible - 142 | const adminButton = page.locator('[data-testid="admin-button"]'); - 143 | await expect(adminButton).toBeVisible(); - 144 | }); - 145 | - 146 | test('should allow switching between LDAP and local login tabs', async ({ page }) => { - 147 | const ldapTab = page.locator('[data-testid="ldap-login-tab"]'); - 148 | const localTab = page.locator('[data-testid="local-login-tab"]'); - 149 | - 150 | // Start on LDAP tab - 151 | let ldapForm = page.locator('[data-testid="ldap-login-form"]'); - 152 | await expect(ldapForm).toBeVisible(); - 153 | - 154 | // Switch to local - 155 | await localTab.click(); - 156 | ldapForm = page.locator('[data-testid="ldap-login-form"]'); - 157 | await expect(ldapForm).not.toBeVisible(); - 158 | - 159 | // Switch back to LDAP - 160 | await ldapTab.click(); - 161 | ldapForm = page.locator('[data-testid="ldap-login-form"]'); - 162 | await expect(ldapForm).toBeVisible(); - 163 | }); - 164 | - 165 | test('should remember login preference on revisit', async ({ page, context }) => { - 166 | // Login with local user - 167 | await auth.loginWithLocalUser(page, LOCAL_USERS.admin, BASE_URL); - 168 | - 169 | // Close and reopen browser - 170 | await page.close(); - 171 | const newPage = await context.newPage(); - 172 | await newPage.goto(BASE_URL); - 173 | - 174 | // Should still be authenticated - 175 | const authenticated = await auth.isAuthenticated(newPage); - 176 | expect(authenticated).toBe(true); - 177 | - 178 | await newPage.close(); - 179 | }); - 180 | - 181 | test('should handle network errors gracefully', async ({ page }) => { - 182 | // Enable offline mode - 183 | await page.context().setOffline(true); - 184 | - 185 | // Try to login - 186 | const usernameInput = page.locator('[data-testid="username-input"]'); - 187 | const passwordInput = page.locator('[data-testid="password-input"]'); - 188 | const submitButton = page.locator('[data-testid="login-submit"]'); - 189 | -> 190 | await usernameInput.fill(LOCAL_USERS.admin.username); - | ^ Error: locator.fill: Test timeout of 30000ms exceeded. - 191 | await passwordInput.fill(LOCAL_USERS.admin.password); - 192 | await submitButton.click(); - 193 | - 194 | // Should show network error - 195 | const errorToast = page.locator('[data-testid="toast-error"]'); - 196 | await expect(errorToast).toBeVisible({ timeout: 5000 }); - 197 | - 198 | // Re-enable network - 199 | await page.context().setOffline(false); - 200 | }); - 201 | - 202 | test('should display user list in local login tab', async ({ page }) => { - 203 | const localTab = page.locator('[data-testid="local-login-tab"]'); - 204 | await localTab.click(); - 205 | - 206 | // User list should be visible - 207 | const userList = page.locator('[data-testid="user-list"]'); - 208 | await expect(userList).toBeVisible(); - 209 | - 210 | // Should have at least one user - 211 | const userItems = page.locator('[data-testid="user-list-item"]'); - 212 | expect(await userItems.count()).toBeGreaterThan(0); - 213 | }); - 214 | - 215 | test('should auto-login when clicking user from list', async ({ page }) => { - 216 | const localTab = page.locator('[data-testid="local-login-tab"]'); - 217 | await localTab.click(); - 218 | - 219 | // Click first user in list - 220 | const firstUser = page.locator('[data-testid="user-list-item"]').first(); - 221 | await firstUser.click(); - 222 | - 223 | // Password input should be focused - 224 | const passwordInput = page.locator('[data-testid="local-password-input"]'); - 225 | await expect(passwordInput).toBeFocused(); - 226 | }); - 227 | }); - 228 | -``` \ No newline at end of file diff --git a/frontend/playwright-report/data/e2fc9fb7628901bc2b220788e3abb82ab1709e5f.md b/frontend/playwright-report/data/e2fc9fb7628901bc2b220788e3abb82ab1709e5f.md deleted file mode 100644 index e970807f..00000000 --- a/frontend/playwright-report/data/e2fc9fb7628901bc2b220788e3abb82ab1709e5f.md +++ /dev/null @@ -1,224 +0,0 @@ -# Instructions - -- Following Playwright test failed. -- Explain why, be concise, respect Playwright best practices. -- Provide a snippet of code with the fix, if possible. - -# Test info - -- Name: 1-login.spec.ts >> Login Workflow >> should maintain session across page reloads -- Location: e2e/workflows/1-login.spec.ts:105:7 - -# Error details - -``` -Test timeout of 30000ms exceeded. -``` - -``` -Error: page.fill: Test timeout of 30000ms exceeded. -Call log: - - waiting for locator('[data-testid="local-username-input"]') - -``` - -# Page snapshot - -```yaml -- generic [active] [ref=e1]: - - generic [ref=e3]: - - generic [ref=e4]: - - img [ref=e6] - - heading "Identity Check" [level=2] [ref=e9] - - paragraph [ref=e10]: Select operator profile or use direct login - - generic [ref=e11]: - - button "newuser user" [ref=e12] [cursor=pointer]: - - generic [ref=e13]: - - img [ref=e15] - - generic [ref=e18]: - - paragraph [ref=e19]: newuser - - paragraph [ref=e20]: user - - img [ref=e21] - - generic [ref=e23]: - - button "Enterprise" [ref=e24] [cursor=pointer]: - - img [ref=e25] - - text: Enterprise - - button "Manual Login" [ref=e28] [cursor=pointer]: - - img [ref=e29] - - text: Manual Login - - button "Open Next.js Dev Tools" [ref=e37] [cursor=pointer]: - - img [ref=e38] - - alert [ref=e41] -``` - -# Test source - -```ts - 1 | import { Page, BrowserContext } from '@playwright/test'; - 2 | - 3 | /** - 4 | * Authentication fixture for E2E tests - 5 | * Handles login flows and session management - 6 | */ - 7 | - 8 | export interface LoginCredentials { - 9 | username: string; - 10 | password: string; - 11 | } - 12 | - 13 | export interface AuthSession { - 14 | token: string; - 15 | userId: string; - 16 | email: string; - 17 | isAdmin: boolean; - 18 | } - 19 | - 20 | /** - 21 | * Perform LDAP authentication - 22 | */ - 23 | export async function loginWithLdap( - 24 | page: Page, - 25 | credentials: LoginCredentials, - 26 | baseUrl: string = 'http://localhost' - 27 | ): Promise { - 28 | await page.goto(`${baseUrl}`); - 29 | - 30 | // Wait for login form to appear - 31 | await page.waitForSelector('[data-testid="login-form"]', { timeout: 5000 }); - 32 | - 33 | // Fill in username - 34 | await page.fill('[data-testid="username-input"]', credentials.username); - 35 | - 36 | // Fill in password - 37 | await page.fill('[data-testid="password-input"]', credentials.password); - 38 | - 39 | // Click login button - 40 | await page.click('[data-testid="login-submit"]'); - 41 | - 42 | // Wait for navigation to inventory page - 43 | await page.waitForURL(`${baseUrl}/*`, { waitUntil: 'networkidle' }); - 44 | } - 45 | - 46 | /** - 47 | * Perform local user authentication - 48 | */ - 49 | export async function loginWithLocalUser( - 50 | page: Page, - 51 | credentials: LoginCredentials, - 52 | baseUrl: string = 'http://localhost' - 53 | ): Promise { - 54 | await page.goto(`${baseUrl}`); - 55 | - 56 | // Wait for identity check overlay - 57 | await page.waitForSelector('[data-testid="identity-check-overlay"]', { timeout: 5000 }); - 58 | - 59 | // Click "Local User" tab if it exists - 60 | const localUserTab = page.locator('[data-testid="local-user-tab"]'); - 61 | if (await localUserTab.isVisible()) { - 62 | await localUserTab.click(); - 63 | } - 64 | - 65 | // Fill in username -> 66 | await page.fill('[data-testid="local-username-input"]', credentials.username); - | ^ Error: page.fill: Test timeout of 30000ms exceeded. - 67 | - 68 | // Fill in password - 69 | await page.fill('[data-testid="local-password-input"]', credentials.password); - 70 | - 71 | // Click login button - 72 | await page.click('[data-testid="local-login-submit"]'); - 73 | - 74 | // Wait for navigation to inventory page - 75 | await page.waitForURL(`${baseUrl}/*`, { waitUntil: 'networkidle' }); - 76 | } - 77 | - 78 | /** - 79 | * Logout from the application - 80 | */ - 81 | export async function logout(page: Page, baseUrl: string = 'http://localhost'): Promise { - 82 | // Click logout button or menu - 83 | const logoutButton = page.locator('[data-testid="logout-button"]'); - 84 | if (await logoutButton.isVisible()) { - 85 | await logoutButton.click(); - 86 | } else { - 87 | // Try finding it in menu - 88 | const menu = page.locator('[data-testid="user-menu"]'); - 89 | if (await menu.isVisible()) { - 90 | await menu.click(); - 91 | await page.click('[data-testid="logout-menu-item"]'); - 92 | } - 93 | } - 94 | - 95 | // Wait for redirect to login - 96 | await page.waitForURL(`${baseUrl}/*`, { timeout: 5000 }); - 97 | } - 98 | - 99 | /** - 100 | * Get authentication token from local storage - 101 | */ - 102 | export async function getAuthToken(page: Page): Promise { - 103 | const token = await page.evaluate(() => { - 104 | return localStorage.getItem('auth_token') || sessionStorage.getItem('auth_token'); - 105 | }); - 106 | return token; - 107 | } - 108 | - 109 | /** - 110 | * Set authentication token in local storage - 111 | */ - 112 | export async function setAuthToken(page: Page, token: string): Promise { - 113 | await page.evaluate((t) => { - 114 | localStorage.setItem('auth_token', t); - 115 | }, token); - 116 | } - 117 | - 118 | /** - 119 | * Clear authentication data - 120 | */ - 121 | export async function clearAuth(page: Page): Promise { - 122 | await page.evaluate(() => { - 123 | localStorage.removeItem('auth_token'); - 124 | sessionStorage.removeItem('auth_token'); - 125 | localStorage.removeItem('user_data'); - 126 | sessionStorage.removeItem('user_data'); - 127 | }); - 128 | } - 129 | - 130 | /** - 131 | * Check if user is authenticated - 132 | */ - 133 | export async function isAuthenticated(page: Page): Promise { - 134 | const token = await getAuthToken(page); - 135 | return !!token; - 136 | } - 137 | - 138 | /** - 139 | * Get current user info from page - 140 | */ - 141 | export async function getCurrentUser(page: Page): Promise<{ username: string; email: string } | null> { - 142 | try { - 143 | const userData = await page.evaluate(() => { - 144 | const stored = localStorage.getItem('user_data') || sessionStorage.getItem('user_data'); - 145 | return stored ? JSON.parse(stored) : null; - 146 | }); - 147 | return userData; - 148 | } catch { - 149 | return null; - 150 | } - 151 | } - 152 | - 153 | /** - 154 | * Wait for authentication to complete - 155 | */ - 156 | export async function waitForAuth(page: Page, timeout: number = 5000): Promise { - 157 | // Wait for either: - 158 | // 1. Token to be in storage - 159 | // 2. Navigation to happen - 160 | // 3. Auth overlay to disappear - 161 | await page.waitForFunction( - 162 | () => { - 163 | const token = - 164 | typeof window !== 'undefined' - 165 | ? localStorage.getItem('auth_token') || sessionStorage.getItem('auth_token') - 166 | : null; -``` \ No newline at end of file diff --git a/frontend/playwright-report/data/e5e30d817d86bfb02dc036758e13ff2f70ee0d1b.md b/frontend/playwright-report/data/e5e30d817d86bfb02dc036758e13ff2f70ee0d1b.md deleted file mode 100644 index 128321ad..00000000 --- a/frontend/playwright-report/data/e5e30d817d86bfb02dc036758e13ff2f70ee0d1b.md +++ /dev/null @@ -1,187 +0,0 @@ -# Instructions - -- Following Playwright test failed. -- Explain why, be concise, respect Playwright best practices. -- Provide a snippet of code with the fix, if possible. - -# Test info - -- Name: 1-login.spec.ts >> Login Workflow >> should successfully login with valid LDAP credentials -- Location: e2e/workflows/1-login.spec.ts:52:7 - -# Error details - -``` -TimeoutError: page.waitForSelector: Timeout 5000ms exceeded. -Call log: - - waiting for locator('[data-testid="login-form"]') to be visible - - waiting for" http://localhost:8917/login" navigation to finish... - - navigated to "http://localhost:8917/login" - -``` - -# Page snapshot - -```yaml -- generic [active] [ref=e1]: - - generic [ref=e3]: - - generic [ref=e4]: - - img [ref=e6] - - heading "Identity Check" [level=2] [ref=e9] - - paragraph [ref=e10]: Select operator profile or use direct login - - generic [ref=e11]: - - button "newuser user" [ref=e12] [cursor=pointer]: - - generic [ref=e13]: - - img [ref=e15] - - generic [ref=e18]: - - paragraph [ref=e19]: newuser - - paragraph [ref=e20]: user - - img [ref=e21] - - generic [ref=e23]: - - button "Enterprise" [ref=e24] [cursor=pointer]: - - img [ref=e25] - - text: Enterprise - - button "Manual Login" [ref=e28] [cursor=pointer]: - - img [ref=e29] - - text: Manual Login - - button "Open Next.js Dev Tools" [ref=e37] [cursor=pointer]: - - img [ref=e38] - - alert [ref=e41] -``` - -# Test source - -```ts - 1 | import { Page, BrowserContext } from '@playwright/test'; - 2 | - 3 | /** - 4 | * Authentication fixture for E2E tests - 5 | * Handles login flows and session management - 6 | */ - 7 | - 8 | export interface LoginCredentials { - 9 | username: string; - 10 | password: string; - 11 | } - 12 | - 13 | export interface AuthSession { - 14 | token: string; - 15 | userId: string; - 16 | email: string; - 17 | isAdmin: boolean; - 18 | } - 19 | - 20 | /** - 21 | * Perform LDAP authentication - 22 | */ - 23 | export async function loginWithLdap( - 24 | page: Page, - 25 | credentials: LoginCredentials, - 26 | baseUrl: string = 'http://localhost' - 27 | ): Promise { - 28 | await page.goto(`${baseUrl}`); - 29 | - 30 | // Wait for login form to appear -> 31 | await page.waitForSelector('[data-testid="login-form"]', { timeout: 5000 }); - | ^ TimeoutError: page.waitForSelector: Timeout 5000ms exceeded. - 32 | - 33 | // Fill in username - 34 | await page.fill('[data-testid="username-input"]', credentials.username); - 35 | - 36 | // Fill in password - 37 | await page.fill('[data-testid="password-input"]', credentials.password); - 38 | - 39 | // Click login button - 40 | await page.click('[data-testid="login-submit"]'); - 41 | - 42 | // Wait for navigation to inventory page - 43 | await page.waitForURL(`${baseUrl}/*`, { waitUntil: 'networkidle' }); - 44 | } - 45 | - 46 | /** - 47 | * Perform local user authentication - 48 | */ - 49 | export async function loginWithLocalUser( - 50 | page: Page, - 51 | credentials: LoginCredentials, - 52 | baseUrl: string = 'http://localhost' - 53 | ): Promise { - 54 | await page.goto(`${baseUrl}`); - 55 | - 56 | // Wait for identity check overlay - 57 | await page.waitForSelector('[data-testid="identity-check-overlay"]', { timeout: 5000 }); - 58 | - 59 | // Click "Local User" tab if it exists - 60 | const localUserTab = page.locator('[data-testid="local-user-tab"]'); - 61 | if (await localUserTab.isVisible()) { - 62 | await localUserTab.click(); - 63 | } - 64 | - 65 | // Fill in username - 66 | await page.fill('[data-testid="local-username-input"]', credentials.username); - 67 | - 68 | // Fill in password - 69 | await page.fill('[data-testid="local-password-input"]', credentials.password); - 70 | - 71 | // Click login button - 72 | await page.click('[data-testid="local-login-submit"]'); - 73 | - 74 | // Wait for navigation to inventory page - 75 | await page.waitForURL(`${baseUrl}/*`, { waitUntil: 'networkidle' }); - 76 | } - 77 | - 78 | /** - 79 | * Logout from the application - 80 | */ - 81 | export async function logout(page: Page, baseUrl: string = 'http://localhost'): Promise { - 82 | // Click logout button or menu - 83 | const logoutButton = page.locator('[data-testid="logout-button"]'); - 84 | if (await logoutButton.isVisible()) { - 85 | await logoutButton.click(); - 86 | } else { - 87 | // Try finding it in menu - 88 | const menu = page.locator('[data-testid="user-menu"]'); - 89 | if (await menu.isVisible()) { - 90 | await menu.click(); - 91 | await page.click('[data-testid="logout-menu-item"]'); - 92 | } - 93 | } - 94 | - 95 | // Wait for redirect to login - 96 | await page.waitForURL(`${baseUrl}/*`, { timeout: 5000 }); - 97 | } - 98 | - 99 | /** - 100 | * Get authentication token from local storage - 101 | */ - 102 | export async function getAuthToken(page: Page): Promise { - 103 | const token = await page.evaluate(() => { - 104 | return localStorage.getItem('auth_token') || sessionStorage.getItem('auth_token'); - 105 | }); - 106 | return token; - 107 | } - 108 | - 109 | /** - 110 | * Set authentication token in local storage - 111 | */ - 112 | export async function setAuthToken(page: Page, token: string): Promise { - 113 | await page.evaluate((t) => { - 114 | localStorage.setItem('auth_token', t); - 115 | }, token); - 116 | } - 117 | - 118 | /** - 119 | * Clear authentication data - 120 | */ - 121 | export async function clearAuth(page: Page): Promise { - 122 | await page.evaluate(() => { - 123 | localStorage.removeItem('auth_token'); - 124 | sessionStorage.removeItem('auth_token'); - 125 | localStorage.removeItem('user_data'); - 126 | sessionStorage.removeItem('user_data'); - 127 | }); - 128 | } - 129 | - 130 | /** - 131 | * Check if user is authenticated -``` \ No newline at end of file diff --git a/frontend/playwright-report/data/e9f5853c90bbe234f26f48d5ffd6b063536c41e3.md b/frontend/playwright-report/data/e9f5853c90bbe234f26f48d5ffd6b063536c41e3.md deleted file mode 100644 index 888f9e98..00000000 --- a/frontend/playwright-report/data/e9f5853c90bbe234f26f48d5ffd6b063536c41e3.md +++ /dev/null @@ -1,224 +0,0 @@ -# Instructions - -- Following Playwright test failed. -- Explain why, be concise, respect Playwright best practices. -- Provide a snippet of code with the fix, if possible. - -# Test info - -- Name: 1-login.spec.ts >> Login Workflow >> should show admin button for admin users -- Location: e2e/workflows/1-login.spec.ts:137:7 - -# Error details - -``` -Test timeout of 30000ms exceeded. -``` - -``` -Error: page.fill: Test timeout of 30000ms exceeded. -Call log: - - waiting for locator('[data-testid="local-username-input"]') - -``` - -# Page snapshot - -```yaml -- generic [active] [ref=e1]: - - generic [ref=e3]: - - generic [ref=e4]: - - img [ref=e6] - - heading "Identity Check" [level=2] [ref=e9] - - paragraph [ref=e10]: Select operator profile or use direct login - - generic [ref=e11]: - - button "newuser user" [ref=e12] [cursor=pointer]: - - generic [ref=e13]: - - img [ref=e15] - - generic [ref=e18]: - - paragraph [ref=e19]: newuser - - paragraph [ref=e20]: user - - img [ref=e21] - - generic [ref=e23]: - - button "Enterprise" [ref=e24] [cursor=pointer]: - - img [ref=e25] - - text: Enterprise - - button "Manual Login" [ref=e28] [cursor=pointer]: - - img [ref=e29] - - text: Manual Login - - button "Open Next.js Dev Tools" [ref=e37] [cursor=pointer]: - - img [ref=e38] - - alert [ref=e41] -``` - -# Test source - -```ts - 1 | import { Page, BrowserContext } from '@playwright/test'; - 2 | - 3 | /** - 4 | * Authentication fixture for E2E tests - 5 | * Handles login flows and session management - 6 | */ - 7 | - 8 | export interface LoginCredentials { - 9 | username: string; - 10 | password: string; - 11 | } - 12 | - 13 | export interface AuthSession { - 14 | token: string; - 15 | userId: string; - 16 | email: string; - 17 | isAdmin: boolean; - 18 | } - 19 | - 20 | /** - 21 | * Perform LDAP authentication - 22 | */ - 23 | export async function loginWithLdap( - 24 | page: Page, - 25 | credentials: LoginCredentials, - 26 | baseUrl: string = 'http://localhost' - 27 | ): Promise { - 28 | await page.goto(`${baseUrl}`); - 29 | - 30 | // Wait for login form to appear - 31 | await page.waitForSelector('[data-testid="login-form"]', { timeout: 5000 }); - 32 | - 33 | // Fill in username - 34 | await page.fill('[data-testid="username-input"]', credentials.username); - 35 | - 36 | // Fill in password - 37 | await page.fill('[data-testid="password-input"]', credentials.password); - 38 | - 39 | // Click login button - 40 | await page.click('[data-testid="login-submit"]'); - 41 | - 42 | // Wait for navigation to inventory page - 43 | await page.waitForURL(`${baseUrl}/*`, { waitUntil: 'networkidle' }); - 44 | } - 45 | - 46 | /** - 47 | * Perform local user authentication - 48 | */ - 49 | export async function loginWithLocalUser( - 50 | page: Page, - 51 | credentials: LoginCredentials, - 52 | baseUrl: string = 'http://localhost' - 53 | ): Promise { - 54 | await page.goto(`${baseUrl}`); - 55 | - 56 | // Wait for identity check overlay - 57 | await page.waitForSelector('[data-testid="identity-check-overlay"]', { timeout: 5000 }); - 58 | - 59 | // Click "Local User" tab if it exists - 60 | const localUserTab = page.locator('[data-testid="local-user-tab"]'); - 61 | if (await localUserTab.isVisible()) { - 62 | await localUserTab.click(); - 63 | } - 64 | - 65 | // Fill in username -> 66 | await page.fill('[data-testid="local-username-input"]', credentials.username); - | ^ Error: page.fill: Test timeout of 30000ms exceeded. - 67 | - 68 | // Fill in password - 69 | await page.fill('[data-testid="local-password-input"]', credentials.password); - 70 | - 71 | // Click login button - 72 | await page.click('[data-testid="local-login-submit"]'); - 73 | - 74 | // Wait for navigation to inventory page - 75 | await page.waitForURL(`${baseUrl}/*`, { waitUntil: 'networkidle' }); - 76 | } - 77 | - 78 | /** - 79 | * Logout from the application - 80 | */ - 81 | export async function logout(page: Page, baseUrl: string = 'http://localhost'): Promise { - 82 | // Click logout button or menu - 83 | const logoutButton = page.locator('[data-testid="logout-button"]'); - 84 | if (await logoutButton.isVisible()) { - 85 | await logoutButton.click(); - 86 | } else { - 87 | // Try finding it in menu - 88 | const menu = page.locator('[data-testid="user-menu"]'); - 89 | if (await menu.isVisible()) { - 90 | await menu.click(); - 91 | await page.click('[data-testid="logout-menu-item"]'); - 92 | } - 93 | } - 94 | - 95 | // Wait for redirect to login - 96 | await page.waitForURL(`${baseUrl}/*`, { timeout: 5000 }); - 97 | } - 98 | - 99 | /** - 100 | * Get authentication token from local storage - 101 | */ - 102 | export async function getAuthToken(page: Page): Promise { - 103 | const token = await page.evaluate(() => { - 104 | return localStorage.getItem('auth_token') || sessionStorage.getItem('auth_token'); - 105 | }); - 106 | return token; - 107 | } - 108 | - 109 | /** - 110 | * Set authentication token in local storage - 111 | */ - 112 | export async function setAuthToken(page: Page, token: string): Promise { - 113 | await page.evaluate((t) => { - 114 | localStorage.setItem('auth_token', t); - 115 | }, token); - 116 | } - 117 | - 118 | /** - 119 | * Clear authentication data - 120 | */ - 121 | export async function clearAuth(page: Page): Promise { - 122 | await page.evaluate(() => { - 123 | localStorage.removeItem('auth_token'); - 124 | sessionStorage.removeItem('auth_token'); - 125 | localStorage.removeItem('user_data'); - 126 | sessionStorage.removeItem('user_data'); - 127 | }); - 128 | } - 129 | - 130 | /** - 131 | * Check if user is authenticated - 132 | */ - 133 | export async function isAuthenticated(page: Page): Promise { - 134 | const token = await getAuthToken(page); - 135 | return !!token; - 136 | } - 137 | - 138 | /** - 139 | * Get current user info from page - 140 | */ - 141 | export async function getCurrentUser(page: Page): Promise<{ username: string; email: string } | null> { - 142 | try { - 143 | const userData = await page.evaluate(() => { - 144 | const stored = localStorage.getItem('user_data') || sessionStorage.getItem('user_data'); - 145 | return stored ? JSON.parse(stored) : null; - 146 | }); - 147 | return userData; - 148 | } catch { - 149 | return null; - 150 | } - 151 | } - 152 | - 153 | /** - 154 | * Wait for authentication to complete - 155 | */ - 156 | export async function waitForAuth(page: Page, timeout: number = 5000): Promise { - 157 | // Wait for either: - 158 | // 1. Token to be in storage - 159 | // 2. Navigation to happen - 160 | // 3. Auth overlay to disappear - 161 | await page.waitForFunction( - 162 | () => { - 163 | const token = - 164 | typeof window !== 'undefined' - 165 | ? localStorage.getItem('auth_token') || sessionStorage.getItem('auth_token') - 166 | : null; -``` \ No newline at end of file diff --git a/frontend/playwright-report/data/f29622b7ac30dae4a89cd725099a3fdfddd92bab.md b/frontend/playwright-report/data/f29622b7ac30dae4a89cd725099a3fdfddd92bab.md deleted file mode 100644 index 31d38f19..00000000 --- a/frontend/playwright-report/data/f29622b7ac30dae4a89cd725099a3fdfddd92bab.md +++ /dev/null @@ -1,174 +0,0 @@ -# Instructions - -- Following Playwright test failed. -- Explain why, be concise, respect Playwright best practices. -- Provide a snippet of code with the fix, if possible. - -# Test info - -- Name: 1-login.spec.ts >> Login Workflow >> should display user list in local login tab -- Location: e2e/workflows/1-login.spec.ts:202:7 - -# Error details - -``` -Error: expect(received).toBeGreaterThan(expected) - -Expected: > 0 -Received: 0 -``` - -# Page snapshot - -```yaml -- generic [ref=e1]: - - generic [ref=e3]: - - generic [ref=e4]: - - img [ref=e6] - - heading "Identity Check" [level=2] [ref=e9] - - paragraph [ref=e10]: Select operator profile or use direct login - - generic [ref=e12]: - - generic [ref=e13]: - - button [ref=e14] [cursor=pointer]: - - img [ref=e15] - - generic [ref=e18]: - - paragraph [ref=e19]: Logging in as - - paragraph [ref=e20]: Manual Input - - generic [ref=e21]: - - text: Username - - generic [ref=e22]: - - img [ref=e23] - - textbox "Admin" [ref=e26] - - generic [ref=e27]: - - text: Password - - generic [ref=e28]: - - img [ref=e29] - - textbox "Enter password" [active] [ref=e32] - - button "Verify Identity" [ref=e33] [cursor=pointer] - - button "Open Next.js Dev Tools" [ref=e39] [cursor=pointer]: - - img [ref=e40] - - alert [ref=e43] -``` - -# Test source - -```ts - 112 | // Should still be authenticated - 113 | const token = await auth.getAuthToken(page); - 114 | expect(token).toBeTruthy(); - 115 | - 116 | // Should not be on login page - 117 | await expect(page).not.toHaveURL(BASE_URL); - 118 | }); - 119 | - 120 | test('should support logout functionality', async ({ page }) => { - 121 | // Login first - 122 | await auth.loginWithLocalUser(page, LOCAL_USERS.admin, BASE_URL); - 123 | await assertions.assertUserAuthenticated(page, BASE_URL); - 124 | - 125 | // Logout - 126 | await auth.logout(page, BASE_URL); - 127 | - 128 | // Should be back on login page - 129 | const identityOverlay = page.locator('[data-testid="identity-check-overlay"]'); - 130 | await expect(identityOverlay).toBeVisible({ timeout: 5000 }); - 131 | - 132 | // Token should be cleared - 133 | const token = await auth.getAuthToken(page); - 134 | expect(token).toBeFalsy(); - 135 | }); - 136 | - 137 | test('should show admin button for admin users', async ({ page }) => { - 138 | await auth.loginWithLocalUser(page, LOCAL_USERS.admin, BASE_URL); - 139 | await assertions.assertUserAuthenticated(page, BASE_URL); - 140 | - 141 | // Admin button should be visible - 142 | const adminButton = page.locator('[data-testid="admin-button"]'); - 143 | await expect(adminButton).toBeVisible(); - 144 | }); - 145 | - 146 | test('should allow switching between LDAP and local login tabs', async ({ page }) => { - 147 | const ldapTab = page.locator('[data-testid="ldap-login-tab"]'); - 148 | const localTab = page.locator('[data-testid="local-login-tab"]'); - 149 | - 150 | // Start on LDAP tab - 151 | let ldapForm = page.locator('[data-testid="ldap-login-form"]'); - 152 | await expect(ldapForm).toBeVisible(); - 153 | - 154 | // Switch to local - 155 | await localTab.click(); - 156 | ldapForm = page.locator('[data-testid="ldap-login-form"]'); - 157 | await expect(ldapForm).not.toBeVisible(); - 158 | - 159 | // Switch back to LDAP - 160 | await ldapTab.click(); - 161 | ldapForm = page.locator('[data-testid="ldap-login-form"]'); - 162 | await expect(ldapForm).toBeVisible(); - 163 | }); - 164 | - 165 | test('should remember login preference on revisit', async ({ page, context }) => { - 166 | // Login with local user - 167 | await auth.loginWithLocalUser(page, LOCAL_USERS.admin, BASE_URL); - 168 | - 169 | // Close and reopen browser - 170 | await page.close(); - 171 | const newPage = await context.newPage(); - 172 | await newPage.goto(BASE_URL); - 173 | - 174 | // Should still be authenticated - 175 | const authenticated = await auth.isAuthenticated(newPage); - 176 | expect(authenticated).toBe(true); - 177 | - 178 | await newPage.close(); - 179 | }); - 180 | - 181 | test('should handle network errors gracefully', async ({ page }) => { - 182 | // Enable offline mode - 183 | await page.context().setOffline(true); - 184 | - 185 | // Try to login - 186 | const usernameInput = page.locator('[data-testid="username-input"]'); - 187 | const passwordInput = page.locator('[data-testid="password-input"]'); - 188 | const submitButton = page.locator('[data-testid="login-submit"]'); - 189 | - 190 | await usernameInput.fill(LOCAL_USERS.admin.username); - 191 | await passwordInput.fill(LOCAL_USERS.admin.password); - 192 | await submitButton.click(); - 193 | - 194 | // Should show network error - 195 | const errorToast = page.locator('[data-testid="toast-error"]'); - 196 | await expect(errorToast).toBeVisible({ timeout: 5000 }); - 197 | - 198 | // Re-enable network - 199 | await page.context().setOffline(false); - 200 | }); - 201 | - 202 | test('should display user list in local login tab', async ({ page }) => { - 203 | const localTab = page.locator('[data-testid="local-login-tab"]'); - 204 | await localTab.click(); - 205 | - 206 | // User list should be visible - 207 | const userList = page.locator('[data-testid="user-list"]'); - 208 | await expect(userList).toBeVisible(); - 209 | - 210 | // Should have at least one user - 211 | const userItems = page.locator('[data-testid="user-list-item"]'); -> 212 | expect(await userItems.count()).toBeGreaterThan(0); - | ^ Error: expect(received).toBeGreaterThan(expected) - 213 | }); - 214 | - 215 | test('should auto-login when clicking user from list', async ({ page }) => { - 216 | const localTab = page.locator('[data-testid="local-login-tab"]'); - 217 | await localTab.click(); - 218 | - 219 | // Click first user in list - 220 | const firstUser = page.locator('[data-testid="user-list-item"]').first(); - 221 | await firstUser.click(); - 222 | - 223 | // Password input should be focused - 224 | const passwordInput = page.locator('[data-testid="local-password-input"]'); - 225 | await expect(passwordInput).toBeFocused(); - 226 | }); - 227 | }); - 228 | -``` \ No newline at end of file diff --git a/frontend/playwright-report/data/fa35d529e1ccc0f83b98a04aad0b74476e30d06d.md b/frontend/playwright-report/data/fa35d529e1ccc0f83b98a04aad0b74476e30d06d.md deleted file mode 100644 index 69cac167..00000000 --- a/frontend/playwright-report/data/fa35d529e1ccc0f83b98a04aad0b74476e30d06d.md +++ /dev/null @@ -1,169 +0,0 @@ -# Instructions - -- Following Playwright test failed. -- Explain why, be concise, respect Playwright best practices. -- Provide a snippet of code with the fix, if possible. - -# Test info - -- Name: 1-login.spec.ts >> Login Workflow >> should auto-login when clicking user from list -- Location: e2e/workflows/1-login.spec.ts:215:7 - -# Error details - -``` -Test timeout of 30000ms exceeded. -``` - -``` -Error: locator.click: Test timeout of 30000ms exceeded. -Call log: - - waiting for locator('[data-testid="user-list-item"]').first() - -``` - -# Page snapshot - -```yaml -- generic [ref=e1]: - - generic [ref=e3]: - - generic [ref=e4]: - - img [ref=e6] - - heading "Identity Check" [level=2] [ref=e9] - - paragraph [ref=e10]: Select operator profile or use direct login - - generic [ref=e12]: - - generic [ref=e13]: - - button [ref=e14] [cursor=pointer]: - - img [ref=e15] - - generic [ref=e18]: - - paragraph [ref=e19]: Logging in as - - paragraph [ref=e20]: Manual Input - - generic [ref=e21]: - - text: Username - - generic [ref=e22]: - - img [ref=e23] - - textbox "Admin" [ref=e26] - - generic [ref=e27]: - - text: Password - - generic [ref=e28]: - - img [ref=e29] - - textbox "Enter password" [active] [ref=e32] - - button "Verify Identity" [ref=e33] [cursor=pointer] - - button "Open Next.js Dev Tools" [ref=e39] [cursor=pointer]: - - img [ref=e40] - - alert [ref=e43] -``` - -# Test source - -```ts - 121 | // Login first - 122 | await auth.loginWithLocalUser(page, LOCAL_USERS.admin, BASE_URL); - 123 | await assertions.assertUserAuthenticated(page, BASE_URL); - 124 | - 125 | // Logout - 126 | await auth.logout(page, BASE_URL); - 127 | - 128 | // Should be back on login page - 129 | const identityOverlay = page.locator('[data-testid="identity-check-overlay"]'); - 130 | await expect(identityOverlay).toBeVisible({ timeout: 5000 }); - 131 | - 132 | // Token should be cleared - 133 | const token = await auth.getAuthToken(page); - 134 | expect(token).toBeFalsy(); - 135 | }); - 136 | - 137 | test('should show admin button for admin users', async ({ page }) => { - 138 | await auth.loginWithLocalUser(page, LOCAL_USERS.admin, BASE_URL); - 139 | await assertions.assertUserAuthenticated(page, BASE_URL); - 140 | - 141 | // Admin button should be visible - 142 | const adminButton = page.locator('[data-testid="admin-button"]'); - 143 | await expect(adminButton).toBeVisible(); - 144 | }); - 145 | - 146 | test('should allow switching between LDAP and local login tabs', async ({ page }) => { - 147 | const ldapTab = page.locator('[data-testid="ldap-login-tab"]'); - 148 | const localTab = page.locator('[data-testid="local-login-tab"]'); - 149 | - 150 | // Start on LDAP tab - 151 | let ldapForm = page.locator('[data-testid="ldap-login-form"]'); - 152 | await expect(ldapForm).toBeVisible(); - 153 | - 154 | // Switch to local - 155 | await localTab.click(); - 156 | ldapForm = page.locator('[data-testid="ldap-login-form"]'); - 157 | await expect(ldapForm).not.toBeVisible(); - 158 | - 159 | // Switch back to LDAP - 160 | await ldapTab.click(); - 161 | ldapForm = page.locator('[data-testid="ldap-login-form"]'); - 162 | await expect(ldapForm).toBeVisible(); - 163 | }); - 164 | - 165 | test('should remember login preference on revisit', async ({ page, context }) => { - 166 | // Login with local user - 167 | await auth.loginWithLocalUser(page, LOCAL_USERS.admin, BASE_URL); - 168 | - 169 | // Close and reopen browser - 170 | await page.close(); - 171 | const newPage = await context.newPage(); - 172 | await newPage.goto(BASE_URL); - 173 | - 174 | // Should still be authenticated - 175 | const authenticated = await auth.isAuthenticated(newPage); - 176 | expect(authenticated).toBe(true); - 177 | - 178 | await newPage.close(); - 179 | }); - 180 | - 181 | test('should handle network errors gracefully', async ({ page }) => { - 182 | // Enable offline mode - 183 | await page.context().setOffline(true); - 184 | - 185 | // Try to login - 186 | const usernameInput = page.locator('[data-testid="username-input"]'); - 187 | const passwordInput = page.locator('[data-testid="password-input"]'); - 188 | const submitButton = page.locator('[data-testid="login-submit"]'); - 189 | - 190 | await usernameInput.fill(LOCAL_USERS.admin.username); - 191 | await passwordInput.fill(LOCAL_USERS.admin.password); - 192 | await submitButton.click(); - 193 | - 194 | // Should show network error - 195 | const errorToast = page.locator('[data-testid="toast-error"]'); - 196 | await expect(errorToast).toBeVisible({ timeout: 5000 }); - 197 | - 198 | // Re-enable network - 199 | await page.context().setOffline(false); - 200 | }); - 201 | - 202 | test('should display user list in local login tab', async ({ page }) => { - 203 | const localTab = page.locator('[data-testid="local-login-tab"]'); - 204 | await localTab.click(); - 205 | - 206 | // User list should be visible - 207 | const userList = page.locator('[data-testid="user-list"]'); - 208 | await expect(userList).toBeVisible(); - 209 | - 210 | // Should have at least one user - 211 | const userItems = page.locator('[data-testid="user-list-item"]'); - 212 | expect(await userItems.count()).toBeGreaterThan(0); - 213 | }); - 214 | - 215 | test('should auto-login when clicking user from list', async ({ page }) => { - 216 | const localTab = page.locator('[data-testid="local-login-tab"]'); - 217 | await localTab.click(); - 218 | - 219 | // Click first user in list - 220 | const firstUser = page.locator('[data-testid="user-list-item"]').first(); -> 221 | await firstUser.click(); - | ^ Error: locator.click: Test timeout of 30000ms exceeded. - 222 | - 223 | // Password input should be focused - 224 | const passwordInput = page.locator('[data-testid="local-password-input"]'); - 225 | await expect(passwordInput).toBeFocused(); - 226 | }); - 227 | }); - 228 | -``` \ No newline at end of file diff --git a/frontend/playwright-report/index.html b/frontend/playwright-report/index.html deleted file mode 100644 index aea9e1a5..00000000 --- a/frontend/playwright-report/index.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - Playwright Test Report - - - - -
- - - \ No newline at end of file