From b294a51a1e072527fb457e78fb3e55bc5368256e Mon Sep 17 00:00:00 2001 From: Daniel Bedeleanu Date: Sun, 19 Apr 2026 10:18:36 +0300 Subject: [PATCH] test: save E2E test progress - data-testid attributes added, 1/16 login tests passing --- ...9e37cd5ecf83154fb9b300824d32ab6910d760.png | Bin 0 -> 34209 bytes ...1afb044f69e204fbe127fa1dbadf98b251de8bc.md | 224 +++++++++++++++++ ...8de4149d79572f42f059e1c21d2a1b03b0e0a62.md | 224 +++++++++++++++++ ...c086a4cde2673fed46f9d88609959f3059fc36.md} | 33 ++- ...28efe50859839024d2ccf522575379c83e61131.md | 224 +++++++++++++++++ ...d536c2f96e6887e8dcdf903496bf540c20b16c.png | Bin 0 -> 25821 bytes ...d4ce422581ae27636b69d2084b159f0becc1c5.png | Bin 0 -> 34222 bytes ...55707639177d53bca85afb8b9268b02973aeb9c.md | 233 +++++++++++++++++ ...d8f14932ab704c3c314dba27be5ca5a190c6e1e.md | 187 ++++++++++++++ ...49d3316f329d7283ba0d5b666781aac30b68caa.md | 213 ++++++++++++++++ ...598dfc727318850f1aaaa533df9f623f04844a4.md | 238 ++++++++++++++++++ ...e82bdf77eafd95f45c92adac93d33895723c22a.md | 224 +++++++++++++++++ ...f63ed0aadc766e5052f30f39c06940999f006f7.md | 197 +++++++++++++++ ...2fc9fb7628901bc2b220788e3abb82ab1709e5f.md | 224 +++++++++++++++++ ...5e30d817d86bfb02dc036758e13ff2f70ee0d1b.md | 187 ++++++++++++++ ...9f5853c90bbe234f26f48d5ffd6b063536c41e3.md | 224 +++++++++++++++++ ...29622b7ac30dae4a89cd725099a3fdfddd92bab.md | 174 +++++++++++++ ...a35d529e1ccc0f83b98a04aad0b74476e30d06d.md | 169 +++++++++++++ frontend/playwright-report/index.html | 2 +- frontend/test-results/.last-run.json | 16 +- .../error-context.md | 197 +++++++++++++++ .../test-failed-1.png | Bin .../error-context.md | 238 ++++++++++++++++++ .../test-failed-1.png | Bin 0 -> 25825 bytes .../error-context.md | 169 +++++++++++++ .../test-failed-1.png | Bin 0 -> 34209 bytes .../error-context.md | 224 +++++++++++++++++ .../test-failed-1.png | Bin 0 -> 25821 bytes .../error-context.md | 233 +++++++++++++++++ .../test-failed-1.png | Bin 0 -> 34209 bytes .../error-context.md | 187 ++++++++++++++ .../test-failed-1.png | Bin 0 -> 25825 bytes .../error-context.md | 224 +++++++++++++++++ .../test-failed-1.png | Bin 0 -> 25825 bytes .../error-context.md | 213 ++++++++++++++++ .../test-failed-1.png | Bin 0 -> 25825 bytes .../error-context.md | 224 +++++++++++++++++ .../test-failed-1.png | Bin 0 -> 25825 bytes .../error-context.md | 224 +++++++++++++++++ .../test-failed-1.png | Bin 0 -> 25825 bytes .../error-context.md | 224 +++++++++++++++++ .../test-failed-1.png | Bin 0 -> 25825 bytes .../error-context.md | 187 ++++++++++++++ .../test-failed-1.png | Bin 0 -> 25825 bytes .../error-context.md | 174 +++++++++++++ .../test-failed-1.png | Bin 0 -> 34222 bytes .../error-context.md | 33 ++- .../test-failed-1.png | Bin 0 -> 25825 bytes .../error-context.md | 224 +++++++++++++++++ .../test-failed-1.png | Bin 0 -> 25825 bytes 50 files changed, 5940 insertions(+), 28 deletions(-) create mode 100644 frontend/playwright-report/data/0b9e37cd5ecf83154fb9b300824d32ab6910d760.png create mode 100644 frontend/playwright-report/data/11afb044f69e204fbe127fa1dbadf98b251de8bc.md create mode 100644 frontend/playwright-report/data/18de4149d79572f42f059e1c21d2a1b03b0e0a62.md rename frontend/playwright-report/data/{d85d52a987bf605d34ced01f09c8c6347b7c781b.md => 25c086a4cde2673fed46f9d88609959f3059fc36.md} (88%) create mode 100644 frontend/playwright-report/data/328efe50859839024d2ccf522575379c83e61131.md create mode 100644 frontend/playwright-report/data/3bd536c2f96e6887e8dcdf903496bf540c20b16c.png create mode 100644 frontend/playwright-report/data/acd4ce422581ae27636b69d2084b159f0becc1c5.png create mode 100644 frontend/playwright-report/data/b55707639177d53bca85afb8b9268b02973aeb9c.md create mode 100644 frontend/playwright-report/data/bd8f14932ab704c3c314dba27be5ca5a190c6e1e.md create mode 100644 frontend/playwright-report/data/c49d3316f329d7283ba0d5b666781aac30b68caa.md create mode 100644 frontend/playwright-report/data/c598dfc727318850f1aaaa533df9f623f04844a4.md create mode 100644 frontend/playwright-report/data/ce82bdf77eafd95f45c92adac93d33895723c22a.md create mode 100644 frontend/playwright-report/data/df63ed0aadc766e5052f30f39c06940999f006f7.md create mode 100644 frontend/playwright-report/data/e2fc9fb7628901bc2b220788e3abb82ab1709e5f.md create mode 100644 frontend/playwright-report/data/e5e30d817d86bfb02dc036758e13ff2f70ee0d1b.md create mode 100644 frontend/playwright-report/data/e9f5853c90bbe234f26f48d5ffd6b063536c41e3.md create mode 100644 frontend/playwright-report/data/f29622b7ac30dae4a89cd725099a3fdfddd92bab.md create mode 100644 frontend/playwright-report/data/fa35d529e1ccc0f83b98a04aad0b74476e30d06d.md create mode 100644 frontend/test-results/1-login-Login-Workflow-sho-0bb23-e-network-errors-gracefully-chromium/error-context.md rename frontend/test-results/{1-login-Login-Workflow-sho-ba9e9-y-check-overlay-on-app-load-chromium => 1-login-Login-Workflow-sho-0bb23-e-network-errors-gracefully-chromium}/test-failed-1.png (100%) create mode 100644 frontend/test-results/1-login-Login-Workflow-sho-1065a-n-LDAP-and-local-login-tabs-chromium/error-context.md create mode 100644 frontend/test-results/1-login-Login-Workflow-sho-1065a-n-LDAP-and-local-login-tabs-chromium/test-failed-1.png create mode 100644 frontend/test-results/1-login-Login-Workflow-sho-19d2a-hen-clicking-user-from-list-chromium/error-context.md create mode 100644 frontend/test-results/1-login-Login-Workflow-sho-19d2a-hen-clicking-user-from-list-chromium/test-failed-1.png create mode 100644 frontend/test-results/1-login-Login-Workflow-sho-1b0d7-alid-local-user-credentials-chromium/error-context.md create mode 100644 frontend/test-results/1-login-Login-Workflow-sho-1b0d7-alid-local-user-credentials-chromium/test-failed-1.png create mode 100644 frontend/test-results/1-login-Login-Workflow-sho-1def4-alid-local-user-credentials-chromium/error-context.md create mode 100644 frontend/test-results/1-login-Login-Workflow-sho-1def4-alid-local-user-credentials-chromium/test-failed-1.png create mode 100644 frontend/test-results/1-login-Login-Workflow-sho-24db1-with-valid-LDAP-credentials-chromium/error-context.md create mode 100644 frontend/test-results/1-login-Login-Workflow-sho-24db1-with-valid-LDAP-credentials-chromium/test-failed-1.png create mode 100644 frontend/test-results/1-login-Login-Workflow-sho-2cf04-session-across-page-reloads-chromium/error-context.md create mode 100644 frontend/test-results/1-login-Login-Workflow-sho-2cf04-session-across-page-reloads-chromium/test-failed-1.png create mode 100644 frontend/test-results/1-login-Login-Workflow-sho-6d596-sername-and-password-fields-chromium/error-context.md create mode 100644 frontend/test-results/1-login-Login-Workflow-sho-6d596-sername-and-password-fields-chromium/test-failed-1.png create mode 100644 frontend/test-results/1-login-Login-Workflow-sho-77e03-dmin-button-for-admin-users-chromium/error-context.md create mode 100644 frontend/test-results/1-login-Login-Workflow-sho-77e03-dmin-button-for-admin-users-chromium/test-failed-1.png create mode 100644 frontend/test-results/1-login-Login-Workflow-sho-8b7ad-tity-after-successful-login-chromium/error-context.md create mode 100644 frontend/test-results/1-login-Login-Workflow-sho-8b7ad-tity-after-successful-login-chromium/test-failed-1.png create mode 100644 frontend/test-results/1-login-Login-Workflow-sho-8fef9-login-preference-on-revisit-chromium/error-context.md create mode 100644 frontend/test-results/1-login-Login-Workflow-sho-8fef9-login-preference-on-revisit-chromium/test-failed-1.png create mode 100644 frontend/test-results/1-login-Login-Workflow-sho-a6e06-ct-invalid-LDAP-credentials-chromium/error-context.md create mode 100644 frontend/test-results/1-login-Login-Workflow-sho-a6e06-ct-invalid-LDAP-credentials-chromium/test-failed-1.png create mode 100644 frontend/test-results/1-login-Login-Workflow-sho-d35b6-ser-list-in-local-login-tab-chromium/error-context.md create mode 100644 frontend/test-results/1-login-Login-Workflow-sho-d35b6-ser-list-in-local-login-tab-chromium/test-failed-1.png rename frontend/test-results/{1-login-Login-Workflow-sho-ba9e9-y-check-overlay-on-app-load-chromium => 1-login-Login-Workflow-should-reject-empty-login-credentials-chromium}/error-context.md (88%) create mode 100644 frontend/test-results/1-login-Login-Workflow-should-reject-empty-login-credentials-chromium/test-failed-1.png create mode 100644 frontend/test-results/1-login-Login-Workflow-should-support-logout-functionality-chromium/error-context.md create mode 100644 frontend/test-results/1-login-Login-Workflow-should-support-logout-functionality-chromium/test-failed-1.png diff --git a/frontend/playwright-report/data/0b9e37cd5ecf83154fb9b300824d32ab6910d760.png b/frontend/playwright-report/data/0b9e37cd5ecf83154fb9b300824d32ab6910d760.png new file mode 100644 index 0000000000000000000000000000000000000000..98a7abd7cb30cc806e43b5ddce0f5cacfc7b09e8 GIT binary patch literal 34209 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

