upskill-event-manager/wordpress-dev/bin/auto-recovery.sh
bengizmo 1bdb4f7de8 docs: Add comprehensive testing resilience documentation and scripts
- Added DEPLOYMENT-RESILIENCE.md with strategies for resilient testing
- Created TROUBLESHOOTING.md with solutions to common issues
- Added SELECTORS.md as a centralized selector database
- Created auto-recovery.sh script for automated test failure recovery
- Enhanced overall testing framework resilience

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-05-21 20:48:10 -03:00

403 lines
No EOL
12 KiB
Bash
Executable file

#!/bin/bash
# auto-recovery.sh - Script to automatically recover from common test failures
# Usage: ./bin/auto-recovery.sh [--ci] [--force]
set -e
# Colors for output
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
RED='\033[0;31m'
NC='\033[0m' # No Color
# Parse arguments
CI_MODE=false
FORCE_MODE=false
for arg in "$@"; do
case $arg in
--ci)
CI_MODE=true
shift
;;
--force)
FORCE_MODE=true
shift
;;
esac
done
echo -e "${GREEN}=== Automated Recovery Tool ===${NC}"
echo "Attempting to recover from common test failures..."
# Check if we're in the right directory
if [ ! -d "tests/e2e" ]; then
echo -e "${RED}Error: Please run this script from the wordpress-dev directory${NC}"
exit 1
fi
# Create logs directory
mkdir -p logs/recovery
# Log function
log() {
echo "[$(date +"%Y-%m-%d %H:%M:%S")] $1" >> logs/recovery/recovery-$(date +"%Y%m%d").log
echo "$1"
}
# Function to check if a command exists
command_exists() {
command -v "$1" >/dev/null 2>&1
}
# Function to run a recovery action
run_recovery() {
local action_name=$1
local action_command=$2
log "Running recovery action: ${action_name}"
if eval "${action_command}"; then
log "${GREEN}✓ Recovery successful: ${action_name}${NC}"
return 0
else
log "${RED}✗ Recovery failed: ${action_name}${NC}"
return 1
fi
}
# Check the test results to determine what failed
check_test_failures() {
log "Analyzing test failures..."
# Check for login failures
if grep -q "LoginPage" test-results/*.txt 2>/dev/null || \
grep -q "Error: locator.fill: Test timeout" test-results/*.txt 2>/dev/null; then
log "${YELLOW}Detected login page failures${NC}"
RECOVERY_ACTIONS+=(fix_login_selectors)
RECOVERY_ACTIONS+=(reset_test_users)
fi
# Check for certificate failures
if grep -q "CertificatePage" test-results/*.txt 2>/dev/null || \
grep -q "certificate" test-results/*.txt 2>/dev/null; then
log "${YELLOW}Detected certificate system failures${NC}"
RECOVERY_ACTIONS+=(fix_certificate_system)
RECOVERY_ACTIONS+=(regenerate_certificate_data)
fi
# Check for plugin activation failures
if grep -q "plugin" test-results/*.txt 2>/dev/null || \
grep -q "activation" test-results/*.txt 2>/dev/null; then
log "${YELLOW}Detected plugin activation failures${NC}"
RECOVERY_ACTIONS+=(reset_plugin)
fi
# Check for dashboard failures
if grep -q "DashboardPage" test-results/*.txt 2>/dev/null || \
grep -q "dashboard" test-results/*.txt 2>/dev/null; then
log "${YELLOW}Detected dashboard failures${NC}"
RECOVERY_ACTIONS+=(reset_events)
fi
# If no specific failures detected or force mode, run all recovery actions
if [ ${#RECOVERY_ACTIONS[@]} -eq 0 ] || [ "$FORCE_MODE" = true ]; then
log "${YELLOW}Running all recovery actions (force mode or general failure)${NC}"
RECOVERY_ACTIONS=(fix_login_selectors reset_test_users fix_certificate_system regenerate_certificate_data reset_plugin reset_events)
fi
}
# Recovery actions
fix_login_selectors() {
log "Fixing login selectors..."
# Run login debug script
npx playwright test tests/e2e/debug-login-page.spec.ts --config=playwright.config.ts || true
# Create backup of LoginPage.ts
cp tests/e2e/pages/LoginPage.ts tests/e2e/pages/LoginPage.ts.bak
# Update selectors based on debug output
cat > tests/e2e/pages/LoginPage.ts << 'EOF'
import { Page, expect } from '@playwright/test';
import { BasePage } from './BasePage';
import { PATHS } from '../config/staging-config';
/**
* Page object representing the login page
*/
export class LoginPage extends BasePage {
// Login form elements based on debug analysis
private readonly usernameInput = 'input[name="log"]';
private readonly passwordInput = 'input[name="pwd"]';
private readonly loginButton = 'input[type="submit"]';
private readonly rememberMeCheckbox = 'input[name="rememberme"]';
private readonly loginError = '.login-error, .login_error, #login_error, .notice-error, .woocommerce-error, .wp-die-message';
private readonly forgotPasswordLink = 'a:text("Lost your password?")';
private readonly loginForm = 'form#hvac_community_loginform';
constructor(page: Page) {
super(page);
}
/**
* Navigate to the login page
*/
async navigate(): Promise<void> {
this.log('Navigating to login page');
await this.page.goto(PATHS.login);
await this.page.waitForLoadState('networkidle');
// Make sure the form is visible before proceeding
await this.page.waitForSelector(this.loginForm, { timeout: 10000 });
}
/**
* Alternative name for navigate for backward compatibility
*/
async navigateToLogin(): Promise<void> {
await this.navigate();
}
/**
* Login with provided credentials
* @param username Username or email
* @param password Password
*/
async login(username: string, password: string): Promise<void> {
this.log(`Logging in as ${username}`);
// Wait for form elements to be ready
await this.page.waitForSelector(this.usernameInput, { state: 'visible', timeout: 10000 });
await this.page.waitForSelector(this.passwordInput, { state: 'visible', timeout: 5000 });
await this.page.waitForSelector(this.loginButton, { state: 'visible', timeout: 5000 });
// Fill in the credentials
await this.page.fill(this.usernameInput, username);
await this.page.fill(this.passwordInput, password);
// Click login and wait for navigation
await this.page.click(this.loginButton);
await this.page.waitForLoadState('networkidle');
this.log('Login form submitted');
}
/**
* Check if we're logged in
*/
async isLoggedIn(): Promise<boolean> {
await this.page.waitForLoadState('networkidle');
const url = await this.getUrl();
return url.includes('hvac-dashboard');
}
/**
* Check if username field is visible
*/
async isUsernameFieldVisible(): Promise<boolean> {
try {
await this.page.waitForSelector(this.usernameInput, { state: 'visible', timeout: 5000 });
return true;
} catch (error) {
return false;
}
}
/**
* Get error message if login failed
*/
async getErrorMessage(): Promise<string | null> {
// Check all possible error selectors
const errorSelectors = this.loginError.split(', ');
for (const selector of errorSelectors) {
if (await this.page.isVisible(selector)) {
return await this.page.textContent(selector);
}
}
// Check for any text containing common error messages
const pageContent = await this.page.content();
if (pageContent.includes('Invalid username') ||
pageContent.includes('incorrect password') ||
pageContent.includes('Unknown username') ||
pageContent.includes('Error:')) {
// Try to find error message in the page content
const errorText = await this.page.evaluate(() => {
const errorElements = Array.from(document.querySelectorAll('p, div, span'))
.filter(el => el.textContent &&
(el.textContent.includes('Invalid') ||
el.textContent.includes('Error') ||
el.textContent.includes('incorrect') ||
el.textContent.includes('Unknown')));
return errorElements.length > 0 ? errorElements[0].textContent : null;
});
return errorText;
}
return null;
}
/**
* Click on "forgot password" link
*/
async clickForgotPassword(): Promise<void> {
await this.page.click(this.forgotPasswordLink);
await this.page.waitForLoadState('networkidle');
}
/**
* Toggle "remember me" checkbox
* @param check If true, check the box; if false, uncheck it
*/
async setRememberMe(check: boolean): Promise<void> {
const isChecked = await this.page.isChecked(this.rememberMeCheckbox);
if (check !== isChecked) {
await this.page.click(this.rememberMeCheckbox);
}
}
}
EOF
log "${GREEN}Updated LoginPage.ts with robust selectors${NC}"
return 0
}
reset_test_users() {
log "Resetting test users..."
if [ -f "bin/create-test-users.sh" ]; then
bash bin/create-test-users.sh
return $?
else
log "${YELLOW}create-test-users.sh not found, creating default test users${NC}"
# Create a basic script to create test users
cat > bin/create-test-users.sh << 'EOF'
#!/bin/bash
# Create test users for E2E testing
# Create test_trainer user
ssh user@staging-server "cd /path/to/wordpress && wp user create test_trainer test_trainer@example.com --role=subscriber --user_pass=Test123! || wp user update test_trainer --role=subscriber --user_pass=Test123!"
# Create admin_trainer user
ssh user@staging-server "cd /path/to/wordpress && wp user create admin_trainer admin_trainer@example.com --role=administrator --user_pass=Admin123! || wp user update admin_trainer --role=administrator --user_pass=Admin123!"
echo "Test users created/updated successfully"
EOF
chmod +x bin/create-test-users.sh
log "${YELLOW}Created create-test-users.sh script, please update SSH credentials before using${NC}"
return 1
fi
}
fix_certificate_system() {
log "Fixing certificate system..."
if [ -f "bin/fix-certificate-system.sh" ]; then
bash bin/fix-certificate-system.sh
return $?
else
log "${YELLOW}fix-certificate-system.sh not found, skipping${NC}"
return 1
fi
}
regenerate_certificate_data() {
log "Regenerating certificate test data..."
if [ -f "bin/create-test-data-with-checkins.sh" ]; then
bash bin/create-test-data-with-checkins.sh
return $?
else
log "${YELLOW}create-test-data-with-checkins.sh not found, skipping${NC}"
return 1
fi
}
reset_plugin() {
log "Resetting plugin state..."
if [ -f "bin/reset-plugin-state.sh" ]; then
bash bin/reset-plugin-state.sh
return $?
else
log "${YELLOW}reset-plugin-state.sh not found, creating default script${NC}"
# Create a basic script to reset plugin state
cat > bin/reset-plugin-state.sh << 'EOF'
#!/bin/bash
# Reset plugin state by deactivating and reactivating
# Deactivate plugin
ssh user@staging-server "cd /path/to/wordpress && wp plugin deactivate hvac-community-events"
# Wait a moment
sleep 2
# Reactivate plugin
ssh user@staging-server "cd /path/to/wordpress && wp plugin activate hvac-community-events"
echo "Plugin reset successfully"
EOF
chmod +x bin/reset-plugin-state.sh
log "${YELLOW}Created reset-plugin-state.sh script, please update SSH credentials before using${NC}"
return 1
fi
}
reset_events() {
log "Resetting test events..."
if [ -f "bin/create-test-events-admin.sh" ]; then
bash bin/create-test-events-admin.sh
return $?
else
log "${YELLOW}create-test-events-admin.sh not found, skipping${NC}"
return 1
fi
}
# Main recovery logic
RECOVERY_ACTIONS=()
SUCCESSES=0
FAILURES=0
# Check for test failures
check_test_failures
# Run recovery actions
log "Running ${#RECOVERY_ACTIONS[@]} recovery actions..."
for action in "${RECOVERY_ACTIONS[@]}"; do
if run_recovery "$action" "$action"; then
SUCCESSES=$((SUCCESSES + 1))
else
FAILURES=$((FAILURES + 1))
fi
done
# Summary
log "\n${GREEN}=== Recovery Summary ===${NC}"
if [ $FAILURES -eq 0 ]; then
log "${GREEN}✓ All recovery actions completed successfully${NC}"
log "Total: ${#RECOVERY_ACTIONS[@]}, Succeeded: ${SUCCESSES}, Failed: ${FAILURES}"
exit 0
else
log "${YELLOW}⚠ Some recovery actions failed${NC}"
log "Total: ${#RECOVERY_ACTIONS[@]}, Succeeded: ${SUCCESSES}, Failed: ${FAILURES}"
if [ "$CI_MODE" = true ]; then
log "${RED}CI mode enabled, failing build due to recovery failures${NC}"
exit 1
fi
log "Please check the logs and try manual recovery for failed actions"
exit 1
fi