243 lines
6.1 KiB
TypeScript
243 lines
6.1 KiB
TypeScript
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);
|
|
}
|