mg(mV 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 literal 0 HcmV?d00001 diff --git a/frontend/playwright-report/data/11afb044f69e204fbe127fa1dbadf98b251de8bc.md b/frontend/playwright-report/data/11afb044f69e204fbe127fa1dbadf98b251de8bc.md new file mode 100644 index 00000000..b1518080 --- /dev/null +++ b/frontend/playwright-report/data/11afb044f69e204fbe127fa1dbadf98b251de8bc.md @@ -0,0 +1,224 @@ +# 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 new file mode 100644 index 00000000..2a30e17a --- /dev/null +++ b/frontend/playwright-report/data/18de4149d79572f42f059e1c21d2a1b03b0e0a62.md @@ -0,0 +1,224 @@ +# 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/d85d52a987bf605d34ced01f09c8c6347b7c781b.md b/frontend/playwright-report/data/25c086a4cde2673fed46f9d88609959f3059fc36.md similarity index 88% rename from frontend/playwright-report/data/d85d52a987bf605d34ced01f09c8c6347b7c781b.md rename to frontend/playwright-report/data/25c086a4cde2673fed46f9d88609959f3059fc36.md index d3bd717f..6b2076cd 100644 --- a/frontend/playwright-report/data/d85d52a987bf605d34ced01f09c8c6347b7c781b.md +++ b/frontend/playwright-report/data/25c086a4cde2673fed46f9d88609959f3059fc36.md @@ -6,22 +6,19 @@ # Test info -- Name: 1-login.spec.ts >> Login Workflow >> should display identity check overlay on app load -- Location: e2e/workflows/1-login.spec.ts:16:7 +- Name: 1-login.spec.ts >> Login Workflow >> should reject empty login credentials +- Location: e2e/workflows/1-login.spec.ts:32:7 # Error details ``` -Error: expect(locator).toBeVisible() failed - -Locator: locator('[data-testid="ldap-login-tab"]') -Expected: visible -Timeout: 5000ms -Error: element(s) not found +Test timeout of 30000ms exceeded. +``` +``` +Error: locator.click: Test timeout of 30000ms exceeded. Call log: - - Expect "toBeVisible" with timeout 5000ms - - waiting for locator('[data-testid="ldap-login-tab"]') + - waiting for locator('[data-testid="login-submit"]') ``` @@ -80,8 +77,7 @@ Call log: 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(); - | ^ Error: expect(locator).toBeVisible() failed + 24 | await expect(ldapTab).toBeVisible(); 25 | await expect(localTab).toBeVisible(); 26 | }); 27 | @@ -91,7 +87,8 @@ Call log: 31 | 32 | test('should reject empty login credentials', async ({ page }) => { 33 | const submitButton = page.locator('[data-testid="login-submit"]'); - 34 | await submitButton.click(); +> 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"]'); @@ -182,4 +179,14 @@ Call log: 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 new file mode 100644 index 00000000..952ef37c --- /dev/null +++ b/frontend/playwright-report/data/328efe50859839024d2ccf522575379c83e61131.md @@ -0,0 +1,224 @@ +# 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 new file mode 100644 index 0000000000000000000000000000000000000000..a0f367fa603e820a1683d8505ac504d513b0d546 GIT binary patch literal 25821 zcmeFZcTiJp*EhNm0dK00N)bf7EueG@ARxqoq7*@jpp<}sfRxZfO;B#&Mg>HrOA(~^ z-V=(7)JQLZM8FVQ2sJVNZ0~2DdA~E?d1uaiXXebAGv7bJHGA*1_qDFIerx^KwJzdJ z@9PWkOY#E%Aaw7po*4k}fPWtNe&_)B>$;2q7XTav?&;mI2>!6a`Xk&H9-+d_x%}$R z`gDqtN}RM@&DcThOzE5IwWg<@e}6w3^Iq(HN4-hT%`laK7Npy1PKqw!Ca1osgQT?A zVq%zsyD5HG;IFr`7u=5=d0Tx}_@3vXns-+pw%={^BuTobaE|x+w!0m_cP8Z1E&{!0 z@N}3toOd~6X*ZorX5KHo7={493V3CA8vGe4aX1qEy1#k2B*207{~r8r9sF-Q`2USB z+~oljeG1Gj0L%6gFyMA_vd~_s|>*?K1p5^oxs-22|bOK|t z^aDky;9&3&I5>bn%Q<=`n#XwKsj8gU>=f$n?b@v=puA ze!$~!`haTZ)zXs@m*dloZWUIdD$`Vw`3XmiOCvp9YC>8PXA zo4e6Hw|ZKh+j^^tTf~{EcItJ1-Jo#+y(QlC3Ro%3e)a?bmn(56PW;DN$4iHf6zE;r zSl^Udli@Gb=jFW`2MGGSkT1ly(v1dcm;DOhpEb{`HmF*@?DioqpcX!sT1L4zL@u2h zOBdw=CX|_t^^I4qjHm9z<^I?>T5NPtQMAYCmR9mTArWD|i8H+1uYIM?M%F$n#H+g5 z&1VRX686@2>>VB(+z42?8?(Bidc-1;i^TO6)aArAHKVgf6(-Bi3+qpa@mV{>beo;l zdR>0i|8~ILKT7p@c@Ofz)F$d6Lz*n9g#&*raZU!_sEme+dejsvS_hp8T2; zF1*tR{w}7SI5PIAN8UNl(*rmsAhC9#B1OZh4Jo@QSlppM|xb9?!B~K=8c+vp}K18_C}~ zuX}3RaW59P${~jgGYxrS%^ieq^QAAJQ@XJaUc9F1#QmhGwJ2iWc)#Fg9}DrkMO51P z<&DCNM=cTs1wX$51XUFXJ1|_$u{8gJ@afw6jN1;85`Lq+pA7>QPCig8OtZSlwS2F} zwExQa1Kfij0Kw09CrtJ)3GLq)`G)BXzYsGa)cG8g^cs}pAGp72_9u`gGs0E$(&+~G zZ}FdRPn4ZlHG0f(b;!OQo3--es73N=)x$F2v;_QpLsJbDI0tr>v^GACmH?5*{{>q5 z`K)`<9|avQSsv=_T-4B)3yO2@K7K|Olh<=UVC#?NNbt?i!8hNE z(>v|cdT6jtQ=k*@{Q>YR`uSUpozIEl@9eymIRKO&5>yNYjaaY+khrRJA{=;X1n`Po zo)C<5_nkf{rtu%tdVbqbn%jZ*#I@(Q!5h+!%l+Ny{`2rb)uX?ky}Ez!zBb?KNcW?W zT!jPo~jXp)G3NBleSj#U*JwaW)WFfI3FD(7XkK&c@S9*~M;8iprsCd(VEX0KFVv%O# zg84fSR`Nz6L2#Kb;k^MZ*EDUHDaGQFedn0Q|1RBPV9jUsQFK=iExY4J$>Giu&OcVJ zOUC%ARZoKod$KgDwrrHmSbn+9-!I^f&DYK)x%ZW~ ztK!kEcD?az>)wIC7zyXaI$0wg$q1N)eYPo6aO2X8l4D8!LrJmRv zEB*u>>5p86PY){sX*bhKG3DWwrh`O=*0Zy$la}(!VIuEesLr;#o#6%?%s>Uce~(;J zN-DKM*gFBD7o z7vPQD{;SV<>E)u2Np8n~S)?G+9HA8vx;eE(YgYNji$c>TX5J^zwl+O+=tx$0)O zb8GVtIwVm(WoP6o-)FlC;U}Aa9aNOvd=CIihmNjRzZVh9yQw?-3jnT0^Q%_hJGVc{Bdhe>aCF{B+g@s7I9cXS!iF#q*&+vUfB0lkpzia+CAFi8z|2g@Ir8O4 z%;hFMNC##v;#UoZS5>E=MgiqI&wJ|5K?fks-&hI?NkQyJ>C>}Y!kUR+R($O!<;%M; z12x5X`+jKz;ztYP0N|kfTQ5y-jTKV8LQM7?etpYkQJMGO$Vg`D9t&@x#&R0h3L(Wd zwT=6=)Snc{BZ;fW0Kot2DQlp2jFZ0R@t8BKr*%|kg^h!pFhn~U(7HC|*t+N8vEFXi z*4Kc--aiE#LM9K6qErX=co3)F$HmG#ONW>_5rI&Hy7=inW^Fv5QLWr1xDRZ0*% z5%K;mFL-5?M-o_`+!}yu8mLw2?oU{ZmF5o#qro)Z{~o}5_3K2yU|dp<%4;1rU23a2 zR7c(S(>e_(9C|L`Ao+X#Hn*}cg3?UKyRoX%IJVjUX<#HUzd^+5nV91+?g=< z(G@g%rt(7=QSZup zr{x_T6%waO#6{&-jS-e7Z~&tu`U>CM;-)bFBesOBv?@pfYYIBtsU&8CXP0L5z zku9w4PvAM47U1(%{274+(D$Z&LBU^YL>wr^fbItX zpI-oihnss5 zS+?w%S6Sm00xUu2w8*$Nvke z-rwOp(q~i+ME4ohZ*u`Y{Qo>Iv37r4jYq(=eg?!Spz{K#bP0XmVr`?s7kZbht>2co z)x84O?ll+(BYl&1Sg}XaJ*#uiuGP}mQQj6pz|wCw43B0fSQMVKHhsIVH1ZS_6Z~5Y zv_$pVC_dfu@xK%g_boSuL-p%txB%6Y;Cp4?lifuOzUZ+=bC=IarSN{a8Dh)m0_XYf zT0j(67d+_gMLQw?OaoM6JBh*%;w#qF&C| z%dZ~hGJ?tjK%vE;2%LRtra8~MZ+7w9b;oLr^VyR%g>M7<=$@zNRYzH7j=7fsKpuqR z>jnJWd;(Fi=1Lx;hLZ=sJh=!ll{~I28k-+*VmMGo+32snfV6S}^K{jT!!eF1w0V;& zkR}FBx-j43q`M~l-nfdfdcEwW_#3A)4L;>XN)DEol~|mJ;~xRTB4{Iv-;=;O$bt_% zJ^eD*>^z{T$lC>0OHPS_N(F!?H-LW}`2Vee8MtJ2>5~;2sD;wTTZ!TOUA@||Aol$U z2F+rxnTgEF$^CjVj9ilbAzgEwgk(cFdtV8JUNVc$!0m2p2Pt(5esH&M3kp zRdeo7g>qdNcLSc;2KAUQS5w&wHYPagr~q7NiwMKL`Qv`uV2lE+4M7``^QelT?w5Hu zpZZ;@Bp3(MZj&eAE;(R-?a zX51pvw~aj}E9|mJG&%#ntxtvD|6aRmgJdS5Js7{dsyxj^07YP6tLHerN6xzrdV<1Lu_E)-bPRL8aYyy84hgGmBF}l6q2XzbKHC(=s4V$4h+9b~EsV%N|byFabujuJZVa^vM;HO*(15&dD{5#}3QofCIgIu)>x&tiL()2=RWt zeLtdCR)id~X9Lff4Y@kjx<4rrzLO7=F!a4eO2o6SQ5eN*THg#`s@;73YA6Cu9^!D_*rmE1i5UER-e2y z7CIKIOOcvV!RT?3r<;vDpd2|)bBJOaVHUmJhNh(^2He4-x5YUtXjIs(fjFtrw%y&F zP>w5nIe{T6|L6xrh)mzphuLe;%L2*2x?_>M1KRfXdf0t5Q%`UXcjWbYI5r>@~CPV%ZT!&{sk>o65 z{O}RdAcWC?@lMU)N;?BWc0>MtsR&}xpWL=ZV7K02%~rsqt8CNf{jfPqTFsN4f!X1( zF@|nSNKX+?D!MQGd}9T^-CZ%Rf*lV*Kbemq^s3n*_8IK;-XIc!LsD7JyWYIeJ|x#W zJJ??w6SC)Px9K+MF;l(J9WQl%CZj>=LC`hjKOWY1+A>hKSbDsZ)mr0i+U~HlKDl7L zo%VIzzR8`nvV_ygnYUV<7@66(nh(Yev?)`!Tsf{mYJe4dHog zH4rqPUR(?9v(1L{oiP|W+pdk;{p%sRo8H}^p&M-5**Ts?q+7XYp<6&X~gw+{QA|{|5ULEkJBN= z#@MHi*V3l5OM-y)bI9P;##~XC>Lt-W>!;I()v7;5ItTPKS2vi!|-O zzEfhtUXp_;=r^R4!6J4iu{rbp%x)~2-36tOi^KOC=6@wsv8ytt;_nYMDyhUa&9PNb zL}RF;%EC|%Y8Q^Xl0uv%hZC{7h@IRa9c|89DjS2AoR1MAGnc}2Q4NSW=%-PhARPpT zvMJSzXdq-Zm17VM9(&@Nu=N4W#xQJ@h}DThm%WG;+9?eG*r12ON&c864oL^SFOJRA z*{um$H;HGKFkl)87M_wQ$@np$-1N*Q-D9q*kTAubq#U zP!lX{Yddv4&!frnWXGYVO0O|dmBc3#j9)s5O;+LXVxz*%X8O8MW$kfl_`bi>tt5gL zRazVaPNuNiZvMlgw*3kl`^}yC-4!I~n>9Utj}-btAm`Ucqi?0-@OU!gF~0Wc#v^Jz zlD2^H9HSkNZ27&?#^0Q6pcfn{8(5s?i1p_IXiK{~h=?6Bzdn!XWj+ci7x(?%ekm-p z$LqEq2#y{3)Ih5gWENfM!2x!6g56MN)ittPOPCfbC+ev3&4i#krVC!Fek}_}#eBO7 zoynVPoj{PH!}^eIMsMXg-bl@}0Jo{sC8es@ln?9c zea6QkZBpBhjHW;%28OH>$BLETRoEQ*Bbehvy`~*NaI;$5pqFV}!H*nI?t&`6ykEre ze}rLYJN=qX>spu{MtmXQhQa~POb=02PRaeQeQ(!CxvbY zru(VPZ7)JAVJpqm<0GiB4>#WWGlCjLL|})W;kGLEW#+Q6TA}+J89OeWS(!~IrG1}; zf|DH49SjS7G33-^;5upB@Nu6Uw2@<3SsrXft&~o%*9^TKAZvbayTx@Xl@mObfob;- zs9tZ>U{d(d+rMBqtTHdRYA={gJ-ew4UrAQDU00zXC-1NE$@8dB_L@&~uU%)AXI|Zq%Qo1|S7PN<2rPtga z7^n&*21;$xx)WxYcRQvL|ICzpE^Sl(IW{5^8n=$N9yLL4_l;_F(tZ{TALHeR=|iz@T+@ zZ~{;b$)A(+I}8y|T3j2N{$okU)h=);S;O;@IevE~7QbzSY&w(Hm_3ipKd39VQ)>gE z6refV`Z=V&?505T`7<)K2`ZlbM7eF7L}tWQ5L8ni)w;84!cJ9m>@+4uF!vvr4w9Ow zD&7$9&r27?uhQ2y!UM3CbJ2vPoH`qPC}H};*e2&%;qG$`Ym;P!piI`3{e3wTPh;Sl z3F0Ra0dVCu#BR#Ig1*L26Wu)~?3(4mj{Mw2&kbT7l*wFzu~sH)Y_vKymym6wD2(nA zR;^bitko|fVsplbQN_V=7d=djg-7gPM*0pc<4;QSDq4r_!q8-?L=HPDs}xL@j*Eg8 zs)wgkAvP17qCXGPH9Dvf=*RcbEgz3!2|@F;g~V`o#=ylA1ZlDfMdp@ae8=LB-LB1@ zlc7#Qf=RQ-$36WSWI8iSx;Ojjq-_u@unJrI^xjiiCeCKKBYh(TG$AaalIg!|18s3K zw`l3uY#do7qXPZ00b*BW<#TXN>z@ZTSZqx1WH3ht!9?r$tR!Cm!#F;8e!cQq24di- zx~H`rbbAN%_lQI!+gt_}L9Kz8qDbvi^M1QFRVP>(r~GkkkYMbjZ&@c8PBu-@;hr=p zJPid!*Qx=LnqZb~s^ACO(PcL)pI#y{Il`VdGn!s@{1XHa$}b7(-H|8b37o-tzd)-k?ZhbWuxRDA~vF^g4m;B6vOC z%JZ?!)EH`a+ba=*B7J!j)L-H7JkSG1`)0*q3gt_G0acQT-eY53bxv3m2971$C}Vwa zF%5w&6gP|~^5=-St8dx-OL!nyiQ2dF1ao<>M%Yw>s|>a=LZ5NvfJX9NwV13h;rBAp zavewRtF6BmD$Iau1~7bG##Zb962d&i8-* zO|6|kNQxXg+qPY314DoxjPoJT^SL7kK^zF0qq!;$EdLF1mm>W_Qac>ERRo251{BY2 zsxX&hA=}rj0ANrEU2eIKCo*J7_dgXG0JyFQa*ThM zXZ}|Pou4kn&lEa$+FGwL)YFI5UA~_5Y~EDTfcge4ePF9!#@g-Hc>#)t!9@G0p)z)d z!Ux`Z#}j2!UEQUV2w`V zUZZ{Z7`D;_%OGi^=^ewlfjur&GxXAA2D(8-+`{hCSpBDq<^r7l51UH&3Tq87MOQ{- z*>Q7sRc1%tv)COnP_NMBbgSu@KyWx2pi_cN;?x~8!+z0k(s(ANGU5C##gowlx@K5~ zJ1J$oa(<`ua>~y(Xz=1X6|o!bYv6OVp|8AUh3?U0UtOq{ydF%c;XA2SzuMU3Wq+h& zs}=ey-h(>FK%@7j!AvFPQQa!NOc~!?*`SHvpQFTXhWJ?3=Q;H zjd?^^PCL;8ZWeG^lxIqYy4+c2jMiA4Fb)L^X(-ENvU zI3IN|;k&3g;Y1+eCejGZXdR^ zuJWkKd2jD&aa)-Oen~}n07Y|6#5eBPJVN%}CN}WK2I&S6?M#NgP$9s0sk2tyX~2P= zk#a>`{{@fiNB|{Tyu4Jq7H&f9{9`6>TOYUA=#fHVeU*mmGUvX}T0xPBYDRhlcY$6{ z#w+E=mw#=TYbPF)eRlMUck^};uK6QJ8%j$JYa*$PCkvBl3+8ybz3Y16eiWXGMbmd0 zQ)3L|JoQ>w#;zg=q6f#1>cq%Uw`0w|dQ902t}IG(51S|ux2smJS$|$N-$MGf^Qx*f zU?RyCF`e#_=78-dsc6FN8kgFjjH%e5K%v*4Ua(zdILjJMGS6_qUkI+mYz~{9-P3kb z+ucyshT&@{C4U0D^7){Yx*2>?F|H2p)BiRnd@&3^k3$FTmUh5dLwK+z1=B^Tgt243 zMwmc4VCRdS@ef<^veP?DR!MI5$iA`^8CusnmDlAcb_L9KY+I&@v#W!hWn|in>4b^w zS+)wng0btE)vygiXe%-9){KD4e6{mdi(Di8XP%vJ-=hLzHnZzv(mQWI<@xWn zHIa2zQ_Cab0eXOkDCjMR`KKO+m43a!j)Mlg7p1c3)fxq0dcXmyyAB>1iC&T?r zOiX$!z3{U*&5+w(lR)Y zUTE}k!{p=Va5I#sTMlQ#OX@!MSKq|IPVZLox5)QkvI-6)S+qWBE=O{NcdS&%( z)CJ1Da}4BD8$_vdY5NM(RCx;7%v$A>`Gs>6s(o9MGkjVcGD@6DRjdPOs> zm5nRsLU*y1-r>HCeE8<-ACtDU{7gC9*|Q$V5ce@3&y-cRy`0xXGkWQsm+T{UedD#M z77_&={*=%vl!{s|mEp9=Xc7lFzhG_r$)2}Nx3v=RN|v-bP29n@C&Bn8GHY`NhFYN`^|blqPH1(ZCB(9qP(`i$ zeV$JTE<8}u%xW-MoQ}z|c>H1LtpbiS24yR#F~{SuHWDkr3ngKc4Kj)(ooZLB-sWM& zgEd_tC(b%ssh>ltYW=j}A&fm^&3vdG+zz^3>pOJZ*bdLy;l_hjxVCn@Z1|_sogAA^i9mL7lt@#yB$-et~}tJ;?=wn zW0i9`XxrK1n}C_|jHkI@p1Knld7CMVfioNZLN=An!F%0{wn(J%#CV3?Xk*ZP?b4aq z5lYob4-Sw&0_EA~$I|Eq}ZLR+41g{ylk5W}&RGO?A zup6MhKy5BIRE+qkduvsx^*3<0xY2P@qg-j_-(NRR=ppsCVnMzNE&4uoJRD}$c%^t~uFuMd|c?E7KJ8iuw`YRnjH|V3I6&kkG4CAtwD!P7C9axbBqh_ysQewiUl$us8x%2zZuD$=h zXli*s#GlMfGQ?o5=gd$3png&c38Gg1F@^ASmrHZ&xu5OQjrW*XHk^#VM*S(0|Fool zXy=EUKL0)1fI`}q#ln_gA*z?gbFMKartEfSr);NZ8%TUDE{61+2zvDMT*7OCs^&HN z)SZDxM>oSZJfO%{k40Aai@78nK66)3)LIOf!Z}h3$fUmVax8U^YI2;1{&U_e8j@+w zKGr)U8r}7LF*%)8-igVy+^?7DjeMU z9JHQJ(}4ii-wX<7@I9Zdbnxb5W+yB9RhqFy zyfgM9LaCEeIosW-y3~EjZFoN3!fCpNc%7C6X8Xn<-~z9%my#x6p;&AsD@koY^4*O; zAzH)tGJ{NASF*ZbZ(|D-HYud0{= zou1&1sA4cEvL;X9O6TiUaDVjwX{P@FSphfY0fz+p+5?)uxa+__}F8NTW%^-OQi@wGwMh{;89p!$>qkEDa+-ATRzGwYL) ze8tj)i#6SPvVg-4aEnhYQq@vYYL&lO)5(f=-GZ823YARpf zf!r9d7o={zR9RRFn$HRF-9{rhNrhz>>o*LiXSIJ z1^>}Mavnv_5()zWHN^!Sx;pp$F>&BoMih7_KaYd8wL!j&4e%`apYg4T3y#mXYj}!X zK&JNZUK)^D3jCi2-cbh+tHe29O1=;FOgeFdx*dLZNM1W~K@UQ^2>{i1!MXX4y!Y~U zdnPaTitss8YE@?C)ycnZmfu?0;$j+l^Ws1UbmoLe6Zvvi95vPO82Fy*8_~UK_Yn_2f&9aqEsa@|k}l@r(Y+ zx%s2ua)SjW#itMUgrchbP4p3Gp)_0CLb!#{{lDT06dp&VuXk`bjNMe$ucA#A*P-QP zFXO7#O%e?M{*PHQGg=B4K9h)KZVu-n#u^}~kM+9@M!wjLXc=~RzFpls+rKY;ed>-$ z-Hq=pjFL)U6~oGbO0SAUHlAH!g5r?Gy*u_};H(j86&|Utc3;ZuX-X94IvKSY!`YL9 zhJmj4@9%s3fG)kVN!FQ@8oUd$!Z?sS2QT}0b0}moh7bcy$XLCsVuwCU=geku=t;3b zI95_-KM!s>&^1OHkDvb+&yUMfu=aom^(F>ePMGJxghVCj*=xRZ?uSE-wct~ipFb*Dx4JUu!(PZ8HlDHQ-*dyx z!=|vR4t~|T#^2SoI*=4%$r3&S~JVRm$qeyWKS7tzt;aHW6JafR{%VgA8RZGPD`B9#gI;PC6V--NlAdyuwPTjST}+E>Sv| zj%l{-_w=@q^;x%+2<@D8t>CShVcTluI@%PQ4nzFe_LMN)J?epH$0C_=>yKa9lCy^$ z>{`lpQN8E}A|GA`DcS~heoTRy(K1#Vii+SK``y(@TS)rI{`fUz*&!$f8oKpPhAsmQ zSu}lPhw|-N%ZrcHJSw?D z=!ht^|GFpnTGzv-Ngkrgur!gKkPy>Ckg8vf*1{>sF8C*(M6n$wfOCP9eXJ&xaBs3hl zcT{J3XKg?h29-=UlI6Cxt?by$$i(e^1tNoegj758-Wn9?#}rjcqMz*+l6C17a*aCw z(oq(Dm8pVq!@^}U!&8#xd@g0lALHjzJiRZpkN#x{!GE{r5RkNIK<5*%6-!Z`0DQ>Q z0%GMTf4yG&YCe2~uolqj9sG@_K~bKApmrD=$CJaSwDWo|f$#h0fs)p}&g=O1LzMPk zi)B^qdBhrZ)}O$jc~w$Ul1^>g6plZ)4m~Kb<|-aLAGEt>5P8RwGJn;p^EV( ztNQ71Z@Pxdi|8M6h@F&TCWD4y1ip77OCmKF(~YI ze&X!beRFc{^5h(S3t661iems&Su9wEr)rZPc}GX z;H;?CI|{Q@287>rT|04{H}npqbdwv*B!91L|6Rl<6bkW{w`#VY)?$az+3SqzHJRI8 z%sywB>fP!y#?2YIIP&@dPq3ShYUCF}yhaD=<*In3WGbi!J^nN1f|e80L$=`Q2i(e2_mi6>>PiU+rgKf(?Lj#ZVpkcXH zkpHq||6JxbupmA53CwDrT>g%=!cV^`Eqg4JV`0~ds#rm;d+<7Qb(?FM-p#>Y9}+Pgw=24jifPrOWGrF)uRf)?9EOk~0kLouF6hCo)dT59`tVTHn5eH)Dm zY;$WhF`X0^`Uwi;{_flrt2o2gmeQzXGEw0AkS~G%+k?M!%_@d5C4Q~Ogs>naF$uP@ ze&W3ILnx|7Ni;xb_oq4L=M>oJQ$E8y<7I+?m-|vFNLFtfoOl3;ln3)bvAQ!^v62}X zAgj>|&sBifuKqpx;HkIW#OxGy3R(U5);$q2XPAYZ#Z|k^PfAUB3DI*ni7NAW{QEe2 zw|Qoaje25(`)mH5u9d79|5$3kJv4B=0Zi~-%{d^E-ac;b*xHZJKcdb=bbW%y^Y>J{ zyZc8tu|9J185X$PQnd?iVk%+GrApY-Df}Z9PZjW`R=HKD#X^Vj)OCN_xB05_= zJv}YG98$tyBF<+!W+?S%8`3YL_c}cJwzIXi-m`hw%STpbw_t|2>a0Z7aQq5?8GX_vCu^&p9oFFEb*u{Db*E zdrkt^D}J*Ba9>K`6?lXT3(kAiQZYroS^uJ`LU%RT$1o1opmJkGdrWyG#NPPSFYy8m z@}hugok8pMo)>vzyw|_U9_s?DdWE76{q{wv4pvO@SkQ%vDYhS+bn!^RL5Vg^hiXU zEr3Sye2xH3=dGFUAknado_nm|N1k%3PsUjTM~R<&h94D<0%FHNRCykEzggfGVbuJz z4!!+YO3&7`h^l_~2*&z}DPBM+^h8tM^vf0f%DJO9n}UWi=-!ua$fHqIZv8y02e zK0%)VIbCQgC%U8HNeQWZqQru>`dpkM`szz#pn~~o0B!XZ;}z{DcGj|_bq8`>I*cNI zhkpR$SvK%~Y69eC>}E@Nqs%uEqRmwKw=2$hMSRe;6MaG|HXZG#8r{%PHyCm2`xc1i z>zQ|QHnp!_OS5Tx_UM+m(_Xy?r)%!I_dRUw*OZ;H9=_HXI$vtJ^=z7#7UfFY6Zf%| zu`|-Kd<@>&fz&HX?g`mJ@RP?^NHF&ybrr9;Y|06+Z+Z;hUS8nxsFAs63P-}ByKk3M z2!?q@9dSMH3RiRiGKk5@7_oV}A1MBP8R1|qp_tKXx& zfZQT9<42NPj*6GfW5xG*$Mc8&raY@L6vxRO0y(`%`{$Ni@)bC4Dml@)dML%Ek3#K-#zcx zfAzI?|5yk&*}HA_=T&L5c*nhYbJ|EBlG~hZJV;5D-E-wr^Z|pEfYH+1W#>!BEidGc z5(dUTyv-KVhlg1lOjyGN3hfi#Zmdu*PJ}_aop<{!c1sscO;ByDzeK|@#0Q#tqQ6f> zp`y+UrWT#Oq?Dd4zRaMC)3xM(@d`ygUXTP`o=Z*AN89&^ zcdHib{gZ2-#)^lV7xm4AD$eLIUbf&OA0O#BL&J5Hd?f@{Naevn1AYEGw~O&>B94^V zgo#kkb0*5W@1m;Osz|SsolbRAJ#adP1m#)eul`5g=Jh&2& zyZpQSQdRG{|MEGG9m$h-?R-GVwL8?;mBK>#S1eB)mls;6w5uNT?-*fNHihR`9tRps z0N&z2Fn!%}P^SN3ZMwF$o{>^-#3@3>b3|LBBN5aESg|X%192jAB;L8?&w?j4 zZA(7qlzN<7trZU{I)5?Si_q5sav%O)5ryRY$3=qwb=OtiK+o~C^s!wofyaMB5i(r= z{vd<1M;zS*%Xwg5(iIe*5r^!5^^^)=`jd>UG9N68srG;puml#uTAYlZZadf$!mDh{?O(uTs8O7 z!l;H~$8ytS4Bn!^`}6AOS!&icg%k3%0d`l5@J}oL*NL}Yv~+mKZUKd|vMp4FoQEJb z$zfrkayjSxhmFZ0jA-NDY?;sv8C^JsZSIj{?9pWFKQrqOXH)Gbx zZB$5h#BywxoAlLCdV@B*Ax|@wqPrV4%nnDPm(qKd_L;{2^en`Cw6>v-vW{AG0CRP_ z9yu&dgi`T6g<-d{q=6RmnainAnu0VHdD>{QBACP{!eo%eViSxmnuM-euU=`{+i<2Y zJfXab_xhE>!kCztnmwtwleT`V z=aPqKI(C!7(3c<14t>~7frc`8XvFMIPc7mGp|6sRM-uaCQaIoFj1`$2LMm&JPkEaC z1>HiVF>B+k(3FfJ*Q)-X6mK+Vn1PtK{QGG*2CH(&er~4W=SCwt7QOSWUxrcQrU|(^ z7eJXQdy!DIiQdg7al6Kk$!YI=%xaY%OdT6Xw-QD( zGpDjsE2WNWZs}p1kXYm=ZQI>U*Y?ewQ5={i8|ddH0`tvJT)ua75Vz55Gff(IKP0vM&>0v; z*H=205BYej&dyTxutzs(ND8SvV82g_vfurBtq)MNRsNgH;l%o6j#}SX-*}a6*znBy zRE1rNX6VJuxBe=g3ISAo72Caxq4)?4(}8SyMGe0_2$#W)Bn_0e(3fS9LtoFaNSMH% znC72_nN5uy;s1h-J(8OGNo8)8P;iUWe(FyJ?CfZ6ny}`DZkM?2Sv}}RC2BD+H3$26 zWtukl3Zz3$rKV}lqPzJ)yj_yB919AwNiXonUakL&C+zP%qVMmlOXl0zt<2cmZFH%1 z9e2s(*J;Zqi*yG#SxcN3O%NophdUQeb*yE$q}oSpXko{XPdW2v(Q+^NZw?I&NPdOx ze6BO`TCGs1WW+6t2A<)wKd&^?zSk<^oT_4y#dzhFYX8_OIOxZ9{R;+YET?!WOb$M( z>)RvA4LrHSBas_ap-CO2sT`=XuUnQT)6>l^{A%D}U?(yqbUH|`2)Z_&P1$WLY<(8c z#{=$L3*;3y>?WYwT(3|GRK!vlG`x_M0>=k<7uIb4IAgNW{;T`)WB@sYnQ5|#ad`-> z@pX}3p|h#k%o~ngto{mD+hBIT9Qtx&@n#YE*W7%Y|ATWaOwW}T25PT>)D;cc&(55z z(1p7!$NA2_FX&%YPbVW5Y(((rCjVSjw!9W~2ztIE>qd3nEcv58WIl*gmCE14ho(VZ z-kLWbN)jG3c07EHVgF6T&aTPNyjGXKnRjL|JGI<{UqG9V+VwO=>( zOo;f`1Fc_#1jfm4w!5(d7&n8-f?g=)+toU&hZ(SPZR!r)S#>AM1b&U}8M-c0j{31t zxDUBBcUb8o@<*JB%pCk{tp;Z*#UGy*^0fCJ%>T9&Z|5Rdru2E?QH7?caEL{*ys`Hq zRvU9zt0mk%R1p+{Ov$zdnHl72{MJm<9y}Hi& zkU`fs=U0`;{kQ91b0Xx8<6_(@7jANr+(e_ZpPL=lGw)t-lT)d%frmKFA5Sk{yEYA# z|Ip4Q>LmfZN(BTv|Cr2f@SfX~()1_IIvCuNCSlM%dy+qVrG6%=>=5W}`OMjI7K?E* zxV7xWyWIzVCnXGrmn^v7^%w*_(`zL?RRmRqOs32I0^FBDuR49GE zDzWWlh=q2P!*A^EF=}+2SYFxw@tjm(i|LAG2aIZt#l=T*j9N#;Ld`m*Aw%-B+g?Kzt zVC`%A=Gr;IKD{E@IO^K^>DvciG1piZX4y zGM6JWXLAkalE;Zo`uN=Jqiws%_(ZbJS?rpIw>(_d+s$yIp!d}W+UQ+m&9#KRvP|V? z&+OIJFG~L({&~;L19#AEJcj-Oy6@p=bX>y{0HYZ@60pz`|QtiU-x}oQdPr{DxX!J(Y8BE zEpxS{N1tjmG;Pt!KV4Ybo0=(W$#_NS>6zzx?0Egyv!$MbRsHF}{;a_V#D~RgxOcd{ zGj#H9#1AF>1au&pM`#~MbVY^k+>W`@*!EKQiKvZ^(z#8~A0W^A^z``0=U=`jpLE)( zXu!OiyBSFxR|?Ybd%Xx4;IY*LT5QrU;q_7F)Vp=#HkY)-UwoUo8YbaLsG!PCsTDD8_|a`VP1@Y{SNk*}WbPX4qlDjRrn_Nr!1KyqBn&e|i-y+4m4 ze@@d~u7s2b{vf}^L@r#4jn(rWkqX}Bx_NCnU);hn^Q1YO_@b`1=!r&;v0GPpU%m^C z6&L;-xj?EjQT&5IuyH;(S)==BIOVL;sXD*T>}+DthlsERjb+hAi>r2dU~kB4pZ+c0 zWbG(+%d14`^Amh=46^SWTIU6=({t=@pX&#m!5@=9NwdSb)HI@u__^lsLcOv~QP9}D^#b?Iq|8>R8^R@>k<);yL{TG*ZkgG50zeNEg-w3_sF5FLa1g!#vi{!NI(?@S zXgZeA8wTX^3bBlBk?Z~uw$rD?f+VHQHn%Ljo~vQ{%fq#L<-IUp3qi@z)&(%{k55Oc zMg(V*haL~o``_w!UJB!XLDO=`o!0As5fC6%sQf%|+0mwk92eGEj4MuDvhHrf5gwOI zsmr5BjaJ;Cf59I6NR+tTffKmSD@E>J=2D90TNepIUW0A?(i#6=<97@=tX{p;;sS1C zXOP^ZnsQ5D0IQo)Y0U+3iS_KrGm3v9pTrX8jkYYbbYN^OzFy%=C^xkuI!+;!t@xKR zxWAvOVJg35t&H0ex~8e8wil z^padZ41!Q?PAL*6i&keXjLH)zKptwk85a`^gVcL;Ya#V22if(*H_p!79)4Qy{>1+Y zLCH?X94Ln%Zj|QOvnr@{&g0*<8W@wA%C9r9PQ-LqAkzdjS23jf9Kcylr}iX4(NrJDLG3-M zM>eF)>Y5A9p|>&+lU!!S`@21Tq_pG;&#xP$x@plj*9!Q{$ELKJDvc z`1His3x$zSIV+QoR;GBGY+HBCO4?)c=|BBq)xPGXd4zo{K?bc!IlBa&5+dt!=S3hk z5bzC7qDc)~wNJUZw5?=&yR{f%IBvmy-Q7?kTb9p!;MEh~uQb{FaQHgg2){c802`R@ zob_~dy;_GJu1w>2^S3juk*M{o{)dYqsaG`7qj4lO!TTSb_$_)LV6;a_!JbeUAfeT9 zcD=7lfwflNo_zo#TGAQ>e(nojcb|wr1GT3A0WfteQfHVG;}odFw?pDRQjhE8v+3t5#KZpT-w)rk;FNeY7sTcX5c`+Rm05 zrily+*((ep`n}D*HZU|(I8oTdW*baxcqVW12qFG?Jlm`cApA-Bsb7o{&BKSVKKA*~ zG(&|)e6_~8j@cw)uT~jOuY3L%7I47$RnE=jQ}<1e+2vh|bbP;{by@o$Oa910uJX%v z4z?RB_nH$Nnj+7wqa0~zeeLbBjm)opu>_W3-v6pM(M{Yg?TjBaBr2O;u}#bzXX$^m zIG#FdYSklP5>Ja@OENHFg7}I5c@h|v5nFxn#K{6)Lod0C4i;!lenimNX$a&sA(@-C z|1WBOwAme73y7er3e<@pKz#u5N`MqG;dzd*iY`KbdHl2gN$Lk=dsQ2`ET-sN3lwIg z8^F9&L15^T4O`E1+&YDsd>70NRe))i>G4K`92PPhc%7=ed9m?KNMwsXfP#TWK(tU7 z7uPEU7a>YgvK`oy01^|6U%Xe|<8;z^V}S0GwE5P|4k?5AubRQ#>`m&NsUHfhN!>S( z4f>Rb&}pqd%Np?s*P2StdW4yObTdxzdz zzx&4w`K!dq`8wzh52=s34$bgS*+-FOZ3FcFT?5!(a$t|$%1k~I3y0>>7&?GFc_5?~ zX!!R#tpK;uRpGv)f<$Vx&Awka4jKRhk5lq5-1zYgt&Zy6$B^qXOn5+(WD|@&51^|GqR({e5$t}bLJEt@ ziG-E2f_tK4n`_~Rh*jKEJtt4+hN5=UAS6}i6IOwjFA7#+1Y~8V&^5%cTaKAATn*x; z!+5ifH%uOKn$gDw-!3|<m*pQNvmQvA(`}BiJM% zbwn;wkdDq_B9M#05GtG)qj9mYD zh^r@xL>#q~Gak@LGlO%@2Q(QpwiX^3XJ*(S_uS!0_;~$t7bo0EcVIY3I^>Q|V#s5gjw@B}LxiNBV_G6>V)5ZIyJ9?B55itgrGwa^O6~U{U9-HA0m&$3S$9 zO>b}CIkTbc$KmiYbBCGwPAA!43J7A88066-`++QBJ)-Fymz;C`x(7{2Ubhw@-{Cs2l&Q>Dc_M9rEx54j%-ZqP~9~g@|HNAgMYu?HdLY2$1vSp}e zhp-m7@N zpkHTQ_W<5MBNvX;0jv82zU~L_)=BN0nAoK^l#I{$hH~+?y>f~cb4e5IIq^mEQI*HlnhtC6cm63-W$F)WRf4h!fh^ zy?SR4`ys%%hRNzUV1Z%UkP7$;5teqjE8Qg4H=3hzgv-Z&4>#UROK{or5Ym!Y*kQHh z3|Ieww@V8<0!s$+a8^b$n45|@nqy)bDt5?Z(QoX|gsi2G*ImW_R;|G;60&DTz^iR@ z>`vbp>;MNSVqTv0-YbmrGFo-pVlwozfCw}409a>B#2&IE+G6e}~ycaNZu(S4{2G*~nE-^GJziVF;i-+24DP6%m_#x94I7Wy8y9G0b&GovzwL z!nMh$Ug*m$nO>5?QUx3`!G_w!C`$Y&89KotYMb+2+u>w<;+>&L7uN0?&-3bOkuZLF zFgT9$N0`}UHvU0)WME`cTKdvm!UH$Q%3m*OHQlIt$a16^Sh{6(US@%(qE&B%qroD> zMXc9gJT5iwo(WzNudIP+8 z1;@ZB0}75`FKF9-EB}6!<>_C1i7wS~wibk~qWR!WQr0pPSO#GFcu3}6qUBPZ?dK-8 zZ%**okpw-Yb4S0(DcrjJ06GXp;R&HP;t+}u%!=?3i?wtlpOr?xJ;PG5MiPyX_KwE_3mdtJu1qBm1 z>#&oS1L5J)_^22oMra~yiPhdsr(Iu70A?f0QZ5W;<<5NN{_vn{f%om>T*}=8g{>Bp zcEJL^4mjkP$=ppBv8#}$b7Zfj6|V`b0@!eMiA*M&d_1K@4NAAYGA8ITw?5y$b@>%d z=^#-2J19_goSCgm=^xF!xVS~N@PJaMBRXJ=8tp`iHJP7Tu(J>X6G3K(g%4gb=mrOx z5+o*`85QGjC^{zO)MQzxD-!p5C>(u1GUf#%;gfz?Yi?Lcc=qh~eW=rz=$KEW(ximj z$T#OWBbGp$1Z`lfvx`A@_n%Ij>(?8(5`rOAQH^)XZ?y|tLdSB81)(gZDPScD|O$3T2Cn#DW4&S6Zj z!sy~Y2Mj2-%x`h%MS#Uv%yrp4Ca~<1RhXGy>#ws2G8mHXeFzxDf11xZy0}B-i47Rf z-#54@A>gthgL}GPK^Wl)$pEEBi}}n$4*NvAAd6Cv)Pf31VC6qCAmA(k%JBqnA5cIc zE!RUJLRjFtJtgaK37$~_&4 zG!IZBi-OwCcl|7T(uPEyDim-Q0|nP+N^CFO61*P*n|}lZMul@jXXUxuXQh{!hMB;w zB7HY300@WbAN$5MtiV8hupz>VGVMF}ePMqSb6Nv!5Rg@(A&788%3wZ$o8|w`5&mBZ z!~ahoNY0UXv(m*D7eR2%g^>5=k3siJC$iP=r+_dOfdk7y=5haQSVS;`m!-!c72@@- zD@++*GY1(JW?wi+Y5JO>$mM`LF5tHn?hUYzq6rcAN#LGaa3<&-&+aN2$~KfhOY^v8 zpUkPWlI2e>O-oCAsUKAUb-z%hC>4ATjfb?UFZZdcg+Fm8^ytT_v z=erx#=A1^)rsF$&Ymra=SEj;s#L>2D=76qeYwIRG^ zI_b;%FN*HYpcql@JXC$mVN{AuL|9l$Q#0wUs}**DsrW5O+ZZm5C;BQdgLQ>$Jiv^T zN>j-bwkTEI+A;O>T)L~tY!Dj@uDNnZ6ZxE{wT#-4j}e<$L%4%UGiW0HeVSxz?y2zK zxca1*?0**@+I7deK%PX5cR8LX?CtBj^nH;~GG?N?t@Y6<>e5Zie){eo5WrK^y|Jbh zx!o|Sv`o!M>EFA1SHY<^9HoJ@DNi01LN{Sq;~3LP)WvOcCqo+h=>aNF*zY}n8fc!_ z3^jS))Xmi9pzg1hr?-ZGD(yN&t=7syE#D?}_o_@<)H}KDsxDtrV2y3W9di=EoM8o~ zC>Yfhrabf)kX{lMwU?>L#>!f>M_>D~H=N!prn`&#I5YXmNi=6^d&ZORYimI}5E6wT z)!+cAI$V~7l$CBnKIlF-7BJo*qqFv4&!;^;aBn$kulMH=*WsnlyR*ZvkdP46A>+!5 zp!U1*KuMoW^0*93<@SKL4bah#wufbEUN3dk9{V!D(cVAN3oIUD$Pbq6Nt~JH&xjXqQCQ|w|;!wUF3`lkL)TD zt}40+J3(UX^vFO}OMIMbk=1*RlP6h+c=;luXl0Sxpf6hf3``5X5)26dyU12rTKOlC zBoF*uhBS5ST_CfmBI@haDW(%1lJ4OmsJ%*#wbcIk<|ohi`1r5yUQW;bemr$IZ`r8w zXUlMG4ERcM_V+6(3}2a{mWM~50G9ZhieVwF_B~m*4K>x%O-pETyanxH6FPDj}13B z4+L8YGD53)ZxqIMj+|{CVytrnCjyaj#Um>cREt6wo4O$DRNlQ{641Vdt+Z#uJ`3&5DV#7Z zB;2z*#OTNGn3?&P29hc5jZ;YU2`A;XsbQJ(1Ec)cF4TW2obj6WH~>mns8{+#zXN?u zBFN#la~{@_g1m9gM+kYcrW)$%T=1ZU_=m>+ds=Js{%ROwEu&9C{S+}%nd4d9E(iH( zU?WsiRP5c|-BrEdA(7?~*das>@^GfSH*Iz)xi6NdU3pNRu`M0Mlrm{c%Z+52k!%Vz z(z@y!PRR4Jw-1R5yp(!)sVOwL*{X|k^9!w4scah11Hg+m87aec)liE{n1q7b71oSI zjDee5tph4#mGXf?$y_r@d7$JZ7N6J}6XSYZm*A6R*7eT+3i983h29AB>ZhTg091yU z#WB^C6i(}%@8bCWg=7#DoR&?K&ge8$$u=v^dH7I7M5K6w3l8V!Xq+&eG`-cckReg| zcL05RjUXL4u#rj`rzhH|;Lnx~B^qXi@wi=Ab95`TQs6h2l$4AMueO4(#jU@;^IQwu z?!iG_g+X35rUgstWqO%+8XnCSG?PwpaX{aB>m2mqvCVO7Sz}$2eyvy5AwW<8%9seU zbxP1Cv!11b=()x~LcPp|=QJhoO))nx2E93hWm?U(JCDbKr*bBsQuE&ul@)x^3+2U6 z>HXj{j0vAS8Ov2U$oMHXPz_S*8fR%_;|5RfFu#N;a;z3(eBfyHuGl`}fIU$CZ@Db? z3V(&>tlQZKBN#R3slh~7M!fwF&$8>~#K9PZu6GZU*;kd{NqCZ;dVoiy{k$3^E;$QW z6S$l_dD7@s^A+L(l%hMLXcIm1%jMhaHC>}1I$4ZaOVN7DegMc>h0am9`I?>0Kf%6dip$lpJC*fb z!2C#)z7Vqx?X{>!w1VwE?yfhT7(b*AF}5JRT2NjM=&d`XhMui}me3IV?v$e9{yt_{ zacU?)%ieYF!8OL$7w(+BeO9jcfyd7u-xGK+j#^{tzDIrPHKG_n%X+mOLV4xwJ{^Z7 zPG!DuSg`8ZC$eWjq5vRO-u%z!EX^r9>K~Cp7a~DA0U+lAKu`S)qNTY){hU4f|3C0Q z$%nuB0f4Q)MwG^mtdT4L04Z+)CSdDH&1tK`pDM{8nSjbCAkR(z+Mw0y{1hWqdIgw{ z1fPhHY-9}T`(BWv&IB+ff;SkWzBAf=d{KOQR~!%pXvKlv>po(U{7ZTq0`@?ZilniVue(WObWZatAbuTuRNGZ)sLJ+$NEacP`%?)fs#T*`1V0xP6K*2GJZzu7 zW@Mqa=6d_~uU`>o`$Yh8HtLZ4%|TP^^-oWd&MQYLxNG zZ03hX$jg^%_zRP7-p+IF$P{L!uE%!s@&w4!)4#GJUe4Ja)VL`S&KcIQiUDu0AHDkL z_Jj~KGn3$g##1FnJK#zT$ODHD5+wS+-THRjiifg_n9tccCqJ985BdTTP7$?~NoQgL zUWY~azXpI0E!&o?6{Th8ESg1`EMP#KgJ$Z@Qsamx#s(cXpDKN~1AflSssppaF2^a4 z{ftM1PIQl#c5MK`>Q~|G+Zqu>Y5rd^G+3IysGMK&;QFN${AA&rl#{9D-LJIig5Am3 z;h%906<_pj=4=0K2mYdN*UY}9rK!6BmP@>inWi^3oJ%75b+~4dqa?T+%;0|g5*Xey zUsa+3mhy?~Ts;#UT)_PVfT?}>ml;cJ<=gcRBU9i*M)HAIO{Sef|!8O(-%@QpAlIH%ZkfNr})QkG^^#CYds@()h7_SP?YVsZJW%4q)( zbw7TY3lI~5EL4Srv`X zf5cpx9|peB{WnyRoBsgy?=J+FvkSFkdVZOoq6b*=2kF}_e_8H)DVHeHJ$t&HR)Sfp zBL55%P^$*e&t7watd|-;w3pnKihTV{bBVeAZziU82arADp;5UQ$y0zR6Y-T{XLT-3 z`%Ca{YyPJ3dE|OAMJn>NGb~{9eWLKKIPmsMkT0yu1;NFKHz~Ac&+)EmJGW>7@mrvt zqzfX;)Gu)WbY}W^*DgL`-_vILr|4%n!Jo7+_hmL6VcPK*+i7d{6!6_@y>wJg{RPmq zi+0zDKEFe2X1B*4Nz=}FH;UG5ez~e>uI}O4c7T06jNT4hH}yU!&b@yozx*)_up0wO zOqDF-1oSWIW&x?P;IH7-&Pu&4x_$)^XZ(1dM%1246%W@Nfc;|eUqCuDbq-n`Js>`T z^&;?rYA_1(2hX+(-sPcr;7T2Oqtc!((tP3aIlEG-=4w2@OzX|2s|o1SJ^_HZCn~H3 zi?fop#zuIC*PqpEY}J#~O;cSnn#KL4vZbR#`y7#g&!*w=cl`5_~)*Y=$VWZ5N2bxlx!wi#Z#c{8Nk@qyd*VR_qGeE8{X(x?x!R^a= zanjMvhmrJqQA|vBYuCUxkWlBy5jvvv=N8b=?UEPhxqQdY?WMCwr4foqD^rS)yLmd4 zmnVOlhLX(Ml5q!RFUaqqRs zmCSN6Qufc0XLzqE1u^gyPEiHoobhG7_oG}|dJ{6nPtCPEhKBL|j6qz*Vn8~m*+8Vm z;oorQ5Ot%!LL0Ez8~|WPOO>wo|D0UYLOniR6F4;!3_A52pzNB-{|ae2FMlB9`860|4SQY@kA%TC)9+Az-|! zd`iPHfHxOZQ`Y|yD@0D@@&0XILs=7+DaVSDneERXuFwIsUuD_>K=cB1|E-e?=JOcr zCh_IBCflb58^`{Xcx6ojA1g>L8GL`ctL54~fP<(qp*~Z`ltZ`L!FzP7e#-V`mEsWL zB=EF8*R9YWgpX-701GH3fH>|O_nm-)}~ zNTnPGN&wI+X4`0ggx4EqF(3)a6T-|_yEpeg9gfa!DBA`fU{1MZ<*!6M+6e1b-#-J6 zfm}Jo2%Hjtit{hnk5Nzye3^tnX-y}}*h zgN<;9`RS9#g(0BZ0QO=*ZEZhE{$vHcCB+82i_#YINpyt$`Q^@UkgBSYpVkQ<){R+L zMCMYOejF$K2f_bzjryf?tRV^}7)Bu&#TQ-?&zcE3G#}owa)8eG&#amO$kh9!W>W4l z56TD@Tp=@b-9p``F9S8^5dy32E$y%vxCn0fGAoM8ETn+_!F`h0JJiX`P16DerTUZ2 zEQ9I>fr(lt7N~BH7Kc@g7-4^uYKzg8RYs+4evL18YRHC3@uIj)UQOhhH zWGUGpJ8{~}hrteO9^7Hwo4Xyf&F*hNURzRv5_9m;GEocQVJ%xG6>nL!o-+o%5U zRYB_vw9oBcoySmkhAYV9+>YJbhd7*4VVSs``kzwHOhDUo;k~x`jeU| zKlloXiDz?9*yrW#0dHkc7cR%XdYgRv-?CNvi6^mqrXEe^3d1HLR*<(MhyH=kR6iCFJ~dJwBgl70;aWvZde=OV`!7$qgjK zzDA3sF247|@BGK5y>SzxQ8n|A834a1dccmCsnrL!9c#O;Qv5hpS8>;=FvQ7i_HKAC z4Ir#Qt7Rhn?*}Z-(uj;&;R+= z;`(66yE0IyFH@K7E_4tIH-6ma)3}raZ)X&q4=Iiis$dGnamS4kl$>(uWN-)y;i<7zYVsL}PJBIAn{d)~x< z%+%u#ngQVVX)a*aCuy05yce!4zdf`7;Rz4IArTGcZ1K9DGr_}x{!KzU9GH-95spox z9a@BhrZ21iuitT_W&{4;$pD7Wn=aniUG>{O*|dPr2|u?hw=%w@TMfpfxV zBTWe()%VUl)8xssMDkW2g%rDWGj&sGo23DYB)iL5n_!5Cfyf<}(9o6UuQg?uqiX0e zYQV-sSzhpp>+}~ic`_J%y8%AEN3ey07;#pIswP*<3}w*a_Vo^xvf+7>qX>c}UnrsB z@r10I?12Jhn}I@}yM4MU^(}Lpx{f%(WA%I9LN=&-!*^Ueg*)lav&pNR^v8B7Q6{pV4WP)I44z4r zH_VOuTBWRqTcv&=N8Luq1E>p}_g^%^+ul@S!La5EDE)R3uIP$^EmwcuC-s|$9uYq4 z-^p}|+MD-3s|HSQt3e4Vw`y+QnTXg9v!FrL?Bz%DrG zJuX=u2vI`5W-d4X{bb|L$tDNxxdOh0a22)rV7&E3&!Jhl?6^$t^g?86e6UPkpQW6% z-gs6Kmx2Pjz}I})5_sVAgAG~7s*#F@y%m)hUxGo|jL+y+k8D-Xect;!=!9og2??L4 z{6{;J8gxUKTa+U{g)fT`qPe#Yqbv?-UN+A~D>pkpdm#64+Ro)qT1O|)GhrQk=p(qh z>|0VDzZr6;W(v{j9*TYkYYerk;VHocPhrAu5`trRCMf{}7l*{aFxZ=00Eo04vE8jk z-cm;joyS6#w`T=cRMc$)8Aj2Rj$Sdu}ulR z>K^O}bxArht*~vm1${4!J^ZTjqhj?F<|`{!v3pQM#&Q>@JqYo4Br;IEw2w!doA;(R zgBDv7buC7Xv+nR>Yb$t&GuS|O)=AL6%1}eLKz&ne^IRPB^#R@Ky%cUQ=x6KlnXoAy zLYR{acGV}UNJ}YT7;iP?*0e`}91>NkwLcuUP!3zNK9-pH&N1WAF9woWUvopC`@S%8 zr`~y%QCoi^9R}g7<81K>(SH_Vv7)E6lLNbJ8HA&Rt@hx$mz(f0UQJ~81qROqc*M#u zaxc4ANgA`u5{jt5es@jGV3R(SWy_VbQG{4N>N$b9v-<$D%rVjEAkcOG>LfvXFiO7y z(|7|$gy)R$i}@t*oJ{GR9Lcdv;CgvN5#dkjRkK4~;=;=u*khY z%Iua)3znzE3$d7)m6;gkl9=5|$BGXc9ko3!NL&$FpWMTcI#I=3dfY&yWF9RO;LT9H zn}I6~+nftM?^r3uu_?>nShv z-muzvg#}qxwBl*SIJ##Ng_A5;g}VkEo|6bc--}+k5qqbv7nm{gPWygoTWwJ(b<52q zXAZ?#?tG^Ubc*30ZdN!qAH1CXG(N1ne6)xM<;l0U%QHnuY;YN_@+|Shp=~K!c^Z}W zCUm;C8}8h-1N3<4#=zG7$6%1s9#%jl{Itv^p4)bv@)pg-T=9)w`@0r0%?tI)$02tH z%b1UBzUMbzt7)987q%dNRXfjOQ$Kv<|94*iErRH6c>r24-=!^sW94`Wj%!npXyM&@ zt;|%e+E|G+Iu4?V8o%XkcK>_Nm>jWuw+h+tZLunJwiXIaM3PO9++#t(QUe`Gq(X^I zL4l&XiiBqwaJ7Pm+0~!JCbaMMd(q4K*P}Gm+p`C7_mZbG)--v{1MPheNaz60uusAk zniLnc&r+#+U+^uLFZvp9TJqTBsj4a1c7?=g)C9CO+byWj{a^?Oqf{!g@{aMSLR$%+ zE7293?w*Iqeh@vQWsU8+!6{(A@!V6dF!S|B;UpgCJ{aIwnd9kqhi+@8=DOUxkt+%I zq=;oNHhnyq$Hc;Odh{xZJlB73=~q5x{cF(&MWMhab!F^^VF_^3KHnr4u2oQK+@Hh- z-DwhHiI}Sncm{fs9BLY%GS^kHz%))ZpV^R29G)W^+B7Zxv?~JtT92|s&g)KPluo-E zX_((jcK*9l?z~jnbFf8i zo%AY0mv)y=R?hVQ(_@RIV3cdi5r@p|M3b&byyCE|WPvLIVhgulbfVKqo8Dhpz-h|V z9O1($-Yev!EIPTF{X$H0T`BEx1sx#51>(~7lQQHwUJ-*RvOihuOr#L$_y+ES`k3NQ znXL{`@S1q)9g0_Kmq`m>!jWC}R4NX@7!5l%DRHaF0)KKcF;#Dqde+Cil2qO{rF{64 zj(EOvY^&LArErS-Wi<&Jum^)uz6hIdn#E_g*IpB=Sx6v897d7Uo+Z6gkY&heBN3p4 zoCOQVj(0H9?0KQyQg{|>2^Td|w^yWq2x#KD4DUz`-^=;?Vxr5m-7?A*4K2}`T7P?T zb>aoOe=}2%0PSC&2}POWCXUeDHKrHKExnG)g{nvuE_l9S{*8JMThu=jNzmh=Ea&i!8ziuFEu7_$B_aN}N7^T2Pb7EQr#^<2l{~`9xB>4zAtxz5E(Jgeg zA7az8BqrGr8C&E|p0Cy8Jt+@I8%5Cod(r2_fp)=Y3*N>Ja@e(})`XlXB_dJxcr@7| zZFrKG8ZNnW#%g>8?MOI2aqDpC09SoI!~1w4g6zv7*O%{H?yq$6GTi#8*lnRHZ4==U z>QG?;PV#ZOTvZmalDeV16=v;=UggQ!{Ds}hk{pJ#*=vR0rtR z9Hl=%2W_l)O-i`3-5on7LCLNWL~gI6&6Pma(_rF&3gx^RE5b3RlvzlG>ixrZ6$0OSwtO%Bx*P=QN9<(odpa2LX=yv@ zWlUm2moq2N9)BN)Rp999tSs7U{MgHw^St-{!VYcB*Cwa<4pFxxd47cQlD`#$ zB72X8p-@#0Hyz=%*STC2Ip&3wx;?O^tQ2F3Wha@G5Avj0eJKdGHgqh(ZYQmF^x{{rz`LyLeRb<^8GGvx(x}@&`#c!Emjs=%GirP=EC@S-QI6+)YqMWX zMTB+qQFb@q76^BAfQVDv{ZBHNKh~r1INH6k1=KSI)Dr#iPy&fUS-%4YN5I}IDhi+1 z-;kR*Zep=b<>S#*2q18fH#b5YG?*Irk-V-n{p&Q68rpCDm%IrG z9RK9$!B0RBOrlah??-|3fYMd~ zE(t%Fo<_J0Lr#{L;I|2A%22BZA{j3*;`jAOR!0R33C2e#eRrT)RsR$6^S*d9)E^}W z6B|D^eVgNvkn#8&PokNkhz7dFmg>B_A*le;7F8dB(cKW0H>3A}Q6<}PI%%xt3 zw`#{j=MDMcWrI_L=q&X&_37=>zmE8NIJ~Co;1c*>U-69q@TWw^n?EAY_OnAz88znQ zGBchB<5T?aUDOKY4J(t`JNF*p<$Q##4v+bs8VKWzz#?G0*5Pw4^_8xl8RMhg$G{@? zV@;cx{Yl2Kli!Dm8ONiH_4tl9Ayp;zbhoY>l|yViNT-UOUA>4y4;O|RiUKYS8W4Ki z-Gw_I{EFbmOwG?U+{A}Mw~O{x}#=h5?*$tsm%}vzlHmf4e1-Q;SwSt7O;@jxwMsqJw6b89G$G6 z1M_{>jrEh-@#n6`pdgCAPPvrD*sbsB9v4HpLMxHakGh3$(A5mNWStf38jY)=71!;% zjo-OskKPSiTpEjTD06yTa#Y4*J#m$?G(g^~R>@XvUsu>U?g51(!OKSOWFHA#DUO-Z zv~URcW=>u|=s<>TEAs^-uD2e23xpZ_?rw8S7*;@6?zv<)sd`NjXH{a3H~MG%`+t#9 zHn_2}qH#HwBTsJB#)}80+T*)gVy%besX@c3DMEbY256`qW|%>NKPyv(r>b=*>GF@( zh93xs;b!oh*3Z$6`nK9i%4k|bk*T#0I^~@;vTb%Oxa=e zh`2Si_ejri<6a-U1Je3?sBi-EqaHrSE!H;qo|l4o;)N)?O7Z9<8-Cocc!Tn!%pR-| zF=6{;A}@63)8vRi+x{$_SdK|2cFNo(XHK~Zmj^?ZR4L=EA*e+7$EyUbX4q!hrD|>*Gfy!cK!Iv44A#93hBv-0b>=LooeOdHofT+#9sgBfp<)o&1+rX zJ;zXbH+aIsA&5pwO4RC@RKbF3U$$Gu&&)oT5;Oz8&5xgb!%e|2w=ncL&LZH*_Fj)r z^JK)4qkC%rX?e7XCl*8t6-}M zKjhEjl*cPkvO4mcgLPFlU*J0%IyDXpCTI0poFqCz4SI>nEsC(8#dovD-et?SqX zS+6c2{&-m?Db_(*C=nO>Tu;jd6`PvY@}%+FB`^Vt%Vm@Xg5oZ^qXTU>q>?(N29zLO zElS&iI?RNbJ;xRvkNi>iAm(JV=;Uv@wrfPa#}8Yd7Mpcu@Jh>vEIc`HA?+3HnX8Zo zIiogWr7t0^DAnPL=`9Iy#`dR;A*JS!}-OpDqV6`$5|9T-+?SK_tabV~Q=)k30k`U(-zJSZ!u{c2DTIY#&U(n!1S-~x_O4H3rvQ7{qWZ~N#>HZn(d5lzVB>N)&P3! zva9>8XMAsirfqHvUBI_xa{83L*>!CqPiT)XJ3?NIjWo(n6U$*+B!AJdDej)J;P{(g z>45t$-NB;2LRe1&0vEj@7NMr{bA@pf(zrd8?fzj#u4N(mqPyaL(OA$|wia1Y)6u2A z6`PP5&WU&-?4un0Z%OLoa-)(HYX=-wVfak-PkF(_(=IM|M91b|=$uu%4nNRwPvuap zo8Db)x|!62MBS)#ccJlvQ{lMY<0{hXyE=C@}y@ZDBI#Me(Hn`>5 zh?$J2Q={5qOFN)aA}&;kQxQ*Kax4x{-e{m%;B%jh^`~(4On@+*Y7~8>d0G3@+=|5qNW}4HYaC*6NK5Q4F&pw;SBbE#GLY@3I;BJ4 z3B6_0VBhr`hgdxc9P2?>Aw%k1#xJDiXpenHznE((r?-VxwC(kEr*?lx8*ulwIC+NXDt0b3jaG#!fnKe0^5VVc9dHsv2&Q0OpsuDQQytSEz zZi3aP_b)KVdjYolTYC+vp)74>x2xXq1us2+H1c^S3R_PtD5av_Hj6` z%7*d_3-dN7R3Q#w=e}6Mp;y8jm0%j7Xn%Qi2cpFZGWoFhZnvd9lTdF%b{p}VHHS^( zMm}Q$tm(%6p!_!5&*@w9zIQwTQ^$(WK zNtr1jKhR1)`p)45Z%u!PD@03#;+PC%L4YfXPcUFIWAavjWWqXp)xpWOCzTbOSC@xV8Oj5k2R>)_$RQY9Re}VOY z7KbZ^a2#}ZTPDpq9K@yzsyNs1^}KMcsuM;aow4}=hylP7lTpx``$=NJYg_~&tXMrW>32l35>}H@A0EkeFmmzu&$! zEG+jbgS#W!eX%ZUvA0;e=#Hp0_~3c!gKx8gQ*s@-QcIuWPP4~l90hg%4F9

mg(mV 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 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 new file mode 100644 index 00000000..fe107665 --- /dev/null +++ b/frontend/playwright-report/data/bd8f14932ab704c3c314dba27be5ca5a190c6e1e.md @@ -0,0 +1,187 @@ +# 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 new file mode 100644 index 00000000..abff9b1c --- /dev/null +++ b/frontend/playwright-report/data/c49d3316f329d7283ba0d5b666781aac30b68caa.md @@ -0,0 +1,213 @@ +# 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 new file mode 100644 index 00000000..654868e4 --- /dev/null +++ b/frontend/playwright-report/data/c598dfc727318850f1aaaa533df9f623f04844a4.md @@ -0,0 +1,238 @@ +# 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 new file mode 100644 index 00000000..ec531fc2 --- /dev/null +++ b/frontend/playwright-report/data/ce82bdf77eafd95f45c92adac93d33895723c22a.md @@ -0,0 +1,224 @@ +# 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 new file mode 100644 index 00000000..b629c616 --- /dev/null +++ b/frontend/playwright-report/data/df63ed0aadc766e5052f30f39c06940999f006f7.md @@ -0,0 +1,197 @@ +# 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 new file mode 100644 index 00000000..e970807f --- /dev/null +++ b/frontend/playwright-report/data/e2fc9fb7628901bc2b220788e3abb82ab1709e5f.md @@ -0,0 +1,224 @@ +# 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 new file mode 100644 index 00000000..128321ad --- /dev/null +++ b/frontend/playwright-report/data/e5e30d817d86bfb02dc036758e13ff2f70ee0d1b.md @@ -0,0 +1,187 @@ +# 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 new file mode 100644 index 00000000..888f9e98 --- /dev/null +++ b/frontend/playwright-report/data/e9f5853c90bbe234f26f48d5ffd6b063536c41e3.md @@ -0,0 +1,224 @@ +# 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 new file mode 100644 index 00000000..31d38f19 --- /dev/null +++ b/frontend/playwright-report/data/f29622b7ac30dae4a89cd725099a3fdfddd92bab.md @@ -0,0 +1,174 @@ +# 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 new file mode 100644 index 00000000..69cac167 --- /dev/null +++ b/frontend/playwright-report/data/fa35d529e1ccc0f83b98a04aad0b74476e30d06d.md @@ -0,0 +1,169 @@ +# 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 index 0d10fa8b..aea9e1a5 100644 --- a/frontend/playwright-report/index.html +++ b/frontend/playwright-report/index.html @@ -87,4 +87,4 @@ Error generating stack: `+l.message+`

- \ No newline at end of file + \ No newline at end of file diff --git a/frontend/test-results/.last-run.json b/frontend/test-results/.last-run.json index 8f99b4a4..5564eed8 100644 --- a/frontend/test-results/.last-run.json +++ b/frontend/test-results/.last-run.json @@ -1,6 +1,20 @@ { "status": "failed", "failedTests": [ - "c505212a9f19102e22e9-48f621cdb716ff0f50ce" + "c505212a9f19102e22e9-b8f769e1bec2e9dccfc0", + "c505212a9f19102e22e9-6a9f032de2702afdb378", + "c505212a9f19102e22e9-787cf157cb53bc071666", + "c505212a9f19102e22e9-84c2788f57def6af5ddb", + "c505212a9f19102e22e9-e95d7ac175608d44215d", + "c505212a9f19102e22e9-a0fc187a3d252408623f", + "c505212a9f19102e22e9-84172ac693ecaace19da", + "c505212a9f19102e22e9-61cc541ea57322557a4f", + "c505212a9f19102e22e9-1d3ef84183762c93c2f1", + "c505212a9f19102e22e9-c50a33e8ac1194b1cf3f", + "c505212a9f19102e22e9-416328d87be35eb14c6f", + "c505212a9f19102e22e9-f6e73b7d908d39f8af63", + "c505212a9f19102e22e9-2933a06b064cdb673ab5", + "c505212a9f19102e22e9-e264b575fec6c229ca47", + "c505212a9f19102e22e9-e34a858da752dac8c229" ] } \ No newline at end of file diff --git a/frontend/test-results/1-login-Login-Workflow-sho-0bb23-e-network-errors-gracefully-chromium/error-context.md b/frontend/test-results/1-login-Login-Workflow-sho-0bb23-e-network-errors-gracefully-chromium/error-context.md new file mode 100644 index 00000000..b629c616 --- /dev/null +++ b/frontend/test-results/1-login-Login-Workflow-sho-0bb23-e-network-errors-gracefully-chromium/error-context.md @@ -0,0 +1,197 @@ +# 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/test-results/1-login-Login-Workflow-sho-ba9e9-y-check-overlay-on-app-load-chromium/test-failed-1.png b/frontend/test-results/1-login-Login-Workflow-sho-0bb23-e-network-errors-gracefully-chromium/test-failed-1.png similarity index 100% rename from frontend/test-results/1-login-Login-Workflow-sho-ba9e9-y-check-overlay-on-app-load-chromium/test-failed-1.png rename to frontend/test-results/1-login-Login-Workflow-sho-0bb23-e-network-errors-gracefully-chromium/test-failed-1.png diff --git a/frontend/test-results/1-login-Login-Workflow-sho-1065a-n-LDAP-and-local-login-tabs-chromium/error-context.md b/frontend/test-results/1-login-Login-Workflow-sho-1065a-n-LDAP-and-local-login-tabs-chromium/error-context.md new file mode 100644 index 00000000..654868e4 --- /dev/null +++ b/frontend/test-results/1-login-Login-Workflow-sho-1065a-n-LDAP-and-local-login-tabs-chromium/error-context.md @@ -0,0 +1,238 @@ +# 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/test-results/1-login-Login-Workflow-sho-1065a-n-LDAP-and-local-login-tabs-chromium/test-failed-1.png b/frontend/test-results/1-login-Login-Workflow-sho-1065a-n-LDAP-and-local-login-tabs-chromium/test-failed-1.png new file mode 100644 index 0000000000000000000000000000000000000000..b0b1cec4275d72ac084b270aab2d77c2c914b2f1 GIT binary patch literal 25825 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 IWn94j0cCc2r2qf` literal 0 HcmV?d00001 diff --git a/frontend/test-results/1-login-Login-Workflow-sho-19d2a-hen-clicking-user-from-list-chromium/error-context.md b/frontend/test-results/1-login-Login-Workflow-sho-19d2a-hen-clicking-user-from-list-chromium/error-context.md new file mode 100644 index 00000000..69cac167 --- /dev/null +++ b/frontend/test-results/1-login-Login-Workflow-sho-19d2a-hen-clicking-user-from-list-chromium/error-context.md @@ -0,0 +1,169 @@ +# 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/test-results/1-login-Login-Workflow-sho-19d2a-hen-clicking-user-from-list-chromium/test-failed-1.png b/frontend/test-results/1-login-Login-Workflow-sho-19d2a-hen-clicking-user-from-list-chromium/test-failed-1.png new file mode 100644 index 0000000000000000000000000000000000000000..98a7abd7cb30cc806e43b5ddce0f5cacfc7b09e8 GIT binary patch literal 34209 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

mg(mV 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 literal 0 HcmV?d00001 diff --git a/frontend/test-results/1-login-Login-Workflow-sho-1b0d7-alid-local-user-credentials-chromium/error-context.md b/frontend/test-results/1-login-Login-Workflow-sho-1b0d7-alid-local-user-credentials-chromium/error-context.md new file mode 100644 index 00000000..952ef37c --- /dev/null +++ b/frontend/test-results/1-login-Login-Workflow-sho-1b0d7-alid-local-user-credentials-chromium/error-context.md @@ -0,0 +1,224 @@ +# 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/test-results/1-login-Login-Workflow-sho-1b0d7-alid-local-user-credentials-chromium/test-failed-1.png b/frontend/test-results/1-login-Login-Workflow-sho-1b0d7-alid-local-user-credentials-chromium/test-failed-1.png new file mode 100644 index 0000000000000000000000000000000000000000..a0f367fa603e820a1683d8505ac504d513b0d546 GIT binary patch literal 25821 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 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/test-results/1-login-Login-Workflow-sho-1def4-alid-local-user-credentials-chromium/test-failed-1.png b/frontend/test-results/1-login-Login-Workflow-sho-1def4-alid-local-user-credentials-chromium/test-failed-1.png new file mode 100644 index 0000000000000000000000000000000000000000..98a7abd7cb30cc806e43b5ddce0f5cacfc7b09e8 GIT binary patch literal 34209 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

mg(mV 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 literal 0 HcmV?d00001 diff --git a/frontend/test-results/1-login-Login-Workflow-sho-24db1-with-valid-LDAP-credentials-chromium/error-context.md b/frontend/test-results/1-login-Login-Workflow-sho-24db1-with-valid-LDAP-credentials-chromium/error-context.md new file mode 100644 index 00000000..128321ad --- /dev/null +++ b/frontend/test-results/1-login-Login-Workflow-sho-24db1-with-valid-LDAP-credentials-chromium/error-context.md @@ -0,0 +1,187 @@ +# 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/test-results/1-login-Login-Workflow-sho-24db1-with-valid-LDAP-credentials-chromium/test-failed-1.png b/frontend/test-results/1-login-Login-Workflow-sho-24db1-with-valid-LDAP-credentials-chromium/test-failed-1.png new file mode 100644 index 0000000000000000000000000000000000000000..b0b1cec4275d72ac084b270aab2d77c2c914b2f1 GIT binary patch literal 25825 zcmeFYcT`hbxGx$&K&e{+Q4p|U0|Z5SiEax@5kwTED=M8RNQVSLL_t959RcYQdO}So z8Tb}vcA&C^lHy9Zn4Yf z#Eq{Tn;p8q6~k*3GkLVM!aCT+G7{Ry zAgsq_dH5w@Q=pWlTYrA8oCNUzn-k<#0S49oXYhY$@PGK=e;Zl&EC90lWmsqq>PM>x z@qmu1avl5XcMoCq@DlTGZx2Y#2*g)%sOGZbaWQCC;j=JnK>V|KmoEP^ke(a#<(T8* zp*E10MoDLNv6dl#-Hj?x$Xf5 z{is+`$jlCS*Y+c-EkqhT_@N!-a~8zYpD-t=sKWa+OzTJ(*GuC)-oeYjVCFC|xW32h z7IjQWT84L#`)&iFDSJNa-DE!vNFG7r9dKCZrh>b6A zx$d6&s!u}Wcc71kwBC?4!2gb;^WhNay8pd0%W$;0r7G7 z2@SQon0ruQQ2%YOc&GW5{>kEpGvhyp@0#g7Q)$yg-r&Aa{*o>nFHbJFfU`Z9_4 zm%2BQw)qk$1)JqMW*N<;%mzPQK5o=@|kQC z0!K)(YOP94CM3Mf`~ct5O@JUz_3x9+UGWZ%dAFmoEwm*-DhdGOD;Cz4s=VP+srs7V zd0+b>Uf|W&gF^qj2MH;L?!I}JdiPw`6>dRA?af>KxsI|w+peED2<$8A?pf~Qm^SWA zBuMBlevz+jM>Jq+u{&BNz_cowP+N3cTLQ56{sh6Wx3wMZ{Em|c933w{8~~*qjxup{ z6$=BLj1RnA$E<3`uYg{*w~JYI9B>Sqc^!T*>$b=ZQ0*yTZumpfh3`Hmxznxy(omH3 zhFh&;@u1^NU?Ep;Vd=K-TJN}xl0iZ$XYLEU%~<++K!v9i6lSP3^6SONCVUTl@>bL3 zyWcu)X&>x#v`>yMe5QY4C8nS=G7rA=yBZW1BVw}4T;;E_IM~*9=^ ztp|Hb65d@SdCY}s^NVug0=qe|0&v^&t3nT-Ud?o~m%a?HR2k1Eg7tDA_cY)#jp6}WIJAD6(((Ky{)dY3CyMMrqpq)f;y$5WU*1|gw7EK_JIcy3 zf3>-OP*_DT!jY@(T8D+5A6X&dnLZBIaA%bCy6_o_WOngj!9a}qRS>8MU^HVRn#TN* z(*E8UflgLro|KG#ndbPTezJk|aZk7F-?gl`i~+6)yZG)wm13Mo>K%Hx)MKKEo_5xg z#s}xEKxyH?U0&Q(;ZBJ*{GQxxBO>Z=NGI|49b+u0+f z7GoJ9fuHbQr@+I zQ=CBK>ZEve{gcEpzrTFWpLM+Wy7&Mn?RwhXsKSt{V-h-Q>;iZq;yFN`XPV0P+MAvSfzn`r(775ua^oqsdfnwYAc+guNxHx{mD?;&Uk~0O z5)Jfa+WM1zRPN{aNQWH*ERN&6npX={m$ecVM+W;+?~+h!uZ!?4MKKWL3FUbEUSF`r z{$?l_NG11xBPh+G=f{KsqUC&$inj-+Js7I`Q#9UGP6RbZ-9vwG-1b-Po3*J7FeNTH zMYb0JlORIjysxgZa!XM^c5$EYIv4Qk;TX+g>;ASE!Q;DFv~Vvrk03qRtR0sA`X_hV zaf$!(~0>G;*yV%!&}V9|NjV!qCwT|DICO9?l3eF}f&BO_CrN(xrmt3C|Ohs?V%pi=8 ztT$cEkT8hyiOSWbw4$Z;5E@|{HXjJ@xZH99RdVW3`f;28;4Q0reHUc^C*Jyr1WEj1 zEBkBz6I(5gS*MWb(GlZPG! zl)g@LJp>AK15Ajc?7Q0I2^t}RUT5gs*U621$3UPkD?miKsr!W+cY?LUt51E6I4*TO z^L2<{$t8Ymklj~c(w^>i{7nWrn*WsuoA? z$ix2Gtesrha)2k9^6WW&&jBjV!i38s`{YTf8B*|%J{6rjxF+4q% zAg8%>X9A>hit8BY+~Z@KS-tA~F@`m8t=UaL<$M4Mfy5Lf?%d(mPK+%NzkPaUJcR^4 z;pqng8Q%i&h26sk>x7$Md4SX{3GAWcZ&aJjJAxcRXAS`=f&a(_9QpIX7vBgow~F+? zl&+(OM^+-&=`Ig>K#tyj9+g@g(`wS0na(J&IXwRD7AyLBXSB#pF+PrL^=ab2tcKm`4+OSTJ(G4N~V*@ zb2Y*@^5v87-sVzEvn(_()s&LR2b={O`)1do6QIIw0%z>MBYvaM3}j^sC`=IOx+35Q zfk6KNR`UPL!8d?9MKu*RkbtpAo(6LCorrk9xC5ZJX8@;}VXP;;+=ll2PHe7Kq-Uk; zk+xtvV9x&gWPCR=l-x?9P0IuyeabheUXQHw?2(z97?D%_R$Ffm8s?P>lbWho>}Kx^ z!l)J9#Nb6*v+RDN-27+Z!KTgko=E%Vo&7|L`+VrutkiT#?~7T;etobk11Xu)#IT{V zCyKqAOXRJNIlH%leqRMl#EPX~CmGBcRN6{({%1Z4yHp>%7&cJ*!C z;01adn6R<7HpHt2m@|SGio6P$K?II9P0TnpP_K9AwCeb8H4rE|r1_CtPn)TE@1uUd zW~`M#S-@dc(AMf63>wDES}!o7(^H4Nl7nq^*1Lu9fKvH!RS zVNt>C01#*!aHp<6LD;Hcq2z$>xw-Mk-Y;?dY^EJ#fA_1K;P(uKCkI~(BPV8>T4P>8 z9EL}2{h3cVY#D|KALuV2Pz3Fk`*(v|NRp_>ac^gQkTF0Q>{SA2SFkFu904MiX+O86#h%yO}*gfiP*{eX>8qMc?-(-48}dBr*1gbNA4H%L~pmF51B;Uot1-S#EmG z`qVrJ#u7aB=!oBzGJAv{8Q*%XhV#~xwL^lH*XFYqO>7{ITa&`ddW44ERM=vo|C>*R zGjr`xUYrOdPM`hvO43MijfoA8w&)N@B3qt78|ok#6w`UiB5WqHJj4?H{lyrWv(5vi z5~T+nD5FV>gU+(_9WWDVielu+RB|Xf%2Ju~v+11WRB-DPylD5*@nJ~`#b{;{p#i^*pOuELk8=5DhgofHi(ZP?~(9NSc`WN%|FWw)|v zF+bJMj+mLtj%wB^s`Q4m;0tmGa+tg~{g#32U+dI?!CRbvq*%AZWpSG($QI5+xn|ep zQrzS5db_NR4hg#N7)jH>_{~2$?{y>@LtTsOeW_TCL$PEGq8xI@TWJ5}lm=KLJ!sXYSqV^| zC>>M9-8O74d*j_BtOr9)nL0DL#UhBp$`Xb}K(&CYa^3SMymcc*!!u-khK&Jx_eGJm zeyVvkZ51G4&oMQrm>08^p;Hw$apx>!NwVY7$1v}ONzBe*eMG%ihr_o~UO&B{p}`#KAF%r5Ok@^r2M@1y_?2Iw zjGxYt5rx(+T~G6Dc42*-%hhRW?uf|>mSeS2nNwiw;jQ%e`Caty}eMNJaHR?^pI^ns4<3E z?@hf^y%D^ZW1w>hrDLIPAK>I!E#{t@0&eu9OOCQwOg&1Sn&=r3$N(GcQt?);T@wf-67#!B)4;YTi6m$@oO9~a5a!2ZyG}J+0MeY)7n@b{;o)U|_!~=PornI&dPu-foWZ3M zYiA5>6A77z>e~71Gq-VCua&NV`pi_|?DUEEhwmU%7?RdIX*A7l^B)t8e7rsDjq?E@!tkdTH|pXQYv=|@fF z5hcOJ` zOYgSzNepe$raYhM9Bs*%McR%-z3;vLwNN0cyF1GA9fa-bRO-?uJQyjdFSDv`y?)^Z zocm^DJ-ZoZ&NkLIFYEt_KAMK14(oNpvYSk%TFCDQmalmKVC8ln_2oFDgPv^?USOcx z@nCP(dokRZq$peVz6^{UV_E+Q)n@4LOUmI#H?9^+<NXVQB|z&mz{W4TrGqUl`=N zEhEZaES80?ue1@oSPMgTQ>cUV3ZANx(mx*4tk?c;Jr1F0qS(})stP0HbB_nV+0Tie zW9E{!J}f;J=S6WBi` zvKh@>C}>@^^X)_s5Z}uQq5C&U`<1X-g|zDVR-J+a`m#IL2 zecsJt1+YpO_Kj847X@MWIL4FmvtA z`izA|G1FQCyuptljr?wtVnyhk!|G(_%2&fMN)Drs8$krxuDt&fMU=AMc5w`#!J$#(Zi{ zHb+%1WXd<$f0QrmxPY|vWf9QPTJuuz{ztAag@5GKS2!ffWiiUMZu)IK((pig^#v-m zw;P~CR%LXW&WYBhBTQ`I0I`1vSKR1U)$!NcU4w_pwAOx#J74F~uv4L7+gPeMhN)(Z z$i(Y6IpI#|;2&4D88Zgt*gLr`uHOsxN1CBqlJowItf+`ygx4$rA1y5D=`!?7J72GM zH!q4hu=u!H0&utqlkSysO^ZxQ3R|x^C-8aqpq-5B!8wc56My*;!4^>Q9EzbP_WPK6 zniu;O+z6!E3?#Wn#KNBZy1F&RUdmU-|vg79|pedbt{?Z5kxnQZ97Ehe}E(@b-h+#BF(m6ocSgFZIqIIPzb) zfRWxhPwmPI>OxK&l!_|!2C|-MYJf~=+T79h-|}l$1}Pq+a`AycCyxT32Z5g50R6ui z6eW4OxEk~IM|GhfiAqYbGu!;q2+mIvOZ#D!O7y^{-&BW(G$`gBNT}^s0+9J+f#+&M zL)9HA2^EqK!F{m(NC(;khl%pXg`EDV#175WBW3tA=i(hErC9ni603Hv*pW)R`_27O z{{aqyt11A9v6sF*Ww}?P7kwqae>cMOH|1agIjy)kjLpF^Weht43}w}1J2Wg;2R>mw zn@xC6QK+05Z(s~^sB%rUgjsXD#@d#fD>w3Xb#iHAqys)0qL9ZE2;s0opq%yL%X31x zR|tlJkmRrc9a5?4f_>FRK%l(I?GTxtypK}*)i1mMoa{hfbq+R4C{1}W^Y_%-L3n$+ zqba0Kyo~lzUV6$dWW8zryzJJogP=c!w7<@jzQ7CofwDnMo3ri+`x>}fvJSKVjA#gJt;hz42z;{FAq^v1 zR?fgk4zR^9#D_;y?2zlT<@U|5L){;u2s7CZ=+$<;k?;hi%z4UKrOZB6FE-VI5E z?{irn$B@r=>T~Pr(4meTBL_ia#8c-%?*{Uo{OY6~rxr{r+u>ubakhKqWE||-T@3wQ z|9C&k28aR%lITH;Rsxg9Y~rB!TFy?$<-2e!TNTp0)~Fn^!j7Q5-*w=u5H822hzXs* za>g}?3_7)0P?ag|a$*`yXgo>Vxg1257I0woOXf)F`UE%4mhC#LZoxkwoR-xzY+*F| z3#Vd@PCYw2epxIsA0Gvy|MFBoC;O&4_&soc^@ch{Y9VaX_SyVl*2%`smb{>T+vS>~ zC6}t7XQh-I_7c?6os@~B6T0#?*O+aYI0{`t8M+mxgQji1Vz|RwHK!TlZ`;lDq1YFx z71BW~+t_Y_#QH;0>T|!vz;#a-Fb?$MVE+?}&keDRTqx59NASSDVGnDtH@8;Rj;=g7$Lm{hFKONx>$ zn}i`j7_uYc#Af z_Zt-W9(&QMk*$|mbC#t2j!<0i70kS>J|$ThM!LIE=h?iOAULM1AFzrJQHHg<+cTn> z^V!Nd6r)MhE$tJVGUe`3#jQu~YY^&$i!#Akgd?EA%Rr^=pSyYPt9t(Wv%&M3^JF$> zFUOzx)BUomj{a4%K6=a)KM=_7uJd5AL!qR_^Fh|it>4IX4}8wMkJUiOB4-96di&Gt z`T5RZzYurl)p3dXZ3^xpQckyJ72|->tJ(}VSv`UK{MK2nW%q5dcC)r@aKP$fldtrk zYt~E5dn%Y2o1NVn(fD^BwXry79yfytcAV;r_xqJvZ>qUDlH?9{buAv!Xn&urZnT_a zBvLeUK6#Tf<4H?TDBa25?QER8?%ypYs{gySTFONpGu%L2+Lda~32^CV6Wigda-7Ls z9@@Rh*P~vggSjTW&8IOO)C{ntVxx;8qOb^T@BLa-<6iOsp*a|Je4XJoToCZmS(D^{*q8U zhj`mLq ztik={1nPBY9~@5H+z`Y_31;h!NOBi*j*ENqEIWva3()=^{rK1Q7EL10KW2H2_6l9%`qQaKTarz-GLYBvf7mM=~$1u-5U(%I(-faoqcp6Q4=wWjT zoMV(&j-1~^luJ{#LiPt)t?f;o;D*4_LX&DrYKl6~`IqVIB;VP`Ci^-$lP{z3LoR(j z**adF-QvfUxZfL7cI1>?+FL>NK9y5bY}TWGK%#Q_7uGx(oSA4+qi{!r$!NtjE^KMc zQyjcL<*bUrsPIa##<7VQ9#FtXz*yMO-WKrCfPmR}CAcZyp!93`a^qmn>A~lo3HbGy z$;ltTd?K6&GqVD;l#xT1-Q5rJ>BObDe?G6#r;e!q=55Erb4J3$=G$vBU4qu>z(F!( zNte#r)YuGuaO+9`9x8BNeY0+K$u`Ayo1IJBaeyxt-^b2(EG(|dp`AJHIX1Z0UHC1f zV2VYRaOB&!B_@3vKYmfW-)EJ54Y95OXR4-Xjll1!f7dClCg?bXEcDFJ!{PJW+7Nm9 z0MN5epv1TR#rE}j{M-)09qQ){qdok( z-YdX^2O^gxP47guoSc{*9Zk#nKCr)sP^b)?pr@ovNF!`%Wz~W)K0G{)7EbPcH3$k> z;eMP;HwOK(h1FFV%K9-L6TcZ!syp4_wcACIs!@`(rO=J6`6;AXuqF+WeOgX_v}qz! zx%I0jwhPj*zCre#skZMehTL=Otv6ll7uKn`@$`pL4iq=&Ny87K?DQHpdl4xvdD4|l z#L>y^KD$0@_&mFeGU=ivtzkDVg`aJ=H5-A-YW^O7YfC7!^t?oh7Pf3er zF*s)@wi#_wy8vXX_16jO%@RjSO>)|+VJ1L@83V5}qeE!vz@01;_%rZwB zwNLWL(n!iF1{j&Gc}7QDZ>&uvw3FaK?M2*L9mB!cYi6e|02P|dAxsci!kv)=En8mj z5vxC*>0h#y05~{MeEFoaPn5$P;~B}twWN5OvQh4U-r9+=l%$4oeo1e9s)t|$@by!6 zjbwi7Cb`yK1t+Xi-+(UYPXKjy!7ToC;IU!{q zI6{D@RUNSbG+pgp}F2|#crM{iQU(m_ZBWPdCB0~bOg%x%>s9c+BkA?K}IdVc7LyddA zcPZw*j86X%=G;f9KT%3xq;eFs;{;lZ8Bsfd=}t*n`ebRJXPz#kWei%BV$ zm@`@1#hUH#3qrz2he9VP<&{nJjdFV!ImKvFgR(`^Jo-H&0CNODcGw0%*BeQC6Rp$l zeKZ8FwSA8iR+8ig#XKp!Mh#9&dO7L1`2<5(quusrBw73Iv_6lpHm{ZbaWO%-qTFW3 zjz75X(e`sEOcw?h#L^mqEvh_7%q#Xf})u%{POHC@rY;D;^LiO#X(hf0UF1cr->XHIn@$!I_E-iuc%>QFCf_|G@l4?!N5HR?hak8T(}Rne^ouLpprgD6 z*Lf60dYZmk2=wv%2Gtkno_n13lMkdQ0EB&~|0*(EK4ZtF=FIGM%-#SUm^* z>sy9NqNBa{s$7x`pPpV+6*Oy}s}uS9UVe@tc{Dj;iTk>V&u8GxJdjX~k?W?D;Pndc zKjWt*%JPa=^%0pKZBO!zZe&duD>~_$Htu$x?^x>_e{Z;zxNWA4}d^M zNq`-bj8!rr$HsO&tIi7WDd34A6M7Ks?KP6ToRdBNG)Y;xD?l7cYc2jC@vY)-R0bizCbF%wZcjX5%L>@IF|+^^C%~ zUe`iI&r$oQsIXmA_HQwmH|`9T2wO68p#LKZal4F83hg4J$!N~bm-8rDlNQ1dbbHoE zwY^LMMnYARU@P-Ot>2Yv#JdW3iY#ifts1yP^1LJ z*8sNFYDp?X%CfFP*RMpuu<=1d_MMAsB%+RAgL~J;BeF?syKS9kBl$d{&bju6AAT~o zVau-4E4GFh#a`2ZLK(NqBTzYx_!YUJDF^0^vRB`UX|jhyk_|@v8nP}M`C2gz(Yn-v zK=?Yvi|XU%8IAvy3-EP$o?mevLffXo*3PR}LXKhCXvt2*>{zRW!7!2 z!k+mUkx)^xa~Np=>;gc>6kj~p6OO3z;Vd#;P&`s)obw8g!nFF{pltHO7Lqk|%Uq_(m~E0DHSI2Z_?96R5Y0||X}7aPW| z(6Fb980L!o3^ zUp@zk+e6FI>BMGmh*M)5rj9-L`*&inr~%jh+^zjDwSUzY(ORqxks3%YZs=F(Kg6pzjv=>L{&4Ri;tH({rX_E7?6_ z5Q61keKGcd>_?Uq1Kx#sXMfpaO)onN-{|jy=UEgP2(45O%82*CF>jLSV{%Pw9}+8B z=iHO7?Vd*Vy#2N$XbC`FTZIF1bMxWNRd#24)@|Pf1lisD=FP5WSJDzk&IF5y@Dnz) z{h1>SC}Vdh?m+q^!UN`vyIbW(B@Q~eGj3bfUt7(YYus%d$L>xHVYh2-ygYGioIZy}^`#)|a&tp>9+FU7wK_qf zn*n*GNlG%oCY&`yBm}6h^<(Sm^)m2nMCazX6M;pllHZ5C*mxj!37)F)BJX#MZb3LK zFms=>a&iiZR<&{3GCIK0(%oB@KtSHmAT%l$F-D-{MQgBs z%W!V+&U`#<$Gtf?qRFFbH5VPg**7YX@#n;YIiqmYNlXi>RcmdIrXk0ylw)nQqk0|# zoO}*Hf?5@;MF7PFD7a4u(!Vyr=fAYs9hWadz#>Z&bt>>;9sG z26`vl()O5M5PRm&rMPEIK0U@l8Hk(^2)igh+RlCAxHdID^jPPZ{h{Ja-&ww~0l7in zLm=l=0YD@YjOC3Ym+C&U>oN8W`qtEmknQVxVtUg%845*S4pNE6=Xk8F;WKQyG{8_m zZ1^vU8?Afu>zw`cTJ7R9> zK;1?Et|bfmIxvfs`9X>RBC9RIT%@gWo#$16o9WLIFWoK02k`sFs%JVcm%O+~n`$O` zdUyn^HqRf{OL@zcq6^s6d-q<9Mntg2)G=rX&K+U*M4DOKE4z-N+;^J)iTxm}$ z^{LlYU-@EJ?T;TjDFR@R<>j+WO9b}-`rf&kKI=D;-1T*jnFAC9=^Dk^g|=LSMLbr~ z_n$AYQEw~R1T^4BJOE7K31LxD4#Cc!zs+c;fc9nXE~qU5u$+n)@!Fo}vZXUN46!}{ z|9Fonq$)eee!`7>v(Wb2oQ9wGYu)b$R2$>h|1uBT-ABALH1IusI)wl5~bpcshS zuLb0lHm>u-*66DgT@6hYm6=bzCgehMSBsCI5u=hYXb;+sNvc=S0#3wK-N}MWR#r;D(Uk@S zE|t&dykCFFbAwVocdT8z`AF^Bq`!<-(8`|6Gp8|&2;Jh^^bQyOutNFVA+tcI^rh32!*njrNvqsDJVFT=m!z$3;)9hqu#hy(F34=?)=-v zP?eT!Sm+2hRCQOOeD`E!Fc;=5^^z@aYR1GIlFrD?SK1^I1ln8<>TgNEjPqVCygRZ~44!^x8Q1^I#Dnb0b2l1mbnq`C%td zM7!;a!O$(=%GR%HxJE{4+*=PLr>hP>zqf8gL4vUFQ{Kj{>If1pbMmB-a6?%LwgI|4 z+rcjOBa)J#TwxmzxcIDU;$+sX=Mh(v?+w94PDp$%I2iifIy?6v{!L~tmB^ypbkG^r zvib=R-THyB7$%Q9#e18)Ii<<(!`euFeUi=9rg{3n)U-yDFDLn}h=F4FqmFK|2X|eV$ zSY}bCMm*B>gFt8dboP56ETj3G!@q#jFnKQ*$CDb@T3S52elNV@RI?p>o9{$Eoo;9; zh3661^3D{F3c(rKkMsP8rr6LS4eMFuW{LqE8?fkZPyUIf+&PY{rj{~fr)TVl>$X3s zejPh_uoa?^4ZuAXr_%?&jjsQA5&K|GxZ{Fo{EC4= zR1_b7&~D`45_httv>JS-7GoTmgLVlvO(Jjb(l!OIG+R>$bZo`4B3@S1pU9;1nEJ>6 zl4Ygp)PMu9&vBWa{#u&0L) zb!&Tp%*xHVKx*YcY^!)l+Y>ad4rq&v$0w9{51k*cI(F8utHo5|y2+ne))y-!uPKU* z7^u0iUFC9o0Klt{rmwwgRm zWb{-^D4qk_J$r(&8R=ws`V;nRbDC(OL)h&`Kwc4Kl7XmKx^3NgB{NoQrcDI zfT%PnDB{_bpugfmXMWZ4RbBWKmp*xO+WufnWMVGQt^twPYVQAMFzV3?kDGROPNAG? zREG-`ujQHE)W5&SGlcq!<&RYu)T6Cu>`uui;7NL-y3}W8mnyVio~P44<;aGxyUeS{ z{pM#a%9wk@yZiuf7ek*YebdBzgKK|=8lFU*OTNfSd<}$Z9WGDtjh^Wqfp&mO$D3OF zI(O$W@1lIMWgemGK|#MF2S?_9M%OC<>OH^Mz+v;maoH!;FJIU#Pml2!+ylq5VAIW)}!!CMZGN6!p+mz81RG@csVEQVh z4&7W?*{or0R0rP~b7FYwTgF}sws(+R-CpTU(uj<5gM z)o$lA^yE&JPu%uN&E7XVAJAjtR=ET{nOM<+fD^o=y!JdceBMzX=yY=1Ow~^SSfY4UxGy82=eb4UC&N@_6CcB%$0u-G4-RXVft@!BIy^Mlp~mF- zc$Jx|tMrS%g6_m`>EL-uH112JtqX*s0)>CWKNDiiA^C9m+MWE4g)bcoBLb)!JYOHL z>G4*%+;W~#n1lmOlc9Fq+-~a|0Z3|bx62Bz9T9?tMwT;l`K6F zdqOYZn^o3LH{rx}iPhDpV4%%|tG+@dpiX5}y4B&v>Ej@?UBI)PwY0AGn{dQkZ#(@! zR=mkVwjlViWz_q02}yB-D8u(s_Gf`!u@Oe{6DSIP98?dqiIn)38V5bUAjvwQcqzTf zc)4cf_@MY%Y0G58_UU0mMX3?Sqi6wYY&np-$pmsY1R#25dpVs{(r>kEjqS_m_{X>` z{NwYQrxR;!?O|Q#aIgzOr0{nPchLAXKr0uiC5a_Dk@LlQaRvNwNztlnyxDHnHvQyC zdE~^CcZOX{cOA-HO+eR$eav~yn)8>Q?nbX@2^atK4e}$q9750_d%9vn(}J zT$$jJL^56UjUVq-dY*n{DWx_^T{_wt@!w0;@5~{dVDF|#nO`~zBzP>xuYI{BvC9?n zr23lMNtZC|KY1;CdF12nwr>(k}SKD`%3K~2|rRPt6*R&s-;bR5>!KFB)wv!pp}RTyRpqd``j z5rl*cVU#^}x0!2aVAOPmXwDOchX-zlt}q*cbwK+&`=2UFv1U^h?NX1+$R2XL={K;F zY>7fz9Mhu@b55+!shR|M(O5!I5K4w6X3+|oYi20qveD=x z_v3F*jg?Y z`S`=ws##;*@cd&B_aMAlDGN4I&k@yT(?z*K$AE0pIJ`D3V}n5G9)p|>0-E!bE8h&} zU`4Tvt?f}SN{1XR66uftqkJ6vA*gJXz8SQ`){z@@pvG+x@?jy5?YAG2(9c&_IXgM4 zI|}OdtoKt8l%t6{b!m`6Y9Xz||Gu^yJP*BB1@WJ%5uD{i(F&s;bJk36_0gdI%e<1` z4~c-G9H4f3LKhOQ-s4Q9Z|d<>(ib)C{p{WCR9@{j0-y?NW^izXs^#H-%&|><3_t9Z z*;+L7tT8;Ov+@LeisQcVq~p2M>Eoe~t{fS6NP{~(u&UQq{FIcC4)hC^G}pW!*klYf zYi1umuD9A_ zYuXllT<7-&Mu#G9ISM!e_WU<UJxJn~$RCE2u+qm36!+asb3AJ9XYRFSQKrMlw^ zvnrsJ%Ybh?TThf&y&Q5lV5`ZO6fk89D0gK}E&%_0wzoH#OYebj7-%qZwb6CO!LKJ1 z%1pT6wuM@T5RdnT(j?Io{1AA;Z?_o|avO2V=T{zy2%RWg{Z5$StZB&Vr1imDy_YwD z*zFfpmI2Lc^&(IDTy6~@JKRBaY*HZp2jan6FmkF)LK5yfb@GzKTZ|8rnyhO8ayrp-|E(vMyEL3%D@V~Z+$zag5};$eiNuw2km zmkzXahw`+@RS^WvB%q6Ea}_=PZNpzW*99X~8c#p)$49&4(BCW8@srA1Q=`L+&rW^l zt$L`WoxzF~8`97IaVylKMdqT=8-wx-dm>5UCo_)3-72^F@&sLRYAmNhG(4}QLpNwP z>%UJsJ7fcXN{`I~kS@mV`>(ovhUCzE0Udm_#F&uAw>cNk>J;5T9FOUnWG8QZ!@-E< zp1^Q7$E)d{9-r9jy?F2L%6Z)a6Cir#$(`9Hr%){II0aL7Zx-DhSlN~T9!)30vEGvd z7W3`fm`Xt(I;OoJ0QiJuJf>O(=f2#FYQO8cAGtV4d?ATGeP!q8D^q{k=Be6z;!bY- zk~=$6C&=D*erI-TJzW^l;vrHiDo*Fm|DIXmt_XR$Y!w?+;?dSIo(L zg{`dz8{FXmX{G~?O|#oswSoScSW7#!$H`pfy-g1LmW+pBBOJcyi0zHFWI4J~{pIG! ztn(r*FBwfSmvKoru+`LL3NDbEEhBchf!Hx+7u?AkD+xE{$& zxleSQx_;A0ybSHttww1_OIF_7 z1OH#`efd9>@Av<08)0a%FOy1^#$fFGl0DgHtdSvEDod8AD7z?n*_W(i7a}Cd9qD z3%eq)qd2OVsXF23E2?|i#kXX$qx5Z_KWQ@3fCF+;roC=y>kM9RRe_oeo zJ##m~#T?Sy<#pDag35o{{R zL<^1vtXSE?o0f*PPXjeLpUWvmClv+VS~;rnFNt#fzB~N#2aWv9&)2WviVxe8$~6QI zWi$c~_>+Y`ZoJA7&*{u^t+apB-&j^J>ob7}lwVvK-8GzVw%qf5FQnDUg~&Q4gcP?| z)ZwiMo?EzYuM6C&l!N?Yg8m=3dQ|*>-0F?{wY2w^KN-;|KI;eBXl-;C zks?d?Hbq}yQ`Z?UHRQzf0*&m`KyLn;x%W{e@{OMzPs8-Qj%J+Vv@KHN^GqyL_&i>D z)az?meEWD@^`qy0m+zq@ox=Wa(upFeflrsc7B+DTIc!Zm$Dtjsbx#v@f4ubJsU74^ z`LED1;6}e^@B(Sf^d~N_qQ*3}k!!_JTp?)R`ab1U1p>I?Y;!-#C;#fluiP&2O6o?Yyo1@PjY$ftdbp94{?I1i+X@h}RVE zXu83dw?vYn^k|0Qve7IEW@zcLx)lqoY{|X?5MmO zQ>d*>T#ubr3+v!BD`RA&hhq9yF`oB~Kjjn41r0`0w8dX}C^FZ2fsFu|n<+V<7usvg zN(%Ypmz+@CTOwm`l==SYscw;!%;>RCq4nMOb>bNxxY^Ayp2b-P2Lf|s(7iMw7WPYH zJ5V0@(?7Ky7%CN02R^uSJarrz9xP2hnGa6^J|TrT&AWii0sjx9eqfVo;bW&n&G|^V z)qX3#7JzJ)P(z8;t#@^R`^g`G_a2sZv!iha7X@DxN)3gh&r4K@abwz#G!-PJII#2l z;fB`vvhZe)PU-6+804=kDw4`eNjD^lv?YsB@QdAhh%*7fCtAoTI?AV%JfCR$E4* z3l^Eyicnt-3?#c=smA&dJr;A%BGv4Xvp$kZoic0MwE%72HLvWl9}reIrD@ybA)`Ia z+rL9-D@A{cTmC+e|1Y#INGpdB<+f98$~Po=mpwszmvfo(B8z$1r^=E7h2(M14NYfs zRgsgLio441io-s_l>byh`o*>ID;}MNvk?Ljgn>JuHK7NTZ%hR5n-4R{uS)mJf0O+- zHrikDS?&y`myp&7fF;K6ZhmuoUaiJc7(TD_O%{wWP**a3a>v&|`19S__ge#N<0or% zAr3e0=w&6~VShoU_=NQTMwveOc#Zjw`;T+A;b|i!WLauv9_rb9S>E{>F3ztf*DYat zYb5?DQjHz30OTsvxc&S#;Gems!Yhy9SX1y8OYJU_sSE`8izoWM%Oy zfms%_hy~$GgaN|XLKHKd#?a`{eB8PO!_WiS>+lv3Yw<4@AWO$>7$!bnOw;-)*=tP^ z@C6N9=sz?6I)8SZ|7_>y-6r?eN8VF?|bN73#nB^nmeTvGT+VXihto!3mV#D`O z0ttFoexwxLGQP;cuN@K=`-4I#W#MV04Tw_9a^c(pc#r#;2Du~8jf*B$MIptvRX8|;w;YRw!j|@R1 z7l4%H0DcWHaAhQfGX_Egl}>W_Kz2I7YZf2gg1@=s7t9oi@PQH;f!{Hn95;!2LFx+f z9TK^T&v^GfeZcx*%m?U<`~jy8AhS6?GDSk;;^A=>veSn-2t+$?^c6tB)MR^2 zF00Atd^$Fg&PGbjK^iGDYgOEPVLbrsBH7Aw`qlj%B3F{f~6`mg)9 z0H`TGAXrA=4Nkzf?Vu_43L3qRSJ9a$8>h0OnoIvpgQ5kB;Nepv4S^k1Q_AZ`N<$n)5Dmr{^OU_~`Xlp%eAf~L*iG)m%NcZM!7(FCV){cL~Yo9SO2PRB)P z%%0aYDbvRuQRFj$PHoF;lh<+_4PXlW5BZ|5#k~rn=rZU|;BR)93V?-iczF=HYm|sBZNlF5KX^PWA;Zm1>I#S7!(B{$1cm z1ac=k^=ryAAi*uLnV=^oo~n~4O3>qp(pKlK3$s_sv1DTkkZkb(sV= z{ECzob>65r%lKoy8MA$ETG!92<*nrydI5v-7}`#KXZA_}-h6 zGZx7o2tn`Cq)t&f-=6i4_({Jp;+>`5D&7)mASE*HM6w}C^&Z=?>&mZJybHky$zoYU zq~A_vG4$L&NS!HujdU??42XU1&#elM?@nZa|@p4rhmirRz?GQb1NzGxTf9S@zG|#X~&pq`LcQ6ovfKQA{?dtRrEtA{D+&So9V8S7*xk6f;mw8xpA67N${$jkm5S(# z1CrtbdQ%R`AG6FkN0ya0HMyXFHkgRsc4VxkyiocD2%iyyuGa{DF=q7!*BuJ?uVp1r z2y20J*IT&*7^B4=e*h^KcAJ@7etGKtHy*$BExG1m*vTK`>a?^7lWVSwpZ z=sXJNz_JwTMKtWQVnl<-+jaAx_?cncGk@+Iz|b56MR9iWcwUPJ zs+@wJz`J)6wWVg5?aGeAkWNpiaRbE@{w;?}S2?{&FbMX}7?l3ES=E_Cm@ zeY*3aAW+kSKzsz7s=0f=(>)SzEPl=_XB`)A{iummr=%&G*@Ft&1iNta`w_E$ptSuY zZddqE1x1QoKNgP7I7m+`L_Hi=9PFrX=m+q3m!)Zh`Ta`Dn&g=qMbpABEsKABL^_8$ zP@_%2PJI#3JTH}R2vaD_pIo8Tw;x5D3>`BFe6cs&XExmelet&m>?%Gdic;S`e<|3| zukgH|e~wFLcthKW#M#G2EFtC+??^nS`rmB{odXHwN<-cEIp?TgelVutCUUih3>gRH zxJyh-AI|vrq|!v|=1Hl=M_3E?IGS^LNIiMi& z7bdz?3Kxp%FK6rfJx(|S*~}CfODo$~HC*kP+q}Nd`~rM|UnG?K7V^T#cx5}=$QPMk zu!CTHZi8x<8F7~)ds<)gVWjpsusSU9 zY35x?{X*yTh(wU$@&0;!vTX6MMs*UwyIbI$Rb{e&Tk4}H$g?V|w>S!bLwFj*vDy@W z5Z!3+k+WtDn_4lC6$Y<~Op~VBxl%9VA~MatsQGcL0p>~PO$LAd`B+4v-1P zPKv}czz2B<%tY4UB2^1WElDqx2nycLgewD@l}Vm= z7J}k5LCi07jtmeK{4eqI|Gx(RP%{}9V8c(?L#)C2Kr{}wNjJPZQZ3286B*I=^(!Gc zvs|64;RRnbk!EL_ZCDuEW}F5;FkIEjQLX!jO&~jql3q}Q1q5Sfn9s5K@pjf44C~}= zEe>k3#6}l9kV)0K+GHytoaUHSO$|b&P1z8LZU$I*i)+O{?emx%%)1*3Wv(}feMu+# zLTPw*cVtp;ZfJD7wh92)EGc2IPc=`s_kGIrnZz~K4WyE^a||OQBD%Xf@!Yg@bh9yJ zTv?v`Hc#Gr0xDey>M|$BeW6t_suQ+0r~{s8+K7n#BL@?ck&Qlmbn2&rgW=&}EK*)f zEU}l_Eo&#(#_fHp4RKk@(KC>o=Rxt!v$jjg(P5Jyt}ISZf66rBP=lIjI0L=+8*^}hh&Yq_^RA2LqC~pVFrUms}NANwY3ouwBH}76nx`^ zS!YTaopll>PP4;^%`Z>4e)i=v(dxOmSu(O?4H?-$X_kd`y*vy7PaWK7bIkz7o#%ie z1D6W@%y80;!9~gQOv@s^J39j40&1!88s;t`CKjuegO_pDXX9h&&!eUFWn(3_u5~rn zN!arsV(>0Z%Di%imr)ct7)1AB@y$52da1>EJ4##&9d{@IwaDPzGJRWvx1d3x?7 z4Q^CK+{J;#$dzz}O0F8_Vz>%CB+@y%Y4y?#?5({W@=Z~I1J^-qKmvqULQrDU?-5Av zatvF$-Et&CmZ|H_+S-}}V-iSA0F7u{Pa9GAm!P#-Tv1{3p(q6+HdWx{g@Da9CQ(Zu&ll}tLY2`Dr}flyWR*EOdL2VF}yWT zr?6D$p^Ug;x&ZfuSwdm1gE1e+I^uXq;GOSLS)1Oo#nZhS&Ire-u($*nue&VlWziTk%+@zyozP3 zg-$b=4#vsa!_d$?ss;iQ&&X|e!HjgBl+(01)#ZHYRM7AMJJZ_onULX*~0I8xeDEdA3iwzz_-x7?)oEf z6BKgIm9`|4E5TCVY{3=`P7Dr4YvqN&CYtSr~wYyW7{XMtW zjgSp9ctfXxSR~`Yg@On7m&27a(IU+cGvC}YA1<+cBNXeVQd{jaaC1=-m=i#1YD&RB zjgU6+vr3ac&$Lvo(ym%qmzimb;*fT;^>%O=-Op;}Hvtgqm%d3{SuAIVxH|W2jHE~+ zNTLF|H)Dy!`^Zs7I$G!TRS?<{a=`bD?p6Ezc8l`m1D9CMn0DJM#IKi*jt(s?E#ZKY z5`dx>&SIkG7q|B-z9J!@?$IXO=lx#a%u_cEBiQ0&weJordR#d_JO0I+BVyQ;kKtuY z|H6X9<#c|AW}nOQGK;H`AaVk%JCd>M4Z+7w?1hNawIsf)^v&(6!tj@2r7W*#lZ}wv z)GZ6VMtl-I!qb41v!SAcM5-{V$M@1gd+?oWUKe9_vCGCMARHH`eFZe;$C9rFrHvrp zULXpzQ3jw_{SRZMawX}8oF{hploga)%D18TxMXc~0IFu(i=>?7eq3{DPYHA}8MJhp z%%Y{?S`B#6*D>NGe~WDVw_Jze^__^e5koXPUI;Y5Jq<|1E8iuiw)7WPlYrX_sBF7! zK%y|EN-khD47!HqIzPiE=~XSjpFQs}utsx{pCLQXi3P=sI4Rh`>wAPn5^t(OU67Fo zbXg1tC;^rp`Z{!bkP$m;1s-^QpMqabPdmQN`-4XqQL~UiRo5?*cRBGZfj7_%HC@#b IWn94j0cCc2r2qf` literal 0 HcmV?d00001 diff --git a/frontend/test-results/1-login-Login-Workflow-sho-2cf04-session-across-page-reloads-chromium/error-context.md b/frontend/test-results/1-login-Login-Workflow-sho-2cf04-session-across-page-reloads-chromium/error-context.md new file mode 100644 index 00000000..e970807f --- /dev/null +++ b/frontend/test-results/1-login-Login-Workflow-sho-2cf04-session-across-page-reloads-chromium/error-context.md @@ -0,0 +1,224 @@ +# 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/test-results/1-login-Login-Workflow-sho-2cf04-session-across-page-reloads-chromium/test-failed-1.png b/frontend/test-results/1-login-Login-Workflow-sho-2cf04-session-across-page-reloads-chromium/test-failed-1.png new file mode 100644 index 0000000000000000000000000000000000000000..b0b1cec4275d72ac084b270aab2d77c2c914b2f1 GIT binary patch literal 25825 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 IWn94j0cCc2r2qf` literal 0 HcmV?d00001 diff --git a/frontend/test-results/1-login-Login-Workflow-sho-6d596-sername-and-password-fields-chromium/error-context.md b/frontend/test-results/1-login-Login-Workflow-sho-6d596-sername-and-password-fields-chromium/error-context.md new file mode 100644 index 00000000..abff9b1c --- /dev/null +++ b/frontend/test-results/1-login-Login-Workflow-sho-6d596-sername-and-password-fields-chromium/error-context.md @@ -0,0 +1,213 @@ +# 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/test-results/1-login-Login-Workflow-sho-6d596-sername-and-password-fields-chromium/test-failed-1.png b/frontend/test-results/1-login-Login-Workflow-sho-6d596-sername-and-password-fields-chromium/test-failed-1.png new file mode 100644 index 0000000000000000000000000000000000000000..b0b1cec4275d72ac084b270aab2d77c2c914b2f1 GIT binary patch literal 25825 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 IWn94j0cCc2r2qf` literal 0 HcmV?d00001 diff --git a/frontend/test-results/1-login-Login-Workflow-sho-77e03-dmin-button-for-admin-users-chromium/error-context.md b/frontend/test-results/1-login-Login-Workflow-sho-77e03-dmin-button-for-admin-users-chromium/error-context.md new file mode 100644 index 00000000..888f9e98 --- /dev/null +++ b/frontend/test-results/1-login-Login-Workflow-sho-77e03-dmin-button-for-admin-users-chromium/error-context.md @@ -0,0 +1,224 @@ +# 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/test-results/1-login-Login-Workflow-sho-77e03-dmin-button-for-admin-users-chromium/test-failed-1.png b/frontend/test-results/1-login-Login-Workflow-sho-77e03-dmin-button-for-admin-users-chromium/test-failed-1.png new file mode 100644 index 0000000000000000000000000000000000000000..b0b1cec4275d72ac084b270aab2d77c2c914b2f1 GIT binary patch literal 25825 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 IWn94j0cCc2r2qf` literal 0 HcmV?d00001 diff --git a/frontend/test-results/1-login-Login-Workflow-sho-8b7ad-tity-after-successful-login-chromium/error-context.md b/frontend/test-results/1-login-Login-Workflow-sho-8b7ad-tity-after-successful-login-chromium/error-context.md new file mode 100644 index 00000000..ec531fc2 --- /dev/null +++ b/frontend/test-results/1-login-Login-Workflow-sho-8b7ad-tity-after-successful-login-chromium/error-context.md @@ -0,0 +1,224 @@ +# 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/test-results/1-login-Login-Workflow-sho-8b7ad-tity-after-successful-login-chromium/test-failed-1.png b/frontend/test-results/1-login-Login-Workflow-sho-8b7ad-tity-after-successful-login-chromium/test-failed-1.png new file mode 100644 index 0000000000000000000000000000000000000000..b0b1cec4275d72ac084b270aab2d77c2c914b2f1 GIT binary patch literal 25825 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 IWn94j0cCc2r2qf` literal 0 HcmV?d00001 diff --git a/frontend/test-results/1-login-Login-Workflow-sho-8fef9-login-preference-on-revisit-chromium/error-context.md b/frontend/test-results/1-login-Login-Workflow-sho-8fef9-login-preference-on-revisit-chromium/error-context.md new file mode 100644 index 00000000..b1518080 --- /dev/null +++ b/frontend/test-results/1-login-Login-Workflow-sho-8fef9-login-preference-on-revisit-chromium/error-context.md @@ -0,0 +1,224 @@ +# 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/test-results/1-login-Login-Workflow-sho-8fef9-login-preference-on-revisit-chromium/test-failed-1.png b/frontend/test-results/1-login-Login-Workflow-sho-8fef9-login-preference-on-revisit-chromium/test-failed-1.png new file mode 100644 index 0000000000000000000000000000000000000000..b0b1cec4275d72ac084b270aab2d77c2c914b2f1 GIT binary patch literal 25825 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 IWn94j0cCc2r2qf` literal 0 HcmV?d00001 diff --git a/frontend/test-results/1-login-Login-Workflow-sho-a6e06-ct-invalid-LDAP-credentials-chromium/error-context.md b/frontend/test-results/1-login-Login-Workflow-sho-a6e06-ct-invalid-LDAP-credentials-chromium/error-context.md new file mode 100644 index 00000000..fe107665 --- /dev/null +++ b/frontend/test-results/1-login-Login-Workflow-sho-a6e06-ct-invalid-LDAP-credentials-chromium/error-context.md @@ -0,0 +1,187 @@ +# 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/test-results/1-login-Login-Workflow-sho-a6e06-ct-invalid-LDAP-credentials-chromium/test-failed-1.png b/frontend/test-results/1-login-Login-Workflow-sho-a6e06-ct-invalid-LDAP-credentials-chromium/test-failed-1.png new file mode 100644 index 0000000000000000000000000000000000000000..b0b1cec4275d72ac084b270aab2d77c2c914b2f1 GIT binary patch literal 25825 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 IWn94j0cCc2r2qf` literal 0 HcmV?d00001 diff --git a/frontend/test-results/1-login-Login-Workflow-sho-d35b6-ser-list-in-local-login-tab-chromium/error-context.md b/frontend/test-results/1-login-Login-Workflow-sho-d35b6-ser-list-in-local-login-tab-chromium/error-context.md new file mode 100644 index 00000000..31d38f19 --- /dev/null +++ b/frontend/test-results/1-login-Login-Workflow-sho-d35b6-ser-list-in-local-login-tab-chromium/error-context.md @@ -0,0 +1,174 @@ +# 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/test-results/1-login-Login-Workflow-sho-d35b6-ser-list-in-local-login-tab-chromium/test-failed-1.png b/frontend/test-results/1-login-Login-Workflow-sho-d35b6-ser-list-in-local-login-tab-chromium/test-failed-1.png new file mode 100644 index 0000000000000000000000000000000000000000..9449d71fc1f5b14a3e9eb0f18fa130cde419aef3 GIT binary patch literal 34222 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

mg(mV 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 Login Workflow >> should display identity check overlay on app load -- Location: e2e/workflows/1-login.spec.ts:16:7 +- Name: 1-login.spec.ts >> Login Workflow >> should reject empty login credentials +- Location: e2e/workflows/1-login.spec.ts:32:7 # Error details ``` -Error: expect(locator).toBeVisible() failed - -Locator: locator('[data-testid="ldap-login-tab"]') -Expected: visible -Timeout: 5000ms -Error: element(s) not found +Test timeout of 30000ms exceeded. +``` +``` +Error: locator.click: Test timeout of 30000ms exceeded. Call log: - - Expect "toBeVisible" with timeout 5000ms - - waiting for locator('[data-testid="ldap-login-tab"]') + - waiting for locator('[data-testid="login-submit"]') ``` @@ -80,8 +77,7 @@ Call log: 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(); - | ^ Error: expect(locator).toBeVisible() failed + 24 | await expect(ldapTab).toBeVisible(); 25 | await expect(localTab).toBeVisible(); 26 | }); 27 | @@ -91,7 +87,8 @@ Call log: 31 | 32 | test('should reject empty login credentials', async ({ page }) => { 33 | const submitButton = page.locator('[data-testid="login-submit"]'); - 34 | await submitButton.click(); +> 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"]'); @@ -182,4 +179,14 @@ Call log: 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/test-results/1-login-Login-Workflow-should-reject-empty-login-credentials-chromium/test-failed-1.png b/frontend/test-results/1-login-Login-Workflow-should-reject-empty-login-credentials-chromium/test-failed-1.png new file mode 100644 index 0000000000000000000000000000000000000000..b0b1cec4275d72ac084b270aab2d77c2c914b2f1 GIT binary patch literal 25825 zcmeFYcT`hbxGx$&K&e{+Q4p|U0|Z5SiEax@5kwTED=M8RNQVSLL_t959RcYQdO}So z8Tb}vcA&C^lHy9Zn4Yf z#Eq{Tn;p8q6~k*3GkLVM!aCT+G7{Ry zAgsq_dH5w@Q=pWlTYrA8oCNUzn-k<#0S49oXYhY$@PGK=e;Zl&EC90lWmsqq>PM>x z@qmu1avl5XcMoCq@DlTGZx2Y#2*g)%sOGZbaWQCC;j=JnK>V|KmoEP^ke(a#<(T8* zp*E10MoDLNv6dl#-Hj?x$Xf5 z{is+`$jlCS*Y+c-EkqhT_@N!-a~8zYpD-t=sKWa+OzTJ(*GuC)-oeYjVCFC|xW32h z7IjQWT84L#`)&iFDSJNa-DE!vNFG7r9dKCZrh>b6A zx$d6&s!u}Wcc71kwBC?4!2gb;^WhNay8pd0%W$;0r7G7 z2@SQon0ruQQ2%YOc&GW5{>kEpGvhyp@0#g7Q)$yg-r&Aa{*o>nFHbJFfU`Z9_4 zm%2BQw)qk$1)JqMW*N<;%mzPQK5o=@|kQC z0!K)(YOP94CM3Mf`~ct5O@JUz_3x9+UGWZ%dAFmoEwm*-DhdGOD;Cz4s=VP+srs7V zd0+b>Uf|W&gF^qj2MH;L?!I}JdiPw`6>dRA?af>KxsI|w+peED2<$8A?pf~Qm^SWA zBuMBlevz+jM>Jq+u{&BNz_cowP+N3cTLQ56{sh6Wx3wMZ{Em|c933w{8~~*qjxup{ z6$=BLj1RnA$E<3`uYg{*w~JYI9B>Sqc^!T*>$b=ZQ0*yTZumpfh3`Hmxznxy(omH3 zhFh&;@u1^NU?Ep;Vd=K-TJN}xl0iZ$XYLEU%~<++K!v9i6lSP3^6SONCVUTl@>bL3 zyWcu)X&>x#v`>yMe5QY4C8nS=G7rA=yBZW1BVw}4T;;E_IM~*9=^ ztp|Hb65d@SdCY}s^NVug0=qe|0&v^&t3nT-Ud?o~m%a?HR2k1Eg7tDA_cY)#jp6}WIJAD6(((Ky{)dY3CyMMrqpq)f;y$5WU*1|gw7EK_JIcy3 zf3>-OP*_DT!jY@(T8D+5A6X&dnLZBIaA%bCy6_o_WOngj!9a}qRS>8MU^HVRn#TN* z(*E8UflgLro|KG#ndbPTezJk|aZk7F-?gl`i~+6)yZG)wm13Mo>K%Hx)MKKEo_5xg z#s}xEKxyH?U0&Q(;ZBJ*{GQxxBO>Z=NGI|49b+u0+f z7GoJ9fuHbQr@+I zQ=CBK>ZEve{gcEpzrTFWpLM+Wy7&Mn?RwhXsKSt{V-h-Q>;iZq;yFN`XPV0P+MAvSfzn`r(775ua^oqsdfnwYAc+guNxHx{mD?;&Uk~0O z5)Jfa+WM1zRPN{aNQWH*ERN&6npX={m$ecVM+W;+?~+h!uZ!?4MKKWL3FUbEUSF`r z{$?l_NG11xBPh+G=f{KsqUC&$inj-+Js7I`Q#9UGP6RbZ-9vwG-1b-Po3*J7FeNTH zMYb0JlORIjysxgZa!XM^c5$EYIv4Qk;TX+g>;ASE!Q;DFv~Vvrk03qRtR0sA`X_hV zaf$!(~0>G;*yV%!&}V9|NjV!qCwT|DICO9?l3eF}f&BO_CrN(xrmt3C|Ohs?V%pi=8 ztT$cEkT8hyiOSWbw4$Z;5E@|{HXjJ@xZH99RdVW3`f;28;4Q0reHUc^C*Jyr1WEj1 zEBkBz6I(5gS*MWb(GlZPG! zl)g@LJp>AK15Ajc?7Q0I2^t}RUT5gs*U621$3UPkD?miKsr!W+cY?LUt51E6I4*TO z^L2<{$t8Ymklj~c(w^>i{7nWrn*WsuoA? z$ix2Gtesrha)2k9^6WW&&jBjV!i38s`{YTf8B*|%J{6rjxF+4q% zAg8%>X9A>hit8BY+~Z@KS-tA~F@`m8t=UaL<$M4Mfy5Lf?%d(mPK+%NzkPaUJcR^4 z;pqng8Q%i&h26sk>x7$Md4SX{3GAWcZ&aJjJAxcRXAS`=f&a(_9QpIX7vBgow~F+? zl&+(OM^+-&=`Ig>K#tyj9+g@g(`wS0na(J&IXwRD7AyLBXSB#pF+PrL^=ab2tcKm`4+OSTJ(G4N~V*@ zb2Y*@^5v87-sVzEvn(_()s&LR2b={O`)1do6QIIw0%z>MBYvaM3}j^sC`=IOx+35Q zfk6KNR`UPL!8d?9MKu*RkbtpAo(6LCorrk9xC5ZJX8@;}VXP;;+=ll2PHe7Kq-Uk; zk+xtvV9x&gWPCR=l-x?9P0IuyeabheUXQHw?2(z97?D%_R$Ffm8s?P>lbWho>}Kx^ z!l)J9#Nb6*v+RDN-27+Z!KTgko=E%Vo&7|L`+VrutkiT#?~7T;etobk11Xu)#IT{V zCyKqAOXRJNIlH%leqRMl#EPX~CmGBcRN6{({%1Z4yHp>%7&cJ*!C z;01adn6R<7HpHt2m@|SGio6P$K?II9P0TnpP_K9AwCeb8H4rE|r1_CtPn)TE@1uUd zW~`M#S-@dc(AMf63>wDES}!o7(^H4Nl7nq^*1Lu9fKvH!RS zVNt>C01#*!aHp<6LD;Hcq2z$>xw-Mk-Y;?dY^EJ#fA_1K;P(uKCkI~(BPV8>T4P>8 z9EL}2{h3cVY#D|KALuV2Pz3Fk`*(v|NRp_>ac^gQkTF0Q>{SA2SFkFu904MiX+O86#h%yO}*gfiP*{eX>8qMc?-(-48}dBr*1gbNA4H%L~pmF51B;Uot1-S#EmG z`qVrJ#u7aB=!oBzGJAv{8Q*%XhV#~xwL^lH*XFYqO>7{ITa&`ddW44ERM=vo|C>*R zGjr`xUYrOdPM`hvO43MijfoA8w&)N@B3qt78|ok#6w`UiB5WqHJj4?H{lyrWv(5vi z5~T+nD5FV>gU+(_9WWDVielu+RB|Xf%2Ju~v+11WRB-DPylD5*@nJ~`#b{;{p#i^*pOuELk8=5DhgofHi(ZP?~(9NSc`WN%|FWw)|v zF+bJMj+mLtj%wB^s`Q4m;0tmGa+tg~{g#32U+dI?!CRbvq*%AZWpSG($QI5+xn|ep zQrzS5db_NR4hg#N7)jH>_{~2$?{y>@LtTsOeW_TCL$PEGq8xI@TWJ5}lm=KLJ!sXYSqV^| zC>>M9-8O74d*j_BtOr9)nL0DL#UhBp$`Xb}K(&CYa^3SMymcc*!!u-khK&Jx_eGJm zeyVvkZ51G4&oMQrm>08^p;Hw$apx>!NwVY7$1v}ONzBe*eMG%ihr_o~UO&B{p}`#KAF%r5Ok@^r2M@1y_?2Iw zjGxYt5rx(+T~G6Dc42*-%hhRW?uf|>mSeS2nNwiw;jQ%e`Caty}eMNJaHR?^pI^ns4<3E z?@hf^y%D^ZW1w>hrDLIPAK>I!E#{t@0&eu9OOCQwOg&1Sn&=r3$N(GcQt?);T@wf-67#!B)4;YTi6m$@oO9~a5a!2ZyG}J+0MeY)7n@b{;o)U|_!~=PornI&dPu-foWZ3M zYiA5>6A77z>e~71Gq-VCua&NV`pi_|?DUEEhwmU%7?RdIX*A7l^B)t8e7rsDjq?E@!tkdTH|pXQYv=|@fF z5hcOJ` zOYgSzNepe$raYhM9Bs*%McR%-z3;vLwNN0cyF1GA9fa-bRO-?uJQyjdFSDv`y?)^Z zocm^DJ-ZoZ&NkLIFYEt_KAMK14(oNpvYSk%TFCDQmalmKVC8ln_2oFDgPv^?USOcx z@nCP(dokRZq$peVz6^{UV_E+Q)n@4LOUmI#H?9^+<NXVQB|z&mz{W4TrGqUl`=N zEhEZaES80?ue1@oSPMgTQ>cUV3ZANx(mx*4tk?c;Jr1F0qS(})stP0HbB_nV+0Tie zW9E{!J}f;J=S6WBi` zvKh@>C}>@^^X)_s5Z}uQq5C&U`<1X-g|zDVR-J+a`m#IL2 zecsJt1+YpO_Kj847X@MWIL4FmvtA z`izA|G1FQCyuptljr?wtVnyhk!|G(_%2&fMN)Drs8$krxuDt&fMU=AMc5w`#!J$#(Zi{ zHb+%1WXd<$f0QrmxPY|vWf9QPTJuuz{ztAag@5GKS2!ffWiiUMZu)IK((pig^#v-m zw;P~CR%LXW&WYBhBTQ`I0I`1vSKR1U)$!NcU4w_pwAOx#J74F~uv4L7+gPeMhN)(Z z$i(Y6IpI#|;2&4D88Zgt*gLr`uHOsxN1CBqlJowItf+`ygx4$rA1y5D=`!?7J72GM zH!q4hu=u!H0&utqlkSysO^ZxQ3R|x^C-8aqpq-5B!8wc56My*;!4^>Q9EzbP_WPK6 zniu;O+z6!E3?#Wn#KNBZy1F&RUdmU-|vg79|pedbt{?Z5kxnQZ97Ehe}E(@b-h+#BF(m6ocSgFZIqIIPzb) zfRWxhPwmPI>OxK&l!_|!2C|-MYJf~=+T79h-|}l$1}Pq+a`AycCyxT32Z5g50R6ui z6eW4OxEk~IM|GhfiAqYbGu!;q2+mIvOZ#D!O7y^{-&BW(G$`gBNT}^s0+9J+f#+&M zL)9HA2^EqK!F{m(NC(;khl%pXg`EDV#175WBW3tA=i(hErC9ni603Hv*pW)R`_27O z{{aqyt11A9v6sF*Ww}?P7kwqae>cMOH|1agIjy)kjLpF^Weht43}w}1J2Wg;2R>mw zn@xC6QK+05Z(s~^sB%rUgjsXD#@d#fD>w3Xb#iHAqys)0qL9ZE2;s0opq%yL%X31x zR|tlJkmRrc9a5?4f_>FRK%l(I?GTxtypK}*)i1mMoa{hfbq+R4C{1}W^Y_%-L3n$+ zqba0Kyo~lzUV6$dWW8zryzJJogP=c!w7<@jzQ7CofwDnMo3ri+`x>}fvJSKVjA#gJt;hz42z;{FAq^v1 zR?fgk4zR^9#D_;y?2zlT<@U|5L){;u2s7CZ=+$<;k?;hi%z4UKrOZB6FE-VI5E z?{irn$B@r=>T~Pr(4meTBL_ia#8c-%?*{Uo{OY6~rxr{r+u>ubakhKqWE||-T@3wQ z|9C&k28aR%lITH;Rsxg9Y~rB!TFy?$<-2e!TNTp0)~Fn^!j7Q5-*w=u5H822hzXs* za>g}?3_7)0P?ag|a$*`yXgo>Vxg1257I0woOXf)F`UE%4mhC#LZoxkwoR-xzY+*F| z3#Vd@PCYw2epxIsA0Gvy|MFBoC;O&4_&soc^@ch{Y9VaX_SyVl*2%`smb{>T+vS>~ zC6}t7XQh-I_7c?6os@~B6T0#?*O+aYI0{`t8M+mxgQji1Vz|RwHK!TlZ`;lDq1YFx z71BW~+t_Y_#QH;0>T|!vz;#a-Fb?$MVE+?}&keDRTqx59NASSDVGnDtH@8;Rj;=g7$Lm{hFKONx>$ zn}i`j7_uYc#Af z_Zt-W9(&QMk*$|mbC#t2j!<0i70kS>J|$ThM!LIE=h?iOAULM1AFzrJQHHg<+cTn> z^V!Nd6r)MhE$tJVGUe`3#jQu~YY^&$i!#Akgd?EA%Rr^=pSyYPt9t(Wv%&M3^JF$> zFUOzx)BUomj{a4%K6=a)KM=_7uJd5AL!qR_^Fh|it>4IX4}8wMkJUiOB4-96di&Gt z`T5RZzYurl)p3dXZ3^xpQckyJ72|->tJ(}VSv`UK{MK2nW%q5dcC)r@aKP$fldtrk zYt~E5dn%Y2o1NVn(fD^BwXry79yfytcAV;r_xqJvZ>qUDlH?9{buAv!Xn&urZnT_a zBvLeUK6#Tf<4H?TDBa25?QER8?%ypYs{gySTFONpGu%L2+Lda~32^CV6Wigda-7Ls z9@@Rh*P~vggSjTW&8IOO)C{ntVxx;8qOb^T@BLa-<6iOsp*a|Je4XJoToCZmS(D^{*q8U zhj`mLq ztik={1nPBY9~@5H+z`Y_31;h!NOBi*j*ENqEIWva3()=^{rK1Q7EL10KW2H2_6l9%`qQaKTarz-GLYBvf7mM=~$1u-5U(%I(-faoqcp6Q4=wWjT zoMV(&j-1~^luJ{#LiPt)t?f;o;D*4_LX&DrYKl6~`IqVIB;VP`Ci^-$lP{z3LoR(j z**adF-QvfUxZfL7cI1>?+FL>NK9y5bY}TWGK%#Q_7uGx(oSA4+qi{!r$!NtjE^KMc zQyjcL<*bUrsPIa##<7VQ9#FtXz*yMO-WKrCfPmR}CAcZyp!93`a^qmn>A~lo3HbGy z$;ltTd?K6&GqVD;l#xT1-Q5rJ>BObDe?G6#r;e!q=55Erb4J3$=G$vBU4qu>z(F!( zNte#r)YuGuaO+9`9x8BNeY0+K$u`Ayo1IJBaeyxt-^b2(EG(|dp`AJHIX1Z0UHC1f zV2VYRaOB&!B_@3vKYmfW-)EJ54Y95OXR4-Xjll1!f7dClCg?bXEcDFJ!{PJW+7Nm9 z0MN5epv1TR#rE}j{M-)09qQ){qdok( z-YdX^2O^gxP47guoSc{*9Zk#nKCr)sP^b)?pr@ovNF!`%Wz~W)K0G{)7EbPcH3$k> z;eMP;HwOK(h1FFV%K9-L6TcZ!syp4_wcACIs!@`(rO=J6`6;AXuqF+WeOgX_v}qz! zx%I0jwhPj*zCre#skZMehTL=Otv6ll7uKn`@$`pL4iq=&Ny87K?DQHpdl4xvdD4|l z#L>y^KD$0@_&mFeGU=ivtzkDVg`aJ=H5-A-YW^O7YfC7!^t?oh7Pf3er zF*s)@wi#_wy8vXX_16jO%@RjSO>)|+VJ1L@83V5}qeE!vz@01;_%rZwB zwNLWL(n!iF1{j&Gc}7QDZ>&uvw3FaK?M2*L9mB!cYi6e|02P|dAxsci!kv)=En8mj z5vxC*>0h#y05~{MeEFoaPn5$P;~B}twWN5OvQh4U-r9+=l%$4oeo1e9s)t|$@by!6 zjbwi7Cb`yK1t+Xi-+(UYPXKjy!7ToC;IU!{q zI6{D@RUNSbG+pgp}F2|#crM{iQU(m_ZBWPdCB0~bOg%x%>s9c+BkA?K}IdVc7LyddA zcPZw*j86X%=G;f9KT%3xq;eFs;{;lZ8Bsfd=}t*n`ebRJXPz#kWei%BV$ zm@`@1#hUH#3qrz2he9VP<&{nJjdFV!ImKvFgR(`^Jo-H&0CNODcGw0%*BeQC6Rp$l zeKZ8FwSA8iR+8ig#XKp!Mh#9&dO7L1`2<5(quusrBw73Iv_6lpHm{ZbaWO%-qTFW3 zjz75X(e`sEOcw?h#L^mqEvh_7%q#Xf})u%{POHC@rY;D;^LiO#X(hf0UF1cr->XHIn@$!I_E-iuc%>QFCf_|G@l4?!N5HR?hak8T(}Rne^ouLpprgD6 z*Lf60dYZmk2=wv%2Gtkno_n13lMkdQ0EB&~|0*(EK4ZtF=FIGM%-#SUm^* z>sy9NqNBa{s$7x`pPpV+6*Oy}s}uS9UVe@tc{Dj;iTk>V&u8GxJdjX~k?W?D;Pndc zKjWt*%JPa=^%0pKZBO!zZe&duD>~_$Htu$x?^x>_e{Z;zxNWA4}d^M zNq`-bj8!rr$HsO&tIi7WDd34A6M7Ks?KP6ToRdBNG)Y;xD?l7cYc2jC@vY)-R0bizCbF%wZcjX5%L>@IF|+^^C%~ zUe`iI&r$oQsIXmA_HQwmH|`9T2wO68p#LKZal4F83hg4J$!N~bm-8rDlNQ1dbbHoE zwY^LMMnYARU@P-Ot>2Yv#JdW3iY#ifts1yP^1LJ z*8sNFYDp?X%CfFP*RMpuu<=1d_MMAsB%+RAgL~J;BeF?syKS9kBl$d{&bju6AAT~o zVau-4E4GFh#a`2ZLK(NqBTzYx_!YUJDF^0^vRB`UX|jhyk_|@v8nP}M`C2gz(Yn-v zK=?Yvi|XU%8IAvy3-EP$o?mevLffXo*3PR}LXKhCXvt2*>{zRW!7!2 z!k+mUkx)^xa~Np=>;gc>6kj~p6OO3z;Vd#;P&`s)obw8g!nFF{pltHO7Lqk|%Uq_(m~E0DHSI2Z_?96R5Y0||X}7aPW| z(6Fb980L!o3^ zUp@zk+e6FI>BMGmh*M)5rj9-L`*&inr~%jh+^zjDwSUzY(ORqxks3%YZs=F(Kg6pzjv=>L{&4Ri;tH({rX_E7?6_ z5Q61keKGcd>_?Uq1Kx#sXMfpaO)onN-{|jy=UEgP2(45O%82*CF>jLSV{%Pw9}+8B z=iHO7?Vd*Vy#2N$XbC`FTZIF1bMxWNRd#24)@|Pf1lisD=FP5WSJDzk&IF5y@Dnz) z{h1>SC}Vdh?m+q^!UN`vyIbW(B@Q~eGj3bfUt7(YYus%d$L>xHVYh2-ygYGioIZy}^`#)|a&tp>9+FU7wK_qf zn*n*GNlG%oCY&`yBm}6h^<(Sm^)m2nMCazX6M;pllHZ5C*mxj!37)F)BJX#MZb3LK zFms=>a&iiZR<&{3GCIK0(%oB@KtSHmAT%l$F-D-{MQgBs z%W!V+&U`#<$Gtf?qRFFbH5VPg**7YX@#n;YIiqmYNlXi>RcmdIrXk0ylw)nQqk0|# zoO}*Hf?5@;MF7PFD7a4u(!Vyr=fAYs9hWadz#>Z&bt>>;9sG z26`vl()O5M5PRm&rMPEIK0U@l8Hk(^2)igh+RlCAxHdID^jPPZ{h{Ja-&ww~0l7in zLm=l=0YD@YjOC3Ym+C&U>oN8W`qtEmknQVxVtUg%845*S4pNE6=Xk8F;WKQyG{8_m zZ1^vU8?Afu>zw`cTJ7R9> zK;1?Et|bfmIxvfs`9X>RBC9RIT%@gWo#$16o9WLIFWoK02k`sFs%JVcm%O+~n`$O` zdUyn^HqRf{OL@zcq6^s6d-q<9Mntg2)G=rX&K+U*M4DOKE4z-N+;^J)iTxm}$ z^{LlYU-@EJ?T;TjDFR@R<>j+WO9b}-`rf&kKI=D;-1T*jnFAC9=^Dk^g|=LSMLbr~ z_n$AYQEw~R1T^4BJOE7K31LxD4#Cc!zs+c;fc9nXE~qU5u$+n)@!Fo}vZXUN46!}{ z|9Fonq$)eee!`7>v(Wb2oQ9wGYu)b$R2$>h|1uBT-ABALH1IusI)wl5~bpcshS zuLb0lHm>u-*66DgT@6hYm6=bzCgehMSBsCI5u=hYXb;+sNvc=S0#3wK-N}MWR#r;D(Uk@S zE|t&dykCFFbAwVocdT8z`AF^Bq`!<-(8`|6Gp8|&2;Jh^^bQyOutNFVA+tcI^rh32!*njrNvqsDJVFT=m!z$3;)9hqu#hy(F34=?)=-v zP?eT!Sm+2hRCQOOeD`E!Fc;=5^^z@aYR1GIlFrD?SK1^I1ln8<>TgNEjPqVCygRZ~44!^x8Q1^I#Dnb0b2l1mbnq`C%td zM7!;a!O$(=%GR%HxJE{4+*=PLr>hP>zqf8gL4vUFQ{Kj{>If1pbMmB-a6?%LwgI|4 z+rcjOBa)J#TwxmzxcIDU;$+sX=Mh(v?+w94PDp$%I2iifIy?6v{!L~tmB^ypbkG^r zvib=R-THyB7$%Q9#e18)Ii<<(!`euFeUi=9rg{3n)U-yDFDLn}h=F4FqmFK|2X|eV$ zSY}bCMm*B>gFt8dboP56ETj3G!@q#jFnKQ*$CDb@T3S52elNV@RI?p>o9{$Eoo;9; zh3661^3D{F3c(rKkMsP8rr6LS4eMFuW{LqE8?fkZPyUIf+&PY{rj{~fr)TVl>$X3s zejPh_uoa?^4ZuAXr_%?&jjsQA5&K|GxZ{Fo{EC4= zR1_b7&~D`45_httv>JS-7GoTmgLVlvO(Jjb(l!OIG+R>$bZo`4B3@S1pU9;1nEJ>6 zl4Ygp)PMu9&vBWa{#u&0L) zb!&Tp%*xHVKx*YcY^!)l+Y>ad4rq&v$0w9{51k*cI(F8utHo5|y2+ne))y-!uPKU* z7^u0iUFC9o0Klt{rmwwgRm zWb{-^D4qk_J$r(&8R=ws`V;nRbDC(OL)h&`Kwc4Kl7XmKx^3NgB{NoQrcDI zfT%PnDB{_bpugfmXMWZ4RbBWKmp*xO+WufnWMVGQt^twPYVQAMFzV3?kDGROPNAG? zREG-`ujQHE)W5&SGlcq!<&RYu)T6Cu>`uui;7NL-y3}W8mnyVio~P44<;aGxyUeS{ z{pM#a%9wk@yZiuf7ek*YebdBzgKK|=8lFU*OTNfSd<}$Z9WGDtjh^Wqfp&mO$D3OF zI(O$W@1lIMWgemGK|#MF2S?_9M%OC<>OH^Mz+v;maoH!;FJIU#Pml2!+ylq5VAIW)}!!CMZGN6!p+mz81RG@csVEQVh z4&7W?*{or0R0rP~b7FYwTgF}sws(+R-CpTU(uj<5gM z)o$lA^yE&JPu%uN&E7XVAJAjtR=ET{nOM<+fD^o=y!JdceBMzX=yY=1Ow~^SSfY4UxGy82=eb4UC&N@_6CcB%$0u-G4-RXVft@!BIy^Mlp~mF- zc$Jx|tMrS%g6_m`>EL-uH112JtqX*s0)>CWKNDiiA^C9m+MWE4g)bcoBLb)!JYOHL z>G4*%+;W~#n1lmOlc9Fq+-~a|0Z3|bx62Bz9T9?tMwT;l`K6F zdqOYZn^o3LH{rx}iPhDpV4%%|tG+@dpiX5}y4B&v>Ej@?UBI)PwY0AGn{dQkZ#(@! zR=mkVwjlViWz_q02}yB-D8u(s_Gf`!u@Oe{6DSIP98?dqiIn)38V5bUAjvwQcqzTf zc)4cf_@MY%Y0G58_UU0mMX3?Sqi6wYY&np-$pmsY1R#25dpVs{(r>kEjqS_m_{X>` z{NwYQrxR;!?O|Q#aIgzOr0{nPchLAXKr0uiC5a_Dk@LlQaRvNwNztlnyxDHnHvQyC zdE~^CcZOX{cOA-HO+eR$eav~yn)8>Q?nbX@2^atK4e}$q9750_d%9vn(}J zT$$jJL^56UjUVq-dY*n{DWx_^T{_wt@!w0;@5~{dVDF|#nO`~zBzP>xuYI{BvC9?n zr23lMNtZC|KY1;CdF12nwr>(k}SKD`%3K~2|rRPt6*R&s-;bR5>!KFB)wv!pp}RTyRpqd``j z5rl*cVU#^}x0!2aVAOPmXwDOchX-zlt}q*cbwK+&`=2UFv1U^h?NX1+$R2XL={K;F zY>7fz9Mhu@b55+!shR|M(O5!I5K4w6X3+|oYi20qveD=x z_v3F*jg?Y z`S`=ws##;*@cd&B_aMAlDGN4I&k@yT(?z*K$AE0pIJ`D3V}n5G9)p|>0-E!bE8h&} zU`4Tvt?f}SN{1XR66uftqkJ6vA*gJXz8SQ`){z@@pvG+x@?jy5?YAG2(9c&_IXgM4 zI|}OdtoKt8l%t6{b!m`6Y9Xz||Gu^yJP*BB1@WJ%5uD{i(F&s;bJk36_0gdI%e<1` z4~c-G9H4f3LKhOQ-s4Q9Z|d<>(ib)C{p{WCR9@{j0-y?NW^izXs^#H-%&|><3_t9Z z*;+L7tT8;Ov+@LeisQcVq~p2M>Eoe~t{fS6NP{~(u&UQq{FIcC4)hC^G}pW!*klYf zYi1umuD9A_ zYuXllT<7-&Mu#G9ISM!e_WU<UJxJn~$RCE2u+qm36!+asb3AJ9XYRFSQKrMlw^ zvnrsJ%Ybh?TThf&y&Q5lV5`ZO6fk89D0gK}E&%_0wzoH#OYebj7-%qZwb6CO!LKJ1 z%1pT6wuM@T5RdnT(j?Io{1AA;Z?_o|avO2V=T{zy2%RWg{Z5$StZB&Vr1imDy_YwD z*zFfpmI2Lc^&(IDTy6~@JKRBaY*HZp2jan6FmkF)LK5yfb@GzKTZ|8rnyhO8ayrp-|E(vMyEL3%D@V~Z+$zag5};$eiNuw2km zmkzXahw`+@RS^WvB%q6Ea}_=PZNpzW*99X~8c#p)$49&4(BCW8@srA1Q=`L+&rW^l zt$L`WoxzF~8`97IaVylKMdqT=8-wx-dm>5UCo_)3-72^F@&sLRYAmNhG(4}QLpNwP z>%UJsJ7fcXN{`I~kS@mV`>(ovhUCzE0Udm_#F&uAw>cNk>J;5T9FOUnWG8QZ!@-E< zp1^Q7$E)d{9-r9jy?F2L%6Z)a6Cir#$(`9Hr%){II0aL7Zx-DhSlN~T9!)30vEGvd z7W3`fm`Xt(I;OoJ0QiJuJf>O(=f2#FYQO8cAGtV4d?ATGeP!q8D^q{k=Be6z;!bY- zk~=$6C&=D*erI-TJzW^l;vrHiDo*Fm|DIXmt_XR$Y!w?+;?dSIo(L zg{`dz8{FXmX{G~?O|#oswSoScSW7#!$H`pfy-g1LmW+pBBOJcyi0zHFWI4J~{pIG! ztn(r*FBwfSmvKoru+`LL3NDbEEhBchf!Hx+7u?AkD+xE{$& zxleSQx_;A0ybSHttww1_OIF_7 z1OH#`efd9>@Av<08)0a%FOy1^#$fFGl0DgHtdSvEDod8AD7z?n*_W(i7a}Cd9qD z3%eq)qd2OVsXF23E2?|i#kXX$qx5Z_KWQ@3fCF+;roC=y>kM9RRe_oeo zJ##m~#T?Sy<#pDag35o{{R zL<^1vtXSE?o0f*PPXjeLpUWvmClv+VS~;rnFNt#fzB~N#2aWv9&)2WviVxe8$~6QI zWi$c~_>+Y`ZoJA7&*{u^t+apB-&j^J>ob7}lwVvK-8GzVw%qf5FQnDUg~&Q4gcP?| z)ZwiMo?EzYuM6C&l!N?Yg8m=3dQ|*>-0F?{wY2w^KN-;|KI;eBXl-;C zks?d?Hbq}yQ`Z?UHRQzf0*&m`KyLn;x%W{e@{OMzPs8-Qj%J+Vv@KHN^GqyL_&i>D z)az?meEWD@^`qy0m+zq@ox=Wa(upFeflrsc7B+DTIc!Zm$Dtjsbx#v@f4ubJsU74^ z`LED1;6}e^@B(Sf^d~N_qQ*3}k!!_JTp?)R`ab1U1p>I?Y;!-#C;#fluiP&2O6o?Yyo1@PjY$ftdbp94{?I1i+X@h}RVE zXu83dw?vYn^k|0Qve7IEW@zcLx)lqoY{|X?5MmO zQ>d*>T#ubr3+v!BD`RA&hhq9yF`oB~Kjjn41r0`0w8dX}C^FZ2fsFu|n<+V<7usvg zN(%Ypmz+@CTOwm`l==SYscw;!%;>RCq4nMOb>bNxxY^Ayp2b-P2Lf|s(7iMw7WPYH zJ5V0@(?7Ky7%CN02R^uSJarrz9xP2hnGa6^J|TrT&AWii0sjx9eqfVo;bW&n&G|^V z)qX3#7JzJ)P(z8;t#@^R`^g`G_a2sZv!iha7X@DxN)3gh&r4K@abwz#G!-PJII#2l z;fB`vvhZe)PU-6+804=kDw4`eNjD^lv?YsB@QdAhh%*7fCtAoTI?AV%JfCR$E4* z3l^Eyicnt-3?#c=smA&dJr;A%BGv4Xvp$kZoic0MwE%72HLvWl9}reIrD@ybA)`Ia z+rL9-D@A{cTmC+e|1Y#INGpdB<+f98$~Po=mpwszmvfo(B8z$1r^=E7h2(M14NYfs zRgsgLio441io-s_l>byh`o*>ID;}MNvk?Ljgn>JuHK7NTZ%hR5n-4R{uS)mJf0O+- zHrikDS?&y`myp&7fF;K6ZhmuoUaiJc7(TD_O%{wWP**a3a>v&|`19S__ge#N<0or% zAr3e0=w&6~VShoU_=NQTMwveOc#Zjw`;T+A;b|i!WLauv9_rb9S>E{>F3ztf*DYat zYb5?DQjHz30OTsvxc&S#;Gems!Yhy9SX1y8OYJU_sSE`8izoWM%Oy zfms%_hy~$GgaN|XLKHKd#?a`{eB8PO!_WiS>+lv3Yw<4@AWO$>7$!bnOw;-)*=tP^ z@C6N9=sz?6I)8SZ|7_>y-6r?eN8VF?|bN73#nB^nmeTvGT+VXihto!3mV#D`O z0ttFoexwxLGQP;cuN@K=`-4I#W#MV04Tw_9a^c(pc#r#;2Du~8jf*B$MIptvRX8|;w;YRw!j|@R1 z7l4%H0DcWHaAhQfGX_Egl}>W_Kz2I7YZf2gg1@=s7t9oi@PQH;f!{Hn95;!2LFx+f z9TK^T&v^GfeZcx*%m?U<`~jy8AhS6?GDSk;;^A=>veSn-2t+$?^c6tB)MR^2 zF00Atd^$Fg&PGbjK^iGDYgOEPVLbrsBH7Aw`qlj%B3F{f~6`mg)9 z0H`TGAXrA=4Nkzf?Vu_43L3qRSJ9a$8>h0OnoIvpgQ5kB;Nepv4S^k1Q_AZ`N<$n)5Dmr{^OU_~`Xlp%eAf~L*iG)m%NcZM!7(FCV){cL~Yo9SO2PRB)P z%%0aYDbvRuQRFj$PHoF;lh<+_4PXlW5BZ|5#k~rn=rZU|;BR)93V?-iczF=HYm|sBZNlF5KX^PWA;Zm1>I#S7!(B{$1cm z1ac=k^=ryAAi*uLnV=^oo~n~4O3>qp(pKlK3$s_sv1DTkkZkb(sV= z{ECzob>65r%lKoy8MA$ETG!92<*nrydI5v-7}`#KXZA_}-h6 zGZx7o2tn`Cq)t&f-=6i4_({Jp;+>`5D&7)mASE*HM6w}C^&Z=?>&mZJybHky$zoYU zq~A_vG4$L&NS!HujdU??42XU1&#elM?@nZa|@p4rhmirRz?GQb1NzGxTf9S@zG|#X~&pq`LcQ6ovfKQA{?dtRrEtA{D+&So9V8S7*xk6f;mw8xpA67N${$jkm5S(# z1CrtbdQ%R`AG6FkN0ya0HMyXFHkgRsc4VxkyiocD2%iyyuGa{DF=q7!*BuJ?uVp1r z2y20J*IT&*7^B4=e*h^KcAJ@7etGKtHy*$BExG1m*vTK`>a?^7lWVSwpZ z=sXJNz_JwTMKtWQVnl<-+jaAx_?cncGk@+Iz|b56MR9iWcwUPJ zs+@wJz`J)6wWVg5?aGeAkWNpiaRbE@{w;?}S2?{&FbMX}7?l3ES=E_Cm@ zeY*3aAW+kSKzsz7s=0f=(>)SzEPl=_XB`)A{iummr=%&G*@Ft&1iNta`w_E$ptSuY zZddqE1x1QoKNgP7I7m+`L_Hi=9PFrX=m+q3m!)Zh`Ta`Dn&g=qMbpABEsKABL^_8$ zP@_%2PJI#3JTH}R2vaD_pIo8Tw;x5D3>`BFe6cs&XExmelet&m>?%Gdic;S`e<|3| zukgH|e~wFLcthKW#M#G2EFtC+??^nS`rmB{odXHwN<-cEIp?TgelVutCUUih3>gRH zxJyh-AI|vrq|!v|=1Hl=M_3E?IGS^LNIiMi& z7bdz?3Kxp%FK6rfJx(|S*~}CfODo$~HC*kP+q}Nd`~rM|UnG?K7V^T#cx5}=$QPMk zu!CTHZi8x<8F7~)ds<)gVWjpsusSU9 zY35x?{X*yTh(wU$@&0;!vTX6MMs*UwyIbI$Rb{e&Tk4}H$g?V|w>S!bLwFj*vDy@W z5Z!3+k+WtDn_4lC6$Y<~Op~VBxl%9VA~MatsQGcL0p>~PO$LAd`B+4v-1P zPKv}czz2B<%tY4UB2^1WElDqx2nycLgewD@l}Vm= z7J}k5LCi07jtmeK{4eqI|Gx(RP%{}9V8c(?L#)C2Kr{}wNjJPZQZ3286B*I=^(!Gc zvs|64;RRnbk!EL_ZCDuEW}F5;FkIEjQLX!jO&~jql3q}Q1q5Sfn9s5K@pjf44C~}= zEe>k3#6}l9kV)0K+GHytoaUHSO$|b&P1z8LZU$I*i)+O{?emx%%)1*3Wv(}feMu+# zLTPw*cVtp;ZfJD7wh92)EGc2IPc=`s_kGIrnZz~K4WyE^a||OQBD%Xf@!Yg@bh9yJ zTv?v`Hc#Gr0xDey>M|$BeW6t_suQ+0r~{s8+K7n#BL@?ck&Qlmbn2&rgW=&}EK*)f zEU}l_Eo&#(#_fHp4RKk@(KC>o=Rxt!v$jjg(P5Jyt}ISZf66rBP=lIjI0L=+8*^}hh&Yq_^RA2LqC~pVFrUms}NANwY3ouwBH}76nx`^ zS!YTaopll>PP4;^%`Z>4e)i=v(dxOmSu(O?4H?-$X_kd`y*vy7PaWK7bIkz7o#%ie z1D6W@%y80;!9~gQOv@s^J39j40&1!88s;t`CKjuegO_pDXX9h&&!eUFWn(3_u5~rn zN!arsV(>0Z%Di%imr)ct7)1AB@y$52da1>EJ4##&9d{@IwaDPzGJRWvx1d3x?7 z4Q^CK+{J;#$dzz}O0F8_Vz>%CB+@y%Y4y?#?5({W@=Z~I1J^-qKmvqULQrDU?-5Av zatvF$-Et&CmZ|H_+S-}}V-iSA0F7u{Pa9GAm!P#-Tv1{3p(q6+HdWx{g@Da9CQ(Zu&ll}tLY2`Dr}flyWR*EOdL2VF}yWT zr?6D$p^Ug;x&ZfuSwdm1gE1e+I^uXq;GOSLS)1Oo#nZhS&Ire-u($*nue&VlWziTk%+@zyozP3 zg-$b=4#vsa!_d$?ss;iQ&&X|e!HjgBl+(01)#ZHYRM7AMJJZ_onULX*~0I8xeDEdA3iwzz_-x7?)oEf z6BKgIm9`|4E5TCVY{3=`P7Dr4YvqN&CYtSr~wYyW7{XMtW zjgSp9ctfXxSR~`Yg@On7m&27a(IU+cGvC}YA1<+cBNXeVQd{jaaC1=-m=i#1YD&RB zjgU6+vr3ac&$Lvo(ym%qmzimb;*fT;^>%O=-Op;}Hvtgqm%d3{SuAIVxH|W2jHE~+ zNTLF|H)Dy!`^Zs7I$G!TRS?<{a=`bD?p6Ezc8l`m1D9CMn0DJM#IKi*jt(s?E#ZKY z5`dx>&SIkG7q|B-z9J!@?$IXO=lx#a%u_cEBiQ0&weJordR#d_JO0I+BVyQ;kKtuY z|H6X9<#c|AW}nOQGK;H`AaVk%JCd>M4Z+7w?1hNawIsf)^v&(6!tj@2r7W*#lZ}wv z)GZ6VMtl-I!qb41v!SAcM5-{V$M@1gd+?oWUKe9_vCGCMARHH`eFZe;$C9rFrHvrp zULXpzQ3jw_{SRZMawX}8oF{hploga)%D18TxMXc~0IFu(i=>?7eq3{DPYHA}8MJhp z%%Y{?S`B#6*D>NGe~WDVw_Jze^__^e5koXPUI;Y5Jq<|1E8iuiw)7WPlYrX_sBF7! zK%y|EN-khD47!HqIzPiE=~XSjpFQs}utsx{pCLQXi3P=sI4Rh`>wAPn5^t(OU67Fo zbXg1tC;^rp`Z{!bkP$m;1s-^QpMqabPdmQN`-4XqQL~UiRo5?*cRBGZfj7_%HC@#b IWn94j0cCc2r2qf` literal 0 HcmV?d00001 diff --git a/frontend/test-results/1-login-Login-Workflow-should-support-logout-functionality-chromium/error-context.md b/frontend/test-results/1-login-Login-Workflow-should-support-logout-functionality-chromium/error-context.md new file mode 100644 index 00000000..2a30e17a --- /dev/null +++ b/frontend/test-results/1-login-Login-Workflow-should-support-logout-functionality-chromium/error-context.md @@ -0,0 +1,224 @@ +# 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/test-results/1-login-Login-Workflow-should-support-logout-functionality-chromium/test-failed-1.png b/frontend/test-results/1-login-Login-Workflow-should-support-logout-functionality-chromium/test-failed-1.png new file mode 100644 index 0000000000000000000000000000000000000000..b0b1cec4275d72ac084b270aab2d77c2c914b2f1 GIT binary patch literal 25825 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 IWn94j0cCc2r2qf` literal 0 HcmV?d00001