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; |