/** * Authentication Setup and State Management * * Provides shared authentication utilities for all HVAC test suites: * - Role-based authentication (trainer, master_trainer, admin) * - Authentication state storage and reuse * - Login/logout helpers * - Session management * - Security best practices * * @package HVAC_Community_Events * @version 3.0.0 * @created 2025-08-20 */ const { expect } = require('@playwright/test'); const path = require('path'); const fs = require('fs').promises; // Authentication configuration const AUTH_CONFIG = { baseUrl: process.env.UPSKILL_STAGING_URL || 'https://upskill-staging.measurequick.com', loginUrl: '/training-login/', wpLoginUrl: '/wp-login.php', timeout: 15000, storageDir: path.join(__dirname, '../.auth'), // Test user credentials for different roles users: { trainer: { email: 'test_trainer@example.com', password: 'TestTrainer123!', role: 'hvac_trainer', dashboardPath: '/trainer/dashboard/', storageFile: 'trainer-auth.json' }, master_trainer: { email: 'test_master@example.com', password: 'TestMaster123!', role: 'hvac_master_trainer', dashboardPath: '/master-trainer/master-dashboard/', storageFile: 'master-trainer-auth.json' }, admin: { email: 'admin@example.com', password: 'AdminTest123!', role: 'administrator', dashboardPath: '/wp-admin/', storageFile: 'admin-auth.json' } } }; /** * Ensure authentication storage directory exists */ async function ensureAuthStorageDir() { try { await fs.mkdir(AUTH_CONFIG.storageDir, { recursive: true }); } catch (error) { console.warn('Could not create auth storage directory:', error.message); } } /** * Save authentication state to file * @param {string} userType - User type (trainer, master_trainer, admin) * @param {Object} context - Browser context with authentication */ async function saveAuthState(userType, context) { await ensureAuthStorageDir(); const user = AUTH_CONFIG.users[userType]; if (!user) { throw new Error(`Unknown user type: ${userType}`); } const storageFile = path.join(AUTH_CONFIG.storageDir, user.storageFile); try { await context.storageState({ path: storageFile }); console.log(`✅ Authentication state saved for ${userType}: ${storageFile}`); } catch (error) { console.warn(`Could not save auth state for ${userType}:`, error.message); } } /** * Load authentication state from file * @param {string} userType - User type (trainer, master_trainer, admin) * @returns {string|null} - Path to storage file or null if not exists */ async function getAuthStatePath(userType) { const user = AUTH_CONFIG.users[userType]; if (!user) { throw new Error(`Unknown user type: ${userType}`); } const storageFile = path.join(AUTH_CONFIG.storageDir, user.storageFile); try { await fs.access(storageFile); return storageFile; } catch { return null; } } /** * Clear authentication state for user type * @param {string} userType - User type (trainer, master_trainer, admin) */ async function clearAuthState(userType = null) { await ensureAuthStorageDir(); if (userType) { const user = AUTH_CONFIG.users[userType]; if (user) { const storageFile = path.join(AUTH_CONFIG.storageDir, user.storageFile); try { await fs.unlink(storageFile); console.log(`🗑️ Cleared auth state for ${userType}`); } catch (error) { // File doesn't exist, that's fine } } } else { // Clear all auth states for (const [type, user] of Object.entries(AUTH_CONFIG.users)) { const storageFile = path.join(AUTH_CONFIG.storageDir, user.storageFile); try { await fs.unlink(storageFile); console.log(`🗑️ Cleared auth state for ${type}`); } catch { // File doesn't exist, that's fine } } } } /** * Verify user is authenticated and has correct role access * @param {Page} page - Playwright page object * @param {string} userType - Expected user type */ async function verifyAuthentication(page, userType) { const user = AUTH_CONFIG.users[userType]; if (!user) { throw new Error(`Unknown user type: ${userType}`); } // Navigate to expected dashboard await page.goto(`${AUTH_CONFIG.baseUrl}${user.dashboardPath}`, { waitUntil: 'networkidle', timeout: AUTH_CONFIG.timeout }); // Verify we're not on login page const currentUrl = page.url(); if (currentUrl.includes('/wp-login.php') || currentUrl.includes('/training-login/')) { throw new Error(`Authentication failed for ${userType} - redirected to login`); } // Verify dashboard is accessible await expect(page.locator('body')).toBeVisible({ timeout: 5000 }); // Role-specific verification switch (userType) { case 'trainer': await expect(page.locator('text=Dashboard, text=Trainer Dashboard')).toBeVisible({ timeout: 5000 }); break; case 'master_trainer': await expect(page.locator('text=Master Dashboard, text=Master Trainer')).toBeVisible({ timeout: 5000 }); break; case 'admin': await expect(page.locator('#wpadminbar, .wp-admin')).toBeVisible({ timeout: 5000 }); break; } console.log(`✅ Authentication verified for ${userType}`); } /** * Perform login for specified user type * @param {Page} page - Playwright page object * @param {string} userType - User type (trainer, master_trainer, admin) * @param {Object} options - Login options */ async function performLogin(page, userType, options = {}) { const user = AUTH_CONFIG.users[userType]; if (!user) { throw new Error(`Unknown user type: ${userType}`); } const useWpLogin = options.useWpLogin || userType === 'admin'; const loginUrl = useWpLogin ? AUTH_CONFIG.wpLoginUrl : AUTH_CONFIG.loginUrl; console.log(`🔑 Logging in as ${userType} via ${loginUrl}`); // Navigate to login page await page.goto(`${AUTH_CONFIG.baseUrl}${loginUrl}`, { waitUntil: 'networkidle', timeout: AUTH_CONFIG.timeout }); // Wait for login form await page.waitForSelector('#user_login, input[name="log"], input[type="email"]', { timeout: 10000 }); // Fill login form (handle different form structures) const emailField = await page.locator('#user_login, input[name="log"], input[type="email"]').first(); const passwordField = await page.locator('#user_pass, input[name="pwd"], input[type="password"]').first(); const submitButton = await page.locator('#wp-submit, button[type="submit"], input[type="submit"]').first(); await emailField.fill(user.email); await passwordField.fill(user.password); // Submit form and wait for navigation await Promise.all([ page.waitForURL(url => !url.includes('/wp-login.php') && !url.includes('/training-login/'), { timeout: AUTH_CONFIG.timeout }), submitButton.click() ]); // Verify login was successful await verifyAuthentication(page, userType); console.log(`✅ Login successful for ${userType}`); } /** * Logout current user * @param {Page} page - Playwright page object */ async function performLogout(page) { try { // Try different logout methods const logoutSelectors = [ 'a[href*="wp-login.php?action=logout"]', 'a[href*="logout"]', 'text=Logout', 'text=Log Out', '#wp-admin-bar-logout a' ]; for (const selector of logoutSelectors) { const logoutLink = await page.locator(selector).first(); if (await logoutLink.isVisible()) { await logoutLink.click(); await page.waitForLoadState('networkidle'); break; } } console.log('✅ Logout completed'); } catch (error) { console.warn('Logout may have failed:', error.message); } } /** * Get user configuration for specified type * @param {string} userType - User type * @returns {Object} User configuration */ function getUserConfig(userType) { const user = AUTH_CONFIG.users[userType]; if (!user) { throw new Error(`Unknown user type: ${userType}`); } return { ...user }; } /** * Get all available user types * @returns {string[]} Array of user types */ function getAvailableUserTypes() { return Object.keys(AUTH_CONFIG.users); } module.exports = { AUTH_CONFIG, ensureAuthStorageDir, saveAuthState, getAuthStatePath, clearAuthState, verifyAuthentication, performLogin, performLogout, getUserConfig, getAvailableUserTypes };