feat: create auth fixture for e2e login and session management
This commit is contained in:
242
frontend/e2e/fixtures/auth.ts
Normal file
242
frontend/e2e/fixtures/auth.ts
Normal file
@@ -0,0 +1,242 @@
|
||||
import { Page, BrowserContext } from '@playwright/test';
|
||||
|
||||
/**
|
||||
* Authentication fixture for E2E tests
|
||||
* Handles login flows and session management
|
||||
*/
|
||||
|
||||
export interface LoginCredentials {
|
||||
username: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export interface AuthSession {
|
||||
token: string;
|
||||
userId: string;
|
||||
email: string;
|
||||
isAdmin: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform LDAP authentication
|
||||
*/
|
||||
export async function loginWithLdap(
|
||||
page: Page,
|
||||
credentials: LoginCredentials,
|
||||
baseUrl: string = 'http://localhost'
|
||||
): Promise<void> {
|
||||
await page.goto(`${baseUrl}`);
|
||||
|
||||
// Wait for login form to appear
|
||||
await page.waitForSelector('[data-testid="login-form"]', { timeout: 5000 });
|
||||
|
||||
// Fill in username
|
||||
await page.fill('[data-testid="username-input"]', credentials.username);
|
||||
|
||||
// Fill in password
|
||||
await page.fill('[data-testid="password-input"]', credentials.password);
|
||||
|
||||
// Click login button
|
||||
await page.click('[data-testid="login-submit"]');
|
||||
|
||||
// Wait for navigation to inventory page
|
||||
await page.waitForURL(`${baseUrl}/*`, { waitUntil: 'networkidle' });
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform local user authentication
|
||||
*/
|
||||
export async function loginWithLocalUser(
|
||||
page: Page,
|
||||
credentials: LoginCredentials,
|
||||
baseUrl: string = 'http://localhost'
|
||||
): Promise<void> {
|
||||
await page.goto(`${baseUrl}`);
|
||||
|
||||
// Wait for identity check overlay
|
||||
await page.waitForSelector('[data-testid="identity-check-overlay"]', { timeout: 5000 });
|
||||
|
||||
// Click "Local User" tab if it exists
|
||||
const localUserTab = page.locator('[data-testid="local-user-tab"]');
|
||||
if (await localUserTab.isVisible()) {
|
||||
await localUserTab.click();
|
||||
}
|
||||
|
||||
// Fill in username
|
||||
await page.fill('[data-testid="local-username-input"]', credentials.username);
|
||||
|
||||
// Fill in password
|
||||
await page.fill('[data-testid="local-password-input"]', credentials.password);
|
||||
|
||||
// Click login button
|
||||
await page.click('[data-testid="local-login-submit"]');
|
||||
|
||||
// Wait for navigation to inventory page
|
||||
await page.waitForURL(`${baseUrl}/*`, { waitUntil: 'networkidle' });
|
||||
}
|
||||
|
||||
/**
|
||||
* Logout from the application
|
||||
*/
|
||||
export async function logout(page: Page, baseUrl: string = 'http://localhost'): Promise<void> {
|
||||
// Click logout button or menu
|
||||
const logoutButton = page.locator('[data-testid="logout-button"]');
|
||||
if (await logoutButton.isVisible()) {
|
||||
await logoutButton.click();
|
||||
} else {
|
||||
// Try finding it in menu
|
||||
const menu = page.locator('[data-testid="user-menu"]');
|
||||
if (await menu.isVisible()) {
|
||||
await menu.click();
|
||||
await page.click('[data-testid="logout-menu-item"]');
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for redirect to login
|
||||
await page.waitForURL(`${baseUrl}/*`, { timeout: 5000 });
|
||||
}
|
||||
|
||||
/**
|
||||
* Get authentication token from local storage
|
||||
*/
|
||||
export async function getAuthToken(page: Page): Promise<string | null> {
|
||||
const token = await page.evaluate(() => {
|
||||
return localStorage.getItem('auth_token') || sessionStorage.getItem('auth_token');
|
||||
});
|
||||
return token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set authentication token in local storage
|
||||
*/
|
||||
export async function setAuthToken(page: Page, token: string): Promise<void> {
|
||||
await page.evaluate((t) => {
|
||||
localStorage.setItem('auth_token', t);
|
||||
}, token);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear authentication data
|
||||
*/
|
||||
export async function clearAuth(page: Page): Promise<void> {
|
||||
await page.evaluate(() => {
|
||||
localStorage.removeItem('auth_token');
|
||||
sessionStorage.removeItem('auth_token');
|
||||
localStorage.removeItem('user_data');
|
||||
sessionStorage.removeItem('user_data');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user is authenticated
|
||||
*/
|
||||
export async function isAuthenticated(page: Page): Promise<boolean> {
|
||||
const token = await getAuthToken(page);
|
||||
return !!token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current user info from page
|
||||
*/
|
||||
export async function getCurrentUser(page: Page): Promise<{ username: string; email: string } | null> {
|
||||
try {
|
||||
const userData = await page.evaluate(() => {
|
||||
const stored = localStorage.getItem('user_data') || sessionStorage.getItem('user_data');
|
||||
return stored ? JSON.parse(stored) : null;
|
||||
});
|
||||
return userData;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for authentication to complete
|
||||
*/
|
||||
export async function waitForAuth(page: Page, timeout: number = 5000): Promise<void> {
|
||||
// Wait for either:
|
||||
// 1. Token to be in storage
|
||||
// 2. Navigation to happen
|
||||
// 3. Auth overlay to disappear
|
||||
await page.waitForFunction(
|
||||
() => {
|
||||
const token =
|
||||
typeof window !== 'undefined'
|
||||
? localStorage.getItem('auth_token') || sessionStorage.getItem('auth_token')
|
||||
: null;
|
||||
return !!token;
|
||||
},
|
||||
{ timeout }
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify login was successful by checking inventory page loaded
|
||||
*/
|
||||
export async function verifyLoginSuccess(page: Page, baseUrl: string = 'http://localhost'): Promise<boolean> {
|
||||
try {
|
||||
// Check if we're on the inventory page
|
||||
await page.waitForURL(/inventory|dashboard/, { timeout: 5000 });
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create test user via API (helper for setup)
|
||||
*/
|
||||
export async function createTestUserViaApi(
|
||||
baseUrl: string,
|
||||
token: string,
|
||||
user: { username: string; password: string; email: string }
|
||||
): Promise<{ id: string; username: string }> {
|
||||
const response = await fetch(`${baseUrl}/api/admin/users`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${token}`,
|
||||
},
|
||||
body: JSON.stringify(user),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to create user: ${response.statusText}`);
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete test user via API (helper for cleanup)
|
||||
*/
|
||||
export async function deleteTestUserViaApi(
|
||||
baseUrl: string,
|
||||
token: string,
|
||||
userId: string
|
||||
): Promise<void> {
|
||||
const response = await fetch(`${baseUrl}/api/admin/users/${userId}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to delete user: ${response.statusText}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to setup admin context for tests
|
||||
*/
|
||||
export async function setupAdminContext(
|
||||
context: BrowserContext,
|
||||
baseUrl: string,
|
||||
adminToken: string
|
||||
): Promise<void> {
|
||||
// Add auth headers to all API requests
|
||||
await context.addInitScript((token) => {
|
||||
(window as any).__testAuthToken = token;
|
||||
}, adminToken);
|
||||
}
|
||||
Reference in New Issue
Block a user