- 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>
403 lines
No EOL
12 KiB
Bash
Executable file
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 |