/** * Base Test Class - Foundation for all test classes * Provides common test functionality, setup, and teardown */ const { getBrowserManager } = require('../browser/BrowserManager'); const { getAuthManager } = require('../authentication/AuthManager'); const path = require('path'); const fs = require('fs').promises; class BaseTest { constructor(testName = 'UnnamedTest') { this.testName = testName; this.browserManager = getBrowserManager(); this.authManager = getAuthManager(); this.config = null; this.testResults = []; this.startTime = null; this.evidenceDir = path.join(process.cwd(), 'tests/evidence'); } /** * Set up test environment * @param {Object} config - Test configuration * @returns {Promise} */ async setUp(config = {}) { this.config = { environment: 'staging', headless: true, slowMo: 0, timeout: 30000, screenshotOnFailure: true, ...config }; this.startTime = Date.now(); // Create evidence directories await this.createEvidenceDirectories(); // Initialize browser await this.browserManager.initialize(this.config); // Set up auth manager await this.authManager.initialize(this.config); console.log(`๐Ÿงช Starting test: ${this.testName}`); } /** * Clean up test environment * @returns {Promise} */ async tearDown() { const endTime = Date.now(); const duration = endTime - this.startTime; try { // Generate test report await this.generateTestReport(duration); // Clean up authentication await this.authManager.cleanup(); // Clean up browser await this.browserManager.cleanup(); console.log(`โœ… Completed test: ${this.testName} (${duration}ms)`); } catch (error) { console.error(`โŒ Error during teardown: ${error.message}`); } } /** * Run a test step with error handling and reporting * @param {string} stepName - Name of the test step * @param {Function} testFunction - Test function to execute * @param {Object} options - Test options * @returns {Promise<*>} Test result */ async runTestStep(stepName, testFunction, options = {}) { const stepStartTime = Date.now(); const stepConfig = { screenshotOnFailure: true, continueOnFailure: false, ...options }; console.log(` ๐Ÿ“‹ ${stepName}`); try { const result = await testFunction(); const stepEndTime = Date.now(); const stepDuration = stepEndTime - stepStartTime; this.testResults.push({ step: stepName, status: 'passed', duration: stepDuration, result: result }); console.log(` โœ… Passed (${stepDuration}ms)`); return result; } catch (error) { const stepEndTime = Date.now(); const stepDuration = stepEndTime - stepStartTime; // Take screenshot on failure if enabled if (stepConfig.screenshotOnFailure) { await this.takeFailureScreenshot(stepName, error); } this.testResults.push({ step: stepName, status: 'failed', duration: stepDuration, error: error.message, stack: error.stack }); console.error(` โŒ Failed (${stepDuration}ms): ${error.message}`); if (!stepConfig.continueOnFailure) { throw error; } return null; } } /** * Take screenshot on test failure * @param {string} stepName - Name of failed step * @param {Error} error - Error that occurred * @returns {Promise} Path to screenshot or null */ async takeFailureScreenshot(stepName, error) { try { const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); const filename = `${this.testName}-${stepName}-failure-${timestamp}.png`; const screenshotPath = await this.browserManager.takeScreenshot(filename); console.log(` ๐Ÿ“ธ Screenshot saved: ${filename}`); return screenshotPath; } catch (screenshotError) { console.warn(`Failed to take failure screenshot: ${screenshotError.message}`); return null; } } /** * Assert condition with custom message * @param {boolean} condition - Condition to assert * @param {string} message - Error message if assertion fails * @param {*} expected - Expected value * @param {*} actual - Actual value * @returns {void} */ assert(condition, message, expected = null, actual = null) { if (!condition) { let errorMessage = message; if (expected !== null && actual !== null) { errorMessage += ` Expected: ${expected}, Actual: ${actual}`; } throw new Error(errorMessage); } } /** * Assert that two values are equal * @param {*} actual - Actual value * @param {*} expected - Expected value * @param {string} message - Custom error message * @returns {void} */ assertEqual(actual, expected, message = '') { const defaultMessage = `Values are not equal.`; this.assert( actual === expected, message || defaultMessage, expected, actual ); } /** * Assert that value is truthy * @param {*} value - Value to check * @param {string} message - Custom error message * @returns {void} */ assertTrue(value, message = '') { const defaultMessage = `Value is not truthy.`; this.assert(!!value, message || defaultMessage, true, !!value); } /** * Assert that value is falsy * @param {*} value - Value to check * @param {string} message - Custom error message * @returns {void} */ assertFalse(value, message = '') { const defaultMessage = `Value is not falsy.`; this.assert(!value, message || defaultMessage, false, !!value); } /** * Assert that element exists on page * @param {string} selector - CSS selector * @param {string} message - Custom error message * @returns {Promise} */ async assertElementExists(selector, message = '') { const page = this.browserManager.getCurrentPage(); const exists = await page.$(selector) !== null; const defaultMessage = `Element not found: ${selector}`; this.assert(exists, message || defaultMessage); } /** * Assert that element is visible * @param {string} selector - CSS selector * @param {string} message - Custom error message * @returns {Promise} */ async assertElementVisible(selector, message = '') { const page = this.browserManager.getCurrentPage(); const visible = await page.isVisible(selector); const defaultMessage = `Element not visible: ${selector}`; this.assert(visible, message || defaultMessage); } /** * Assert that element contains text * @param {string} selector - CSS selector * @param {string} expectedText - Expected text * @param {string} message - Custom error message * @returns {Promise} */ async assertElementText(selector, expectedText, message = '') { const page = this.browserManager.getCurrentPage(); const actualText = await page.textContent(selector); const defaultMessage = `Element text mismatch for ${selector}`; this.assert( actualText && actualText.includes(expectedText), message || defaultMessage, expectedText, actualText ); } /** * Assert that URL matches pattern * @param {RegExp|string} pattern - URL pattern * @param {string} message - Custom error message * @returns {void} */ assertUrlMatches(pattern, message = '') { const page = this.browserManager.getCurrentPage(); const currentUrl = page.url(); const matches = pattern instanceof RegExp ? pattern.test(currentUrl) : currentUrl.includes(pattern); const defaultMessage = `URL does not match pattern. Pattern: ${pattern}, URL: ${currentUrl}`; this.assert(matches, message || defaultMessage, pattern, currentUrl); } /** * Wait with timeout * @param {number} ms - Milliseconds to wait * @returns {Promise} */ async wait(ms) { await new Promise(resolve => setTimeout(resolve, ms)); } /** * Create evidence directories * @returns {Promise} * @private */ async createEvidenceDirectories() { const dirs = [ path.join(this.evidenceDir, 'screenshots'), path.join(this.evidenceDir, 'videos'), path.join(this.evidenceDir, 'reports'), path.join(this.evidenceDir, 'logs') ]; for (const dir of dirs) { try { await fs.mkdir(dir, { recursive: true }); } catch (error) { console.warn(`Failed to create directory ${dir}: ${error.message}`); } } } /** * Generate test report * @param {number} totalDuration - Total test duration * @returns {Promise} * @private */ async generateTestReport(totalDuration) { const report = { testName: this.testName, startTime: this.startTime, endTime: Date.now(), totalDuration: totalDuration, config: this.config, results: this.testResults, summary: this.getTestSummary() }; const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); const reportPath = path.join( this.evidenceDir, 'reports', `${this.testName}-${timestamp}.json` ); try { await fs.writeFile(reportPath, JSON.stringify(report, null, 2)); console.log(`๐Ÿ“Š Test report saved: ${reportPath}`); } catch (error) { console.warn(`Failed to save test report: ${error.message}`); } } /** * Get test summary statistics * @returns {Object} Test summary * @private */ getTestSummary() { const passed = this.testResults.filter(r => r.status === 'passed').length; const failed = this.testResults.filter(r => r.status === 'failed').length; const total = this.testResults.length; return { total: total, passed: passed, failed: failed, successRate: total > 0 ? ((passed / total) * 100).toFixed(2) : '0' }; } } module.exports = BaseTest;