Some checks are pending
		
		
	
	HVAC Plugin CI/CD Pipeline / Security Analysis (push) Waiting to run
				
			HVAC Plugin CI/CD Pipeline / Code Quality & Standards (push) Waiting to run
				
			HVAC Plugin CI/CD Pipeline / Unit Tests (push) Waiting to run
				
			HVAC Plugin CI/CD Pipeline / Integration Tests (push) Waiting to run
				
			HVAC Plugin CI/CD Pipeline / Deploy to Staging (push) Blocked by required conditions
				
			HVAC Plugin CI/CD Pipeline / Deploy to Production (push) Blocked by required conditions
				
			HVAC Plugin CI/CD Pipeline / Notification (push) Blocked by required conditions
				
			Security Monitoring & Compliance / Dependency Vulnerability Scan (push) Waiting to run
				
			Security Monitoring & Compliance / Secrets & Credential Scan (push) Waiting to run
				
			Security Monitoring & Compliance / WordPress Security Analysis (push) Waiting to run
				
			Security Monitoring & Compliance / Static Code Security Analysis (push) Waiting to run
				
			Security Monitoring & Compliance / Security Compliance Validation (push) Waiting to run
				
			Security Monitoring & Compliance / Security Summary Report (push) Blocked by required conditions
				
			Security Monitoring & Compliance / Security Team Notification (push) Blocked by required conditions
				
			- Add 90+ test files including E2E, unit, and integration tests - Implement Page Object Model (POM) architecture - Add Docker testing environment with comprehensive services - Include modernized test framework with error recovery - Add specialized test suites for master trainer and trainer workflows - Update .gitignore to properly track test infrastructure 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
		
			
				
	
	
		
			358 lines
		
	
	
		
			No EOL
		
	
	
		
			11 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			358 lines
		
	
	
		
			No EOL
		
	
	
		
			11 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /**
 | |
|  * 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<void>}
 | |
|      */
 | |
|     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<void>}
 | |
|      */
 | |
|     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<string|null>} 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<void>}
 | |
|      */
 | |
|     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<void>}
 | |
|      */
 | |
|     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<void>}
 | |
|      */
 | |
|     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<void>}
 | |
|      */
 | |
|     async wait(ms) {
 | |
|         await new Promise(resolve => setTimeout(resolve, ms));
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Create evidence directories
 | |
|      * @returns {Promise<void>}
 | |
|      * @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<void>}
 | |
|      * @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; |