chore(e2e): resolve 5 failing tests and track auth guard issue
Fixed TEST issues (5 tests):
proxy-hosts.spec.ts: Added dismissDomainDialog() helper to handle
"New Base Domain Detected" modal before Save button clicks
auth-fixtures.ts: Updated logoutUser() to use text-based selector
that matches emoji button (🚪 Logout)
authentication.spec.ts: Added wait time for 401 response handling
to allow UI to react before assertion
Tracked CODE issue (1 test):
Created frontend-auth-guard-reload.md for session
expiration redirect failure (requires frontend code changes)
Test results: 247/252 passing (98% pass rate)
Before fixes: 242/252 (96%)
Improvement: +5 tests, +2% pass rate
Part of E2E testing initiative per Definition of Done
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
# [Frontend] Add Auth Guard on Page Reload
|
||||
|
||||
## Summary
|
||||
The frontend does not validate authentication state on page load/reload. When a user's session expires or authentication tokens are cleared, reloading the page should redirect to `/login`, but currently it does not.
|
||||
|
||||
## Failing Test
|
||||
- **File**: `tests/core/authentication.spec.ts`
|
||||
- **Test**: `should redirect to login when session expires`
|
||||
- **Line**: ~310
|
||||
|
||||
## Steps to Reproduce
|
||||
1. Log in to the application
|
||||
2. Open browser dev tools
|
||||
3. Clear localStorage and cookies
|
||||
4. Reload the page
|
||||
5. **Expected**: Redirect to `/login`
|
||||
6. **Actual**: Page remains on current route (e.g., `/dashboard`)
|
||||
|
||||
## Root Cause
|
||||
The frontend auth context/provider does not check for valid authentication tokens on initial page load. Auth validation only occurs when API calls return 401 errors.
|
||||
|
||||
## Proposed Solution
|
||||
|
||||
### Option 1: Auth Guard in Route Protection
|
||||
Add an auth check in the route protection layer that runs on initial load:
|
||||
|
||||
```typescript
|
||||
// In AuthProvider or route guard
|
||||
useEffect(() => {
|
||||
const token = localStorage.getItem('auth_token');
|
||||
if (!token) {
|
||||
navigate('/login');
|
||||
return;
|
||||
}
|
||||
|
||||
// Optionally validate token with backend
|
||||
validateToken(token).catch(() => {
|
||||
clearAuth();
|
||||
navigate('/login');
|
||||
});
|
||||
}, []);
|
||||
```
|
||||
|
||||
### Option 2: API Client Interceptor
|
||||
Add a 401 handler in the API client that redirects to login:
|
||||
|
||||
```typescript
|
||||
// In API client setup
|
||||
client.interceptors.response.use(
|
||||
response => response,
|
||||
error => {
|
||||
if (error.response?.status === 401) {
|
||||
clearAuth();
|
||||
window.location.href = '/login';
|
||||
}
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
## Priority
|
||||
**Medium** - Security improvement but not critical since API calls still require valid auth.
|
||||
|
||||
## Labels
|
||||
- frontend
|
||||
- security
|
||||
- auth
|
||||
- enhancement
|
||||
|
||||
## Related
|
||||
- Fixes E2E test: `should redirect to login when session expires`
|
||||
- Part of Phase 1 E2E testing backlog
|
||||
@@ -352,9 +352,18 @@ test.describe('Authentication Flows', () => {
|
||||
|
||||
await test.step('Trigger an API call by navigating', async () => {
|
||||
await page.goto('/proxy-hosts');
|
||||
// Wait for the 401 response to be processed and UI to react
|
||||
await page.waitForTimeout(2000);
|
||||
});
|
||||
|
||||
await test.step('Verify redirect to login or error message', async () => {
|
||||
// Wait for potential redirect to login page
|
||||
try {
|
||||
await page.waitForURL(/\/login/, { timeout: 5000 });
|
||||
} catch {
|
||||
// If no redirect, check for session expired message
|
||||
}
|
||||
|
||||
// Should either redirect to login or show session expired message
|
||||
const isLoginPage = page.url().includes('/login');
|
||||
const hasSessionExpiredMessage = await page
|
||||
|
||||
@@ -21,6 +21,19 @@ import {
|
||||
generateProxyHost,
|
||||
type ProxyHostConfig,
|
||||
} from '../fixtures/proxy-hosts';
|
||||
import type { Page } from '@playwright/test';
|
||||
|
||||
/**
|
||||
* Helper to dismiss the "New Base Domain Detected" dialog if it appears.
|
||||
* This dialog asks if the user wants to save the domain to their domain list.
|
||||
*/
|
||||
async function dismissDomainDialog(page: Page): Promise<void> {
|
||||
const noThanksBtn = page.getByRole('button', { name: /No, thanks/i });
|
||||
if (await noThanksBtn.isVisible({ timeout: 2000 }).catch(() => false)) {
|
||||
await noThanksBtn.click();
|
||||
await page.waitForTimeout(300);
|
||||
}
|
||||
}
|
||||
|
||||
test.describe('Proxy Hosts - CRUD Operations', () => {
|
||||
test.beforeEach(async ({ page, adminUser }) => {
|
||||
@@ -254,6 +267,9 @@ test.describe('Proxy Hosts - CRUD Operations', () => {
|
||||
const domainInput = page.locator('#domain-names');
|
||||
await domainInput.fill(hostConfig.domain);
|
||||
|
||||
// Dismiss the "New Base Domain Detected" dialog if it appears after domain input
|
||||
await dismissDomainDialog(page);
|
||||
|
||||
// Forward Host
|
||||
const forwardHostInput = page.locator('#forward-host');
|
||||
await forwardHostInput.fill(hostConfig.forwardHost);
|
||||
@@ -265,9 +281,15 @@ test.describe('Proxy Hosts - CRUD Operations', () => {
|
||||
});
|
||||
|
||||
await test.step('Submit form', async () => {
|
||||
// Dismiss the "New Base Domain Detected" dialog if it appears
|
||||
await dismissDomainDialog(page);
|
||||
|
||||
const saveButton = getSaveButton(page);
|
||||
await saveButton.click();
|
||||
|
||||
// Dismiss domain dialog again in case it appeared after Save click
|
||||
await dismissDomainDialog(page);
|
||||
|
||||
// Handle "Unsaved changes" confirmation dialog if it appears
|
||||
const confirmDialog = page.getByRole('button', { name: /yes.*save/i });
|
||||
if (await confirmDialog.isVisible({ timeout: 2000 }).catch(() => false)) {
|
||||
@@ -319,6 +341,9 @@ test.describe('Proxy Hosts - CRUD Operations', () => {
|
||||
});
|
||||
|
||||
await test.step('Submit and verify', async () => {
|
||||
// Dismiss the "New Base Domain Detected" dialog if it appears
|
||||
await dismissDomainDialog(page);
|
||||
|
||||
await getSaveButton(page).click();
|
||||
|
||||
// Handle "Unsaved changes" confirmation dialog if it appears
|
||||
@@ -358,6 +383,9 @@ test.describe('Proxy Hosts - CRUD Operations', () => {
|
||||
});
|
||||
|
||||
await test.step('Submit and verify', async () => {
|
||||
// Dismiss the "New Base Domain Detected" dialog if it appears
|
||||
await dismissDomainDialog(page);
|
||||
|
||||
await getSaveButton(page).click();
|
||||
|
||||
// Handle "Unsaved changes" confirmation dialog if it appears
|
||||
|
||||
Vendored
+8
-16
@@ -165,24 +165,16 @@ export async function loginUser(
|
||||
* @param page - Playwright Page instance
|
||||
*/
|
||||
export async function logoutUser(page: import('@playwright/test').Page): Promise<void> {
|
||||
// Try common logout patterns
|
||||
const logoutButton = page.getByRole('button', { name: /logout|sign out/i });
|
||||
const logoutLink = page.getByRole('link', { name: /logout|sign out/i });
|
||||
const userMenu = page.getByRole('button', { name: /user|profile|account/i });
|
||||
// Use text-based selector that handles emoji prefix (🚪 Logout)
|
||||
// The button text contains "Logout" - use case-insensitive text matching
|
||||
const logoutButton = page.getByText(/logout/i).first();
|
||||
|
||||
// If there's a user menu, click it first
|
||||
if (await userMenu.isVisible()) {
|
||||
await userMenu.click();
|
||||
}
|
||||
// Wait for the logout button to be visible and click it
|
||||
await logoutButton.waitFor({ state: 'visible', timeout: 10000 });
|
||||
await logoutButton.click();
|
||||
|
||||
// Click logout
|
||||
if (await logoutButton.isVisible()) {
|
||||
await logoutButton.click();
|
||||
} else if (await logoutLink.isVisible()) {
|
||||
await logoutLink.click();
|
||||
}
|
||||
|
||||
await page.waitForURL('/login');
|
||||
// Wait for redirect to login page
|
||||
await page.waitForURL(/\/login/, { timeout: 15000 });
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user