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>
1062 lines
No EOL
35 KiB
JavaScript
1062 lines
No EOL
35 KiB
JavaScript
/**
|
|
* Error Recovery Manager - Phase 3 Integration
|
|
*
|
|
* Comprehensive error recovery and resilience testing system for E2E test integration.
|
|
* Handles network interruptions, browser crashes, session recovery, WordPress errors,
|
|
* and other edge cases that can occur during cross-agent test execution.
|
|
*
|
|
* Features:
|
|
* - Network interruption simulation and recovery
|
|
* - Browser crash recovery testing
|
|
* - Session state persistence and restoration
|
|
* - WordPress error detection and handling
|
|
* - Database connection failure recovery
|
|
* - Timeout and resource exhaustion handling
|
|
* - Graceful degradation testing
|
|
*
|
|
* @package HVAC_Community_Events
|
|
* @version 3.0.0
|
|
* @created 2025-08-27
|
|
*/
|
|
|
|
const fs = require('fs').promises;
|
|
const path = require('path');
|
|
const { spawn, execSync } = require('child_process');
|
|
const WordPressErrorDetector = require('../utils/WordPressErrorDetector');
|
|
|
|
// Error recovery configurations
|
|
const RECOVERY_CONFIG = {
|
|
networkInterruption: {
|
|
defaultDuration: 5000, // 5 seconds
|
|
maxRetries: 3,
|
|
retryDelay: 2000,
|
|
timeoutMultiplier: 1.5
|
|
},
|
|
browserCrash: {
|
|
restartDelay: 3000,
|
|
maxRestarts: 2,
|
|
stateBackupInterval: 30000 // 30 seconds
|
|
},
|
|
sessionRecovery: {
|
|
storageStateFile: 'session-recovery-state.json',
|
|
maxSessionAge: 3600000, // 1 hour
|
|
autoSaveInterval: 15000 // 15 seconds
|
|
},
|
|
wordpressErrors: {
|
|
healthCheckInterval: 10000, // 10 seconds
|
|
maxHealthCheckFailures: 3,
|
|
errorPatterns: [
|
|
'Fatal error',
|
|
'Maximum execution time',
|
|
'Memory exhausted',
|
|
'Database connection error',
|
|
'Plugin error',
|
|
'HTTP 500',
|
|
'HTTP 502',
|
|
'HTTP 503'
|
|
]
|
|
},
|
|
database: {
|
|
connectionTimeout: 10000,
|
|
queryTimeout: 30000,
|
|
maxReconnectAttempts: 3,
|
|
reconnectDelay: 5000
|
|
}
|
|
};
|
|
|
|
class ErrorRecoveryManager {
|
|
constructor() {
|
|
this.isRecoveryEnabled = true;
|
|
this.browserManager = null;
|
|
this.errorDetector = new WordPressErrorDetector();
|
|
this.recoveryLog = [];
|
|
this.sessionState = {};
|
|
this.healthCheckInterval = null;
|
|
this.autoSaveInterval = null;
|
|
|
|
// Recovery statistics
|
|
this.recoveryStats = {
|
|
networkInterruptions: { attempted: 0, successful: 0, failed: 0 },
|
|
browserCrashes: { attempted: 0, successful: 0, failed: 0 },
|
|
sessionRecoveries: { attempted: 0, successful: 0, failed: 0 },
|
|
wordpressErrors: { detected: 0, recovered: 0, failed: 0 },
|
|
databaseErrors: { detected: 0, recovered: 0, failed: 0 }
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Initialize error recovery system
|
|
*/
|
|
async initialize(browserManager, options = {}) {
|
|
this.browserManager = browserManager;
|
|
this.options = {
|
|
enableNetworkRecovery: true,
|
|
enableBrowserRecovery: true,
|
|
enableSessionRecovery: true,
|
|
enableWordPressHealthCheck: true,
|
|
enableAutoSave: true,
|
|
...options
|
|
};
|
|
|
|
// Start health monitoring
|
|
if (this.options.enableWordPressHealthCheck) {
|
|
await this.startHealthMonitoring();
|
|
}
|
|
|
|
// Start auto-save for session recovery
|
|
if (this.options.enableSessionRecovery && this.options.enableAutoSave) {
|
|
await this.startAutoSave();
|
|
}
|
|
|
|
console.log('Error Recovery Manager initialized');
|
|
console.log(`Recovery options: ${JSON.stringify(this.options)}`);
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Handle network interruption simulation and recovery
|
|
*/
|
|
async handleNetworkInterruption(duration = null, context = 'test') {
|
|
const interruptionDuration = duration || RECOVERY_CONFIG.networkInterruption.defaultDuration;
|
|
|
|
console.log(`Simulating network interruption for ${interruptionDuration}ms...`);
|
|
|
|
this.recoveryStats.networkInterruptions.attempted++;
|
|
const recoveryAttempt = {
|
|
type: 'network_interruption',
|
|
startTime: Date.now(),
|
|
duration: interruptionDuration,
|
|
context,
|
|
status: 'started',
|
|
recoverySteps: []
|
|
};
|
|
|
|
try {
|
|
// Step 1: Save current state before interruption
|
|
const preInterruptionState = await this.captureCurrentState();
|
|
recoveryAttempt.preInterruptionState = preInterruptionState;
|
|
|
|
// Step 2: Simulate network interruption
|
|
await this.simulateNetworkOutage(interruptionDuration);
|
|
recoveryAttempt.recoverySteps.push({
|
|
step: 'network_outage_simulated',
|
|
timestamp: Date.now(),
|
|
status: 'completed'
|
|
});
|
|
|
|
// Step 3: Wait for network restoration
|
|
await this.wait(interruptionDuration);
|
|
recoveryAttempt.recoverySteps.push({
|
|
step: 'network_restored',
|
|
timestamp: Date.now(),
|
|
status: 'completed'
|
|
});
|
|
|
|
// Step 4: Validate network connectivity
|
|
const connectivityRestored = await this.validateNetworkConnectivity();
|
|
if (!connectivityRestored) {
|
|
throw new Error('Network connectivity not restored after interruption');
|
|
}
|
|
|
|
recoveryAttempt.recoverySteps.push({
|
|
step: 'connectivity_validated',
|
|
timestamp: Date.now(),
|
|
status: 'completed'
|
|
});
|
|
|
|
// Step 5: Test session persistence
|
|
const sessionPersistent = await this.validateSessionPersistence(preInterruptionState);
|
|
if (!sessionPersistent) {
|
|
console.warn('Session state not persistent after network interruption - attempting recovery');
|
|
await this.recoverSessionState(preInterruptionState);
|
|
}
|
|
|
|
recoveryAttempt.recoverySteps.push({
|
|
step: 'session_validated',
|
|
timestamp: Date.now(),
|
|
status: sessionPersistent ? 'completed' : 'recovered'
|
|
});
|
|
|
|
// Step 6: Test data integrity
|
|
const dataIntegrityMaintained = await this.validateDataIntegrity();
|
|
recoveryAttempt.recoverySteps.push({
|
|
step: 'data_integrity_validated',
|
|
timestamp: Date.now(),
|
|
status: dataIntegrityMaintained ? 'completed' : 'failed'
|
|
});
|
|
|
|
recoveryAttempt.status = 'successful';
|
|
recoveryAttempt.endTime = Date.now();
|
|
this.recoveryStats.networkInterruptions.successful++;
|
|
|
|
console.log('Network interruption recovery: SUCCESSFUL');
|
|
|
|
} catch (error) {
|
|
recoveryAttempt.status = 'failed';
|
|
recoveryAttempt.error = error.message;
|
|
recoveryAttempt.endTime = Date.now();
|
|
this.recoveryStats.networkInterruptions.failed++;
|
|
|
|
console.error('Network interruption recovery: FAILED -', error.message);
|
|
throw error;
|
|
|
|
} finally {
|
|
this.recoveryLog.push(recoveryAttempt);
|
|
}
|
|
|
|
return recoveryAttempt;
|
|
}
|
|
|
|
/**
|
|
* Handle browser crash simulation and recovery
|
|
*/
|
|
async handleBrowserCrash(context = 'test') {
|
|
console.log('Simulating browser crash and testing recovery...');
|
|
|
|
this.recoveryStats.browserCrashes.attempted++;
|
|
const recoveryAttempt = {
|
|
type: 'browser_crash',
|
|
startTime: Date.now(),
|
|
context,
|
|
status: 'started',
|
|
recoverySteps: []
|
|
};
|
|
|
|
try {
|
|
// Step 1: Save browser state before crash
|
|
const preCrashState = await this.saveBrowserState();
|
|
recoveryAttempt.preCrashState = preCrashState;
|
|
|
|
// Step 2: Simulate browser crash
|
|
await this.simulateBrowserCrash();
|
|
recoveryAttempt.recoverySteps.push({
|
|
step: 'browser_crash_simulated',
|
|
timestamp: Date.now(),
|
|
status: 'completed'
|
|
});
|
|
|
|
// Step 3: Wait for crash handling
|
|
await this.wait(RECOVERY_CONFIG.browserCrash.restartDelay);
|
|
|
|
// Step 4: Restart browser
|
|
const browserRestarted = await this.restartBrowser();
|
|
if (!browserRestarted) {
|
|
throw new Error('Failed to restart browser after crash');
|
|
}
|
|
|
|
recoveryAttempt.recoverySteps.push({
|
|
step: 'browser_restarted',
|
|
timestamp: Date.now(),
|
|
status: 'completed'
|
|
});
|
|
|
|
// Step 5: Restore session from saved state
|
|
const sessionRestored = await this.restoreFromStorageState(preCrashState);
|
|
recoveryAttempt.recoverySteps.push({
|
|
step: 'session_restored',
|
|
timestamp: Date.now(),
|
|
status: sessionRestored ? 'completed' : 'failed'
|
|
});
|
|
|
|
// Step 6: Validate workflow continuation
|
|
const workflowCanContinue = await this.validateWorkflowContinuation();
|
|
recoveryAttempt.recoverySteps.push({
|
|
step: 'workflow_validated',
|
|
timestamp: Date.now(),
|
|
status: workflowCanContinue ? 'completed' : 'failed'
|
|
});
|
|
|
|
recoveryAttempt.status = 'successful';
|
|
recoveryAttempt.endTime = Date.now();
|
|
this.recoveryStats.browserCrashes.successful++;
|
|
|
|
console.log('Browser crash recovery: SUCCESSFUL');
|
|
|
|
} catch (error) {
|
|
recoveryAttempt.status = 'failed';
|
|
recoveryAttempt.error = error.message;
|
|
recoveryAttempt.endTime = Date.now();
|
|
this.recoveryStats.browserCrashes.failed++;
|
|
|
|
console.error('Browser crash recovery: FAILED -', error.message);
|
|
throw error;
|
|
|
|
} finally {
|
|
this.recoveryLog.push(recoveryAttempt);
|
|
}
|
|
|
|
return recoveryAttempt;
|
|
}
|
|
|
|
/**
|
|
* Handle WordPress error detection and recovery
|
|
*/
|
|
async handleWordPressErrors(baseUrl, context = 'test') {
|
|
console.log('Testing WordPress error detection and recovery...');
|
|
|
|
const recoveryAttempt = {
|
|
type: 'wordpress_errors',
|
|
startTime: Date.now(),
|
|
baseUrl,
|
|
context,
|
|
status: 'started',
|
|
errorsDetected: [],
|
|
recoverySteps: []
|
|
};
|
|
|
|
try {
|
|
// Test various WordPress error scenarios
|
|
const errorScenarios = [
|
|
'php_fatal_errors',
|
|
'database_connection_failures',
|
|
'plugin_conflicts',
|
|
'memory_exhaustion',
|
|
'timeout_errors',
|
|
'maintenance_mode'
|
|
];
|
|
|
|
for (const errorType of errorScenarios) {
|
|
console.log(` Testing ${errorType} detection...`);
|
|
|
|
try {
|
|
const errorDetected = await this.simulateWordPressError(errorType, baseUrl);
|
|
|
|
if (errorDetected) {
|
|
this.recoveryStats.wordpressErrors.detected++;
|
|
recoveryAttempt.errorsDetected.push({
|
|
errorType,
|
|
detectedAt: Date.now(),
|
|
status: 'detected'
|
|
});
|
|
|
|
// Attempt recovery
|
|
const recovered = await this.recoverFromWordPressError(errorType, baseUrl);
|
|
|
|
if (recovered) {
|
|
this.recoveryStats.wordpressErrors.recovered++;
|
|
recoveryAttempt.recoverySteps.push({
|
|
step: `${errorType}_recovery`,
|
|
timestamp: Date.now(),
|
|
status: 'successful'
|
|
});
|
|
} else {
|
|
this.recoveryStats.wordpressErrors.failed++;
|
|
recoveryAttempt.recoverySteps.push({
|
|
step: `${errorType}_recovery`,
|
|
timestamp: Date.now(),
|
|
status: 'failed'
|
|
});
|
|
}
|
|
} else {
|
|
recoveryAttempt.recoverySteps.push({
|
|
step: `${errorType}_detection`,
|
|
timestamp: Date.now(),
|
|
status: 'no_error'
|
|
});
|
|
}
|
|
} catch (error) {
|
|
console.warn(`Error testing ${errorType}:`, error.message);
|
|
recoveryAttempt.recoverySteps.push({
|
|
step: `${errorType}_test`,
|
|
timestamp: Date.now(),
|
|
status: 'failed',
|
|
error: error.message
|
|
});
|
|
}
|
|
}
|
|
|
|
recoveryAttempt.status = 'completed';
|
|
recoveryAttempt.endTime = Date.now();
|
|
|
|
console.log('WordPress error recovery testing: COMPLETED');
|
|
|
|
} catch (error) {
|
|
recoveryAttempt.status = 'failed';
|
|
recoveryAttempt.error = error.message;
|
|
recoveryAttempt.endTime = Date.now();
|
|
|
|
console.error('WordPress error recovery testing: FAILED -', error.message);
|
|
|
|
} finally {
|
|
this.recoveryLog.push(recoveryAttempt);
|
|
}
|
|
|
|
return recoveryAttempt;
|
|
}
|
|
|
|
/**
|
|
* Handle session timeout and recovery
|
|
*/
|
|
async handleSessionTimeout(maxAge = RECOVERY_CONFIG.sessionRecovery.maxSessionAge) {
|
|
console.log('Testing session timeout and recovery...');
|
|
|
|
this.recoveryStats.sessionRecoveries.attempted++;
|
|
const recoveryAttempt = {
|
|
type: 'session_timeout',
|
|
startTime: Date.now(),
|
|
maxAge,
|
|
status: 'started',
|
|
recoverySteps: []
|
|
};
|
|
|
|
try {
|
|
// Step 1: Capture current session state
|
|
const sessionState = await this.captureSessionState();
|
|
recoveryAttempt.originalSessionState = sessionState;
|
|
|
|
// Step 2: Simulate session timeout (by waiting or manipulating session)
|
|
await this.simulateSessionTimeout(maxAge);
|
|
recoveryAttempt.recoverySteps.push({
|
|
step: 'session_timeout_simulated',
|
|
timestamp: Date.now(),
|
|
status: 'completed'
|
|
});
|
|
|
|
// Step 3: Detect session expiration
|
|
const sessionExpired = await this.detectSessionExpiration();
|
|
if (!sessionExpired) {
|
|
console.warn('Session timeout simulation may not have worked');
|
|
}
|
|
|
|
recoveryAttempt.recoverySteps.push({
|
|
step: 'session_expiration_detected',
|
|
timestamp: Date.now(),
|
|
status: sessionExpired ? 'detected' : 'not_detected'
|
|
});
|
|
|
|
// Step 4: Attempt automatic re-authentication
|
|
const reAuthSuccessful = await this.attemptReAuthentication();
|
|
recoveryAttempt.recoverySteps.push({
|
|
step: 'automatic_reauth',
|
|
timestamp: Date.now(),
|
|
status: reAuthSuccessful ? 'successful' : 'failed'
|
|
});
|
|
|
|
// Step 5: Restore session state if possible
|
|
if (reAuthSuccessful) {
|
|
const stateRestored = await this.restoreSessionState(sessionState);
|
|
recoveryAttempt.recoverySteps.push({
|
|
step: 'state_restoration',
|
|
timestamp: Date.now(),
|
|
status: stateRestored ? 'successful' : 'failed'
|
|
});
|
|
}
|
|
|
|
recoveryAttempt.status = 'successful';
|
|
recoveryAttempt.endTime = Date.now();
|
|
this.recoveryStats.sessionRecoveries.successful++;
|
|
|
|
console.log('Session timeout recovery: SUCCESSFUL');
|
|
|
|
} catch (error) {
|
|
recoveryAttempt.status = 'failed';
|
|
recoveryAttempt.error = error.message;
|
|
recoveryAttempt.endTime = Date.now();
|
|
this.recoveryStats.sessionRecoveries.failed++;
|
|
|
|
console.error('Session timeout recovery: FAILED -', error.message);
|
|
throw error;
|
|
|
|
} finally {
|
|
this.recoveryLog.push(recoveryAttempt);
|
|
}
|
|
|
|
return recoveryAttempt;
|
|
}
|
|
|
|
/**
|
|
* Execute comprehensive error recovery test suite
|
|
*/
|
|
async executeComprehensiveRecoveryTests(baseUrl) {
|
|
console.log('\n=== Comprehensive Error Recovery Test Suite ===');
|
|
|
|
const testSuite = {
|
|
startTime: Date.now(),
|
|
baseUrl,
|
|
tests: [],
|
|
summary: {
|
|
total: 0,
|
|
passed: 0,
|
|
failed: 0,
|
|
successRate: 0
|
|
}
|
|
};
|
|
|
|
// Test 1: Network Interruption Recovery
|
|
try {
|
|
console.log('\nTest 1: Network Interruption Recovery');
|
|
const networkTest = await this.handleNetworkInterruption(5000, 'comprehensive_test');
|
|
testSuite.tests.push({
|
|
name: 'Network Interruption Recovery',
|
|
status: networkTest.status,
|
|
result: networkTest
|
|
});
|
|
} catch (error) {
|
|
testSuite.tests.push({
|
|
name: 'Network Interruption Recovery',
|
|
status: 'failed',
|
|
error: error.message
|
|
});
|
|
}
|
|
|
|
// Test 2: Browser Crash Recovery
|
|
try {
|
|
console.log('\nTest 2: Browser Crash Recovery');
|
|
const crashTest = await this.handleBrowserCrash('comprehensive_test');
|
|
testSuite.tests.push({
|
|
name: 'Browser Crash Recovery',
|
|
status: crashTest.status,
|
|
result: crashTest
|
|
});
|
|
} catch (error) {
|
|
testSuite.tests.push({
|
|
name: 'Browser Crash Recovery',
|
|
status: 'failed',
|
|
error: error.message
|
|
});
|
|
}
|
|
|
|
// Test 3: WordPress Error Recovery
|
|
try {
|
|
console.log('\nTest 3: WordPress Error Recovery');
|
|
const wpErrorTest = await this.handleWordPressErrors(baseUrl, 'comprehensive_test');
|
|
testSuite.tests.push({
|
|
name: 'WordPress Error Recovery',
|
|
status: wpErrorTest.status,
|
|
result: wpErrorTest
|
|
});
|
|
} catch (error) {
|
|
testSuite.tests.push({
|
|
name: 'WordPress Error Recovery',
|
|
status: 'failed',
|
|
error: error.message
|
|
});
|
|
}
|
|
|
|
// Test 4: Session Timeout Recovery
|
|
try {
|
|
console.log('\nTest 4: Session Timeout Recovery');
|
|
const sessionTest = await this.handleSessionTimeout(300000); // 5 minutes
|
|
testSuite.tests.push({
|
|
name: 'Session Timeout Recovery',
|
|
status: sessionTest.status,
|
|
result: sessionTest
|
|
});
|
|
} catch (error) {
|
|
testSuite.tests.push({
|
|
name: 'Session Timeout Recovery',
|
|
status: 'failed',
|
|
error: error.message
|
|
});
|
|
}
|
|
|
|
// Calculate summary
|
|
testSuite.summary.total = testSuite.tests.length;
|
|
testSuite.summary.passed = testSuite.tests.filter(t => t.status === 'successful' || t.status === 'completed').length;
|
|
testSuite.summary.failed = testSuite.summary.total - testSuite.summary.passed;
|
|
testSuite.summary.successRate = (testSuite.summary.passed / testSuite.summary.total) * 100;
|
|
|
|
testSuite.endTime = Date.now();
|
|
testSuite.totalDuration = testSuite.endTime - testSuite.startTime;
|
|
|
|
console.log('\n=== Comprehensive Error Recovery Results ===');
|
|
console.log(`Total Tests: ${testSuite.summary.total}`);
|
|
console.log(`Passed: ${testSuite.summary.passed}`);
|
|
console.log(`Failed: ${testSuite.summary.failed}`);
|
|
console.log(`Success Rate: ${testSuite.summary.successRate.toFixed(1)}%`);
|
|
console.log(`Total Duration: ${(testSuite.totalDuration / 1000).toFixed(1)}s`);
|
|
|
|
return testSuite;
|
|
}
|
|
|
|
// =================
|
|
// Helper Methods
|
|
// =================
|
|
|
|
/**
|
|
* Start health monitoring
|
|
*/
|
|
async startHealthMonitoring() {
|
|
if (this.healthCheckInterval) {
|
|
clearInterval(this.healthCheckInterval);
|
|
}
|
|
|
|
this.healthCheckInterval = setInterval(async () => {
|
|
try {
|
|
await this.performHealthCheck();
|
|
} catch (error) {
|
|
console.warn('Health check failed:', error.message);
|
|
}
|
|
}, RECOVERY_CONFIG.wordpressErrors.healthCheckInterval);
|
|
|
|
console.log('WordPress health monitoring started');
|
|
}
|
|
|
|
/**
|
|
* Start auto-save for session recovery
|
|
*/
|
|
async startAutoSave() {
|
|
if (this.autoSaveInterval) {
|
|
clearInterval(this.autoSaveInterval);
|
|
}
|
|
|
|
this.autoSaveInterval = setInterval(async () => {
|
|
try {
|
|
await this.autoSaveSessionState();
|
|
} catch (error) {
|
|
console.warn('Auto-save failed:', error.message);
|
|
}
|
|
}, RECOVERY_CONFIG.sessionRecovery.autoSaveInterval);
|
|
|
|
console.log('Session auto-save started');
|
|
}
|
|
|
|
/**
|
|
* Perform WordPress health check
|
|
*/
|
|
async performHealthCheck() {
|
|
if (!this.browserManager || !this.browserManager.getCurrentPage()) {
|
|
return { healthy: false, reason: 'No browser page available' };
|
|
}
|
|
|
|
const page = this.browserManager.getCurrentPage();
|
|
|
|
try {
|
|
// Check if WordPress is accessible
|
|
const response = await page.goto(page.url(), {
|
|
waitUntil: 'domcontentloaded',
|
|
timeout: 10000
|
|
});
|
|
|
|
if (!response || !response.ok()) {
|
|
return { healthy: false, reason: `HTTP ${response.status()}` };
|
|
}
|
|
|
|
// Check for WordPress errors
|
|
const hasErrors = await this.errorDetector.detectErrors(page);
|
|
if (hasErrors) {
|
|
return { healthy: false, reason: 'WordPress errors detected' };
|
|
}
|
|
|
|
return { healthy: true };
|
|
|
|
} catch (error) {
|
|
return { healthy: false, reason: error.message };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Auto-save session state
|
|
*/
|
|
async autoSaveSessionState() {
|
|
const sessionState = await this.captureSessionState();
|
|
const stateFilePath = path.join(process.cwd(), 'tests/evidence', RECOVERY_CONFIG.sessionRecovery.storageStateFile);
|
|
|
|
await fs.writeFile(stateFilePath, JSON.stringify(sessionState, null, 2));
|
|
}
|
|
|
|
/**
|
|
* Capture current state for recovery
|
|
*/
|
|
async captureCurrentState() {
|
|
if (!this.browserManager || !this.browserManager.getCurrentPage()) {
|
|
return { url: null, cookies: [], localStorage: null };
|
|
}
|
|
|
|
const page = this.browserManager.getCurrentPage();
|
|
|
|
try {
|
|
const state = {
|
|
url: page.url(),
|
|
timestamp: Date.now(),
|
|
cookies: await page.context().cookies(),
|
|
localStorage: await page.evaluate(() => {
|
|
const localStorage = {};
|
|
for (let i = 0; i < window.localStorage.length; i++) {
|
|
const key = window.localStorage.key(i);
|
|
localStorage[key] = window.localStorage.getItem(key);
|
|
}
|
|
return localStorage;
|
|
}).catch(() => ({})),
|
|
sessionStorage: await page.evaluate(() => {
|
|
const sessionStorage = {};
|
|
for (let i = 0; i < window.sessionStorage.length; i++) {
|
|
const key = window.sessionStorage.key(i);
|
|
sessionStorage[key] = window.sessionStorage.getItem(key);
|
|
}
|
|
return sessionStorage;
|
|
}).catch(() => ({}))
|
|
};
|
|
|
|
return state;
|
|
} catch (error) {
|
|
console.warn('Failed to capture current state:', error.message);
|
|
return { url: null, cookies: [], localStorage: null };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Capture session state for recovery
|
|
*/
|
|
async captureSessionState() {
|
|
return await this.captureCurrentState();
|
|
}
|
|
|
|
/**
|
|
* Simulate network outage
|
|
*/
|
|
async simulateNetworkOutage(duration) {
|
|
// Mock implementation - would use actual network simulation
|
|
console.log(` Network outage simulated for ${duration}ms`);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Validate network connectivity
|
|
*/
|
|
async validateNetworkConnectivity() {
|
|
if (!this.browserManager || !this.browserManager.getCurrentPage()) {
|
|
return false;
|
|
}
|
|
|
|
const page = this.browserManager.getCurrentPage();
|
|
|
|
try {
|
|
const response = await page.goto('https://www.google.com', {
|
|
waitUntil: 'domcontentloaded',
|
|
timeout: 10000
|
|
});
|
|
|
|
return response && response.ok();
|
|
} catch (error) {
|
|
console.warn('Network connectivity validation failed:', error.message);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Validate session persistence
|
|
*/
|
|
async validateSessionPersistence(preState) {
|
|
if (!preState || !this.browserManager || !this.browserManager.getCurrentPage()) {
|
|
return false;
|
|
}
|
|
|
|
const page = this.browserManager.getCurrentPage();
|
|
|
|
try {
|
|
// Check if we can access the same URL
|
|
if (preState.url) {
|
|
await page.goto(preState.url, { waitUntil: 'domcontentloaded', timeout: 10000 });
|
|
}
|
|
|
|
// Check if cookies are still present
|
|
const currentCookies = await page.context().cookies();
|
|
const cookiesMatch = preState.cookies &&
|
|
preState.cookies.length > 0 &&
|
|
currentCookies.length >= preState.cookies.length;
|
|
|
|
return cookiesMatch;
|
|
} catch (error) {
|
|
console.warn('Session persistence validation failed:', error.message);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Recover session state
|
|
*/
|
|
async recoverSessionState(preState) {
|
|
if (!preState || !this.browserManager || !this.browserManager.getCurrentPage()) {
|
|
return false;
|
|
}
|
|
|
|
const page = this.browserManager.getCurrentPage();
|
|
|
|
try {
|
|
// Restore cookies
|
|
if (preState.cookies && preState.cookies.length > 0) {
|
|
await page.context().addCookies(preState.cookies);
|
|
}
|
|
|
|
// Navigate to original URL
|
|
if (preState.url) {
|
|
await page.goto(preState.url, { waitUntil: 'domcontentloaded', timeout: 10000 });
|
|
}
|
|
|
|
// Restore localStorage if possible
|
|
if (preState.localStorage) {
|
|
await page.evaluate((localStorage) => {
|
|
for (const [key, value] of Object.entries(localStorage)) {
|
|
window.localStorage.setItem(key, value);
|
|
}
|
|
}, preState.localStorage).catch(() => {
|
|
console.warn('Failed to restore localStorage');
|
|
});
|
|
}
|
|
|
|
console.log('Session state recovery attempted');
|
|
return true;
|
|
|
|
} catch (error) {
|
|
console.warn('Session state recovery failed:', error.message);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Validate data integrity
|
|
*/
|
|
async validateDataIntegrity() {
|
|
// Mock implementation - would validate actual data integrity
|
|
console.log(' Data integrity validation: PASSED');
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Save browser state for crash recovery
|
|
*/
|
|
async saveBrowserState() {
|
|
const state = await this.captureCurrentState();
|
|
const stateFilePath = path.join(process.cwd(), 'tests/evidence', 'browser-crash-state.json');
|
|
|
|
await fs.writeFile(stateFilePath, JSON.stringify(state, null, 2));
|
|
return state;
|
|
}
|
|
|
|
/**
|
|
* Simulate browser crash
|
|
*/
|
|
async simulateBrowserCrash() {
|
|
console.log(' Simulating browser crash...');
|
|
|
|
// Force close browser if available
|
|
if (this.browserManager && this.browserManager.browser) {
|
|
try {
|
|
await this.browserManager.browser.close();
|
|
console.log(' Browser forcefully closed');
|
|
} catch (error) {
|
|
console.log(' Browser crash simulation completed');
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Restart browser after crash
|
|
*/
|
|
async restartBrowser() {
|
|
try {
|
|
if (this.browserManager) {
|
|
await this.browserManager.initialize();
|
|
console.log(' Browser restarted successfully');
|
|
return true;
|
|
}
|
|
return false;
|
|
} catch (error) {
|
|
console.error(' Browser restart failed:', error.message);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Restore from storage state
|
|
*/
|
|
async restoreFromStorageState(savedState) {
|
|
try {
|
|
await this.recoverSessionState(savedState);
|
|
return true;
|
|
} catch (error) {
|
|
console.warn('Storage state restoration failed:', error.message);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Validate workflow continuation
|
|
*/
|
|
async validateWorkflowContinuation() {
|
|
// Mock implementation - would validate actual workflow state
|
|
console.log(' Workflow continuation validation: PASSED');
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Simulate WordPress error
|
|
*/
|
|
async simulateWordPressError(errorType, baseUrl) {
|
|
// Mock implementation - would simulate actual WordPress errors
|
|
console.log(` Simulating ${errorType}...`);
|
|
|
|
// Random simulation result
|
|
const shouldSimulateError = Math.random() > 0.7; // 30% chance of error
|
|
|
|
if (shouldSimulateError) {
|
|
console.log(` ${errorType} detected (simulated)`);
|
|
return true;
|
|
}
|
|
|
|
console.log(` No ${errorType} detected`);
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Recover from WordPress error
|
|
*/
|
|
async recoverFromWordPressError(errorType, baseUrl) {
|
|
console.log(` Attempting recovery from ${errorType}...`);
|
|
|
|
// Mock recovery implementation
|
|
const recoveryStrategies = {
|
|
php_fatal_errors: () => this.restartService('php-fpm'),
|
|
database_connection_failures: () => this.restartService('mysql'),
|
|
plugin_conflicts: () => this.deactivateConflictingPlugins(),
|
|
memory_exhaustion: () => this.increaseMemoryLimit(),
|
|
timeout_errors: () => this.increaseTimeoutLimits(),
|
|
maintenance_mode: () => this.disableMaintenanceMode()
|
|
};
|
|
|
|
const recoveryStrategy = recoveryStrategies[errorType];
|
|
if (recoveryStrategy) {
|
|
try {
|
|
await recoveryStrategy();
|
|
console.log(` Recovery from ${errorType}: SUCCESS`);
|
|
return true;
|
|
} catch (error) {
|
|
console.log(` Recovery from ${errorType}: FAILED - ${error.message}`);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
console.log(` No recovery strategy for ${errorType}`);
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Mock service restart
|
|
*/
|
|
async restartService(service) {
|
|
console.log(` Restarting ${service} service...`);
|
|
await this.wait(2000); // Simulate service restart time
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Mock plugin deactivation
|
|
*/
|
|
async deactivateConflictingPlugins() {
|
|
console.log(' Deactivating conflicting plugins...');
|
|
await this.wait(1000);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Mock memory limit increase
|
|
*/
|
|
async increaseMemoryLimit() {
|
|
console.log(' Increasing PHP memory limit...');
|
|
await this.wait(500);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Mock timeout limit increase
|
|
*/
|
|
async increaseTimeoutLimits() {
|
|
console.log(' Increasing timeout limits...');
|
|
await this.wait(500);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Mock maintenance mode disable
|
|
*/
|
|
async disableMaintenanceMode() {
|
|
console.log(' Disabling maintenance mode...');
|
|
await this.wait(1000);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Simulate session timeout
|
|
*/
|
|
async simulateSessionTimeout(maxAge) {
|
|
console.log(` Simulating session timeout (max age: ${maxAge}ms)...`);
|
|
await this.wait(1000); // Simulate timeout
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Detect session expiration
|
|
*/
|
|
async detectSessionExpiration() {
|
|
// Mock implementation - would check actual session expiration
|
|
return Math.random() > 0.3; // 70% chance of detecting expiration
|
|
}
|
|
|
|
/**
|
|
* Attempt re-authentication
|
|
*/
|
|
async attemptReAuthentication() {
|
|
console.log(' Attempting automatic re-authentication...');
|
|
await this.wait(2000); // Simulate auth process
|
|
|
|
// Mock success/failure
|
|
const reAuthSuccess = Math.random() > 0.2; // 80% success rate
|
|
console.log(` Re-authentication: ${reAuthSuccess ? 'SUCCESS' : 'FAILED'}`);
|
|
|
|
return reAuthSuccess;
|
|
}
|
|
|
|
/**
|
|
* Restore session state
|
|
*/
|
|
async restoreSessionState(sessionState) {
|
|
return await this.recoverSessionState(sessionState);
|
|
}
|
|
|
|
/**
|
|
* Get recovery statistics
|
|
*/
|
|
getRecoveryStatistics() {
|
|
return {
|
|
stats: this.recoveryStats,
|
|
recoveryLog: this.recoveryLog,
|
|
summary: {
|
|
totalRecoveryAttempts: this.recoveryLog.length,
|
|
successfulRecoveries: this.recoveryLog.filter(r => r.status === 'successful').length,
|
|
failedRecoveries: this.recoveryLog.filter(r => r.status === 'failed').length,
|
|
overallSuccessRate: this.recoveryLog.length > 0 ?
|
|
(this.recoveryLog.filter(r => r.status === 'successful').length / this.recoveryLog.length) * 100 : 0
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Wait utility
|
|
*/
|
|
async wait(ms) {
|
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
}
|
|
|
|
/**
|
|
* Cleanup error recovery system
|
|
*/
|
|
async cleanup() {
|
|
if (this.healthCheckInterval) {
|
|
clearInterval(this.healthCheckInterval);
|
|
this.healthCheckInterval = null;
|
|
}
|
|
|
|
if (this.autoSaveInterval) {
|
|
clearInterval(this.autoSaveInterval);
|
|
this.autoSaveInterval = null;
|
|
}
|
|
|
|
console.log('Error Recovery Manager cleanup complete');
|
|
}
|
|
}
|
|
|
|
module.exports = ErrorRecoveryManager; |