#!/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 { 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 { await this.navigate(); } /** * Login with provided credentials * @param username Username or email * @param password Password */ async login(username: string, password: string): Promise { 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 { await this.page.waitForLoadState('networkidle'); const url = await this.getUrl(); return url.includes('hvac-dashboard'); } /** * Check if username field is visible */ async isUsernameFieldVisible(): Promise { 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 { // 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 { 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 { 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