# Instructions - Following Playwright test failed. - Explain why, be concise, respect Playwright best practices. - Provide a snippet of code with the fix, if possible. # Test info - Name: 1-login.spec.ts >> Login Workflow >> should reject invalid local user credentials - Location: e2e/workflows/1-login.spec.ts:66:7 # Error details ``` Test timeout of 30000ms exceeded. ``` ``` Error: page.fill: Test timeout of 30000ms exceeded. Call log: - waiting for locator('[data-testid="local-username-input"]') ``` # Page snapshot ```yaml - generic [ref=e1]: - generic [ref=e3]: - generic [ref=e4]: - img [ref=e6] - heading "Identity Check" [level=2] [ref=e9] - paragraph [ref=e10]: Select operator profile or use direct login - generic [ref=e12]: - generic [ref=e13]: - button [ref=e14] [cursor=pointer]: - img [ref=e15] - generic [ref=e18]: - paragraph [ref=e19]: Logging in as - paragraph [ref=e20]: Manual Input - generic [ref=e21]: - text: Username - generic [ref=e22]: - img [ref=e23] - textbox "Admin" [ref=e26] - generic [ref=e27]: - text: Password - generic [ref=e28]: - img [ref=e29] - textbox "Enter password" [active] [ref=e32] - button "Verify Identity" [ref=e33] [cursor=pointer] - button "Open Next.js Dev Tools" [ref=e39] [cursor=pointer]: - img [ref=e40] - alert [ref=e43] ``` # Test source ```ts 1 | import { test, expect } from '@playwright/test'; 2 | import * as auth from '../fixtures/auth'; 3 | import * as assertions from '../utils/assertions'; 4 | import { LDAP_USERS, LOCAL_USERS } from '../fixtures/test-data'; 5 | 6 | test.describe('Login Workflow', () => { 7 | const BASE_URL = process.env.BASE_URL || 'http://localhost:8917'; 8 | 9 | test.beforeEach(async ({ page }) => { 10 | // Navigate to login page 11 | await page.goto(BASE_URL); 12 | // Wait for identity check overlay 13 | await page.waitForSelector('[data-testid="identity-check-overlay"]', { timeout: 10000 }); 14 | }); 15 | 16 | test('should display identity check overlay on app load', async ({ page }) => { 17 | const overlay = page.locator('[data-testid="identity-check-overlay"]'); 18 | await expect(overlay).toBeVisible(); 19 | 20 | // Verify tabs are available 21 | const ldapTab = page.locator('[data-testid="ldap-login-tab"]'); 22 | const localTab = page.locator('[data-testid="local-login-tab"]'); 23 | 24 | await expect(ldapTab).toBeVisible(); 25 | await expect(localTab).toBeVisible(); 26 | }); 27 | 28 | test('should display login form with username and password fields', async ({ page }) => { 29 | await assertions.assertLoginFormVisible(page); 30 | }); 31 | 32 | test('should reject empty login credentials', async ({ page }) => { 33 | const submitButton = page.locator('[data-testid="login-submit"]'); 34 | await submitButton.click(); 35 | 36 | // Should show validation error 37 | const errorMessage = page.locator('[data-testid="toast-error"]'); 38 | await expect(errorMessage).toBeVisible({ timeout: 3000 }); 39 | }); 40 | 41 | test('should reject invalid LDAP credentials', async ({ page }) => { 42 | await auth.loginWithLdap(page, LDAP_USERS.invalid, BASE_URL); 43 | 44 | // Should show error message 45 | const errorToast = page.locator('[data-testid="toast-error"]'); 46 | await expect(errorToast).toBeVisible({ timeout: 5000 }); 47 | 48 | // Should still be on login page 49 | await expect(page).toHaveURL(BASE_URL); 50 | }); 51 | 52 | test('should successfully login with valid LDAP credentials', async ({ page }) => { 53 | // Skip if LDAP is not configured in test environment 54 | test.skip(process.env.SKIP_LDAP === 'true', 'LDAP not available in test environment'); 55 | 56 | await auth.loginWithLdap(page, LDAP_USERS.valid, BASE_URL); 57 | 58 | // Verify we're authenticated 59 | await assertions.assertUserAuthenticated(page, BASE_URL); 60 | 61 | // Verify token is stored 62 | const token = await auth.getAuthToken(page); 63 | expect(token).toBeTruthy(); 64 | }); 65 | 66 | test('should reject invalid local user credentials', async ({ page }) => { 67 | // Switch to local user tab 68 | const localTab = page.locator('[data-testid="local-login-tab"]'); 69 | await localTab.click(); 70 | 71 | // Try to login with invalid credentials > 72 | await page.fill('[data-testid="local-username-input"]', 'invalid_user'); | ^ Error: page.fill: Test timeout of 30000ms exceeded. 73 | await page.fill('[data-testid="local-password-input"]', 'wrong_password'); 74 | await page.click('[data-testid="local-login-submit"]'); 75 | 76 | // Should show error 77 | const errorToast = page.locator('[data-testid="toast-error"]'); 78 | await expect(errorToast).toBeVisible({ timeout: 5000 }); 79 | }); 80 | 81 | test('should successfully login with valid local user credentials', async ({ page }) => { 82 | // Switch to local user tab 83 | const localTab = page.locator('[data-testid="local-login-tab"]'); 84 | await localTab.click(); 85 | 86 | // Login with valid credentials 87 | await auth.loginWithLocalUser(page, LOCAL_USERS.admin, BASE_URL); 88 | 89 | // Verify authenticated 90 | await assertions.assertUserAuthenticated(page, BASE_URL); 91 | }); 92 | 93 | test('should show user identity after successful login', async ({ page }) => { 94 | await auth.loginWithLocalUser(page, LOCAL_USERS.admin, BASE_URL); 95 | 96 | // Check that user info is displayed 97 | const userDisplay = page.locator('[data-testid="user-display"]'); 98 | await expect(userDisplay).toBeVisible(); 99 | 100 | // Verify username is shown 101 | const username = await userDisplay.textContent(); 102 | expect(username).toContain(LOCAL_USERS.admin.username); 103 | }); 104 | 105 | test('should maintain session across page reloads', async ({ page }) => { 106 | await auth.loginWithLocalUser(page, LOCAL_USERS.admin, BASE_URL); 107 | await assertions.assertUserAuthenticated(page, BASE_URL); 108 | 109 | // Reload page 110 | await page.reload(); 111 | 112 | // Should still be authenticated 113 | const token = await auth.getAuthToken(page); 114 | expect(token).toBeTruthy(); 115 | 116 | // Should not be on login page 117 | await expect(page).not.toHaveURL(BASE_URL); 118 | }); 119 | 120 | test('should support logout functionality', async ({ page }) => { 121 | // Login first 122 | await auth.loginWithLocalUser(page, LOCAL_USERS.admin, BASE_URL); 123 | await assertions.assertUserAuthenticated(page, BASE_URL); 124 | 125 | // Logout 126 | await auth.logout(page, BASE_URL); 127 | 128 | // Should be back on login page 129 | const identityOverlay = page.locator('[data-testid="identity-check-overlay"]'); 130 | await expect(identityOverlay).toBeVisible({ timeout: 5000 }); 131 | 132 | // Token should be cleared 133 | const token = await auth.getAuthToken(page); 134 | expect(token).toBeFalsy(); 135 | }); 136 | 137 | test('should show admin button for admin users', async ({ page }) => { 138 | await auth.loginWithLocalUser(page, LOCAL_USERS.admin, BASE_URL); 139 | await assertions.assertUserAuthenticated(page, BASE_URL); 140 | 141 | // Admin button should be visible 142 | const adminButton = page.locator('[data-testid="admin-button"]'); 143 | await expect(adminButton).toBeVisible(); 144 | }); 145 | 146 | test('should allow switching between LDAP and local login tabs', async ({ page }) => { 147 | const ldapTab = page.locator('[data-testid="ldap-login-tab"]'); 148 | const localTab = page.locator('[data-testid="local-login-tab"]'); 149 | 150 | // Start on LDAP tab 151 | let ldapForm = page.locator('[data-testid="ldap-login-form"]'); 152 | await expect(ldapForm).toBeVisible(); 153 | 154 | // Switch to local 155 | await localTab.click(); 156 | ldapForm = page.locator('[data-testid="ldap-login-form"]'); 157 | await expect(ldapForm).not.toBeVisible(); 158 | 159 | // Switch back to LDAP 160 | await ldapTab.click(); 161 | ldapForm = page.locator('[data-testid="ldap-login-form"]'); 162 | await expect(ldapForm).toBeVisible(); 163 | }); 164 | 165 | test('should remember login preference on revisit', async ({ page, context }) => { 166 | // Login with local user 167 | await auth.loginWithLocalUser(page, LOCAL_USERS.admin, BASE_URL); 168 | 169 | // Close and reopen browser 170 | await page.close(); 171 | const newPage = await context.newPage(); 172 | await newPage.goto(BASE_URL); ```